Email tempaltes (#853)

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev 2022-01-24 16:35:58 +07:00 committed by GitHub
parent 390a4b28d1
commit 52baf5444d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
64 changed files with 1816 additions and 125 deletions

5
.vscode/launch.json vendored
View File

@ -13,7 +13,10 @@
"ELASTIC_URL": "http://localhost:9200",
"MONGO_URL": "mongodb://localhost:27017",
"APM_SERVER_URL2": "http://localhost:8200",
"METRICS_CONSOLE": "true" // Show metrics in console evert 30 seconds.
"METRICS_CONSOLE": "true", // Show metrics in console evert 30 seconds.,
"MINIO_ENDPOINT": "localhost",
"MINIO_ACCESS_KEY":"minioadmin",
"MINIO_SECRET_KEY":"minioadmin"
},
"runtimeArgs": ["--nolazy", "-r", "ts-node/register"],
"sourceMaps": true,

View File

@ -64,6 +64,8 @@ specifiers:
'@rush-temp/model-setting': file:./projects/model-setting.tgz
'@rush-temp/model-task': file:./projects/model-task.tgz
'@rush-temp/model-telegram': file:./projects/model-telegram.tgz
'@rush-temp/model-templates': file:./projects/model-templates.tgz
'@rush-temp/model-text-editor': file:./projects/model-text-editor.tgz
'@rush-temp/model-view': file:./projects/model-view.tgz
'@rush-temp/model-workbench': file:./projects/model-workbench.tgz
'@rush-temp/mongo': file:./projects/mongo.tgz
@ -93,6 +95,9 @@ specifiers:
'@rush-temp/telegram': file:./projects/telegram.tgz
'@rush-temp/telegram-assets': file:./projects/telegram-assets.tgz
'@rush-temp/telegram-resources': file:./projects/telegram-resources.tgz
'@rush-temp/templates': file:./projects/templates.tgz
'@rush-temp/templates-assets': file:./projects/templates-assets.tgz
'@rush-temp/templates-resources': file:./projects/templates-resources.tgz
'@rush-temp/text-editor': file:./projects/text-editor.tgz
'@rush-temp/theme': file:./projects/theme.tgz
'@rush-temp/tool': file:./projects/tool.tgz
@ -249,6 +254,8 @@ dependencies:
'@rush-temp/model-setting': file:projects/model-setting.tgz_typescript@4.5.4
'@rush-temp/model-task': file:projects/model-task.tgz_typescript@4.5.4
'@rush-temp/model-telegram': file:projects/model-telegram.tgz_typescript@4.5.4
'@rush-temp/model-templates': file:projects/model-templates.tgz_typescript@4.5.4
'@rush-temp/model-text-editor': file:projects/model-text-editor.tgz_typescript@4.5.4
'@rush-temp/model-view': file:projects/model-view.tgz_typescript@4.5.4
'@rush-temp/model-workbench': file:projects/model-workbench.tgz_typescript@4.5.4
'@rush-temp/mongo': file:projects/mongo.tgz
@ -278,6 +285,9 @@ dependencies:
'@rush-temp/telegram': file:projects/telegram.tgz
'@rush-temp/telegram-assets': file:projects/telegram-assets.tgz
'@rush-temp/telegram-resources': file:projects/telegram-resources.tgz_096c09b0b673a57c275d9767a12070b1
'@rush-temp/templates': file:projects/templates.tgz
'@rush-temp/templates-assets': file:projects/templates-assets.tgz
'@rush-temp/templates-resources': file:projects/templates-resources.tgz_096c09b0b673a57c275d9767a12070b1
'@rush-temp/text-editor': file:projects/text-editor.tgz_096c09b0b673a57c275d9767a12070b1
'@rush-temp/theme': file:projects/theme.tgz_096c09b0b673a57c275d9767a12070b1
'@rush-temp/tool': file:projects/tool.tgz
@ -11636,7 +11646,7 @@ packages:
dev: false
file:projects/model-all.tgz_typescript@4.5.4:
resolution: {integrity: sha512-xDbSAT10XpV9IMIWZmkZF1DKtDmPuh++4I5oxtxRHGrrdJ1h0J37nkAmZ6AayfWgqaCXG8EBn+SplvLw07HESg==, tarball: file:projects/model-all.tgz}
resolution: {integrity: sha512-5WhGhRWED9z51QVhlJWZJ321v4zkTY33gwVOdMVHidlhnmKJ2LPWfIN8Gs1K2I0Z2Gea3GfcgDbSrTYSGQd7bw==, tarball: file:projects/model-all.tgz}
id: file:projects/model-all.tgz
name: '@rush-temp/model-all'
version: 0.0.0
@ -12015,6 +12025,48 @@ packages:
- typescript
dev: false
file:projects/model-templates.tgz_typescript@4.5.4:
resolution: {integrity: sha512-yX+lWtzx5DiEX8LI6I8/XaAw+RkthYZgqVcAEvpL7vraVJHwSukdKHyHXRu6XdGAyXeMdlgPcwD9xF6CQts5gA==, tarball: file:projects/model-templates.tgz}
id: file:projects/model-templates.tgz
name: '@rush-temp/model-templates'
version: 0.0.0
dependencies:
'@rushstack/heft': 0.41.8
'@types/heft-jest': 1.0.2
'@typescript-eslint/eslint-plugin': 5.7.0_c25e8c1f4f4f7aaed27aa6f9ce042237
'@typescript-eslint/parser': 5.7.0_eslint@7.32.0+typescript@4.5.4
eslint: 7.32.0
eslint-config-standard-with-typescript: 21.0.1_ce2fa0c4dfa1c256100cababd749a13a
eslint-plugin-import: 2.25.3_eslint@7.32.0
eslint-plugin-node: 11.1.0_eslint@7.32.0
eslint-plugin-promise: 5.2.0_eslint@7.32.0
prettier: 2.5.1
transitivePeerDependencies:
- supports-color
- typescript
dev: false
file:projects/model-text-editor.tgz_typescript@4.5.4:
resolution: {integrity: sha512-2B69wwO38YusKx2s2f+p0sf+2ExNVFMTqO41+2SJKIQCfVF6vshEOn9HS3m93V5xIlZ3wJmVW5vl5b5x0beF1g==, tarball: file:projects/model-text-editor.tgz}
id: file:projects/model-text-editor.tgz
name: '@rush-temp/model-text-editor'
version: 0.0.0
dependencies:
'@rushstack/heft': 0.41.8
'@types/heft-jest': 1.0.2
'@typescript-eslint/eslint-plugin': 5.7.0_c25e8c1f4f4f7aaed27aa6f9ce042237
'@typescript-eslint/parser': 5.7.0_eslint@7.32.0+typescript@4.5.4
eslint: 7.32.0
eslint-config-standard-with-typescript: 21.0.1_ce2fa0c4dfa1c256100cababd749a13a
eslint-plugin-import: 2.25.3_eslint@7.32.0
eslint-plugin-node: 11.1.0_eslint@7.32.0
eslint-plugin-promise: 5.2.0_eslint@7.32.0
prettier: 2.5.1
transitivePeerDependencies:
- supports-color
- typescript
dev: false
file:projects/model-view.tgz_typescript@4.5.4:
resolution: {integrity: sha512-Nw3Id7t/uufJKYSapqk8INj5vMWLppU+gjcKnDD4tNoTtbbr/Zfe9NNwpcieZBJphJ0BpMTs1vYBmlZUTEQinw==, tarball: file:projects/model-view.tgz}
id: file:projects/model-view.tgz
@ -12252,7 +12304,7 @@ packages:
dev: false
file:projects/prod.tgz_sass@1.45.0+typescript@4.5.4:
resolution: {integrity: sha512-FWLkhyyIA2ZiXuydQap4fYlvfq02Q18A0fjVgVmy/7Wedebed2Xj6n2uQVjDVN50a5EP+5CutRcLAuxs7bI4Vw==, tarball: file:projects/prod.tgz}
resolution: {integrity: sha512-TRuJuHrFjTf/GKVoEbO9fUjFNW/dRPVDdYd8HyhpTr26o8RpI8yCYWasAX/V7nJiMRXL06FbEp0vwjMh1odogQ==, tarball: file:projects/prod.tgz}
id: file:projects/prod.tgz
name: '@rush-temp/prod'
version: 0.0.0
@ -12553,7 +12605,7 @@ packages:
dev: false
file:projects/setting-resources.tgz_096c09b0b673a57c275d9767a12070b1:
resolution: {integrity: sha512-KNZjL1XCVMDaeEnRfQBi4JAMjdg5aUyf6jrCrLGMZfG8Keq4JjHKqCNGVQ+xaYFZcQrEifs7M5OD9D6oimC4ag==, tarball: file:projects/setting-resources.tgz}
resolution: {integrity: sha512-64E4dI7SY+8qkmtk5dn8wbbxOSOqM+AD4Xh+i0BTv09Fhafe/TAcYLdByY5jzVkLmBohrYgBioZJcPbmulsvXg==, tarball: file:projects/setting-resources.tgz}
id: file:projects/setting-resources.tgz
name: '@rush-temp/setting-resources'
version: 0.0.0
@ -12745,6 +12797,82 @@ packages:
- supports-color
dev: false
file:projects/templates-assets.tgz:
resolution: {integrity: sha512-ZmNp9/xT3bhobdqstxRpaa+ddU1K3WLzF1ErI6L+cARerFsrgeG0g7+DaaMpd4QIv4Ip3+3MhDIbvjq8JCKE4g==, tarball: file:projects/templates-assets.tgz}
name: '@rush-temp/templates-assets'
version: 0.0.0
dependencies:
'@rushstack/heft': 0.41.8
'@types/heft-jest': 1.0.2
'@types/node': 16.11.14
'@typescript-eslint/eslint-plugin': 5.7.0_c25e8c1f4f4f7aaed27aa6f9ce042237
'@typescript-eslint/parser': 5.7.0_eslint@7.32.0+typescript@4.5.4
eslint: 7.32.0
eslint-config-standard-with-typescript: 21.0.1_ce2fa0c4dfa1c256100cababd749a13a
eslint-plugin-import: 2.25.3_eslint@7.32.0
eslint-plugin-node: 11.1.0_eslint@7.32.0
eslint-plugin-promise: 5.2.0_eslint@7.32.0
prettier: 2.5.1
typescript: 4.5.4
transitivePeerDependencies:
- supports-color
dev: false
file:projects/templates-resources.tgz_096c09b0b673a57c275d9767a12070b1:
resolution: {integrity: sha512-dsQP6LQ9+rxBOtUE2GXjmu7lhsQUAtP3ZWdzfo58ncdXao64Dn8w2nYrrUZ9dz7rcxwOPP3oHsrK7N2Egs1CMw==, tarball: file:projects/templates-resources.tgz}
id: file:projects/templates-resources.tgz
name: '@rush-temp/templates-resources'
version: 0.0.0
dependencies:
'@typescript-eslint/eslint-plugin': 5.7.0_c25e8c1f4f4f7aaed27aa6f9ce042237
'@typescript-eslint/parser': 5.7.0_eslint@7.32.0+typescript@4.5.4
eslint: 7.32.0
eslint-config-standard-with-typescript: 21.0.1_ce2fa0c4dfa1c256100cababd749a13a
eslint-plugin-import: 2.25.3_eslint@7.32.0
eslint-plugin-node: 11.1.0_eslint@7.32.0
eslint-plugin-promise: 5.2.0_eslint@7.32.0
eslint-plugin-svelte3: 3.2.1_eslint@7.32.0+svelte@3.44.3
prettier: 2.5.1
prettier-plugin-svelte: 2.5.1_prettier@2.5.1+svelte@3.44.3
sass: 1.45.0
svelte: 3.44.3
svelte-check: 2.2.11_4374c622c67ed7479ff0e44c29d09bce
svelte-loader: 3.1.2_svelte@3.44.3
svelte-preprocess: 4.10.1_14d64cad431e31f100de7363af24a44f
typescript: 4.5.4
transitivePeerDependencies:
- '@babel/core'
- coffeescript
- less
- node-sass
- postcss
- postcss-load-config
- pug
- stylus
- sugarss
- supports-color
dev: false
file:projects/templates.tgz:
resolution: {integrity: sha512-GRoSidgyl5TL1m7EI6J/XuUCM7aQ8zp5jH8yl8oDXPHG2rS2v+7pSz9ccBE/ynC0pUkxKL1oSgDRMq9eAXC4Ag==, tarball: file:projects/templates.tgz}
name: '@rush-temp/templates'
version: 0.0.0
dependencies:
'@rushstack/heft': 0.41.8
'@types/heft-jest': 1.0.2
'@typescript-eslint/eslint-plugin': 5.7.0_c25e8c1f4f4f7aaed27aa6f9ce042237
'@typescript-eslint/parser': 5.7.0_eslint@7.32.0+typescript@4.5.4
eslint: 7.32.0
eslint-config-standard-with-typescript: 21.0.1_ce2fa0c4dfa1c256100cababd749a13a
eslint-plugin-import: 2.25.3_eslint@7.32.0
eslint-plugin-node: 11.1.0_eslint@7.32.0
eslint-plugin-promise: 5.2.0_eslint@7.32.0
prettier: 2.5.1
typescript: 4.5.4
transitivePeerDependencies:
- supports-color
dev: false
file:projects/text-editor.tgz_096c09b0b673a57c275d9767a12070b1:
resolution: {integrity: sha512-mHTzsgBUTd2d3Y8xW1y8GOTLU0r4IyOsF7RpV1N4GVwaqnCZSxuFdC/T7PqX33XYI0hD6q+0KNegRs+MzvOeeg==, tarball: file:projects/text-editor.tgz}
id: file:projects/text-editor.tgz

