mirror of
https://github.com/hcengineering/platform.git
synced 2026-08-17 18:05:42 +02:00
Cards fixes (#8003)
Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
Generated
+91
-6
@@ -475,6 +475,9 @@ importers:
|
||||
'@rush-temp/model-server-calendar':
|
||||
specifier: file:./projects/model-server-calendar.tgz
|
||||
version: file:projects/model-server-calendar.tgz
|
||||
'@rush-temp/model-server-card':
|
||||
specifier: file:./projects/model-server-card.tgz
|
||||
version: file:projects/model-server-card.tgz
|
||||
'@rush-temp/model-server-chunter':
|
||||
specifier: file:./projects/model-server-chunter.tgz
|
||||
version: file:projects/model-server-chunter.tgz
|
||||
@@ -820,6 +823,12 @@ importers:
|
||||
'@rush-temp/server-calendar-resources':
|
||||
specifier: file:./projects/server-calendar-resources.tgz
|
||||
version: file:projects/server-calendar-resources.tgz(@babel/core@7.23.9)(@jest/types@29.6.3)(@types/node@20.11.19)(babel-jest@29.7.0(@babel/core@7.23.9))(esbuild@0.24.2)(ts-node@10.9.2(@types/node@20.11.19)(typescript@5.3.3))
|
||||
'@rush-temp/server-card':
|
||||
specifier: file:./projects/server-card.tgz
|
||||
version: file:projects/server-card.tgz(@babel/core@7.23.9)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.23.9))(esbuild@0.24.2)(ts-node@10.9.2(@types/node@20.11.19)(typescript@5.3.3))
|
||||
'@rush-temp/server-card-resources':
|
||||
specifier: file:./projects/server-card-resources.tgz
|
||||
version: file:projects/server-card-resources.tgz(@babel/core@7.23.9)(@jest/types@29.6.3)(@types/node@20.11.19)(babel-jest@29.7.0(@babel/core@7.23.9))(esbuild@0.24.2)(ts-node@10.9.2(@types/node@20.11.19)(typescript@5.3.3))
|
||||
'@rush-temp/server-chunter':
|
||||
specifier: file:./projects/server-chunter.tgz
|
||||
version: file:projects/server-chunter.tgz(@babel/core@7.23.9)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.23.9))(esbuild@0.24.2)(ts-node@10.9.2(@types/node@20.11.19)(typescript@5.3.3))
|
||||
@@ -3920,7 +3929,7 @@ packages:
|
||||
version: 0.0.0
|
||||
|
||||
'@rush-temp/card@file:projects/card.tgz':
|
||||
resolution: {integrity: sha512-rEy3T9z44ZC/94K3q01+0gbaZ0sJBlwB7KvmQuAbiZMU31t+pzS+yoIYkry7YHm/Ca7kd7H4uvcROiAr7yb91w==, tarball: file:projects/card.tgz}
|
||||
resolution: {integrity: sha512-/vbzO3L239DHkxNpG5cw/ngDrvuvXrRcoq46Y7uJzNwSNAOMpiiRSMdpj4dr2bmPXEhkFGPeA0mKLJwXLseEhw==, tarball: file:projects/card.tgz}
|
||||
version: 0.0.0
|
||||
|
||||
'@rush-temp/chunter-assets@file:projects/chunter-assets.tgz':
|
||||
@@ -4208,7 +4217,7 @@ packages:
|
||||
version: 0.0.0
|
||||
|
||||
'@rush-temp/model-all@file:projects/model-all.tgz':
|
||||
resolution: {integrity: sha512-gkINR6HbfTB1ZSXTI2WHg9B+zIZtcSmb+Bb9fVxbGtq6e+RnxSlOlvjEBoru3ElfIO8x6SYd/KoQhRossQQKnQ==, tarball: file:projects/model-all.tgz}
|
||||
resolution: {integrity: sha512-aDRtqYlITENsW02SJbs+KjoynFL+/QYQmYQVN6++EqsnAT+jHGPl/UpnmSr3ZMcALGcwTx61LKQ1nnxNen8tyA==, tarball: file:projects/model-all.tgz}
|
||||
version: 0.0.0
|
||||
|
||||
'@rush-temp/model-analytics-collector@file:projects/model-analytics-collector.tgz':
|
||||
@@ -4343,6 +4352,10 @@ packages:
|
||||
resolution: {integrity: sha512-tZOPOELF87VVVRzz1CpJN2JWcTMcpO5l+9pbFEB0YHiGZnBd0oujKLMRiSaI4xXiGo0vhA9BjuHpMvcw7zzw0A==, tarball: file:projects/model-server-calendar.tgz}
|
||||
version: 0.0.0
|
||||
|
||||
'@rush-temp/model-server-card@file:projects/model-server-card.tgz':
|
||||
resolution: {integrity: sha512-SP381dOkAOymE4k9VaEP2o28yv7oJxJEol5LeYjcqHXkyN/al/v8UetEGmS4/gvuxk0Np0KZPpLnczV9O3ApUg==, tarball: file:projects/model-server-card.tgz}
|
||||
version: 0.0.0
|
||||
|
||||
'@rush-temp/model-server-chunter@file:projects/model-server-chunter.tgz':
|
||||
resolution: {integrity: sha512-qZkbTEVB25W2XQRrvecg/aTQ+kJhY4uBaq6AVGaEf9jS7ZI7NBzk71rXSv9800GGIe0CPWRpSP/453yZQKfNrg==, tarball: file:projects/model-server-chunter.tgz}
|
||||
version: 0.0.0
|
||||
@@ -4672,7 +4685,7 @@ packages:
|
||||
version: 0.0.0
|
||||
|
||||
'@rush-temp/prod@file:projects/prod.tgz':
|
||||
resolution: {integrity: sha512-m903gPliLrwApgEDgmJCrV74n9agm3V0SYbi3pYOmiVhcY6MPCRk8FprcHnYIW/VAqbzL/Bn8BYnLF8O9IQBLQ==, tarball: file:projects/prod.tgz}
|
||||
resolution: {integrity: sha512-VeRmwZegE4UJxGZfbKFP55egCMYIBuW6fCawXf1NHcXc93vnE6m9sax3s/O++FU3bMIhusbA0d74ZRRCCozxTw==, tarball: file:projects/prod.tgz}
|
||||
version: 0.0.0
|
||||
|
||||
'@rush-temp/products-assets@file:projects/products-assets.tgz':
|
||||
@@ -4803,6 +4816,14 @@ packages:
|
||||
resolution: {integrity: sha512-FQkSKyOpAPE2lnQ6wDRR1ar17olXsmHTWD1vW+ajxSNMuayv4mEVFftAFVfj6b1WvqyuJMiWR8TDv3CRWW5bug==, tarball: file:projects/server-calendar.tgz}
|
||||
version: 0.0.0
|
||||
|
||||
'@rush-temp/server-card-resources@file:projects/server-card-resources.tgz':
|
||||
resolution: {integrity: sha512-lp3RSXAHV78ux6p3I0iSbVkfY3aB0fXDfxMhxQZfjU9sSXLOsKolgDxMaUNXVm+/WIpS6dwLk4jWHgGopH8Nmw==, tarball: file:projects/server-card-resources.tgz}
|
||||
version: 0.0.0
|
||||
|
||||
'@rush-temp/server-card@file:projects/server-card.tgz':
|
||||
resolution: {integrity: sha512-ATIgXsB+SGQDQHyZqzQ8ooD/yAjqyimZzjMVZLMFJvRQenPLZEwdW0D+ikWila7ixB115IvKtGhrM8y7mvnMog==, tarball: file:projects/server-card.tgz}
|
||||
version: 0.0.0
|
||||
|
||||
'@rush-temp/server-chunter-resources@file:projects/server-chunter-resources.tgz':
|
||||
resolution: {integrity: sha512-anzs0C7AXicSpf9F+jjqdokc3cZunw5O8gC0095fQmEoOW94OmvTyboGrx8iK3i+iYam4Z6Bkt60rXRo3JvXZQ==, tarball: file:projects/server-chunter-resources.tgz}
|
||||
version: 0.0.0
|
||||
@@ -4940,7 +4961,7 @@ packages:
|
||||
version: 0.0.0
|
||||
|
||||
'@rush-temp/server-pipeline@file:projects/server-pipeline.tgz':
|
||||
resolution: {integrity: sha512-AEDx9sHjL/Dh1KCJ0DiICkS4JZ24HrqPlz4Wp09xmI9d3q35DpCS2oP8+qUjSF1qX5kZSIu0GrDshztCFYQScQ==, tarball: file:projects/server-pipeline.tgz}
|
||||
resolution: {integrity: sha512-fmLSX42maXiVGBW3TM2hB1y5UG+weaJSBpInOVUMf/5Y59N3s0rACYuHR+wuzcmIdIRiXKMN7ypET7mz4sxT2g==, tarball: file:projects/server-pipeline.tgz}
|
||||
version: 0.0.0
|
||||
|
||||
'@rush-temp/server-preference@file:projects/server-preference.tgz':
|
||||
@@ -5056,7 +5077,7 @@ packages:
|
||||
version: 0.0.0
|
||||
|
||||
'@rush-temp/setting-resources@file:projects/setting-resources.tgz':
|
||||
resolution: {integrity: sha512-XudJm31jzp8dEwS9TsP8lpxB2/yYjvafLULunIlgG46LdhQBfDG711m9w9gLb41Jj9O1quj2egHI87fHfmwrrg==, tarball: file:projects/setting-resources.tgz}
|
||||
resolution: {integrity: sha512-FVaGM7H5JXFKHZ7thrdx9tj94krIQWzJ61p/QXBwhW78ycK9lNliYCHP0pLMA+rsScxAdmR6wa7n7nRO5XBGbw==, tarball: file:projects/setting-resources.tgz}
|
||||
version: 0.0.0
|
||||
|
||||
'@rush-temp/setting@file:projects/setting.tgz':
|
||||
@@ -5204,7 +5225,7 @@ packages:
|
||||
version: 0.0.0
|
||||
|
||||
'@rush-temp/tool@file:projects/tool.tgz':
|
||||
resolution: {integrity: sha512-dMjmwZoox3cdyn4o7EY2NL8Kst4si0xI+HT9YSnaQd7A4cBwapn/oXi163xq2f9mOhf2oekXpt2iT1hK8JoQQQ==, tarball: file:projects/tool.tgz}
|
||||
resolution: {integrity: sha512-1/Aodec9LySBThOZAeA3XJfP83lSVTVRFdlb8uNTORq1sHJxCADx9BliCh2dTzdhydW46wcAxMPeArhopkcN1Q==, tarball: file:projects/tool.tgz}
|
||||
version: 0.0.0
|
||||
|
||||
'@rush-temp/tracker-assets@file:projects/tracker-assets.tgz':
|
||||
@@ -19404,6 +19425,20 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@rush-temp/model-server-card@file:projects/model-server-card.tgz':
|
||||
dependencies:
|
||||
'@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.3.3))(eslint@8.56.0)(typescript@5.7.3)
|
||||
'@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.7.3)
|
||||
eslint: 8.56.0
|
||||
eslint-config-standard-with-typescript: 40.0.0(@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.3.3))(eslint@8.56.0)(typescript@5.3.3))(eslint-plugin-import@2.29.1(eslint@8.56.0))(eslint-plugin-n@15.7.0(eslint@8.56.0))(eslint-plugin-promise@6.1.1(eslint@8.56.0))(eslint@8.56.0)(typescript@5.7.3)
|
||||
eslint-plugin-import: 2.29.1(eslint@8.56.0)
|
||||
eslint-plugin-n: 15.7.0(eslint@8.56.0)
|
||||
eslint-plugin-promise: 6.1.1(eslint@8.56.0)
|
||||
prettier: 3.2.5
|
||||
typescript: 5.7.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@rush-temp/model-server-chunter@file:projects/model-server-chunter.tgz':
|
||||
dependencies:
|
||||
'@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.3.3))(eslint@8.56.0)(typescript@5.3.3)
|
||||
@@ -22459,6 +22494,56 @@ snapshots:
|
||||
- supports-color
|
||||
- ts-node
|
||||
|
||||
'@rush-temp/server-card-resources@file:projects/server-card-resources.tgz(@babel/core@7.23.9)(@jest/types@29.6.3)(@types/node@20.11.19)(babel-jest@29.7.0(@babel/core@7.23.9))(esbuild@0.24.2)(ts-node@10.9.2(@types/node@20.11.19)(typescript@5.3.3))':
|
||||
dependencies:
|
||||
'@types/jest': 29.5.12
|
||||
'@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.3.3))(eslint@8.56.0)(typescript@5.7.3)
|
||||
'@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.7.3)
|
||||
eslint: 8.56.0
|
||||
eslint-config-standard-with-typescript: 40.0.0(@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.3.3))(eslint@8.56.0)(typescript@5.3.3))(eslint-plugin-import@2.29.1(eslint@8.56.0))(eslint-plugin-n@15.7.0(eslint@8.56.0))(eslint-plugin-promise@6.1.1(eslint@8.56.0))(eslint@8.56.0)(typescript@5.7.3)
|
||||
eslint-plugin-import: 2.29.1(eslint@8.56.0)
|
||||
eslint-plugin-n: 15.7.0(eslint@8.56.0)
|
||||
eslint-plugin-promise: 6.1.1(eslint@8.56.0)
|
||||
jest: 29.7.0(@types/node@20.11.19)(ts-node@10.9.2(@types/node@20.11.19)(typescript@5.3.3))
|
||||
prettier: 3.2.5
|
||||
ts-jest: 29.1.2(@babel/core@7.23.9)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.23.9))(esbuild@0.24.2)(jest@29.7.0(@types/node@20.11.19)(ts-node@10.9.2(@types/node@20.11.19)(typescript@5.3.3)))(typescript@5.7.3)
|
||||
typescript: 5.7.3
|
||||
transitivePeerDependencies:
|
||||
- '@babel/core'
|
||||
- '@jest/types'
|
||||
- '@types/node'
|
||||
- babel-jest
|
||||
- babel-plugin-macros
|
||||
- esbuild
|
||||
- node-notifier
|
||||
- supports-color
|
||||
- ts-node
|
||||
|
||||
'@rush-temp/server-card@file:projects/server-card.tgz(@babel/core@7.23.9)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.23.9))(esbuild@0.24.2)(ts-node@10.9.2(@types/node@20.11.19)(typescript@5.3.3))':
|
||||
dependencies:
|
||||
'@types/jest': 29.5.12
|
||||
'@types/node': 20.11.19
|
||||
'@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.3.3))(eslint@8.56.0)(typescript@5.7.3)
|
||||
'@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.7.3)
|
||||
eslint: 8.56.0
|
||||
eslint-config-standard-with-typescript: 40.0.0(@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.3.3))(eslint@8.56.0)(typescript@5.3.3))(eslint-plugin-import@2.29.1(eslint@8.56.0))(eslint-plugin-n@15.7.0(eslint@8.56.0))(eslint-plugin-promise@6.1.1(eslint@8.56.0))(eslint@8.56.0)(typescript@5.7.3)
|
||||
eslint-plugin-import: 2.29.1(eslint@8.56.0)
|
||||
eslint-plugin-n: 15.7.0(eslint@8.56.0)
|
||||
eslint-plugin-promise: 6.1.1(eslint@8.56.0)
|
||||
jest: 29.7.0(@types/node@20.11.19)(ts-node@10.9.2(@types/node@20.11.19)(typescript@5.3.3))
|
||||
prettier: 3.2.5
|
||||
ts-jest: 29.1.2(@babel/core@7.23.9)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.23.9))(esbuild@0.24.2)(jest@29.7.0(@types/node@20.11.19)(ts-node@10.9.2(@types/node@20.11.19)(typescript@5.3.3)))(typescript@5.7.3)
|
||||
typescript: 5.7.3
|
||||
transitivePeerDependencies:
|
||||
- '@babel/core'
|
||||
- '@jest/types'
|
||||
- babel-jest
|
||||
- babel-plugin-macros
|
||||
- esbuild
|
||||
- node-notifier
|
||||
- supports-color
|
||||
- ts-node
|
||||
|
||||
'@rush-temp/server-chunter-resources@file:projects/server-chunter-resources.tgz(@babel/core@7.23.9)(@jest/types@29.6.3)(@types/node@20.11.19)(babel-jest@29.7.0(@babel/core@7.23.9))(esbuild@0.24.2)(ts-node@10.9.2(@types/node@20.11.19)(typescript@5.3.3))':
|
||||
dependencies:
|
||||
'@types/jest': 29.5.12
|
||||
|
||||
@@ -110,16 +110,6 @@
|
||||
"@hcengineering/inventory": "^0.6.11",
|
||||
"@hcengineering/inventory-assets": "^0.6.11",
|
||||
"@hcengineering/inventory-resources": "^0.6.0",
|
||||
"@hcengineering/server-attachment": "^0.6.1",
|
||||
"@hcengineering/server-attachment-resources": "^0.6.0",
|
||||
"@hcengineering/server-collaboration": "^0.6.0",
|
||||
"@hcengineering/server-collaboration-resources": "^0.6.0",
|
||||
"@hcengineering/server-contact": "^0.6.1",
|
||||
"@hcengineering/server-contact-resources": "^0.6.0",
|
||||
"@hcengineering/server-notification": "^0.6.1",
|
||||
"@hcengineering/server-notification-resources": "^0.6.0",
|
||||
"@hcengineering/server-setting": "^0.6.0",
|
||||
"@hcengineering/server-setting-resources": "^0.6.0",
|
||||
"@hcengineering/templates": "^0.6.11",
|
||||
"@hcengineering/templates-assets": "^0.6.11",
|
||||
"@hcengineering/templates-resources": "^0.6.0",
|
||||
@@ -133,30 +123,10 @@
|
||||
"@hcengineering/tags-assets": "^0.6.0",
|
||||
"@hcengineering/tags": "^0.6.16",
|
||||
"@hcengineering/tags-resources": "^0.6.0",
|
||||
"@hcengineering/server-chunter": "^0.6.0",
|
||||
"@hcengineering/server-chunter-resources": "^0.6.0",
|
||||
"@hcengineering/server-inventory": "^0.6.1",
|
||||
"@hcengineering/server-inventory-resources": "^0.6.0",
|
||||
"@hcengineering/server-lead": "^0.6.0",
|
||||
"@hcengineering/server-lead-resources": "^0.6.0",
|
||||
"@hcengineering/server-recruit": "^0.6.0",
|
||||
"@hcengineering/server-recruit-resources": "^0.6.0",
|
||||
"@hcengineering/server-tags": "^0.6.0",
|
||||
"@hcengineering/server-tags-resources": "^0.6.0",
|
||||
"@hcengineering/server-task": "^0.6.0",
|
||||
"@hcengineering/server-task-resources": "^0.6.0",
|
||||
"@hcengineering/calendar": "^0.6.24",
|
||||
"@hcengineering/calendar-assets": "^0.6.22",
|
||||
"@hcengineering/calendar-resources": "^0.6.0",
|
||||
"@hcengineering/server-calendar": "^0.6.0",
|
||||
"@hcengineering/server-calendar-resources": "^0.6.0",
|
||||
"@hcengineering/server-gmail": "^0.6.0",
|
||||
"@hcengineering/server-gmail-resources": "^0.6.0",
|
||||
"@hcengineering/server-telegram": "^0.6.0",
|
||||
"@hcengineering/server-telegram-resources": "^0.6.0",
|
||||
"@hcengineering/presentation": "^0.6.3",
|
||||
"@hcengineering/server-tracker": "^0.6.0",
|
||||
"@hcengineering/server-tracker-resources": "^0.6.0",
|
||||
"@hcengineering/tracker": "^0.6.24",
|
||||
"@hcengineering/tracker-assets": "^0.6.0",
|
||||
"@hcengineering/tracker-resources": "^0.6.0",
|
||||
@@ -169,20 +139,12 @@
|
||||
"@hcengineering/hr": "^0.6.19",
|
||||
"@hcengineering/hr-assets": "^0.6.19",
|
||||
"@hcengineering/hr-resources": "^0.6.0",
|
||||
"@hcengineering/server-hr": "^0.6.0",
|
||||
"@hcengineering/server-hr-resources": "^0.6.0",
|
||||
"@hcengineering/bitrix": "^0.6.52",
|
||||
"@hcengineering/bitrix-assets": "^0.6.0",
|
||||
"@hcengineering/bitrix-resources": "^0.6.0",
|
||||
"@hcengineering/request": "^0.6.14",
|
||||
"@hcengineering/request-assets": "^0.6.0",
|
||||
"@hcengineering/request-resources": "^0.6.0",
|
||||
"@hcengineering/server-request": "^0.6.0",
|
||||
"@hcengineering/server-request-resources": "^0.6.0",
|
||||
"@hcengineering/server-view": "^0.6.0",
|
||||
"@hcengineering/server-view-resources": "^0.6.0",
|
||||
"@hcengineering/server-activity": "^0.6.0",
|
||||
"@hcengineering/server-activity-resources": "^0.6.0",
|
||||
"@hcengineering/drive": "^0.6.0",
|
||||
"@hcengineering/drive-assets": "^0.6.0",
|
||||
"@hcengineering/drive-resources": "^0.6.0",
|
||||
|
||||
@@ -101,6 +101,8 @@
|
||||
"@hcengineering/server-storage": "^0.6.0",
|
||||
"@hcengineering/server-calendar": "^0.6.0",
|
||||
"@hcengineering/server-calendar-resources": "^0.6.0",
|
||||
"@hcengineering/server-card": "^0.6.0",
|
||||
"@hcengineering/server-card-resources": "^0.6.0",
|
||||
"@hcengineering/server-chunter": "^0.6.0",
|
||||
"@hcengineering/server-chunter-resources": "^0.6.0",
|
||||
"@hcengineering/server-contact": "^0.6.1",
|
||||
|
||||
@@ -23,6 +23,7 @@ import { devTool } from '.'
|
||||
import { addLocation } from '@hcengineering/platform'
|
||||
import { serverActivityId } from '@hcengineering/server-activity'
|
||||
import { serverAttachmentId } from '@hcengineering/server-attachment'
|
||||
import { serverCardId } from '@hcengineering/server-card'
|
||||
import { serverCalendarId } from '@hcengineering/server-calendar'
|
||||
import { serverChunterId } from '@hcengineering/server-chunter'
|
||||
import { serverCollaborationId } from '@hcengineering/server-collaboration'
|
||||
@@ -59,6 +60,7 @@ addLocation(serverSettingId, () => import('@hcengineering/server-setting-resourc
|
||||
addLocation(serverTaskId, () => import('@hcengineering/server-task-resources'))
|
||||
addLocation(serverTrackerId, () => import('@hcengineering/server-tracker-resources'))
|
||||
addLocation(serverTagsId, () => import('@hcengineering/server-tags-resources'))
|
||||
addLocation(serverCardId, () => import('@hcengineering/server-card-resources'))
|
||||
addLocation(serverCalendarId, () => import('@hcengineering/server-calendar-resources'))
|
||||
addLocation(serverGmailId, () => import('@hcengineering/server-gmail-resources'))
|
||||
addLocation(serverTelegramId, () => import('@hcengineering/server-telegram-resources'))
|
||||
|
||||
@@ -87,6 +87,7 @@
|
||||
"@hcengineering/model-bitrix": "^0.6.0",
|
||||
"@hcengineering/model-request": "^0.6.0",
|
||||
"@hcengineering/model-support": "^0.6.0",
|
||||
"@hcengineering/model-server-card": "^0.6.0",
|
||||
"@hcengineering/model-server-request": "^0.6.0",
|
||||
"@hcengineering/model-server-view": "^0.6.0",
|
||||
"@hcengineering/model-server-activity": "^0.6.0",
|
||||
|
||||
@@ -37,6 +37,7 @@ import { requestId, createModel as requestModel } from '@hcengineering/model-req
|
||||
import { aiBotId, createModel as aiBotModel } from '@hcengineering/model-ai-bot'
|
||||
import { serverActivityId, createModel as serverActivityModel } from '@hcengineering/model-server-activity'
|
||||
import { serverAttachmentId, createModel as serverAttachmentModel } from '@hcengineering/model-server-attachment'
|
||||
import { serverCardId, createModel as serverCardModel } from '@hcengineering/model-server-card'
|
||||
import { serverCalendarId, createModel as serverCalendarModel } from '@hcengineering/model-server-calendar'
|
||||
import { serverChunterId, createModel as serverChunterModel } from '@hcengineering/model-server-chunter'
|
||||
import {
|
||||
@@ -146,7 +147,8 @@ export default function buildModel (enabled: string[] = ['*'], disabled: string[
|
||||
view.class.Action,
|
||||
contact.class.ChannelProvider,
|
||||
setting.class.IntegrationType,
|
||||
setting.class.WorkspaceSettingCategory
|
||||
setting.class.WorkspaceSettingCategory,
|
||||
setting.class.SettingsCategory
|
||||
]
|
||||
|
||||
const builders: BuilderConfig[] = [
|
||||
@@ -366,7 +368,7 @@ export default function buildModel (enabled: string[] = ['*'], disabled: string[
|
||||
{
|
||||
label: card.string.Cards,
|
||||
description: card.string.ConfigDescription,
|
||||
enabled: false,
|
||||
enabled: true,
|
||||
beta: true,
|
||||
icon: card.icon.Card,
|
||||
classFilter: defaultFilter
|
||||
@@ -440,6 +442,7 @@ export default function buildModel (enabled: string[] = ['*'], disabled: string[
|
||||
[serverTagsModel, serverTagsId],
|
||||
[serverTaskModel, serverTaskId],
|
||||
[serverTrackerModel, serverTrackerId],
|
||||
[serverCardModel, serverCardId],
|
||||
[serverCalendarModel, serverCalendarId],
|
||||
[serverRecruitModel, serverRecruitId],
|
||||
[serverGmailModel, serverGmailId],
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/activity": "^0.6.0",
|
||||
"@hcengineering/contact": "^0.6.24",
|
||||
"@hcengineering/core": "^0.6.32",
|
||||
"@hcengineering/model": "^0.6.11",
|
||||
"@hcengineering/chunter": "^0.6.20",
|
||||
|
||||
+91
-18
@@ -14,8 +14,11 @@
|
||||
import activity from '@hcengineering/activity'
|
||||
import { CardEvents, cardId, type Card, type MasterTag, type Tag } from '@hcengineering/card'
|
||||
import chunter from '@hcengineering/chunter'
|
||||
import contact from '@hcengineering/contact'
|
||||
import core, {
|
||||
AccountRole,
|
||||
DOMAIN_MODEL,
|
||||
IndexKind,
|
||||
type CollectionSize,
|
||||
type Domain,
|
||||
type MarkupBlobRef,
|
||||
@@ -24,12 +27,12 @@ import core, {
|
||||
} from '@hcengineering/core'
|
||||
import {
|
||||
Collection,
|
||||
Index,
|
||||
Model,
|
||||
Prop,
|
||||
TypeCollaborativeDoc,
|
||||
TypeRef,
|
||||
TypeString,
|
||||
UX,
|
||||
type Builder
|
||||
} from '@hcengineering/model'
|
||||
import attachment from '@hcengineering/model-attachment'
|
||||
@@ -38,8 +41,9 @@ import presentation from '@hcengineering/model-presentation'
|
||||
import setting from '@hcengineering/model-setting'
|
||||
import view, { createAction } from '@hcengineering/model-view'
|
||||
import workbench from '@hcengineering/model-workbench'
|
||||
import { getEmbeddedLabel } from '@hcengineering/platform'
|
||||
import { getEmbeddedLabel, type IntlString } from '@hcengineering/platform'
|
||||
import time, { type ToDo } from '@hcengineering/time'
|
||||
import { type AnyComponent } from '@hcengineering/ui'
|
||||
import card from './plugin'
|
||||
|
||||
export { cardId } from '@hcengineering/card'
|
||||
@@ -53,12 +57,12 @@ export class TMasterTag extends TClass implements MasterTag {}
|
||||
export class TTag extends TMixin implements Tag {}
|
||||
|
||||
@Model(card.class.Card, core.class.Doc, DOMAIN_CARD)
|
||||
@UX(card.string.Card)
|
||||
export class TCard extends TDoc implements Card {
|
||||
@Prop(TypeRef(card.class.MasterTag), card.string.MasterTag)
|
||||
declare _class: Ref<MasterTag>
|
||||
|
||||
@Prop(TypeString(), core.string.Name)
|
||||
@Index(IndexKind.FullText)
|
||||
title!: string
|
||||
|
||||
@Prop(TypeCollaborativeDoc(), card.string.Content)
|
||||
@@ -75,8 +79,15 @@ export class TCard extends TDoc implements Card {
|
||||
todos?: CollectionSize<ToDo>
|
||||
}
|
||||
|
||||
@Model(card.class.MasterTagEditorSection, core.class.Doc, DOMAIN_MODEL)
|
||||
export class MasterTagEditorSection extends TDoc implements MasterTagEditorSection {
|
||||
id!: string
|
||||
label!: IntlString
|
||||
component!: AnyComponent
|
||||
}
|
||||
|
||||
export function createModel (builder: Builder): void {
|
||||
builder.createModel(TMasterTag, TTag, TCard)
|
||||
builder.createModel(TMasterTag, TTag, TCard, MasterTagEditorSection)
|
||||
|
||||
builder.createDoc(
|
||||
workbench.class.Application,
|
||||
@@ -113,20 +124,6 @@ export function createModel (builder: Builder): void {
|
||||
card.viewlet.CardTable
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
setting.class.WorkspaceSettingCategory,
|
||||
core.space.Model,
|
||||
{
|
||||
name: 'masterTags',
|
||||
label: card.string.Tags,
|
||||
icon: card.icon.Tags,
|
||||
role: AccountRole.User,
|
||||
component: card.component.MasterTags,
|
||||
order: 4400
|
||||
},
|
||||
card.ids.MasterTags
|
||||
)
|
||||
|
||||
builder.mixin(card.class.Card, core.class.Class, view.mixin.ObjectPresenter, {
|
||||
presenter: card.component.CardPresenter
|
||||
})
|
||||
@@ -268,11 +265,55 @@ export function createModel (builder: Builder): void {
|
||||
}
|
||||
})
|
||||
|
||||
builder.createDoc(
|
||||
setting.class.WorkspaceSettingCategory,
|
||||
core.space.Model,
|
||||
{
|
||||
name: 'tagrelation',
|
||||
label: card.string.TagRelations,
|
||||
icon: setting.icon.Relations,
|
||||
props: { _classes: [card.class.Card, contact.class.Contact], exclude: [] },
|
||||
component: setting.component.RelationSetting,
|
||||
group: 'settings-editor',
|
||||
role: AccountRole.Maintainer,
|
||||
order: 4501
|
||||
},
|
||||
card.ids.TagRelations
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
setting.class.SettingsCategory,
|
||||
core.space.Model,
|
||||
{
|
||||
name: 'masterTags',
|
||||
label: card.string.MasterTags,
|
||||
icon: card.icon.Card,
|
||||
component: card.component.ManageMasterTagsContent,
|
||||
extraComponents: {
|
||||
navigation: card.component.ManageMasterTags,
|
||||
tools: card.component.ManageMasterTagsTools
|
||||
},
|
||||
group: 'settings-editor',
|
||||
role: AccountRole.User,
|
||||
order: 5000,
|
||||
expandable: true
|
||||
},
|
||||
card.ids.ManageMasterTags
|
||||
)
|
||||
|
||||
builder.mixin(card.class.Card, core.class.Class, view.mixin.ClassFilters, {
|
||||
filters: [],
|
||||
ignoreKeys: ['parent']
|
||||
})
|
||||
|
||||
builder.createDoc(core.class.FullTextSearchContext, core.space.Model, {
|
||||
toClass: card.class.Card,
|
||||
fullTextSummary: true,
|
||||
forceIndex: true,
|
||||
childProcessingAllowed: true,
|
||||
propagate: []
|
||||
})
|
||||
|
||||
builder.createDoc(
|
||||
presentation.class.ObjectSearchCategory,
|
||||
core.space.Model,
|
||||
@@ -283,10 +324,42 @@ export function createModel (builder: Builder): void {
|
||||
query: card.completion.CardQuery,
|
||||
context: ['search', 'mention', 'spotlight'],
|
||||
classToSearch: card.class.Card,
|
||||
includeChilds: true,
|
||||
priority: 500
|
||||
},
|
||||
card.completion.CardCategory
|
||||
)
|
||||
|
||||
builder.createDoc(card.class.MasterTagEditorSection, core.space.Model, {
|
||||
id: 'general',
|
||||
label: setting.string.General,
|
||||
component: card.component.GeneralSection
|
||||
})
|
||||
|
||||
builder.createDoc(card.class.MasterTagEditorSection, core.space.Model, {
|
||||
id: 'properties',
|
||||
label: setting.string.Properties,
|
||||
component: card.component.ProperitiesSection
|
||||
})
|
||||
|
||||
builder.createDoc(card.class.MasterTagEditorSection, core.space.Model, {
|
||||
id: 'tags',
|
||||
label: card.string.Tags,
|
||||
component: card.component.TagsSection
|
||||
})
|
||||
|
||||
builder.createDoc(card.class.MasterTagEditorSection, core.space.Model, {
|
||||
id: 'childs',
|
||||
label: card.string.MasterTags,
|
||||
masterOnly: true,
|
||||
component: card.component.ChildsSection
|
||||
})
|
||||
|
||||
builder.createDoc(card.class.MasterTagEditorSection, core.space.Model, {
|
||||
id: 'relations',
|
||||
label: core.string.Relations,
|
||||
component: card.component.RelationsSection
|
||||
})
|
||||
}
|
||||
|
||||
export default card
|
||||
|
||||
@@ -34,7 +34,9 @@ export default mergeIds(cardId, card, {
|
||||
DeleteMasterTag: '' as Ref<Action>
|
||||
},
|
||||
ids: {
|
||||
MasterTags: '' as Ref<Doc>
|
||||
MasterTags: '' as Ref<Doc>,
|
||||
ManageMasterTags: '' as Ref<Doc>,
|
||||
TagRelations: '' as Ref<Doc>
|
||||
},
|
||||
completion: {
|
||||
CardQuery: '' as Resource<ObjectSearchFactory>,
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
extends: ['./node_modules/@hcengineering/platform-rig/profiles/model/eslint.config.json'],
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: './tsconfig.json'
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
*
|
||||
!/lib/**
|
||||
!CHANGELOG.md
|
||||
/lib/**/__tests__/
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
|
||||
"rigPackageName": "@hcengineering/platform-rig",
|
||||
"rigProfile": "model"
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"name": "@hcengineering/model-server-card",
|
||||
"version": "0.6.0",
|
||||
"main": "lib/index.js",
|
||||
"svelte": "src/index.ts",
|
||||
"types": "types/index.d.ts",
|
||||
"author": "Anticrm Platform Contributors",
|
||||
"template": "@hcengineering/model-package",
|
||||
"license": "EPL-2.0",
|
||||
"scripts": {
|
||||
"build": "compile",
|
||||
"build:watch": "compile",
|
||||
"format": "format src",
|
||||
"_phase:build": "compile transpile src",
|
||||
"_phase:format": "format src",
|
||||
"_phase:validate": "compile validate"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@hcengineering/platform-rig": "^0.6.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.11.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"eslint-plugin-n": "^15.4.0",
|
||||
"eslint": "^8.54.0",
|
||||
"@typescript-eslint/parser": "^6.11.0",
|
||||
"eslint-config-standard-with-typescript": "^40.0.0",
|
||||
"prettier": "^3.1.0",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/core": "^0.6.32",
|
||||
"@hcengineering/model": "^0.6.11",
|
||||
"@hcengineering/platform": "^0.6.11",
|
||||
"@hcengineering/card": "^0.6.0",
|
||||
"@hcengineering/server-card": "^0.6.0",
|
||||
"@hcengineering/server-core": "^0.6.1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
//
|
||||
// Copyright © 2025 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 Builder } from '@hcengineering/model'
|
||||
|
||||
import core from '@hcengineering/core'
|
||||
import serverCore from '@hcengineering/server-core'
|
||||
import serverCard from '@hcengineering/server-card'
|
||||
import card from '@hcengineering/card'
|
||||
|
||||
export { serverCardId } from '@hcengineering/server-card'
|
||||
|
||||
export function createModel (builder: Builder): void {
|
||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
trigger: serverCard.trigger.OnAttribute,
|
||||
isAsync: true,
|
||||
txMatch: {
|
||||
_class: core.class.TxCreateDoc,
|
||||
objectClass: core.class.Attribute
|
||||
}
|
||||
})
|
||||
|
||||
builder.mixin(card.class.Card, core.class.Class, serverCore.mixin.SearchPresenter, {
|
||||
searchIcon: card.icon.Card,
|
||||
title: [['title']]
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "./node_modules/@hcengineering/platform-rig/profiles/model/tsconfig.json",
|
||||
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./lib",
|
||||
"declarationDir": "./types",
|
||||
"tsBuildInfoFile": ".build/build.tsbuildinfo"
|
||||
}
|
||||
}
|
||||
@@ -32,18 +32,6 @@ export interface SearchItem {
|
||||
category: ObjectSearchCategory
|
||||
}
|
||||
|
||||
function findCategoryByClass (
|
||||
categories: ObjectSearchCategory[],
|
||||
_class: Ref<Class<Doc>>
|
||||
): ObjectSearchCategory | undefined {
|
||||
for (const category of categories) {
|
||||
if (category.classToSearch === _class) {
|
||||
return category
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
function packSearchResultsForListView (sections: SearchSection[]): SearchItem[] {
|
||||
let results: SearchItem[] = []
|
||||
for (const section of sections) {
|
||||
@@ -63,35 +51,37 @@ function packSearchResultsForListView (sections: SearchSection[]): SearchItem[]
|
||||
|
||||
async function searchCategory (
|
||||
client: TxOperations,
|
||||
cl: Ref<Class<Doc>>,
|
||||
category: ObjectSearchCategory,
|
||||
query: string,
|
||||
categories: ObjectSearchCategory[],
|
||||
limit?: number
|
||||
): Promise<SearchSection | undefined> {
|
||||
if (category.classToSearch === undefined) return
|
||||
const classes =
|
||||
category.includeChilds === true
|
||||
? client.getHierarchy().getDescendants(category.classToSearch)
|
||||
: [category.classToSearch]
|
||||
const r = await client.searchFulltext(
|
||||
{
|
||||
query: `${query}*`,
|
||||
classes: [cl]
|
||||
classes
|
||||
},
|
||||
{
|
||||
limit: limit ?? 5
|
||||
}
|
||||
)
|
||||
const category = findCategoryByClass(categories, cl)
|
||||
return category !== undefined ? { category, items: r.docs } : undefined
|
||||
return { category, items: r.docs }
|
||||
}
|
||||
|
||||
async function doFulltextSearch (
|
||||
client: TxOperations,
|
||||
classes: Array<Ref<Class<Doc>>>,
|
||||
query: string,
|
||||
categories: ObjectSearchCategory[],
|
||||
query: string,
|
||||
limit?: number
|
||||
): Promise<SearchSection[]> {
|
||||
const sections: SearchSection[] = []
|
||||
const promises: Array<Promise<SearchSection | undefined>> = []
|
||||
for (const cl of classes) {
|
||||
promises.push(searchCategory(client, cl, query, categories, limit))
|
||||
for (const cat of categories) {
|
||||
promises.push(searchCategory(client, cat, query, limit))
|
||||
}
|
||||
|
||||
const resolvedSections = await Promise.all(promises)
|
||||
@@ -140,6 +130,6 @@ export async function searchFor (
|
||||
}
|
||||
}
|
||||
|
||||
const sections = await doFulltextSearch(client, classesToSearch, query, categories, limit)
|
||||
const sections = await doFulltextSearch(client, cats, query, limit)
|
||||
return { items: packSearchResultsForListView(sections), query }
|
||||
}
|
||||
|
||||
@@ -72,6 +72,7 @@ export interface ObjectSearchCategory extends Doc {
|
||||
// Query for documents with pattern
|
||||
query: Resource<ObjectSearchFactory>
|
||||
classToSearch?: Ref<Class<Doc>>
|
||||
includeChilds?: boolean
|
||||
|
||||
priority?: number
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
import core, {
|
||||
Association,
|
||||
BulkUpdateEvent,
|
||||
Class,
|
||||
Client,
|
||||
@@ -30,6 +31,7 @@ import core, {
|
||||
Mixin,
|
||||
ModelDb,
|
||||
Ref,
|
||||
Relation,
|
||||
ReverseLookups,
|
||||
SearchOptions,
|
||||
SearchQuery,
|
||||
@@ -659,6 +661,7 @@ export class LiveQuery implements WithTx, Client {
|
||||
}
|
||||
}
|
||||
await this.handleDocUpdateLookup(q, tx)
|
||||
await this.handleDocUpdateRelation(q, tx)
|
||||
}
|
||||
}
|
||||
return {}
|
||||
@@ -724,6 +727,50 @@ export class LiveQuery implements WithTx, Client {
|
||||
return
|
||||
}
|
||||
await this.handleDocUpdateLookup(q, tx)
|
||||
await this.handleDocUpdateRelation(q, tx)
|
||||
}
|
||||
|
||||
private isPossibleAssociationTx (tx: TxUpdateDoc<Doc> | TxMixin<Doc, Doc>, association: Association): boolean {
|
||||
const h = this.getHierarchy()
|
||||
const byClass =
|
||||
h.isDerived(tx.objectClass, association.classA) ||
|
||||
h.isDerived(tx.objectClass, association.classB) ||
|
||||
h.isDerived(association.classA, tx.objectClass) ||
|
||||
h.isDerived(association.classB, tx.objectClass)
|
||||
if (byClass) {
|
||||
return true
|
||||
}
|
||||
if (tx._class === core.class.TxMixin) {
|
||||
const mixinTx = tx as TxMixin<Doc, Doc>
|
||||
return h.isDerived(mixinTx.mixin, association.classA) || h.isDerived(mixinTx.mixin, association.classB)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private async handleDocUpdateRelation (q: Query, tx: TxUpdateDoc<Doc> | TxMixin<Doc, Doc>): Promise<void> {
|
||||
if (q.options?.associations === undefined) return
|
||||
for (const assoc of q.options.associations) {
|
||||
const association = this.getModel().findObject(assoc[0])
|
||||
if (association === undefined) continue
|
||||
if (this.isPossibleAssociationTx(tx, association)) {
|
||||
if (q.result instanceof Promise) {
|
||||
q.result = await q.result
|
||||
}
|
||||
const docs = q.result.getDocs()
|
||||
for (const doc of docs) {
|
||||
const docToUpdate = doc.$associations?.[association._id].find((it) => it._id === tx.objectId)
|
||||
if (docToUpdate !== undefined) {
|
||||
if (tx._class === core.class.TxMixin) {
|
||||
TxProcessor.updateMixin4Doc(docToUpdate, tx as TxMixin<Doc, Doc>)
|
||||
} else {
|
||||
TxProcessor.updateDoc2Doc(docToUpdate, tx as TxUpdateDoc<Doc>)
|
||||
}
|
||||
q.result.updateDoc(doc, false)
|
||||
this.queriesToUpdate.set(q.id, q)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async handleDocUpdateLookup (q: Query, tx: TxUpdateDoc<Doc> | TxMixin<Doc, Doc>): Promise<void> {
|
||||
@@ -971,6 +1018,7 @@ export class LiveQuery implements WithTx, Client {
|
||||
}
|
||||
|
||||
await this.handleDocAddLookup(q, doc)
|
||||
await this.handleDocAddRelation(q, doc)
|
||||
}
|
||||
}
|
||||
return {}
|
||||
@@ -1039,6 +1087,35 @@ export class LiveQuery implements WithTx, Client {
|
||||
}
|
||||
}
|
||||
|
||||
private async handleDocAddRelation (q: Query, doc: Doc): Promise<void> {
|
||||
if (q.options?.associations === undefined) return
|
||||
if (doc._class !== core.class.Relation) return
|
||||
const relation = doc as Relation
|
||||
const assoc = q.options.associations.find((p) => p[0] === relation.association)
|
||||
if (assoc !== undefined) {
|
||||
if (q.result instanceof Promise) {
|
||||
q.result = await q.result
|
||||
}
|
||||
const direct = assoc[1] === 1
|
||||
const res = q.result.findDoc(direct ? relation.docA : relation.docB)
|
||||
if (res === undefined) return
|
||||
const association = this.getModel().findObject(assoc[0])
|
||||
if (association === undefined) return
|
||||
const docToPush = await this.findOne(!direct ? association.classB : association.classA, {
|
||||
_id: direct ? relation.docB : relation.docA
|
||||
})
|
||||
if (docToPush === undefined) return
|
||||
const arr = res?.$associations?.[relation.association] ?? []
|
||||
arr.push(docToPush)
|
||||
if (res?.$associations === undefined) {
|
||||
res.$associations = {}
|
||||
}
|
||||
res.$associations[relation.association] = arr
|
||||
q.result.updateDoc(res, false)
|
||||
this.queriesToUpdate.set(q.id, q)
|
||||
}
|
||||
}
|
||||
|
||||
private async handleDocAddLookup (q: Query, doc: Doc): Promise<void> {
|
||||
if (q.options?.lookup === undefined) return
|
||||
const lookup = q.options.lookup
|
||||
@@ -1120,16 +1197,16 @@ export class LiveQuery implements WithTx, Client {
|
||||
if (q.result instanceof Promise) {
|
||||
q.result = await q.result
|
||||
}
|
||||
if (
|
||||
q.options?.limit !== undefined &&
|
||||
q.options.limit === q.result.length &&
|
||||
h.isDerived(q._class, tx.objectClass)
|
||||
) {
|
||||
await this.refresh(q)
|
||||
return
|
||||
}
|
||||
const index = q.result.getDocs().find((p) => p._id === tx.objectId && h.isDerived(p._class, tx.objectClass))
|
||||
if (index !== undefined) {
|
||||
if (
|
||||
q.options?.limit !== undefined &&
|
||||
q.options.limit === q.result.length &&
|
||||
h.isDerived(q._class, tx.objectClass)
|
||||
) {
|
||||
await this.refresh(q)
|
||||
return
|
||||
}
|
||||
q.result.delete(index._id)
|
||||
this.refs.updateDocuments(q, [index], true)
|
||||
|
||||
@@ -1139,6 +1216,13 @@ export class LiveQuery implements WithTx, Client {
|
||||
await this.callback(q, true)
|
||||
}
|
||||
await this.handleDocRemoveLookup(q, tx)
|
||||
await this.handleDocRemoveRelation(q, tx)
|
||||
}
|
||||
|
||||
private async handleDocRemoveRelation (q: Query, tx: TxRemoveDoc<Doc>): Promise<void> {
|
||||
if (q.options?.associations === undefined) return
|
||||
if (tx.objectClass !== core.class.Relation) return
|
||||
await this.refresh(q)
|
||||
}
|
||||
|
||||
private async handleDocRemoveLookup (q: Query, tx: TxRemoveDoc<Doc>): Promise<void> {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
resultSort,
|
||||
WithLookup,
|
||||
type Class,
|
||||
type Doc,
|
||||
type Hierarchy,
|
||||
@@ -9,9 +10,9 @@ import {
|
||||
} from '@hcengineering/core'
|
||||
|
||||
export class ResultArray {
|
||||
private docs: Map<Ref<Doc>, Doc>
|
||||
private docs: Map<Ref<Doc>, WithLookup<Doc>>
|
||||
|
||||
private readonly clones = new Map<string, Map<Ref<Doc>, Doc>>()
|
||||
private readonly clones = new Map<string, Map<Ref<Doc>, WithLookup<Doc>>>()
|
||||
|
||||
get length (): number {
|
||||
return this.docs.size
|
||||
@@ -28,11 +29,11 @@ export class ResultArray {
|
||||
this.clones.clear()
|
||||
}
|
||||
|
||||
getDocs (): Doc[] {
|
||||
getDocs (): WithLookup<Doc>[] {
|
||||
return Array.from(this.docs.values())
|
||||
}
|
||||
|
||||
findDoc (_id: Ref<Doc>): Doc | undefined {
|
||||
findDoc (_id: Ref<Doc>): WithLookup<Doc> | undefined {
|
||||
return this.docs.get(_id)
|
||||
}
|
||||
|
||||
@@ -61,14 +62,14 @@ export class ResultArray {
|
||||
return doc
|
||||
}
|
||||
|
||||
updateDoc (doc: Doc, mainClone = true): void {
|
||||
updateDoc (doc: WithLookup<Doc>, mainClone = true): void {
|
||||
this.docs.set(doc._id, mainClone ? this.hierarchy.clone(doc) : doc)
|
||||
for (const [, v] of this.clones.entries()) {
|
||||
v.set(doc._id, this.hierarchy.clone(doc))
|
||||
}
|
||||
}
|
||||
|
||||
push (doc: Doc): void {
|
||||
push (doc: WithLookup<Doc>): void {
|
||||
this.docs.set(doc._id, this.hierarchy.clone(doc))
|
||||
for (const [, v] of this.clones.entries()) {
|
||||
v.set(doc._id, this.hierarchy.clone(doc))
|
||||
@@ -76,7 +77,7 @@ export class ResultArray {
|
||||
// this.changes.add(doc._id)
|
||||
}
|
||||
|
||||
pop (): Doc | undefined {
|
||||
pop (): WithLookup<Doc> | undefined {
|
||||
const lastElement = Array.from(this.docs)[this.docs.size - 1]
|
||||
if (lastElement !== undefined) {
|
||||
this.docs.delete(lastElement[0])
|
||||
|
||||
@@ -13,12 +13,12 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher, ComponentType } from 'svelte'
|
||||
import type { Asset, IntlString } from '@hcengineering/platform'
|
||||
import { AnySvelteComponent, BreadcrumbItem } from '../types'
|
||||
import type { IntlString } from '@hcengineering/platform'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { BreadcrumbItem } from '../types'
|
||||
import Breadcrumb from './Breadcrumb.svelte'
|
||||
import ChevronRight from './icons/ChevronRight.svelte'
|
||||
import Label from './Label.svelte'
|
||||
import Breadcrumb from './Breadcrumb.svelte'
|
||||
|
||||
export let items: BreadcrumbItem[]
|
||||
export let afterLabel: IntlString | undefined = undefined
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
<!--
|
||||
// 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 { IntlString } from '@hcengineering/platform'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import ui from '../plugin'
|
||||
import { showPopup } from '../popups'
|
||||
import type { DropdownIntlItem } from '../types'
|
||||
import Button from './Button.svelte'
|
||||
import DropdownIcon from './icons/Dropdown.svelte'
|
||||
import NestedMenu from './NestedMenu.svelte'
|
||||
import Label from './Label.svelte'
|
||||
|
||||
export let items: [DropdownIntlItem, DropdownIntlItem[]][]
|
||||
export let label: IntlString = ui.string.DropdownDefaultLabel
|
||||
export let disabled: boolean = false
|
||||
export let selected: DropdownIntlItem | undefined = undefined
|
||||
|
||||
let container: HTMLElement
|
||||
let opened: boolean = false
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
function openPopup (): void {
|
||||
if (!opened) {
|
||||
opened = true
|
||||
showPopup(NestedMenu, { items }, container, (result) => {
|
||||
if (result !== undefined) {
|
||||
selected = result
|
||||
dispatch('selected', result.id)
|
||||
}
|
||||
opened = false
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div bind:this={container}>
|
||||
<Button width={'min-content'} {disabled} on:click={openPopup}>
|
||||
<span slot="content" class="overflow-label disabled flex-grow text-left mr-2">
|
||||
<Label label={selected !== undefined ? selected.label : label} />
|
||||
</span>
|
||||
<svelte:fragment slot="iconRight">
|
||||
<DropdownIcon
|
||||
size={'small'}
|
||||
fill={!disabled ? 'var(--primary-button-content-color)' : 'var(--theme-dark-color)'}
|
||||
/>
|
||||
</svelte:fragment>
|
||||
</Button>
|
||||
</div>
|
||||
@@ -0,0 +1,119 @@
|
||||
<!--
|
||||
// Copyright © 2025 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 { createEventDispatcher } from 'svelte'
|
||||
import { Label, Scroller, Submenu } from '..'
|
||||
import { resizeObserver } from '../resize'
|
||||
import { DropdownIntlItem } from '../types'
|
||||
import NestedMenu from './NestedMenu.svelte'
|
||||
|
||||
export let items: [DropdownIntlItem, DropdownIntlItem[]][]
|
||||
export let nestedFrom: DropdownIntlItem | undefined = undefined
|
||||
export let onSelect: ((val: DropdownIntlItem) => void) | undefined = undefined
|
||||
|
||||
const elements: HTMLElement[] = []
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
const actionElements: HTMLButtonElement[] = []
|
||||
|
||||
const keyDown = (event: KeyboardEvent, index: number): void => {
|
||||
if (event.key === 'ArrowDown') {
|
||||
actionElements[(index + 1) % actionElements.length]?.focus()
|
||||
}
|
||||
|
||||
if (event.key === 'ArrowUp') {
|
||||
actionElements[(actionElements.length + index - 1) % actionElements.length]?.focus()
|
||||
}
|
||||
|
||||
if (event.key === 'ArrowLeft') {
|
||||
dispatch('close')
|
||||
}
|
||||
}
|
||||
|
||||
function onNestedSelect (val: any): void {
|
||||
dispatch('close', val)
|
||||
}
|
||||
|
||||
function click (val: DropdownIntlItem): void {
|
||||
onSelect?.(val)
|
||||
dispatch('close', val)
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="selectPopup" use:resizeObserver={() => dispatch('changeContent')}>
|
||||
<div class="menu-space" />
|
||||
<Scroller>
|
||||
{#if nestedFrom}
|
||||
<!-- svelte-ignore a11y-mouse-events-have-key-events -->
|
||||
<button
|
||||
class="menu-item"
|
||||
on:keydown={(event) => {
|
||||
keyDown(event, -1)
|
||||
}}
|
||||
on:mouseover={(event) => {
|
||||
event.currentTarget.focus()
|
||||
}}
|
||||
on:click={() => {
|
||||
if (nestedFrom !== undefined) {
|
||||
click(nestedFrom)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div class="overflow-label pr-1"><Label label={nestedFrom.label} /></div>
|
||||
</button>
|
||||
<div class="divider" />
|
||||
{/if}
|
||||
{#each items as item, i}
|
||||
{#if item[1].length > 0 && nestedFrom === undefined}
|
||||
<Submenu
|
||||
bind:element={elements[i]}
|
||||
on:keydown={(event) => {
|
||||
keyDown(event, i)
|
||||
}}
|
||||
on:mouseover={() => {
|
||||
elements[i]?.focus()
|
||||
}}
|
||||
label={item[0].label}
|
||||
props={{
|
||||
items: item[1].map((p) => {
|
||||
return [p, []]
|
||||
}),
|
||||
nestedFrom: item[0],
|
||||
onSelect: onNestedSelect
|
||||
}}
|
||||
options={{ component: NestedMenu }}
|
||||
/>
|
||||
{:else}
|
||||
<!-- svelte-ignore a11y-mouse-events-have-key-events -->
|
||||
<button
|
||||
class="menu-item"
|
||||
on:keydown={(event) => {
|
||||
keyDown(event, i)
|
||||
}}
|
||||
on:mouseover={(event) => {
|
||||
event.currentTarget.focus()
|
||||
}}
|
||||
on:click={() => {
|
||||
click(item[0])
|
||||
}}
|
||||
>
|
||||
<div class="overflow-label pr-1"><Label label={item[0].label} /></div>
|
||||
</button>
|
||||
{/if}
|
||||
{/each}
|
||||
</Scroller>
|
||||
<div class="menu-space" />
|
||||
</div>
|
||||
@@ -281,6 +281,8 @@ export { default as CodeInput } from './components/CodeInput.svelte'
|
||||
export { default as TimeLeft } from './components/TimeLeft.svelte'
|
||||
export { default as SectionEmpty } from './components/SectionEmpty.svelte'
|
||||
export { default as EmbeddedPDF } from './components/EmbeddedPDF.svelte'
|
||||
export { default as NestedMenu } from './components/NestedMenu.svelte'
|
||||
export { default as NestedDropdown } from './components/NestedDropdown.svelte'
|
||||
|
||||
export { default as Dock } from './components/Dock.svelte'
|
||||
|
||||
|
||||
@@ -26,19 +26,8 @@
|
||||
clip-rule="evenodd"
|
||||
d="M6 4.00012C3.79086 4.00012 2 5.79098 2 8.00012V24.0001C2 26.2093 3.79086 28.0001 6 28.0001H26C28.2091 28.0001 30 26.2093 30 24.0001V8.00012C30 5.79098 28.2091 4.00012 26 4.00012H6ZM15 6.00012H6C4.89543 6.00012 4 6.89555 4 8.00012V24.0001C4 25.1047 4.89543 26.0001 6 26.0001H15V6.00012ZM17 6.00012V26.0001H26C27.1046 26.0001 28 25.1047 28 24.0001V8.00012C28 6.89555 27.1046 6.00012 26 6.00012H17Z"
|
||||
/></symbol>
|
||||
<symbol id="masterTag" viewBox="0 0 27 27">
|
||||
<rect width="27" height="27" rx="13.5" fill="url(#paint0_linear_1280_1795)"/>
|
||||
<circle cx="13.4999" cy="13.5" r="10.2892" fill="#36373D"/>
|
||||
<path d="M8.55228 12.5117C8.32591 11.8852 8.21272 11.5719 8.26099 11.3712C8.31377 11.1516 8.46992 10.9805 8.66949 10.9234C8.85196 10.8712 9.13581 10.9973 9.70351 11.2496C10.2056 11.4728 10.4567 11.5844 10.6926 11.5782C10.9523 11.5714 11.2022 11.4672 11.4009 11.2829C11.5814 11.1155 11.7025 10.8488 11.9447 10.3154L12.4783 9.13972C12.9241 8.15768 13.147 7.66666 13.5 7.66666C13.853 7.66666 14.0759 8.15768 14.5216 9.13972L15.0553 10.3154C15.2975 10.8488 15.4186 11.1155 15.5991 11.2829C15.7978 11.4672 16.0476 11.5714 16.3074 11.5782C16.5433 11.5844 16.7944 11.4728 17.2965 11.2496C17.8642 10.9973 18.1481 10.8712 18.3305 10.9234C18.5301 10.9805 18.6862 11.1516 18.739 11.3712C18.7873 11.5719 18.6741 11.8852 18.4477 12.5116L17.4747 15.2046C17.0584 16.3566 16.8504 16.9326 16.4148 17.2579C15.9793 17.5833 15.4165 17.5833 14.2909 17.5833H12.7091C11.5835 17.5833 11.0207 17.5833 10.5852 17.2579C10.1496 16.9326 9.94153 16.3566 9.52529 15.2046L8.55228 12.5117Z" stroke="#FDFDFD" stroke-width="0.875"/>
|
||||
<path d="M13.5 14.6667H13.5052" stroke="#FDFDFD" stroke-width="1.16667" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M10.5833 19.3333H16.4166" stroke="#FDFDFD" stroke-width="0.875" stroke-linecap="round"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_1280_1795" x1="2.51228" y1="4.28851" x2="23.7537" y2="23.254" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="white"/>
|
||||
<stop offset="0.535" stop-color="#DCE9FE"/>
|
||||
<stop offset="1" stop-color="#848B98"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<symbol id="masterTag" viewBox="0 0 32 32">
|
||||
<path d="M27.4,19.9h-6.4l1.9-7.8h4.5c0.6,0,1-0.4,1-1s-0.4-1-1-1h-4l1.7-6.9c0.1-0.5-0.2-1.1-0.7-1.2c-0.5-0.1-1.1,0.2-1.2,0.7l-1.8,7.4h-7.7l1.7-6.9c0.1-0.5-0.2-1.1-0.7-1.2c-0.5-0.1-1.1,0.2-1.2,0.7l-1.8,7.4H4.6c-0.6,0-1,0.4-1,1s0.4,1,1,1h6.4l-1.9,7.8H4.6c-0.6,0-1,0.4-1,1s0.4,1,1,1h4l-1.7,6.9c-0.1,0.5,0.2,1.1,0.7,1.2c0.1,0,0.2,0,0.2,0c0.4,0,0.9-0.3,1-0.8l1.8-7.4h7.7l-1.7,6.9c-0.1,0.5,0.2,1.1,0.7,1.2c0.1,0,0.2,0,0.2,0c0.4,0,0.9-0.3,1-0.8l1.8-7.4h6.9c0.6,0,1-0.4,1-1S27.9,19.9,27.4,19.9z M11.2,19.9l1.9-7.8h7.7l-1.9,7.8H11.2z" />
|
||||
</symbol>
|
||||
<symbol id="masterTags" viewBox="0 0 16 16">
|
||||
<path d="M2 4.5C2.55228 4.5 3 4.05228 3 3.5C3 2.94772 2.55228 2.5 2 2.5C1.44772 2.5 1 2.94772 1 3.5C1 4.05228 1.44772 4.5 2 4.5Z" />
|
||||
|
||||
|
Before Width: | Height: | Size: 6.0 KiB After Width: | Height: | Size: 4.8 KiB |
@@ -5,19 +5,20 @@
|
||||
"Cards": "Karty",
|
||||
"Content": "Obsah",
|
||||
"CreateCard": "Vytvořit kartu",
|
||||
"CreateMasterTag": "Vytvořit hlavní štítek",
|
||||
"CreateMasterTag": "Vytvořit typ",
|
||||
"CreateTag": "Vytvořit štítek",
|
||||
"MasterTag": "Hlavní štítek",
|
||||
"MasterTags": "Hlavní štítky",
|
||||
"Parent": "Rodič",
|
||||
"MasterTag": "Typ",
|
||||
"MasterTags": "Typy",
|
||||
"Parent": "Nadřazený",
|
||||
"Tags": "Štítky",
|
||||
"Tag": "Štítek",
|
||||
"CardLibrary": "Knihovna karet",
|
||||
"MinimizeAll": "Minimalizovat vše",
|
||||
"ExpandAll": "Rozbalit vše",
|
||||
"DeleteTag": "Smazat štítek",
|
||||
"TagRelations": "Vztahy štítků",
|
||||
"DeleteTagConfirm": "Opravdu chcete smazat tento štítek? Všechny související vlastnosti budou smazány",
|
||||
"DeleteMasterTag": "Smazat hlavní štítek",
|
||||
"DeleteMasterTagConfirm": "Opravdu chcete smazat tento hlavní štítek? Všechny objekty související s tímto hlavním štítkem budou smazány"
|
||||
"DeleteMasterTag": "Smazat typ",
|
||||
"DeleteMasterTagConfirm": "Opravdu chcete smazat tento typ? Všechny objekty související s tímto typem budou smazány"
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@
|
||||
"CardLibrary": "Kartenbibliothek",
|
||||
"MinimizeAll": "Alle minimieren",
|
||||
"ExpandAll": "Alle erweitern",
|
||||
"TagRelations": "Tag-Beziehungen",
|
||||
"DeleteTag": "Tag löschen",
|
||||
"DeleteTagConfirm": "Möchten Sie diesen Tag wirklich löschen? Alle zugehörigen Eigenschaften werden gelöscht",
|
||||
"DeleteMasterTag": "Master-Tag löschen",
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
"Cards": "Cards",
|
||||
"Content": "Content",
|
||||
"CreateCard": "Create Card",
|
||||
"CreateMasterTag": "Create Master Tag",
|
||||
"CreateMasterTag": "Create Type",
|
||||
"CreateTag": "Create Tag",
|
||||
"MasterTag": "Master Tag",
|
||||
"MasterTags": "Master Tags",
|
||||
"MasterTag": "Type",
|
||||
"MasterTags": "Types",
|
||||
"Parent": "Parent",
|
||||
"Tags": "Tags",
|
||||
"Tag": "Tag",
|
||||
@@ -16,8 +16,9 @@
|
||||
"MinimizeAll": "Minimize All",
|
||||
"ExpandAll": "Expand All",
|
||||
"DeleteTag": "Delete Tag",
|
||||
"TagRelations": "Tag Relations",
|
||||
"DeleteTagConfirm": "Are you sure you want to delete this tag? All related properties will be deleted",
|
||||
"DeleteMasterTag": "Delete Master Tag",
|
||||
"DeleteMasterTagConfirm": "Are you sure you want to delete this master tag? All objects related to this master tag will be deleted"
|
||||
"DeleteMasterTag": "Delete Type",
|
||||
"DeleteMasterTagConfirm": "Are you sure you want to delete this type? All objects related to this type will be deleted"
|
||||
}
|
||||
}
|
||||
@@ -5,10 +5,10 @@
|
||||
"Cards": "Tarjetas",
|
||||
"Content": "Contenido",
|
||||
"CreateCard": "Crear Tarjeta",
|
||||
"CreateMasterTag": "Crear Etiqueta Maestra",
|
||||
"CreateMasterTag": "Crear Tipo",
|
||||
"CreateTag": "Crear Etiqueta",
|
||||
"MasterTag": "Etiqueta Maestra",
|
||||
"MasterTags": "Etiquetas Maestras",
|
||||
"MasterTag": "Tipo",
|
||||
"MasterTags": "Tipos",
|
||||
"Parent": "Padre",
|
||||
"Tags": "Etiquetas",
|
||||
"Tag": "Etiqueta",
|
||||
@@ -16,8 +16,9 @@
|
||||
"MinimizeAll": "Minimizar Todo",
|
||||
"ExpandAll": "Expandir Todo",
|
||||
"DeleteTag": "Eliminar Etiqueta",
|
||||
"DeleteTagConfirm": "¿Está seguro de que desea eliminar esta etiqueta? Todas las propiedades relacion",
|
||||
"DeleteMasterTag": "Eliminar Etiqueta Maestra",
|
||||
"DeleteMasterTagConfirm": "¿Está seguro de que desea eliminar esta etiqueta maestra? Todos los objetos relacionados con esta etiqueta maestra serán eliminados"
|
||||
"TagRelations": "Relaciones de Etiquetas",
|
||||
"DeleteTagConfirm": "¿Estás seguro de que quieres eliminar esta etiqueta? Todas las propiedades relacionadas serán eliminadas",
|
||||
"DeleteMasterTag": "Eliminar Tipo",
|
||||
"DeleteMasterTagConfirm": "¿Estás seguro de que quieres eliminar este tipo? Todos los objetos relacionados con este tipo serán eliminados"
|
||||
}
|
||||
}
|
||||
@@ -5,19 +5,20 @@
|
||||
"Cards": "Cartes",
|
||||
"Content": "Contenu",
|
||||
"CreateCard": "Créer une carte",
|
||||
"CreateMasterTag": "Créer un tag maître",
|
||||
"CreateTag": "Créer un tag",
|
||||
"MasterTag": "Tag maître",
|
||||
"MasterTags": "Tags maîtres",
|
||||
"CreateMasterTag": "Créer un type",
|
||||
"CreateTag": "Créer une étiquette",
|
||||
"MasterTag": "Type",
|
||||
"MasterTags": "Types",
|
||||
"Parent": "Parent",
|
||||
"Tags": "Tags",
|
||||
"Tag": "Tag",
|
||||
"Tags": "Étiquettes",
|
||||
"Tag": "Étiquette",
|
||||
"CardLibrary": "Bibliothèque de cartes",
|
||||
"MinimizeAll": "Minimiser tout",
|
||||
"ExpandAll": "Développer tout",
|
||||
"DeleteTag": "Supprimer le tag",
|
||||
"DeleteTagConfirm": "Voulez-vous vraiment supprimer ce tag? Toutes les propriétés associées seront supprimées",
|
||||
"DeleteMasterTag": "Supprimer le tag maître",
|
||||
"DeleteMasterTagConfirm": "Voulez-vous vraiment supprimer ce tag maître? Tous les objets liés à ce tag maître seront supprimés"
|
||||
"MinimizeAll": "Tout réduire",
|
||||
"ExpandAll": "Tout développer",
|
||||
"DeleteTag": "Supprimer l'étiquette",
|
||||
"TagRelations": "Relations d'étiquettes",
|
||||
"DeleteTagConfirm": "Êtes-vous sûr de vouloir supprimer cette étiquette ? Toutes les propriétés associées seront supprimées",
|
||||
"DeleteMasterTag": "Supprimer le type",
|
||||
"DeleteMasterTagConfirm": "Êtes-vous sûr de vouloir supprimer ce type ? Tous les objets liés à ce type seront supprimés"
|
||||
}
|
||||
}
|
||||
@@ -5,10 +5,10 @@
|
||||
"Cards": "Carte",
|
||||
"Content": "Contenuto",
|
||||
"CreateCard": "Crea Carta",
|
||||
"CreateMasterTag": "Crea Tag Principale",
|
||||
"CreateMasterTag": "Crea Tipo",
|
||||
"CreateTag": "Crea Tag",
|
||||
"MasterTag": "Tag Principale",
|
||||
"MasterTags": "Tag Principali",
|
||||
"MasterTag": "Tipo",
|
||||
"MasterTags": "Tipi",
|
||||
"Parent": "Genitore",
|
||||
"Tags": "Tag",
|
||||
"Tag": "Tag",
|
||||
@@ -16,8 +16,9 @@
|
||||
"MinimizeAll": "Minimizza Tutto",
|
||||
"ExpandAll": "Espandi Tutto",
|
||||
"DeleteTag": "Elimina Tag",
|
||||
"TagRelations": "Relazioni Tag",
|
||||
"DeleteTagConfirm": "Sei sicuro di voler eliminare questo tag? Tutte le proprietà correlate verranno eliminate",
|
||||
"DeleteMasterTag": "Elimina Tag Principale",
|
||||
"DeleteMasterTagConfirm": "Sei sicuro di voler eliminare questo tag principale? Tutti gli oggetti correlati a questo tag principale verranno eliminati"
|
||||
"DeleteMasterTag": "Elimina Tipo",
|
||||
"DeleteMasterTagConfirm": "Sei sicuro di voler eliminare questo tipo? Tutti gli oggetti correlati a questo tipo verranno eliminati"
|
||||
}
|
||||
}
|
||||
@@ -5,19 +5,20 @@
|
||||
"Cards": "Cartões",
|
||||
"Content": "Conteúdo",
|
||||
"CreateCard": "Criar Cartão",
|
||||
"CreateMasterTag": "Criar Tag Mestre",
|
||||
"CreateMasterTag": "Criar Tipo",
|
||||
"CreateTag": "Criar Tag",
|
||||
"MasterTag": "Tag Mestre",
|
||||
"MasterTags": "Tags Mestre",
|
||||
"Parent": "Pai",
|
||||
"MasterTag": "Tipo",
|
||||
"MasterTags": "Tipos",
|
||||
"Parent": "Principal",
|
||||
"Tags": "Tags",
|
||||
"Tag": "Tag",
|
||||
"CardLibrary": "Biblioteca de Cartões",
|
||||
"MinimizeAll": "Minimizar Tudo",
|
||||
"ExpandAll": "Expandir Tudo",
|
||||
"DeleteTag": "Excluir Tag",
|
||||
"DeleteTagConfirm": "Tem certeza de que deseja excluir esta tag? Todas as propriedades relacionadas serão excluídas",
|
||||
"DeleteMasterTag": "Excluir Tag Mestre",
|
||||
"DeleteMasterTagConfirm": "Tem certeza de que deseja excluir esta tag mestre? Todos os objetos relacionados a esta tag mestre serão excluídos"
|
||||
"TagRelations": "Relações de Tag",
|
||||
"DeleteTagConfirm": "Tem certeza que deseja excluir esta tag? Todas as propriedades relacionadas serão excluídas",
|
||||
"DeleteMasterTag": "Excluir Tipo",
|
||||
"DeleteMasterTagConfirm": "Tem certeza que deseja excluir este tipo? Todos os objetos relacionados a este tipo serão excluídos"
|
||||
}
|
||||
}
|
||||
@@ -5,10 +5,10 @@
|
||||
"Cards": "Карты",
|
||||
"Content": "Содержание",
|
||||
"CreateCard": "Создать карту",
|
||||
"CreateMasterTag": "Создать мастер-тег",
|
||||
"CreateMasterTag": "Создать тип",
|
||||
"CreateTag": "Создать тег",
|
||||
"MasterTag": "Мастер-тег",
|
||||
"MasterTags": "Мастер-теги",
|
||||
"MasterTag": "Тип",
|
||||
"MasterTags": "Типы",
|
||||
"Parent": "Родитель",
|
||||
"Tags": "Теги",
|
||||
"Tag": "Тег",
|
||||
@@ -16,8 +16,9 @@
|
||||
"MinimizeAll": "Свернуть все",
|
||||
"ExpandAll": "Развернуть все",
|
||||
"DeleteTag": "Удалить тег",
|
||||
"TagRelations": "Связи тегов",
|
||||
"DeleteTagConfirm": "Вы действительно хотите удалить этот тег? Все связанные свойства будут удалены",
|
||||
"DeleteMasterTag": "Удалить мастер-тег",
|
||||
"DeleteMasterTagConfirm": "Вы действительно хотите удалить этот мастер-тег? Все объекты, связанные с этим мастер-тегом, будут удалены"
|
||||
"DeleteMasterTag": "Удалить тип",
|
||||
"DeleteMasterTagConfirm": "Вы действительно хотите удалить этот тип? Все объекты, связанные с этим типом, будут удалены"
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,14 @@
|
||||
{
|
||||
"string": {
|
||||
"Card": "卡片",
|
||||
"CardApplication": "卡片应用",
|
||||
"CardApplication": "卡片",
|
||||
"Cards": "卡片",
|
||||
"Content": "内容",
|
||||
"CreateCard": "创建卡片",
|
||||
"CreateMasterTag": "创建主标签",
|
||||
"CreateMasterTag": "创建类型",
|
||||
"CreateTag": "创建标签",
|
||||
"MasterTag": "主标签",
|
||||
"MasterTags": "主标签",
|
||||
"MasterTag": "类型",
|
||||
"MasterTags": "类型",
|
||||
"Parent": "父级",
|
||||
"Tags": "标签",
|
||||
"Tag": "标签",
|
||||
@@ -16,8 +16,9 @@
|
||||
"MinimizeAll": "全部最小化",
|
||||
"ExpandAll": "全部展开",
|
||||
"DeleteTag": "删除标签",
|
||||
"DeleteTagConfirm": "您确定要删除此标签吗?所有相关属性将被删除",
|
||||
"DeleteMasterTag": "删除主标签",
|
||||
"DeleteMasterTagConfirm": "您确定要删除此主标签吗?所有与此主标签相关的对象将被删除"
|
||||
"TagRelations": "标签关系",
|
||||
"DeleteTagConfirm": "您确定要删除此标签吗?所有相关属性都将被删除",
|
||||
"DeleteMasterTag": "删除类型",
|
||||
"DeleteMasterTagConfirm": "您确定要删除此类型吗?所有与此类型相关的对象都将被删除"
|
||||
}
|
||||
}
|
||||
@@ -67,3 +67,24 @@
|
||||
</span>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.issuePresenterRoot {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
|
||||
&:not(.list) {
|
||||
color: var(--theme-content-color);
|
||||
}
|
||||
|
||||
&.list {
|
||||
color: var(--theme-halfcontent-color);
|
||||
}
|
||||
|
||||
.icon {
|
||||
margin-right: 0.5rem;
|
||||
color: var(--theme-dark-color);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
<script lang="ts">
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
import attachment, { Attachment } from '@hcengineering/attachment'
|
||||
import { Card, CardEvents } from '@hcengineering/card'
|
||||
import { Doc, Mixin, Ref, WithLookup, generateId, type Blob } from '@hcengineering/core'
|
||||
import notification from '@hcengineering/notification'
|
||||
import { Panel } from '@hcengineering/panel'
|
||||
@@ -24,10 +25,9 @@
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { Heading } from '@hcengineering/text-editor'
|
||||
import { TableOfContents } from '@hcengineering/text-editor-resources'
|
||||
import { Button, EditBox, FocusHandler, IconMixin, IconMoreH, createFocusManager, navigate } from '@hcengineering/ui'
|
||||
import { Button, EditBox, FocusHandler, IconMoreH, createFocusManager, navigate } from '@hcengineering/ui'
|
||||
import view from '@hcengineering/view'
|
||||
import {
|
||||
DocAttributeBar,
|
||||
ParentsNavigator,
|
||||
RelationsEditor,
|
||||
getDocMixins,
|
||||
@@ -35,12 +35,11 @@
|
||||
showMenu
|
||||
} from '@hcengineering/view-resources'
|
||||
import { createEventDispatcher, onDestroy, onMount } from 'svelte'
|
||||
import { Card, CardEvents } from '@hcengineering/card'
|
||||
import card from '../plugin'
|
||||
import CardAttributeEditor from './CardAttributeEditor.svelte'
|
||||
import CardPresenter from './CardPresenter.svelte'
|
||||
import ContentEditor from './ContentEditor.svelte'
|
||||
import TagsEditor from './TagsEditor.svelte'
|
||||
import CardAttributeEditor from './CardAttributeEditor.svelte'
|
||||
|
||||
export let _id: Ref<Card>
|
||||
export let readonly: boolean = false
|
||||
|
||||
@@ -74,10 +74,9 @@
|
||||
ev.stopPropagation()
|
||||
const loc = getCurrentResolvedLocation()
|
||||
loc.path[2] = settingId
|
||||
loc.path[3] = 'setting'
|
||||
loc.path[4] = 'masterTags'
|
||||
loc.path[3] = 'masterTags'
|
||||
loc.path[4] = value._class
|
||||
loc.path.length = 5
|
||||
loc.query = { _class: value._class }
|
||||
loc.fragment = undefined
|
||||
navigate(loc)
|
||||
}}
|
||||
|
||||
@@ -14,8 +14,9 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Card, MasterTag } from '@hcengineering/card'
|
||||
import { fillDefaults } from '@hcengineering/core'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { DropdownLabelsIntl, Icon, Label } from '@hcengineering/ui'
|
||||
import { DropdownLabelsIntl, Label } from '@hcengineering/ui'
|
||||
import card from '../plugin'
|
||||
|
||||
export let value: Card
|
||||
@@ -38,17 +39,18 @@
|
||||
})
|
||||
|
||||
async function select (event: CustomEvent): Promise<void> {
|
||||
const update = { _class: event.detail } as any
|
||||
const _class = event.detail
|
||||
const update = { _class } as any
|
||||
selected = event.detail
|
||||
await client.update(value, update)
|
||||
const updated = fillDefaults(hierarchy, hierarchy.clone(value), _class)
|
||||
await client.diffUpdate(value, updated)
|
||||
}
|
||||
|
||||
$: label = hierarchy.getClass(selected).label
|
||||
</script>
|
||||
|
||||
<div class="flex flex-gap-2 items-center item caption-color">
|
||||
<Icon icon={card.icon.MasterTag} size="large" />
|
||||
<Label label={card.string.MasterTag} />
|
||||
{#if value._class === card.class.Card}
|
||||
<DropdownLabelsIntl
|
||||
{items}
|
||||
|
||||
@@ -14,38 +14,27 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
import { CardEvents, MasterTag } from '@hcengineering/card'
|
||||
import core, { Class, Doc, MarkupBlobRef, Ref, SortingOrder } from '@hcengineering/core'
|
||||
import { Card, CardEvents, cardId, MasterTag } from '@hcengineering/card'
|
||||
import core, { Class, Data, Doc, fillDefaults, MarkupBlobRef, Ref, SortingOrder } from '@hcengineering/core'
|
||||
import { translate } from '@hcengineering/platform'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { makeRank } from '@hcengineering/rank'
|
||||
import {
|
||||
Button,
|
||||
ButtonWithDropdown,
|
||||
IconAdd,
|
||||
IconDropdown,
|
||||
NavItem,
|
||||
Scroller,
|
||||
SelectPopupValueType,
|
||||
deviceOptionsStore as deviceInfo,
|
||||
getCurrentLocation,
|
||||
IconAdd,
|
||||
navigate,
|
||||
showPopup
|
||||
Scroller
|
||||
} from '@hcengineering/ui'
|
||||
import { NavLink, showMenu } from '@hcengineering/view-resources'
|
||||
import { NavFooter, NavHeader } from '@hcengineering/workbench-resources'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { NavFooter, NavHeader, SavedView } from '@hcengineering/workbench-resources'
|
||||
import card from '../plugin'
|
||||
import CreateTag from './CreateTag.svelte'
|
||||
import TagHierarchy from './TagHierarchy.svelte'
|
||||
|
||||
export let _class: Ref<Class<Doc>>
|
||||
|
||||
const client = getClient()
|
||||
|
||||
const clazz = client.getHierarchy().getClass(card.class.Card)
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const hierarchy = client.getHierarchy()
|
||||
|
||||
let classes: MasterTag[] = []
|
||||
let allClasses: MasterTag[] = []
|
||||
@@ -64,11 +53,15 @@
|
||||
const lastOne = await client.findOne(card.class.Card, {}, { sort: { rank: SortingOrder.Descending } })
|
||||
const title = await translate(card.string.Card, {})
|
||||
|
||||
const _id = await client.createDoc(card.class.Card, core.space.Workspace, {
|
||||
const data: Data<Card> = {
|
||||
title,
|
||||
rank: makeRank(lastOne?.rank, undefined),
|
||||
content: '' as MarkupBlobRef
|
||||
})
|
||||
}
|
||||
|
||||
const filledData = fillDefaults(hierarchy, data, _class)
|
||||
|
||||
const _id = await client.createDoc(_class, core.space.Workspace, filledData)
|
||||
|
||||
Analytics.handleEvent(CardEvents.CardCreated)
|
||||
|
||||
@@ -78,25 +71,7 @@
|
||||
navigate(loc)
|
||||
}
|
||||
|
||||
function createMasteTag (): void {
|
||||
showPopup(CreateTag, {
|
||||
parent: undefined,
|
||||
_class: card.class.MasterTag
|
||||
})
|
||||
}
|
||||
|
||||
async function dropdownItemSelected (res?: SelectPopupValueType['id']): Promise<void> {
|
||||
if (res === card.string.CreateCard) {
|
||||
await createCard()
|
||||
} else if (res === card.string.CreateMasterTag) {
|
||||
createMasteTag()
|
||||
}
|
||||
}
|
||||
|
||||
const dropdownItems = [
|
||||
{ id: card.string.CreateCard, label: card.string.CreateCard },
|
||||
{ id: card.string.CreateMasterTag, label: card.string.CreateMasterTag }
|
||||
]
|
||||
let menuSelection: boolean = false
|
||||
</script>
|
||||
|
||||
<div
|
||||
@@ -107,56 +82,28 @@
|
||||
<NavHeader label={card.string.Cards} />
|
||||
|
||||
<div class="antiNav-subheader">
|
||||
{#if allClasses.length > 0}
|
||||
<ButtonWithDropdown
|
||||
icon={IconAdd}
|
||||
justify={'left'}
|
||||
kind={'primary'}
|
||||
label={card.string.CreateCard}
|
||||
on:click={createCard}
|
||||
mainButtonId={'new-card'}
|
||||
dropdownIcon={IconDropdown}
|
||||
{dropdownItems}
|
||||
on:dropdown-selected={(ev) => {
|
||||
void dropdownItemSelected(ev.detail)
|
||||
}}
|
||||
/>
|
||||
{:else}
|
||||
<Button
|
||||
icon={IconAdd}
|
||||
label={card.string.CreateMasterTag}
|
||||
justify={'left'}
|
||||
width={'100%'}
|
||||
kind={'primary'}
|
||||
gap={'large'}
|
||||
on:click={createMasteTag}
|
||||
/>
|
||||
{/if}
|
||||
<Button
|
||||
icon={IconAdd}
|
||||
label={card.string.CreateCard}
|
||||
disabled={allClasses.length === 0 || _class === undefined}
|
||||
justify={'left'}
|
||||
width={'100%'}
|
||||
kind={'primary'}
|
||||
gap={'large'}
|
||||
on:click={createCard}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<NavLink space={card.class.Card}>
|
||||
<NavItem
|
||||
_id={card.class.Card}
|
||||
label={card.string.CardLibrary}
|
||||
icon={card.icon.Card}
|
||||
isFold
|
||||
empty
|
||||
selected={card.class.Card === _class}
|
||||
on:click={() => {
|
||||
dispatch('select', card.class.Card)
|
||||
}}
|
||||
on:contextmenu={(evt) => {
|
||||
showMenu(evt, { object: clazz })
|
||||
}}
|
||||
/>
|
||||
</NavLink>
|
||||
<SavedView alias={cardId} on:select={(res) => (menuSelection = res.detail)} />
|
||||
|
||||
<div class="antiNav-divider line" />
|
||||
|
||||
<Scroller shrink>
|
||||
<TagHierarchy bind:_class {classes} {allClasses} on:select />
|
||||
<TagHierarchy bind:_class deselect={menuSelection} {classes} {allClasses} />
|
||||
</Scroller>
|
||||
|
||||
<div class="mt-2" />
|
||||
|
||||
<NavFooter split />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -81,10 +81,9 @@
|
||||
ev.stopPropagation()
|
||||
const loc = getCurrentResolvedLocation()
|
||||
loc.path[2] = settingId
|
||||
loc.path[3] = 'setting'
|
||||
loc.path[4] = 'masterTags'
|
||||
loc.path[3] = 'masterTags'
|
||||
loc.path[4] = tag._id
|
||||
loc.path.length = 5
|
||||
loc.query = { _class: tag._id }
|
||||
loc.fragment = undefined
|
||||
navigate(loc)
|
||||
}}
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
export let classes: MasterTag[] = []
|
||||
export let allClasses: MasterTag[] = []
|
||||
export let _class: Ref<Class<Doc>> | undefined
|
||||
export let deselect: boolean = false
|
||||
export let level: number = 0
|
||||
|
||||
const client = getClient()
|
||||
@@ -58,10 +59,11 @@
|
||||
<NavItem
|
||||
_id={clazz._id}
|
||||
label={clazz.label}
|
||||
icon={clazz.icon}
|
||||
isFold
|
||||
empty
|
||||
{level}
|
||||
selected={clazz._id === _class}
|
||||
selected={!deselect && clazz._id === _class}
|
||||
on:click={() => {
|
||||
dispatch('select', clazz._id)
|
||||
}}
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
ScrollerBar
|
||||
} from '@hcengineering/ui'
|
||||
import MasterTagSelector from './MasterTagSelector.svelte'
|
||||
import { fillDefaults } from '@hcengineering/core'
|
||||
|
||||
export let doc: Card
|
||||
|
||||
@@ -65,6 +66,8 @@
|
||||
async (res) => {
|
||||
if (res !== undefined) {
|
||||
await client.createMixin(doc._id, doc._class, doc.space, res, {})
|
||||
const updated = fillDefaults(hierarchy, hierarchy.clone(doc), res)
|
||||
await client.diffUpdate(doc, updated)
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -75,20 +78,22 @@
|
||||
|
||||
<div class="container py-4 gap-2">
|
||||
<MasterTagSelector value={doc} />
|
||||
<div class="divider" />
|
||||
<ScrollerBar gap={'none'} bind:scroller={divScroll}>
|
||||
<div class="tags gap-2">
|
||||
{#each activeTags as mixin}
|
||||
<div class="tag no-word-wrap">
|
||||
<Label label={mixin.label} />
|
||||
<ButtonIcon icon={IconClose} size="extra-small" kind="tertiary" on:click={() => removeTag(mixin._id)} />
|
||||
</div>
|
||||
{/each}
|
||||
{#if dropdownItems.length > 0}
|
||||
<CircleButton icon={IconAdd} size="small" ghost on:click={add} />
|
||||
{/if}
|
||||
</div>
|
||||
</ScrollerBar>
|
||||
{#if activeTags.length > 0 || dropdownItems.length > 0}
|
||||
<div class="divider" />
|
||||
<ScrollerBar gap={'none'} bind:scroller={divScroll}>
|
||||
<div class="tags gap-2">
|
||||
{#each activeTags as mixin}
|
||||
<div class="tag no-word-wrap">
|
||||
<Label label={mixin.label} />
|
||||
<ButtonIcon icon={IconClose} size="extra-small" kind="tertiary" on:click={() => removeTag(mixin._id)} />
|
||||
</div>
|
||||
{/each}
|
||||
{#if dropdownItems.length > 0}
|
||||
<CircleButton icon={IconAdd} size="small" ghost on:click={add} />
|
||||
{/if}
|
||||
</div>
|
||||
</ScrollerBar>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
<!--
|
||||
// Copyright © 2025 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 { MasterTag } from '@hcengineering/card'
|
||||
import { Class, Doc, Ref } from '@hcengineering/core'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { ClassHierarchy } from '@hcengineering/setting-resources'
|
||||
import { ButtonIcon, getCurrentLocation, Icon, IconAdd, Label, navigate, showPopup } from '@hcengineering/ui'
|
||||
import card from '../../plugin'
|
||||
import CreateTag from '../CreateTag.svelte'
|
||||
import TagsHierarchy from './TagsHierarchy.svelte'
|
||||
|
||||
export let masterTag: MasterTag
|
||||
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
let descendants: Ref<Class<Doc>>[] = []
|
||||
$: getDescendants(masterTag._id)
|
||||
|
||||
const query = createQuery()
|
||||
|
||||
query.query(card.class.MasterTag, {}, () => {
|
||||
getDescendants(masterTag._id)
|
||||
})
|
||||
|
||||
function getDescendants (id: Ref<Class<Doc>>): void {
|
||||
const desc = hierarchy.getDescendants(id)
|
||||
const filtered: Ref<Class<Doc>>[] = []
|
||||
for (const _id of desc) {
|
||||
const _class = hierarchy.getClass(_id)
|
||||
if (_class.extends === id && _class._class === card.class.MasterTag) {
|
||||
filtered.push(_id)
|
||||
continue
|
||||
}
|
||||
}
|
||||
descendants = filtered
|
||||
}
|
||||
|
||||
function open (_class: Ref<Class<Doc>>): void {
|
||||
const loc = getCurrentLocation()
|
||||
loc.path[4] = _class
|
||||
navigate(loc)
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="hulyTableAttr-header font-medium-12">
|
||||
<Icon icon={card.icon.MasterTag} size="small" />
|
||||
<span><Label label={card.string.MasterTags} /></span>
|
||||
<ButtonIcon
|
||||
kind="primary"
|
||||
icon={IconAdd}
|
||||
size="small"
|
||||
dataId={'btnAdd'}
|
||||
on:click={() => {
|
||||
showPopup(CreateTag, {
|
||||
parent: masterTag,
|
||||
_class: card.class.MasterTag
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{#if descendants.length}
|
||||
<div class="hulyTableAttr-content task">
|
||||
<TagsHierarchy
|
||||
classes={descendants}
|
||||
_class={undefined}
|
||||
on:select={(e) => {
|
||||
open(e.detail)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -0,0 +1,84 @@
|
||||
<!--
|
||||
// Copyright © 2025 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 { MasterTag } from '@hcengineering/card'
|
||||
import { getEmbeddedLabel, translateCB } from '@hcengineering/platform'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { ButtonIcon, IconDelete, ModernEditbox, showPopup, themeStore } from '@hcengineering/ui'
|
||||
import { IconPicker } from '@hcengineering/view-resources'
|
||||
import card from '../../plugin'
|
||||
import { deleteMasterTag } from '../../utils'
|
||||
|
||||
export let masterTag: MasterTag
|
||||
|
||||
let name: string = ''
|
||||
|
||||
$: translateCB(masterTag.label, {}, $themeStore.language, (p) => {
|
||||
name = p
|
||||
})
|
||||
|
||||
const client = getClient()
|
||||
|
||||
async function attributeUpdated<T extends keyof MasterTag> (field: T, value: MasterTag[T]): Promise<void> {
|
||||
if (masterTag === undefined || masterTag[field] === value) {
|
||||
return
|
||||
}
|
||||
|
||||
await client.update(masterTag, { [field]: value })
|
||||
}
|
||||
|
||||
async function handleDelete (): Promise<void> {
|
||||
await deleteMasterTag(masterTag)
|
||||
}
|
||||
|
||||
function setIcon (): void {
|
||||
showPopup(IconPicker, { icon: masterTag.icon, showEmoji: false, showColor: false }, 'top', async (res) => {
|
||||
if (res !== undefined) {
|
||||
await attributeUpdated('icon', res.icon)
|
||||
masterTag.icon = res.icon
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="hulyComponent-content__column-group">
|
||||
<div class="hulyComponent-content__header items-center">
|
||||
<div class="flex items-center">
|
||||
<ButtonIcon
|
||||
icon={masterTag.icon ?? card.icon.MasterTag}
|
||||
size={'large'}
|
||||
iconSize={'large'}
|
||||
kind={'tertiary'}
|
||||
on:click={setIcon}
|
||||
/>
|
||||
<ModernEditbox
|
||||
kind="ghost"
|
||||
size="large"
|
||||
label={masterTag.label}
|
||||
value={name}
|
||||
on:blur={(evt) => {
|
||||
if (evt.detail === undefined || evt.detail.trim().length === 0) {
|
||||
return
|
||||
}
|
||||
if (name !== evt.detail) {
|
||||
const emb = getEmbeddedLabel(evt.detail)
|
||||
void attributeUpdated('label', emb)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<ButtonIcon icon={IconDelete} size="small" kind="secondary" on:click={handleDelete} />
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,165 @@
|
||||
<!--
|
||||
// Copyright © 2025 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 { MasterTag } from '@hcengineering/card'
|
||||
import { Ref } from '@hcengineering/core'
|
||||
import { createQuery } from '@hcengineering/presentation'
|
||||
import { clearSettingsStore } from '@hcengineering/setting-resources'
|
||||
import {
|
||||
Icon,
|
||||
IconOpenedArrow,
|
||||
Label,
|
||||
Location,
|
||||
getCurrentResolvedLocation,
|
||||
navigate,
|
||||
resolvedLocationStore
|
||||
} from '@hcengineering/ui'
|
||||
import { onDestroy } from 'svelte'
|
||||
import card from '../../plugin'
|
||||
|
||||
export let categoryName: string
|
||||
|
||||
let selectedTagId: Ref<MasterTag> | undefined
|
||||
|
||||
onDestroy(resolvedLocationStore.subscribe(handleLocationChanged))
|
||||
|
||||
function handleLocationChanged (loc: Location): void {
|
||||
selectedTagId = loc.path[4] as Ref<MasterTag>
|
||||
}
|
||||
|
||||
let tags: MasterTag[] = []
|
||||
const tagsQuery = createQuery()
|
||||
$: tagsQuery.query(
|
||||
card.class.MasterTag,
|
||||
{
|
||||
_class: card.class.MasterTag
|
||||
},
|
||||
(result) => {
|
||||
tags = result
|
||||
}
|
||||
)
|
||||
|
||||
function selectProjectType (id: string): void {
|
||||
clearSettingsStore()
|
||||
const loc = getCurrentResolvedLocation()
|
||||
loc.path[3] = categoryName
|
||||
loc.path[4] = id
|
||||
loc.path.length = 5
|
||||
navigate(loc)
|
||||
}
|
||||
</script>
|
||||
|
||||
{#each tags as tag}
|
||||
<button
|
||||
class="hulyTaskNavLink-container font-regular-14"
|
||||
class:selected={tag._id === selectedTagId}
|
||||
on:click={() => {
|
||||
selectProjectType(tag._id)
|
||||
}}
|
||||
>
|
||||
<div class="hulyTaskNavLink-avatar">
|
||||
{#if tag?.icon}
|
||||
<div class="hulyTaskNavLink-icon">
|
||||
<Icon icon={tag?.icon} size="small" fill="currentColor" />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="hulyTaskNavLink-content">
|
||||
<span class="hulyTaskNavLink-content__title"><Label label={tag.label} /></span>
|
||||
</div>
|
||||
{#if tag._id === selectedTagId}
|
||||
<div class="hulyTaskNavLink-icon right">
|
||||
<IconOpenedArrow size={'small'} />
|
||||
</div>
|
||||
{/if}
|
||||
</button>
|
||||
{/each}
|
||||
|
||||
<style lang="scss">
|
||||
.hulyTaskNavLink-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
gap: 0.75rem;
|
||||
padding: 0 0.75rem 0 0.5rem;
|
||||
height: 3.5rem;
|
||||
min-width: 0;
|
||||
border: none;
|
||||
border-radius: 0.375rem;
|
||||
outline: none;
|
||||
|
||||
&.selected {
|
||||
cursor: auto;
|
||||
}
|
||||
.hulyTaskNavLink-avatar {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
min-width: 0;
|
||||
background-color: var(--global-ui-BackgroundColor);
|
||||
border-radius: 0.375rem;
|
||||
}
|
||||
.hulyTaskNavLink-icon {
|
||||
flex-shrink: 0;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
color: var(--global-secondary-TextColor);
|
||||
|
||||
&.right {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
.hulyTaskNavLink-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.125rem;
|
||||
flex-grow: 1;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
|
||||
&__title {
|
||||
white-space: nowrap;
|
||||
word-break: break-all;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
text-align: left;
|
||||
min-width: 0;
|
||||
color: var(--global-primary-TextColor);
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--global-ui-hover-highlight-BackgroundColor);
|
||||
}
|
||||
&.selected {
|
||||
background-color: var(--global-ui-highlight-BackgroundColor);
|
||||
|
||||
.hulyTaskNavLink-icon {
|
||||
color: var(--global-accent-TextColor);
|
||||
|
||||
&.right {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
.hulyTaskNavLink-content .hulyTaskNavLink-content__title {
|
||||
font-weight: 700;
|
||||
color: var(--global-accent-TextColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,93 @@
|
||||
<!--
|
||||
// Copyright © 2025 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 { MasterTag } from '@hcengineering/card'
|
||||
import { Ref } from '@hcengineering/core'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import {
|
||||
BreadcrumbItem,
|
||||
Breadcrumbs,
|
||||
Header,
|
||||
Location,
|
||||
deviceOptionsStore as deviceInfo,
|
||||
getCurrentLocation,
|
||||
navigate,
|
||||
resolvedLocationStore
|
||||
} from '@hcengineering/ui'
|
||||
import { onDestroy, onMount } from 'svelte'
|
||||
import card from '../../plugin'
|
||||
import MasterTagEditor from './MasterTagEditor.svelte'
|
||||
|
||||
let masterTag: MasterTag | undefined
|
||||
let selectedTagId: Ref<MasterTag> | undefined
|
||||
onDestroy(resolvedLocationStore.subscribe(handleLocationChanged))
|
||||
|
||||
function handleLocationChanged ({ path }: Location): void {
|
||||
if (path[3] !== 'masterTags' || path[4] === undefined) {
|
||||
selectedTagId = undefined
|
||||
} else {
|
||||
selectedTagId = path[4] as Ref<MasterTag>
|
||||
}
|
||||
}
|
||||
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
|
||||
$: masterTag = getMasterTag(selectedTagId)
|
||||
|
||||
function getMasterTag (_id: Ref<MasterTag> | undefined): MasterTag | undefined {
|
||||
if (_id === undefined) return undefined
|
||||
return client.getModel().findObject(_id)
|
||||
}
|
||||
|
||||
function getBreadcrumbs (tag: Ref<MasterTag> | undefined): BreadcrumbItem[] {
|
||||
if (tag === undefined) return []
|
||||
const toAncestors = hierarchy.getAncestors(card.class.Card)
|
||||
const ancestors = hierarchy.getAncestors(tag)
|
||||
const filtered = ancestors.filter((it) => !toAncestors.includes(it))
|
||||
return filtered.reverse().map((it) => ({
|
||||
id: it,
|
||||
label: hierarchy.getClass(it).label
|
||||
}))
|
||||
}
|
||||
|
||||
$: items = getBreadcrumbs(masterTag?._id)
|
||||
|
||||
onMount(() => {
|
||||
setTimeout(() => {
|
||||
if (masterTag === undefined) $deviceInfo.navigator.visible = true
|
||||
}, 500)
|
||||
})
|
||||
|
||||
function handleSelect (e: CustomEvent<any>): void {
|
||||
const id = items[e.detail]?.id
|
||||
if (id !== undefined) {
|
||||
const loc = getCurrentLocation()
|
||||
loc.path[4] = id
|
||||
navigate(loc)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="hulyComponent">
|
||||
{#if masterTag !== undefined}
|
||||
<Header adaptive={'disabled'}>
|
||||
<Breadcrumbs {items} selected={items.length - 1} size={'large'} on:select={handleSelect} />
|
||||
</Header>
|
||||
{#key masterTag._id}
|
||||
<MasterTagEditor {masterTag} on:change />
|
||||
{/key}
|
||||
{/if}
|
||||
</div>
|
||||
@@ -0,0 +1,28 @@
|
||||
<!--
|
||||
// Copyright © 2025 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 { ButtonIcon, IconAdd, showPopup } from '@hcengineering/ui'
|
||||
import CreateTag from '../CreateTag.svelte'
|
||||
import card from '../../plugin'
|
||||
|
||||
function handleAdd (): void {
|
||||
showPopup(CreateTag, {
|
||||
parent: undefined,
|
||||
_class: card.class.MasterTag
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<ButtonIcon id={'new-master-tag'} icon={IconAdd} kind={'tertiary'} size={'extra-small'} on:click={handleAdd} />
|
||||
@@ -0,0 +1,83 @@
|
||||
<!--
|
||||
// Copyright © 2025 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 { MasterTag } from '@hcengineering/card'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import {
|
||||
ButtonIcon,
|
||||
Component,
|
||||
IconDescription,
|
||||
NavItem,
|
||||
Scroller,
|
||||
Separator,
|
||||
defineSeparators,
|
||||
secondNavSeparators
|
||||
} from '@hcengineering/ui'
|
||||
import card from '../../plugin'
|
||||
|
||||
export let masterTag: MasterTag
|
||||
export let visibleSecondNav: boolean = true
|
||||
export let readonly: boolean = true
|
||||
|
||||
const client = getClient()
|
||||
|
||||
$: isMasterTag = masterTag._class === card.class.MasterTag
|
||||
$: sections = client
|
||||
.getModel()
|
||||
.findAllSync(card.class.MasterTagEditorSection, !isMasterTag ? { masterOnly: { $ne: true } } : {})
|
||||
|
||||
const sectionRefs: Record<string, HTMLElement | undefined> = {}
|
||||
|
||||
defineSeparators('spaceTypeEditor', secondNavSeparators)
|
||||
</script>
|
||||
|
||||
<div class="hulyComponent-content__container columns">
|
||||
{#if visibleSecondNav}
|
||||
<div class="hulyComponent-content__column">
|
||||
<div class="hulyComponent-content__navHeader">
|
||||
<div class="hulyComponent-content__navHeader-menu">
|
||||
<ButtonIcon kind="tertiary" icon={IconDescription} size="small" inheritColor />
|
||||
</div>
|
||||
</div>
|
||||
{#each sections as navItem (navItem.id)}
|
||||
<NavItem
|
||||
type="type-anchor-link"
|
||||
label={navItem.label}
|
||||
on:click={() => {
|
||||
sectionRefs[navItem.id]?.scrollIntoView()
|
||||
}}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
<Separator name="spaceTypeEditor" index={0} color="transparent" />
|
||||
{/if}
|
||||
<div class="hulyComponent-content__column content">
|
||||
<Scroller align="center" padding="var(--spacing-3)" bottomPadding="var(--spacing-3)">
|
||||
<div class="hulyComponent-content gap">
|
||||
{#each sections as section, i}
|
||||
<div bind:this={sectionRefs[section.id]} class:hulyTableAttr-container={i}>
|
||||
<Component
|
||||
is={section.component}
|
||||
disabled={readonly}
|
||||
props={{
|
||||
masterTag
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</Scroller>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,23 @@
|
||||
<!--
|
||||
// Copyright © 2025s 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 { MasterTag } from '@hcengineering/card'
|
||||
import { ClassAttributes } from '@hcengineering/setting-resources'
|
||||
import card from '../../plugin'
|
||||
|
||||
export let masterTag: MasterTag
|
||||
</script>
|
||||
|
||||
<ClassAttributes ofClass={card.class.Card} _class={masterTag._id} showHierarchy showHeader={false} disabled={false} />
|
||||
@@ -0,0 +1,82 @@
|
||||
<!--
|
||||
// Copyright © 2025 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 card, { MasterTag } from '@hcengineering/card'
|
||||
import contact from '@hcengineering/contact'
|
||||
import core, { Association, Class, Doc, Ref } from '@hcengineering/core'
|
||||
import { IntlString } from '@hcengineering/platform'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import setting from '@hcengineering/setting'
|
||||
import { clearSettingsStore, settingsStore } from '@hcengineering/setting-resources'
|
||||
import { ButtonIcon, Icon, IconAdd, Label, showPopup } from '@hcengineering/ui'
|
||||
import { onDestroy } from 'svelte'
|
||||
|
||||
export let masterTag: MasterTag
|
||||
|
||||
let associations: Association[] = []
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
$: descendants = new Set(hierarchy.getDescendants(masterTag._id))
|
||||
|
||||
$: filtered = associations.filter((it) => descendants.has(it.classA) || descendants.has(it.classB))
|
||||
const query = createQuery()
|
||||
|
||||
query.query(core.class.Association, {}, (res) => {
|
||||
associations = res
|
||||
})
|
||||
|
||||
function getClassLabel (_class: Ref<Class<Doc>>): IntlString {
|
||||
const _classLabel = hierarchy.getClass(_class)
|
||||
return _classLabel.label
|
||||
}
|
||||
|
||||
function addRelation (): void {
|
||||
showPopup(setting.component.CreateRelation, {
|
||||
exclude: [],
|
||||
_classes: [card.class.Card, contact.class.Contact]
|
||||
})
|
||||
}
|
||||
|
||||
const handleSelect = (association: Association): void => {
|
||||
$settingsStore = { id: association._id, component: setting.component.EditRelation, props: { association } }
|
||||
}
|
||||
onDestroy(() => {
|
||||
clearSettingsStore()
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="hulyTableAttr-header font-medium-12">
|
||||
<Icon icon={setting.icon.Relations} size="small" />
|
||||
<span><Label label={core.string.Relations} /></span>
|
||||
<ButtonIcon kind="primary" icon={IconAdd} size="small" dataId={'btnAdd'} on:click={addRelation} />
|
||||
</div>
|
||||
{#if filtered.length}
|
||||
<div class="hulyTableAttr-content task">
|
||||
{#each filtered as association}
|
||||
<button
|
||||
class="hulyTableAttr-content__row justify-start"
|
||||
on:click|stopPropagation={() => {
|
||||
handleSelect(association)
|
||||
}}
|
||||
>
|
||||
<div class="hulyTableAttr-content__row-label font-medium-14 cursor-pointer">
|
||||
{association.nameA} (<Label label={getClassLabel(association.classA)} />) - {association.nameB} (<Label
|
||||
label={getClassLabel(association.classB)}
|
||||
/>)
|
||||
</div>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
@@ -0,0 +1,78 @@
|
||||
<!--
|
||||
// Copyright © 2025 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 cardPlugin from '@hcengineering/card'
|
||||
import core, { Class, ClassifierKind, Doc, Ref } from '@hcengineering/core'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { Icon, Label } from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
export let classes: Ref<Class<Doc>>[] = []
|
||||
export let _class: Ref<Class<Doc>> | undefined
|
||||
export let kind: ClassifierKind = ClassifierKind.CLASS
|
||||
export let level: number = 0
|
||||
|
||||
const client = getClient()
|
||||
const dispatch = createEventDispatcher()
|
||||
let descendants = new Map<Ref<Class<Doc>>, Ref<Class<Doc>>[]>()
|
||||
|
||||
function getDescendants (_class: Ref<Class<Doc>>): Ref<Class<Doc>>[] {
|
||||
const hierarchy = client.getHierarchy()
|
||||
const result: Ref<Class<Doc>>[] = []
|
||||
const desc = hierarchy.getDescendants(_class)
|
||||
for (const clazz of desc) {
|
||||
const cls = hierarchy.getClass(clazz)
|
||||
if (cls.extends === _class && !cls.hidden && kind === cls.kind && cls.label !== undefined) {
|
||||
result.push(clazz)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
function fillDescendants (classes: Ref<Class<Doc>>[]): void {
|
||||
for (const cl of classes) {
|
||||
descendants.set(cl, getDescendants(cl))
|
||||
}
|
||||
descendants = descendants
|
||||
}
|
||||
|
||||
const query = createQuery()
|
||||
query.query(core.class.Class, {}, () => {
|
||||
fillDescendants(classes)
|
||||
})
|
||||
|
||||
$: fillDescendants(classes)
|
||||
</script>
|
||||
|
||||
{#each classes as cl}
|
||||
{@const clazz = client.getHierarchy().getClass(cl)}
|
||||
<button
|
||||
class="hulyTableAttr-content__row justify-start cursor-pointer"
|
||||
on:click={() => {
|
||||
dispatch('select', cl)
|
||||
}}
|
||||
>
|
||||
<div
|
||||
class="hulyTableAttr-content__row-label font-medium-14 flex flex-gap-2"
|
||||
style:margin-left={`${level * 1.25}rem`}
|
||||
>
|
||||
<Icon icon={clazz.icon ?? cardPlugin.icon.Tag} size="small" />
|
||||
<Label label={clazz.label} />
|
||||
</div>
|
||||
</button>
|
||||
{#if (descendants.get(cl)?.length ?? 0) > 0}
|
||||
<svelte:self classes={descendants.get(cl) ?? []} {_class} {kind} level={level + 1} on:select />
|
||||
{/if}
|
||||
{/each}
|
||||
@@ -0,0 +1,85 @@
|
||||
<!--
|
||||
// Copyright © 2025 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 { MasterTag } from '@hcengineering/card'
|
||||
import { Class, ClassifierKind, Doc, Ref } from '@hcengineering/core'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { ClassHierarchy } from '@hcengineering/setting-resources'
|
||||
import { ButtonIcon, getCurrentLocation, Icon, IconAdd, Label, navigate, showPopup } from '@hcengineering/ui'
|
||||
import card from '../../plugin'
|
||||
import CreateTag from '../CreateTag.svelte'
|
||||
import TagsHierarchy from './TagsHierarchy.svelte'
|
||||
|
||||
export let masterTag: MasterTag
|
||||
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
let descendants: Ref<Class<Doc>>[] = []
|
||||
$: getDescendants(masterTag._id)
|
||||
|
||||
const query = createQuery()
|
||||
|
||||
query.query(card.class.Tag, {}, () => {
|
||||
getDescendants(masterTag._id)
|
||||
})
|
||||
|
||||
function getDescendants (id: Ref<Class<Doc>>): void {
|
||||
const desc = hierarchy.getDescendants(id)
|
||||
const filtered: Ref<Class<Doc>>[] = []
|
||||
for (const _id of desc) {
|
||||
const _class = hierarchy.getClass(_id)
|
||||
if (_class.extends === id && _class._class === card.class.Tag) {
|
||||
filtered.push(_id)
|
||||
continue
|
||||
}
|
||||
}
|
||||
descendants = filtered
|
||||
}
|
||||
|
||||
function open (_class: Ref<Class<Doc>>): void {
|
||||
const loc = getCurrentLocation()
|
||||
loc.path[4] = _class
|
||||
navigate(loc)
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="hulyTableAttr-header font-medium-12">
|
||||
<Icon icon={card.icon.Tag} size="small" />
|
||||
<span><Label label={card.string.Tags} /></span>
|
||||
<ButtonIcon
|
||||
kind="primary"
|
||||
icon={IconAdd}
|
||||
size="small"
|
||||
dataId={'btnAdd'}
|
||||
on:click={() => {
|
||||
showPopup(CreateTag, {
|
||||
parent: masterTag,
|
||||
_class: card.class.Tag
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{#if descendants.length}
|
||||
<div class="hulyTableAttr-content task">
|
||||
<TagsHierarchy
|
||||
kind={ClassifierKind.MIXIN}
|
||||
classes={descendants}
|
||||
_class={undefined}
|
||||
on:select={(e) => {
|
||||
open(e.detail)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -26,6 +26,14 @@ import {
|
||||
queryCard,
|
||||
deleteMasterTag
|
||||
} from './utils'
|
||||
import ManageMasterTagsContent from './components/settings/ManageMasterTagsContent.svelte'
|
||||
import ManageMasterTagsTools from './components/settings/ManageMasterTagsTools.svelte'
|
||||
import ManageMasterTags from './components/settings/ManageMasterTags.svelte'
|
||||
import GeneralSection from './components/settings/GeneralSection.svelte'
|
||||
import ProperitiesSection from './components/settings/ProperitiesSection.svelte'
|
||||
import TagsSection from './components/settings/TagsSection.svelte'
|
||||
import RelationsSection from './components/settings/RelationsSection.svelte'
|
||||
import ChildsSection from './components/settings/ChildsSection.svelte'
|
||||
|
||||
export default async (): Promise<Resources> => ({
|
||||
component: {
|
||||
@@ -33,7 +41,15 @@ export default async (): Promise<Resources> => ({
|
||||
CreateTag,
|
||||
CardPresenter,
|
||||
EditCard,
|
||||
Main
|
||||
Main,
|
||||
ManageMasterTags,
|
||||
ManageMasterTagsContent,
|
||||
ManageMasterTagsTools,
|
||||
GeneralSection,
|
||||
ProperitiesSection,
|
||||
TagsSection,
|
||||
RelationsSection,
|
||||
ChildsSection
|
||||
},
|
||||
completion: {
|
||||
CardQuery: queryCard
|
||||
|
||||
@@ -21,11 +21,19 @@ import { type Viewlet } from '@hcengineering/view'
|
||||
|
||||
export default mergeIds(cardId, card, {
|
||||
component: {
|
||||
ManageMasterTags: '' as AnyComponent,
|
||||
ManageMasterTagsContent: '' as AnyComponent,
|
||||
ManageMasterTagsTools: '' as AnyComponent,
|
||||
MasterTags: '' as AnyComponent,
|
||||
CreateTag: '' as AnyComponent,
|
||||
CardPresenter: '' as AnyComponent,
|
||||
EditCard: '' as AnyComponent,
|
||||
Main: '' as AnyComponent
|
||||
Main: '' as AnyComponent,
|
||||
GeneralSection: '' as AnyComponent,
|
||||
ProperitiesSection: '' as AnyComponent,
|
||||
TagsSection: '' as AnyComponent,
|
||||
ChildsSection: '' as AnyComponent,
|
||||
RelationsSection: '' as AnyComponent
|
||||
},
|
||||
viewlet: {
|
||||
CardTable: '' as Ref<Viewlet>
|
||||
@@ -44,6 +52,7 @@ export default mergeIds(cardId, card, {
|
||||
DeleteTag: '' as IntlString,
|
||||
DeleteTagConfirm: '' as IntlString,
|
||||
DeleteMasterTag: '' as IntlString,
|
||||
DeleteMasterTagConfirm: '' as IntlString
|
||||
DeleteMasterTagConfirm: '' as IntlString,
|
||||
TagRelations: '' as IntlString
|
||||
}
|
||||
})
|
||||
|
||||
@@ -57,8 +57,8 @@ export async function deleteMasterTag (tag: MasterTag | undefined): Promise<void
|
||||
const desc = hierarchy.getClass(obj)
|
||||
await ops.remove(desc)
|
||||
}
|
||||
await ops.remove(tag)
|
||||
await ops.commit()
|
||||
await client.remove(tag)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
@@ -81,12 +81,20 @@ export async function deleteMasterTag (tag: MasterTag | undefined): Promise<void
|
||||
for (const obj of cards) {
|
||||
await ops.update(obj, { $unset: update })
|
||||
}
|
||||
await ops.remove(tag)
|
||||
await ops.commit()
|
||||
await client.remove(tag)
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
const ops = client.apply(undefined, 'delete-tag')
|
||||
const hierarchy = client.getHierarchy()
|
||||
const desc = hierarchy.getDescendants(tag._id)
|
||||
for (const obj of desc) {
|
||||
const desc = hierarchy.getClass(obj)
|
||||
await ops.remove(desc)
|
||||
}
|
||||
await ops.commit()
|
||||
await client.remove(tag)
|
||||
}
|
||||
}
|
||||
@@ -112,7 +120,7 @@ async function generateLocation (loc: Location, id: string): Promise<ResolvedLoc
|
||||
}
|
||||
const appComponent = loc.path[0] ?? ''
|
||||
const workspace = loc.path[1] ?? ''
|
||||
const special = card.class.Card
|
||||
const special = doc._class
|
||||
|
||||
const objectPanel = client.getHierarchy().classHierarchyMixin(doc._class as Ref<Class<Doc>>, view.mixin.ObjectPanel)
|
||||
const component = objectPanel?.component ?? view.component.EditDoc
|
||||
|
||||
@@ -38,7 +38,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/platform": "^0.6.11",
|
||||
"@hcengineering/core": "^0.6.32"
|
||||
"@hcengineering/core": "^0.6.32",
|
||||
"@hcengineering/ui": "^0.6.15"
|
||||
},
|
||||
"repository": "https://github.com/hcengineering/platform",
|
||||
"publishConfig": {
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
import { Class, Mixin, Doc, Ref, MarkupBlobRef, Rank } from '@hcengineering/core'
|
||||
import { Asset, IntlString, plugin, Plugin } from '@hcengineering/platform'
|
||||
import type { AnyComponent } from '@hcengineering/ui'
|
||||
|
||||
export * from './analytics'
|
||||
|
||||
@@ -29,6 +30,13 @@ export interface Card extends Doc {
|
||||
rank: Rank
|
||||
}
|
||||
|
||||
export interface MasterTagEditorSection extends Doc {
|
||||
id: string
|
||||
label: IntlString
|
||||
component: AnyComponent
|
||||
masterOnly?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
@@ -41,7 +49,8 @@ const cardPlugin = plugin(cardId, {
|
||||
class: {
|
||||
Card: '' as Ref<Class<Card>>,
|
||||
MasterTag: '' as Ref<Class<MasterTag>>,
|
||||
Tag: '' as Ref<Class<Tag>>
|
||||
Tag: '' as Ref<Class<Tag>>,
|
||||
MasterTagEditorSection: '' as Ref<Class<MasterTagEditorSection>>
|
||||
},
|
||||
icon: {
|
||||
MasterTags: '' as Asset,
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
"@hcengineering/panel": "^0.6.23",
|
||||
"@hcengineering/view": "^0.6.13",
|
||||
"@hcengineering/view-resources": "^0.6.0",
|
||||
"@hcengineering/card": "^0.6.0",
|
||||
"@hcengineering/task": "^0.6.20",
|
||||
"@hcengineering/contact-resources": "^0.6.0",
|
||||
"@hcengineering/login": "^0.6.12",
|
||||
|
||||
@@ -23,20 +23,24 @@
|
||||
DropdownIntlItem,
|
||||
DropdownLabelsIntl,
|
||||
EditBox,
|
||||
Label
|
||||
Label,
|
||||
NestedDropdown
|
||||
} from '@hcengineering/ui'
|
||||
import view from '@hcengineering/view'
|
||||
import card from '@hcengineering/card'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import setting from '../plugin'
|
||||
|
||||
export let association: Association | Data<Association>
|
||||
export let kind: ButtonKind = 'regular'
|
||||
export let size: ButtonSize = 'medium'
|
||||
export let _classes: Ref<Class<Doc>>[] = [core.class.Doc]
|
||||
export let exclude: Ref<Class<Doc>>[] = [card.class.Card]
|
||||
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
|
||||
const descendants = hierarchy.getDescendants(core.class.Doc)
|
||||
const descendants = new Set(_classes.map((p) => hierarchy.getDescendants(p)).reduce((a, b) => a.concat(b)))
|
||||
const viewlets = new Set(
|
||||
client
|
||||
.getModel()
|
||||
@@ -44,43 +48,69 @@
|
||||
.map((p) => p.attachTo)
|
||||
)
|
||||
|
||||
function filterClasses (descendants: Ref<Class<Doc>>[], viewlets: Set<Ref<Class<Doc>>>): DropdownIntlItem[] {
|
||||
const result = []
|
||||
function filterClasses (
|
||||
descendants: Set<Ref<Class<Doc>>>,
|
||||
viewlets: Set<Ref<Class<Doc>>>,
|
||||
exclude: Ref<Class<Doc>>[]
|
||||
): [DropdownIntlItem, DropdownIntlItem[]][] {
|
||||
const ignore = new Set<Ref<Class<Doc>>>()
|
||||
const added = new Set<Ref<Class<Doc>>>()
|
||||
for (const _id of descendants) {
|
||||
if (added.has(_id)) continue
|
||||
const _class = hierarchy.getClass(_id)
|
||||
if (_class.extends !== undefined && added.has(_class.extends)) {
|
||||
added.add(_id)
|
||||
result.push({ id: _id, label: _class.label })
|
||||
const base = new Map<Ref<Class<Doc>>, Class<Doc>[]>()
|
||||
for (const excluded of exclude) {
|
||||
const desc = hierarchy.getDescendants(excluded)
|
||||
for (const _id of desc) {
|
||||
ignore.add(_id)
|
||||
}
|
||||
}
|
||||
for (const _id of descendants) {
|
||||
if (added.has(_id) || ignore.has(_id)) continue
|
||||
const _class = hierarchy.getClass(_id)
|
||||
if (_class.label === undefined) continue
|
||||
if (viewlets.has(hierarchy.getBaseClass(_id))) {
|
||||
added.add(_id)
|
||||
result.push({ id: _id, label: _class.label })
|
||||
const descendants = hierarchy.getDescendants(_id)
|
||||
const toAdd: Class<Doc>[] = []
|
||||
for (const desc of descendants) {
|
||||
if (added.has(desc) || ignore.has(desc)) continue
|
||||
const _class = hierarchy.getClass(desc)
|
||||
if (_class.label === undefined) continue
|
||||
added.add(desc)
|
||||
toAdd.push(_class)
|
||||
}
|
||||
base.set(_id, toAdd)
|
||||
}
|
||||
}
|
||||
const result: [DropdownIntlItem, DropdownIntlItem[]][] = []
|
||||
for (const [key, value] of base) {
|
||||
try {
|
||||
const clazz = hierarchy.getClass(key)
|
||||
result.push([{ id: key, label: clazz.label }, value.map((it) => ({ id: it._id, label: it.label }))])
|
||||
} catch {}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
const classes = filterClasses(descendants, viewlets)
|
||||
const classes = filterClasses(descendants, viewlets, exclude)
|
||||
|
||||
let classA = classes.find((p) => p.id === association.classA) ?? classes[0]
|
||||
let classB = classes.find((p) => p.id === association.classB) ?? classes[0]
|
||||
let classARef = classA?.id
|
||||
let classBRef = classB?.id
|
||||
let classARef: Ref<Class<Doc>> | undefined = undefined
|
||||
let classBRef: Ref<Class<Doc>> | undefined = undefined
|
||||
let nameA = association.nameA
|
||||
let nameB = association.nameB
|
||||
|
||||
$: classA = isEmptyClass(classARef) ? undefined : hierarchy.getClass(classARef as Ref<Class<Doc>>)
|
||||
$: classB = isEmptyClass(classBRef) ? undefined : hierarchy.getClass(classBRef as Ref<Class<Doc>>)
|
||||
|
||||
function isEmptyClass (ref: Ref<Class<Doc>> | undefined): boolean {
|
||||
return ref === undefined || ref !== ''
|
||||
}
|
||||
|
||||
$: editable = !isAssociation(association)
|
||||
|
||||
$: fill(association)
|
||||
|
||||
function fill (association: Association | Data<Association>): void {
|
||||
classA = classes.find((p) => p.id === association.classA) ?? classes[0]
|
||||
classB = classes.find((p) => p.id === association.classB) ?? classes[0]
|
||||
classBRef = classB?.id
|
||||
classARef = classA?.id
|
||||
classBRef = !isEmptyClass(association.classB) ? association.classB : undefined
|
||||
classARef = !isEmptyClass(association.classA) ? association.classA : undefined
|
||||
nameA = association.nameA
|
||||
nameB = association.nameB
|
||||
}
|
||||
@@ -102,8 +132,8 @@
|
||||
})
|
||||
} else {
|
||||
await client.createDoc(core.class.Association, core.space.Model, {
|
||||
classA: classARef as Ref<Class<Doc>>,
|
||||
classB: classBRef as Ref<Class<Doc>>,
|
||||
classA: classARef,
|
||||
classB: classBRef,
|
||||
type: mode,
|
||||
nameA,
|
||||
nameB
|
||||
@@ -143,13 +173,11 @@
|
||||
</div>
|
||||
<div>
|
||||
{#if editable}
|
||||
<DropdownLabelsIntl
|
||||
label={core.string.Class}
|
||||
<NestedDropdown
|
||||
items={classes}
|
||||
width="8rem"
|
||||
bind:selected={classARef}
|
||||
{kind}
|
||||
{size}
|
||||
on:selected={(e) => {
|
||||
classARef = e.detail
|
||||
}}
|
||||
/>
|
||||
{:else if classA}
|
||||
<Label label={classA.label} />
|
||||
@@ -184,13 +212,11 @@
|
||||
</div>
|
||||
<div>
|
||||
{#if editable}
|
||||
<DropdownLabelsIntl
|
||||
label={core.string.Class}
|
||||
<NestedDropdown
|
||||
items={classes}
|
||||
width="8rem"
|
||||
bind:selected={classBRef}
|
||||
{kind}
|
||||
{size}
|
||||
on:selected={(e) => {
|
||||
classBRef = e.detail
|
||||
}}
|
||||
/>
|
||||
{:else if classB}
|
||||
<Label label={classB.label} />
|
||||
|
||||
@@ -53,11 +53,6 @@
|
||||
<button class="hulyTableAttr-content__row-dragMenu" on:click|stopPropagation={clickMore}>
|
||||
<IconMoreV2 size={'small'} />
|
||||
</button>
|
||||
{#if attribute.isCustom}
|
||||
<div class="hulyChip-item font-medium-12">
|
||||
<Label label={settings.string.Custom} />
|
||||
</div>
|
||||
{/if}
|
||||
{#if attribute.icon !== undefined}
|
||||
<div class="hulyTableAttr-content__row-icon">
|
||||
<Icon icon={attribute.icon} size={'small'} />
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import core, { AnyAttribute, Class, ClassifierKind, Doc, Ref, Space } from '@hcengineering/core'
|
||||
import core, { AnyAttribute, Class, Doc, Ref, Space } from '@hcengineering/core'
|
||||
import { IntlString } from '@hcengineering/platform'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import {
|
||||
@@ -22,28 +22,29 @@
|
||||
ButtonIcon,
|
||||
IconAdd,
|
||||
IconEdit,
|
||||
Label,
|
||||
getEventPositionElement,
|
||||
showPopup,
|
||||
IconSettings,
|
||||
Label,
|
||||
ModernButton,
|
||||
deviceWidths,
|
||||
getEventPositionElement,
|
||||
resizeObserver,
|
||||
deviceWidths
|
||||
showPopup
|
||||
} from '@hcengineering/ui'
|
||||
import { ObjectPresenter } from '@hcengineering/view-resources'
|
||||
import { onDestroy } from 'svelte'
|
||||
import settings from '../plugin'
|
||||
import CreateAttribute from './CreateAttribute.svelte'
|
||||
import { clearSettingsStore, settingsStore } from '../store'
|
||||
import ClassAttributesList from './ClassAttributesList.svelte'
|
||||
import CreateAttribute from './CreateAttribute.svelte'
|
||||
import EditAttribute from './EditAttribute.svelte'
|
||||
import EditClassLabel from './EditClassLabel.svelte'
|
||||
import { settingsStore, clearSettingsStore } from '../store'
|
||||
import TypesPopup from './typeEditors/TypesPopup.svelte'
|
||||
import { onDestroy } from 'svelte'
|
||||
|
||||
export let _class: Ref<Class<Doc>>
|
||||
export let ofClass: Ref<Class<Doc>> | undefined = undefined
|
||||
export let showHierarchy: boolean = false
|
||||
export let showTitle: boolean = !showHierarchy
|
||||
export let showHeader: boolean = true
|
||||
export let disabled: boolean = true
|
||||
export let attributeMapper:
|
||||
| {
|
||||
@@ -101,16 +102,18 @@
|
||||
showPopup(EditClassLabel, { clazz }, getEventPositionElement(evt))
|
||||
}
|
||||
|
||||
const classUpdated = (_clazz: Ref<Class<Doc>>): void => {
|
||||
const classUpdated = (_clazz: Ref<Class<Doc>>, to: Ref<Class<Doc>>): void => {
|
||||
selected = undefined
|
||||
classes = client
|
||||
.getHierarchy()
|
||||
const h = client.getHierarchy()
|
||||
const toAncestors = new Set(h.getAncestors(to))
|
||||
classes = h
|
||||
.getAncestors(_class)
|
||||
.map((it) => client.getHierarchy().getClass(it))
|
||||
.filter((it) => {
|
||||
return (
|
||||
!it.hidden &&
|
||||
it.label !== undefined &&
|
||||
!toAncestors.has(it._id) &&
|
||||
it._id !== core.class.Doc &&
|
||||
it._id !== core.class.AttachedDoc &&
|
||||
it._id !== _class
|
||||
@@ -118,7 +121,7 @@
|
||||
})
|
||||
clazzHierarchy = client.getHierarchy().getClass(_class)
|
||||
}
|
||||
$: classUpdated(_class)
|
||||
$: classUpdated(_class, ofClass ?? core.class.Doc)
|
||||
|
||||
settingsStore.subscribe((value) => {
|
||||
if ((value.id === undefined && selected !== undefined) || (value.id !== undefined && value.id !== selected?._id)) {
|
||||
@@ -163,14 +166,18 @@
|
||||
}}
|
||||
>
|
||||
<div class="hulyTableAttr-header font-medium-12" class:withButton={showHierarchy}>
|
||||
{#if showHierarchy}
|
||||
<ModernButton icon={IconSettings} kind={'secondary'} size={'small'} {disabled} hasMenu>
|
||||
<Label label={settings.string.ClassColon} />
|
||||
<ObjectPresenter _class={clazzHierarchy._class} objectId={clazzHierarchy._id} value={clazzHierarchy} />
|
||||
</ModernButton>
|
||||
{#if showHeader}
|
||||
{#if showHierarchy}
|
||||
<ModernButton icon={IconSettings} kind={'secondary'} size={'small'} {disabled} hasMenu>
|
||||
<Label label={settings.string.ClassColon} />
|
||||
<ObjectPresenter _class={clazzHierarchy._class} objectId={clazzHierarchy._id} value={clazzHierarchy} />
|
||||
</ModernButton>
|
||||
{:else}
|
||||
<IconSettings size={'small'} />
|
||||
<span><Label label={settings.string.ClassProperties} /></span>
|
||||
{/if}
|
||||
{:else}
|
||||
<IconSettings size={'small'} />
|
||||
<span><Label label={settings.string.ClassProperties} /></span>
|
||||
<div></div>
|
||||
{/if}
|
||||
<ButtonIcon
|
||||
kind={'primary'}
|
||||
|
||||
@@ -138,7 +138,6 @@
|
||||
/>
|
||||
<ModernEditbox bind:value={name} label={core.string.Name} size={'large'} kind={'ghost'} autoFocus />
|
||||
</div>
|
||||
<ModernEditbox bind:value={name} label={core.string.Name} size={'large'} kind={'ghost'} autoFocus />
|
||||
</div>
|
||||
<div class="hulyModal-content__settingsSet">
|
||||
<div class="hulyModal-content__settingsSet-line">
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
<!--
|
||||
// Copyright © 2025 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 cardPlugin from '@hcengineering/card'
|
||||
import core, { Class, Doc, Ref } from '@hcengineering/core'
|
||||
import { getEmbeddedLabel } from '@hcengineering/platform'
|
||||
import presentation, { Card, getClient } from '@hcengineering/presentation'
|
||||
import { DropdownIntlItem, DropdownLabelsIntl, EditBox, Label } from '@hcengineering/ui'
|
||||
import NestedDropdown from '@hcengineering/ui/src/components/NestedDropdown.svelte'
|
||||
import view from '@hcengineering/view'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import setting from '../plugin'
|
||||
|
||||
export let _classes: Ref<Class<Doc>>[] = [core.class.Doc]
|
||||
export let exclude: Ref<Class<Doc>>[] = [cardPlugin.class.Card]
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
|
||||
const descendants = new Set(_classes.map((p) => hierarchy.getDescendants(p)).reduce((a, b) => a.concat(b)))
|
||||
const viewlets = new Set(
|
||||
client
|
||||
.getModel()
|
||||
.findAllSync(view.class.Viewlet, { descriptor: view.viewlet.Table })
|
||||
.map((p) => p.attachTo)
|
||||
)
|
||||
|
||||
function filterClasses (
|
||||
descendants: Set<Ref<Class<Doc>>>,
|
||||
viewlets: Set<Ref<Class<Doc>>>,
|
||||
exclude: Ref<Class<Doc>>[]
|
||||
): [DropdownIntlItem, DropdownIntlItem[]][] {
|
||||
const ignore = new Set<Ref<Class<Doc>>>()
|
||||
const added = new Set<Ref<Class<Doc>>>()
|
||||
const base = new Map<Ref<Class<Doc>>, Class<Doc>[]>()
|
||||
for (const excluded of exclude) {
|
||||
const desc = hierarchy.getDescendants(excluded)
|
||||
for (const _id of desc) {
|
||||
ignore.add(_id)
|
||||
}
|
||||
}
|
||||
for (const _id of descendants) {
|
||||
if (added.has(_id) || ignore.has(_id)) continue
|
||||
const _class = hierarchy.getClass(_id)
|
||||
if (_class.label === undefined) continue
|
||||
if (viewlets.has(hierarchy.getBaseClass(_id))) {
|
||||
added.add(_id)
|
||||
const descendants = hierarchy.getDescendants(_id)
|
||||
const toAdd: Class<Doc>[] = []
|
||||
for (const desc of descendants) {
|
||||
if (added.has(desc) || ignore.has(desc)) continue
|
||||
const _class = hierarchy.getClass(desc)
|
||||
if (_class.label === undefined) continue
|
||||
added.add(desc)
|
||||
toAdd.push(_class)
|
||||
}
|
||||
base.set(_id, toAdd)
|
||||
}
|
||||
}
|
||||
const result: [DropdownIntlItem, DropdownIntlItem[]][] = []
|
||||
for (const [key, value] of base) {
|
||||
try {
|
||||
const clazz = hierarchy.getClass(key)
|
||||
result.push([{ id: key, label: clazz.label }, value.map((it) => ({ id: it._id, label: it.label }))])
|
||||
} catch {}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
const classes = filterClasses(descendants, viewlets, exclude)
|
||||
|
||||
let classARef: Ref<Class<Doc>> | undefined = undefined
|
||||
let classBRef: Ref<Class<Doc>> | undefined = undefined
|
||||
let nameA: string = ''
|
||||
let nameB: string = ''
|
||||
|
||||
async function save (): Promise<void> {
|
||||
if (classBRef === undefined || classARef === undefined) {
|
||||
return
|
||||
}
|
||||
await client.createDoc(core.class.Association, core.space.Model, {
|
||||
classA: classARef,
|
||||
classB: classBRef,
|
||||
type: mode,
|
||||
nameA,
|
||||
nameB
|
||||
})
|
||||
dispatch('close')
|
||||
}
|
||||
|
||||
const items: DropdownIntlItem[] = [
|
||||
{
|
||||
id: '1:1',
|
||||
label: getEmbeddedLabel('1:1')
|
||||
},
|
||||
{
|
||||
id: '1:N',
|
||||
label: getEmbeddedLabel('1:N')
|
||||
},
|
||||
{
|
||||
id: 'N:N',
|
||||
label: getEmbeddedLabel('N:N')
|
||||
}
|
||||
]
|
||||
|
||||
let mode: '1:1' | '1:N' | 'N:N' = 'N:N' as '1:1' | '1:N' | 'N:N'
|
||||
</script>
|
||||
|
||||
<Card
|
||||
label={core.string.AddRelation}
|
||||
okLabel={presentation.string.Create}
|
||||
canSave={nameB.trim() !== '' && nameA.trim() !== '' && classARef !== undefined && classBRef !== undefined}
|
||||
okAction={save}
|
||||
on:close
|
||||
>
|
||||
<div class="flex-between flex-gap-4">
|
||||
<div class="flex-col p-4 flex-gap-2">
|
||||
<div class="flex-col-center">A</div>
|
||||
<div>
|
||||
<EditBox bind:value={nameA} placeholder={core.string.Name} kind={'default'} />
|
||||
</div>
|
||||
<div>
|
||||
<NestedDropdown
|
||||
items={classes}
|
||||
on:selected={(e) => {
|
||||
classARef = e.detail
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex-col p-4 flex-gap-2">
|
||||
<span class="label">
|
||||
<Label label={setting.string.Type} />
|
||||
</span>
|
||||
<DropdownLabelsIntl
|
||||
selected={mode}
|
||||
{items}
|
||||
label={setting.string.Type}
|
||||
on:selected={(res) => {
|
||||
mode = res.detail
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex-col p-4 flex-gap-2">
|
||||
<div class="flex-col-center">B</div>
|
||||
<div>
|
||||
<EditBox bind:value={nameB} placeholder={core.string.Name} kind={'default'} />
|
||||
</div>
|
||||
<div>
|
||||
<NestedDropdown
|
||||
items={classes}
|
||||
on:selected={(e) => {
|
||||
classBRef = e.detail
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
@@ -29,10 +29,10 @@
|
||||
showPopup,
|
||||
themeStore
|
||||
} from '@hcengineering/ui'
|
||||
import { IconPicker } from '@hcengineering/view-resources'
|
||||
import view from '@hcengineering/view-resources/src/plugin'
|
||||
import setting from '../plugin'
|
||||
import { clearSettingsStore } from '../store'
|
||||
import { IconPicker, iconsLibrary } from '@hcengineering/view-resources'
|
||||
|
||||
export let attribute: AnyAttribute
|
||||
export let exist: boolean
|
||||
@@ -166,11 +166,6 @@
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
<div class="hulyModal-content__titleGroup">
|
||||
{#if attribute.isCustom}
|
||||
<div class="hulyChip-item font-medium-12">
|
||||
<Label label={setting.string.Custom} />
|
||||
</div>
|
||||
{/if}
|
||||
<div class="flex items-center">
|
||||
<ButtonIcon
|
||||
icon={icon ?? setting.icon.Enums}
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
<!--
|
||||
// Copyright © 2025 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, { Association } from '@hcengineering/core'
|
||||
import { getEmbeddedLabel, IntlString } from '@hcengineering/platform'
|
||||
import presentation, { getClient, MessageBox } from '@hcengineering/presentation'
|
||||
import { Button, DropdownIntlItem, EditBox, IconDelete, Label, Modal, showPopup } from '@hcengineering/ui'
|
||||
import view from '@hcengineering/view'
|
||||
import setting from '../plugin'
|
||||
import { clearSettingsStore } from '../store'
|
||||
|
||||
export let association: Association
|
||||
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
|
||||
const classA = hierarchy.getClass(association.classA)
|
||||
const classB = hierarchy.getClass(association.classB)
|
||||
let nameA = association.nameA
|
||||
let nameB = association.nameB
|
||||
|
||||
async function save (): Promise<void> {
|
||||
await client.diffUpdate(association, {
|
||||
nameA,
|
||||
nameB
|
||||
})
|
||||
clearSettingsStore()
|
||||
}
|
||||
|
||||
const items: DropdownIntlItem[] = [
|
||||
{
|
||||
id: '1:1',
|
||||
label: getEmbeddedLabel('1:1')
|
||||
},
|
||||
{
|
||||
id: '1:N',
|
||||
label: getEmbeddedLabel('1:N')
|
||||
},
|
||||
{
|
||||
id: 'N:N',
|
||||
label: getEmbeddedLabel('N:N')
|
||||
}
|
||||
]
|
||||
|
||||
const label = items.find((item) => item.id === association?.type)?.label ?? ('' as IntlString)
|
||||
|
||||
async function remove (): Promise<void> {
|
||||
showPopup(MessageBox, {
|
||||
label: view.string.DeleteObject,
|
||||
message: view.string.DeleteObjectConfirm,
|
||||
params: { count: 1 },
|
||||
dangerous: true,
|
||||
action: async () => {
|
||||
await client.remove(association)
|
||||
clearSettingsStore()
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal
|
||||
label={core.string.Relation}
|
||||
type={'type-aside'}
|
||||
okLabel={presentation.string.Save}
|
||||
okAction={save}
|
||||
canSave={true}
|
||||
onCancel={clearSettingsStore}
|
||||
>
|
||||
<svelte:fragment slot="actions">
|
||||
<Button icon={IconDelete} kind={'dangerous'} on:click={remove} />
|
||||
</svelte:fragment>
|
||||
<div class="hulyModal-content__settingsSet">
|
||||
<div class="flex flex-gap-4">
|
||||
<div class="flex-col p-4 flex-gap-2">
|
||||
<div class="flex-col-center">A</div>
|
||||
<div>
|
||||
<EditBox bind:value={nameA} placeholder={core.string.Name} kind={'default'} />
|
||||
</div>
|
||||
<div>
|
||||
<Label label={classA.label} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-col p-4 flex-gap-2">
|
||||
<span class="label">
|
||||
<Label label={setting.string.Type} />
|
||||
</span>
|
||||
<Label {label} />
|
||||
</div>
|
||||
<div class="flex-col p-4 flex-gap-2">
|
||||
<div class="flex-col-center">B</div>
|
||||
<div>
|
||||
<EditBox bind:value={nameB} placeholder={core.string.Name} kind={'default'} />
|
||||
</div>
|
||||
<div>
|
||||
<Label label={classB.label} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
@@ -1,22 +1,28 @@
|
||||
<script lang="ts">
|
||||
import core, { Association, Class, Data, Doc, Ref } from '@hcengineering/core'
|
||||
import { IntlString, translate } from '@hcengineering/platform'
|
||||
import { createQuery, getClient, MessageBox } from '@hcengineering/presentation'
|
||||
import {
|
||||
Button,
|
||||
Header,
|
||||
Breadcrumb,
|
||||
Separator,
|
||||
Button,
|
||||
defineSeparators,
|
||||
twoPanelsSeparators,
|
||||
Header,
|
||||
IconDelete,
|
||||
showPopup
|
||||
Separator,
|
||||
showPopup,
|
||||
twoPanelsSeparators
|
||||
} from '@hcengineering/ui'
|
||||
import settings from '../plugin'
|
||||
import view from '@hcengineering/view-resources/src/plugin'
|
||||
import settings from '../plugin'
|
||||
import card from '@hcengineering/card'
|
||||
import AssociationEditor from './AssociationEditor.svelte'
|
||||
|
||||
export let _classes: Ref<Class<Doc>>[] = [core.class.Doc]
|
||||
export let exclude: Ref<Class<Doc>>[] = [card.class.Card]
|
||||
|
||||
const query = createQuery()
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
|
||||
let selected: Association | Data<Association> | undefined
|
||||
|
||||
@@ -26,6 +32,37 @@
|
||||
associations = res
|
||||
})
|
||||
|
||||
$: filtered = filterAssociations(associations, _classes, exclude)
|
||||
|
||||
function filterAssociations (
|
||||
associations: Association[],
|
||||
_classes: Ref<Class<Doc>>[],
|
||||
exclude: Ref<Class<Doc>>[]
|
||||
): Association[] {
|
||||
_classes = _classes ?? [core.class.Doc]
|
||||
exclude = exclude ?? [card.class.Card]
|
||||
const res: Association[] = []
|
||||
const descendants = new Set(_classes.map((p) => hierarchy.getDescendants(p)).reduce((a, b) => a.concat(b)))
|
||||
const excluded = new Set()
|
||||
for (const _class of exclude) {
|
||||
const desc = hierarchy.getDescendants(_class)
|
||||
for (const _id of desc) {
|
||||
excluded.add(_id)
|
||||
}
|
||||
}
|
||||
for (const association of associations) {
|
||||
if (
|
||||
descendants.has(association.classA) &&
|
||||
descendants.has(association.classB) &&
|
||||
!excluded.has(association.classA) &&
|
||||
!excluded.has(association.classB)
|
||||
) {
|
||||
res.push(association)
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
function createRelation (): void {
|
||||
selected = {
|
||||
classA: '' as Ref<Class<Doc>>,
|
||||
@@ -40,6 +77,19 @@
|
||||
return (data as Association)?._id !== undefined
|
||||
}
|
||||
|
||||
function getClassLabel (_class: Ref<Class<Doc>>): IntlString | undefined {
|
||||
const _classLabel = client.getModel().findObject(_class)
|
||||
return _classLabel?.label
|
||||
}
|
||||
|
||||
async function getLabel (association: Association): Promise<string> {
|
||||
const aLabel = getClassLabel(association.classA)
|
||||
const bLabel = getClassLabel(association.classB)
|
||||
const aClass = aLabel !== undefined ? await translate(aLabel, {}) : undefined
|
||||
const bClass = bLabel !== undefined ? await translate(bLabel, {}) : undefined
|
||||
return `${association.nameA} ${aClass !== undefined ? '(' + aClass + ')' : ''} - ${association.nameB} ${bClass !== undefined ? '(' + bClass + ')' : ''}`
|
||||
}
|
||||
|
||||
defineSeparators('workspaceSettings', twoPanelsSeparators)
|
||||
|
||||
async function remove (val: Association | Data<Association> | undefined): Promise<void> {
|
||||
@@ -82,7 +132,7 @@
|
||||
</div>
|
||||
|
||||
<div class="flex-col overflow-y-auto">
|
||||
{#each associations as association (association._id)}
|
||||
{#each filtered as association (association._id)}
|
||||
<button
|
||||
class="list-item"
|
||||
class:selected={selected === association}
|
||||
@@ -90,7 +140,9 @@
|
||||
selected = association
|
||||
}}
|
||||
>
|
||||
<span class="font-regular-14 overflow-label">{association.nameA} - {association.nameB}</span>
|
||||
{#await getLabel(association) then label}
|
||||
<span class="font-regular-14 overflow-label">{label}</span>
|
||||
{/await}
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
@@ -98,6 +150,8 @@
|
||||
<Separator name={'workspaceSettings'} index={0} color={'var(--theme-divider-color)'} />
|
||||
{#if selected !== undefined}
|
||||
<AssociationEditor
|
||||
{exclude}
|
||||
{_classes}
|
||||
association={selected}
|
||||
on:close={() => {
|
||||
selected = undefined
|
||||
|
||||
@@ -103,5 +103,5 @@
|
||||
{:else if kind === 'content' && category === undefined}
|
||||
<div class="hulyComponent" />
|
||||
{:else if category}
|
||||
<Component is={category.component} props={{ kind: 'content' }} on:change />
|
||||
<Component is={category.component} props={{ kind: 'content', ...category.props }} on:change />
|
||||
{/if}
|
||||
|
||||
@@ -63,6 +63,8 @@ import WorkspaceSettings from './components/WorkspaceSettings.svelte'
|
||||
import SettingsWidget from './components/SettingsWidget.svelte'
|
||||
import ClassHierarchy from './components/ClassHierarchy.svelte'
|
||||
import CreateAttributePopup from './components/CreateAttributePopup.svelte'
|
||||
import CreateRelation from './components/CreateRelation.svelte'
|
||||
import EditRelation from './components/EditRelation.svelte'
|
||||
import setting from './plugin'
|
||||
import { filterDescendants, getOwnerFirstName, getOwnerLastName, getOwnerPosition, getValue } from './utils'
|
||||
|
||||
@@ -137,7 +139,9 @@ export default async (): Promise<Resources> => ({
|
||||
RoleEditor,
|
||||
RoleAssignmentEditor,
|
||||
SettingsWidget,
|
||||
CreateAttributePopup
|
||||
CreateAttributePopup,
|
||||
CreateRelation,
|
||||
EditRelation
|
||||
},
|
||||
actionImpl: {
|
||||
DeleteMixin
|
||||
|
||||
@@ -169,7 +169,9 @@ export default plugin(settingId, {
|
||||
RoleAssignmentEditor: '' as AnyComponent,
|
||||
RelationSetting: '' as AnyComponent,
|
||||
Backup: '' as AnyComponent,
|
||||
CreateAttributePopup: '' as AnyComponent
|
||||
CreateAttributePopup: '' as AnyComponent,
|
||||
CreateRelation: '' as AnyComponent,
|
||||
EditRelation: '' as AnyComponent
|
||||
},
|
||||
string: {
|
||||
Settings: '' as IntlString,
|
||||
|
||||
@@ -35,6 +35,8 @@
|
||||
|
||||
export let selection: number | undefined = undefined
|
||||
|
||||
export let onContextMenu: ((ev: MouseEvent, object: Doc) => void) | undefined
|
||||
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
|
||||
@@ -62,8 +64,13 @@
|
||||
|
||||
const showContextMenu = async (ev: MouseEvent, object: Doc, row: number): Promise<void> => {
|
||||
selection = row
|
||||
const items = object
|
||||
showMenu(ev, { object: items, baseMenuClass })
|
||||
ev.stopPropagation()
|
||||
ev.preventDefault()
|
||||
if (onContextMenu !== undefined) {
|
||||
onContextMenu(ev, object)
|
||||
} else {
|
||||
showMenu(ev, { object, baseMenuClass })
|
||||
}
|
||||
}
|
||||
|
||||
function onRow (object: Doc): void {
|
||||
|
||||
@@ -119,33 +119,6 @@
|
||||
position: relative;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
|
||||
&.show::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 0.25rem;
|
||||
opacity: 0.95;
|
||||
}
|
||||
&.show::after {
|
||||
content: attr(data-tooltip);
|
||||
position: absolute;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
min-width: 0;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: calc(100% - 0.5rem);
|
||||
text-align: center;
|
||||
font-size: 0.75rem;
|
||||
color: var(--theme-content-color);
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
}
|
||||
|
||||
.editor-container {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { IntlString } from '@hcengineering/platform'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { Button, IconAdd, Label, Section, showPopup } from '@hcengineering/ui'
|
||||
import { showMenu } from '../actions'
|
||||
import view, { Viewlet, ViewletPreference } from '@hcengineering/view'
|
||||
import DocTable from './DocTable.svelte'
|
||||
import ObjectBoxPopup from './ObjectBoxPopup.svelte'
|
||||
@@ -14,6 +15,8 @@
|
||||
export let readonly: boolean = false
|
||||
export let direction: 'A' | 'B'
|
||||
|
||||
const client = getClient()
|
||||
|
||||
$: _class = direction === 'B' ? association.classB : association.classA
|
||||
|
||||
function add (): void {
|
||||
@@ -76,6 +79,17 @@
|
||||
}
|
||||
|
||||
$: config = preference?.config ?? viewlet?.config
|
||||
|
||||
async function onContextMenu (ev: MouseEvent, doc: Doc): Promise<void> {
|
||||
const q =
|
||||
direction === 'A'
|
||||
? { docA: object._id, docB: doc._id, association: association._id }
|
||||
: { docA: doc._id, docB: object._id, association: association._id }
|
||||
const relation = await client.findOne(core.class.Relation, q)
|
||||
if (relation !== undefined) {
|
||||
showMenu(ev, { object: relation, includedActions: [view.action.Delete] })
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Section {label}>
|
||||
@@ -90,9 +104,7 @@
|
||||
<svelte:fragment slot="content">
|
||||
<div class="antiSection-empty solid flex-col mt-3">
|
||||
{#if docs?.length > 0 && config != null}
|
||||
<div class="self-start flex-col flex-gap-2">
|
||||
<DocTable objects={docs} {_class} {config} />
|
||||
</div>
|
||||
<DocTable objects={docs} {_class} {config} {onContextMenu} />
|
||||
{:else if !readonly}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
|
||||
@@ -19,13 +19,13 @@
|
||||
import { Button, IconAdd, eventToHTMLElement, getCurrentLocation, showPopup } from '@hcengineering/ui'
|
||||
import { Filter, FilteredView, ViewOptions, Viewlet } from '@hcengineering/view'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { filterStore, removeFilter, updateFilter, selectedFilterStore } from '../../filter'
|
||||
import { filterStore, removeFilter, selectedFilterStore, updateFilter } from '../../filter'
|
||||
import view from '../../plugin'
|
||||
import { activeViewlet, getActiveViewletId, makeViewletKey } from '../../utils'
|
||||
import { getViewOptions, viewOptionStore } from '../../viewOptions'
|
||||
import FilterSave from './FilterSave.svelte'
|
||||
import FilterSection from './FilterSection.svelte'
|
||||
import FilterTypePopup from './FilterTypePopup.svelte'
|
||||
import { activeViewlet, getActiveViewletId, makeViewletKey } from '../../utils'
|
||||
import { getViewOptions, viewOptionStore } from '../../viewOptions'
|
||||
|
||||
export let _class: Ref<Class<Doc>> | undefined
|
||||
export let space: Ref<Space> | undefined
|
||||
@@ -145,11 +145,9 @@
|
||||
|
||||
$: makeQuery(query, $filterStore)
|
||||
|
||||
let clazz: Class<Doc<Space>>
|
||||
let visible: boolean = false
|
||||
$: if (_class) {
|
||||
clazz = hierarchy.getClass(_class)
|
||||
visible = hierarchy.hasMixin(clazz, view.mixin.ClassFilters)
|
||||
visible = hierarchy.classHierarchyMixin(_class, view.mixin.ClassFilters) !== undefined
|
||||
}
|
||||
|
||||
const me = getCurrentAccount()._id
|
||||
|
||||
@@ -81,8 +81,7 @@
|
||||
export let visible: boolean = false
|
||||
$: {
|
||||
if (_class) {
|
||||
const clazz = hierarchy.getClass(_class)
|
||||
visible = hierarchy.hasMixin(clazz, view.mixin.ClassFilters)
|
||||
visible = hierarchy.classHierarchyMixin(_class, view.mixin.ClassFilters) !== undefined
|
||||
} else visible = false
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -37,8 +37,7 @@ export default mergeIds(viewId, view, {
|
||||
LabelYes: '' as IntlString,
|
||||
LabelNo: '' as IntlString,
|
||||
ChooseAColor: '' as IntlString,
|
||||
DeleteObject: '' as IntlString,
|
||||
DeleteObjectConfirm: '' as IntlString,
|
||||
|
||||
DeletePopupNoPermissionTitle: '' as IntlString,
|
||||
DeletePopupNoPermissionLabel: '' as IntlString,
|
||||
DeletePopupCreatorLabel: '' as IntlString,
|
||||
|
||||
@@ -214,7 +214,9 @@ const view = plugin(viewId, {
|
||||
Leave: '' as IntlString,
|
||||
Copied: '' as IntlString,
|
||||
And: '' as IntlString,
|
||||
Title: '' as IntlString
|
||||
Title: '' as IntlString,
|
||||
DeleteObject: '' as IntlString,
|
||||
DeleteObjectConfirm: '' as IntlString
|
||||
},
|
||||
icon: {
|
||||
Table: '' as Asset,
|
||||
|
||||
@@ -146,7 +146,6 @@
|
||||
return special.checkIsDisabled && (await (await getResource(special.checkIsDisabled))())
|
||||
}
|
||||
|
||||
let savedMenu: boolean = false
|
||||
let menuSelection: boolean = false
|
||||
</script>
|
||||
|
||||
@@ -173,11 +172,7 @@
|
||||
{/if}
|
||||
<div class="min-h-3 flex-no-shrink" />
|
||||
|
||||
<SavedView
|
||||
{currentApplication}
|
||||
on:shown={(res) => (savedMenu = res.detail)}
|
||||
on:select={(res) => (menuSelection = res.detail)}
|
||||
/>
|
||||
<SavedView alias={currentApplication?.alias} on:select={(res) => (menuSelection = res.detail)} />
|
||||
{#if starred.length}
|
||||
<StarredNav
|
||||
label={preference.string.Starred}
|
||||
|
||||
@@ -32,13 +32,12 @@
|
||||
setViewOptions,
|
||||
viewOptionStore
|
||||
} from '@hcengineering/view-resources'
|
||||
import { Application } from '@hcengineering/workbench'
|
||||
import copy from 'fast-copy'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import TodoCheck from './icons/TodoCheck.svelte'
|
||||
import TodoUncheck from './icons/TodoUncheck.svelte'
|
||||
|
||||
export let currentApplication: Application | undefined
|
||||
export let alias: string | undefined
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const client = getClient()
|
||||
@@ -47,23 +46,19 @@
|
||||
const filteredViewsQuery = createQuery()
|
||||
let availableFilteredViews: FilteredView[] = []
|
||||
let myFilteredViews: FilteredView[] = []
|
||||
$: if (currentApplication?.alias !== undefined) {
|
||||
filteredViewsQuery.query<FilteredView>(
|
||||
view.class.FilteredView,
|
||||
{ attachedTo: currentApplication?.alias },
|
||||
(result) => {
|
||||
myFilteredViews = result.filter((p) => p.users.includes(me))
|
||||
availableFilteredViews = result.filter((p) => p.sharable && !p.users.includes(me))
|
||||
$: if (alias !== undefined) {
|
||||
filteredViewsQuery.query<FilteredView>(view.class.FilteredView, { attachedTo: alias }, (result) => {
|
||||
myFilteredViews = result.filter((p) => p.users.includes(me))
|
||||
availableFilteredViews = result.filter((p) => p.sharable && !p.users.includes(me))
|
||||
|
||||
const location = getLocation()
|
||||
if (location.query?.filterViewId) {
|
||||
const targetView = result.find((view) => view._id === location.query?.filterViewId)
|
||||
if (targetView) {
|
||||
load(targetView)
|
||||
}
|
||||
const location = getLocation()
|
||||
if (location.query?.filterViewId) {
|
||||
const targetView = result.find((view) => view._id === location.query?.filterViewId)
|
||||
if (targetView) {
|
||||
load(targetView)
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
} else {
|
||||
filteredViewsQuery.unsubscribe()
|
||||
myFilteredViews = []
|
||||
|
||||
@@ -40,6 +40,7 @@ export { default as TreeSeparator } from './components/navigator/TreeSeparator.s
|
||||
export { default as WorkbenchTabs } from './components/WorkbenchTabs.svelte'
|
||||
export { default as AppItem } from './components/AppItem.svelte'
|
||||
export { default as ComponentNavigator } from './components/ComponentNavigator.svelte'
|
||||
export { default as SavedView } from './components/SavedView.svelte'
|
||||
|
||||
export { SpecialView }
|
||||
|
||||
|
||||
@@ -2260,6 +2260,21 @@
|
||||
"packageName": "@hcengineering/green",
|
||||
"projectFolder": "pods/green",
|
||||
"shouldPublish": false
|
||||
},
|
||||
{
|
||||
"packageName": "@hcengineering/server-card",
|
||||
"projectFolder": "server-plugins/card",
|
||||
"shouldPublish": false
|
||||
},
|
||||
{
|
||||
"packageName": "@hcengineering/server-card-resources",
|
||||
"projectFolder": "server-plugins/card-resources",
|
||||
"shouldPublish": false
|
||||
},
|
||||
{
|
||||
"packageName": "@hcengineering/model-server-card",
|
||||
"projectFolder": "models/server-card",
|
||||
"shouldPublish": false
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
extends: ['./node_modules/@hcengineering/platform-rig/profiles/default/eslint.config.json'],
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: './tsconfig.json'
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
*
|
||||
!/lib/**
|
||||
!CHANGELOG.md
|
||||
/lib/**/__tests__/
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
|
||||
"rigPackageName": "@hcengineering/platform-rig"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'],
|
||||
roots: ["./src"],
|
||||
coverageReporters: ["text-summary", "html"]
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"name": "@hcengineering/server-card-resources",
|
||||
"version": "0.6.0",
|
||||
"main": "lib/index.js",
|
||||
"svelte": "src/index.ts",
|
||||
"types": "types/index.d.ts",
|
||||
"files": [
|
||||
"lib/**/*",
|
||||
"types/**/*",
|
||||
"tsconfig.json"
|
||||
],
|
||||
"author": "Anticrm Platform Contributors",
|
||||
"license": "EPL-2.0",
|
||||
"scripts": {
|
||||
"build": "compile",
|
||||
"build:watch": "compile",
|
||||
"format": "format src",
|
||||
"test": "jest --passWithNoTests --silent",
|
||||
"_phase:build": "compile transpile src",
|
||||
"_phase:test": "jest --passWithNoTests --silent",
|
||||
"_phase:format": "format src",
|
||||
"_phase:validate": "compile validate"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@hcengineering/platform-rig": "^0.6.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.11.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"eslint-plugin-n": "^15.4.0",
|
||||
"eslint": "^8.54.0",
|
||||
"@typescript-eslint/parser": "^6.11.0",
|
||||
"eslint-config-standard-with-typescript": "^40.0.0",
|
||||
"prettier": "^3.1.0",
|
||||
"typescript": "^5.3.3",
|
||||
"jest": "^29.7.0",
|
||||
"ts-jest": "^29.1.1",
|
||||
"@types/jest": "^29.5.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/view": "^0.6.13",
|
||||
"@hcengineering/card": "^0.6.0",
|
||||
"@hcengineering/core": "^0.6.32",
|
||||
"@hcengineering/platform": "^0.6.11",
|
||||
"@hcengineering/server-core": "^0.6.1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
//
|
||||
// Copyright © 2025 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 { AnyAttribute, Tx, TxCreateDoc, TxProcessor } from '@hcengineering/core'
|
||||
import card from '@hcengineering/card'
|
||||
import view from '@hcengineering/view'
|
||||
import { TriggerControl } from '@hcengineering/server-core'
|
||||
|
||||
async function OnAttribute (ctx: TxCreateDoc<AnyAttribute>[], control: TriggerControl): Promise<Tx[]> {
|
||||
const attr = TxProcessor.createDoc2Doc(ctx[0])
|
||||
if (control.hierarchy.isDerived(attr.attributeOf, card.class.Card)) {
|
||||
const desc = control.hierarchy.getDescendants(attr.attributeOf)
|
||||
const res: Tx[] = []
|
||||
for (const des of desc) {
|
||||
const viewlets = control.modelDb.findAllSync(view.class.Viewlet, { attachTo: des })
|
||||
for (const viewlet of viewlets) {
|
||||
viewlet.config.push(attr.name)
|
||||
res.push(
|
||||
control.txFactory.createTxUpdateDoc(viewlet._class, viewlet.space, viewlet._id, {
|
||||
config: viewlet.config
|
||||
})
|
||||
)
|
||||
const prefs = await control.findAll(control.ctx, view.class.ViewletPreference, { attachedTo: viewlet._id })
|
||||
for (const pref of prefs) {
|
||||
res.push(
|
||||
control.txFactory.createTxUpdateDoc(pref._class, pref.space, pref._id, {
|
||||
config: viewlet.config
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||
export default async () => ({
|
||||
trigger: {
|
||||
OnAttribute
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "./node_modules/@hcengineering/platform-rig/profiles/default/tsconfig.json",
|
||||
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./lib",
|
||||
"declarationDir": "./types",
|
||||
"tsBuildInfoFile": ".build/build.tsbuildinfo"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
extends: ['./node_modules/@hcengineering/platform-rig/profiles/default/eslint.config.json'],
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: './tsconfig.json'
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
*
|
||||
!/lib/**
|
||||
!CHANGELOG.md
|
||||
/lib/**/__tests__/
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
|
||||
"rigPackageName": "@hcengineering/platform-rig"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'],
|
||||
roots: ["./src"],
|
||||
coverageReporters: ["text-summary", "html"]
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"name": "@hcengineering/server-card",
|
||||
"version": "0.6.0",
|
||||
"main": "lib/index.js",
|
||||
"svelte": "src/index.ts",
|
||||
"types": "types/index.d.ts",
|
||||
"files": [
|
||||
"lib/**/*",
|
||||
"types/**/*",
|
||||
"tsconfig.json"
|
||||
],
|
||||
"author": "Anticrm Platform Contributors",
|
||||
"license": "EPL-2.0",
|
||||
"scripts": {
|
||||
"build": "compile",
|
||||
"build:watch": "compile",
|
||||
"format": "format src",
|
||||
"test": "jest --passWithNoTests --silent",
|
||||
"_phase:build": "compile transpile src",
|
||||
"_phase:test": "jest --passWithNoTests --silent",
|
||||
"_phase:format": "format src",
|
||||
"_phase:validate": "compile validate"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@hcengineering/platform-rig": "^0.6.0",
|
||||
"@types/node": "~20.11.16",
|
||||
"@typescript-eslint/eslint-plugin": "^6.11.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"eslint-plugin-n": "^15.4.0",
|
||||
"eslint": "^8.54.0",
|
||||
"@typescript-eslint/parser": "^6.11.0",
|
||||
"eslint-config-standard-with-typescript": "^40.0.0",
|
||||
"prettier": "^3.1.0",
|
||||
"typescript": "^5.3.3",
|
||||
"jest": "^29.7.0",
|
||||
"ts-jest": "^29.1.1",
|
||||
"@types/jest": "^29.5.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/core": "^0.6.32",
|
||||
"@hcengineering/server-core": "^0.6.1",
|
||||
"@hcengineering/platform": "^0.6.11"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
//
|
||||
// Copyright © 2025 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 { Plugin, Resource } from '@hcengineering/platform'
|
||||
import { plugin } from '@hcengineering/platform'
|
||||
import type { TriggerFunc } from '@hcengineering/server-core'
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const serverCardId = 'server-card' as Plugin
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export default plugin(serverCardId, {
|
||||
trigger: {
|
||||
OnAttribute: '' as Resource<TriggerFunc>
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "./node_modules/@hcengineering/platform-rig/profiles/default/tsconfig.json",
|
||||
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./lib",
|
||||
"declarationDir": "./types",
|
||||
"tsBuildInfoFile": ".build/build.tsbuildinfo"
|
||||
}
|
||||
}
|
||||
@@ -19,12 +19,9 @@ import plugin, {
|
||||
} from '@hcengineering/server-core'
|
||||
|
||||
export function findSearchPresenter (hierarchy: Hierarchy, _class: Ref<Class<Doc>>): SearchPresenter | undefined {
|
||||
const ancestors = hierarchy.getAncestors(_class).reverse()
|
||||
for (const _class of ancestors) {
|
||||
const searchMixin = hierarchy.classHierarchyMixin(_class, plugin.mixin.SearchPresenter)
|
||||
if (searchMixin !== undefined) {
|
||||
return searchMixin
|
||||
}
|
||||
const searchMixin = hierarchy.classHierarchyMixin(_class, plugin.mixin.SearchPresenter)
|
||||
if (searchMixin !== undefined) {
|
||||
return searchMixin
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
@@ -65,6 +65,8 @@
|
||||
"@hcengineering/server-tracker-resources": "^0.6.0",
|
||||
"@hcengineering/server-tags": "^0.6.0",
|
||||
"@hcengineering/server-tags-resources": "^0.6.0",
|
||||
"@hcengineering/server-card": "^0.6.0",
|
||||
"@hcengineering/server-card-resources": "^0.6.0",
|
||||
"@hcengineering/server-calendar": "^0.6.0",
|
||||
"@hcengineering/server-calendar-resources": "^0.6.0",
|
||||
"@hcengineering/server-github": "^0.6.0",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { addLocation } from '@hcengineering/platform'
|
||||
import { serverActivityId } from '@hcengineering/server-activity'
|
||||
import { serverAttachmentId } from '@hcengineering/server-attachment'
|
||||
import { serverCardId } from '@hcengineering/server-card'
|
||||
import { serverCalendarId } from '@hcengineering/server-calendar'
|
||||
import { serverChunterId } from '@hcengineering/server-chunter'
|
||||
import { serverCollaborationId } from '@hcengineering/server-collaboration'
|
||||
@@ -43,6 +44,7 @@ export function registerServerPlugins (): void {
|
||||
addLocation(serverTaskId, () => import('@hcengineering/server-task-resources'))
|
||||
addLocation(serverTrackerId, () => import('@hcengineering/server-tracker-resources'))
|
||||
addLocation(serverTagsId, () => import('@hcengineering/server-tags-resources'))
|
||||
addLocation(serverCardId, () => import('@hcengineering/server-card-resources'))
|
||||
addLocation(serverCalendarId, () => import('@hcengineering/server-calendar-resources'))
|
||||
addLocation(serverGmailId, () => import('@hcengineering/server-gmail-resources'))
|
||||
addLocation(serverTelegramId, () => import('@hcengineering/server-telegram-resources'))
|
||||
|
||||
Reference in New Issue
Block a user