mirror of
https://github.com/hcengineering/platform.git
synced 2025-05-03 05:43:24 +00:00
Fillable templates (#2667)
Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
parent
a088b51e24
commit
af09bb3ead
@ -39,6 +39,7 @@
|
||||
"@hcengineering/contact": "^0.6.11",
|
||||
"@hcengineering/contact-resources": "^0.6.0",
|
||||
"@hcengineering/view": "^0.6.2",
|
||||
"cross-fetch": "^3.1.5"
|
||||
"cross-fetch": "^3.1.5",
|
||||
"@hcengineering/templates": "^0.6.0"
|
||||
}
|
||||
}
|
||||
|
@ -55,6 +55,7 @@ import workbench from '@hcengineering/model-workbench'
|
||||
import type { Asset, IntlString, Resource } from '@hcengineering/platform'
|
||||
import setting from '@hcengineering/setting'
|
||||
import { AnyComponent } from '@hcengineering/ui'
|
||||
import templates from '@hcengineering/templates'
|
||||
import contact from './plugin'
|
||||
|
||||
export const DOMAIN_CONTACT = 'contact' as Domain
|
||||
@ -576,6 +577,57 @@ export function createModel (builder: Builder): void {
|
||||
},
|
||||
contact.filter.FilterChannelNin
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
templates.class.TemplateFieldCategory,
|
||||
core.space.Model,
|
||||
{
|
||||
label: contact.string.CurrentEmployee
|
||||
},
|
||||
contact.templateFieldCategory.CurrentEmployee
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
templates.class.TemplateField,
|
||||
core.space.Model,
|
||||
{
|
||||
label: contact.string.Name,
|
||||
category: contact.templateFieldCategory.CurrentEmployee,
|
||||
func: contact.function.GetCurrentEmployeeName
|
||||
},
|
||||
contact.templateField.CurrentEmployeeName
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
templates.class.TemplateField,
|
||||
core.space.Model,
|
||||
{
|
||||
label: contact.string.Email,
|
||||
category: contact.templateFieldCategory.CurrentEmployee,
|
||||
func: contact.function.GetCurrentEmployeeEmail
|
||||
},
|
||||
contact.templateField.CurrentEmployeeEmail
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
templates.class.TemplateFieldCategory,
|
||||
core.space.Model,
|
||||
{
|
||||
label: contact.string.Contact
|
||||
},
|
||||
contact.templateFieldCategory.Contact
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
templates.class.TemplateField,
|
||||
core.space.Model,
|
||||
{
|
||||
label: contact.string.Name,
|
||||
category: contact.templateFieldCategory.Contact,
|
||||
func: contact.function.GetContactName
|
||||
},
|
||||
contact.templateField.ContactName
|
||||
)
|
||||
}
|
||||
|
||||
export { contactOperation } from './migration'
|
||||
|
@ -19,6 +19,7 @@ import type { Ref } from '@hcengineering/core'
|
||||
import {} from '@hcengineering/core'
|
||||
import { ObjectSearchCategory, ObjectSearchFactory } from '@hcengineering/model-presentation'
|
||||
import { IntlString, mergeIds, Resource } from '@hcengineering/platform'
|
||||
import { TemplateFieldFunc } from '@hcengineering/templates'
|
||||
import type { AnyComponent } from '@hcengineering/ui'
|
||||
import { Action, ActionCategory, ViewAction } from '@hcengineering/view'
|
||||
|
||||
@ -75,7 +76,8 @@ export default mergeIds(contactId, contact, {
|
||||
Birthday: '' as IntlString,
|
||||
CreatedOn: '' as IntlString,
|
||||
Whatsapp: '' as IntlString,
|
||||
WhatsappPlaceholder: '' as IntlString
|
||||
WhatsappPlaceholder: '' as IntlString,
|
||||
CurrentEmployee: '' as IntlString
|
||||
},
|
||||
completion: {
|
||||
PersonQuery: '' as Resource<ObjectSearchFactory>,
|
||||
@ -94,5 +96,10 @@ export default mergeIds(contactId, contact, {
|
||||
actionImpl: {
|
||||
KickEmployee: '' as ViewAction,
|
||||
OpenChannel: '' as ViewAction
|
||||
},
|
||||
function: {
|
||||
GetCurrentEmployeeName: '' as Resource<TemplateFieldFunc>,
|
||||
GetCurrentEmployeeEmail: '' as Resource<TemplateFieldFunc>,
|
||||
GetContactName: '' as Resource<TemplateFieldFunc>
|
||||
}
|
||||
})
|
||||
|
@ -36,6 +36,7 @@
|
||||
"@hcengineering/model-view": "^0.6.0",
|
||||
"@hcengineering/model-workbench": "^0.6.1",
|
||||
"@hcengineering/task": "^0.6.1",
|
||||
"@hcengineering/templates": "^0.6.0",
|
||||
"@hcengineering/activity": "^0.6.0"
|
||||
}
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ import {
|
||||
} from '@hcengineering/setting'
|
||||
import task from '@hcengineering/task'
|
||||
import setting from './plugin'
|
||||
import templates from '@hcengineering/templates'
|
||||
|
||||
import workbench from '@hcengineering/model-workbench'
|
||||
import { AnyComponent } from '@hcengineering/ui'
|
||||
@ -411,6 +412,37 @@ export function createModel (builder: Builder): void {
|
||||
group: 'edit'
|
||||
}
|
||||
})
|
||||
|
||||
builder.createDoc(
|
||||
templates.class.TemplateFieldCategory,
|
||||
core.space.Model,
|
||||
{
|
||||
label: setting.string.Integrations
|
||||
},
|
||||
setting.templateFieldCategory.Integration
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
templates.class.TemplateField,
|
||||
core.space.Model,
|
||||
{
|
||||
label: setting.string.IntegrationWith,
|
||||
category: setting.templateFieldCategory.Integration,
|
||||
func: setting.function.GetValue
|
||||
},
|
||||
setting.templateField.Value
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
templates.class.TemplateField,
|
||||
core.space.Model,
|
||||
{
|
||||
label: setting.string.Owner,
|
||||
category: setting.templateFieldCategory.Integration,
|
||||
func: setting.function.GetOwnerName
|
||||
},
|
||||
setting.templateField.OwnerName
|
||||
)
|
||||
}
|
||||
|
||||
export { settingOperation } from './migration'
|
||||
|
@ -15,11 +15,12 @@
|
||||
|
||||
import type { TxViewlet } from '@hcengineering/activity'
|
||||
import { Doc, Ref } from '@hcengineering/core'
|
||||
import { mergeIds } from '@hcengineering/platform'
|
||||
import { IntlString, mergeIds, Resource } from '@hcengineering/platform'
|
||||
import { settingId } from '@hcengineering/setting'
|
||||
import setting from '@hcengineering/setting-resources/src/plugin'
|
||||
import { AnyComponent } from '@hcengineering/ui'
|
||||
import { Action, ActionCategory, ViewAction } from '@hcengineering/view'
|
||||
import { TemplateFieldFunc } from '@hcengineering/templates'
|
||||
|
||||
export default mergeIds(settingId, setting, {
|
||||
activity: {
|
||||
@ -52,5 +53,12 @@ export default mergeIds(settingId, setting, {
|
||||
},
|
||||
actionImpl: {
|
||||
DeleteMixin: '' as ViewAction<Record<string, any>>
|
||||
},
|
||||
string: {
|
||||
Value: '' as IntlString
|
||||
},
|
||||
function: {
|
||||
GetValue: '' as Resource<TemplateFieldFunc>,
|
||||
GetOwnerName: '' as Resource<TemplateFieldFunc>
|
||||
}
|
||||
})
|
||||
|
@ -14,12 +14,13 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { Domain, IndexKind } from '@hcengineering/core'
|
||||
import { Domain, DOMAIN_MODEL, IndexKind, Ref } from '@hcengineering/core'
|
||||
import { Builder, Index, Model, Prop, TypeString } from '@hcengineering/model'
|
||||
import core, { TDoc } from '@hcengineering/model-core'
|
||||
import textEditor from '@hcengineering/model-text-editor'
|
||||
import { IntlString, Resource } from '@hcengineering/platform'
|
||||
import setting from '@hcengineering/setting'
|
||||
import type { MessageTemplate } from '@hcengineering/templates'
|
||||
import type { MessageTemplate, TemplateField, TemplateFieldCategory, TemplateFieldFunc } from '@hcengineering/templates'
|
||||
import templates from './plugin'
|
||||
|
||||
export const DOMAIN_TEMPLATES = 'templates' as Domain
|
||||
@ -35,8 +36,20 @@ export class TMessageTemplate extends TDoc implements MessageTemplate {
|
||||
message!: string
|
||||
}
|
||||
|
||||
@Model(templates.class.TemplateFieldCategory, core.class.Doc, DOMAIN_MODEL)
|
||||
export class TTemplateFieldCategory extends TDoc implements TemplateFieldCategory {
|
||||
label!: IntlString
|
||||
}
|
||||
|
||||
@Model(templates.class.TemplateField, core.class.Doc, DOMAIN_MODEL)
|
||||
export class TTemplateField extends TDoc implements TemplateField {
|
||||
category!: Ref<TemplateFieldCategory>
|
||||
label!: IntlString
|
||||
func!: Resource<TemplateFieldFunc>
|
||||
}
|
||||
|
||||
export function createModel (builder: Builder): void {
|
||||
builder.createModel(TMessageTemplate)
|
||||
builder.createModel(TMessageTemplate, TTemplateFieldCategory, TTemplateField)
|
||||
|
||||
builder.createDoc(
|
||||
setting.class.WorkspaceSettingCategory,
|
||||
|
@ -13,47 +13,46 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { IntlString, getEmbeddedLabel } from '@hcengineering/platform'
|
||||
import { Asset, getEmbeddedLabel, getResource, IntlString } from '@hcengineering/platform'
|
||||
import presentation, { getClient } from '@hcengineering/presentation'
|
||||
import {
|
||||
AnySvelteComponent,
|
||||
getEventPositionElement,
|
||||
IconSize,
|
||||
Scroller,
|
||||
showPopup,
|
||||
SelectPopup,
|
||||
AnySvelteComponent,
|
||||
getEventPositionElement
|
||||
showPopup
|
||||
} from '@hcengineering/ui'
|
||||
import { Level } from '@tiptap/extension-heading'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import textEditorPlugin from '../plugin'
|
||||
import { FormatMode, FORMAT_MODES, RefInputAction, RefInputActionItem, TextEditorHandler } from '../types'
|
||||
import EmojiPopup from './EmojiPopup.svelte'
|
||||
import Emoji from './icons/Emoji.svelte'
|
||||
import TextStyle from './icons/TextStyle.svelte'
|
||||
import TextEditor from './TextEditor.svelte'
|
||||
import { Asset } from '@hcengineering/platform'
|
||||
import { FormatMode, FORMAT_MODES, RefInputAction, TextEditorHandler } from '../types'
|
||||
import { headingLevels, mInsertTable } from './extensions'
|
||||
import { Level } from '@tiptap/extension-heading'
|
||||
import presentation from '@hcengineering/presentation'
|
||||
import Header from './icons/Header.svelte'
|
||||
import IconTable from './icons/IconTable.svelte'
|
||||
import Attach from './icons/Attach.svelte'
|
||||
import Bold from './icons/Bold.svelte'
|
||||
import Code from './icons/Code.svelte'
|
||||
import CodeBlock from './icons/CodeBlock.svelte'
|
||||
import Emoji from './icons/Emoji.svelte'
|
||||
import Header from './icons/Header.svelte'
|
||||
import IconTable from './icons/IconTable.svelte'
|
||||
import Italic from './icons/Italic.svelte'
|
||||
import Link from './icons/Link.svelte'
|
||||
import ListBullet from './icons/ListBullet.svelte'
|
||||
import ListNumber from './icons/ListNumber.svelte'
|
||||
import Quote from './icons/Quote.svelte'
|
||||
import Strikethrough from './icons/Strikethrough.svelte'
|
||||
import AddColAfter from './icons/table/AddColAfter.svelte'
|
||||
import AddColBefore from './icons/table/AddColBefore.svelte'
|
||||
import AddRowAfter from './icons/table/AddRowAfter.svelte'
|
||||
import AddRowBefore from './icons/table/AddRowBefore.svelte'
|
||||
import DeleteCol from './icons/table/DeleteCol.svelte'
|
||||
import DeleteRow from './icons/table/DeleteRow.svelte'
|
||||
import DeleteTable from './icons/table/DeleteTable.svelte'
|
||||
import TextStyle from './icons/TextStyle.svelte'
|
||||
import LinkPopup from './LinkPopup.svelte'
|
||||
import StyleButton from './StyleButton.svelte'
|
||||
import AddRowBefore from './icons/table/AddRowBefore.svelte'
|
||||
import AddRowAfter from './icons/table/AddRowAfter.svelte'
|
||||
import AddColBefore from './icons/table/AddColBefore.svelte'
|
||||
import AddColAfter from './icons/table/AddColAfter.svelte'
|
||||
import DeleteRow from './icons/table/DeleteRow.svelte'
|
||||
import DeleteCol from './icons/table/DeleteCol.svelte'
|
||||
import DeleteTable from './icons/table/DeleteTable.svelte'
|
||||
import TextEditor from './TextEditor.svelte'
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
@ -68,6 +67,7 @@
|
||||
export let withoutTopBorder = false
|
||||
export let enableFormatting = false
|
||||
export let autofocus = false
|
||||
export let full = false
|
||||
|
||||
let textEditor: TextEditor
|
||||
|
||||
@ -92,6 +92,9 @@
|
||||
export function isEmptyContent (): boolean {
|
||||
return textEditor.isEmptyContent()
|
||||
}
|
||||
export function insertText (text: string): void {
|
||||
textEditor.insertText(text)
|
||||
}
|
||||
|
||||
$: varsStyle =
|
||||
maxHeight === 'card'
|
||||
@ -113,8 +116,7 @@
|
||||
order: number
|
||||
hidden?: boolean
|
||||
}
|
||||
let defActions: RefAction[]
|
||||
$: defActions = [
|
||||
const defActions: RefAction[] = [
|
||||
{
|
||||
label: textEditorPlugin.string.Attach,
|
||||
icon: Attach,
|
||||
@ -159,6 +161,21 @@
|
||||
// }
|
||||
]
|
||||
|
||||
const client = getClient()
|
||||
let actions: RefAction[] = []
|
||||
client.findAll<RefInputActionItem>(textEditorPlugin.class.RefInputActionItem, {}).then(async (res) => {
|
||||
const cont: RefAction[] = []
|
||||
for (const r of res) {
|
||||
cont.push({
|
||||
label: r.label,
|
||||
icon: r.icon,
|
||||
order: r.order ?? 10000,
|
||||
action: await getResource(r.action)
|
||||
})
|
||||
}
|
||||
actions = defActions.concat(...cont).sort((a, b) => a.order - b.order)
|
||||
})
|
||||
|
||||
function updateFormattingState () {
|
||||
activeModes = new Set(FORMAT_MODES.filter(textEditor.checkIsActive))
|
||||
for (const l of headingLevels) {
|
||||
@ -361,7 +378,12 @@
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div class="ref-container clear-mins" tabindex="-1" on:click|preventDefault|stopPropagation={() => (needFocus = true)}>
|
||||
<div
|
||||
class="ref-container clear-mins"
|
||||
class:h-full={full}
|
||||
tabindex="-1"
|
||||
on:click|preventDefault|stopPropagation={() => (needFocus = true)}
|
||||
>
|
||||
{#if isFormatting}
|
||||
<div class="formatPanel buttons-group xsmall-gap mb-4" class:withoutTopBorder>
|
||||
<StyleButton
|
||||
@ -497,26 +519,19 @@
|
||||
</div>
|
||||
</div>
|
||||
{#if showButtons}
|
||||
{#if $$slots.right}
|
||||
<div class="flex-between">
|
||||
<div class="buttons-group xsmall-gap mt-4">
|
||||
{#each defActions.filter((it) => it.hidden === undefined || it.hidden === false) as a}
|
||||
<StyleButton icon={a.icon} size={buttonSize} on:click={(evt) => handleAction(a, evt)} />
|
||||
{/each}
|
||||
<slot />
|
||||
</div>
|
||||
<div class="buttons-group xsmall-gap mt-4">
|
||||
<slot name="right" />
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex-between">
|
||||
<div class="buttons-group xsmall-gap mt-4">
|
||||
{#each defActions.filter((it) => it.hidden === undefined || it.hidden === false) as a}
|
||||
{#each actions.filter((it) => it.hidden !== true) as a}
|
||||
<StyleButton icon={a.icon} size={buttonSize} on:click={(evt) => handleAction(a, evt)} />
|
||||
{/each}
|
||||
<slot />
|
||||
</div>
|
||||
{/if}
|
||||
{#if $$slots.right}
|
||||
<div class="buttons-group xsmall-gap mt-4">
|
||||
<slot name="right" />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
@ -76,6 +76,7 @@
|
||||
"NotSpecified": "Not specified",
|
||||
"CreatedOn": "Created",
|
||||
"Whatsapp": "Whatsapp",
|
||||
"WhatsappPlaceholder": "Whatsapp"
|
||||
"WhatsappPlaceholder": "Whatsapp",
|
||||
"CurrentEmployee": "Current employee"
|
||||
}
|
||||
}
|
@ -76,6 +76,7 @@
|
||||
"NotSpecified": "Не указан",
|
||||
"CreatedOn": "Создан",
|
||||
"Whatsapp": "Whatsapp",
|
||||
"WhatsappPlaceholder": "Whatsapp"
|
||||
"WhatsappPlaceholder": "Whatsapp",
|
||||
"CurrentEmployee": "Текущий сотрудник"
|
||||
}
|
||||
}
|
@ -45,6 +45,7 @@
|
||||
"@hcengineering/panel": "^0.6.0",
|
||||
"@hcengineering/view-resources": "^0.6.0",
|
||||
"@hcengineering/attachment": "^0.6.1",
|
||||
"@hcengineering/login-resources": "^0.6.2"
|
||||
"@hcengineering/login-resources": "^0.6.2",
|
||||
"@hcengineering/templates": "^0.6.0"
|
||||
}
|
||||
}
|
||||
|
@ -56,7 +56,14 @@ import PersonRefPresenter from './components/PersonRefPresenter.svelte'
|
||||
import EmployeeRefPresenter from './components/EmployeeRefPresenter.svelte'
|
||||
import ChannelFilter from './components/ChannelFilter.svelte'
|
||||
import contact from './plugin'
|
||||
import { employeeSort, filterChannelInResult, filterChannelNinResult } from './utils'
|
||||
import {
|
||||
employeeSort,
|
||||
filterChannelInResult,
|
||||
filterChannelNinResult,
|
||||
getContactName,
|
||||
getCurrentEmployeeEmail,
|
||||
getCurrentEmployeeName
|
||||
} from './utils'
|
||||
|
||||
export {
|
||||
Channels,
|
||||
@ -192,6 +199,9 @@ export default async (): Promise<Resources> => ({
|
||||
GetColorUrl: (uri: string) => uri,
|
||||
EmployeeSort: employeeSort,
|
||||
FilterChannelInResult: filterChannelInResult,
|
||||
FilterChannelNinResult: filterChannelNinResult
|
||||
FilterChannelNinResult: filterChannelNinResult,
|
||||
GetCurrentEmployeeName: getCurrentEmployeeName,
|
||||
GetCurrentEmployeeEmail: getCurrentEmployeeEmail,
|
||||
GetContactName: getContactName
|
||||
}
|
||||
})
|
||||
|
@ -14,11 +14,13 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import contact, { ChannelProvider, Employee, formatName } from '@hcengineering/contact'
|
||||
import { Doc, ObjQueryType, Ref, Timestamp, toIdMap } from '@hcengineering/core'
|
||||
import { ChannelProvider, Contact, Employee, EmployeeAccount, formatName } from '@hcengineering/contact'
|
||||
import { Doc, getCurrentAccount, ObjQueryType, Ref, Timestamp, toIdMap } from '@hcengineering/core'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { TemplateDataProvider } from '@hcengineering/templates'
|
||||
import view, { Filter } from '@hcengineering/view'
|
||||
import { FilterQuery } from '@hcengineering/view-resources'
|
||||
import contact from './plugin'
|
||||
|
||||
const client = getClient()
|
||||
const channelProviders = client.findAll(contact.class.ChannelProvider, {})
|
||||
@ -107,3 +109,25 @@ export async function getRefs (filter: Filter, onUpdate: () => void): Promise<Ar
|
||||
})
|
||||
return await promise
|
||||
}
|
||||
|
||||
export async function getCurrentEmployeeName (): Promise<string> {
|
||||
const me = getCurrentAccount() as EmployeeAccount
|
||||
return formatName(me.name)
|
||||
}
|
||||
|
||||
export async function getCurrentEmployeeEmail (): Promise<string> {
|
||||
const me = getCurrentAccount() as EmployeeAccount
|
||||
return me.email
|
||||
}
|
||||
|
||||
export async function getContactName (provider: TemplateDataProvider): Promise<string | undefined> {
|
||||
const value = provider.get(contact.templateFieldCategory.Contact) as Contact
|
||||
if (value === undefined) return
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
if (hierarchy.isDerived(value._class, contact.class.Person)) {
|
||||
return formatName(value.name)
|
||||
} else {
|
||||
return value.name
|
||||
}
|
||||
}
|
||||
|
@ -30,6 +30,7 @@
|
||||
"@hcengineering/platform": "^0.6.8",
|
||||
"@hcengineering/ui": "^0.6.3",
|
||||
"@hcengineering/core": "^0.6.21",
|
||||
"@hcengineering/templates": "^0.6.0",
|
||||
"@hcengineering/view": "^0.6.2",
|
||||
"crypto-js": "^4.1.1"
|
||||
},
|
||||
|
@ -33,6 +33,7 @@ import type { Asset, Plugin, Resource } from '@hcengineering/platform'
|
||||
import { IntlString, plugin } from '@hcengineering/platform'
|
||||
import type { AnyComponent, IconSize } from '@hcengineering/ui'
|
||||
import { FilterMode, ViewAction, Viewlet } from '@hcengineering/view'
|
||||
import { TemplateFieldCategory, TemplateField } from '@hcengineering/templates'
|
||||
|
||||
/**
|
||||
* @public
|
||||
@ -287,6 +288,15 @@ const contactPlugin = plugin(contactId, {
|
||||
filter: {
|
||||
FilterChannelIn: '' as Ref<FilterMode>,
|
||||
FilterChannelNin: '' as Ref<FilterMode>
|
||||
},
|
||||
templateFieldCategory: {
|
||||
CurrentEmployee: '' as Ref<TemplateFieldCategory>,
|
||||
Contact: '' as Ref<TemplateFieldCategory>
|
||||
},
|
||||
templateField: {
|
||||
CurrentEmployeeName: '' as Ref<TemplateField>,
|
||||
CurrentEmployeeEmail: '' as Ref<TemplateField>,
|
||||
ContactName: '' as Ref<TemplateField>
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -47,6 +47,7 @@
|
||||
"@hcengineering/attachment-resources": "^0.6.0",
|
||||
"@hcengineering/login": "^0.6.1",
|
||||
"@hcengineering/core": "^0.6.21",
|
||||
"@hcengineering/panel": "^0.6.0"
|
||||
"@hcengineering/panel": "^0.6.0",
|
||||
"@hcengineering/templates": "^0.6.0"
|
||||
}
|
||||
}
|
||||
|
@ -14,17 +14,15 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import contact, { Channel, Contact, EmployeeAccount, formatName } from '@hcengineering/contact'
|
||||
import { Ref, SortingOrder } from '@hcengineering/core'
|
||||
import { Message, SharedMessage } from '@hcengineering/gmail'
|
||||
import gmail from '../plugin'
|
||||
import { Channel, Contact, EmployeeAccount, formatName } from '@hcengineering/contact'
|
||||
import contact from '@hcengineering/contact'
|
||||
import plugin, { IconShare, Button, Icon, Label, Scroller } from '@hcengineering/ui'
|
||||
import { getCurrentAccount, Ref, SortingOrder, Space } from '@hcengineering/core'
|
||||
import setting from '@hcengineering/setting'
|
||||
import Messages from './Messages.svelte'
|
||||
import { NotificationClientImpl } from '@hcengineering/notification-resources'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import plugin, { Button, Icon, IconShare, Label, Scroller } from '@hcengineering/ui'
|
||||
import gmail from '../plugin'
|
||||
import IconInbox from './icons/Inbox.svelte'
|
||||
import Messages from './Messages.svelte'
|
||||
|
||||
export let object: Contact
|
||||
export let channel: Channel
|
||||
@ -41,8 +39,7 @@
|
||||
|
||||
const messagesQuery = createQuery()
|
||||
const accauntsQuery = createQuery()
|
||||
const settingsQuery = createQuery()
|
||||
const accountId = getCurrentAccount()._id
|
||||
|
||||
const notificationClient = NotificationClientImpl.getClient()
|
||||
|
||||
function updateMessagesQuery (channelId: Ref<Channel>): void {
|
||||
@ -67,13 +64,6 @@
|
||||
})
|
||||
}
|
||||
|
||||
settingsQuery.query(
|
||||
setting.class.Integration,
|
||||
{ type: gmail.integrationType.Gmail, space: accountId as string as Ref<Space> },
|
||||
(res) => {
|
||||
enabled = res.length > 0
|
||||
}
|
||||
)
|
||||
const client = getClient()
|
||||
|
||||
async function share (): Promise<void> {
|
||||
|
@ -15,12 +15,15 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import contact, { Channel, formatName } from '@hcengineering/contact'
|
||||
import { Class, Doc, Ref } from '@hcengineering/core'
|
||||
import { Class, Doc, getCurrentAccount, Ref, Space } from '@hcengineering/core'
|
||||
import { SharedMessage } from '@hcengineering/gmail'
|
||||
import { NotificationClientImpl } from '@hcengineering/notification-resources'
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
import { createQuery } from '@hcengineering/presentation'
|
||||
import setting, { Integration } from '@hcengineering/setting'
|
||||
import templates, { TemplateDataProvider } from '@hcengineering/templates'
|
||||
import { Button, eventToHTMLElement, Icon, Label, Panel, showPopup } from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { createEventDispatcher, onDestroy } from 'svelte'
|
||||
import gmail from '../plugin'
|
||||
import Chats from './Chats.svelte'
|
||||
import Connect from './Connect.svelte'
|
||||
@ -35,7 +38,7 @@
|
||||
let currentMessage: SharedMessage | undefined = undefined
|
||||
let channel: Channel | undefined = undefined
|
||||
const notificationClient = NotificationClientImpl.getClient()
|
||||
let enabled: boolean
|
||||
let integration: Integration | undefined
|
||||
|
||||
const channelQuery = createQuery()
|
||||
const dispatch = createEventDispatcher()
|
||||
@ -71,6 +74,29 @@
|
||||
await notificationClient.updateLastView(channel._id, channel._class, undefined, true)
|
||||
}
|
||||
}
|
||||
|
||||
const settingsQuery = createQuery()
|
||||
const accountId = getCurrentAccount()._id
|
||||
|
||||
let templateProvider: TemplateDataProvider | undefined
|
||||
|
||||
getResource(templates.function.GetTemplateDataProvider).then((p) => {
|
||||
templateProvider = p()
|
||||
})
|
||||
|
||||
onDestroy(() => {
|
||||
templateProvider?.destroy()
|
||||
})
|
||||
|
||||
$: templateProvider && integration && templateProvider.set(setting.templateFieldCategory.Integration, integration)
|
||||
|
||||
settingsQuery.query(
|
||||
setting.class.Integration,
|
||||
{ type: gmail.integrationType.Gmail, space: accountId as string as Ref<Space> },
|
||||
(res) => {
|
||||
integration = res[0]
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
{#if channel && object}
|
||||
@ -97,7 +123,7 @@
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="utils">
|
||||
{#if !enabled}
|
||||
{#if !integration}
|
||||
<Button
|
||||
label={gmail.string.Connect}
|
||||
kind={'primary'}
|
||||
@ -113,7 +139,7 @@
|
||||
{:else if currentMessage}
|
||||
<FullMessage {currentMessage} bind:newMessage on:close={back} />
|
||||
{:else}
|
||||
<Chats {object} {channel} bind:newMessage bind:enabled on:select={selectHandler} />
|
||||
<Chats {object} {channel} bind:newMessage enabled={integration !== undefined} on:select={selectHandler} />
|
||||
{/if}
|
||||
</Panel>
|
||||
{/if}
|
||||
|
@ -15,16 +15,16 @@
|
||||
<script lang="ts">
|
||||
import attachmentP, { Attachment } from '@hcengineering/attachment'
|
||||
import { AttachmentPresenter } from '@hcengineering/attachment-resources'
|
||||
import { Channel, Contact, formatName } from '@hcengineering/contact'
|
||||
import contact, { Channel, Contact, formatName } from '@hcengineering/contact'
|
||||
import { Data, generateId } from '@hcengineering/core'
|
||||
import { NewMessage, SharedMessage } from '@hcengineering/gmail'
|
||||
import { NotificationClientImpl } from '@hcengineering/notification-resources'
|
||||
import { getResource, setPlatformStatus, unknownError } from '@hcengineering/platform'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { TextEditor } from '@hcengineering/text-editor'
|
||||
import { Scroller, IconArrowLeft, IconAttachment, Label } from '@hcengineering/ui'
|
||||
import Button from '@hcengineering/ui/src/components/Button.svelte'
|
||||
import EditBox from '@hcengineering/ui/src/components/EditBox.svelte'
|
||||
import templates, { TemplateDataProvider } from '@hcengineering/templates'
|
||||
import { StyledTextEditor } from '@hcengineering/text-editor'
|
||||
import { Button, EditBox, IconArrowLeft, IconAttachment, Label, Scroller } from '@hcengineering/ui'
|
||||
import { onDestroy } from 'svelte'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import plugin from '../plugin'
|
||||
|
||||
@ -35,7 +35,7 @@
|
||||
const notificationClient = NotificationClientImpl.getClient()
|
||||
let objectId = generateId()
|
||||
|
||||
let editor: TextEditor
|
||||
let editor: StyledTextEditor
|
||||
let copy: string = ''
|
||||
|
||||
const obj: Data<NewMessage> = {
|
||||
@ -46,6 +46,18 @@
|
||||
status: 'new'
|
||||
}
|
||||
|
||||
let templateProvider: TemplateDataProvider | undefined
|
||||
|
||||
getResource(templates.function.GetTemplateDataProvider).then((p) => {
|
||||
templateProvider = p()
|
||||
})
|
||||
|
||||
onDestroy(() => {
|
||||
templateProvider?.destroy()
|
||||
})
|
||||
|
||||
$: templateProvider && templateProvider.set(contact.templateFieldCategory.Contact, object)
|
||||
|
||||
async function sendMsg () {
|
||||
await client.createDoc(
|
||||
plugin.class.NewMessage,
|
||||
@ -202,7 +214,7 @@
|
||||
</div>
|
||||
{/if}
|
||||
<div class="input mt-4 clear-mins">
|
||||
<TextEditor bind:this={editor} bind:content={obj.content} on:blur={editor.submit} />
|
||||
<StyledTextEditor bind:this={editor} full bind:content={obj.content} maxHeight="panel" on:blur={editor.submit} />
|
||||
</div>
|
||||
</div>
|
||||
</Scroller>
|
||||
@ -230,7 +242,8 @@
|
||||
color: #d6d6d6;
|
||||
caret-color: var(--caret-color);
|
||||
min-height: 0;
|
||||
height: calc(100% - 12rem);
|
||||
margin-bottom: 2rem;
|
||||
height: 100%;
|
||||
border-radius: 0.25rem;
|
||||
|
||||
:global(.ProseMirror) {
|
||||
|
@ -16,21 +16,31 @@
|
||||
import attachmentP, { Attachment } from '@hcengineering/attachment'
|
||||
import { AttachmentPresenter } from '@hcengineering/attachment-resources'
|
||||
import contact, { Channel, Contact, formatName } from '@hcengineering/contact'
|
||||
import { generateId, Ref, toIdMap } from '@hcengineering/core'
|
||||
import { generateId, getCurrentAccount, Ref, Space, toIdMap } from '@hcengineering/core'
|
||||
import { NotificationClientImpl } from '@hcengineering/notification-resources'
|
||||
import { getResource, setPlatformStatus, unknownError } from '@hcengineering/platform'
|
||||
import setting, { Integration } from '@hcengineering/setting'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { TextEditor } from '@hcengineering/text-editor'
|
||||
import { Icon, IconAttachment, Label, Panel, Scroller } from '@hcengineering/ui'
|
||||
import Button from '@hcengineering/ui/src/components/Button.svelte'
|
||||
import EditBox from '@hcengineering/ui/src/components/EditBox.svelte'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import {
|
||||
Button,
|
||||
EditBox,
|
||||
eventToHTMLElement,
|
||||
Icon,
|
||||
IconAttachment,
|
||||
Label,
|
||||
Panel,
|
||||
Scroller,
|
||||
showPopup
|
||||
} from '@hcengineering/ui'
|
||||
import { createEventDispatcher, onDestroy } from 'svelte'
|
||||
import plugin from '../plugin'
|
||||
import templates, { TemplateDataProvider } from '@hcengineering/templates'
|
||||
import Connect from './Connect.svelte'
|
||||
import { StyledTextEditor } from '@hcengineering/text-editor'
|
||||
|
||||
export let value: Contact[] | Contact
|
||||
const contacts = Array.isArray(value) ? value : [value]
|
||||
|
||||
console.log(contacts)
|
||||
const contactMap = toIdMap(contacts)
|
||||
|
||||
const query = createQuery()
|
||||
@ -51,17 +61,23 @@
|
||||
|
||||
const attachmentParentId = generateId()
|
||||
|
||||
let editor: TextEditor
|
||||
let editor: StyledTextEditor
|
||||
let subject: string = ''
|
||||
let content: string = ''
|
||||
let copy: string = ''
|
||||
let saved = false
|
||||
|
||||
async function sendMsg () {
|
||||
const templateProvider = (await getResource(templates.function.GetTemplateDataProvider))()
|
||||
if (templateProvider === undefined) return
|
||||
for (const channel of channels) {
|
||||
const target = contacts.find((p) => p._id === channel.attachedTo)
|
||||
if (target === undefined) continue
|
||||
templateProvider.set(contact.templateFieldCategory.Contact, target)
|
||||
const message = await templateProvider.fillTemplate(content)
|
||||
const id = await client.createDoc(plugin.class.NewMessage, plugin.space.Gmail, {
|
||||
subject,
|
||||
content,
|
||||
content: message,
|
||||
to: channel.value,
|
||||
status: 'new',
|
||||
copy: copy
|
||||
@ -86,6 +102,7 @@
|
||||
}
|
||||
)
|
||||
}
|
||||
templateProvider.destroy()
|
||||
}
|
||||
saved = true
|
||||
dispatch('close')
|
||||
@ -166,6 +183,30 @@
|
||||
if (contact === undefined) return channel.value
|
||||
return `${formatName(contact.name)} (${channel.value})`
|
||||
}
|
||||
|
||||
const settingsQuery = createQuery()
|
||||
const accountId = getCurrentAccount()._id
|
||||
|
||||
let templateProvider: TemplateDataProvider | undefined
|
||||
let integration: Integration | undefined
|
||||
|
||||
getResource(templates.function.GetTemplateDataProvider).then((p) => {
|
||||
templateProvider = p()
|
||||
})
|
||||
|
||||
onDestroy(() => {
|
||||
templateProvider?.destroy()
|
||||
})
|
||||
|
||||
$: templateProvider && integration && templateProvider.set(setting.templateFieldCategory.Integration, integration)
|
||||
|
||||
settingsQuery.query(
|
||||
setting.class.Integration,
|
||||
{ type: plugin.integrationType.Gmail, space: accountId as string as Ref<Space> },
|
||||
(res) => {
|
||||
integration = res[0]
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
<Panel
|
||||
@ -189,6 +230,18 @@
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="utils">
|
||||
{#if !integration}
|
||||
<Button
|
||||
label={plugin.string.Connect}
|
||||
kind={'primary'}
|
||||
on:click={(e) => {
|
||||
showPopup(Connect, {}, eventToHTMLElement(e))
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
|
||||
<input
|
||||
bind:this={inputFile}
|
||||
multiple
|
||||
@ -223,13 +276,15 @@
|
||||
inputFile.click()
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
label={plugin.string.Send}
|
||||
size={'small'}
|
||||
kind={'primary'}
|
||||
disabled={channels.length === 0}
|
||||
on:click={sendMsg}
|
||||
/>
|
||||
{#if integration}
|
||||
<Button
|
||||
label={plugin.string.Send}
|
||||
size={'small'}
|
||||
kind={'primary'}
|
||||
disabled={channels.length === 0}
|
||||
on:click={sendMsg}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -262,7 +317,7 @@
|
||||
</div>
|
||||
{/if}
|
||||
<div class="input mt-4 clear-mins">
|
||||
<TextEditor bind:this={editor} bind:content on:blur={editor.submit} />
|
||||
<StyledTextEditor bind:this={editor} full bind:content on:blur={editor.submit} />
|
||||
</div>
|
||||
</div>
|
||||
</Scroller>
|
||||
@ -291,7 +346,8 @@
|
||||
color: #d6d6d6;
|
||||
caret-color: var(--caret-color);
|
||||
min-height: 0;
|
||||
height: calc(100% - 12rem);
|
||||
height: 100%;
|
||||
margin-bottom: 2rem;
|
||||
border-radius: 0.25rem;
|
||||
|
||||
:global(.ProseMirror) {
|
||||
|
@ -17,7 +17,7 @@
|
||||
"Saving": "Saving...",
|
||||
"Saved": "Saved",
|
||||
"Add": "Add",
|
||||
"LearnMore": "Learn more",
|
||||
"Value": "Value",
|
||||
"EnterCurrentPassword": "Enter current password",
|
||||
"EnterNewPassword": "Enter new password",
|
||||
"RepeatNewPassword": "Repeat new password",
|
||||
|
@ -17,7 +17,7 @@
|
||||
"Saving": "Сохранение...",
|
||||
"Saved": "Сохранено",
|
||||
"Add": "Добавить",
|
||||
"LearnMore": "Узнать больше",
|
||||
"Value": "Значение",
|
||||
"EnterCurrentPassword": "Введите текущий пароль",
|
||||
"EnterNewPassword": "Введите новый пароль",
|
||||
"RepeatNewPassword": "Повторите новый пароль",
|
||||
|
@ -47,6 +47,7 @@
|
||||
"@hcengineering/contact-resources": "^0.6.0",
|
||||
"@hcengineering/login": "^0.6.1",
|
||||
"@hcengineering/model": "^0.6.0",
|
||||
"@hcengineering/templates": "^0.6.0",
|
||||
"@hcengineering/workbench": "^0.6.2",
|
||||
"@hcengineering/workbench-resources": "^0.6.1"
|
||||
}
|
||||
|
@ -44,6 +44,7 @@ import StringTypeEditor from './components/typeEditors/StringTypeEditor.svelte'
|
||||
import WorkspaceSettings from './components/WorkspaceSettings.svelte'
|
||||
import InviteSetting from './components/InviteSetting.svelte'
|
||||
import setting from './plugin'
|
||||
import { getOwnerName, getValue } from './utils'
|
||||
|
||||
export { ClassSetting }
|
||||
|
||||
@ -101,5 +102,9 @@ export default async (): Promise<Resources> => ({
|
||||
},
|
||||
actionImpl: {
|
||||
DeleteMixin
|
||||
},
|
||||
function: {
|
||||
GetOwnerName: getOwnerName,
|
||||
GetValue: getValue
|
||||
}
|
||||
})
|
||||
|
@ -1,5 +1,8 @@
|
||||
import contact, { EmployeeAccount, formatName } from '@hcengineering/contact'
|
||||
import { Class, Doc, Hierarchy, Ref } from '@hcengineering/core'
|
||||
import setting from '@hcengineering/setting'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import setting, { Integration } from '@hcengineering/setting'
|
||||
import { TemplateDataProvider } from '@hcengineering/templates'
|
||||
|
||||
function isEditable (hierarchy: Hierarchy, p: Class<Doc>): boolean {
|
||||
let ancestors = [p._id]
|
||||
@ -45,3 +48,19 @@ export function filterDescendants (
|
||||
|
||||
return _classes.map((p) => p._id)
|
||||
}
|
||||
|
||||
export async function getValue (provider: TemplateDataProvider): Promise<string | undefined> {
|
||||
const value = provider.get(setting.templateFieldCategory.Integration) as Integration
|
||||
if (value === undefined) return
|
||||
return value.value
|
||||
}
|
||||
|
||||
export async function getOwnerName (provider: TemplateDataProvider): Promise<string | undefined> {
|
||||
const value = provider.get(setting.templateFieldCategory.Integration) as Integration
|
||||
if (value === undefined) return
|
||||
const client = getClient()
|
||||
const employeeAccount = await client.findOne(contact.class.EmployeeAccount, {
|
||||
_id: value.modifiedBy as Ref<EmployeeAccount>
|
||||
})
|
||||
return employeeAccount != null ? formatName(employeeAccount.name) : undefined
|
||||
}
|
||||
|
@ -28,6 +28,7 @@
|
||||
"dependencies": {
|
||||
"@hcengineering/platform": "^0.6.8",
|
||||
"@hcengineering/core": "^0.6.21",
|
||||
"@hcengineering/templates": "^0.6.0",
|
||||
"@hcengineering/ui": "^0.6.3"
|
||||
},
|
||||
"repository": "https://github.com/hcengineering/anticrm",
|
||||
|
@ -17,6 +17,7 @@ import type { Class, Configuration, Doc, Mixin, Ref, Space } from '@hcengineerin
|
||||
import type { Plugin } from '@hcengineering/platform'
|
||||
import { Asset, IntlString, plugin, Resource } from '@hcengineering/platform'
|
||||
import { AnyComponent } from '@hcengineering/ui'
|
||||
import { TemplateFieldCategory, TemplateField } from '@hcengineering/templates'
|
||||
|
||||
/**
|
||||
* @public
|
||||
@ -145,7 +146,6 @@ export default plugin(settingId, {
|
||||
Delete: '' as IntlString,
|
||||
Disconnect: '' as IntlString,
|
||||
Add: '' as IntlString,
|
||||
LearnMore: '' as IntlString,
|
||||
EditProfile: '' as IntlString,
|
||||
ChangePassword: '' as IntlString,
|
||||
CurrentPassword: '' as IntlString,
|
||||
@ -175,5 +175,12 @@ export default plugin(settingId, {
|
||||
SelectWorkspace: '' as Asset,
|
||||
Clazz: '' as Asset,
|
||||
Enums: '' as Asset
|
||||
},
|
||||
templateFieldCategory: {
|
||||
Integration: '' as Ref<TemplateFieldCategory>
|
||||
},
|
||||
templateField: {
|
||||
OwnerName: '' as Ref<TemplateField>,
|
||||
Value: '' as Ref<TemplateField>
|
||||
}
|
||||
})
|
||||
|
@ -45,6 +45,7 @@
|
||||
"@hcengineering/notification-resources": "^0.6.0",
|
||||
"@hcengineering/attachment": "^0.6.1",
|
||||
"@hcengineering/attachment-resources": "^0.6.0",
|
||||
"@hcengineering/panel": "^0.6.0"
|
||||
"@hcengineering/panel": "^0.6.0",
|
||||
"@hcengineering/templates": "^0.6.0"
|
||||
}
|
||||
}
|
||||
|
@ -29,6 +29,9 @@
|
||||
import TelegramIcon from './icons/Telegram.svelte'
|
||||
import Messages from './Messages.svelte'
|
||||
import Reconnect from './Reconnect.svelte'
|
||||
import templates, { TemplateDataProvider } from '@hcengineering/templates'
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
import { onDestroy } from 'svelte'
|
||||
|
||||
export let _id: Ref<Contact>
|
||||
export let _class: Ref<Class<Contact>>
|
||||
@ -54,6 +57,19 @@
|
||||
}
|
||||
)
|
||||
|
||||
let templateProvider: TemplateDataProvider | undefined
|
||||
|
||||
getResource(templates.function.GetTemplateDataProvider).then((p) => {
|
||||
templateProvider = p()
|
||||
})
|
||||
|
||||
onDestroy(() => {
|
||||
templateProvider?.destroy()
|
||||
})
|
||||
|
||||
$: templateProvider && object && templateProvider.set(contact.templateFieldCategory.Contact, object)
|
||||
$: templateProvider && integration && templateProvider.set(setting.templateFieldCategory.Integration, integration)
|
||||
|
||||
const query = createQuery()
|
||||
$: _id &&
|
||||
_class &&
|
||||
|
@ -11,6 +11,7 @@
|
||||
"SearchTemplate": "search for template",
|
||||
"TemplatePlaceholder": "New template",
|
||||
"Title": "Title",
|
||||
"Message": "Message"
|
||||
"Message": "Message",
|
||||
"Field": "Field"
|
||||
}
|
||||
}
|
@ -11,6 +11,7 @@
|
||||
"SearchTemplate": "Поиск шаблона",
|
||||
"TemplatePlaceholder": "Новый шаблон",
|
||||
"Title": "Заголовок",
|
||||
"Message": "Сообщение"
|
||||
"Message": "Сообщение",
|
||||
"Field": "Поле"
|
||||
}
|
||||
}
|
65
plugins/templates-resources/src/components/FieldPopup.svelte
Normal file
65
plugins/templates-resources/src/components/FieldPopup.svelte
Normal file
@ -0,0 +1,65 @@
|
||||
<!--
|
||||
// Copyright © 2023 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { DocumentQuery, FindOptions, IdMap, toIdMap } from '@hcengineering/core'
|
||||
import type { Asset, IntlString } from '@hcengineering/platform'
|
||||
import presentation, { createQuery, ObjectPopup } from '@hcengineering/presentation'
|
||||
import { TemplateField, TemplateFieldCategory } from '@hcengineering/templates'
|
||||
import { AnySvelteComponent, Label } from '@hcengineering/ui'
|
||||
import templates from '../plugin'
|
||||
|
||||
export let options: FindOptions<TemplateField> | undefined = undefined
|
||||
export let docQuery: DocumentQuery<TemplateField> | undefined = undefined
|
||||
|
||||
export let placeholder: IntlString = presentation.string.Search
|
||||
export let shadows: boolean = true
|
||||
export let icon: Asset | AnySvelteComponent | undefined = undefined
|
||||
|
||||
const query = createQuery()
|
||||
|
||||
let categories: IdMap<TemplateFieldCategory> = new Map()
|
||||
|
||||
query.query(templates.class.TemplateFieldCategory, {}, (res) => {
|
||||
categories = toIdMap(res)
|
||||
})
|
||||
</script>
|
||||
|
||||
<ObjectPopup
|
||||
_class={templates.class.TemplateField}
|
||||
{options}
|
||||
closeAfterSelect
|
||||
{placeholder}
|
||||
searchField={'label'}
|
||||
{docQuery}
|
||||
groupBy={'category'}
|
||||
{shadows}
|
||||
on:update
|
||||
on:close
|
||||
on:changeContent
|
||||
>
|
||||
<svelte:fragment slot="item" let:item={field}>
|
||||
<div class="flex flex-grow overflow-label">
|
||||
<Label label={field.label} />
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="category" let:item={field}>
|
||||
<div class="flex flex-grow overflow-label">
|
||||
<span class="fs-medium flex-center gap-2 mt-2 mb-2 ml-2">
|
||||
<Label label={categories.get(field.category)?.label ?? field.label} />
|
||||
</span>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
</ObjectPopup>
|
@ -19,6 +19,7 @@
|
||||
import { TextEditorHandler } from '@hcengineering/text-editor'
|
||||
import { closePopup, EditWithIcon, IconSearch, Label, deviceOptionsStore } from '@hcengineering/ui'
|
||||
import templates from '../plugin'
|
||||
import { getTemplateDataProvider } from '../utils'
|
||||
|
||||
export let editor: TextEditorHandler
|
||||
let items: MessageTemplate[] = []
|
||||
@ -33,8 +34,10 @@
|
||||
|
||||
let selected = 0
|
||||
|
||||
function dispatchItem (item: MessageTemplate): void {
|
||||
editor.insertText(item.message)
|
||||
const provider = getTemplateDataProvider()
|
||||
async function dispatchItem (item: MessageTemplate): Promise<void> {
|
||||
const message = await provider.fillTemplate(item.message)
|
||||
editor.insertText(message)
|
||||
closePopup()
|
||||
}
|
||||
|
||||
|
@ -3,8 +3,9 @@
|
||||
import { getClient, LiveQuery, MessageViewer } from '@hcengineering/presentation'
|
||||
import { MessageTemplate } from '@hcengineering/templates'
|
||||
import { StyledTextEditor } from '@hcengineering/text-editor'
|
||||
import { Button, CircleButton, EditBox, Icon, IconAdd, Label } from '@hcengineering/ui'
|
||||
import { Button, CircleButton, EditBox, eventToHTMLElement, Icon, IconAdd, Label, showPopup } from '@hcengineering/ui'
|
||||
import templatesPlugin from '../plugin'
|
||||
import FieldPopup from './FieldPopup.svelte'
|
||||
import TemplateElement from './TemplateElement.svelte'
|
||||
|
||||
const client = getClient()
|
||||
@ -69,6 +70,14 @@
|
||||
const updateTemplate = (evt: any) => {
|
||||
newTemplate = { title: newTemplate?.title ?? '', message: evt.detail }
|
||||
}
|
||||
|
||||
function addField (ev: MouseEvent) {
|
||||
showPopup(FieldPopup, {}, eventToHTMLElement(ev), (res) => {
|
||||
if (res !== undefined) {
|
||||
textEditor.insertText(`\${${res._id}}`)
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="antiComponent">
|
||||
@ -132,15 +141,18 @@
|
||||
on:click={saveNewTemplate}
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
label={templatesPlugin.string.Cancel}
|
||||
on:click={() => {
|
||||
if (mode === Mode.Create) {
|
||||
newTemplate = undefined
|
||||
}
|
||||
mode = Mode.View
|
||||
}}
|
||||
/>
|
||||
<div class="ml-2">
|
||||
<Button
|
||||
label={templatesPlugin.string.Cancel}
|
||||
on:click={() => {
|
||||
if (mode === Mode.Create) {
|
||||
newTemplate = undefined
|
||||
}
|
||||
mode = Mode.View
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<Button label={templatesPlugin.string.Field} on:click={addField} />
|
||||
</div>
|
||||
</StyledTextEditor>
|
||||
{:else}
|
||||
|
@ -19,6 +19,7 @@ import Templates from './components/Templates.svelte'
|
||||
import { TextEditorHandler } from '@hcengineering/text-editor'
|
||||
import { showPopup } from '@hcengineering/ui'
|
||||
import TemplatePopup from './components/TemplatePopup.svelte'
|
||||
import { getTemplateDataProvider } from './utils'
|
||||
|
||||
function ShowTemplates (element: HTMLElement, editor: TextEditorHandler): void {
|
||||
showPopup(TemplatePopup, { editor }, element)
|
||||
@ -30,5 +31,8 @@ export default async (): Promise<Resources> => ({
|
||||
},
|
||||
action: {
|
||||
ShowTemplates
|
||||
},
|
||||
function: {
|
||||
GetTemplateDataProvider: getTemplateDataProvider
|
||||
}
|
||||
})
|
||||
|
@ -29,7 +29,8 @@ export default mergeIds(templatesId, templates, {
|
||||
SaveTemplate: '' as IntlString,
|
||||
Suggested: '' as IntlString,
|
||||
SearchTemplate: '' as IntlString,
|
||||
TemplatePlaceholder: '' as IntlString
|
||||
TemplatePlaceholder: '' as IntlString,
|
||||
Field: '' as IntlString
|
||||
},
|
||||
component: {
|
||||
Templates: '' as AnyComponent
|
||||
|
60
plugins/templates-resources/src/utils.ts
Normal file
60
plugins/templates-resources/src/utils.ts
Normal file
@ -0,0 +1,60 @@
|
||||
import { generateId, Ref } from '@hcengineering/core'
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import templates, {
|
||||
TemplateData,
|
||||
TemplateDataProvider,
|
||||
TemplateField,
|
||||
TemplateFieldCategory
|
||||
} from '@hcengineering/templates'
|
||||
|
||||
const fieldRegexp = /\$\{(\S+?)}/gi
|
||||
|
||||
const templateData: Map<Ref<TemplateFieldCategory>, TemplateData[]> = new Map()
|
||||
|
||||
class TemplateDataProviderImpl implements TemplateDataProvider {
|
||||
private readonly id = generateId()
|
||||
|
||||
set (key: Ref<TemplateFieldCategory>, value: any): void {
|
||||
const data = templateData.get(key) ?? []
|
||||
data.unshift({
|
||||
owner: this.id,
|
||||
data: value
|
||||
})
|
||||
templateData.set(key, data)
|
||||
}
|
||||
|
||||
get (key: Ref<TemplateFieldCategory>): any | undefined {
|
||||
const data = templateData.get(key) ?? []
|
||||
const current = data.find((p) => p.owner === this.id)
|
||||
return current?.data ?? data[0]?.data
|
||||
}
|
||||
|
||||
async fillTemplate (message: string): Promise<string> {
|
||||
while (true) {
|
||||
const matched = fieldRegexp.exec(message)
|
||||
if (matched === null) return message
|
||||
const client = getClient()
|
||||
const field = await client.findOne(templates.class.TemplateField, { _id: matched[1] as Ref<TemplateField> })
|
||||
if (field === undefined) continue
|
||||
const f = await getResource(field.func)
|
||||
const result = await f(this)
|
||||
if (result !== undefined) {
|
||||
message = message.replaceAll(matched[0], result)
|
||||
fieldRegexp.lastIndex = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
destroy (): void {
|
||||
for (const key of templateData.keys()) {
|
||||
const data = templateData.get(key) ?? []
|
||||
const res = data.filter((p) => p.owner === this.id)
|
||||
templateData.set(key, res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getTemplateDataProvider (): TemplateDataProviderImpl {
|
||||
return new TemplateDataProviderImpl()
|
||||
}
|
@ -14,7 +14,7 @@
|
||||
//
|
||||
|
||||
import type { Class, Doc, Ref, Space } from '@hcengineering/core'
|
||||
import type { Plugin } from '@hcengineering/platform'
|
||||
import type { IntlString, Plugin, Resource } from '@hcengineering/platform'
|
||||
import { Asset, plugin } from '@hcengineering/platform'
|
||||
|
||||
/**
|
||||
@ -25,6 +25,45 @@ export interface MessageTemplate extends Doc {
|
||||
message: string
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface TemplateData {
|
||||
owner: string
|
||||
data: any
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface TemplateDataProvider {
|
||||
set: (key: Ref<TemplateFieldCategory>, value: any) => void
|
||||
get: (key: Ref<TemplateFieldCategory>) => any | undefined
|
||||
fillTemplate: (message: string) => Promise<string>
|
||||
destroy: () => void
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface TemplateFieldCategory extends Doc {
|
||||
label: IntlString
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export declare type TemplateFieldFunc = (provider: TemplateDataProvider) => Promise<string | undefined>
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface TemplateField extends Doc {
|
||||
category: Ref<TemplateFieldCategory>
|
||||
label: IntlString
|
||||
func: Resource<TemplateFieldFunc>
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
@ -32,7 +71,9 @@ export const templatesId = 'templates' as Plugin
|
||||
|
||||
export default plugin(templatesId, {
|
||||
class: {
|
||||
MessageTemplate: '' as Ref<Class<MessageTemplate>>
|
||||
MessageTemplate: '' as Ref<Class<MessageTemplate>>,
|
||||
TemplateField: '' as Ref<Class<TemplateField>>,
|
||||
TemplateFieldCategory: '' as Ref<Class<TemplateFieldCategory>>
|
||||
},
|
||||
space: {
|
||||
Templates: '' as Ref<Space>
|
||||
@ -40,5 +81,8 @@ export default plugin(templatesId, {
|
||||
icon: {
|
||||
Templates: '' as Asset,
|
||||
Template: '' as Asset
|
||||
},
|
||||
function: {
|
||||
GetTemplateDataProvider: '' as Resource<() => TemplateDataProvider>
|
||||
}
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user