View File

@ -93,6 +93,9 @@
"@anticrm/server-attachment": "~0.6.1",
"@anticrm/server-attachment-resources": "~0.6.0",
"@anticrm/server-contact": "~0.6.1",
"@anticrm/server-contact-resources": "~0.6.0"
"@anticrm/server-contact-resources": "~0.6.0",
"@anticrm/templates": "~0.6.0",
"@anticrm/templates-assets": "~0.6.0",
"@anticrm/templates-resources": "~0.6.0"
}
}

View File

@ -31,6 +31,7 @@ import { clientId } from '@anticrm/client'
import { gmailId } from '@anticrm/gmail'
import { imageCropperId } from '@anticrm/image-cropper'
import { inventoryId } from '@anticrm/inventory'
import { templatesId } from '@anticrm/templates'
import '@anticrm/login-assets'
import '@anticrm/task-assets'
@ -46,6 +47,7 @@ import '@anticrm/lead-assets'
import '@anticrm/gmail-assets'
import '@anticrm/workbench-assets'
import '@anticrm/inventory-assets'
import '@anticrm/templates-assets'
import { setMetadata } from '@anticrm/platform'
export async function configurePlatform() {
@ -76,4 +78,5 @@ export async function configurePlatform() {
addLocation(gmailId, () => import(/* webpackChunkName: "gmail" */ '@anticrm/gmail-resources'))
addLocation(imageCropperId, () => import(/* webpackChunkName: "image-cropper" */ '@anticrm/image-cropper-resources'))
addLocation(inventoryId, () => import(/* webpackChunkName: "inventory" */ '@anticrm/inventory-resources'))
addLocation(templatesId, () => import(/* webpackChunkName: "templates" */ '@anticrm/templates-resources'))
}

View File

@ -48,6 +48,8 @@
"@anticrm/model-gmail": "~0.6.0",
"@anticrm/model-inventory": "~0.6.0",
"@anticrm/model-presentation": "~0.6.0",
"@anticrm/model-templates": "~0.6.0",
"@anticrm/model-text-editor": "~0.6.0",
"@anticrm/core": "~0.6.13"
}
}

View File

@ -29,6 +29,8 @@ import { createModel as leadModel } from '@anticrm/model-lead'
import { createModel as gmailModel } from '@anticrm/model-gmail'
import { createModel as inventoryModel } from '@anticrm/model-inventory'
import { createModel as presentationModel } from '@anticrm/model-presentation'
import { createModel as templatesModel } from '@anticrm/model-templates'
import { createModel as textEditorModel } from '@anticrm/model-text-editor'
import { createModel as serverCoreModel } from '@anticrm/model-server-core'
import { createModel as serverAttachmentModel } from '@anticrm/model-server-attachment'
@ -54,6 +56,8 @@ leadModel(builder)
gmailModel(builder)
inventoryModel(builder)
presentationModel(builder)
templatesModel(builder)
textEditorModel(builder)
serverCoreModel(builder)
serverAttachmentModel(builder)

View File

