Merge branch 'main' into ano/attachments-initial

# Conflicts:
#	plugins/board-resources/src/components/editor/CardActions.svelte
This commit is contained in:
Anna No
2022-04-14 21:26:30 +07:00
123 changed files with 2232 additions and 491 deletions
+122 -9
View File
@@ -53,6 +53,7 @@ specifiers:
'@rush-temp/login': file:./projects/login.tgz
'@rush-temp/login-assets': file:./projects/login-assets.tgz
'@rush-temp/login-resources': file:./projects/login-resources.tgz
'@rush-temp/middleware': file:./projects/middleware.tgz
'@rush-temp/model': file:./projects/model.tgz
'@rush-temp/model-activity': file:./projects/model-activity.tgz
'@rush-temp/model-all': file:./projects/model-all.tgz
@@ -67,6 +68,7 @@ specifiers:
'@rush-temp/model-inventory': file:./projects/model-inventory.tgz
'@rush-temp/model-lead': file:./projects/model-lead.tgz
'@rush-temp/model-notification': file:./projects/model-notification.tgz
'@rush-temp/model-preference': file:./projects/model-preference.tgz
'@rush-temp/model-presentation': file:./projects/model-presentation.tgz
'@rush-temp/model-recruit': file:./projects/model-recruit.tgz
'@rush-temp/model-rig': file:./projects/model-rig.tgz
@@ -101,6 +103,8 @@ specifiers:
'@rush-temp/platform': file:./projects/platform.tgz
'@rush-temp/platform-rig': file:./projects/platform-rig.tgz
'@rush-temp/pod-account': file:./projects/pod-account.tgz
'@rush-temp/preference': file:./projects/preference.tgz
'@rush-temp/preference-assets': file:./projects/preference-assets.tgz
'@rush-temp/presentation': file:./projects/presentation.tgz
'@rush-temp/prod': file:./projects/prod.tgz
'@rush-temp/prod-tracker': file:./projects/prod-tracker.tgz
@@ -127,6 +131,7 @@ specifiers:
'@rush-temp/server-lead-resources': file:./projects/server-lead-resources.tgz
'@rush-temp/server-notification': file:./projects/server-notification.tgz
'@rush-temp/server-notification-resources': file:./projects/server-notification-resources.tgz
'@rush-temp/server-preference': file:./projects/server-preference.tgz
'@rush-temp/server-recruit': file:./projects/server-recruit.tgz
'@rush-temp/server-recruit-resources': file:./projects/server-recruit-resources.tgz
'@rush-temp/server-setting': file:./projects/server-setting.tgz
@@ -320,6 +325,7 @@ dependencies:
'@rush-temp/login': file:projects/login.tgz
'@rush-temp/login-assets': file:projects/login-assets.tgz_typescript@4.5.4
'@rush-temp/login-resources': file:projects/login-resources.tgz_096c09b0b673a57c275d9767a12070b1
'@rush-temp/middleware': file:projects/middleware.tgz
'@rush-temp/model': file:projects/model.tgz
'@rush-temp/model-activity': file:projects/model-activity.tgz_typescript@4.5.4
'@rush-temp/model-all': file:projects/model-all.tgz_typescript@4.5.4
@@ -334,6 +340,7 @@ dependencies:
'@rush-temp/model-inventory': file:projects/model-inventory.tgz_typescript@4.5.4
'@rush-temp/model-lead': file:projects/model-lead.tgz_typescript@4.5.4
'@rush-temp/model-notification': file:projects/model-notification.tgz_typescript@4.5.4
'@rush-temp/model-preference': file:projects/model-preference.tgz_typescript@4.5.4
'@rush-temp/model-presentation': file:projects/model-presentation.tgz_typescript@4.5.4
'@rush-temp/model-recruit': file:projects/model-recruit.tgz_typescript@4.5.4
'@rush-temp/model-rig': file:projects/model-rig.tgz_37f79b97d0d86442e45d380c86f520c5
@@ -368,6 +375,8 @@ dependencies:
'@rush-temp/platform': file:projects/platform.tgz
'@rush-temp/platform-rig': file:projects/platform-rig.tgz_37f79b97d0d86442e45d380c86f520c5
'@rush-temp/pod-account': file:projects/pod-account.tgz
'@rush-temp/preference': file:projects/preference.tgz
'@rush-temp/preference-assets': file:projects/preference-assets.tgz
'@rush-temp/presentation': file:projects/presentation.tgz_096c09b0b673a57c275d9767a12070b1
'@rush-temp/prod': file:projects/prod.tgz_a07ec81d4d975778878ca12202ea119e
'@rush-temp/prod-tracker': file:projects/prod-tracker.tgz_a07ec81d4d975778878ca12202ea119e
@@ -394,6 +403,7 @@ dependencies:
'@rush-temp/server-lead-resources': file:projects/server-lead-resources.tgz
'@rush-temp/server-notification': file:projects/server-notification.tgz
'@rush-temp/server-notification-resources': file:projects/server-notification-resources.tgz
'@rush-temp/server-preference': file:projects/server-preference.tgz
'@rush-temp/server-recruit': file:projects/server-recruit.tgz
'@rush-temp/server-recruit-resources': file:projects/server-recruit-resources.tgz
'@rush-temp/server-setting': file:projects/server-setting.tgz
@@ -11738,7 +11748,7 @@ packages:
dev: false
file:projects/board-resources.tgz_096c09b0b673a57c275d9767a12070b1:
resolution: {integrity: sha512-vNNtH4in7D54mv4ckcYvE+iLxqt10x0kpToPg+xMRl4LuXIehuKDM+x9uiSb61y7FBesLyrJ3+606D99cc+1fQ==, tarball: file:projects/board-resources.tgz}
resolution: {integrity: sha512-nI0XMUXXwKiWWIwh1LHsD6P+VjNPy1M1NN3VBxLzPFsG6TGmEX/RHZhR9a6Ao6YwY783n8TfdrajFABs8IrGPg==, tarball: file:projects/board-resources.tgz}
id: file:projects/board-resources.tgz
name: '@rush-temp/board-resources'
version: 0.0.0
@@ -11773,7 +11783,7 @@ packages:
dev: false
file:projects/board.tgz:
resolution: {integrity: sha512-K6XdiXv9w+xvcegm1Ql7XmBXIR/nMElWROWkVabz/SGhrvmENtjOa6mKVZploQf5cTafItd1V2e/NxZu8DnrgA==, tarball: file:projects/board.tgz}
resolution: {integrity: sha512-Jgyvcqd3njP8w/I6a1M7PNNCQWojSyNBzzVauBja3IRaFLMgFv7g3zqbilHkJw1IoYpsLab8wZoJ7X4+TM1s6w==, tarball: file:projects/board.tgz}
name: '@rush-temp/board'
version: 0.0.0
dependencies:
@@ -12770,6 +12780,26 @@ packages:
- supports-color
dev: false
file:projects/middleware.tgz:
resolution: {integrity: sha512-beRGnoVJZVP/5mEU9bxmXLikwWCRLXBWaeFyIGIZbgjHypFCY57237qoOpLEgXwmBzEUk2jhophOYiT+Xd4qLQ==, tarball: file:projects/middleware.tgz}
name: '@rush-temp/middleware'
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/model-activity.tgz_typescript@4.5.4:
resolution: {integrity: sha512-TslFvrgSosU016NXZCahvZcTkVGONQ37mfJCHJ/PRA9I33M++MaIlw/ZIhWl9Z/e8dCuYW8PVrlijoYwCmc+kg==, tarball: file:projects/model-activity.tgz}
id: file:projects/model-activity.tgz
@@ -12792,7 +12822,7 @@ packages:
dev: false
file:projects/model-all.tgz_typescript@4.5.4:
resolution: {integrity: sha512-RLEP3hoSiGNZeIxc4bRN7FQFcFFz4Sv6E2rF1tQHkBIIFJdUSVxDk4xwkopcU5UNNZyoW1UrwlG5jXOzWV9Pkg==, tarball: file:projects/model-all.tgz}
resolution: {integrity: sha512-NxbYGWKtCvK3tUbHGFiHRNdc/AmAFb9WWma26yTTzkRBM0ZLgXFfNqNaloQad+dXa1NR0SqpCKswdhelZ1QYgg==, tarball: file:projects/model-all.tgz}
id: file:projects/model-all.tgz
name: '@rush-temp/model-all'
version: 0.0.0
@@ -13047,6 +13077,27 @@ packages:
- typescript
dev: false
file:projects/model-preference.tgz_typescript@4.5.4:
resolution: {integrity: sha512-VA7EbuQe0imqaTsLhaXC83m/EB+vTdMRhCDU6AsPsU+Ay0eXTuebZucwwFh92lQ0fDbu1KDJTFcyPxpWwscSXw==, tarball: file:projects/model-preference.tgz}
id: file:projects/model-preference.tgz
name: '@rush-temp/model-preference'
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-presentation.tgz_typescript@4.5.4:
resolution: {integrity: sha512-nJF6vTxtGM/Q3nvEyhNk7LBiTrnCQpDLMco/zvSyLsRhjrA8yrAZwo3hdIK9K22Sic8Rl1vw6et/DH7CjGXjAA==, tarball: file:projects/model-presentation.tgz}
id: file:projects/model-presentation.tgz
@@ -13832,6 +13883,47 @@ packages:
- supports-color
dev: false
file:projects/preference-assets.tgz:
resolution: {integrity: sha512-ppUED31aeD7XmNevsK2XDz527e+gbmV43y68t7bM/DfypniAvcPaNBZ20CzQETmHsBrHSdUNTGFMv3HKMtmzNA==, tarball: file:projects/preference-assets.tgz}
name: '@rush-temp/preference-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/preference.tgz:
resolution: {integrity: sha512-WGzTspMfF/YBgM5AlF26hbbPD9Qbj4Vp7AL0DJeescfvY2MF4rFQ+wJK5ewuFWbDYGVu0kMJiszRP/7OVXqHFQ==, tarball: file:projects/preference.tgz}
name: '@rush-temp/preference'
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/presentation.tgz_096c09b0b673a57c275d9767a12070b1:
resolution: {integrity: sha512-wc5U6pu+2JFrN7NknB2nCs3qbDEh5AgNhRfoTFBPHOxsBplCEtNtUvHtt91pY2lGlgQVZvNsLKEhaDKycVF19w==, tarball: file:projects/presentation.tgz}
id: file:projects/presentation.tgz
@@ -13869,7 +13961,7 @@ packages:
dev: false
file:projects/prod-tracker.tgz_a07ec81d4d975778878ca12202ea119e:
resolution: {integrity: sha512-dl5eoirwPVY6dcl4eNTnk0BUpGnXVxppdq2EQd1S81HGk0zT+2X76KJaZ3sUf7byks3kNFl4rFmWOn+JLrpXNA==, tarball: file:projects/prod-tracker.tgz}
resolution: {integrity: sha512-DegdZVFP21HUwqbhSqazxR2nu9OqSlJ8QxOn1S3IOD9AGi7H5OOSvw6E/RItOlGAts0bi261Z81Dgi9EJvTtvA==, tarball: file:projects/prod-tracker.tgz}
id: file:projects/prod-tracker.tgz
name: '@rush-temp/prod-tracker'
version: 0.0.0
@@ -13915,7 +14007,7 @@ packages:
dev: false
file:projects/prod.tgz_a07ec81d4d975778878ca12202ea119e:
resolution: {integrity: sha512-VvhwhIktsLPXGK+aXdKgBVK9CbQkWIVyYy+MmgjEDIpMcoNWbZzL/fdu+Dw/F1OT80Vq01Y7aDeN8TaoHJkWZg==, tarball: file:projects/prod.tgz}
resolution: {integrity: sha512-WaWihWZzBZ9WDVbJtB+fFusKyv8pR4hbBAZGkpcZ0yiOQL4tL5R0ZFsh0TxDy1TD68XKFqF2FnM3ngNQQr8bKA==, tarball: file:projects/prod.tgz}
id: file:projects/prod.tgz
name: '@rush-temp/prod'
version: 0.0.0
@@ -14444,6 +14536,27 @@ packages:
- supports-color
dev: false
file:projects/server-preference.tgz:
resolution: {integrity: sha512-YYnxaZnUVlY/TL829SCP22F6jd03uQROfLRH7oqTH9tnBPaDvr66Zpb4Qyyw/lK2zsNFuZRbJHSGPF45fsa+Zg==, tarball: file:projects/server-preference.tgz}
name: '@rush-temp/server-preference'
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/server-recruit-resources.tgz:
resolution: {integrity: sha512-TM38Nr2D+for8rBa4J83JMrgVC6wAFoOhXjCaBZTLceLtec+43JObZjd27ZE+7N0fyB57ONxZOADN381res5wg==, tarball: file:projects/server-recruit-resources.tgz}
name: '@rush-temp/server-recruit-resources'
@@ -14728,7 +14841,7 @@ packages:
dev: false
file:projects/server.tgz:
resolution: {integrity: sha512-TEYNvDfpWbWWHcltHwEyC1B81v5c9UUyGmNx9C9sdItiWxLb8P/lQ+a+xca+5eJ+FQmRxm3VJywrvscmttTfPg==, tarball: file:projects/server.tgz}
resolution: {integrity: sha512-6vh78sMqcZsw0xfPyn3wQfOBG0e2jWhVLU8zsSvN1m9bFOuuf7strkWQWun+c5dkgtd6vvd57yh0ihz4BDMkZw==, tarball: file:projects/server.tgz}
name: '@rush-temp/server'
version: 0.0.0
dependencies:
@@ -14934,7 +15047,7 @@ packages:
dev: false
file:projects/task-resources.tgz_6cae74ea501386c76c405ad0408e0339:
resolution: {integrity: sha512-YOuOktJH1qu9wET1Zf204iCCzaL8yT6HjNuQr/u3ib921oc0nz0kdrw9UVWsMGabA2xkzJxT1BjGQFPPGgFTvA==, tarball: file:projects/task-resources.tgz}
resolution: {integrity: sha512-RGVNKBNCTeRoBjCTl05vX1s7rHV02HUFdxSd3QjZIUYSUo+09fAnaVSeohOq0aXy+R4vvd8shkZuppJupufu7A==, tarball: file:projects/task-resources.tgz}
id: file:projects/task-resources.tgz
name: '@rush-temp/task-resources'
version: 0.0.0
@@ -15312,7 +15425,7 @@ packages:
dev: false
file:projects/tracker-resources.tgz_096c09b0b673a57c275d9767a12070b1:
resolution: {integrity: sha512-am69PuPAqWdxUR/7fLKNvmEvfAxMPptRLgABjeQmvBFfS7/RTRFsjz2y+aiLUoMVUktcfHIIMWXxWx/otsmPVw==, tarball: file:projects/tracker-resources.tgz}
resolution: {integrity: sha512-2Ap2E6dyQwwxZqOqSHVvQzjCVVuisPK/llz6Qe9I24qb7QvZk4sZrccxpVYNhj4n1FrvNkk9tjccb13eegCAow==, tarball: file:projects/tracker-resources.tgz}
id: file:projects/tracker-resources.tgz
name: '@rush-temp/tracker-resources'
version: 0.0.0
@@ -15502,7 +15615,7 @@ packages:
dev: false
file:projects/workbench-resources.tgz_096c09b0b673a57c275d9767a12070b1:
resolution: {integrity: sha512-PB/gIO0hbQwlQ3b3RlHWf0ZcsjET1b4nuzS8sTOs6xDdtVZbWH3xTbms89OiLW0znBpHTsKRV5BIacXKe9cc+g==, tarball: file:projects/workbench-resources.tgz}
resolution: {integrity: sha512-VMERIjagr6R/HiIj4wyaMCkQBe3K5Lck4JLs9qBw6U025TSH+6aJeJCpo7+lF/yEfANR+Y5HMbCSlohp9GMgaA==, tarball: file:projects/workbench-resources.tgz}
id: file:projects/workbench-resources.tgz
name: '@rush-temp/workbench-resources'
version: 0.0.0
+2
View File
@@ -105,6 +105,8 @@
"@anticrm/notification": "~0.6.0",
"@anticrm/notification-assets": "~0.6.0",
"@anticrm/notification-resources": "~0.6.0",
"@anticrm/preference": "~0.6.0",
"@anticrm/preference-assets": "~0.6.0",
"@anticrm/core": "~0.6.16",
"@anticrm/rekoni": "~0.6.0",
"@anticrm/tags-assets": "~0.6.0",
+3 -1
View File
@@ -1,5 +1,5 @@
//
// Copyright © 2020 Anticrm Platform Contributors.
// Copyright © 2022 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
@@ -34,6 +34,7 @@ import { imageCropperId } from '@anticrm/image-cropper'
import { inventoryId } from '@anticrm/inventory'
import { templatesId } from '@anticrm/templates'
import { notificationId } from '@anticrm/notification'
import { preferenceId } from '@anticrm/preference'
import { tagsId } from '@anticrm/tags'
import { calendarId } from '@anticrm/calendar'
import { trackerId } from '@anticrm/tracker'
@@ -60,6 +61,7 @@ import '@anticrm/tags-assets'
import '@anticrm/calendar-assets'
import '@anticrm/tracker-assets'
import '@anticrm/board-assets'
import '@anticrm/preference-assets'
import presentation, { presentationId } from '@anticrm/presentation'
import { coreId } from '@anticrm/core'
import { textEditorId } from '@anticrm/text-editor'
+2 -3
View File
@@ -17,8 +17,7 @@
import type { Doc, Ref, TxResult } from '@anticrm/core'
import { DOMAIN_TX, MeasureMetricsContext } from '@anticrm/core'
import { createInMemoryAdapter, createInMemoryTxAdapter } from '@anticrm/dev-storage'
import type { DbConfiguration } from '@anticrm/server-core'
import { createServerStorage, FullTextAdapter, IndexedDoc } from '@anticrm/server-core'
import { createPipeline, DbConfiguration, FullTextAdapter, IndexedDoc } from '@anticrm/server-core'
import { start as startJsonRpc } from '@anticrm/server-ws'
class NullFullTextAdapter implements FullTextAdapter {
@@ -71,6 +70,6 @@ export async function start (port: number, host?: string): Promise<void> {
},
workspace: ''
}
return createServerStorage(conf)
return createPipeline(conf, [])
}, port, host)
}
+3 -1
View File
@@ -68,6 +68,8 @@
"@anticrm/model-server-gmail": "~0.6.0",
"@anticrm/model-server-telegram": "~0.6.0",
"@anticrm/model-tracker": "~0.6.0",
"@anticrm/model-board": "~0.6.0"
"@anticrm/model-board": "~0.6.0",
"@anticrm/model-preference": "~0.6.0"
}
}
+3 -1
View File
@@ -1,5 +1,5 @@
//
// Copyright © 2020 Anticrm Platform Contributors.
// Copyright © 2022 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
@@ -54,6 +54,7 @@ import { createModel as serverGmailModel } from '@anticrm/model-server-gmail'
import { createModel as serverTelegramModel } from '@anticrm/model-server-telegram'
import { createModel as trackerModel } from '@anticrm/model-tracker'
import { createModel as boardModel } from '@anticrm/model-board'
import { createModel as preferenceModel } from '@anticrm/model-preference'
export const version: Data<Version> = jsonVersion as Data<Version>
@@ -78,6 +79,7 @@ const builders: [(b: Builder) => void, string][] = [
[templatesModel, 'templates'],
[textEditorModel, 'text-editor'],
[notificationModel, 'notification'],
[preferenceModel, 'preference'],
[serverCoreModel, 'server-core'],
[serverAttachmentModel, 'server-attachment'],
+3
View File
@@ -35,6 +35,9 @@ export class TChannel extends TSpace implements Channel {
@Prop(TypeTimestamp(), chunter.string.LastMessage)
lastMessage?: Timestamp
@Prop(ArrOf(TypeRef(chunter.class.ChunterMessage)), chunter.string.PinnedMessages)
pinned?: Ref<ChunterMessage>[]
@Prop(TypeString(), chunter.string.Topic)
@Index(IndexKind.FullText)
topic?: string
+2 -1
View File
@@ -48,7 +48,8 @@ export default mergeIds(chunterId, chunter, {
CreateBy: '' as IntlString,
Create: '' as IntlString,
MarkUnread: '' as IntlString,
LastMessage: '' as IntlString
LastMessage: '' as IntlString,
PinnedMessages: '' as IntlString
},
viewlet: {
Chat: '' as Ref<ViewletDescriptor>
+7
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'
}
}
+4
View File
@@ -0,0 +1,4 @@
*
!/lib/**
!CHANGELOG.md
/lib/**/__tests__/
+18
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"
}
+35
View File
@@ -0,0 +1,35 @@
{
"name": "@anticrm/model-preference",
"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.16",
"@anticrm/model": "~0.6.0",
"@anticrm/ui": "~0.6.0",
"@anticrm/platform": "~0.6.5",
"@anticrm/model-core": "~0.6.0",
"@anticrm/preference": "~0.6.0"
}
}
+37
View File
@@ -0,0 +1,37 @@
//
// Copyright © 2022 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 { Doc, Ref, Space } from '@anticrm/core'
import { Builder, Model, Prop, TypeRef } from '@anticrm/model'
import core, { TDoc } from '@anticrm/model-core'
import preference, { DOMAIN_PREFERENCE, Preference, SpacePreference } from '@anticrm/preference'
@Model(preference.class.Preference, core.class.Doc, DOMAIN_PREFERENCE)
export class TPreference extends TDoc implements Preference {
@Prop(TypeRef(core.class.Doc), core.string.AttachedTo)
attachedTo!: Ref<Doc>
}
@Model(preference.class.SpacePreference, preference.class.Preference)
export class TSpacePreference extends TPreference implements SpacePreference {
@Prop(TypeRef(core.class.Space), core.string.Space)
declare attachedTo: Ref<Space>
}
export function createModel (builder: Builder): void {
builder.createModel(TPreference, TSpacePreference)
}
export { preference as default }
+8
View File
@@ -0,0 +1,8 @@
{
"extends": "./node_modules/@anticrm/model-rig/profiles/default/tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib",
}
}
+5 -2
View File
@@ -1,6 +1,5 @@
//
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 Hardcore Engineering Inc.
// Copyright © 2022 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
@@ -16,6 +15,8 @@
import { MeasureContext } from '.'
import type { Doc, Class, Ref } from './classes'
import { Hierarchy } from './hierarchy'
import { ModelDb } from './memdb'
import type { DocumentQuery, FindOptions, FindResult, TxResult } from './storage'
import type { Tx } from './tx'
@@ -23,6 +24,8 @@ import type { Tx } from './tx'
* @public
*/
export interface ServerStorage {
hierarchy: Hierarchy
modelDb: ModelDb
findAll: <T extends Doc>(
ctx: MeasureContext,
_class: Ref<Class<T>>,
+2 -1
View File
@@ -14,6 +14,7 @@
"Members": "Members",
"Search": "Search...",
"Unassigned": "Unassigned",
"CreateMore": "Create more"
"CreateMore": "Create more",
"NumberMembers": "{lenght, plural, =0 {no members} =1 {1 member} other {# members}}"
}
}
+2 -1
View File
@@ -14,6 +14,7 @@
"Members": "Участники",
"Search": "Поиск...",
"Unassigned": "Не назначен",
"CreateMore": "Создать еще"
"CreateMore": "Создать еще",
"NumberMembers": "{lenght, plural, =0 {нет участников} =1 {1 участик} other {# участника}}"
}
}
@@ -20,7 +20,7 @@
export let avatar: string | null | undefined = undefined
export let direct: Blob | undefined = undefined
export let size: 'x-small' | 'small' | 'medium' | 'large' | 'x-large'
export let size: 'inline' | 'x-small' | 'small' | 'medium' | 'large' | 'x-large'
let url: string | undefined
$: if (direct !== undefined) {
@@ -50,7 +50,7 @@
flex-shrink: 0;
position: relative;
overflow: hidden;
background-color: var(--popup-bg-hover);
background-color: var(--avatar-bg-color);
border-radius: 50%;
pointer-events: none;
@@ -61,6 +61,10 @@
&.no-img { border-color: transparent; }
}
.ava-inline {
width: .875rem; // 24
height: .875rem;
}
.ava-x-small {
width: 1.5rem; // 24
height: 1.5rem;
@@ -92,6 +96,7 @@
border-radius: 50%;
}
.ava-inline .ava-mask, .ava-inline.no-img,
.ava-x-small .ava-mask, .ava-x-small.no-img,
.ava-small .ava-mask, .ava-small.no-img,
.ava-medium .ava-mask, .ava-medium.no-img { border-style: none; }
@@ -0,0 +1,53 @@
<!--
// 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 type { Class, Doc, Ref } from '@anticrm/core'
import { createQuery } from '../utils'
import { Person } from '@anticrm/contact'
import Avatar from './Avatar.svelte'
export let _class: Ref<Class<Doc>>
export let items: Ref<Person>[] = []
export let size: 'inline' | 'x-small' | 'small' | 'medium' | 'large' | 'x-large'
export let limit: number = 3
let persons: Person[] = []
const query = createQuery()
$: query.query<Person>(_class, { _id: { $in: items } }, result => { persons = result }, { limit })
</script>
<div class="avatars-container">
{#each persons as person}
<div class="combine-avatar {size}">
<Avatar avatar={person.avatar} {size} />
</div>
{/each}
</div>
<style lang="scss">
.avatars-container {
display: flex;
align-items: center;
.combine-avatar.inline:not(:first-child) { margin-left: calc(1px - (.875rem / 2)); }
.combine-avatar.x-small:not(:first-child) { margin-left: calc(1px - (1.5rem / 2)); }
.combine-avatar.small:not(:first-child) { margin-left: calc(1px - 1rem); }
.combine-avatar.medium:not(:first-child) { margin-left: calc(1px - (2.25rem / 2)); }
.combine-avatar.large:not(:first-child) { margin-left: calc(1px - (4.5rem / 2)); }
.combine-avatar.x-large:not(:first-child) { margin-left: calc(1px - (7.5rem / 2)); }
.combine-avatar:not(:last-child) { mask: radial-gradient(circle at 100% 50%, rgba(0, 0, 0, 0) 48.5%, rgb(0, 0, 0) 50%); }
}
</style>
@@ -13,7 +13,6 @@
// limitations under the License.
-->
<script lang="ts">
import { onMount } from 'svelte'
import type { IntlString } from '@anticrm/platform'
import { getClient } from '../utils'
@@ -36,7 +36,7 @@
<div class="selectPopup">
<div class="header">
<input type='text' bind:value={search} placeholder={phTraslate} on:input={(ev) => { }} on:change/>
<input type='text' bind:value={search} placeholder={phTraslate} on:input={() => { }} on:change/>
</div>
<div class="scroll">
<div class="box">
@@ -18,7 +18,7 @@
import contact, { Contact, formatName } from '@anticrm/contact'
import type { Class, Ref } from '@anticrm/core'
import type { IntlString } from '@anticrm/platform'
import type { TooltipAligment } from '@anticrm/ui'
import type { TooltipAlignment, ButtonKind, ButtonSize } from '@anticrm/ui'
import { Button, Label, showPopup, Tooltip } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte'
import presentation from '..'
@@ -35,17 +35,16 @@
export let allowDeselect = false
export let titleDeselect: IntlString | undefined = undefined
export let readonly = false
export let kind: 'primary' | 'secondary' | 'no-border' | 'transparent' | 'link' | 'dangerous' = 'no-border'
export let size: 'small' | 'medium' | 'large' | 'x-large' = 'small'
export let kind: ButtonKind = 'no-border'
export let size: ButtonSize = 'small'
export let justify: 'left' | 'center' = 'center'
export let width: string | undefined = undefined
export let labelDirection: TooltipAligment | undefined = undefined
export let labelDirection: TooltipAlignment | undefined = undefined
const dispatch = createEventDispatcher()
let selected: Contact | undefined
let container: HTMLElement
let opened: boolean = false
const client = getClient()
@@ -62,14 +61,13 @@
</script>
<div bind:this={container} class="min-w-0">
<Tooltip label={label} fill={width === '100%'} direction={labelDirection}>
<Tooltip {label} fill={width === '100%'} direction={labelDirection}>
<Button
icon={(size === 'x-large' && selected) ? undefined : IconPerson}
width={width ?? 'min-content'}
{size} {kind} {justify}
on:click={() => {
if (!opened && !readonly) {
opened = true
if (!readonly) {
showPopup(UsersPopup, { _class, allowDeselect, selected: value, titleDeselect, placeholder }, container, (result) => {
if (result === null) {
value = null
@@ -79,7 +77,6 @@
value = result._id
dispatch('change', value)
}
opened = false
})
}
}}
@@ -15,18 +15,26 @@
<script lang="ts">
import { Person } from '@anticrm/contact'
import type { Class, Doc, Ref } from '@anticrm/core'
import { IntlString } from '@anticrm/platform'
import { ActionIcon, CircleButton, IconAdd, IconClose, Label, ShowMore, showPopup } from '@anticrm/ui'
import type { IntlString } from '@anticrm/platform'
import { translate } from '@anticrm/platform'
import type { ButtonKind, ButtonSize, TooltipAlignment } from '@anticrm/ui'
import { Tooltip, showPopup, Button } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte'
import { UserInfo } from '..'
import presentation, { CombineAvatars, UsersPopup } from '..'
import { createQuery } from '../utils'
import UsersPopup from './UsersPopup.svelte'
import Members from './icons/Members.svelte'
export let items: Ref<Person>[] = []
export let _class: Ref<Class<Doc>>
export let title: IntlString
export let label: IntlString
export let noItems: IntlString
export let kind: ButtonKind = 'no-border'
export let size: ButtonSize = 'small'
export let justify: 'left' | 'center' = 'center'
export let width: string | undefined = undefined
export let labelDirection: TooltipAlignment | undefined = undefined
let persons: Person[] = []
const query = createQuery()
@@ -37,71 +45,45 @@
const dispatch = createEventDispatcher()
async function addRef (person: Person): Promise<void> {
dispatch('open', person)
}
async function addPerson (evt: Event): Promise<void> {
showPopup(
UsersPopup,
{
_class,
title,
label,
multiSelect: true,
allowDeselect: false,
ignoreUsers: items
selectedUsers: items
},
evt.target as HTMLElement,
() => { },
(result) => {
// We have some value selected
if (result !== undefined) {
addRef(result)
items = result
dispatch('update', items)
}
}
)
}
async function removePerson (person: Person): Promise<void> {
dispatch('delete', person)
}
</script>
<div class="flex-row">
<ShowMore>
<div class="persons-container">
<div class="flex flex-reverse">
<div class="ml-4">
<CircleButton icon={IconAdd} size={'small'} selected on:click={addPerson} />
<Tooltip {label} fill={width === '100%'} direction={labelDirection}>
<Button
icon={persons.length === 0 ? Members : undefined}
label={persons.length === 0 ? presentation.string.Members : undefined}
width={width ?? 'min-content'}
{kind} {size} {justify}
on:click={addPerson}
>
<svelte:fragment slot="content">
{#if persons.length > 0}
<div class="flex-row-center flex-nowrap">
<CombineAvatars {_class} bind:items size={'inline'} />
{#await translate(presentation.string.NumberMembers, { lenght: persons.length }) then text}
<span class="ml-1-5">{text}</span>
{/await}
</div>
<div class="person-items">
{#if items?.length === 0}
<div class="flex flex-grow title-center">
<Label label={noItems} />
</div>
{/if}
{#each persons as person}
<div class="antiComponentBox flex-center antiComponentBoxFocused">
<UserInfo value={person} size={'x-small'} />
<div class="ml-1">
<ActionIcon icon={IconClose} size={'small'} action={() => removePerson(person)} />
</div>
</div>
{/each}
</div>
</div>
</div>
</ShowMore>
</div>
<style lang="scss">
.persons-container {
color: var(--theme-caption-color);
}
.person-items {
flex-grow: 1;
display: grid;
gap: 0.25rem;
grid-template-columns: repeat(3, 1fr);
}
.margin_025 {
margin: 0.25rem;
}
</style>
{/if}
</svelte:fragment>
</Button>
</Tooltip>
@@ -15,7 +15,7 @@
<script lang="ts">
import type { IntlString } from '@anticrm/platform'
import { translate } from '@anticrm/platform'
import { afterUpdate, createEventDispatcher } from 'svelte'
import { createEventDispatcher } from 'svelte'
import { Tooltip, CheckBox } from '@anticrm/ui'
import UserInfo from './UserInfo.svelte'
@@ -28,10 +28,11 @@
export let _class: Ref<Class<Person>>
export let selected: Ref<Person> | undefined
export let multiSelect: boolean = false
export let allowDeselect: boolean = false
export let titleDeselect: IntlString | undefined = undefined
export let placeholder: IntlString = presentation.string.Search
export let selectedUsers: Ref<Person>[] = []
export let ignoreUsers: Ref<Person>[] = []
let search: string = ''
@@ -43,23 +44,47 @@
let phTraslate: string = ''
$: if (placeholder) translate(placeholder, {}).then(res => { phTraslate = res })
afterUpdate(() => { dispatch('update', Date.now()) })
const isSelected = (person: Person): boolean => {
if (selectedUsers.filter(p => p === person._id).length > 0) return true
return false
}
const checkSelected = (person: Person): void => {
if (isSelected(person)) selectedUsers = selectedUsers.filter(p => p !== person._id)
else selectedUsers.push(person._id)
objects = objects
dispatch('update', selectedUsers)
}
</script>
<div class="selectPopup">
<div class="header">
<input type='text' bind:value={search} placeholder={phTraslate} on:input={(ev) => { }} on:change/>
<input type='text' bind:value={search} placeholder={phTraslate} on:change/>
</div>
<div class="scroll">
<div class="box">
{#each objects as person}
<button class="menu-item flex-between" on:click={() => { dispatch('close', person) }}>
<button class="menu-item" on:click={() => {
if (!multiSelect) {
selected = person._id === selected ? undefined : person._id
dispatch('close', selected != undefined ? person : undefined)
} else checkSelected(person)
}}>
{#if multiSelect}
<div class="check pointer-events-none">
<CheckBox checked={isSelected(person)} primary />
</div>
{/if}
<UserInfo size={'x-small'} value={person} />
{#if allowDeselect && person._id === selected}
<div class="check" on:click|stopPropagation={() => { dispatch('close', null) }}>
<Tooltip label={titleDeselect ?? presentation.string.Deselect}>
<CheckBox checked primary />
</Tooltip>
<div class="check-right pointer-events-none">
{#if titleDeselect}
<Tooltip label={titleDeselect ?? presentation.string.Deselect}>
<CheckBox checked circle primary />
</Tooltip>
{:else}
<CheckBox checked circle primary />
{/if}
</div>
{/if}
</button>
@@ -15,7 +15,7 @@
-->
<script lang="ts">
export let size: 'x-small' | 'small' | 'medium' | 'large' | 'x-large'
export let size: 'inline' | 'x-small' | 'small' | 'medium' | 'large' | 'x-large'
const fill: string = 'var(--theme-caption-color)'
</script>
@@ -30,6 +30,10 @@
.svg-avatar {
.op { opacity: .05; }
}
.inline {
width: .75rem;
height: .75rem;
}
.x-small {
width: 1rem;
height: 1rem;
@@ -0,0 +1,29 @@
<!--
// 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">
export let size: 'small' | 'medium' | 'large'
const fill: string = 'currentColor'
</script>
<svg class="svg-{size}" {fill} viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<path d="M10.2857 5.44444C10.2857 6.79447 9.26237 7.88889 8 7.88889C6.73764 7.88889 5.71429 6.79447 5.71429 5.44444C5.71429 4.09441 6.73764 3 8 3C9.26237 3 10.2857 4.09441 10.2857 5.44444Z" />
<path d="M3.42857 11.9603C3.42857 10.9748 3.98128 10.081 4.85831 9.7786C5.79546 9.45545 7.02325 9.11111 8 9.11111C8.97675 9.11111 10.2045 9.45545 11.1417 9.77859C12.0187 10.081 12.5714 10.9748 12.5714 11.9603V12.7778C12.5714 13.4528 12.0598 14 11.4286 14H4.57143C3.94025 14 3.42857 13.4528 3.42857 12.7778V11.9603Z" />
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.63272 4.75066C4.59249 4.97531 4.57143 5.20725 4.57143 5.44444C4.57143 6.00206 4.68782 6.5306 4.89604 7.00381C4.59565 7.53433 4.05083 7.88889 3.42857 7.88889C2.4818 7.88889 1.71429 7.06808 1.71429 6.05556C1.71429 5.04303 2.4818 4.22222 3.42857 4.22222C3.89788 4.22222 4.32315 4.42391 4.63272 4.75066Z" />
<path d="M2.28571 12.7778V11.9603C2.28571 10.8522 2.76028 9.77952 3.59669 9.11537C3.5397 9.11257 3.48361 9.11111 3.42857 9.11111C2.69601 9.11111 1.77516 9.36937 1.0723 9.61172C0.414531 9.83853 0 10.5089 0 11.248V11.8611C0 12.3674 0.383756 12.7778 0.857143 12.7778H2.28571Z" />
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.3716 4.75066C11.4118 4.97531 11.4329 5.20725 11.4329 5.44444C11.4329 6.00206 11.3166 6.5306 11.1086 7.00381C11.4087 7.53433 11.953 7.88889 12.5747 7.88889C13.5205 7.88889 14.2873 7.06808 14.2873 6.05556C14.2873 5.04303 13.5205 4.22222 12.5747 4.22222C12.1058 4.22222 11.6809 4.42391 11.3716 4.75066Z" />
<path d="M13.7164 12.7778V11.9603C13.7164 10.8522 13.2423 9.77952 12.4067 9.11537C12.4636 9.11257 12.5197 9.11111 12.5747 9.11111C13.3065 9.11111 14.2265 9.36937 14.9287 9.61172C15.5859 9.83853 16 10.5089 16 11.248V11.8611C16 12.3674 15.6166 12.7778 15.1437 12.7778H13.7164Z" />
</svg>
+1
View File
@@ -21,6 +21,7 @@ export { default as AttributeBarEditor } from './components/AttributeBarEditor.s
export { default as AttributeEditor } from './components/AttributeEditor.svelte'
export { default as AttributesBar } from './components/AttributesBar.svelte'
export { default as Avatar } from './components/Avatar.svelte'
export { default as CombineAvatars } from './components/CombineAvatars.svelte'
export { default as Card } from './components/Card.svelte'
export { default as EditableAvatar } from './components/EditableAvatar.svelte'
export { default as Members } from './components/Members.svelte'
+2 -1
View File
@@ -43,7 +43,8 @@ export default plugin(presentationId, {
Members: '' as IntlString,
Search: '' as IntlString,
Unassigned: '' as IntlString,
CreateMore: '' as IntlString
CreateMore: '' as IntlString,
NumberMembers: '' as IntlString
},
metadata: {
RequiredVersion: '' as Metadata<string>
+1
View File
@@ -79,6 +79,7 @@
--popup-shadow: rgb(0 0 0 / 20%) 0px 4px 24px;
--card-shadow: rgb(0 0 0 / 50%) 0px 16px 70px;
--card-overlay-color: rgba(28, 29, 31, .5);
--avatar-bg-color: #4f5358;
--button-bg-color: #303236;
--button-bg-hover: #37383b;
+3 -1
View File
@@ -185,7 +185,7 @@ p:last-child { margin-block-end: 0; }
&.circle {
padding: .25rem;
background-color: var(--popup-bg-hover);
background-color: var(--avatar-bg-color);
border-radius: 50%;
}
}
@@ -286,6 +286,7 @@ p:last-child { margin-block-end: 0; }
.step-tb75 + .step-tb75 { margin-top: .75rem; }
.ml-1 { margin-left: .25rem; }
.ml-1-5 { margin-left: .375rem; }
.ml-2 { margin-left: .5rem; }
.ml-3 { margin-left: .75rem; }
.ml-4 { margin-left: 1rem; }
@@ -378,6 +379,7 @@ p:last-child { margin-block-end: 0; }
.h-full { height: 100%; }
.h-2 { height: .5rem; }
.h-9 { height: 2.25rem; }
.h-16 { height: 4rem; }
.h-18 { height: 4.5rem; }
.w-full { width: 100%; }
.w-9 { width: 2.25rem; }
+4 -4
View File
@@ -65,6 +65,7 @@
color: var(--caption-color);
cursor: pointer;
&.high { height: 3rem; }
.icon {
margin-right: .75rem;
width: 1rem;
@@ -84,14 +85,13 @@
text-overflow: ellipsis;
overflow: hidden;
}
.check {
.check, .check-right {
display: flex;
justify-content: flex-end;
align-items: center;
margin-left: 1rem;
width: 1rem;
margin-right: .75rem;
height: 2rem;
}
.check-right { margin: 0 0 0 2rem; }
&:hover { background-color: var(--popup-bg-hover); }
}
}
+2 -2
View File
@@ -15,14 +15,14 @@
<script lang="ts">
import type { IntlString, Asset } from '@anticrm/platform'
import type { AnySvelteComponent, TooltipAligment } from '../types'
import type { AnySvelteComponent, TooltipAlignment } from '../types'
import Icon from './Icon.svelte'
import Tooltip from './Tooltip.svelte'
export let label: IntlString = '' as IntlString
export let labelProps: any = undefined
export let direction: TooltipAligment | undefined = undefined
export let direction: TooltipAlignment | undefined = undefined
export let icon: Asset | AnySvelteComponent
export let size: 'small' | 'medium' | 'large'
export let action: (ev: Event) => Promise<void> | void = async () => { }
+5 -4
View File
@@ -14,7 +14,7 @@
-->
<script lang="ts">
import type { IntlString, Asset } from '@anticrm/platform'
import type { AnySvelteComponent } from '../types'
import type { AnySvelteComponent, ButtonKind, ButtonSize } from '../types'
import Spinner from './Spinner.svelte'
import Label from './Label.svelte'
import Icon from './Icon.svelte'
@@ -22,8 +22,8 @@
export let label: IntlString | undefined = undefined
export let labelParams: Record<string, any> = {}
export let kind: 'primary' | 'secondary' | 'no-border' | 'transparent' | 'link' | 'link-bordered' | 'dangerous' = 'secondary'
export let size: 'small' | 'medium' | 'large' | 'x-large' = 'medium'
export let kind: ButtonKind = 'secondary'
export let size: ButtonSize = 'medium'
export let shape: 'circle' | undefined = undefined
export let icon: Asset | AnySvelteComponent | undefined = undefined
export let justify: 'left' | 'center' = 'center'
@@ -83,7 +83,8 @@
<style lang="scss">
.small {
height: 1.5rem;
line-height: 1.5rem;
font-size: .75rem;
line-height: .75rem;
&.only-icon { width: 1.5rem; }
}
.medium {
+20 -34
View File
@@ -34,13 +34,6 @@
<label class="checkbox" class:circle class:primary class:checked>
<input class="chBox" type="checkbox" bind:checked on:change={handleValueChanged} />
<svg class="checkSVG" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
{#if !circle}
<path
class="back"
class:primary
d="M4,0h8c2.2,0,4,1.8,4,4v8c0,2.2-1.8,4-4,4H4c-2.2,0-4-1.8-4-4V4C0,1.8,1.8,0,4,0z"
/>
{/if}
{#if checked}
{#if symbol === 'minus'}
<rect class="check" class:primary x="4" y="7.4" width="8" height="1.2" />
@@ -54,12 +47,27 @@
<style lang="scss">
.checkbox {
flex-shrink: 0;
display: inline-flex;
justify-content: center;
align-items: center;
width: 1rem;
height: 1rem;
width: .875rem;
height: .875rem;
border: 1px solid var(--dark-color);
border-radius: .25rem;
&.circle {
width: 1rem;
height: 1rem;
border-radius: 50%;
}
&.checked {
background-color: var(--accent-color);
border-color: transparent;
}
&.primary.checked {
background-color: var(--primary-bg-color);
border-color: transparent;
}
.chBox {
position: absolute;
@@ -94,35 +102,13 @@
}
}
.checkSVG {
width: 1rem;
height: 1rem;
border-radius: 0.25rem;
width: .875rem;
height: .875rem;
.back {
fill: var(--theme-button-bg-hovered);
}
.check {
visibility: hidden;
fill: var(--theme-button-bg-enabled);
}
}
}
.circle {
width: 1.25rem;
height: 1.25rem;
background-color: var(--theme-button-bg-hovered);
border: 1px solid var(--theme-bg-focused-color);
border-radius: 50%;
&.checked {
background-color: var(--theme-bg-check);
}
&.primary {
border-color: transparent;
&.checked {
background-color: var(--primary-button-enabled);
}
}
}
</style>
+4 -4
View File
@@ -20,7 +20,7 @@
import Label from './Label.svelte'
import Icon from './Icon.svelte'
import { showPopup, Button, Tooltip, DropdownPopup } from '..'
import type { AnySvelteComponent, ListItem, TooltipAligment } from '../types'
import type { AnySvelteComponent, ListItem, TooltipAlignment, ButtonKind, ButtonSize } from '../types'
export let icon: Asset | AnySvelteComponent | undefined = undefined
export let label: IntlString
@@ -28,11 +28,11 @@
export let items: ListItem[] = []
export let selected: ListItem | undefined = undefined
export let kind: 'primary' | 'secondary' | 'no-border' | 'transparent' | 'link' | 'dangerous' = 'no-border'
export let size: 'small' | 'medium' | 'large' | 'x-large' = 'small'
export let kind: ButtonKind = 'no-border'
export let size: ButtonSize = 'small'
export let justify: 'left' | 'center' = 'center'
export let width: string | undefined = undefined
export let labelDirection: TooltipAligment | undefined = undefined
export let labelDirection: TooltipAlignment | undefined = undefined
let container: HTMLElement
let opened: boolean = false
@@ -16,7 +16,7 @@
<script lang="ts">
import { IntlString, Asset } from '@anticrm/platform'
import DropdownLabelsPopup from './DropdownLabelsPopup.svelte'
import type { AnySvelteComponent, DropdownTextItem, TooltipAligment } from '../types'
import type { AnySvelteComponent, DropdownTextItem, TooltipAlignment, ButtonKind, ButtonSize } from '../types'
import { showPopup, Tooltip, Button, Label } from '..'
import { createEventDispatcher } from 'svelte'
import ui from '../plugin'
@@ -27,11 +27,11 @@
export let items: DropdownTextItem[]
export let selected: DropdownTextItem['id'] | undefined = undefined
export let kind: 'primary' | 'secondary' | 'no-border' | 'transparent' | 'link' | 'dangerous' = 'no-border'
export let size: 'small' | 'medium' | 'large' | 'x-large' = 'small'
export let kind: ButtonKind = 'no-border'
export let size: ButtonSize = 'small'
export let justify: 'left' | 'center' = 'center'
export let width: string | undefined = undefined
export let labelDirection: TooltipAligment | undefined = undefined
export let labelDirection: TooltipAlignment | undefined = undefined
let container: HTMLElement
let opened: boolean = false
@@ -73,46 +73,3 @@
</Button>
</Tooltip>
</div>
<!-- <div class="flex-col cursor-pointer"
bind:this={btn}
on:click|preventDefault={() => {
if (!opened) {
opened = true
showPopup(DropdownLabelsPopup, { placeholder, items, selected }, btn, (result) => {
if (result) {
selected = result
dispatch('selected', result)
}
opened = false
})
}
}}
>
<div class="overflow-label label"><Label label={label} /></div>
<div class="flex-row-center space">
<span class="mr-1">
{#if opened}
<IconUp size={'small'} />
{:else}
<IconDown size={'small'} />
{/if}
</span>
<span class="overflow-label" class:caption-color={selected} class:content-dark-color={!selected}>
{#if selectedItem}
{selectedItem.label}
{:else}
<Label label={none} />
{/if}
</span>
</div>
</div>
<style lang="scss">
.label {
margin-bottom: .125rem;
font-weight: 500;
font-size: .75rem;
color: var(--theme-content-accent-color);
}
</style> -->
@@ -16,12 +16,12 @@
import type { Asset, IntlString } from '@anticrm/platform'
import { translate } from '@anticrm/platform'
import { createEventDispatcher } from 'svelte'
import { Icon, Label, getPlatformColor } from '..'
import { Icon, Label } from '..'
export let placeholder: IntlString | undefined = undefined
export let placeholderParam: any | undefined = undefined
export let searchable: boolean = false
export let value: Array<{id: number | string, color: number, label: IntlString}>
export let value: Array<{id: number | string, icon: Asset, label: IntlString}>
let search: string = ''
@@ -41,7 +41,7 @@
<div class="box">
{#each value.filter(el => el.label.toLowerCase().includes(search.toLowerCase())) as item}
<button class="menu-item" on:click={() => { dispatch('close', item.id) }}>
<div class="color" style="background-color: {getPlatformColor(item.color)}" />
<div class="icon"><Icon icon={item.icon} size={'small'} /></div>
<span class="label"><Label label={item.label} /></span>
</button>
{/each}
+2 -2
View File
@@ -14,11 +14,11 @@
-->
<script lang="ts">
import type { IntlString } from '@anticrm/platform'
import type { TooltipAligment, AnySvelteComponent, AnyComponent } from '..'
import type { TooltipAlignment, AnySvelteComponent, AnyComponent } from '..'
import { tooltipstore as tooltip, showTooltip } from '..'
export let label: IntlString | undefined = undefined
export let direction: TooltipAligment | undefined = undefined
export let direction: TooltipAlignment | undefined = undefined
export let component: AnySvelteComponent | AnyComponent | undefined = undefined
export let props: any | undefined = undefined
export let anchor: HTMLElement | undefined = undefined
@@ -15,12 +15,12 @@
<script lang="ts">
import { afterUpdate, onDestroy } from 'svelte'
import { tooltipstore as tooltip, closeTooltip, Component } from '..'
import type { TooltipAligment } from '..'
import type { TooltipAlignment } from '..'
import Label from './Label.svelte'
let tooltipHTML: HTMLElement
let nubHTML: HTMLElement
let dir: TooltipAligment
let dir: TooltipAlignment
let rect: DOMRect
let rectAnchor: DOMRect
let tooltipSW: boolean // tooltipSW = true - Label; false - Component
@@ -25,10 +25,11 @@
export let withTime: boolean = false
export let mondayStart: boolean = true
export let editable: boolean = false
export let icon: 'normal' | 'warning' | 'overdue' = 'normal'
export let icon: 'normal' | 'warning' | 'critical' | 'overdue' = 'normal'
export let labelOver: IntlString | undefined = undefined // label instead of date
export let labelNull: IntlString = ui.string.NoDate
export let showIcon = true
export let shouldShowLabel: boolean = true
const dispatch = createEventDispatcher()
@@ -54,27 +55,35 @@
<button
class="datetime-button"
class:editable
class:dateTimeButtonNoLabel={!shouldShowLabel}
on:click={() => {
if (editable && !opened) {
opened = true
showPopup(DatePopup,
{ currentDate, mondayStart, withTime },
undefined,
() => { opened = false },
(result) => { if (result !== undefined) onChange(result) })
showPopup(
DatePopup,
{ currentDate, mondayStart, withTime },
undefined,
() => {
opened = false
},
(result) => {
if (result !== undefined) onChange(result)
}
)
}
}}
>
{#if showIcon}
<div class="btn-icon {icon}">
<Icon icon={icon === 'overdue' ? DPCalendarOver : DPCalendar} size={'full'}/>
<div class="btn-icon {icon}" class:buttonIconNoLabel={!shouldShowLabel}>
<Icon icon={icon === 'overdue' ? DPCalendarOver : DPCalendar} size={'full'} />
</div>
{/if}
{#if value !== null && value !== undefined}
{#if labelOver !== undefined}
{#if shouldShowLabel && labelOver !== undefined}
<Label label={labelOver} />
{:else}
{new Date(value).getDate()} {getMonthName(new Date(value), 'short')}
{:else if shouldShowLabel}
{new Date(value).getDate()}
{getMonthName(new Date(value), 'short')}
{#if new Date(value).getFullYear() !== today.getFullYear()}
{new Date(value).getFullYear()}
{/if}
@@ -85,7 +94,7 @@
{new Date(value).getMinutes().toString().padStart(2, '0')}
{/if}
{/if}
{:else}
{:else if shouldShowLabel}
<Label label={labelNull} />
{/if}
</button>
@@ -96,7 +105,7 @@
display: flex;
align-items: center;
flex-shrink: 0;
padding: 0 .5rem;
padding: 0 0.5rem;
font-weight: 400;
min-width: 1.5rem;
width: auto;
@@ -106,22 +115,38 @@
color: var(--accent-color);
background-color: var(--button-bg-color);
border: 1px solid transparent;
border-radius: .25rem;
border-radius: 0.25rem;
box-shadow: var(--button-shadow);
transition-property: border, background-color, color, box-shadow;
transition-duration: .15s;
transition-duration: 0.15s;
cursor: default;
&.dateTimeButtonNoLabel {
padding: 0;
}
.btn-icon {
margin-right: .375rem;
width: .875rem;
height: .875rem;
transition: color .15s;
margin-right: 0.375rem;
width: 0.875rem;
height: 0.875rem;
transition: color 0.15s;
pointer-events: none;
&.normal { color: var(--content-color); }
&.warning { color: var(--warning-color); }
&.overdue { color: var(--error-color); }
&.buttonIconNoLabel {
margin-right: 0;
}
&.normal {
color: var(--content-color);
}
&.warning {
color: var(--warning-color);
}
&.critical {
color: var(--error-color);
}
&.overdue {
color: var(--error-color);
}
}
&:hover {
@@ -134,15 +159,28 @@
&:hover {
background-color: var(--button-bg-hover);
.btn-icon {
&.normal { color: var(--caption-color); }
&.warning { color: var(--warning-color); }
&.overdue { color: var(--error-color); }
&.normal {
color: var(--caption-color);
}
&.warning {
color: var(--warning-color);
}
&.critical {
color: var(--error-color);
}
&.overdue {
color: var(--error-color);
}
}
.time-divider {
background-color: var(--button-border-hover);
}
.time-divider { background-color: var(--button-border-hover); }
}
&:focus-within {
border-color: var(--primary-edit-border-color);
&:hover { background-color: transparent; }
&:hover {
background-color: transparent;
}
}
}
&:disabled {
@@ -151,17 +189,21 @@
&:hover {
color: var(--content-color);
.btn-icon { color: var(--content-color); }
.btn-icon {
color: var(--content-color);
}
}
}
.time-divider {
flex-shrink: 0;
margin: 0 .25rem;
margin: 0 0.25rem;
width: 1px;
min-width: 1px;
height: .75rem;
height: 0.75rem;
background-color: var(--button-border-color);
}
.separator { margin: 0 .1rem; }
.separator {
margin: 0 0.1rem;
}
}
</style>
@@ -78,6 +78,7 @@ export function getMonthName (date: Date, option: 'narrow' | 'short' | 'long' |
return new Intl.DateTimeFormat(locale, { month: option }).format(date)
} catch (err) {
console.error(err)
return ''
}
}
@@ -100,3 +101,10 @@ export function addZero (value: number): string {
}
return `${value}`
}
export const getDaysDifference = (from: Date, to: Date): number => {
const firstDateMs = from.setHours(0, 0, 0, 0)
const secondDateMs = to.setHours(0, 0, 0, 0)
return Math.round(Math.abs(secondDateMs - firstDateMs) / MILLISECONDS_IN_DAY)
}
+4 -1
View File
@@ -20,7 +20,7 @@ import { readable } from 'svelte/store'
import Root from './components/internal/Root.svelte'
export type { AnyComponent, AnySvelteComponent, Action, LabelAndProps, TooltipAligment, AnySvelteComponentWithProps, Location, PopupAlignment } from './types'
export type { AnyComponent, AnySvelteComponent, Action, LabelAndProps, TooltipAlignment, AnySvelteComponentWithProps, Location, PopupAlignment, ButtonKind, ButtonSize } from './types'
// export { applicationShortcutKey } from './utils'
export { getCurrentLocation, locationToUrl, navigate, location } from './location'
@@ -102,6 +102,8 @@ export { default as IconCheck } from './components/icons/Check.svelte'
export { default as IconArrowLeft } from './components/icons/ArrowLeft.svelte'
export { default as IconNavPrev } from './components/icons/NavPrev.svelte'
export { default as IconNavNext } from './components/icons/NavNext.svelte'
export { default as IconDPCalendar } from './components/calendar/icons/DPCalendar.svelte'
export { default as IconDPCalendarOver } from './components/calendar/icons/DPCalendarOver.svelte'
export { default as PanelInstance } from './components/PanelInstance.svelte'
export { default as Panel } from './components/Panel.svelte'
@@ -110,6 +112,7 @@ export * from './utils'
export * from './popups'
export * from './tooltips'
export * from './panelup'
export * from './components/calendar/internal/DateUtils'
export function createApp (target: HTMLElement): SvelteComponent {
return new Root({ target })
+2 -2
View File
@@ -123,9 +123,9 @@ export function fitPopupElement (modalHTML: HTMLElement, element?: PopupAlignmen
const rectPopup = modalHTML.getBoundingClientRect()
// Vertical
if (rect.bottom + rectPopup.height + 28 <= document.body.clientHeight) {
modalHTML.style.top = `calc(${rect.bottom}px + .75rem)`
modalHTML.style.top = `calc(${rect.bottom}px + .125rem)`
} else if (rectPopup.height + 28 < rect.top) {
modalHTML.style.bottom = `calc(${document.body.clientHeight - rect.y}px + .75rem)`
modalHTML.style.bottom = `calc(${document.body.clientHeight - rect.y}px + .125rem)`
} else {
modalHTML.style.top = modalHTML.style.bottom = '1rem'
}
+2 -2
View File
@@ -1,4 +1,4 @@
import { AnySvelteComponent, AnyComponent, LabelAndProps, TooltipAligment } from './types'
import { AnySvelteComponent, AnyComponent, LabelAndProps, TooltipAlignment } from './types'
import { IntlString } from '@anticrm/platform'
import { writable } from 'svelte/store'
@@ -15,7 +15,7 @@ export const tooltipstore = writable<LabelAndProps>({
export function showTooltip (
label: IntlString | undefined,
element: HTMLElement,
direction?: TooltipAligment,
direction?: TooltipAlignment,
component?: AnySvelteComponent | AnyComponent,
props?: any,
anchor?: HTMLElement,
+4 -3
View File
@@ -63,14 +63,15 @@ export interface Tab {
export type TabModel = Tab[]
export type ButtonKind = 'primary' | 'secondary' | 'no-border' | 'transparent' | 'link' | 'link-bordered' | 'dangerous'
export type ButtonSize = 'small' | 'medium' | 'large' | 'x-large'
export type PopupAlignment = HTMLElement | EventTarget | null | 'right' | 'top' | 'account' | 'full' | 'content' | 'middle'
export type TooltipAligment = 'top' | 'bottom' | 'left' | 'right'
export type TooltipAlignment = 'top' | 'bottom' | 'left' | 'right'
export interface LabelAndProps {
label: IntlString | undefined
element: HTMLElement | undefined
direction?: TooltipAligment
direction?: TooltipAlignment
component?: AnySvelteComponent | AnyComponent
props?: any
anchor: HTMLElement | undefined
@@ -36,20 +36,18 @@
export let _class: Ref<Class<Card>>
const dispatch = createEventDispatcher()
const client = getClient()
const query = createQuery()
const cardQuery = createQuery()
const stateQuery = createQuery()
let object: Card | undefined
let state: State | undefined
let handleMove: () => void
$: _id &&
_class &&
query.query(_class, { _id }, async (result) => {
$: cardQuery.query(_class, { _id }, async (result) => {
object = result[0]
})
$: object &&
query.query(task.class.State, { _id: object.state }, async (result) => {
$: object?.state && stateQuery.query(task.class.State, { _id: object.state }, async (result) => {
state = result[0]
})
@@ -104,7 +102,7 @@
<div class="flex-grow mr-4">
<div class="flex-row-streach">
<div class="w-9" />
<CardDetails value={object} />
<CardDetails bind:value={object} />
</div>
<div class="flex-row-streach mt-4 mb-2">
<div class="w-9">
@@ -128,10 +126,10 @@
</div>
<CardAttachments value={object} />
<!-- TODO checklists -->
<CardActivity value={object} />
<CardActivity bind:value={object} />
</div>
<CardActions value={object} />
<CardActions bind:value={object} />
</div>
</div>
</Scroller>
@@ -36,12 +36,12 @@ export function hasDate (card: Card): boolean {
export function addCurrentUser (card: Card, client: Client): void {
const employee = (getCurrentAccount() as EmployeeAccount).employee
card.members = card.members ?? []
const members = card.members ?? []
if (card.members.includes(employee)) {
if (members.includes(employee)) {
return
}
card.members.push(employee)
updateCard(client, card, 'members', card.members)
members.push(employee)
updateCard(client, card, 'members', members)
}
@@ -72,7 +72,7 @@
<UserBoxList
_class={contact.class.Employee}
items={participants}
title={calendar.string.Participants}
label={calendar.string.Participants}
on:open={(evt) => {
participants.push(evt.detail._id)
participants = participants
@@ -61,7 +61,7 @@
<UserBoxList
_class={contact.class.Employee}
items={object.participants}
title={calendar.string.Participants}
label={calendar.string.Participants}
on:open={(evt) => {
client.update(object, { $push: { participants: evt.detail._id } })
}}
@@ -78,7 +78,7 @@
<UserBoxList
_class={contact.class.Employee}
items={participants}
title={calendar.string.Participants}
label={calendar.string.Participants}
on:open={(evt) => {
participants.push(evt.detail._id)
participants = participants
+4
View File
@@ -31,6 +31,10 @@
"MarkUnread": "Mark unread",
"GetNewReplies": "Get notified about new replies",
"TurnOffReplies": "Turn off notifications for replies",
"PinMessage": "Pin message",
"UnpinMessage": "Unpin message",
"Pinned": "Pinned:",
"EditMessage": "Edit message",
"DeleteMessage": "Delete message"
}
}
+4
View File
@@ -30,6 +30,10 @@
"MarkUnread": "Отметить как непрочитанное",
"GetNewReplies": "Получать уведомления о новых ответах",
"TurnOffReplies": "Выключить уведомления об ответах",
"PinMessage": "Закрепить сообщение",
"UnpinMessage": "Открепить сообщение",
"Pinned": "Закреплено:",
"EditMessage": "Редактировать сообщение",
"DeleteMessage": "Удалить сообщение"
}
}
@@ -14,7 +14,7 @@
-->
<script lang="ts">
import attachment from '@anticrm/attachment'
import type { Message } from '@anticrm/chunter'
import type { ChunterMessage, Message } from '@anticrm/chunter'
import contact, { Employee } from '@anticrm/contact'
import core, { Doc, Ref, Space, WithLookup } from '@anticrm/core'
import { NotificationClientImpl } from '@anticrm/notification-resources'
@@ -23,7 +23,9 @@
import chunter from '../plugin'
import ChannelSeparator from './ChannelSeparator.svelte'
import MessageComponent from './Message.svelte'
export let space: Ref<Space> | undefined
export let pinnedIds: Ref<ChunterMessage>[]
let div: HTMLDivElement | undefined
let autoscroll: boolean = false
@@ -111,7 +113,7 @@
{#if newMessagesPos === i}
<ChannelSeparator title={chunter.string.New} line reverse isNew />
{/if}
<MessageComponent {message} {employees} on:openThread />
<MessageComponent {message} {employees} on:openThread isPinned={pinnedIds.includes(message._id)} />
{/each}
{/if}
</div>
@@ -14,14 +14,15 @@
-->
<script lang="ts">
import { AttachmentRefInput } from '@anticrm/attachment-resources'
import { Message } from '@anticrm/chunter'
import { ChunterMessage, Message } from '@anticrm/chunter'
import { generateId, getCurrentAccount, Ref, Space, TxFactory } from '@anticrm/core'
import { NotificationClientImpl } from '@anticrm/notification-resources'
import { getClient } from '@anticrm/presentation'
import { createQuery, getClient } from '@anticrm/presentation'
import { getCurrentLocation, navigate } from '@anticrm/ui'
import { createBacklinks } from '../backlinks'
import chunter from '../plugin'
import Channel from './Channel.svelte'
import PinnedMessages from './PinnedMessages.svelte'
export let space: Ref<Space>
@@ -63,13 +64,26 @@
loc.path[3] = _id
navigate(loc)
}
const pinnedQuery = createQuery()
let pinnedIds: Ref<ChunterMessage>[] = []
pinnedQuery.query(
chunter.class.Channel,
{ _id: space },
(res) => {
pinnedIds = res[0]?.pinned ?? []
},
{ limit: 1 }
)
</script>
<PinnedMessages {space} {pinnedIds} />
<Channel
{space}
on:openThread={(e) => {
openThread(e.detail)
}}
{pinnedIds}
/>
<div class="reference">
<AttachmentRefInput {space} {_class} objectId={_id} on:message={onMessage} />
@@ -14,17 +14,18 @@
-->
<script lang="ts">
import { Attachment } from '@anticrm/attachment'
import { AttachmentList } from '@anticrm/attachment-resources'
import { AttachmentList, AttachmentRefInput } from '@anticrm/attachment-resources'
import type { Message } from '@anticrm/chunter'
import { Employee, EmployeeAccount, formatName } from '@anticrm/contact'
import { Ref, WithLookup, getCurrentAccount } from '@anticrm/core'
import { NotificationClientImpl } from '@anticrm/notification-resources'
import { getResource } from '@anticrm/platform'
import { Avatar, getClient, MessageViewer } from '@anticrm/presentation'
import { ActionIcon, IconMoreH, Menu, showPopup, getCurrentLocation, navigate } from '@anticrm/ui'
import { ActionIcon, IconMoreH, Menu, showPopup } from '@anticrm/ui'
import { Action } from '@anticrm/view'
import { getActions } from '@anticrm/view-resources'
import { createEventDispatcher } from 'svelte'
import { UnpinMessage } from '../index';
import chunter from '../plugin'
import { getTime } from '../utils'
// import Share from './icons/Share.svelte'
@@ -37,6 +38,7 @@
export let message: WithLookup<Message>
export let employees: Map<Ref<Employee>, Employee>
export let thread: boolean = false
export let isPinned: boolean = false
$: employee = getEmployee(message)
$: attachments = (message.$lookup?.attachments ?? []) as Attachment[]
@@ -59,24 +61,39 @@
action: chunter.actionImpl.SubscribeMessage
} as Action)
async function deleteMessage () {
await client.remove(message)
const loc = getCurrentLocation()
$: pinActions = isPinned
? ({
label: chunter.string.UnpinMessage,
action: chunter.actionImpl.UnpinMessage
} as Action)
: ({
label: chunter.string.PinMessage,
action: chunter.actionImpl.PinMessage
} as Action)
if (loc.path[3] === message._id) {
loc.path.length = 3
navigate(loc)
}
}
$: isEditing = false;
const editAction = {
label: chunter.string.EditMessage,
action: () => isEditing = true
}
const deleteAction = {
label: chunter.string.DeleteMessage,
action: deleteMessage
action: async () => {
(await client.findAll(chunter.class.ThreadMessage, {attachedTo: message._id})).forEach(c => {
UnpinMessage(c)
})
UnpinMessage(message)
await client.remove(message)
}
}
const showMenu = async (ev: Event): Promise<void> => {
const actions = await getActions(client, message, chunter.class.Message)
actions.push(subscribeAction)
actions.push(pinActions)
showPopup(
Menu,
{
@@ -89,13 +106,28 @@
await impl(message)
}
})),
...(getCurrentAccount()._id === message.createBy ? [deleteAction] : [])
...(getCurrentAccount()._id === message.createBy ? [editAction, deleteAction] : [])
]
},
ev.target as HTMLElement
)
}
async function onMessageEdit (event: CustomEvent) {
const { message: newContent, attachments: newAttachments } = event.detail
if (newContent !== message.content || newAttachments !== attachments) {
await client.update(
message,
{
content: newContent,
attachments: newAttachments
}
)
}
isEditing = false
}
function getEmployee (message: WithLookup<Message>): Employee | undefined {
const employee = (message.$lookup?.createBy as EmployeeAccount).employee
if (employee !== undefined) {
@@ -115,8 +147,18 @@
{#if employee}{formatName(employee.name)}{/if}
<span>{getTime(message.createOn)}</span>
</div>
<div class="text"><MessageViewer message={message.content} /></div>
{#if message.attachments}<div class="attachments"><AttachmentList {attachments} /></div>{/if}
{#if isEditing}
<AttachmentRefInput
space={message.space}
_class={chunter.class.Comment}
objectId={message._id}
content={message.content}
on:message={onMessageEdit}
/>
{:else}
<div class="text"><MessageViewer message={message.content} /></div>
{#if message.attachments}<div class="attachments"><AttachmentList {attachments} /></div>{/if}
{/if}
{#if reactions || message.replies}
<div class="footer flex-col">
<div>
@@ -0,0 +1,29 @@
<script lang="ts">
import { ChunterMessage } from '@anticrm/chunter'
import { Ref, Space } from '@anticrm/core'
import { Label, showPopup } from '@anticrm/ui'
import PinnedMessagesPopup from './PinnedMessagesPopup.svelte'
import chunter from '../plugin'
export let space: Ref<Space>
export let pinnedIds: Ref<ChunterMessage>[]
function showMessages (ev: MouseEvent & { currentTarget: EventTarget & HTMLDivElement }) {
showPopup(PinnedMessagesPopup, { space }, ev.target)
}
</script>
{#if pinnedIds.length > 0}
<div class="bottom-divider over-underline pt-2 pb-2 container">
<div on:click={(ev) => showMessages(ev)}>
<Label label={chunter.string.Pinned} />
{pinnedIds.length}
</div>
</div>
{/if}
<style lang="scss">
.container {
padding-left: 2.5rem;
}
</style>
@@ -0,0 +1,116 @@
<script lang="ts">
import chunter, { ChunterMessage } from '@anticrm/chunter'
import contact, { Employee, EmployeeAccount, formatName } from '@anticrm/contact'
import { Ref, Space } from '@anticrm/core'
import { Avatar, createQuery, getClient, MessageViewer } from '@anticrm/presentation'
import { IconClose } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte'
import { UnpinMessage } from '../index'
import { getTime } from '../utils'
export let space: Ref<Space>
const pinnedQuery = createQuery()
let pinnedIds: Ref<ChunterMessage>[] = []
pinnedQuery.query(
chunter.class.Channel,
{ _id: space },
(res) => {
pinnedIds = res[0]?.pinned ?? []
},
{ limit: 1 }
)
const messagesQuery = createQuery()
let pinnedMessages: ChunterMessage[] = []
$: pinnedIds &&
messagesQuery.query(chunter.class.ChunterMessage, { _id: { $in: pinnedIds } }, (res) => {
pinnedMessages = res
})
const employeeAccoutsQuery = createQuery()
let employeeAcounts: EmployeeAccount[]
employeeAccoutsQuery.query(contact.class.EmployeeAccount, {}, (res) => (employeeAcounts = res))
const client = getClient()
const dispatch = createEventDispatcher()
async function getAvatar (_id?: Ref<Employee>): Promise<string | undefined | null> {
if (_id === undefined) return (await client.findOne(contact.class.Employee, { _id }))?.avatar
}
function getEmployee (message: ChunterMessage): EmployeeAccount | undefined {
return employeeAcounts?.find((e) => e._id === message.createBy)
}
</script>
<div class="antiPopup vScroll popup">
{#each pinnedMessages as message}
<div class="message">
<div class="header">
{#await getEmployee(message) then employeeAccount}
{#await getAvatar(employeeAccount?.employee) then avatar}
<div class="avatar">
<Avatar size={'medium'} {avatar} />
</div>
{/await}
<span class="name">
{formatName(employeeAccount?.name ?? '')}
</span>
{/await}
<div
class="cross"
on:click={async () => {
if (pinnedIds.length === 1)
dispatch('close')
UnpinMessage(message)
}}
>
<IconClose size="small" />
</div>
</div>
<MessageViewer message={message.content} />
<span class="time">{getTime(message.createOn)}</span>
</div>
{/each}
</div>
<style lang="scss">
.popup {
padding: 1.25rem 1.25rem 1.25rem;
max-height: 20rem;
color: var(--theme-caption-color);
}
.message {
padding: 0.75rem 1rem 0.75rem;
margin-bottom: 1rem;
box-shadow: inherit;
border-radius: inherit;
}
.header {
display: flex;
flex-direction: row;
align-items: center;
.name {
font-weight: 500;
margin-left: 1rem;
flex-grow: 2;
}
.cross {
opacity: 0.4;
cursor: pointer;
&:hover {
opacity: 1;
}
}
}
.time {
font-size: 0.75rem;
}
</style>
@@ -14,7 +14,7 @@
-->
<script lang="ts">
import { Attachment } from '@anticrm/attachment'
import { AttachmentList } from '@anticrm/attachment-resources'
import { AttachmentList, AttachmentRefInput } from '@anticrm/attachment-resources'
import type { ThreadMessage } from '@anticrm/chunter'
import { Employee, EmployeeAccount, formatName } from '@anticrm/contact'
import { Ref, WithLookup, getCurrentAccount } from '@anticrm/core'
@@ -24,6 +24,7 @@
import { ActionIcon, IconMoreH, Menu, showPopup } from '@anticrm/ui'
import { Action } from '@anticrm/view'
import { getActions } from '@anticrm/view-resources'
import { UnpinMessage } from '../index';
import chunter from '../plugin'
import { getTime } from '../utils'
// import Share from './icons/Share.svelte'
@@ -33,6 +34,7 @@
export let message: WithLookup<ThreadMessage>
export let employees: Map<Ref<Employee>, Employee>
export let isPinned: boolean = false
$: attachments = (message.$lookup?.attachments ?? []) as Attachment[]
@@ -53,14 +55,35 @@
action: chunter.actionImpl.SubscribeComment
} as Action)
$: pinActions = isPinned
? ({
label: chunter.string.UnpinMessage,
action: chunter.actionImpl.UnpinMessage
} as Action)
: ({
label: chunter.string.PinMessage,
action: chunter.actionImpl.PinMessage
} as Action)
$: isEditing = false;
const editAction = {
label: chunter.string.EditMessage,
action: () => isEditing = true
}
const deleteAction = {
label: chunter.string.DeleteMessage,
action: async () => await client.removeDoc(message._class, message.space, message._id)
action: async () => {
await client.removeDoc(message._class, message.space, message._id)
UnpinMessage(message)
}
}
const showMenu = async (ev: Event): Promise<void> => {
const actions = await getActions(client, message, chunter.class.ThreadMessage)
actions.push(subscribeAction)
actions.push(pinActions)
showPopup(
Menu,
{
@@ -73,13 +96,28 @@
await impl(message)
}
})),
...(getCurrentAccount()._id === message.createBy ? [deleteAction] : [])
...(getCurrentAccount()._id === message.createBy ? [editAction, deleteAction] : [])
]
},
ev.target as HTMLElement
)
}
async function onMessageEdit (event: CustomEvent) {
const { message: newContent, attachments: newAttachments } = event.detail
if (newContent !== message.content || newAttachments !== attachments) {
await client.update(
message,
{
content: newContent,
attachments: newAttachments
}
)
}
isEditing = false
}
$: employee = getEmployee(message)
function getEmployee (comment: WithLookup<ThreadMessage>): Employee | undefined {
@@ -97,8 +135,18 @@
{#if employee}{formatName(employee.name)}{/if}
<span>{getTime(message.createOn)}</span>
</div>
<div class="text"><MessageViewer message={message.content} /></div>
{#if message.attachments}<div class="attachments"><AttachmentList {attachments} /></div>{/if}
{#if isEditing}
<AttachmentRefInput
space={message.space}
_class={chunter.class.Comment}
objectId={message._id}
content={message.content}
on:message={onMessageEdit}
/>
{:else}
<div class="text"><MessageViewer message={message.content} /></div>
{#if message.attachments}<div class="attachments"><AttachmentList {attachments} /></div>{/if}
{/if}
{#if reactions}
<div class="footer">
<div><Reactions /></div>
@@ -15,12 +15,12 @@
<script lang="ts">
import attachment from '@anticrm/attachment'
import { AttachmentRefInput } from '@anticrm/attachment-resources'
import type { ThreadMessage, Message } from '@anticrm/chunter'
import type { ThreadMessage, Message, ChunterMessage, Channel } from '@anticrm/chunter'
import contact, { Employee } from '@anticrm/contact'
import core, { Doc, generateId, getCurrentAccount, Ref, Space, TxFactory } from '@anticrm/core'
import { NotificationClientImpl } from '@anticrm/notification-resources'
import { createQuery, getClient } from '@anticrm/presentation'
import { IconClose, Label } from '@anticrm/ui'
import { IconClose, Label, getCurrentLocation, navigate } from '@anticrm/ui'
import { afterUpdate, beforeUpdate, createEventDispatcher } from 'svelte'
import { createBacklinks } from '../backlinks'
import chunter from '../plugin'
@@ -57,6 +57,9 @@
createBy: core.class.Account
}
const pinnedQuery = createQuery()
let pinnedIds: Ref<ChunterMessage>[] = []
$: updateQueries(_id)
function updateQueries (id: Ref<Message>) {
@@ -65,7 +68,15 @@
{
_id: id
},
(res) => (message = res[0]),
(res) => {
message = res[0]
if (!message) {
const loc = getCurrentLocation()
loc.path.length = 3
navigate(loc)
}
},
{
lookup: {
_id: { attachments: attachment.class.Attachment },
@@ -88,6 +99,15 @@
lookup
}
)
pinnedQuery.query(
chunter.class.Channel,
{ _id: currentSpace },
(res) => {
pinnedIds = res[0]?.pinned ?? []
},
{ limit: 1 }
)
}
let employees: Map<Ref<Employee>, Employee> = new Map<Ref<Employee>, Employee>()
@@ -174,7 +194,11 @@
{#if newMessagesPos === i}
<ChannelSeparator title={chunter.string.New} line reverse isNew />
{/if}
<ThreadComment message={comment} {employees} />
<ThreadComment
message={comment}
{employees}
isPinned={pinnedIds.includes(comment._id)}
/>
{/each}
{/if}
</div>
+22 -2
View File
@@ -13,9 +13,11 @@
// limitations under the License.
//
import chunter, { Message, ThreadMessage } from '@anticrm/chunter'
import core from '@anticrm/core'
import chunter, { Channel, ChunterMessage, Message, ThreadMessage } from '@anticrm/chunter'
import { NotificationClientImpl } from '@anticrm/notification-resources'
import { Resources } from '@anticrm/platform'
import { getClient } from '@anticrm/presentation'
import TxBacklinkCreate from './components/activity/TxBacklinkCreate.svelte'
import TxBacklinkReference from './components/activity/TxBacklinkReference.svelte'
import TxCommentCreate from './components/activity/TxCommentCreate.svelte'
@@ -62,6 +64,22 @@ async function UnsubscribeComment (object: ThreadMessage): Promise<void> {
await client.unsubscribe(object.attachedTo)
}
async function PinMessage (message: ChunterMessage): Promise<void> {
const client = getClient()
await client.updateDoc<Channel>(chunter.class.Channel, core.space.Space, message.space, {
$push: { pinned: message._id }
})
}
export async function UnpinMessage (message: ChunterMessage): Promise<void> {
const client = getClient()
await client.updateDoc<Channel>(chunter.class.Channel, core.space.Space, message.space, {
$pull: { pinned: message._id }
})
}
export default async (): Promise<Resources> => ({
component: {
CommentInput,
@@ -85,6 +103,8 @@ export default async (): Promise<Resources> => ({
SubscribeMessage,
SubscribeComment,
UnsubscribeMessage,
UnsubscribeComment
UnsubscribeComment,
PinMessage,
UnpinMessage
}
})
+8 -2
View File
@@ -30,7 +30,9 @@ export default mergeIds(chunterId, chunter, {
SubscribeMessage: '' as Resource<(object: Doc) => Promise<void>>,
SubscribeComment: '' as Resource<(object: Doc) => Promise<void>>,
UnsubscribeMessage: '' as Resource<(object: Doc) => Promise<void>>,
UnsubscribeComment: '' as Resource<(object: Doc) => Promise<void>>
UnsubscribeComment: '' as Resource<(object: Doc) => Promise<void>>,
PinMessage: '' as Resource<(object: Doc) => Promise<void>>,
UnpinMessage: '' as Resource<(object: Doc) => Promise<void>>
},
string: {
Channel: '' as IntlString,
@@ -51,6 +53,10 @@ export default mergeIds(chunterId, chunter, {
New: '' as IntlString,
GetNewReplies: '' as IntlString,
TurnOffReplies: '' as IntlString,
DeleteMessage: '' as IntlString
PinMessage: '' as IntlString,
UnpinMessage: '' as IntlString,
Pinned: '' as IntlString,
DeleteMessage: '' as IntlString,
EditMessage: '' as IntlString
}
})
+1
View File
@@ -24,6 +24,7 @@ import { AnyComponent } from '@anticrm/ui'
*/
export interface Channel extends Space {
lastMessage?: Timestamp
pinned?: Ref<ChunterMessage>[]
topic?: string
}
@@ -19,7 +19,7 @@
import { IntlString } from '@anticrm/platform'
import { createQuery } from '@anticrm/presentation'
import { Dropdown } from '@anticrm/ui'
import type { TooltipAligment } from '@anticrm/ui'
import type { TooltipAlignment, ButtonKind, ButtonSize } from '@anticrm/ui'
import { ListItem } from '@anticrm/ui/src/types'
import { createEventDispatcher } from 'svelte'
import contact from '../plugin'
@@ -28,11 +28,11 @@
export let value: Ref<Organization> | undefined
export let label: IntlString = contact.string.Organization
export let kind: 'primary' | 'secondary' | 'no-border' | 'transparent' | 'link' | 'dangerous' = 'no-border'
export let size: 'small' | 'medium' | 'large' | 'x-large' = 'small'
export let kind: ButtonKind = 'no-border'
export let size: ButtonSize = 'small'
export let justify: 'left' | 'center' = 'center'
export let width: string | undefined = undefined
export let labelDirection: TooltipAligment | undefined = undefined
export let labelDirection: TooltipAlignment | undefined = undefined
const query = createQuery()
const dispatch = createEventDispatcher()
+7
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'
}
}
@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
<symbol id="star" viewBox="0 0 16 16">
<path
d="M4.8,14.5c-0.3,0-0.5-0.1-0.7-0.2c-0.4-0.3-0.6-0.8-0.6-1.3L4,10.1c0-0.1,0-0.2-0.1-0.3l-2-2C1.5,7.4,1.4,6.8,1.6,6.3 c0.2-0.5,0.6-0.8,1.1-0.9L5.4,5c0.1,0,0.2-0.1,0.2-0.2l1.2-2.6C7,1.8,7.5,1.5,8,1.5s1,0.3,1.2,0.8l0,0l1.2,2.6 c0,0.1,0.1,0.2,0.2,0.2l2.7,0.4c0.5,0.1,0.9,0.4,1.1,0.9c0.2,0.5,0,1-0.3,1.4l-2,2C12.1,9.8,12,10,12,10.1l0.5,2.8 c0.1,0.5-0.1,1-0.6,1.3c-0.4,0.3-0.9,0.3-1.4,0.1L8.1,13c-0.1,0-0.2,0-0.3,0l-2.4,1.3C5.2,14.4,5,14.5,4.8,14.5z M8,2.5 c-0.1,0-0.2,0-0.3,0.2L6.5,5.3C6.3,5.7,5.9,6,5.5,6L2.8,6.4c-0.2,0-0.2,0.2-0.3,0.2c0,0.1-0.1,0.2,0.1,0.4l2,2 C4.9,9.4,5,9.8,4.9,10.2l-0.5,2.8c0,0.2,0.1,0.3,0.1,0.4c0.1,0,0.2,0.1,0.3,0l2.4-1.3c0.4-0.2,0.9-0.2,1.2,0l2.4,1.3 c0.1,0.1,0.3,0,0.3,0c0,0,0.2-0.1,0.1-0.4l-0.5-2.8c-0.1-0.4,0.1-0.9,0.4-1.2l2-2c0.1-0.2,0.1-0.3,0.1-0.4c0-0.1-0.1-0.2-0.3-0.2 L10.5,6c-0.4-0.1-0.8-0.3-1-0.7L8.3,2.7C8.2,2.5,8.1,2.5,8,2.5z"
/>
</symbol>
</svg>

After

Width:  |  Height:  |  Size: 1006 B

+18
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"
}
+8
View File
@@ -0,0 +1,8 @@
{
"string": {
"DeleteStarred": "Delete starred",
"Starred": "Starred",
"Star": "Star",
"Unstar": "Unstar"
}
}
+8
View File
@@ -0,0 +1,8 @@
{
"string": {
"DeleteStarred": "Удалить закладки",
"Starred": "Закладки",
"Star": "Добавить в закладки",
"Unstar": "Удалить из закладок"
}
}
+33
View File
@@ -0,0 +1,33 @@
{
"name": "@anticrm/preference-assets",
"version": "0.6.0",
"main": "lib/index.js",
"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.11.12"
},
"dependencies": {
"@anticrm/platform": "~0.6.5",
"@anticrm/preference": "~0.6.0"
}
}
+24
View File
@@ -0,0 +1,24 @@
//
// Copyright © 2022 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 preference, { preferenceId } from '@anticrm/preference'
const icons = require('../assets/icons.svg') as string // eslint-disable-line
loadMetadata(preference.icon, {
Star: `${icons}#star`
})
addStringsLoader(preferenceId, async (lang: string) => await import(`../lang/${lang}.json`))
+15
View File
@@ -0,0 +1,15 @@
{
"compilerOptions": {
"moduleResolution": "node",
"target": "esnext",
"module": "esnext",
"declaration": true,
"outDir": "./lib",
"strict": true,
"esModuleInterop": true,
"lib": [
"esnext",
"dom"
]
}
}
+7
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'
}
}
+4
View File
@@ -0,0 +1,4 @@
*
!/lib/**
!CHANGELOG.md
/lib/**/__tests__/
+18
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"
}
+33
View File
@@ -0,0 +1,33 @@
{
"name": "@anticrm/preference",
"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.16",
"@anticrm/ui": "~0.6.0"
}
}
+66
View File
@@ -0,0 +1,66 @@
//
// Copyright © 2022 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 { Class, Doc, Domain, Ref, Space } from '@anticrm/core'
import type { Asset, IntlString, Plugin } from '@anticrm/platform'
import { plugin } from '@anticrm/platform'
/**
* @public
*/
export interface Preference extends Doc {
attachedTo: Ref<Doc>
}
/**
* @public
*/
export interface SpacePreference extends Preference {
attachedTo: Ref<Space>
}
/**
* @public
*/
export const preferenceId = 'preference' as Plugin
/**
* @public
*/
export const DOMAIN_PREFERENCE = 'preference' as Domain
/**
* @public
*/
const preference = plugin(preferenceId, {
class: {
Preference: '' as Ref<Class<Preference>>,
SpacePreference: '' as Ref<Class<SpacePreference>>
},
space: {
Preference: '' as Ref<Space>
},
icon: {
Star: '' as Asset
},
string: {
DeleteStarred: '' as IntlString,
Starred: '' as IntlString,
Star: '' as IntlString,
Unstar: '' as IntlString
}
})
export default preference
+9
View File
@@ -0,0 +1,9 @@
{
"extends": "./node_modules/@anticrm/platform-rig/profiles/default/tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib",
"lib": ["esnext", "dom"]
}
}
@@ -165,18 +165,6 @@
placeholder={recruit.string.Location} bind:value={location}
maxWidth={'37.5rem'} kind={'small-style'}
/>
<UserBoxList
_class={contact.class.Employee}
items={doc.participants}
title={calendar.string.Participants}
on:open={(evt) => {
doc.participants = [...(doc.participants ?? []), evt.detail._id]
}}
on:delete={(evt) => {
doc.participants = doc.participants?.filter((it) => it !== evt.detail._id) ?? [currentUser.employee]
}}
noItems={calendar.string.NoParticipants}
/>
<StyledTextBox
emphasized
showButtons={false}
@@ -199,5 +187,14 @@
/>
<DateRangePresenter bind:value={startDate} labelNull={recruit.string.StartDate} withTime editable on:change={updateStart} />
<DateRangePresenter bind:value={dueDate} labelNull={recruit.string.DueDate} withTime editable />
<UserBoxList
_class={contact.class.Employee}
items={doc.participants}
label={calendar.string.Participants}
on:update={(evt) => {
doc.participants = evt.detail
}}
noItems={calendar.string.NoParticipants}
/>
</svelte:fragment>
</Card>
@@ -86,7 +86,7 @@
<UserBoxList
_class={contact.class.Employee}
items={object.participants}
title={calendar.string.Participants}
label={calendar.string.Participants}
on:open={(evt) => {
client.update(object, { $push: { participants: evt.detail._id } })
}}
+7 -2
View File
@@ -27,6 +27,7 @@
"Canceled": "Canceled",
"SetPriority": "Set priority\u2026",
"SetStatus": "Set status\u2026",
"SelectIssue": "Select issue",
"Priority": "Priority",
"NoPriority": "No priority",
"Urgent": "Urgent",
@@ -51,7 +52,7 @@
"Project": "Project",
"Space": "",
"DueDate": "Set due date\u2026",
"ModificationDate": "Last Modified: {value}",
"ModificationDate": "Updated {value}",
"Team": "",
"Issue": "",
"Document": "",
@@ -60,7 +61,11 @@
"Rank": "",
"IssueTitlePlaceholder": "Issue title",
"IssueDescriptionPlaceholder": "Add description",
"AddIssueTooltip": "Add issue..."
"AddIssueTooltip": "Add issue...",
"DueDatePopupTitle": "Due on {value}",
"DueDatePopupOverdueTitle": "Was due on {value}",
"DueDatePopupDescription": "{value, plural, =0 {Today} =1 {Tomorrow} other {# days remaining}}",
"DueDatePopupOverdueDescription": "{value, plural, =1 {1 day overdue} other {# days overdue}}"
},
"status": {}
}
@@ -91,8 +91,6 @@
{ icon: tracker.icon.Parent, label: tracker.string.Parent }
]
let issueDate: number | null = null
const handlePriorityChanged = (newPriority: IssuePriority | undefined) => {
if (newPriority === undefined) {
return
@@ -166,7 +164,7 @@
size="small"
kind="no-border"
/>
<DatePresenter value={issueDate} editable />
<DatePresenter bind:value={object.dueDate} editable />
<Button
icon={tracker.icon.MoreActions}
width="min-content"
@@ -52,7 +52,7 @@
/>
{:else if kind === 'icon'}
<div class="flex-presenter" on:click={handlePriorityEditorOpened}>
<div class="icon">
<div class="priorityIcon">
<Icon icon={issuePriorities[priority].icon} size={'small'} />
</div>
{#if shouldShowLabel}
@@ -62,3 +62,15 @@
{/if}
</div>
{/if}
<style lang="scss">
.priorityIcon {
width: 1rem;
height: 1rem;
color: var(--theme-content-dark-color);
&:hover {
color: var(--theme-caption-color);
}
}
</style>
@@ -52,7 +52,7 @@
/>
{:else if kind === 'icon'}
<div class="flex-presenter" on:click={handleStatusEditorOpened}>
<div class="icon">
<div class="statusIcon">
<Icon icon={issueStatuses[status].icon} size={'small'} />
</div>
{#if shouldShowLabel}
@@ -62,3 +62,10 @@
{/if}
</div>
{/if}
<style lang="scss">
.statusIcon {
width: 1rem;
height: 1rem;
}
</style>
@@ -49,11 +49,14 @@
<Scroller>
<IssuesList
_class={tracker.class.Issue}
config={[
leftItemsConfig={[
{ key: '', presenter: tracker.component.PriorityPresenter, props: { currentSpace } },
{ key: '', presenter: tracker.component.IssuePresenter, props: { currentTeam } },
{ key: '', presenter: tracker.component.StatusPresenter, props: { currentSpace } },
{ key: '', presenter: tracker.component.TitlePresenter },
{ key: '', presenter: tracker.component.TitlePresenter }
]}
rightItemsConfig={[
{ key: '', presenter: tracker.component.DueDatePresenter, props: { currentSpace } },
{ key: 'modifiedOn', presenter: tracker.component.ModificationDatePresenter },
{ key: '', presenter: tracker.component.AssigneePresenter, props: { currentSpace } }
]}
@@ -0,0 +1,85 @@
<!--
// Copyright © 2022 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 { Icon, Label, IconDPCalendarOver, IconDPCalendar } from '@anticrm/ui'
import tracker from '../../plugin'
export let formattedDate: string = ''
export let daysDifference: number = 0
export let isOverdue: boolean = false
export let iconModifier: 'warning' | 'critical' | 'overdue' | undefined = undefined
</script>
{#if formattedDate}
<div class="root">
<div
class="iconContainer"
class:mIconContainerWarning={iconModifier === 'warning'}
class:mIconContainerCritical={iconModifier === 'critical' || iconModifier === 'overdue'}
>
<Icon icon={isOverdue ? IconDPCalendarOver : IconDPCalendar} size={'small'} />
</div>
<div class="messageContainer">
<div class="title">
<Label
label={isOverdue ? tracker.string.DueDatePopupOverdueTitle : tracker.string.DueDatePopupTitle}
params={{ value: formattedDate }}
/>
</div>
<div class="description">
<Label
label={isOverdue ? tracker.string.DueDatePopupOverdueDescription : tracker.string.DueDatePopupDescription}
params={{ value: daysDifference }}
/>
</div>
</div>
</div>
{/if}
<style lang="scss">
.root {
display: flex;
width: 15rem;
}
.iconContainer {
color: var(--content-color);
margin-right: 1rem;
&.mIconContainerWarning {
color: var(--warning-color);
}
&.mIconContainerCritical {
color: var(--error-color);
}
}
.messageContainer {
display: flex;
flex-direction: column;
justify-content: center;
}
.title {
color: var(--theme-caption-color);
font-weight: 500;
}
.description {
margin-top: 0.25rem;
color: var(--theme-content-dark-color);
}
</style>
@@ -0,0 +1,89 @@
<!--
// Copyright © 2022 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 { Ref, Timestamp } from '@anticrm/core'
import { Issue, IssueStatus, Team } from '@anticrm/tracker'
import { DatePresenter, Tooltip, getDaysDifference } from '@anticrm/ui'
import { getClient } from '@anticrm/presentation'
import DueDatePopup from './DueDatePopup.svelte'
import tracker from '../../plugin'
export let value: Issue
export let currentSpace: Ref<Team> | undefined = undefined
const WARNING_DAYS = 7
const client = getClient()
$: today = new Date(new Date(Date.now()).setHours(0, 0, 0, 0))
$: dueDateMs = value.dueDate
$: isOverdue = dueDateMs !== null && dueDateMs < today.getTime()
$: dueDate = dueDateMs === null ? null : new Date(dueDateMs)
$: daysDifference = dueDate === null ? null : getDaysDifference(today, dueDate)
$: iconModifier = getIconModifier(isOverdue, daysDifference)
$: formattedDate = !dueDateMs ? '' : new Date(dueDateMs).toLocaleString('default', { month: 'short', day: 'numeric' })
const handleDueDateChanged = async (event: CustomEvent<Timestamp>) => {
const newDate = event.detail
if (newDate === undefined) {
return
}
const currentIssue = await client.findOne(tracker.class.Issue, { space: currentSpace, _id: value._id })
if (currentIssue === undefined) {
return
}
await client.update(currentIssue, { dueDate: newDate })
}
const getIconModifier = (isOverdue: boolean, daysDifference: number | null) => {
if (isOverdue) {
return 'overdue' as 'overdue' // Fixes `DatePresenter` icon type issue
}
if (daysDifference === 0) {
return 'critical' as 'critical'
}
if (daysDifference !== null && daysDifference <= WARNING_DAYS) {
return 'warning' as 'warning'
}
}
$: shouldRenderPresenter = dueDateMs && value.status !== IssueStatus.Done && value.status !== IssueStatus.Canceled
</script>
{#if shouldRenderPresenter}
<Tooltip
direction={'top'}
component={DueDatePopup}
props={{
formattedDate: formattedDate,
daysDifference: daysDifference,
isOverdue: isOverdue,
iconModifier: iconModifier
}}
>
<DatePresenter
value={dueDateMs}
editable={true}
shouldShowLabel={false}
icon={iconModifier}
on:change={handleDueDateChanged}
/>
</Tooltip>
{/if}
@@ -16,14 +16,16 @@
import { Class, Doc, DocumentQuery, FindOptions, Ref, getObjectValue } from '@anticrm/core'
import { SortingOrder } from '@anticrm/core'
import { createQuery, getClient } from '@anticrm/presentation'
import { CheckBox, Loading, showPopup, Spinner, IconMoreV } from '@anticrm/ui'
import { CheckBox, Loading, showPopup, Spinner, IconMoreV, Tooltip } from '@anticrm/ui'
import { BuildModelKey } from '@anticrm/view'
import { createEventDispatcher } from 'svelte'
import { buildModel, LoadingProps, Menu } from '@anticrm/view-resources'
import tracker from '../../plugin'
export let _class: Ref<Class<Doc>>
export let baseMenuClass: Ref<Class<Doc>> | undefined = undefined
export let config: (BuildModelKey | string)[]
export let leftItemsConfig: (BuildModelKey | string)[]
export let rightItemsConfig: (BuildModelKey | string)[] | undefined = undefined
export let options: FindOptions<Doc> | undefined = undefined
export let query: DocumentQuery<Doc>
@@ -92,13 +94,20 @@
return props.length
}
const buildItemModels = async () => {
const leftModels = await buildModel({ client, _class, keys: leftItemsConfig, options })
const rightModels = rightItemsConfig && (await buildModel({ client, _class, keys: rightItemsConfig, options }))
return { leftModels, rightModels }
}
</script>
{#await buildModel({ client, _class, keys: config, options })}
{#await buildItemModels()}
{#if !isLoading}
<Loading />
{/if}
{:then attributeModels}
{:then itemModels}
<div class="listRoot">
{#if docObjects}
{#each docObjects as docObject, rowIndex (docObject._id)}
@@ -107,65 +116,80 @@
class:mListGridChecked={selectedIssueIds.has(docObject._id)}
class:mListGridFixed={rowIndex === selectedRowIndex}
>
{#each attributeModels as attributeModel, attributeModelIndex}
{#if attributeModelIndex === 0}
<div class="gridElement">
<div class="eListGridCheckBox ml-2">
<CheckBox
checked={selectedIssueIds.has(docObject._id)}
on:value={(event) => {
handleIssueSelected(docObject._id, event)
}}
/>
<div class="modelsContainer">
{#each itemModels.leftModels as attributeModel, attributeModelIndex}
{#if attributeModelIndex === 0}
<div class="gridElement">
<Tooltip direction={'bottom'} label={tracker.string.SelectIssue}>
<div class="eListGridCheckBox ml-2">
<CheckBox
checked={selectedIssueIds.has(docObject._id)}
on:value={(event) => {
handleIssueSelected(docObject._id, event)
}}
/>
</div>
</Tooltip>
<div class="priorityPresenter">
<svelte:component
this={attributeModel.presenter}
value={getObjectValue(attributeModel.key, docObject) ?? ''}
{...attributeModel.props}
/>
</div>
</div>
<div class="priorityPresenter">
{:else if attributeModelIndex === 1}
<div class="issuePresenter">
<svelte:component
this={attributeModel.presenter}
value={getObjectValue(attributeModel.key, docObject) ?? ''}
{...attributeModel.props}
/>
<div
id="context-menu"
class="eIssuePresenterContextMenu"
on:click={(event) => showMenu(event, docObject, rowIndex)}
>
<IconMoreV size={'small'} />
</div>
</div>
{:else}
<div class="gridElement">
<svelte:component
this={attributeModel.presenter}
value={getObjectValue(attributeModel.key, docObject) ?? ''}
{...attributeModel.props}
/>
</div>
</div>
{:else if attributeModelIndex === 1}
<div class="issuePresenter">
<svelte:component
this={attributeModel.presenter}
value={getObjectValue(attributeModel.key, docObject) ?? ''}
{...attributeModel.props}
/>
<div
id="context-menu"
class="eIssuePresenterContextMenu"
on:click={(event) => showMenu(event, docObject, rowIndex)}
>
<IconMoreV size={'small'} />
{/if}
{/each}
</div>
{#if itemModels.rightModels}
<div class="modelsContainer">
{#each itemModels.rightModels as attributeModel}
<div class="gridElement">
<svelte:component
this={attributeModel.presenter}
value={getObjectValue(attributeModel.key, docObject) ?? ''}
{...attributeModel.props}
/>
</div>
</div>
{:else}
<div class="gridElement">
<svelte:component
this={attributeModel.presenter}
value={getObjectValue(attributeModel.key, docObject) ?? ''}
{...attributeModel.props}
/>
</div>
{/if}
{/each}
{/each}
</div>
{/if}
</div>
{/each}
{:else if loadingProps !== undefined}
{#each Array(getLoadingElementsLength(loadingProps, options)) as _, rowIndex}
<div class="listGrid mListGridIsLoading" class:fixed={rowIndex === selectedRowIndex}>
{#each attributeModels as _, attributeModelIndex}
{#if attributeModelIndex === 0}
<div class="gridElement">
<CheckBox checked={false} />
<div class="ml-4">
<Spinner size="small" />
</div>
<div class="modelsContainer">
<div class="gridElement">
<CheckBox checked={false} />
<div class="ml-4">
<Spinner size="small" />
</div>
{/if}
{/each}
</div>
</div>
</div>
{/each}
{/if}
@@ -182,8 +206,9 @@
}
.listGrid {
display: grid;
grid-template-columns: 4rem 5rem 2rem auto 4rem 2rem;
display: flex;
align-items: center;
justify-content: space-between;
height: 3.25rem;
color: var(--theme-caption-color);
border-bottom: 1px solid var(--theme-button-border-hovered);
@@ -203,7 +228,7 @@
}
&.mListGridIsLoading {
grid-template-columns: auto;
justify-content: flex-start;
}
&:hover {
@@ -225,24 +250,20 @@
}
}
.checkBox {
.modelsContainer {
display: flex;
align-items: center;
justify-content: center;
padding: 0.03rem;
border-radius: 0.25rem;
background-color: rgba(247, 248, 248, 0.5);
opacity: 0;
&:hover {
opacity: 1;
}
}
.gridElement {
display: flex;
align-items: center;
justify-content: start;
margin-left: 0.5rem;
&:first-child {
margin-left: 0;
}
}
.priorityPresenter {
@@ -252,7 +273,7 @@
.issuePresenter {
display: flex;
align-items: center;
padding-right: 1rem;
margin-left: 0.5rem;
.eIssuePresenterContextMenu {
visibility: hidden;
@@ -16,6 +16,7 @@
import { Ref } from '@anticrm/core'
import { Issue, IssuePriority, Team } from '@anticrm/tracker'
import { getClient } from '@anticrm/presentation'
import { Tooltip } from '@anticrm/ui'
import tracker from '../../plugin'
import PrioritySelector from '../PrioritySelector.svelte'
@@ -40,10 +41,12 @@
</script>
{#if value}
<PrioritySelector
kind={'icon'}
shouldShowLabel={false}
priority={value.priority}
onPriorityChange={handlePriorityChanged}
/>
<Tooltip direction={'bottom'} label={tracker.string.SetPriority}>
<PrioritySelector
kind={'icon'}
shouldShowLabel={false}
priority={value.priority}
onPriorityChange={handlePriorityChanged}
/>
</Tooltip>
{/if}
@@ -16,6 +16,7 @@
import { Ref } from '@anticrm/core'
import { Issue, IssueStatus, Team } from '@anticrm/tracker'
import { getClient } from '@anticrm/presentation'
import { Tooltip } from '@anticrm/ui'
import tracker from '../../plugin'
import StatusSelector from '../StatusSelector.svelte'
@@ -40,5 +41,7 @@
</script>
{#if value}
<StatusSelector kind={'icon'} shouldShowLabel={false} status={value.status} onStatusChange={handleStatusChanged} />
<Tooltip direction={'bottom'} label={tracker.string.SetStatus}>
<StatusSelector kind={'icon'} shouldShowLabel={false} status={value.status} onStatusChange={handleStatusChanged} />
</Tooltip>
{/if}
+2
View File
@@ -29,6 +29,7 @@ import IssuePresenter from './components/issues/IssuePresenter.svelte'
import TitlePresenter from './components/issues/TitlePresenter.svelte'
import PriorityPresenter from './components/issues/PriorityPresenter.svelte'
import StatusPresenter from './components/issues/StatusPresenter.svelte'
import DueDatePresenter from './components/issues/DueDatePresenter.svelte'
import AssigneePresenter from './components/issues/AssigneePresenter.svelte'
import ModificationDatePresenter from './components/issues/ModificationDatePresenter.svelte'
@@ -52,6 +53,7 @@ export default async (): Promise<Resources> => ({
PriorityPresenter,
StatusPresenter,
AssigneePresenter,
DueDatePresenter,
EditIssue,
NewIssueHeader
}
+6
View File
@@ -40,6 +40,7 @@ export default mergeIds(trackerId, tracker, {
AddIssue: '' as IntlString,
NewIssue: '' as IntlString,
Team: '' as IntlString,
SelectIssue: '' as IntlString,
SelectTeam: '' as IntlString,
SaveIssue: '' as IntlString,
Todo: '' as IntlString,
@@ -76,6 +77,10 @@ export default mergeIds(trackerId, tracker, {
DocumentIcon: '' as IntlString,
DocumentColor: '' as IntlString,
Rank: '' as IntlString,
DueDatePopupTitle: '' as IntlString,
DueDatePopupOverdueTitle: '' as IntlString,
DueDatePopupDescription: '' as IntlString,
DueDatePopupOverdueDescription: '' as IntlString,
IssueTitlePlaceholder: '' as IntlString,
IssueDescriptionPlaceholder: '' as IntlString,
@@ -98,6 +103,7 @@ export default mergeIds(trackerId, tracker, {
PriorityPresenter: '' as AnyComponent,
StatusPresenter: '' as AnyComponent,
AssigneePresenter: '' as AnyComponent,
DueDatePresenter: '' as AnyComponent,
EditIssue: '' as AnyComponent,
CreateTeam: '' as AnyComponent,
NewIssueHeader: '' as AnyComponent
+1
View File
@@ -43,6 +43,7 @@
"@anticrm/calendar": "~0.6.0",
"@anticrm/notification": "~0.6.0",
"@anticrm/notification-resources": "~0.6.0",
"@anticrm/preference": "~0.6.0",
"@anticrm/contact": "~0.6.5",
"@anticrm/view-resources": "~0.6.0"
}
@@ -69,11 +69,10 @@
}
</script>
<div class="antiPopup">
<div class="ap-space" />
<div class="ap-scroll"><div class="ap-box">
<div class="selectPopup">
<div class="scroll"><div class="box">
{#await getItems() then items}
<div class="ap-menuItem flex-row-center" on:click={() => { editProfile(items) }}>
<div class="menu-item high flex-row-center" on:click={() => { editProfile(items) }}>
{#if employee}
<Avatar avatar={employee.avatar} size={'medium'} />
{/if}
@@ -86,7 +85,7 @@
</div>
{#if items}
{#each filterItems(items) as item }
<button class="ap-menuItem" on:click={() => selectCategory(item)}>
<button class="menu-item" on:click={() => selectCategory(item)}>
<div class='mr-2'>
<Icon icon={item.icon} size={'small'}/>
</div>
@@ -94,13 +93,13 @@
</button>
{/each}
{/if}
<button class="ap-menuItem" on:click={selectWorkspace}>
<button class="menu-item" on:click={selectWorkspace}>
<div class='mr-2'>
<Icon icon={setting.icon.SelectWorkspace} size={'small'}/>
</div>
<Label label={setting.string.SelectWorkspace} />
</button>
<button class="ap-menuItem" on:click={signOut}>
<button class="menu-item" on:click={signOut}>
<div class='mr-2'>
<Icon icon={setting.icon.Signout} size={'small'}/>
</div>
@@ -108,5 +107,4 @@
</button>
{/await}
</div></div>
<div class="ap-space" />
</div>
@@ -1,40 +1,45 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
//
// Copyright © 2022 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 core, { Ref, SortingOrder, Space } from '@anticrm/core'
import core, { Doc, Ref, SortingOrder, Space } from '@anticrm/core'
import { getResource } from '@anticrm/platform'
import { createQuery, getClient } from '@anticrm/presentation'
import { Scroller } from '@anticrm/ui'
import type { NavigatorModel, SpecialNavModel } from '@anticrm/workbench'
import { createEventDispatcher } from 'svelte'
import preferece, { SpacePreference } from '@anticrm/preference'
import { getSpecialSpaceClass } from '../utils'
import preference from '@anticrm/preference'
import SpacesNav from './navigator/SpacesNav.svelte'
import SpecialElement from './navigator/SpecialElement.svelte'
import StarredNav from './navigator/StarredNav.svelte'
import TreeSeparator from './navigator/TreeSeparator.svelte'
export let model: NavigatorModel | undefined
export let currentSpace: Ref<Space> | undefined
export let currentSpecial: string | undefined
const client = getClient()
const hierarchy = client.getHierarchy()
const query = createQuery()
let spaces: Space[] = []
let starred: Space[] = []
let shownSpaces: Space[] = []
$: if (model) {
query.query(
core.class.Space,
@@ -45,31 +50,31 @@
{ sort: { name: SortingOrder.Ascending } })
}
let specTopCount: number
let specBottomCount: number
let spModelCount: number
let topSpecials: SpecialNavModel[] = []
let bottomSpecials: SpecialNavModel[] = []
async function update (model: NavigatorModel, spaces: Space[]) {
let preferences: Map<Ref<Doc>, SpacePreference> = new Map<Ref<Doc>, SpacePreference>()
const preferenceQuery = createQuery()
preferenceQuery.query(preferece.class.SpacePreference, {}, (res) => {
preferences = new Map(res.map((r) => { return [r.attachedTo, r] }))
})
async function update (model: NavigatorModel, spaces: Space[], preferences: Map<Ref<Doc>, SpacePreference>) {
if (model.specials !== undefined) {
topSpecials = await getSpecials(model.specials, 'top', spaces)
specTopCount = topSpecials.length
bottomSpecials = await getSpecials(model.specials, 'bottom', spaces)
specBottomCount = bottomSpecials.length
} else {
topSpecials = []
bottomSpecials = []
specBottomCount = 0
specTopCount = 0
}
if (model.spaces) spModelCount = model.spaces.length
shownSpaces = spaces.filter(sp => !sp.archived)
shownSpaces = spaces.filter((sp) => !sp.archived && !preferences.has(sp._id))
starred = spaces.filter((sp) => preferences.has(sp._id))
}
$: if (model) update(model, spaces)
$: if (model) update(model, spaces, preferences)
async function getSpecials (specials: SpecialNavModel[], state: 'top' | 'bottom', spaces: Space[]): Promise<SpecialNavModel[]> {
const result: SpecialNavModel[] = []
for (const sp of specials) {
@@ -105,6 +110,10 @@
{#if topSpecials.length > 0 || bottomSpecials.length > 0}<TreeSeparator />{/if}
{#if starred.length}
<StarredNav label={preference.string.Starred} spaces={starred} on:space {currentSpace} />
{/if}
{#each model.spaces as m (m.label)}
<SpacesNav spaces={shownSpaces.filter(it => hierarchy.isDerived(it._class, m.spaceClass))} {currentSpace} model={m} on:space {currentSpecial}/>
{/each}
@@ -21,6 +21,7 @@
import { getClient } from '@anticrm/presentation'
import { Action, AnyComponent, IconAdd, IconEdit, showPanel, showPopup } from '@anticrm/ui'
import view from '@anticrm/view'
import preference from '@anticrm/preference'
import { getActions as getContributedActions } from '@anticrm/view-resources'
import { SpacesNavModel } from '@anticrm/workbench'
import { createEventDispatcher } from 'svelte'
@@ -54,7 +55,17 @@
}
}
async function getEditor(_class: Ref<Class<Doc>>): Promise<AnyComponent | undefined> {
const starSpace: Action = {
label: preference.string.Star,
icon: preference.icon.Star,
action: async (_id: Ref<Space>): Promise<void> => {
await client.createDoc(preference.class.SpacePreference, preference.space.Preference, {
attachedTo: _id
})
}
}
async function getEditor (_class: Ref<Class<Doc>>): Promise<AnyComponent | undefined> {
const hierarchy = client.getHierarchy()
const clazz = hierarchy.getClass(_class)
const editorMixin = hierarchy.as(clazz, view.mixin.ObjectEditor)
@@ -62,12 +73,12 @@
return editorMixin.editor
}
function selectSpace(id: Ref<Space>, spaceSpecial?: string) {
function selectSpace (id: Ref<Space>, spaceSpecial?: string) {
dispatch('space', { space: id, spaceSpecial })
}
async function getActions(space: Space): Promise<Action[]> {
const result = [editSpace]
async function getActions (space: Space): Promise<Action[]> {
const result = [editSpace, starSpace]
const extraActions = await getContributedActions(client, space, core.class.Space)
for (const act of extraActions) {
@@ -0,0 +1,113 @@
<!--
// Copyright © 2022 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 type { Doc, Ref, Space } from '@anticrm/core'
import core from '@anticrm/core'
import notification from '@anticrm/notification'
import { NotificationClientImpl } from '@anticrm/notification-resources'
import { getResource, IntlString } from '@anticrm/platform'
import preference from '@anticrm/preference'
import { getClient } from '@anticrm/presentation'
import { Action, IconEdit } from '@anticrm/ui'
import view from '@anticrm/view'
import { getActions as getContributedActions } from '@anticrm/view-resources'
import { createEventDispatcher } from 'svelte'
import { classIcon } from '../../utils'
import TreeItem from './TreeItem.svelte'
import TreeNode from './TreeNode.svelte'
export let label: IntlString
export let currentSpace: Ref<Space> | undefined
export let spaces: Space[]
const client = getClient()
const dispatch = createEventDispatcher()
const unStarSpace: Action = {
label: preference.string.Unstar,
icon: preference.icon.Star,
action: async (_id: Ref<Doc>): Promise<void> => {
const current = await client.findOne(preference.class.SpacePreference, { attachedTo: _id as Ref<Space> })
if (current !== undefined) {
await client.remove(current)
}
}
}
const unStarAll: Action = {
label: preference.string.DeleteStarred,
icon: view.icon.Delete,
action: async (): Promise<void> => {
const ids = spaces.map((space) => space._id)
const current = await client.findAll(preference.class.SpacePreference, { attachedTo: { $in: ids } })
await Promise.all(current.map(async (item) => { await client.remove(item) }))
}
}
function selectSpace (id: Ref<Space>, spaceSpecial?: string) {
dispatch('space', { space: id, spaceSpecial })
}
async function getActions (space: Space): Promise<Action[]> {
const result = [unStarSpace]
const extraActions = await getContributedActions(client, space, core.class.Space)
for (const act of extraActions) {
result.push({
icon: act.icon ?? IconEdit,
label: act.label,
action: async () => {
const impl = await getResource(act.action)
await impl(space)
}
})
}
return result
}
const notificationClient = NotificationClientImpl.getClient()
const lastViews = notificationClient.getLastViews()
const hierarchy = client.getHierarchy()
function isChanged (space: Space, lastViews: Map<Ref<Doc>, number>): boolean {
const clazz = hierarchy.getClass(space._class)
const lastEditMixin = hierarchy.as(clazz, notification.mixin.SpaceLastEdit)
const field = lastEditMixin?.lastEditField
const lastView = lastViews.get(space._id)
if (lastView === undefined || lastView === -1) return false
if (field === undefined) return false
const value = (space as any)[field]
if (isNaN(value)) return false
return lastView < value
}
</script>
<TreeNode label={label} parent actions={async () => [unStarAll]} indent={'ml-2'}>
{#each spaces as space (space._id)}
<TreeItem
indent={'ml-4'}
_id={space._id}
title={space.name}
icon={classIcon(client, space._class)}
selected={currentSpace === space._id}
actions={() => getActions(space)}
bold={isChanged(space, $lastViews)}
on:click={() => {
selectSpace(space._id)
}}
/>
{/each}
</TreeNode>
+2
View File
@@ -98,6 +98,8 @@
"@anticrm/notification": "~0.6.0",
"@anticrm/notification-assets": "~0.6.0",
"@anticrm/notification-resources": "~0.6.0",
"@anticrm/preference": "~0.6.0",
"@anticrm/preference-assets": "~0.6.0",
"@anticrm/calendar": "~0.6.0",
"@anticrm/calendar-assets": "~0.6.0",
"@anticrm/calendar-resources": "~0.6.0",
+2
View File
@@ -33,6 +33,7 @@ import { templatesId } from '@anticrm/templates'
import { notificationId } from '@anticrm/notification'
import { calendarId } from '@anticrm/calendar'
import { trackerId } from '@anticrm/tracker'
import { preferenceId } from '@anticrm/preference'
import '@anticrm/login-assets'
import '@anticrm/task-assets'
@@ -47,6 +48,7 @@ import '@anticrm/gmail-assets'
import '@anticrm/workbench-assets'
import '@anticrm/templates-assets'
import '@anticrm/notification-assets'
import '@anticrm/preference-assets'
import '@anticrm/tracker-assets'
import presentation, { presentationId } from '@anticrm/presentation'
import { coreId } from '@anticrm/core'
+25
View File
@@ -1262,6 +1262,31 @@
"packageName": "@anticrm/kanban",
"projectFolder": "packages/kanban",
"shouldPublish": true
},
{
"packageName": "@anticrm/preference",
"projectFolder": "plugins/preference",
"shouldPublish": true
},
{
"packageName": "@anticrm/preference-assets",
"projectFolder": "plugins/preference-assets",
"shouldPublish": true
},
{
"packageName": "@anticrm/model-preference",
"projectFolder": "models/preference",
"shouldPublish": true
},
{
"packageName": "@anticrm/server-preference",
"projectFolder": "server-plugins/preference",
"shouldPublish": true
},
{
"packageName": "@anticrm/middleware",
"projectFolder": "server/middleware",
"shouldPublish": true
}
]
}
+53 -1
View File
@@ -15,7 +15,21 @@
import chunter, { Channel, Comment, Message, ThreadMessage } from '@anticrm/chunter'
import { EmployeeAccount } from '@anticrm/contact'
import core, { Class, Doc, DocumentQuery, FindOptions, FindResult, Hierarchy, Ref, Tx, TxCreateDoc, TxProcessor, TxUpdateDoc, TxRemoveDoc } from '@anticrm/core'
import core, {
Class,
Doc,
DocumentQuery,
FindOptions,
FindResult,
Hierarchy,
Ref,
Tx,
TxCreateDoc,
TxProcessor,
TxUpdateDoc,
TxRemoveDoc,
TxCollectionCUD
} from '@anticrm/core'
import login from '@anticrm/login'
import { getMetadata } from '@anticrm/platform'
import { TriggerControl } from '@anticrm/server-core'
@@ -144,12 +158,50 @@ export async function MessageCreate (tx: Tx, control: TriggerControl): Promise<T
return []
}
/**
* @public
*/
export async function MessageDelete (tx: Tx, control: TriggerControl): Promise<Tx[]> {
const hierarchy = control.hierarchy
if (tx._class !== core.class.TxCollectionCUD) return []
const rmTx = (tx as TxCollectionCUD<Channel, Message>).tx
if (!hierarchy.isDerived(rmTx.objectClass, chunter.class.Message)) {
return []
}
const createTx = (await control.findAll(core.class.TxCreateDoc, {
objectId: rmTx.objectId
}, { limit: 1 }))[0]
const message = TxProcessor.createDoc2Doc(createTx as TxCreateDoc<Message>)
const channel = (await control.findAll(chunter.class.Channel, {
_id: message.space
}, { limit: 1 }))[0]
if (channel.lastMessage === message.createOn) {
const messages = await control.findAll(chunter.class.Message, {
attachedTo: channel._id
})
const lastMessageDate = messages.reduce((maxDate, mess) => mess.createOn > maxDate ? mess.createOn : maxDate, 0)
const updateTx = control.txFactory.createTxUpdateDoc<Channel>(channel._class, channel.space, channel._id, {
lastMessage: lastMessageDate > 0 ? lastMessageDate : undefined
})
return [updateTx]
}
return []
}
/**
* @public
*/
export async function ChunterTrigger (tx: Tx, control: TriggerControl): Promise<Tx[]> {
const promises = [
MessageCreate(tx, control),
MessageDelete(tx, control),
CommentCreate(tx, control),
CommentDelete(tx, control)
]

Some files were not shown because too many files have changed in this diff Show More