@ -17,8 +17,8 @@ import { Builder, Model } from '@anticrm/model'
import { Ref, Domain, DOMAIN_MODEL } from '@anticrm/core'
import core, { TDoc } from '@anticrm/model-core'
import setting from '@anticrm/setting'
import type { Integration, IntegrationType, Handler } from '@anticrm/setting'
import type { IntlString } from '@anticrm/platform'
import type { Integration, IntegrationType, Handler, SettingsCategory } from '@anticrm/setting'
import type { Asset, IntlString } from '@anticrm/platform'
import task from '@anticrm/task'
import workbench from '@anticrm/model-workbench'
@ -31,6 +31,13 @@ export class TIntegration extends TDoc implements Integration {
type!: Ref<IntegrationType>
value!: string
}
@Model(setting.class.SettingsCategory, core.class.Doc, DOMAIN_MODEL)
export class TSettingsCategory extends TDoc implements SettingsCategory {
name!: string
label!: IntlString
icon!: Asset
component!: AnyComponent
}
@Model(setting.class.IntegrationType, core.class.Doc, DOMAIN_MODEL)
export class TIntegrationType extends TDoc implements IntegrationType {
@ -42,7 +49,65 @@ export class TIntegrationType extends TDoc implements IntegrationType {
}
export function createModel (builder: Builder): void {
builder.createModel(TIntegration, TIntegrationType)
builder.createModel(TIntegration, TIntegrationType, TSettingsCategory)
builder.createDoc(setting.class.SettingsCategory, core.space.Model, {
name: 'profile',
label: setting.string.EditProfile,
icon: setting.icon.EditProfile,
component: setting.component.Profile,
order: 0
}, setting.ids.Profile)
builder.createDoc(setting.class.SettingsCategory, core.space.Model, {
name: 'password',
label: setting.string.ChangePassword,
icon: setting.icon.Password,
component: setting.component.Password,
order: 1000
}, setting.ids.Password)
builder.createDoc(setting.class.SettingsCategory, core.space.Model, {
name: 'setting',
label: setting.string.Setting,
icon: setting.icon.Setting,
component: setting.component.Setting,
order: 2000
}, setting.ids.Setting)
builder.createDoc(setting.class.SettingsCategory, core.space.Model, {
name: 'integrations',
label: setting.string.Integrations,
icon: setting.icon.Integrations,
component: setting.component.Integrations,
order: 3000
}, setting.ids.Integrations)
builder.createDoc(setting.class.SettingsCategory, core.space.Model, {
name: 'statuses',
label: setting.string.ManageStatuses,
icon: task.icon.ManageStatuses,
component: setting.component.ManageStatuses,
order: 4000
}, setting.ids.ManageStatuses)
builder.createDoc(setting.class.SettingsCategory, core.space.Model, {
name: 'support',
label: setting.string.Support,
icon: setting.icon.Support,
component: setting.component.Support,
order: 5000
}, setting.ids.Support)
builder.createDoc(setting.class.SettingsCategory, core.space.Model, {
name: 'privacy',
label: setting.string.Privacy,
icon: setting.icon.Privacy,
component: setting.component.Privacy,
order: 6000
}, setting.ids.Privacy)
builder.createDoc(setting.class.SettingsCategory, core.space.Model, {
name: 'terms',
label: setting.string.Terms,
icon: setting.icon.Terms,
component: setting.component.Terms,
order: 10000
}, setting.ids.Terms)
builder.createDoc(
workbench.class.Application,
@ -51,59 +116,7 @@ export function createModel (builder: Builder): void {
label: setting.string.Setting,
icon: setting.icon.Setting,
hidden: true,
navigatorModel: {
specials: [
{
id: 'profile',
label: setting.string.EditProfile,
icon: setting.icon.EditProfile,
component: setting.component.Profile
},
{
id: 'password',
label: setting.string.ChangePassword,
icon: setting.icon.Password,
component: setting.component.Password
},
{
id: 'setting',
label: setting.string.Setting,
icon: setting.icon.Setting,
component: setting.component.Setting
},
{
id: 'integrations',
label: setting.string.Integrations,
icon: setting.icon.Integrations,
component: setting.component.Integrations
},
{
id: 'statuses',
label: setting.string.ManageStatuses,
icon: task.icon.ManageStatuses,
component: setting.component.ManageStatuses
},
{
id: 'support',
label: setting.string.Support,
icon: setting.icon.Support,
component: setting.component.Support
},
{
id: 'privacy',
label: setting.string.Privacy,
icon: setting.icon.Privacy,
component: setting.component.Privacy
},
{
id: 'terms',
label: setting.string.Terms,
icon: setting.icon.Terms,
component: setting.component.Terms
}
],
spaces: []
}
component: setting.component.Settings
},
setting.ids.SettingApp
)

View File

@ -0,0 +1,7 @@
module.exports = {
extends: ['./node_modules/@anticrm/model-rig/profiles/default/config/eslint.config.json'],
parserOptions: {
tsconfigRootDir: __dirname,
project: './tsconfig.json'
}
}

View File

@ -0,0 +1,4 @@
*
!/lib/**
!CHANGELOG.md
/lib/**/__tests__/

View File

@ -0,0 +1,18 @@
// The "rig.json" file directs tools to look for their config files in an external package.
// Documentation for this system: https://www.npmjs.com/package/@rushstack/rig-package
{
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
/**
* (Required) The name of the rig package to inherit from.
* It should be an NPM package name with the "-rig" suffix.
*/
"rigPackageName": "@anticrm/model-rig"
/**
* (Optional) Selects a config profile from the rig package. The name must consist of
* lowercase alphanumeric words separated by hyphens, for example "sample-profile".
* If omitted, then the "default" profile will be used."
*/
// "rigProfile": "your-profile-name"
}

View File

@ -0,0 +1,40 @@
{
"name": "@anticrm/model-templates",
"version": "0.6.0",
"main": "lib/index.js",
"author": "Anticrm Platform Contributors",
"license": "EPL-2.0",
"scripts": {
"build": "heft build",
"build:watch": "tsc",
"lint:fix": "eslint --fix src",
"lint": "eslint src",
"format": "prettier --write src && eslint --fix src"
},
"devDependencies": {
"@anticrm/model-rig": "~0.6.0",
"@typescript-eslint/eslint-plugin": "^5.4.0",
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-promise": "^5.1.1",
"eslint-plugin-node": "^11.1.0",
"eslint": "^7.32.0",
"@types/heft-jest": "^1.0.2",
"@typescript-eslint/parser": "^5.4.0",
"eslint-config-standard-with-typescript": "^21.0.1",
"prettier": "^2.4.1",
"@rushstack/heft": "^0.41.1"
},
"dependencies": {
"@anticrm/activity": "~0.6.0",
"@anticrm/model": "~0.6.0",
"@anticrm/core": "~0.6.0",
"@anticrm/platform": "~0.6.5",
"@anticrm/model-core": "~0.6.0",
"@anticrm/model-contact": "~0.6.1",
"@anticrm/templates": "~0.6.0",
"@anticrm/templates-resources": "~0.6.0",
"@anticrm/setting": "~0.6.0",
"@anticrm/ui": "~0.6.0",
"@anticrm/model-text-editor": "~0.6.0"
}
}

View File

@ -0,0 +1,67 @@
//
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 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.
//
import type { Domain } from '@anticrm/core'
import { Builder, Model, Prop, TypeString } from '@anticrm/model'
import core, { TDoc } from '@anticrm/model-core'
import textEditor from '@anticrm/model-text-editor'
import { IntlString } from '@anticrm/platform'
import setting from '@anticrm/setting'
import type { MessageTemplate } from '@anticrm/templates'
import templates from './plugin'
export const DOMAIN_TEMPLATES = 'templates' as Domain
@Model(templates.class.MessageTemplate, core.class.Doc, DOMAIN_TEMPLATES)
export class TMessageTemplate extends TDoc implements MessageTemplate {
@Prop(TypeString(), 'Title' as IntlString)
title!: string;
@Prop(TypeString(), 'Message' as IntlString)
message!: string;
}
export function createModel (builder: Builder): void {
builder.createModel(TMessageTemplate)
builder.createDoc(
core.class.Space,
core.space.Model,
{
name: 'Templates',
description: 'Space for all templates',
private: true,
archived: false,
members: []
},
templates.space.Templates
)
builder.createDoc(setting.class.SettingsCategory, core.space.Model, {
name: 'message-templates',
label: templates.string.Templates,
icon: templates.icon.Templates,
component: templates.component.Templates,
order: 3500
}, templates.ids.Templates)
builder.createDoc(textEditor.class.RefInputActionItem, core.space.Model, {
label: templates.string.Templates,
icon: templates.icon.Templates,
action: templates.action.ShowTemplates,
order: 1500
}, templates.ids.TemplatePopupAction)
}

View File

@ -0,0 +1,39 @@
//
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 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.
//
import { Ref } from '@anticrm/core'
import { mergeIds, Resource } from '@anticrm/platform'
import { SettingsCategory } from '@anticrm/setting'
import { templatesId } from '@anticrm/templates'
import templates from '@anticrm/templates-resources/src/plugin'
import type { AnyComponent } from '@anticrm/ui'
import { RefInputAction, RefInputActionItem } from '@anticrm/model-text-editor'
export default mergeIds(templatesId, templates, {
ids: {
Templates: '' as Ref<SettingsCategory>,
TemplatePopupAction: '' as Ref<RefInputActionItem>
},
// Without it, CLI version is failed with some svelte dependency exception.
componnets: {
Dummy: '' as AnyComponent
},
action: {
ShowTemplates: '' as Resource<RefInputAction>
}
})

View File

@ -0,0 +1,8 @@
{
"extends": "./node_modules/@anticrm/model-rig/profiles/default/tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib",
}
}

View File

@ -0,0 +1,7 @@
module.exports = {
extends: ['./node_modules/@anticrm/model-rig/profiles/default/config/eslint.config.json'],
parserOptions: {
tsconfigRootDir: __dirname,
project: './tsconfig.json'
}
}

View File

@ -0,0 +1,4 @@
*
!/lib/**
!CHANGELOG.md
/lib/**/__tests__/

View File

@ -0,0 +1,18 @@
// The "rig.json" file directs tools to look for their config files in an external package.
// Documentation for this system: https://www.npmjs.com/package/@rushstack/rig-package
{
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
/**
* (Required) The name of the rig package to inherit from.
* It should be an NPM package name with the "-rig" suffix.
*/
"rigPackageName": "@anticrm/model-rig"
/**
* (Optional) Selects a config profile from the rig package. The name must consist of
* lowercase alphanumeric words separated by hyphens, for example "sample-profile".
* If omitted, then the "default" profile will be used."
*/
// "rigProfile": "your-profile-name"
}

View File

@ -0,0 +1,35 @@
{
"name": "@anticrm/model-text-editor",
"version": "0.6.0",
"main": "lib/index.js",
"author": "Anticrm Platform Contributors",
"license": "EPL-2.0",
"scripts": {
"build": "heft build",
"build:watch": "tsc",
"lint:fix": "eslint --fix src",
"lint": "eslint src",
"format": "prettier --write src && eslint --fix src"
},
"devDependencies": {
"@anticrm/model-rig": "~0.6.0",
"@typescript-eslint/eslint-plugin": "^5.4.0",
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-promise": "^5.1.1",
"eslint-plugin-node": "^11.1.0",
"eslint": "^7.32.0",
"@types/heft-jest": "^1.0.2",
"@typescript-eslint/parser": "^5.4.0",
"eslint-config-standard-with-typescript": "^21.0.1",
"prettier": "^2.4.1",
"@rushstack/heft": "^0.41.1"
},
"dependencies": {
"@anticrm/core": "~0.6.11",
"@anticrm/model": "~0.6.0",
"@anticrm/platform": "~0.6.5",
"@anticrm/ui": "~0.6.0",
"@anticrm/text-editor": "~0.6.0",
"@anticrm/model-core": "~0.6.0"
}
}

View File

@ -0,0 +1,40 @@
//
// Copyright © 2020 Anticrm Platform Contributors.
//
// 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.
//
import { DOMAIN_MODEL } from '@anticrm/core'
import { Builder, Model } from '@anticrm/model'
import core, { TDoc } from '@anticrm/model-core'
import type { Asset, IntlString, Resource } from '@anticrm/platform'
// Import types to prevent .svelte components to being exposed to type typescript.
import { RefInputAction, RefInputActionItem } from '@anticrm/text-editor/src/types'
import textEditor from './plugin'
export { default } from './plugin'
export { RefInputAction, RefInputActionItem }
@Model(textEditor.class.RefInputActionItem, core.class.Doc, DOMAIN_MODEL)
export class TRefInputActionItem extends TDoc implements RefInputActionItem {
label!: IntlString
icon!: Asset
// Query for documents with pattern
action!: Resource<RefInputAction>
}
export function createModel (builder: Builder): void {
builder.createModel(
TRefInputActionItem
)
}

View File

@ -0,0 +1,23 @@
//
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 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.
//
import { mergeIds } from '@anticrm/platform'
// Import plugin directly to prevent .svelte components to being exposed to type typescript.
import textEditor, { textEditorId } from '@anticrm/text-editor/src/plugin'
export default mergeIds(textEditorId, textEditor, {
})

View File

@ -0,0 +1,8 @@
{
"extends": "./node_modules/@anticrm/model-rig/profiles/default/tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib",
}
}

View File

@ -14,7 +14,7 @@
// limitations under the License.
-->
<script lang="ts">
import { getResource, translate } from '@anticrm/platform'
import { getResource } from '@anticrm/platform'
import { getClient, ObjectSearchCategory, ObjectSearchResult } from '@anticrm/presentation'
import { ActionIcon, EditWithIcon, IconSearch, Label } from '@anticrm/ui'
import plugin from '../plugin'
@ -91,12 +91,6 @@
}
}
$: updateItems(category, query)
let placeholder = ''
$: translate(category.label, {}).then((v) => {
placeholder = v
})
</script>
<div
@ -119,7 +113,7 @@
{/each}
</div>
<div class='mt-4 mb-4'>
<EditWithIcon icon={IconSearch} bind:value={query} on:input={() => updateItems(category, query) } placeholder={placeholder} />
<EditWithIcon icon={IconSearch} bind:value={query} on:input={() => updateItems(category, query) } placeholder={category.label} />
</div>
<Label label={plugin.string.Suggested}/>
<div class="scroll mt-2">

View File

@ -14,11 +14,14 @@
-->
<script lang="ts">
import { getResource } from '@anticrm/platform'
import presentation, { getClient, ObjectSearchCategory, ObjectSearchFactory } from '@anticrm/presentation'
import { Asset, getResource, IntlString } from '@anticrm/platform'
import presentation, { getClient, ObjectSearchCategory } from '@anticrm/presentation'
import { AnySvelteComponent, Icon } from '@anticrm/ui'
import { AnyExtension } from '@tiptap/core'
import { createEventDispatcher } from 'svelte'
import { Completion } from '../Completion'
import textEditorPlugin from '../plugin'
import { RefInputAction, RefInputActionItem, TextEditorHandler } from '../types'
import Attach from './icons/Attach.svelte'
import Emoji from './icons/Emoji.svelte'
import GIF from './icons/GIF.svelte'
@ -40,7 +43,52 @@
client.findAll(presentation.class.ObjectSearchCategory, {}).then((r) => {
categories = r
})
interface RefAction {
label: IntlString
icon: Asset | AnySvelteComponent
action: RefInputAction
order: number
}
const defActions: RefAction[] = [
{
label: textEditorPlugin.string.Attach,
icon: Attach,
action: () => {},
order: 1000
},
{
label: textEditorPlugin.string.TextStyle,
icon: TextStyle,
action: () => {},
order: 2000
},
{
label: textEditorPlugin.string.Emoji,
icon: Emoji,
action: () => {},
order: 3000
},
{
label: textEditorPlugin.string.GIF,
icon: GIF,
action: () => {},
order: 4000
}
]
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)
})
// Current selected category
let category: ObjectSearchCategory | undefined = categories[0]
@ -95,6 +143,16 @@
}
})
]
const editorHandler: TextEditorHandler = {
insertText: (text) => {
textEditor.insertText(text)
}
}
function handleAction (a: RefAction, evt?: Event): void {
console.log('handle event', a.label)
a.action(evt?.target as HTMLElement, editorHandler)
}
</script>
<div class="ref-container">
@ -113,10 +171,11 @@
{/if}
</div>
<div class="buttons">
<div class="tool"><Attach /></div>
<div class="tool"><TextStyle /></div>
<div class="tool"><Emoji /></div>
<div class="tool"><GIF /></div>
{#each actions as a}
<div class="tool" on:click={(evt) => handleAction(a, evt)}>
<Icon icon={a.icon} size={'large'}/>
</div>
{/each}
</div>
</div>

View File

@ -0,0 +1,112 @@
<!--
// Copyright © 2020 Anticrm Platform Contributors.
//
// 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 { ScrollBox } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte'
import Emoji from './icons/Emoji.svelte'
import GIF from './icons/GIF.svelte'
import TextStyle from './icons/TextStyle.svelte'
import TextEditor from './TextEditor.svelte'
const dispatch = createEventDispatcher()
export let content: string = ''
let textEditor: TextEditor
export function submit (): void {
textEditor.submit()
}
</script>
<div class="ref-container">
<div class="textInput">
<div class="inputMsg">
<ScrollBox bothScroll stretch>
<TextEditor
bind:content
bind:this={textEditor}
on:value
on:content={(ev) => {
dispatch('message', ev.detail)
content = ''
textEditor.clear()
}}
supportSubmit={false}
/>
</ScrollBox>
</div>
</div>
<div class="buttons">
<div class="tool"><TextStyle size={'large'} /></div>
<div class="tool"><Emoji size={'large'}/></div>
<div class="tool"><GIF size={'large'}/></div>
<div class="flex-grow">
<slot />
</div>
</div>
</div>
<style lang="scss">
.ref-container {
flex-grow: 1;
display: flex;
flex-direction: column;
min-height: 4.5rem;
.textInput {
flex-grow: 1;
display: flex;
justify-content: space-between;
align-items: flex-end;
min-height: 2.75rem;
background-color: transparent;
.inputMsg {
align-self: stretch;
width: 100%;
color: var(--theme-content-color);
background-color: transparent;
:global(.ProseMirror) {
min-height: 0;
max-height: 100%;
height: 100%;
}
}
}
.buttons {
margin: 10px 0 0 8px;
display: flex;
align-items: center;
.tool {
display: flex;
justify-content: center;
align-items: center;
width: 20px;
height: 20px;
opacity: 0.3;
cursor: pointer;
&:hover {
opacity: 1;
}
}
.tool + .tool {
margin-left: 16px;
}
}
}
</style>

View File

@ -15,7 +15,7 @@
-->
<script lang="ts">
import { AnyExtension, Editor, Extension } from '@tiptap/core'
import { AnyExtension, Editor, Extension, HTMLContent } from '@tiptap/core'
import Highlight from '@tiptap/extension-highlight'
import Link from '@tiptap/extension-link'
// import Typography from '@tiptap/extension-typography'
@ -26,6 +26,7 @@
export let content: string = ''
export let placeholder: string = 'Type something...'
export let extensions: AnyExtension[] = []
export let supportSubmit = true
let element: HTMLElement
let editor: Editor
@ -41,6 +42,9 @@
content = ''
editor.commands.clearContent(false)
}
export function insertText (text: string): void {
editor.commands.insertContent(text as HTMLContent)
}
const Handle = Extension.create({
addKeyboardShortcuts () {
@ -73,7 +77,7 @@
StarterKit,
Highlight,
Link,
Handle, // order important
...(supportSubmit ? [Handle] : []), // order important
// Typography, // we need to disable 1/2 -> ½ rule (https://github.com/hcengineering/anticrm/issues/345)
Placeholder.configure({ placeholder: placeholder }),
...extensions
@ -84,6 +88,10 @@
},
onBlur: () => {
dispatch('blur')
},
onUpdate: () => {
content = editor.getHTML()
dispatch('value', content)
}
})
})
@ -95,7 +103,7 @@
})
</script>
<div style="width: 100%" bind:this={element}/>
<div style="width: 100%;" bind:this={element}/>
<style lang="scss" global>
@ -103,9 +111,9 @@
overflow-y: auto;
max-height: 5.5rem;
outline: none;
p {
margin: 0;
line-height: 150%;
p:not(:last-child) {
margin-block-end: 1em;
}
> * + * {

View File

@ -1,8 +1,8 @@
<script lang="ts">
export let size: number = 20
export let size: string
const fill: string = 'var(--theme-caption-color)'
</script>
<svg width={size} height={size} {fill} viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<svg class={`class-${size}`} {fill} viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path d="M4.82816 10.4853L10.485 4.82845C11.6566 3.65688 13.5561 3.65688 14.7277 4.82845C15.8992 6.00002 15.8992 7.89952 14.7277 9.07109L8.01014 15.7886C7.42436 16.3744 6.47461 16.3744 5.88882 15.7886C5.30304 15.2028 5.30304 14.2531 5.88882 13.6673L11.8992 7.65687C12.0945 7.46161 12.0945 7.14503 11.8992 6.94977C11.704 6.75451 11.3874 6.75451 11.1921 6.94977L5.18172 12.9602C4.20541 13.9365 4.20541 15.5194 5.18172 16.4957C6.15803 17.472 7.74094 17.472 8.71725 16.4957L15.4348 9.77819C16.9969 8.2161 16.9969 5.68344 15.4348 4.12134C13.8727 2.55924 11.34 2.55924 9.77791 4.12134L4.12106 9.77819C3.92579 9.97346 3.92579 10.29 4.12106 10.4853C4.31632 10.6806 4.6329 10.6806 4.82816 10.4853Z"/>
</svg>

View File

@ -1,8 +1,8 @@
<script lang="ts">
export let size: number = 20
export let size: string
const fill: string = 'var(--theme-caption-color)'
</script>
<svg width={size} height={size} {fill} viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<svg class={`class-${size}`} {fill} viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path d="M10 2C14.4183 2 18 5.58172 18 10C18 14.4183 14.4183 18 10 18C5.58172 18 2 14.4183 2 10C2 5.58172 5.58172 2 10 2ZM10 3C6.13401 3 3 6.13401 3 10C3 13.866 6.13401 17 10 17C13.866 17 17 13.866 17 10C17 6.13401 13.866 3 10 3ZM7.15467 12.4273C8.66416 13.9463 11.0877 14.0045 12.6671 12.5961L12.8453 12.4273C13.04 12.2314 13.3566 12.2304 13.5524 12.4251C13.7265 12.5981 13.7467 12.8674 13.6123 13.0627L13.5547 13.1322L13.5323 13.1545C11.5691 15.1054 8.39616 15.0953 6.44533 13.1322C6.25069 12.9363 6.25169 12.6197 6.44757 12.4251C6.64344 12.2304 6.96002 12.2314 7.15467 12.4273ZM12.5 7.5C13.0523 7.5 13.5 7.94772 13.5 8.5C13.5 9.05228 13.0523 9.5 12.5 9.5C11.9477 9.5 11.5 9.05228 11.5 8.5C11.5 7.94772 11.9477 7.5 12.5 7.5ZM7.5 7.5C8.05228 7.5 8.5 7.94772 8.5 8.5C8.5 9.05228 8.05228 9.5 7.5 9.5C6.94772 9.5 6.5 9.05228 6.5 8.5C6.5 7.94772 6.94772 7.5 7.5 7.5Z"/>
</svg>

View File

@ -1,8 +1,8 @@
<script lang="ts">
export let size: number = 20
export let size: string
const fill: string = 'var(--theme-caption-color)'
</script>
<svg width={size} height={size} {fill} viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<svg class={`class-${size}`} {fill} viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path d="M15.4 3C16.7864 3 17.9194 4.1869 17.9959 5.68238L18 5.84375V14.1562C18 15.6727 16.9148 16.9118 15.5475 16.9955L15.4 17H4.6C3.21357 17 2.0806 15.8131 2.00412 14.3176L2 14.1562V5.84375C2 4.32735 3.08516 3.08816 4.45246 3.0045L4.6 3H15.4ZM15.25 4H4.75C3.82377 4 3.06561 4.793 3.00404 5.79653L3 5.92857V14.0714C3 15.0922 3.71957 15.9277 4.63018 15.9956L4.75 16H15.25C16.1762 16 16.9344 15.207 16.996 14.2035L17 14.0714V5.92857C17 4.90783 16.2804 4.0723 15.3698 4.00445L15.25 4ZM6.85135 7.00214C7.4713 7.00214 7.99907 7.0988 8.43229 7.30058C8.69893 7.42477 8.80624 7.72539 8.67198 7.97203C8.53772 8.21867 8.21272 8.31793 7.94609 8.19374C7.6779 8.06883 7.31378 8.00214 6.85135 8.00214C5.82473 8.00214 5.08108 8.8345 5.08108 10.0011C5.08108 11.1209 5.89403 12 6.85135 12C7.44761 12 7.85273 11.6487 7.91152 11.3268L7.91892 11.2472V10.5011L7.54054 10.5C7.24201 10.5 7 10.2761 7 10C7 9.75454 7.19122 9.55039 7.44338 9.50806L7.54054 9.5L8.45946 9.50107C8.72482 9.50107 8.94552 9.67794 8.99129 9.91119L9 10.0011V11.2472C9 12.1244 8.11351 13 6.85135 13C5.25622 13 4 11.6415 4 10.0011C4 8.31985 5.17727 7.00214 6.85135 7.00214ZM11 7C11.2455 7 11.4496 7.17688 11.4919 7.41012L11.5 7.5V12.5C11.5 12.7761 11.2761 13 11 13C10.7545 13 10.5504 12.8231 10.5081 12.5899L10.5 12.5V7.5C10.5 7.22386 10.7239 7 11 7ZM15.5 7C15.7761 7 16 7.22386 16 7.5C16 7.74546 15.8231 7.94961 15.5899 7.99194L15.5 8H14V10H15.5C15.7761 10 16 10.2239 16 10.5C16 10.7455 15.8231 10.9496 15.5899 10.9919L15.5 11H14V12.5C14 12.7761 13.7761 13 13.5 13C13.2545 13 13.0504 12.8231 13.0081 12.5899L13 12.5V7.5C13 7.22386 13.2239 7 13.5 7H15.5Z"/>
</svg>

View File

@ -1,8 +1,8 @@
<script lang="ts">
export let size: number = 20
export let size: string
const fill: string = 'var(--theme-caption-color)'
</script>
<svg width={size} height={size} {fill} viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<svg class={`class-${size}`} {fill} viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path d="M14.0872 6.70308C14.9271 5.85998 16.2664 5.81735 17.1568 6.57701L17.2869 6.69697L17.4134 6.83357C18.1376 7.68212 18.1376 8.93671 17.4134 9.78526L17.293 9.91574L10.4167 16.8183C10.2617 16.9739 10.0749 17.0931 9.86988 17.1682L9.71291 17.2161L6.62737 17.9764C6.28827 18.0599 5.98023 17.7813 6.00917 17.4498L6.0236 17.366L6.81558 14.2953C6.86895 14.0883 6.96574 13.8958 7.09885 13.7302L7.20537 13.6112L14.0872 6.70308ZM16.5811 7.40543C16.1201 6.94613 15.393 6.91684 14.898 7.31661L14.7957 7.40884L7.91383 14.317C7.87193 14.359 7.83804 14.408 7.8135 14.4616L7.7839 14.545L7.20061 16.805L9.47368 16.2452C9.51317 16.2354 9.55114 16.221 9.58682 16.2022L9.6385 16.1709L9.70829 16.1126L16.5845 9.20999C17.0806 8.71206 17.0806 7.90677 16.5811 7.40543ZM6.42419 2.22879L6.46607 2.31323L9.448 9.94224L8.676 10.7172L8.004 8.99824H4.007L2.96614 11.678C2.87719 11.9068 2.63835 12.0329 2.40561 11.9879L2.31893 11.9628C2.09015 11.8738 1.96399 11.635 2.00906 11.4023L2.03413 11.3156L5.53436 2.31403C5.6877 1.9197 6.21661 1.89109 6.42419 2.22879ZM6.00061 3.871L4.396 7.99824H7.614L6.00061 3.871Z"/>
</svg>

View File

@ -1,14 +1,31 @@
//
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 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.
//
import { addStringsLoader } from '@anticrm/platform'
import { textEditorId } from './plugin'
//
export * from '@anticrm/presentation/src/types'
export * from './types'
export { default as ReferenceInput } from './components/ReferenceInput.svelte'
export { default as TextEditor } from './components/TextEditor.svelte'
export * from '@anticrm/presentation/src/types'
export { default as StyledTextEditor } from './components/StyledTextEditor.svelte'
addStringsLoader(textEditorId, async (lang: string) => {
return await import(`../lang/${lang}.json`)
})
export { default } from './plugin'
export { default } from './plugin'

View File

@ -14,7 +14,9 @@
// limitations under the License.
//
import { Class, Ref } from '@anticrm/core'
import { IntlString, Plugin, plugin } from '@anticrm/platform'
import { RefInputActionItem } from './types'
/**
* @public
@ -22,7 +24,14 @@ import { IntlString, Plugin, plugin } from '@anticrm/platform'
export const textEditorId = 'text-editor' as Plugin
export default plugin(textEditorId, {
class: {
RefInputActionItem: '' as Ref<Class<RefInputActionItem>>
},
string: {
Suggested: '' as IntlString
Suggested: '' as IntlString,
Attach: '' as IntlString,
TextStyle: '' as IntlString,
Emoji: '' as IntlString,
GIF: '' as IntlString
}
})

View File

@ -0,0 +1,26 @@
import { Asset, IntlString, Resource } from '@anticrm/platform'
import { Doc } from '@anticrm/core'
/**
* @public
*/
export interface TextEditorHandler {
insertText: (html: string) => void
}
/**
* @public
*/
export type RefInputAction = (element: HTMLElement, editor: TextEditorHandler) => void
/**
* A contribution to reference input control, to allow to add more actions to it.
* @public
*/
export interface RefInputActionItem extends Doc {
label: IntlString
icon: Asset
// Query for documents with pattern
action: Resource<RefInputAction>
order?: number
}

View File

@ -28,4 +28,8 @@
<path d="M4.7,7.8h0.7c0.3,0,0.5-0.2,0.5-0.5S5.6,6.8,5.3,6.8H4.7c-0.3,0-0.5,0.2-0.5,0.5S4.4,7.8,4.7,7.8z"/>
<path d="M6.7,9.5h-2c-0.3,0-0.5,0.2-0.5,0.5s0.2,0.5,0.5,0.5h2c0.3,0,0.5-0.2,0.5-0.5S6.9,9.5,6.7,9.5z"/>
</symbol>
<symbol id="signout" viewBox="0 0 16 16" fill="none">
<path d="M12.24 4.42664C13.0789 5.26583 13.6502 6.33493 13.8815 7.49876C14.1129 8.66259 13.994 9.86889 13.5398 10.9651C13.0856 12.0614 12.3165 12.9983 11.3299 13.6575C10.3432 14.3167 9.18328 14.6686 7.99668 14.6686C6.81007 14.6686 5.65011 14.3167 4.66346 13.6575C3.67681 12.9983 2.90777 12.0614 2.45359 10.9651C1.9994 9.86889 1.88047 8.66259 2.11182 7.49876C2.34317 6.33493 2.91442 5.26583 3.75334 4.42664" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8 1.33331V7.99998" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
</symbol>
</svg>

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

@ -21,6 +21,8 @@
"LearnMore": "Learn more",
"EnterCurrentPassword": "Enter current password",
"EnterNewPassword": "Enter new password",
"RepeatNewPassword": "Repeat new password"
"RepeatNewPassword": "Repeat new password",
"Signout": "Sign out",
"Settings": "Settings"
}
}

View File

@ -24,7 +24,8 @@ loadMetadata(setting.icon, {
Integrations: `${icons}#integration`,
Support: `${icons}#support`,
Privacy: `${icons}#privacy`,
Terms: `${icons}#terms`
Terms: `${icons}#terms`,
Signout: `${icons}#signout`
})
addStringsLoader(settingId, async (lang: string) => await import(`../lang/${lang}.json`))

View File

@ -45,6 +45,7 @@
"@anticrm/task": "~0.6.0",
"@anticrm/task-resources": "~0.6.0",
"@anticrm/workbench": "~0.6.1",
"@anticrm/contact-resources": "~0.6.0"
"@anticrm/contact-resources": "~0.6.0",
"@anticrm/login": "~0.6.1"
}
}

View File

@ -0,0 +1,76 @@
<!--
// Copyright © 2021 Anticrm Platform Contributors.
//
// 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 type { Asset, IntlString } from '@anticrm/platform'
import { Icon, Label } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte'
export let icon: Asset | undefined = undefined
export let label: IntlString | undefined = undefined
const dispatch = createEventDispatcher()
</script>
<!-- svelte-ignore a11y-mouse-events-have-key-events -->
<div
class="flex-row-center container"
on:click|stopPropagation={() => {
dispatch('click')
}}
>
<div class="icon">
{#if icon}
<Icon {icon} size={'small'} />
{/if}
</div>
<span>
{#if label}<Label {label} />{:else}{label}{/if}
</span>
</div>
<style lang="scss">
.container {
margin: 0 16px;
padding-left: 10px;
padding-right: 12px;
min-height: 36px;
font-weight: 500;
color: var(--theme-caption-color);
border-radius: 8px;
user-select: none;
cursor: pointer;
.icon {
margin-right: 18px;
width: 16px;
min-width: 16px;
height: 16px;
border-radius: 4px;
}
span {
flex-grow: 1;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
&:hover {
background-color: var(--theme-button-bg-enabled);
.tool {
visibility: visible;
}
}
}
</style>

View File

@ -0,0 +1,106 @@
<script lang="ts">
import { getClient } from '@anticrm/presentation'
import setting, { SettingsCategory } from '@anticrm/setting'
import { Component, getCurrentLocation, Label, location, navigate, setMetadataLocalStorage } from '@anticrm/ui'
import { onDestroy } from 'svelte'
import CategoryElement from './CategoryElement.svelte'
import login from '@anticrm/login'
const client = getClient()
let category: SettingsCategory | undefined
let categoryId: string = ''
let categories: SettingsCategory[] = []
client.findAll(setting.class.SettingsCategory, {}, { sort: { order: 1 } }).then(s => {
categories = s
category = findCategory(categoryId)
})
onDestroy(
location.subscribe(async (loc) => {
categoryId = loc.path[2]
category = findCategory(categoryId)
})
)
function findCategory (name: string): SettingsCategory | undefined {
return categories.find((x) => x.name === name)
}
function selectCategory (id: string): void {
const loc = getCurrentLocation()
loc.path[2] = id
loc.path.length = 3
navigate(loc)
}
function signOut (): void {
setMetadataLocalStorage(login.metadata.LoginToken, null)
setMetadataLocalStorage(login.metadata.LoginEndpoint, null)
setMetadataLocalStorage(login.metadata.LoginEmail, null)
navigate({ path: [login.component.LoginApp] })
}
</script>
<div class='container'>
<div class="panel-navigator">
<div class="flex-between navheader-container">
<span class="fs-title overflow-label">
<Label label={setting.string.Settings}/>
</span>
</div>
{#each categories as category}
<CategoryElement icon={category.icon} label={category.label} on:click={() => { selectCategory(category.name) }}/>
{/each}
<div class='signout'>
<CategoryElement icon={setting.icon.Signout} label={setting.string.Signout} on:click={signOut}/>
</div>
</div>
<div class="panel-component">
{#if category}
<Component is={category.component} />
{/if}
</div>
</div>
<style lang="scss">
.container {
display: flex;
height: 100%;
padding-bottom: 1.25rem;
background: var(--theme-menu-color);
.panel-navigator {
display: flex;
flex-direction: column;
margin-right: 1rem;
min-width: 18rem;
width: 18rem;
height: 100%;
border-radius: 1.25rem;
background-color: var(--theme-bg-color);
.signout {
display: flex;
flex-direction: column-reverse;
flex-grow: 1;
margin-bottom: 2rem;
}
}
.panel-component {
flex-grow: 1;
display: flex;
flex-direction: column;
margin-right: 1rem;
height: 100%;
border-radius: 1.25rem;
background-color: var(--theme-bg-color);
overflow: hidden;
}
}
.navheader-container {
padding: 0 1.75rem;
height: 4rem;
}
</style>

View File

@ -22,9 +22,11 @@ import ManageStatuses from './components/statuses/ManageStatuses.svelte'
import Support from './components/Support.svelte'
import Privacy from './components/Privacy.svelte'
import Terms from './components/Terms.svelte'
import Settings from './components/Settings.svelte'
export default async (): Promise<Resources> => ({
component: {
Settings,
Profile,
Password,
Setting,

View File

@ -42,6 +42,19 @@ export interface Integration extends Doc {
value: string
}
/**
* @public
*/
export interface SettingsCategory extends Doc {
name: string
label: IntlString
icon: Asset
component: AnyComponent
// If defined, will sort using order.
order?: number
}
/**
* @public
*/
@ -49,13 +62,23 @@ export const settingId = 'setting' as Plugin
export default plugin(settingId, {
ids: {
SettingApp: '' as Ref<Doc>
SettingApp: '' as Ref<Doc>,
Profile: '' as Ref<Doc>,
Password: '' as Ref<Doc>,
Setting: '' as Ref<Doc>,
Integrations: '' as Ref<Doc>,
ManageStatuses: '' as Ref<Doc>,
Support: '' as Ref<Doc>,
Privacy: '' as Ref<Doc>,
Terms: '' as Ref<Doc>
},
class: {
SettingsCategory: '' as Ref<Class<SettingsCategory>>,
Integration: '' as Ref<Class<Integration>>,
IntegrationType: '' as Ref<Class<IntegrationType>>
},
component: {
Settings: '' as AnyComponent,
Profile: '' as AnyComponent,
Password: '' as AnyComponent,
Setting: '' as AnyComponent,
@ -66,6 +89,7 @@ export default plugin(settingId, {
Terms: '' as AnyComponent
},
string: {
Settings: '' as IntlString,
Setting: '' as IntlString,
Integrations: '' as IntlString,
ManageStatuses: '' as IntlString,
@ -87,7 +111,8 @@ export default plugin(settingId, {
Saved: '' as IntlString,
EnterCurrentPassword: '' as IntlString,
EnterNewPassword: '' as IntlString,
RepeatNewPassword: '' as IntlString
RepeatNewPassword: '' as IntlString,
Signout: '' as IntlString
},
icon: {
EditProfile: '' as Asset,
@ -96,6 +121,7 @@ export default plugin(settingId, {
Integrations: '' as Asset,
Support: '' as Asset,
Privacy: '' as Asset,
Terms: '' as Asset
Terms: '' as Asset,
Signout: '' as Asset
}
})

View File

@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
<symbol id="templates" fill="none" viewBox="0 0 16 16">
<path d="M8.58334 3.33337V3.33337C7.42111 3.33337 6.83999 3.33337 6.36142 3.45325C4.92972 3.81187 3.81184 4.92975 3.45322 6.36145C3.33334 6.84002 3.33334 7.42114 3.33334 8.58337V10.8C3.33334 11.6139 3.33334 12.0208 3.5562 12.296C3.60017 12.3503 3.64972 12.3999 3.70402 12.4439C3.97922 12.6667 4.38615 12.6667 5.20001 12.6667H7.41668C8.57891 12.6667 9.16003 12.6667 9.6386 12.5468C11.0703 12.1882 12.1882 11.0703 12.5468 9.63863C12.6667 9.16006 12.6667 8.57894 12.6667 7.41671V7.41671" stroke="white"/>
<path d="M6 6.66663L10 6.66663" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6 9.33337H8" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12.6667 5.33337L12.6667 1.33337M10.6667 3.33337H14.6667" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
</symbol>
</svg>

After

Width:  |  Height:  |  Size: 975 B

View File

@ -0,0 +1,14 @@
{
"string": {
"Cancel": "Cancel",
"Templates": "Templates",
"TemplatesHeader": "TEMPLATES",
"CreateTemplate": "CREATE TEMPLATE",
"SaveTemplate": "Save template",
"EditTemplate": "Edit template",
"ViewTemplate": "View template",
"Suggested": "SUGGESTED",
"SearchTemplate": "search for template",
"TemplatePlaceholder": "New template"
}
}

View File

@ -0,0 +1,33 @@
{
"name": "@anticrm/templates-assets",
"version": "0.6.0",
"main": "src/index.ts",
"author": "Anticrm Platform Contributors",
"license": "EPL-2.0",
"scripts": {
"build": "heft build",
"lint": "eslint src",
"lint:fix": "eslint --fix src",
"format": "prettier --write src && eslint --fix src",
"build:watch": "tsc"
},
"devDependencies": {
"@anticrm/platform-rig": "~0.6.0",
"@typescript-eslint/eslint-plugin": "^5.4.0",
"@typescript-eslint/parser": "^5.4.0",
"eslint-config-standard-with-typescript": "^21.0.1",
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^5.1.1",
"eslint": "^7.32.0",
"prettier": "^2.4.1",
"@types/heft-jest": "^1.0.2",
"@rushstack/heft": "^0.41.1",
"typescript": "^4.3.5",
"@types/node": "^16.4.10"
},
"dependencies": {
"@anticrm/platform": "~0.6.5",
"@anticrm/templates": "~0.6.0"
}
}

View File

@ -0,0 +1,25 @@
//
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 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.
//
import { addStringsLoader, loadMetadata } from '@anticrm/platform'
import templates, { templatesId } from '@anticrm/templates'
const icons = require('../assets/icons.svg')
loadMetadata(templates.icon, {
Templates: `${icons}#templates`
})
addStringsLoader(templatesId, async (lang: string) => await import(`../lang/${lang}.json`))

View File

@ -0,0 +1,15 @@
{
"compilerOptions": {
"moduleResolution": "node",
"target": "esnext",
"module": "esnext",
"declaration": true,
"outDir": "./lib",
"strict": true,
"esModuleInterop": true,
"lib": [
"esnext",
"dom"
]
}
}

View File

@ -0,0 +1,7 @@
module.exports = {
extends: ['./node_modules/@anticrm/platform-rig/profiles/ui/config/eslint.config.json'],
parserOptions: { tsconfigRootDir: __dirname },
settings: {
'svelte3/ignore-styles': () => true
}
}

View File

@ -0,0 +1,46 @@
{
"name": "@anticrm/templates-resources",
"version": "0.6.0",
"main": "src/index.ts",
"author": "Anticrm Platform Contributors",
"license": "EPL-2.0",
"scripts": {
"build": "echo 'no build for ui'",
"build:docs": "api-extractor run --local",
"lint": "svelte-check && eslint",
"lint:fix": "eslint --fix src",
"format": "prettier --write --plugin-search-dir=. src && eslint --fix src",
"svelte-check": "svelte-check"
},
"devDependencies": {
"@anticrm/platform-rig": "~0.6.0",
"svelte-loader": "^3.1.2",
"sass": "^1.37.5",
"svelte-preprocess": "^4.7.4",
"@typescript-eslint/eslint-plugin": "^5.4.0",
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-promise": "^5.1.1",
"eslint-plugin-node": "^11.1.0",
"eslint": "^7.32.0",
"@typescript-eslint/parser": "^5.4.0",
"eslint-config-standard-with-typescript": "^21.0.1",
"eslint-plugin-svelte3": "~3.2.1",
"prettier-plugin-svelte": "^2.2.0",
"prettier": "^2.4.1",
"svelte-check": "^2.2.10",
"typescript": "^4.3.5"
},
"dependencies": {
"@anticrm/platform": "~0.6.5",
"svelte": "^3.37.0",
"@anticrm/templates": "~0.6.0",
"@anticrm/ui": "~0.6.0",
"@anticrm/presentation": "~0.6.2",
"@anticrm/text-editor": "~0.6.0",
"@anticrm/setting": "~0.6.0",
"@anticrm/chunter": "~0.6.0",
"@anticrm/chunter-resources": "~0.6.0",
"@anticrm/core": "~0.6.11",
"@anticrm/view-resources": "~0.6.0"
}
}

View File

@ -0,0 +1,5 @@
module.exports = {
plugins: [
require('autoprefixer')
]
}

View File

@ -0,0 +1,88 @@
<!--
// Copyright © 2021 Anticrm Platform Contributors.
//
// 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 { Doc } from '@anticrm/core'
import { IconMoreV, showPopup } from '@anticrm/ui'
import { ContextMenu } from '@anticrm/view-resources'
import { createEventDispatcher } from 'svelte'
export let label: string = ''
export let active: boolean = false
export let object: Doc | undefined
const dispatch = createEventDispatcher()
const showMenu = async (ev: MouseEvent, object?: Doc): Promise<void> => {
if (object !== undefined) {
showPopup(ContextMenu, { object }, ev.target as HTMLElement)
}
}
</script>
<!-- svelte-ignore a11y-mouse-events-have-key-events -->
<div
class="flex-row-center container" class:active={active}
on:click|stopPropagation={() => {
dispatch('click')
}}
>
<span>
{label}
</span>
{#if object}
<div class="menuRow" on:click={(ev) => showMenu(ev, object)}><IconMoreV size={'small'} /></div>
{/if}
</div>
<style lang="scss">
.container {
min-height: 36px;
font-weight: 500;
color: var(--theme-caption-color);
border-radius: 8px;
user-select: none;
cursor: pointer;
span {
flex-grow: 1;
margin-left: 1rem;
margin-right: 1rem;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
&.active {
background-color: var(--theme-button-bg-enabled);
}
&:hover {
background-color: var(--theme-button-bg-enabled);
.menuRow {
visibility: visible;
}
}
}
.menuRow {
visibility: hidden;
margin-left: 0.5rem;
opacity: 0.6;
cursor: pointer;
&:hover {
opacity: 1;
}
}
</style>

View File

@ -0,0 +1,116 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 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 { createQuery } from '@anticrm/presentation'
import { MessageTemplate } from '@anticrm/templates'
import { TextEditorHandler } from '@anticrm/text-editor'
import { closePopup, EditWithIcon, IconSearch, Label, ScrollBox } from '@anticrm/ui'
import templates from '../plugin'
import TemplateElement from './TemplateElement.svelte'
export let editor: TextEditorHandler
let items: MessageTemplate[] = []
let query: string = ''
const liveQuery = createQuery()
$: liveQuery.query(templates.class.MessageTemplate, query.trim().length === 0 ? {} : { $search: query }, (res) => {
items = res
})
let selected = 0
function dispatchItem (item: MessageTemplate): void {
editor.insertText(item.message)
closePopup()
}
export function onKeyDown (ev: KeyboardEvent) {
if (ev.key === 'ArrowDown') {
if (selected < items.length - 1) selected++
return true
}
if (ev.key === 'ArrowUp') {
if (selected > 0) selected--
return true
}
if (ev.key === 'Enter') {
const item = items[selected]
if (item) {
dispatchItem(item)
return true
} else {
return false
}
}
// TODO: How to prevent Esc, it should hide popup instead of closing editor.
if (ev.key === 'Esc') {
return false
}
return false
}
</script>
<svelte:window on:keydown={onKeyDown}/>
<div class="antiPopup template-popup">
<div class="mt-4 mb-4">
<EditWithIcon icon={IconSearch} bind:value={query} placeholder={templates.string.SearchTemplate} />
</div>
<Label label={templates.string.Suggested} />
<div class='scroll mt-2'>
{#each items as item, i}
<div
class="item"
class:selected={i === selected}
on:click={() => {
dispatchItem(item)
}}
on:focus={() => {
selected = i
}}
on:mouseover={() => {
selected = i
}}>
{item.title}
</div>
{/each}
</div>
</div>
<style lang="scss">
.template-popup {
width: 19.75rem;
height: 18.5rem;
padding: 16px;
background-color: var(--theme-button-bg-hovered);
.selected {
background-color: var(--theme-button-bg-focused);
}
.scroll {
max-height: calc(300px - 128px);
display: grid;
grid-auto-flow: row;
gap: 12px;
overflow-y: auto;
.item {
padding: 5px;
}
}
}
</style>

View File

@ -0,0 +1,182 @@
<script lang="ts">
import { Data, Ref } from '@anticrm/core'
import { getClient, LiveQuery, MessageViewer } from '@anticrm/presentation'
import { MessageTemplate } from '@anticrm/templates'
import { StyledTextEditor } from '@anticrm/text-editor'
import { Button, CircleButton, EditBox, Icon, IconAdd, Label, ScrollBox } from '@anticrm/ui'
import templatesPlugin from '../plugin'
import TemplateElement from './TemplateElement.svelte'
const client = getClient()
const query = new LiveQuery()
let templates: MessageTemplate[] = []
let selected: Ref<MessageTemplate> | undefined
let newTemplate: Data<MessageTemplate> | undefined = undefined
query.query(templatesPlugin.class.MessageTemplate, {}, (t) => {
templates = t
if (templates.findIndex(t => t._id === selected) === -1) {
selected = undefined
newTemplate = undefined
}
})
const Mode = {
View: 1,
Edit: 2,
Create: 3
}
let mode = Mode.View
async function addTemplate (): Promise<void> {
newTemplate = {
title: '',
message: ''
}
mode = Mode.Create
}
async function saveNewTemplate (): Promise<void> {
if (newTemplate === undefined) {
return
}
if (mode === Mode.Create) {
if (newTemplate.title.trim().length > 0) {
const ref = await client.createDoc(templatesPlugin.class.MessageTemplate, templatesPlugin.space.Templates, newTemplate)
selected = ref
}
} else if (selected !== undefined) {
await client.updateDoc(templatesPlugin.class.MessageTemplate, templatesPlugin.space.Templates, selected, newTemplate)
}
mode = Mode.View
}
let textEditor: StyledTextEditor
export function submit (): void {
textEditor.submit()
}
</script>
<div class="flex-between navheader-container">
<span class="fs-title overflow-label flex-row-center">
<div class="mr-2">
<Icon icon={templatesPlugin.icon.Templates} size={'medium'} />
</div>
<Label label={templatesPlugin.string.Templates} />
</span>
</div>
<div class="flex flex-grow">
<div class="tempaltes-nav flex-row">
<div class="flex-between flex-reverse">
<CircleButton icon={IconAdd} on:click={addTemplate} />
<Label label={templatesPlugin.string.TemplatesHeader} />
</div>
<div class="templates flex-row">
<ScrollBox vertical stretch>
{#each templates as t}
<TemplateElement
label={t.title}
active={newTemplate === undefined && t._id === selected}
on:click={() => {
selected = t._id
newTemplate = { title: t.title, message: t.message }
mode = Mode.View
}}
object={t}
/>
{/each}
</ScrollBox>
</div>
</div>
<div class="templates-edit">
{#if newTemplate}
<span class="fs-title overflow-label">
{#if mode === Mode.Create}
<Label label={templatesPlugin.string.CreateTemplate} />
{:else if mode === Mode.Edit}
<Label label={templatesPlugin.string.EditTemplate} />
{:else}
<Label label={templatesPlugin.string.ViewTemplate} />
{/if}
</span>
<div class="titleedit mt-4 mb-4">
{#if mode !== Mode.View}
<EditBox bind:value={newTemplate.title} maxWidth={'12rem'} placeholder={templatesPlugin.string.TemplatePlaceholder} />
{:else}
{newTemplate.title}
{/if}
</div>
{#if mode !== Mode.View}
<StyledTextEditor
bind:content={newTemplate.message}
bind:this={textEditor}
on:value={(evt) => {
newTemplate = { title: newTemplate?.title ?? '', message: evt.detail }
}}>
<div class="flex flex-reverse flex-reverse flex-grow">
<div class="ml-2">
<Button disabled={newTemplate.title.trim().length == 0 } primary label={templatesPlugin.string.SaveTemplate} on:click={saveNewTemplate} />
</div>
<Button
label={templatesPlugin.string.Cancel}
on:click={() => {
if (mode === Mode.Create) {
newTemplate = undefined
}
mode = Mode.View
}}
/>
</div>
</StyledTextEditor>
{:else}
<div class='text'>
<MessageViewer message={newTemplate.message}/>
</div>
<div class='flex flex-reverse'>
<Button primary label={templatesPlugin.string.EditTemplate} on:click={() => { mode = Mode.Edit }} />
</div>
{/if}
{/if}
</div>
</div>
<style lang="scss">
.navheader-container {
padding: 0 1.75rem;
height: 4rem;
border-bottom: 1px solid var(--theme-menu-divider);
}
.tempaltes-nav {
width: 425px;
border-right: 1px solid var(--theme-menu-divider);
padding: 2rem;
display: flex;
flex-direction: column;
}
.templates-edit {
flex-grow: 1;
display: flex;
flex-direction: column;
padding: 2rem;
background-color: var(--theme-button-bg-enabled);
.titleedit {
font-size: 18px;
border-bottom: 1px solid var(--theme-menu-divider);
padding-bottom: 2rem;
}
}
.templates {
margin-top: 2rem;
flex-grow: 1;
display: flex;
flex-direction: column;
gap: 12px;
}
.text {
flex-grow: 1;
line-height: 150%;
}
</style>

View File

@ -0,0 +1,34 @@
//
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 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.
//
import { Resources } from '@anticrm/platform'
import Templates from './components/Templates.svelte'
import { TextEditorHandler } from '@anticrm/text-editor'
import { showPopup } from '@anticrm/ui'
import TemplatePopup from './components/TemplatePopup.svelte'
function ShowTemplates (element: HTMLElement, editor: TextEditorHandler): void {
showPopup(TemplatePopup, { editor }, element)
}
export default async (): Promise<Resources> => ({
component: {
Templates
},
action: {
ShowTemplates
}
})

View File

@ -0,0 +1,37 @@
//
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 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.
//
import { IntlString, mergeIds } from '@anticrm/platform'
import { AnyComponent } from '@anticrm/ui'
import templates, { templatesId } from '@anticrm/templates'
export default mergeIds(templatesId, templates, {
string: {
Cancel: '' as IntlString,
Templates: '' as IntlString,
TemplatesHeader: '' as IntlString,
CreateTemplate: '' as IntlString,
ViewTemplate: '' as IntlString,
EditTemplate: '' as IntlString,
SaveTemplate: '' as IntlString,
Suggested: '' as IntlString,
SearchTemplate: '' as IntlString,
TemplatePlaceholder: '' as IntlString
},
component: {
Templates: '' as AnyComponent
}
})

View File

@ -0,0 +1,5 @@
const sveltePreprocess = require('svelte-preprocess')
module.exports = {
preprocess: sveltePreprocess()
};

View File

@ -0,0 +1,15 @@
{
"compilerOptions": {
"moduleResolution": "node",
"target": "esnext",
"module": "esnext",
"declaration": true,
"outDir": "./lib",
"strict": true,
"esModuleInterop": true,
"lib": [
"esnext",
"dom"
]
}
}

View File

@ -0,0 +1,7 @@
module.exports = {
extends: ['./node_modules/@anticrm/platform-rig/profiles/default/config/eslint.config.json'],
parserOptions: {
tsconfigRootDir: __dirname,
project: './tsconfig.json'
}
}

View File

@ -0,0 +1,4 @@
*
!/lib/**
!CHANGELOG.md
/lib/**/__tests__/

View File

@ -0,0 +1,18 @@
// The "rig.json" file directs tools to look for their config files in an external package.
// Documentation for this system: https://www.npmjs.com/package/@rushstack/rig-package
{
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
/**
* (Required) The name of the rig package to inherit from.
* It should be an NPM package name with the "-rig" suffix.
*/
"rigPackageName": "@anticrm/platform-rig"
/**
* (Optional) Selects a config profile from the rig package. The name must consist of
* lowercase alphanumeric words separated by hyphens, for example "sample-profile".
* If omitted, then the "default" profile will be used."
*/
// "rigProfile": "your-profile-name"
}

View File

@ -0,0 +1,34 @@
{
"name": "@anticrm/templates",
"version": "0.6.0",
"main": "lib/index.js",
"author": "Anticrm Platform Contributors",
"license": "EPL-2.0",
"scripts": {
"build": "heft build",
"build:watch": "tsc",
"lint:fix": "eslint --fix src",
"lint": "eslint src",
"format": "prettier --write src && eslint --fix src"
},
"devDependencies": {
"@anticrm/platform-rig": "~0.6.0",
"@types/heft-jest": "^1.0.2",
"@typescript-eslint/eslint-plugin": "^5.4.0",
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-promise": "^5.1.1",
"eslint-plugin-node": "^11.1.0",
"eslint": "^7.32.0",
"@typescript-eslint/parser": "^5.4.0",
"eslint-config-standard-with-typescript": "^21.0.1",
"prettier": "^2.4.1",
"@rushstack/heft": "^0.41.1",
"typescript": "^4.3.5"
},
"dependencies": {
"@anticrm/platform": "~0.6.5",
"@anticrm/core": "~0.6.11",
"@anticrm/ui": "~0.6.0",
"@anticrm/setting": "~0.6.0"
}
}

View File

@ -0,0 +1,43 @@
//
// Copyright © 2020, 2021 Anticrm Platform Contributors.
//
// 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.
//
import type { Class, Doc, Ref, Space } from '@anticrm/core'
import type { Plugin } from '@anticrm/platform'
import { Asset, plugin } from '@anticrm/platform'
/**
* @public
*/
export interface MessageTemplate extends Doc {
title: string
message: string
}
/**
* @public
*/
export const templatesId = 'templates' as Plugin
export default plugin(templatesId, {
class: {
MessageTemplate: '' as Ref<Class<MessageTemplate>>
},
space: {
Templates: '' as Ref<Space>
},
icon: {
Templates: '' as Asset
}
})

View File

@ -0,0 +1,9 @@
{
"extends": "./node_modules/@anticrm/platform-rig/profiles/default/tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib",
"lib": ["esnext", "dom"]
}
}

View File

@ -14,18 +14,16 @@
-->
<script lang="ts">
import { getCurrentLocation, Label, navigate, setMetadataLocalStorage } from '@anticrm/ui'
import { Avatar, createQuery, getClient } from '@anticrm/presentation'
import workbench, { Application, SpecialNavModel } from '@anticrm/workbench'
import setting from '@anticrm/setting'
import login from '@anticrm/login'
import { getCurrentAccount, Ref } from '@anticrm/core'
import contact, { Employee, EmployeeAccount, formatName } from '@anticrm/contact'
import { getCurrentAccount, Ref } from '@anticrm/core'
import login from '@anticrm/login'
import { Avatar, createQuery, getClient } from '@anticrm/presentation'
import setting, { SettingsCategory } from '@anticrm/setting'
import { closePopup, getCurrentLocation, Icon, Label, navigate, setMetadataLocalStorage } from '@anticrm/ui'
const client = getClient()
async function getItems(): Promise<SpecialNavModel[] | undefined> {
const app = await client.findOne(workbench.class.Application, { _id: setting.ids.SettingApp as Ref<Application> })
return app?.navigatorModel?.specials
async function getItems (): Promise<SettingsCategory[]> {
return await client.findAll(setting.class.SettingsCategory, {}, { sort: { order: 1 } })
}
let account: EmployeeAccount | undefined
@ -45,10 +43,11 @@
}, { limit: 1 })
function selectSpecial (sp: SpecialNavModel): void {
function selectCategory (sp: SettingsCategory): void {
closePopup()
const loc = getCurrentLocation()
loc.path[1] = setting.ids.SettingApp
loc.path[2] = sp.id
loc.path[2] = sp.name
loc.path.length = 3
navigate(loc)
}
@ -60,14 +59,14 @@
navigate({ path: [login.component.LoginApp] })
}
function filterItems (items: SpecialNavModel[]): SpecialNavModel[] {
return items?.filter((p) => p.id !== 'profile' && p.id !== 'password')
function filterItems (items: SettingsCategory[]): SettingsCategory[] {
return items?.filter((p) => p.name !== 'profile' && p.name !== 'password')
}
function editProfile (items: SpecialNavModel[] | undefined): void {
const profile = items?.find((p) => p.id === 'profile')
function editProfile (items: SettingsCategory[] | undefined): void {
const profile = items?.find((p) => p.name === 'profile')
if (profile === undefined) return
selectSpecial(profile)
selectCategory(profile)
}
</script>
@ -88,10 +87,15 @@
<div class="content">
{#if items}
{#each filterItems(items) as item }
<div class="item" on:click={() => selectSpecial(item)}><Label label={item.label} /></div>
<div class="item flex-row-center" on:click={() => selectCategory(item)}>
<div class='mr-2'>
<Icon icon={item.icon} size={'x-small'}/>
</div>
<Label label={item.label} />
</div>
{/each}
{/if}
<div class="item" on:click={signOut}><Label label={'Sign out'} /></div>
<div class="item" on:click={signOut}><Label label={setting.string.Signout} /></div>
</div>
{/await}
</div>

View File

@ -172,14 +172,14 @@
<div class="panel-component">
{#if currentApplication && currentApplication.component}
<Component is={currentApplication.component} />
{/if}
{#if ownSpecialComponent}
<svelte:component this={ownSpecialComponent} model={navigatorModel} />
{:else if specialComponent}
<Component is={specialComponent} />
{:else}
<SpaceView {currentSpace} {currentView} {createItemDialog} />
{#if ownSpecialComponent}
<svelte:component this={ownSpecialComponent} model={navigatorModel} />
{:else if specialComponent}
<Component is={specialComponent} />
{:else}
<SpaceView {currentSpace} {currentView} {createItemDialog} />
{/if}
{/if}
</div>
<!-- <div class="aside"><Chat thread/></div> -->

View File

@ -940,6 +940,31 @@
"packageName": "@anticrm/inventory-resources",
"projectFolder": "plugins/inventory-resources",
"shouldPublish": true
},
{
"packageName": "@anticrm/templates",
"projectFolder": "plugins/templates",
"shouldPublish": true
},
{
"packageName": "@anticrm/templates-assets",
"projectFolder": "plugins/templates-assets",
"shouldPublish": true
},
{
"packageName": "@anticrm/templates-resources",
"projectFolder": "plugins/templates-resources",
"shouldPublish": true
},
{
"packageName": "@anticrm/model-templates",
"projectFolder": "models/templates",
"shouldPublish": true
},
{
"packageName": "@anticrm/model-text-editor",
"projectFolder": "models/text-editor",
"shouldPublish": true
}
]
}