mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-26 19:44:57 +02:00
Merge branch 'develop' of https://github.com/hcengineering/platform into staging-new
Signed-off-by: Artem Savchenko <armisav@gmail.com>
This commit is contained in:
Generated
+198
-8
@@ -514,6 +514,15 @@ importers:
|
||||
'@rush-temp/importer':
|
||||
specifier: file:./projects/importer.tgz
|
||||
version: file:projects/importer.tgz(@babel/core@7.23.9)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.23.9))(esbuild@0.25.9)(postcss-load-config@4.0.2(postcss@8.5.3)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.15.29)(typescript@5.9.3)))(postcss@8.5.3)(prosemirror-inputrules@1.4.0)(prosemirror-model@1.24.1)(prosemirror-state@1.4.3)(prosemirror-view@1.37.2)(sass@1.93.2)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.15.29)(typescript@5.9.3))
|
||||
'@rush-temp/inbox':
|
||||
specifier: file:./projects/inbox.tgz
|
||||
version: file:projects/inbox.tgz(@babel/core@7.23.9)(@jest/types@29.6.3)(@types/node@22.15.29)(babel-jest@29.7.0(@babel/core@7.23.9))(esbuild@0.25.9)(postcss-load-config@4.0.2(postcss@8.5.3)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.15.29)(typescript@5.9.3)))(postcss@8.5.3)(sass@1.93.2)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.15.29)(typescript@5.9.3))
|
||||
'@rush-temp/inbox-assets':
|
||||
specifier: file:./projects/inbox-assets.tgz
|
||||
version: file:projects/inbox-assets.tgz(@babel/core@7.23.9)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.23.9))(esbuild@0.25.9)(postcss-load-config@4.0.2(postcss@8.5.3)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.15.29)(typescript@5.9.3)))(postcss@8.5.3)(sass@1.93.2)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.15.29)(typescript@5.9.3))
|
||||
'@rush-temp/inbox-resources':
|
||||
specifier: file:./projects/inbox-resources.tgz
|
||||
version: file:projects/inbox-resources.tgz(@babel/core@7.23.9)(@jest/types@29.6.3)(@types/node@22.15.29)(babel-jest@29.7.0(@babel/core@7.23.9))(esbuild@0.25.9)(postcss-load-config@4.0.2(postcss@8.5.3)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.15.29)(typescript@5.9.3)))(postcss@8.5.3)(prosemirror-inputrules@1.4.0)(prosemirror-model@1.24.1)(prosemirror-state@1.4.3)(prosemirror-view@1.37.2)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.15.29)(typescript@5.9.3))
|
||||
'@rush-temp/integration-client':
|
||||
specifier: file:./projects/integration-client.tgz
|
||||
version: file:projects/integration-client.tgz(@babel/core@7.23.9)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.23.9))(encoding@0.1.13)(postcss-load-config@4.0.2(postcss@8.5.3)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.15.29)(typescript@5.9.3)))(postcss@8.5.3)(sass@1.93.2)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.15.29)(typescript@5.9.3))
|
||||
@@ -664,6 +673,9 @@ importers:
|
||||
'@rush-temp/model-huly-mail':
|
||||
specifier: file:./projects/model-huly-mail.tgz
|
||||
version: file:projects/model-huly-mail.tgz(@babel/core@7.23.9)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.23.9))(esbuild@0.25.9)(postcss-load-config@4.0.2(postcss@8.5.3)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.15.29)(typescript@5.9.3)))(postcss@8.5.3)(sass@1.93.2)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.15.29)(typescript@5.9.3))
|
||||
'@rush-temp/model-inbox':
|
||||
specifier: file:./projects/model-inbox.tgz
|
||||
version: file:projects/model-inbox.tgz(@babel/core@7.23.9)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.23.9))(esbuild@0.25.9)(postcss-load-config@4.0.2(postcss@8.5.3)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.15.29)(typescript@5.9.3)))(postcss@8.5.3)(sass@1.93.2)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.15.29)(typescript@5.9.3))
|
||||
'@rush-temp/model-inventory':
|
||||
specifier: file:./projects/model-inventory.tgz
|
||||
version: file:projects/model-inventory.tgz(@babel/core@7.23.9)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.23.9))(esbuild@0.25.9)(postcss-load-config@4.0.2(postcss@8.5.3)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.15.29)(typescript@5.9.3)))(postcss@8.5.3)(sass@1.93.2)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.15.29)(typescript@5.9.3))
|
||||
@@ -4928,7 +4940,7 @@ packages:
|
||||
version: 0.0.0
|
||||
|
||||
'@rush-temp/desktop@file:projects/desktop.tgz':
|
||||
resolution: {integrity: sha512-HiBnbdBc/mg0wlsEUcRw05sKWnlyUnExnNV3vkyEFlnSIN3ROFYSIhEI26+UpXhfa+8t6TTSQx9JK8gmiII0aQ==, tarball: file:projects/desktop.tgz}
|
||||
resolution: {integrity: sha512-OqIIA7HRUTB18L4jdl39LCzh258UAs2Rp3qbua38PAsOV7qmoQC0+xFmtH0aAmaFL8cut6K88/FnbjjtzAYP8A==, tarball: file:projects/desktop.tgz}
|
||||
version: 0.0.0
|
||||
|
||||
'@rush-temp/devmodel-resources@file:projects/devmodel-resources.tgz':
|
||||
@@ -5103,6 +5115,18 @@ packages:
|
||||
resolution: {integrity: sha512-PXqcGrMal8EqtqMeGqvXahFZSbfitShE6UKT5WlIE9hCrNlMv2H13YqSSDoDmYPjmNbUlZ4RlL8z1BtZgC6XxA==, tarball: file:projects/importer.tgz}
|
||||
version: 0.0.0
|
||||
|
||||
'@rush-temp/inbox-assets@file:projects/inbox-assets.tgz':
|
||||
resolution: {integrity: sha512-Fe6/dReQqNS0AhZR3gFEajiP8Qm9CinWX9fSXeYnXbfTMh0ijzxIq9ra8kwsgIjI8bQ2GE09likyzVAa91IW1Q==, tarball: file:projects/inbox-assets.tgz}
|
||||
version: 0.0.0
|
||||
|
||||
'@rush-temp/inbox-resources@file:projects/inbox-resources.tgz':
|
||||
resolution: {integrity: sha512-xszlMc55DpQSp+mfmiY3EM3udDFfhLfWvzre9Px6XMN8Gnt0DcVFm0S3KyR8NKJzV3uVOPHaweXoXmZa2GFvxw==, tarball: file:projects/inbox-resources.tgz}
|
||||
version: 0.0.0
|
||||
|
||||
'@rush-temp/inbox@file:projects/inbox.tgz':
|
||||
resolution: {integrity: sha512-IxHFbCtBvZ8rEL/XqrSuSB+VgL83uYcjbO9OiPetTNWtz7rDmvLyj+UnkXJWtlyTKWn9dKfcsuB3bkHQZkm/fA==, tarball: file:projects/inbox.tgz}
|
||||
version: 0.0.0
|
||||
|
||||
'@rush-temp/integration-client@file:projects/integration-client.tgz':
|
||||
resolution: {integrity: sha512-p3uOUY9iHR+qtaBa8CNxEQc31gsM8dGdKU++quV8XDUAD+hOl+Ikjj8Fh+V1OLA0KXb2wN2Ob8MQUwmHsKPHIQ==, tarball: file:projects/integration-client.tgz}
|
||||
version: 0.0.0
|
||||
@@ -5204,7 +5228,7 @@ packages:
|
||||
version: 0.0.0
|
||||
|
||||
'@rush-temp/model-all@file:projects/model-all.tgz':
|
||||
resolution: {integrity: sha512-h3FPiVybmZEa2IAX89OlWzrRUJYLg5fVLdq2lO3ctmkwK20sbvBdhoLJZfg1YYaeO1NTsUP4j8kyu5DxTxu8og==, tarball: file:projects/model-all.tgz}
|
||||
resolution: {integrity: sha512-DNEVWMrvvqLdwjxdOH270bA0W+mIxoLFBqx49J647HY4E9mmWqqgeMNSLLUXY1WlhpFSok3GyZqVV8AncdBCCQ==, tarball: file:projects/model-all.tgz}
|
||||
version: 0.0.0
|
||||
|
||||
'@rush-temp/model-analytics-collector@file:projects/model-analytics-collector.tgz':
|
||||
@@ -5303,6 +5327,10 @@ packages:
|
||||
resolution: {integrity: sha512-icnJmXV6KKX2gci/n1l8fOZIFPiRpq68/VIlhu2TdknycHtRKDADLSsZGpXaKoUq35tjwMmuZ9ZLF93jzWGSNQ==, tarball: file:projects/model-huly-mail.tgz}
|
||||
version: 0.0.0
|
||||
|
||||
'@rush-temp/model-inbox@file:projects/model-inbox.tgz':
|
||||
resolution: {integrity: sha512-lVBzx78GwVYVjBePMXW/hZ2c88MwaS6g2WGXV+3Sp5O+OoRzKaZ0Uoj6mKOabr4l5pJ/A/KnBFJ50Lh4jVNTZQ==, tarball: file:projects/model-inbox.tgz}
|
||||
version: 0.0.0
|
||||
|
||||
'@rush-temp/model-inventory@file:projects/model-inventory.tgz':
|
||||
resolution: {integrity: sha512-pYB7K/D3aGlYDsxFoqnScmPQr1TiJ3NbLRXNEIzqqI0cp5Fpaz9cqvOuKhscFxDJQ6fujf/NLOdKLHh3FxM3Cg==, tarball: file:projects/model-inventory.tgz}
|
||||
version: 0.0.0
|
||||
@@ -5324,7 +5352,7 @@ packages:
|
||||
version: 0.0.0
|
||||
|
||||
'@rush-temp/model-notification@file:projects/model-notification.tgz':
|
||||
resolution: {integrity: sha512-FKgjGbbY8VKz0bA+//O/OKfEXUhhTMfYMy7fcydejDs0uJ/m1XSaDF0IBkBR9XvDNaXO4v9/IYOmQJTIvAs6Ow==, tarball: file:projects/model-notification.tgz}
|
||||
resolution: {integrity: sha512-uRMC3n/pyGJlDk/GCbSkZrUMklyVhUmcn+tvlqZxpXznr4WSlSDMkY3VfB7064Ydn/K4KX5Wj5BjdxFWowgTdg==, tarball: file:projects/model-notification.tgz}
|
||||
version: 0.0.0
|
||||
|
||||
'@rush-temp/model-preference@file:projects/model-preference.tgz':
|
||||
@@ -5560,7 +5588,7 @@ packages:
|
||||
version: 0.0.0
|
||||
|
||||
'@rush-temp/notification-resources@file:projects/notification-resources.tgz':
|
||||
resolution: {integrity: sha512-k27Ea3pPJEKFqeuWkV4rahl88Uu7xCWeRz/ErVgPctWxIFw0xVGjbrsNyg+ufI+vZp40yaWVMw+tHn44s6VM1A==, tarball: file:projects/notification-resources.tgz}
|
||||
resolution: {integrity: sha512-h0E2s9AO3VTo0SXkqqGMzpZFAasOIuaMJeX5L2FTUylV8fJtS5ONbDmmNssPLwQ+c2JWa5Ok6uXYB5KJx0I56g==, tarball: file:projects/notification-resources.tgz}
|
||||
version: 0.0.0
|
||||
|
||||
'@rush-temp/notification@file:projects/notification.tgz':
|
||||
@@ -5680,7 +5708,7 @@ packages:
|
||||
version: 0.0.0
|
||||
|
||||
'@rush-temp/pod-server@file:projects/pod-server.tgz':
|
||||
resolution: {integrity: sha512-aR9P6JsNdSrHmfvsc3oqgYvdeEhH03KBR/xb0YmaL+Zjcey4erHd4lT0pz5bl8OKQnJKeL9hmuYp+G4PA/J3GQ==, tarball: file:projects/pod-server.tgz}
|
||||
resolution: {integrity: sha512-kQYQpvwBHRxO7a4DXdzC8reqtzPv718JOuilFmULpoEa2KWNBUQKLHN71K3vxtMQwK/+vu7pl8H9jtjskSLySA==, tarball: file:projects/pod-server.tgz}
|
||||
version: 0.0.0
|
||||
|
||||
'@rush-temp/pod-sign@file:projects/pod-sign.tgz':
|
||||
@@ -5756,7 +5784,7 @@ packages:
|
||||
version: 0.0.0
|
||||
|
||||
'@rush-temp/prod@file:projects/prod.tgz':
|
||||
resolution: {integrity: sha512-jk0NWxDQLOtjcbnQVNRPS7/IriZT/UntwqO+yH8RswKec9XKe99w7kjku2ooCIp85qZyeUiT0HxIWrApnsKBUw==, tarball: file:projects/prod.tgz}
|
||||
resolution: {integrity: sha512-cBCJ4o7HyPky1hndFE/lQ/AxXM47a0iC4s8H6ihpgZ17SD3iLJwx5V7Wk9+zmfJK03XuqxE96oUt4EjezIStuw==, tarball: file:projects/prod.tgz}
|
||||
version: 0.0.0
|
||||
|
||||
'@rush-temp/products-assets@file:projects/products-assets.tgz':
|
||||
@@ -5844,7 +5872,7 @@ packages:
|
||||
version: 0.0.0
|
||||
|
||||
'@rush-temp/server-activity-resources@file:projects/server-activity-resources.tgz':
|
||||
resolution: {integrity: sha512-mlvStvUKlViTLzdD2iir0RFdYbMtThsI2SGaSJYQS4xNx6kovGnuNzqNh/zT38g7eol5oNDQOep/yV8z+Y4ztQ==, tarball: file:projects/server-activity-resources.tgz}
|
||||
resolution: {integrity: sha512-m1Ha9wIYVNbZdPtMCTmDjM8wOuMMSOgiykWCZQRWzf6OBtTlM+c04oUZ51bju78JiRCwAicYNcahJe8PNXcCyg==, tarball: file:projects/server-activity-resources.tgz}
|
||||
version: 0.0.0
|
||||
|
||||
'@rush-temp/server-activity@file:projects/server-activity.tgz':
|
||||
@@ -6316,7 +6344,7 @@ packages:
|
||||
version: 0.0.0
|
||||
|
||||
'@rush-temp/workbench-resources@file:projects/workbench-resources.tgz':
|
||||
resolution: {integrity: sha512-bk7Jl+uJ94+3ceG1IJ/PjfDcxsINfvWlp0zIdwhs3Px3F/NQWhznRcI4pnb8CEWYmGWSI2AQhfzqDchC88NJ3g==, tarball: file:projects/workbench-resources.tgz}
|
||||
resolution: {integrity: sha512-JVucJQFm2Dl0k/S5H3fnmMN/29bHklJ6MOCPbsPrrucIr8W59f+Wja5syWfsqvq/tvGgnhX2uCQD1Nz9gs54yg==, tarball: file:projects/workbench-resources.tgz}
|
||||
version: 0.0.0
|
||||
|
||||
'@rush-temp/workbench@file:projects/workbench.tgz':
|
||||
@@ -22292,6 +22320,131 @@ snapshots:
|
||||
- supports-color
|
||||
- ts-node
|
||||
|
||||
'@rush-temp/inbox-assets@file:projects/inbox-assets.tgz(@babel/core@7.23.9)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.23.9))(esbuild@0.25.9)(postcss-load-config@4.0.2(postcss@8.5.3)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.15.29)(typescript@5.9.3)))(postcss@8.5.3)(sass@1.93.2)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.15.29)(typescript@5.9.3))':
|
||||
dependencies:
|
||||
'@hcengineering/platform': 0.7.5
|
||||
'@hcengineering/platform-rig': 0.7.19(@babel/core@7.23.9)(postcss-load-config@4.0.2(postcss@8.5.3)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.15.29)(typescript@5.9.3)))(postcss@8.5.3)(sass@1.93.2)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.15.29)(typescript@5.9.3))
|
||||
'@types/jest': 29.5.12
|
||||
'@types/node': 22.15.29
|
||||
'@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.9.3))(eslint@8.56.0)(typescript@5.9.3)
|
||||
'@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.9.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.9.3))(eslint@8.56.0)(typescript@5.9.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.9.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@22.15.29)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.15.29)(typescript@5.9.3))
|
||||
prettier: 3.6.2
|
||||
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.25.9)(jest@29.7.0(@types/node@22.15.29)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.15.29)(typescript@5.9.3)))(typescript@5.9.3)
|
||||
typescript: 5.9.3
|
||||
transitivePeerDependencies:
|
||||
- '@babel/core'
|
||||
- '@jest/types'
|
||||
- babel-jest
|
||||
- babel-plugin-macros
|
||||
- coffeescript
|
||||
- esbuild
|
||||
- less
|
||||
- node-notifier
|
||||
- postcss
|
||||
- postcss-load-config
|
||||
- pug
|
||||
- sass
|
||||
- stylus
|
||||
- sugarss
|
||||
- supports-color
|
||||
- ts-node
|
||||
|
||||
'@rush-temp/inbox-resources@file:projects/inbox-resources.tgz(@babel/core@7.23.9)(@jest/types@29.6.3)(@types/node@22.15.29)(babel-jest@29.7.0(@babel/core@7.23.9))(esbuild@0.25.9)(postcss-load-config@4.0.2(postcss@8.5.3)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.15.29)(typescript@5.9.3)))(postcss@8.5.3)(prosemirror-inputrules@1.4.0)(prosemirror-model@1.24.1)(prosemirror-state@1.4.3)(prosemirror-view@1.37.2)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.15.29)(typescript@5.9.3))':
|
||||
dependencies:
|
||||
'@hcengineering/analytics': 0.7.5
|
||||
'@hcengineering/communication-shared': 0.7.5
|
||||
'@hcengineering/communication-types': 0.7.5
|
||||
'@hcengineering/core': 0.7.8
|
||||
'@hcengineering/platform': 0.7.5
|
||||
'@hcengineering/platform-rig': 0.7.19(@babel/core@7.23.9)(postcss-load-config@4.0.2(postcss@8.5.3)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.15.29)(typescript@5.9.3)))(postcss@8.5.3)(sass@1.93.2)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.15.29)(typescript@5.9.3))
|
||||
'@hcengineering/rank': 0.7.5
|
||||
'@hcengineering/text': 0.7.5(prosemirror-inputrules@1.4.0)(prosemirror-model@1.24.1)(prosemirror-state@1.4.3)(prosemirror-view@1.37.2)
|
||||
'@hcengineering/text-markdown': 0.7.5
|
||||
'@types/jest': 29.5.12
|
||||
'@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.9.3))(eslint@8.56.0)(typescript@5.9.3)
|
||||
'@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.9.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.9.3))(eslint@8.56.0)(typescript@5.9.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.9.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-plugin-svelte: 2.35.1(eslint@8.56.0)(svelte@4.2.20)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.15.29)(typescript@5.9.3))
|
||||
fast-equals: 5.2.2
|
||||
jest: 29.7.0(@types/node@22.15.29)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.15.29)(typescript@5.9.3))
|
||||
prettier: 3.6.2
|
||||
prettier-plugin-svelte: 3.4.0(prettier@3.6.2)(svelte@4.2.20)
|
||||
sass: 1.93.2
|
||||
svelte: 4.2.20
|
||||
svelte-check: 3.6.9(@babel/core@7.23.9)(postcss-load-config@4.0.2(postcss@8.5.3)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.15.29)(typescript@5.9.3)))(postcss@8.5.3)(sass@1.93.2)(svelte@4.2.20)
|
||||
svelte-eslint-parser: 0.33.1(svelte@4.2.20)
|
||||
svelte-loader: 3.2.0(svelte@4.2.20)
|
||||
svelte-preprocess: 5.1.4(@babel/core@7.23.9)(postcss-load-config@4.0.2(postcss@8.5.3)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.15.29)(typescript@5.9.3)))(postcss@8.5.3)(sass@1.93.2)(svelte@4.2.20)(typescript@5.9.3)
|
||||
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.25.9)(jest@29.7.0(@types/node@22.15.29)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.15.29)(typescript@5.9.3)))(typescript@5.9.3)
|
||||
typescript: 5.9.3
|
||||
transitivePeerDependencies:
|
||||
- '@babel/core'
|
||||
- '@jest/types'
|
||||
- '@types/node'
|
||||
- babel-jest
|
||||
- babel-plugin-macros
|
||||
- coffeescript
|
||||
- esbuild
|
||||
- less
|
||||
- node-notifier
|
||||
- postcss
|
||||
- postcss-load-config
|
||||
- prosemirror-inputrules
|
||||
- prosemirror-model
|
||||
- prosemirror-state
|
||||
- prosemirror-view
|
||||
- pug
|
||||
- stylus
|
||||
- sugarss
|
||||
- supports-color
|
||||
- ts-node
|
||||
|
||||
'@rush-temp/inbox@file:projects/inbox.tgz(@babel/core@7.23.9)(@jest/types@29.6.3)(@types/node@22.15.29)(babel-jest@29.7.0(@babel/core@7.23.9))(esbuild@0.25.9)(postcss-load-config@4.0.2(postcss@8.5.3)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.15.29)(typescript@5.9.3)))(postcss@8.5.3)(sass@1.93.2)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.15.29)(typescript@5.9.3))':
|
||||
dependencies:
|
||||
'@hcengineering/core': 0.7.8
|
||||
'@hcengineering/platform': 0.7.5
|
||||
'@hcengineering/platform-rig': 0.7.19(@babel/core@7.23.9)(postcss-load-config@4.0.2(postcss@8.5.3)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.15.29)(typescript@5.9.3)))(postcss@8.5.3)(sass@1.93.2)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.15.29)(typescript@5.9.3))
|
||||
'@types/jest': 29.5.12
|
||||
'@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.9.3))(eslint@8.56.0)(typescript@5.9.3)
|
||||
'@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.9.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.9.3))(eslint@8.56.0)(typescript@5.9.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.9.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@22.15.29)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.15.29)(typescript@5.9.3))
|
||||
prettier: 3.6.2
|
||||
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.25.9)(jest@29.7.0(@types/node@22.15.29)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.15.29)(typescript@5.9.3)))(typescript@5.9.3)
|
||||
typescript: 5.9.3
|
||||
transitivePeerDependencies:
|
||||
- '@babel/core'
|
||||
- '@jest/types'
|
||||
- '@types/node'
|
||||
- babel-jest
|
||||
- babel-plugin-macros
|
||||
- coffeescript
|
||||
- esbuild
|
||||
- less
|
||||
- node-notifier
|
||||
- postcss
|
||||
- postcss-load-config
|
||||
- pug
|
||||
- sass
|
||||
- stylus
|
||||
- sugarss
|
||||
- supports-color
|
||||
- ts-node
|
||||
|
||||
'@rush-temp/integration-client@file:projects/integration-client.tgz(@babel/core@7.23.9)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.23.9))(encoding@0.1.13)(postcss-load-config@4.0.2(postcss@8.5.3)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.15.29)(typescript@5.9.3)))(postcss@8.5.3)(sass@1.93.2)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.15.29)(typescript@5.9.3))':
|
||||
dependencies:
|
||||
'@hcengineering/account-client': 0.7.6
|
||||
@@ -24261,6 +24414,43 @@ snapshots:
|
||||
- supports-color
|
||||
- ts-node
|
||||
|
||||
'@rush-temp/model-inbox@file:projects/model-inbox.tgz(@babel/core@7.23.9)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.23.9))(esbuild@0.25.9)(postcss-load-config@4.0.2(postcss@8.5.3)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.15.29)(typescript@5.9.3)))(postcss@8.5.3)(sass@1.93.2)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.15.29)(typescript@5.9.3))':
|
||||
dependencies:
|
||||
'@hcengineering/core': 0.7.8
|
||||
'@hcengineering/model': 0.7.5
|
||||
'@hcengineering/platform': 0.7.5
|
||||
'@hcengineering/platform-rig': 0.7.19(@babel/core@7.23.9)(postcss-load-config@4.0.2(postcss@8.5.3)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.15.29)(typescript@5.9.3)))(postcss@8.5.3)(sass@1.93.2)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.15.29)(typescript@5.9.3))
|
||||
'@types/jest': 29.5.12
|
||||
'@types/node': 22.15.29
|
||||
'@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.9.3))(eslint@8.56.0)(typescript@5.9.3)
|
||||
'@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.9.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.9.3))(eslint@8.56.0)(typescript@5.9.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.9.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@22.15.29)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.15.29)(typescript@5.9.3))
|
||||
prettier: 3.6.2
|
||||
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.25.9)(jest@29.7.0(@types/node@22.15.29)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.15.29)(typescript@5.9.3)))(typescript@5.9.3)
|
||||
typescript: 5.9.3
|
||||
transitivePeerDependencies:
|
||||
- '@babel/core'
|
||||
- '@jest/types'
|
||||
- babel-jest
|
||||
- babel-plugin-macros
|
||||
- coffeescript
|
||||
- esbuild
|
||||
- less
|
||||
- node-notifier
|
||||
- postcss
|
||||
- postcss-load-config
|
||||
- pug
|
||||
- sass
|
||||
- stylus
|
||||
- sugarss
|
||||
- supports-color
|
||||
- ts-node
|
||||
|
||||
'@rush-temp/model-inventory@file:projects/model-inventory.tgz(@babel/core@7.23.9)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.23.9))(esbuild@0.25.9)(postcss-load-config@4.0.2(postcss@8.5.3)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.15.29)(typescript@5.9.3)))(postcss@8.5.3)(sass@1.93.2)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.15.29)(typescript@5.9.3))':
|
||||
dependencies:
|
||||
'@hcengineering/core': 0.7.8
|
||||
|
||||
@@ -1 +1 @@
|
||||
"0.7.269"
|
||||
"0.7.278"
|
||||
|
||||
@@ -266,6 +266,9 @@
|
||||
"@hcengineering/ai-assistant": "^0.7.0",
|
||||
"@hcengineering/ai-assistant-assets": "^0.7.0",
|
||||
"@hcengineering/ai-assistant-resources": "^0.7.0",
|
||||
"@hcengineering/inbox": "^0.7.0",
|
||||
"@hcengineering/inbox-assets": "^0.7.0",
|
||||
"@hcengineering/inbox-resources": "^0.7.0",
|
||||
"electron-squirrel-startup": "~1.0.0",
|
||||
"dotenv": "~16.0.0",
|
||||
"electron-context-menu": "^4.0.4",
|
||||
|
||||
@@ -24,7 +24,12 @@
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--bg-body, #ffffff);
|
||||
}
|
||||
|
||||
:root {
|
||||
--bg-body: #ffffff;
|
||||
--bg-secondary: #f3f3f3;
|
||||
--bg-tertiary: #ffffff;
|
||||
--bg-hover: #e8e8e8;
|
||||
@@ -39,6 +44,7 @@
|
||||
}
|
||||
|
||||
[data-theme="theme-dark"] {
|
||||
--bg-body: #1a1928;
|
||||
--bg-secondary: #323233;
|
||||
--bg-tertiary: #252526;
|
||||
--bg-hover: #2a2d2e;
|
||||
@@ -236,4 +242,13 @@
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
|
||||
<% if (typeof isWindows !== 'undefined' && isWindows) { %>
|
||||
<script>
|
||||
const lastUsedTheme = localStorage.getItem('last-used-theme')
|
||||
if (lastUsedTheme != null) {
|
||||
document.body.setAttribute('data-theme', lastUsedTheme)
|
||||
}
|
||||
</script>
|
||||
<% } %>
|
||||
+28
-18
@@ -25,6 +25,7 @@ import {
|
||||
closePopup,
|
||||
createApp,
|
||||
getCurrentResolvedLocation,
|
||||
Menu,
|
||||
navigate,
|
||||
parseLocation,
|
||||
pushRootBarProgressComponent,
|
||||
@@ -33,10 +34,15 @@ import {
|
||||
} from '@hcengineering/ui'
|
||||
import { handleDownloadItem } from '@hcengineering/desktop-downloads'
|
||||
import notification, { notificationId } from '@hcengineering/notification'
|
||||
import { chatId } from '@hcengineering/chat'
|
||||
import { inboxId } from '@hcengineering/inbox'
|
||||
import workbench, { workbenchId, logOut } from '@hcengineering/workbench'
|
||||
import view, { Action, encodeObjectURI } from '@hcengineering/view'
|
||||
import { resolveLocation } from '@hcengineering/notification-resources'
|
||||
import { themeStore, ThemeVariant } from '@hcengineering/theme'
|
||||
import type { Application } from '@hcengineering/workbench'
|
||||
import { isAllowedToRole } from '@hcengineering/workbench-resources'
|
||||
import card from '@hcengineering/card'
|
||||
import communication from '@hcengineering/communication'
|
||||
|
||||
import { isOwnerOrMaintainer, getCurrentAccount, Ref } from '@hcengineering/core'
|
||||
import { configurePlatform } from './platform'
|
||||
@@ -44,9 +50,6 @@ import { setupTitleBarMenu } from './titleBarMenu'
|
||||
import { defineScreenShare, defineGetDisplayMedia } from './screenShare'
|
||||
import { CommandLogout, CommandSelectWorkspace, CommandOpenSettings, CommandOpenInbox, CommandOpenPlanner, CommandOpenOffice, CommandOpenApplication, LaunchApplication, NotificationParams, CommandCloseTab } from './types'
|
||||
import { ipcMainExposed } from './typesUtils'
|
||||
import { themeStore, ThemeVariant } from '@hcengineering/theme'
|
||||
import type { Application } from '@hcengineering/workbench'
|
||||
import { isAllowedToRole } from '@hcengineering/workbench-resources'
|
||||
import { IpcMessage } from './ipcMessages'
|
||||
|
||||
function currentOsIsWindows (): boolean {
|
||||
@@ -82,11 +85,13 @@ window.addEventListener('DOMContentLoaded', () => {
|
||||
}
|
||||
})
|
||||
|
||||
void ipcMain.isOsUsingDarkTheme().then((isDarkTheme) => {
|
||||
menuBar.setTheme(isDarkTheme ? ThemeVariant.Dark : ThemeVariant.Light)
|
||||
}).catch(() => {
|
||||
menuBar.setTheme(ThemeVariant.Light) // fallback
|
||||
})
|
||||
if (menuBar.lastUsedThemeIsUnknown()) {
|
||||
void ipcMain.isOsUsingDarkTheme().then((isDarkTheme) => {
|
||||
menuBar.setTheme(isDarkTheme ? ThemeVariant.Dark : ThemeVariant.Light)
|
||||
}).catch(() => {
|
||||
menuBar.setTheme(ThemeVariant.Light) // fallback
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -194,39 +199,44 @@ window.addEventListener('DOMContentLoaded', () => {
|
||||
navigate(parseLocation(urlObject))
|
||||
}
|
||||
|
||||
function getBasicNotificationPath (notificationParams: NotificationParams): string {
|
||||
return `${workbenchId}/${notificationParams.application ?? notificationId}/${notificationId}`
|
||||
function getBasicNotificationPath (worksapce: string, app: string): string {
|
||||
return `${workbenchId}/${worksapce}/${app}`
|
||||
}
|
||||
|
||||
ipcMain.handleNotificationNavigation((notificationParams: NotificationParams) => {
|
||||
const currentLocation = getCurrentResolvedLocation()
|
||||
const workspace = currentLocation.path[1]
|
||||
// Support for new inbox with cardId (card-based)
|
||||
if (notificationParams.cardId != null) {
|
||||
const currentLocation = getCurrentResolvedLocation()
|
||||
navigateToUrl(`${workbenchId}/${currentLocation.path[1]}/${chatId}/${notificationParams.cardId}`)
|
||||
const objectUri = encodeObjectURI(notificationParams.cardId, card.class.Card)
|
||||
navigateToUrl(`${workbenchId}/${workspace}/${inboxId}/${objectUri}`)
|
||||
return
|
||||
}
|
||||
|
||||
const isCommunicationEnabled = getMetadata(communication.metadata.Enabled) ?? false
|
||||
const app = isCommunicationEnabled ? inboxId : notificationParams.application
|
||||
|
||||
// Support for old inbox with objectId + objectClass (legacy)
|
||||
if (notificationParams.objectId != null && notificationParams.objectClass != null) {
|
||||
const encodedObjectURI = encodeObjectURI(notificationParams.objectId, notificationParams.objectClass)
|
||||
const notificationLocation = {
|
||||
path: [workbenchId, notificationParams.application, notificationId, encodedObjectURI],
|
||||
path: [workbenchId, workspace, app, encodedObjectURI],
|
||||
fragment: undefined,
|
||||
query: {}
|
||||
query: undefined
|
||||
}
|
||||
|
||||
void resolveLocation(notificationLocation).then((resolvedLocation) => {
|
||||
if (resolvedLocation?.loc != null) {
|
||||
navigate(resolvedLocation.loc)
|
||||
} else {
|
||||
navigateToUrl(`${workbenchId}/${notificationParams.application}/${notificationId}/${encodedObjectURI}`)
|
||||
navigateToUrl(`${workbenchId}/${workspace}/${app}/${encodedObjectURI}`)
|
||||
}
|
||||
}).catch(() => {
|
||||
navigateToUrl(getBasicNotificationPath(notificationParams))
|
||||
navigateToUrl(getBasicNotificationPath(workspace, app))
|
||||
})
|
||||
} else {
|
||||
// Fallback to basic notification navigation
|
||||
navigateToUrl(getBasicNotificationPath(notificationParams))
|
||||
navigateToUrl(getBasicNotificationPath(workspace, app))
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ import workbench, { workbenchId } from '@hcengineering/workbench'
|
||||
import desktopPreferences, { defaultNotificationPreference } from '@hcengineering/desktop-preferences'
|
||||
import { activePreferences } from '@hcengineering/desktop-preferences-resources'
|
||||
import { getDisplayInboxData, InboxNotificationsClientImpl } from '@hcengineering/notification-resources'
|
||||
import { chatId } from '@hcengineering/chat'
|
||||
import { inboxId } from '@hcengineering/inbox'
|
||||
import communication from '@hcengineering/communication'
|
||||
import { ipcMainExposed } from './typesUtils'
|
||||
|
||||
@@ -179,7 +179,7 @@ export function configureNotifications (): void {
|
||||
notificationHistory.set(notification.id, notification.created.getTime())
|
||||
electronAPI.sendNotification({
|
||||
silent: !preferences.playSound,
|
||||
application: chatId,
|
||||
application: inboxId,
|
||||
title: notification.content.title,
|
||||
body: `${notification.content.senderName}: ${notification.content.shortText}`,
|
||||
cardId: notification.cardId,
|
||||
|
||||
@@ -76,6 +76,7 @@ import { viewId } from '@hcengineering/view'
|
||||
import workbench, { workbenchId } from '@hcengineering/workbench'
|
||||
import { mailId } from '@hcengineering/mail'
|
||||
import { chatId } from '@hcengineering/chat'
|
||||
import { inboxId } from '@hcengineering/inbox'
|
||||
import { achievementId } from '@hcengineering/achievement'
|
||||
import communication, { communicationId } from '@hcengineering/communication'
|
||||
import { emojiId } from '@hcengineering/emoji'
|
||||
@@ -133,6 +134,7 @@ import '@hcengineering/view-assets'
|
||||
import '@hcengineering/workbench-assets'
|
||||
import '@hcengineering/mail-assets'
|
||||
import '@hcengineering/chat-assets'
|
||||
import '@hcengineering/inbox-assets'
|
||||
import '@hcengineering/achievement-assets'
|
||||
import '@hcengineering/emoji-assets'
|
||||
import '@hcengineering/media-assets'
|
||||
@@ -256,6 +258,7 @@ function configureI18n (): void {
|
||||
addStringsLoader(cardId, async (lang: string) => await import(`@hcengineering/card-assets/lang/${lang}.json`))
|
||||
addStringsLoader(mailId, async (lang: string) => await import(`@hcengineering/mail-assets/lang/${lang}.json`))
|
||||
addStringsLoader(chatId, async (lang: string) => await import(`@hcengineering/chat-assets/lang/${lang}.json`))
|
||||
addStringsLoader(inboxId, async (lang: string) => await import(`@hcengineering/inbox-assets/lang/${lang}.json`))
|
||||
addStringsLoader(processId, async (lang: string) => await import(`@hcengineering/process-assets/lang/${lang}.json`))
|
||||
addStringsLoader(achievementId, async (lang: string) => await import(`@hcengineering/achievement-assets/lang/${lang}.json`))
|
||||
addStringsLoader(communicationId, async (lang: string) => await import(`@hcengineering/communication-assets/lang/${lang}.json`))
|
||||
@@ -433,6 +436,7 @@ export async function configurePlatform (onWorkbenchConnect?: () => Promise<void
|
||||
addLocation(surveyId, () => import(/* webpackChunkName: "survey" */ '@hcengineering/survey-resources'))
|
||||
addLocation(cardId, () => import(/* webpackChunkName: "card" */ '@hcengineering/card-resources'))
|
||||
addLocation(chatId, () => import(/* webpackChunkName: "chat" */ '@hcengineering/chat-resources'))
|
||||
addLocation(inboxId, () => import(/* webpackChunkName: "inbox" */ '@hcengineering/inbox-resources'))
|
||||
addLocation(processId, () => import(/* webpackChunkName: "process" */ '@hcengineering/process-resources'))
|
||||
addLocation(achievementId, () => import(/* webpackChunkName: "achievement" */ '@hcengineering/achievement-resources'))
|
||||
addLocation(communicationId, () => import(/* webpackChunkName: "communication" */ '@hcengineering/communication-resources'))
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
import { IPCMainExposed, MenuBarAction } from './types'
|
||||
import { isMenuBarAction } from './typesUtils'
|
||||
import { TitleBarMenuState } from './titleBarMenuState'
|
||||
import { ThemeVariant, type ThemeVariantType } from '@hcengineering/theme'
|
||||
import { type ThemeVariantType } from '@hcengineering/theme'
|
||||
|
||||
const ToggleMinimizeToTrayAction: MenuBarAction = 'toggle-minimize-to-tray'
|
||||
const ToggleAutoLaunchAction: MenuBarAction = 'toggle-auto-launch'
|
||||
@@ -28,7 +28,7 @@ const LabelAutoLaunchEnabled = '☑ Launch at Login'
|
||||
const LabelAutoLaunchDisabled = '☐ Launch at Login'
|
||||
|
||||
export async function setupTitleBarMenu (ipcMain: IPCMainExposed, root: HTMLElement): Promise<MenuBar> {
|
||||
const themeManager = new ThemeManager(ThemeVariant.Light)
|
||||
const themeManager = new ThemeManager()
|
||||
const minimizeToTrayEnabled = await ipcMain.isMinimizeToTrayEnabled()
|
||||
const isAutoLaunchEnabled = await ipcMain.isAutoLaunchEnabled()
|
||||
const menuManager = new MenuBarManager(root, minimizeToTrayEnabled, isAutoLaunchEnabled)
|
||||
@@ -75,12 +75,19 @@ export async function setupTitleBarMenu (ipcMain: IPCMainExposed, root: HTMLElem
|
||||
}
|
||||
|
||||
export class MenuBar {
|
||||
private static readonly lastUsedThemeKey = 'last-used-theme'
|
||||
|
||||
constructor (private readonly theme: ThemeManager) {
|
||||
}
|
||||
|
||||
public setTheme (theme: ThemeVariantType): void {
|
||||
localStorage.setItem(MenuBar.lastUsedThemeKey, theme)
|
||||
this.theme.setTheme(theme)
|
||||
}
|
||||
|
||||
public lastUsedThemeIsUnknown (): boolean {
|
||||
return localStorage.getItem(MenuBar.lastUsedThemeKey) == null
|
||||
}
|
||||
}
|
||||
|
||||
export function buildHulyApplicationMenu (minimizeToTrayEnabled: boolean, autoLaunchEnabled: boolean): HTMLElement {
|
||||
@@ -130,14 +137,14 @@ export function buildHulyApplicationMenu (minimizeToTrayEnabled: boolean, autoLa
|
||||
}
|
||||
|
||||
class ThemeManager {
|
||||
private readonly domThemeKey = 'data-theme'
|
||||
|
||||
constructor (theme: ThemeVariantType) {
|
||||
this.setTheme(theme)
|
||||
}
|
||||
private static readonly domThemeKey = 'data-theme'
|
||||
|
||||
public setTheme (theme: ThemeVariantType): void {
|
||||
document.body.setAttribute(this.domThemeKey, theme)
|
||||
ThemeManager.setTheme(theme)
|
||||
}
|
||||
|
||||
public static setTheme (theme: ThemeVariantType): void {
|
||||
document.body.setAttribute(ThemeManager.domThemeKey, theme)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -286,6 +286,9 @@
|
||||
"@hcengineering/ai-assistant": "^0.7.0",
|
||||
"@hcengineering/ai-assistant-assets": "^0.7.0",
|
||||
"@hcengineering/ai-assistant-resources": "^0.7.0",
|
||||
"@hcengineering/inbox": "^0.7.0",
|
||||
"@hcengineering/inbox-assets": "^0.7.0",
|
||||
"@hcengineering/inbox-resources": "^0.7.0",
|
||||
"readable-stream": "^4.7.0",
|
||||
"svelte": "^4.2.20"
|
||||
}
|
||||
|
||||
@@ -73,7 +73,8 @@ import { mailId } from '@hcengineering/mail'
|
||||
import { chatId } from '@hcengineering/chat'
|
||||
import github, { githubId } from '@hcengineering/github'
|
||||
import { bitrixId } from '@hcengineering/bitrix'
|
||||
import { achievementId } from '@hcengineering/achievement'
|
||||
import {inboxId} from '@hcengineering/inbox'
|
||||
import {achievementId} from '@hcengineering/achievement'
|
||||
import communication, { communicationId } from '@hcengineering/communication'
|
||||
import { emojiId } from '@hcengineering/emoji'
|
||||
import billingPlugin, { billingId } from '@hcengineering/billing'
|
||||
@@ -129,6 +130,7 @@ import '@hcengineering/media-assets'
|
||||
import '@hcengineering/view-assets'
|
||||
import '@hcengineering/workbench-assets'
|
||||
import '@hcengineering/chat-assets'
|
||||
import '@hcengineering/inbox-assets'
|
||||
import '@hcengineering/mail-assets'
|
||||
import '@hcengineering/github-assets'
|
||||
import '@hcengineering/achievement-assets'
|
||||
@@ -387,6 +389,7 @@ function configureI18n (): void {
|
||||
communicationId,
|
||||
async (lang: string) => await import(`@hcengineering/communication-assets/lang/${lang}.json`)
|
||||
)
|
||||
addStringsLoader(inboxId, async (lang: string) => await import(`@hcengineering/inbox-assets/lang/${lang}.json`))
|
||||
addStringsLoader(emojiId, async (lang: string) => await import(`@hcengineering/emoji-assets/lang/${lang}.json`))
|
||||
addStringsLoader(billingId, async (lang: string) => await import(`@hcengineering/billing-assets/lang/${lang}.json`))
|
||||
addStringsLoader(
|
||||
|
||||
@@ -23,7 +23,7 @@ import {
|
||||
type ViewAction,
|
||||
type ViewActionAvailabilityFunction
|
||||
} from '@hcengineering/view'
|
||||
import { type NotificationGroup, type NotificationType } from '@hcengineering/notification'
|
||||
import { type NotificationGroup } from '@hcengineering/notification'
|
||||
|
||||
export default mergeIds(activityId, activity, {
|
||||
string: {
|
||||
@@ -44,7 +44,6 @@ export default mergeIds(activityId, activity, {
|
||||
ids: {
|
||||
ReactionAddedActivityViewlet: '' as Ref<DocUpdateMessageViewlet>,
|
||||
ActivityNotificationGroup: '' as Ref<NotificationGroup>,
|
||||
AddReactionNotification: '' as Ref<NotificationType>,
|
||||
AddReactionAction: '' as Ref<Action>,
|
||||
SaveForLaterAction: '' as Ref<Action>,
|
||||
RemoveFromLaterAction: '' as Ref<Action>,
|
||||
|
||||
@@ -133,6 +133,7 @@
|
||||
"@hcengineering/model-emoji": "^0.7.0",
|
||||
"@hcengineering/model-billing": "^0.7.0",
|
||||
"@hcengineering/model-huly-mail": "^0.7.0",
|
||||
"@hcengineering/model-ai-assistant": "^0.7.0"
|
||||
"@hcengineering/model-ai-assistant": "^0.7.0",
|
||||
"@hcengineering/model-inbox": "^0.7.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,6 +118,7 @@ import survey, { surveyId, createModel as surveyModel } from '@hcengineering/mod
|
||||
import { presenceId, createModel as presenceModel } from '@hcengineering/model-presence'
|
||||
import chat, { chatId, createModel as chatModel } from '@hcengineering/model-chat'
|
||||
import processes, { processId, createModel as processModel } from '@hcengineering/model-process'
|
||||
import inbox, { createModel as inboxModel, inboxId } from '@hcengineering/model-inbox'
|
||||
import { achievementId, createModel as achievementModel } from '@hcengineering/model-achievement'
|
||||
import { emojiId, createModel as emojiModel } from '@hcengineering/model-emoji'
|
||||
import { billingId, createModel as billingModel } from '@hcengineering/model-billing'
|
||||
@@ -480,6 +481,11 @@ export default function buildModel (): Builder {
|
||||
chatId,
|
||||
{ label: chat.string.Chat, hidden: true, enabled: false, beta: true, classFilter: defaultFilter }
|
||||
],
|
||||
[
|
||||
inboxModel,
|
||||
inboxId,
|
||||
{ label: inbox.string.Inbox, hidden: true, enabled: false, beta: true, classFilter: defaultFilter }
|
||||
],
|
||||
[achievementModel, achievementId],
|
||||
[emojiModel, emojiId],
|
||||
[communicationModel, communicationId],
|
||||
|
||||
@@ -57,6 +57,7 @@ import { surveyOperation } from '@hcengineering/model-survey'
|
||||
import { cardOperation } from '@hcengineering/model-card'
|
||||
import { aiBotId, aiBotOperation } from '@hcengineering/model-ai-bot'
|
||||
import { chatId, chatOperation } from '@hcengineering/model-chat'
|
||||
import { inboxId, inboxOperation } from '@hcengineering/model-inbox'
|
||||
import { processId, processOperation } from '@hcengineering/model-process'
|
||||
import { communicationId, communicationOperation } from '@hcengineering/model-communication'
|
||||
import { recorderId, recorderOperation } from '@hcengineering/model-recorder'
|
||||
@@ -106,6 +107,7 @@ export const migrateOperations: [string, MigrateOperation][] = [
|
||||
['survey', surveyOperation],
|
||||
[aiBotId, aiBotOperation],
|
||||
[chatId, chatOperation],
|
||||
[inboxId, inboxOperation],
|
||||
[processId, processOperation],
|
||||
[communicationId, communicationOperation],
|
||||
[recorderId, recorderOperation]
|
||||
|
||||
@@ -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,7 @@
|
||||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'],
|
||||
roots: ["./src"],
|
||||
coverageReporters: ["text-summary", "html"]
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"name": "@hcengineering/model-inbox",
|
||||
"version": "0.7.0",
|
||||
"main": "lib/index.js",
|
||||
"svelte": "src/index.ts",
|
||||
"types": "types/index.d.ts",
|
||||
"author": "Hardcore Engineering Inc",
|
||||
"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",
|
||||
"_phase:test": "jest --passWithNoTests --silent --forceExit",
|
||||
"test": "jest --passWithNoTests --silent --forceExit"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@hcengineering/platform-rig": "^0.7.19",
|
||||
"@typescript-eslint/eslint-plugin": "^6.21.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.21.0",
|
||||
"eslint-config-standard-with-typescript": "^40.0.0",
|
||||
"prettier": "^3.6.2",
|
||||
"typescript": "^5.9.3",
|
||||
"@types/node": "^22.15.29",
|
||||
"jest": "^29.7.0",
|
||||
"@types/jest": "^29.5.5",
|
||||
"ts-jest": "^29.1.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/card": "^0.7.0",
|
||||
"@hcengineering/inbox": "^0.7.0",
|
||||
"@hcengineering/inbox-resources": "^0.7.0",
|
||||
"@hcengineering/core": "^0.7.8",
|
||||
"@hcengineering/model": "^0.7.5",
|
||||
"@hcengineering/model-core": "^0.7.0",
|
||||
"@hcengineering/model-workbench": "^0.7.0",
|
||||
"@hcengineering/platform": "^0.7.5",
|
||||
"@hcengineering/setting": "^0.7.0",
|
||||
"@hcengineering/ui": "^0.7.0",
|
||||
"@hcengineering/view": "^0.7.0",
|
||||
"@hcengineering/workbench": "^0.7.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
//
|
||||
// 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/model-core'
|
||||
import workbench from '@hcengineering/model-workbench'
|
||||
import { inboxId } from '@hcengineering/inbox'
|
||||
|
||||
import inbox from './plugin'
|
||||
|
||||
export { inboxId } from '@hcengineering/inbox'
|
||||
export { inboxOperation } from './migration'
|
||||
export default inbox
|
||||
|
||||
export function createModel (builder: Builder): void {
|
||||
builder.createDoc(
|
||||
workbench.class.Application,
|
||||
core.space.Model,
|
||||
{
|
||||
label: inbox.string.Inbox,
|
||||
icon: inbox.icon.Inbox,
|
||||
alias: inboxId,
|
||||
hidden: false,
|
||||
component: inbox.component.InboxApplication,
|
||||
position: 'top',
|
||||
order: 100
|
||||
},
|
||||
inbox.app.Inbox
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
//
|
||||
// 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 MigrateOperation, type MigrationClient, type MigrationUpgradeClient } from '@hcengineering/model'
|
||||
|
||||
export const inboxOperation: MigrateOperation = {
|
||||
async migrate (client: MigrationClient, mode): Promise<void> {},
|
||||
async upgrade (state: Map<string, Set<string>>, client: () => Promise<MigrationUpgradeClient>, mode): Promise<void> {}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// 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 { inboxId } from '@hcengineering/inbox'
|
||||
import inbox from '@hcengineering/inbox-resources/src/plugin'
|
||||
import { type Ref } from '@hcengineering/core'
|
||||
import { type Application } from '@hcengineering/model-workbench'
|
||||
import { mergeIds } from '@hcengineering/platform'
|
||||
|
||||
export default mergeIds(inboxId, inbox, {
|
||||
app: {
|
||||
Inbox: '' as Ref<Application>
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"extends": "./node_modules/@hcengineering/platform-rig/profiles/model/tsconfig.json",
|
||||
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./lib",
|
||||
"declarationDir": "./types",
|
||||
"tsBuildInfoFile": ".build/build.tsbuildinfo"
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "lib", "dist", "types", "bundle"]
|
||||
}
|
||||
@@ -39,6 +39,7 @@
|
||||
"@hcengineering/contact": "^0.7.0",
|
||||
"@hcengineering/core": "^0.7.8",
|
||||
"@hcengineering/model": "^0.7.5",
|
||||
"@hcengineering/model-activity": "^0.7.0",
|
||||
"@hcengineering/model-attachment": "^0.7.0",
|
||||
"@hcengineering/model-core": "^0.7.0",
|
||||
"@hcengineering/model-preference": "^0.7.0",
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import activity, { type ActivityMessage } from '@hcengineering/activity'
|
||||
import activity, { type ActivityMessage, type Reaction } from '@hcengineering/activity'
|
||||
import { type PersonSpace } from '@hcengineering/contact'
|
||||
import {
|
||||
AccountRole,
|
||||
@@ -83,7 +83,8 @@ import {
|
||||
type NotificationType,
|
||||
type NotificationTypeSetting,
|
||||
type PushSubscription,
|
||||
type PushSubscriptionKeys
|
||||
type PushSubscriptionKeys,
|
||||
type ReactionInboxNotification
|
||||
} from '@hcengineering/notification'
|
||||
import { type Asset, type IntlString, type Resource } from '@hcengineering/platform'
|
||||
import setting from '@hcengineering/setting'
|
||||
@@ -285,6 +286,17 @@ export class TMentionInboxNotification extends TCommonInboxNotification implemen
|
||||
mentionedInClass!: Ref<Class<Doc>>
|
||||
}
|
||||
|
||||
@Model(notification.class.ReactionInboxNotification, notification.class.CommonInboxNotification)
|
||||
export class TReactionInboxNotification extends TCommonInboxNotification implements ReactionInboxNotification {
|
||||
emoji!: string
|
||||
ref!: Ref<Reaction>
|
||||
@Prop(TypeRef(activity.class.ActivityMessage), core.string.AttachedTo)
|
||||
attachedTo!: Ref<ActivityMessage>
|
||||
|
||||
@Prop(TypeRef(activity.class.ActivityMessage), core.string.AttachedToClass)
|
||||
attachedToClass!: Ref<Class<ActivityMessage>>
|
||||
}
|
||||
|
||||
@Model(notification.class.ActivityNotificationViewlet, core.class.Doc, DOMAIN_MODEL)
|
||||
export class TActivityNotificationViewlet extends TDoc implements ActivityNotificationViewlet {
|
||||
messageMatch!: DocumentQuery<Doc>
|
||||
@@ -358,7 +370,8 @@ export function createModel (builder: Builder): void {
|
||||
TNotificationProvider,
|
||||
TNotificationProviderSetting,
|
||||
TNotificationTypeSetting,
|
||||
TNotificationProviderDefaults
|
||||
TNotificationProviderDefaults,
|
||||
TReactionInboxNotification
|
||||
)
|
||||
|
||||
builder.mixin(notification.class.BrowserNotification, core.class.Class, core.mixin.TransientConfiguration, {
|
||||
@@ -637,14 +650,6 @@ export function createModel (builder: Builder): void {
|
||||
notification.category.Notification
|
||||
)
|
||||
|
||||
builder.createDoc(notification.class.ActivityNotificationViewlet, core.space.Model, {
|
||||
messageMatch: {
|
||||
_class: activity.class.DocUpdateMessage,
|
||||
objectClass: activity.class.Reaction
|
||||
},
|
||||
presenter: notification.component.ReactionNotificationPresenter
|
||||
})
|
||||
|
||||
builder.createDoc(core.class.DomainIndexConfiguration, core.space.Model, {
|
||||
domain: DOMAIN_NOTIFICATION,
|
||||
indexes: [{ keys: { user: 1, archived: 1, space: 1 } }],
|
||||
|
||||
@@ -44,9 +44,12 @@ import notification, {
|
||||
type BrowserNotification,
|
||||
type DocNotifyContext,
|
||||
type InboxNotification,
|
||||
type OldCollaborators
|
||||
type OldCollaborators,
|
||||
type ReactionInboxNotification,
|
||||
type ActivityInboxNotification
|
||||
} from '@hcengineering/notification'
|
||||
import { DOMAIN_PREFERENCE } from '@hcengineering/preference'
|
||||
import activity, { type ActivityMessage, type DocUpdateMessage, type Reaction } from '@hcengineering/activity'
|
||||
|
||||
import {
|
||||
DOMAIN_SPACE,
|
||||
@@ -56,6 +59,7 @@ import {
|
||||
getSocialIdFromOldAccount
|
||||
} from '@hcengineering/model-core'
|
||||
import { DOMAIN_DOC_NOTIFY, DOMAIN_NOTIFICATION, DOMAIN_USER_NOTIFY } from './index'
|
||||
import { DOMAIN_ACTIVITY, DOMAIN_REACTION } from '@hcengineering/model-activity'
|
||||
|
||||
export async function removeNotifications (
|
||||
client: MigrationClient,
|
||||
@@ -311,6 +315,88 @@ async function migrateCollaborators (client: MigrationClient): Promise<void> {
|
||||
client.logger.log('finished processing collaborators ', {})
|
||||
}
|
||||
|
||||
async function migrateReactionNotifications (client: MigrationClient): Promise<void> {
|
||||
const hierarchy = client.hierarchy
|
||||
const iterator = await client.traverse<DocNotifyContext>(DOMAIN_DOC_NOTIFY, {
|
||||
_class: notification.class.DocNotifyContext
|
||||
})
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
const contexts = await iterator.next(500)
|
||||
const res: ReactionInboxNotification[] = []
|
||||
const removeIds: Ref<InboxNotification>[] = []
|
||||
if (contexts == null || contexts.length === 0) break
|
||||
const filtered = contexts.filter((it) => hierarchy.isDerived(it.objectClass, activity.class.ActivityMessage))
|
||||
if (filtered.length === 0) continue
|
||||
|
||||
for (const context of filtered) {
|
||||
const notifications = await client.find<ActivityInboxNotification>(DOMAIN_NOTIFICATION, {
|
||||
docNotifyContext: context._id,
|
||||
_class: notification.class.ActivityInboxNotification,
|
||||
attachedToClass: activity.class.DocUpdateMessage
|
||||
})
|
||||
if (notifications.length === 0) continue
|
||||
const messages = await client.find<DocUpdateMessage>(DOMAIN_ACTIVITY, {
|
||||
_id: { $in: notifications.map((it) => it.attachedTo) as Ref<DocUpdateMessage>[] },
|
||||
_class: activity.class.DocUpdateMessage,
|
||||
objectClass: activity.class.Reaction
|
||||
})
|
||||
const contextMessage = (
|
||||
await client.find<ActivityMessage>(DOMAIN_ACTIVITY, { _id: context.objectId as any })
|
||||
)[0]
|
||||
if (contextMessage == null) continue
|
||||
const newContext = (
|
||||
await client.find<DocNotifyContext>(DOMAIN_DOC_NOTIFY, {
|
||||
user: context.user,
|
||||
objectId: contextMessage.attachedTo
|
||||
})
|
||||
)[0]
|
||||
if (newContext == null) {
|
||||
continue
|
||||
}
|
||||
for (const it of notifications) {
|
||||
const reactionMessage = messages.find((m) => m._id === it.attachedTo)
|
||||
if (reactionMessage == null) continue
|
||||
const emoji =
|
||||
it.data ??
|
||||
(await client.find<Reaction>(DOMAIN_REACTION, { _id: reactionMessage.objectId as Ref<Reaction> }))[0].emoji
|
||||
if (emoji == null || emoji.trim() === '') continue
|
||||
res.push({
|
||||
_id: it._id as Ref<ReactionInboxNotification>,
|
||||
_class: notification.class.ReactionInboxNotification,
|
||||
space: it.space,
|
||||
emoji,
|
||||
user: it.user,
|
||||
docNotifyContext: newContext._id,
|
||||
objectId: newContext.objectId,
|
||||
objectClass: newContext.objectClass,
|
||||
ref: reactionMessage.objectId as Ref<Reaction>,
|
||||
attachedTo: reactionMessage.attachedTo as Ref<ActivityMessage>,
|
||||
attachedToClass: reactionMessage.attachedToClass as Ref<Class<ActivityMessage>>,
|
||||
isViewed: it.isViewed,
|
||||
archived: it.archived,
|
||||
modifiedOn: it.modifiedOn,
|
||||
createdBy: it.createdBy,
|
||||
createdOn: it.createdOn,
|
||||
modifiedBy: it.modifiedBy
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (removeIds.length > 0) {
|
||||
await client.deleteMany(DOMAIN_NOTIFICATION, { _id: { $in: removeIds } })
|
||||
}
|
||||
|
||||
if (res.length > 0) {
|
||||
await client.create(DOMAIN_NOTIFICATION, res)
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrates old accounts to new accounts/social ids.
|
||||
* Should be applied to prodcution directly without applying migrateSocialIdsToAccountUuids
|
||||
@@ -396,6 +482,7 @@ async function migrateAccounts (client: MigrationClient): Promise<void> {
|
||||
client.logger.log('finished processing collaborators ', {})
|
||||
|
||||
client.logger.log('processing notifications fields ', {})
|
||||
|
||||
function chunkArray<T> (array: T[], chunkSize: number): T[][] {
|
||||
const chunks: T[][] = []
|
||||
for (let i = 0; i < array.length; i += chunkSize) {
|
||||
@@ -845,6 +932,11 @@ export const notificationOperation: MigrateOperation = {
|
||||
state: 'migrate-collaborators-v2',
|
||||
mode: 'upgrade',
|
||||
func: migrateCollaborators
|
||||
},
|
||||
{
|
||||
state: 'migrate-reaction-notifications',
|
||||
mode: 'upgrade',
|
||||
func: migrateReactionNotifications
|
||||
}
|
||||
])
|
||||
},
|
||||
|
||||
@@ -315,7 +315,7 @@ export function createModel (builder: Builder): void {
|
||||
},
|
||||
label: process.string.RunProcess,
|
||||
icon: process.icon.Process,
|
||||
input: 'focus',
|
||||
input: 'any',
|
||||
category: view.category.General,
|
||||
target: card.class.Card,
|
||||
context: {
|
||||
@@ -588,10 +588,10 @@ export function createModel (builder: Builder): void {
|
||||
component: process.component.ProcessesSettingSection
|
||||
})
|
||||
|
||||
builder.createDoc(presentation.class.ComponentPointExtension, core.space.Model, {
|
||||
extension: workbench.extensions.WorkbenchExtensions,
|
||||
component: process.component.NotifierExtension
|
||||
})
|
||||
// builder.createDoc(presentation.class.ComponentPointExtension, core.space.Model, {
|
||||
// extension: workbench.extensions.WorkbenchExtensions,
|
||||
// component: process.component.NotifierExtension
|
||||
// })
|
||||
|
||||
builder.createDoc(process.class.UpdateCriteriaComponent, core.space.Model, {
|
||||
category: 'attribute',
|
||||
|
||||
@@ -26,10 +26,6 @@ export { activityServerOperation } from './migration'
|
||||
export { serverActivityId } from '@hcengineering/server-activity'
|
||||
|
||||
export function createModel (builder: Builder): void {
|
||||
builder.mixin(activity.class.Reaction, core.class.Class, serverNotification.mixin.NotificationPresenter, {
|
||||
presenter: serverActivity.function.ReactionNotificationContentProvider
|
||||
})
|
||||
|
||||
builder.mixin(activity.class.DocUpdateMessage, core.class.Class, serverNotification.mixin.TextPresenter, {
|
||||
presenter: serverActivity.function.DocUpdateMessageTextPresenter
|
||||
})
|
||||
|
||||
@@ -593,5 +593,54 @@ describe('drawing module tests', () => {
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe('tools selection', () => {
|
||||
it('select color after panning', () => {
|
||||
const colorsList: ColorsList = [
|
||||
['alpha', new ThemeAwareColor('red', 'yellow')],
|
||||
['beta', new ThemeAwareColor('blue', 'blue')]
|
||||
]
|
||||
|
||||
const toolChangedSpy = jest.fn()
|
||||
|
||||
const initialPenColor: ColorMetaNameOrHex = 'alpha'
|
||||
const systemUnderTest = drawing(drawingPlugInPoint, {
|
||||
colorsList,
|
||||
getCurrentTheme: () => 'theme-light',
|
||||
subscribeOnThemeChange: () => {},
|
||||
readonly: false,
|
||||
imageWidth: 400,
|
||||
imageHeight: 300,
|
||||
commands: [],
|
||||
tool: 'pen',
|
||||
penColor: initialPenColor,
|
||||
toolChanged: toolChangedSpy
|
||||
})
|
||||
|
||||
systemUnderTest.update?.({
|
||||
colorsList,
|
||||
getCurrentTheme: () => 'theme-light',
|
||||
subscribeOnThemeChange: () => {},
|
||||
readonly: false,
|
||||
tool: 'pan',
|
||||
toolChanged: toolChangedSpy
|
||||
})
|
||||
|
||||
expect(toolChangedSpy).toHaveBeenCalledWith('pan')
|
||||
toolChangedSpy.mockClear()
|
||||
|
||||
const newColor: ColorMetaNameOrHex = 'beta'
|
||||
systemUnderTest.update?.({
|
||||
colorsList,
|
||||
getCurrentTheme: () => 'theme-light',
|
||||
subscribeOnThemeChange: () => {},
|
||||
readonly: false,
|
||||
penColor: newColor,
|
||||
toolChanged: toolChangedSpy
|
||||
})
|
||||
|
||||
expect(toolChangedSpy).toHaveBeenCalledWith('pen')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -230,6 +230,7 @@
|
||||
penWidth,
|
||||
eraserWidth,
|
||||
fontSize,
|
||||
enableMiddleMousePanning: false,
|
||||
changingCmdId,
|
||||
cmdAdded: addCommand,
|
||||
cmdChanging: showCommandProps,
|
||||
@@ -240,6 +241,9 @@
|
||||
cmdDeleted: deleteCommand,
|
||||
editorCreated: (editor) => {
|
||||
cmdEditor = editor
|
||||
},
|
||||
toolChanged: (newTool) => {
|
||||
tool = newTool
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -57,6 +57,7 @@ export interface DrawingProps {
|
||||
changingCmdId?: CommandUid
|
||||
personCursorPos?: Point
|
||||
personCursorVisible?: boolean
|
||||
enableMiddleMousePanning?: boolean
|
||||
cmdAdded?: (cmd: DrawingCmd) => void
|
||||
cmdChanging?: (id: CommandUid) => void
|
||||
cmdUnchanged?: (id: CommandUid) => void
|
||||
@@ -67,6 +68,7 @@ export interface DrawingProps {
|
||||
personCursorMoved?: (nodePos: Point) => void
|
||||
panning?: (offset: Point) => void
|
||||
panned?: (offset: Point) => void
|
||||
toolChanged?: (tool: DrawingTool) => void
|
||||
}
|
||||
|
||||
export type DrawingTool = 'pen' | 'erase' | 'pan' | 'text'
|
||||
@@ -81,6 +83,7 @@ type PointStatus = 'last-point' | 'intermediate-point'
|
||||
|
||||
class DrawState {
|
||||
on = false
|
||||
usedBeforePanningTool: DrawingTool | undefined = undefined
|
||||
tool: DrawingTool = 'pen'
|
||||
penColor: ColorMetaNameOrHex = 'alpha'
|
||||
penWidth = 4
|
||||
@@ -373,6 +376,7 @@ export function drawing (
|
||||
resizeObserver.observe(canvas)
|
||||
|
||||
let touchId: number | undefined
|
||||
let isMiddleMousePanning = false
|
||||
|
||||
function findTouch (touches: TouchList, id: number | undefined = touchId): Touch | undefined {
|
||||
for (let i = 0; i < touches.length; i++) {
|
||||
@@ -398,7 +402,8 @@ export function drawing (
|
||||
}
|
||||
const touch = e.changedTouches[0]
|
||||
touchId = touch.identifier
|
||||
drawStart(touchToNodePoint(touch, canvas))
|
||||
const forcePan = false
|
||||
drawStart(touchToNodePoint(touch, canvas), forcePan)
|
||||
}
|
||||
|
||||
canvas.ontouchmove = (e) => {
|
||||
@@ -407,7 +412,8 @@ export function drawing (
|
||||
}
|
||||
const touch = findTouch(e.changedTouches)
|
||||
if (touch !== undefined) {
|
||||
drawContinue(touchToNodePoint(touch, canvas))
|
||||
const forcePan = false
|
||||
drawContinue(touchToNodePoint(touch, canvas), forcePan)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -417,7 +423,8 @@ export function drawing (
|
||||
}
|
||||
const touch = findTouch(e.changedTouches)
|
||||
if (touch !== undefined) {
|
||||
drawEnd(touchToNodePoint(touch, canvas))
|
||||
const forcePan = false
|
||||
drawEnd(touchToNodePoint(touch, canvas), forcePan)
|
||||
}
|
||||
touchId = undefined
|
||||
}
|
||||
@@ -428,12 +435,22 @@ export function drawing (
|
||||
if (readonly) {
|
||||
return
|
||||
}
|
||||
const MiddleMouseButton = 1
|
||||
if (e.button === MiddleMouseButton && props.enableMiddleMousePanning === true) {
|
||||
e.preventDefault()
|
||||
isMiddleMousePanning = true
|
||||
canvas.setPointerCapture(e.pointerId)
|
||||
const forcePan = true
|
||||
drawStart(pointerToNodePoint(e), forcePan)
|
||||
return
|
||||
}
|
||||
if (e.button !== 0) {
|
||||
return
|
||||
}
|
||||
e.preventDefault()
|
||||
canvas.setPointerCapture(e.pointerId)
|
||||
drawStart(pointerToNodePoint(e))
|
||||
const forcePan = false
|
||||
drawStart(pointerToNodePoint(e), forcePan)
|
||||
}
|
||||
|
||||
canvas.onpointermove = (e) => {
|
||||
@@ -441,7 +458,8 @@ export function drawing (
|
||||
return
|
||||
}
|
||||
e.preventDefault()
|
||||
drawContinue(pointerToNodePoint(e))
|
||||
const forcePan = isMiddleMousePanning
|
||||
drawContinue(pointerToNodePoint(e), forcePan)
|
||||
}
|
||||
|
||||
canvas.onpointerup = (e) => {
|
||||
@@ -450,10 +468,23 @@ export function drawing (
|
||||
}
|
||||
e.preventDefault()
|
||||
canvas.releasePointerCapture(e.pointerId)
|
||||
drawEnd(pointerToNodePoint(e))
|
||||
const forcePan = isMiddleMousePanning
|
||||
drawEnd(pointerToNodePoint(e), forcePan)
|
||||
if (e.button === 1) {
|
||||
isMiddleMousePanning = false
|
||||
}
|
||||
}
|
||||
|
||||
canvas.onpointercancel = canvas.onpointerup
|
||||
canvas.onpointercancel = (e) => {
|
||||
if (readonly) {
|
||||
return
|
||||
}
|
||||
e.preventDefault()
|
||||
canvas.releasePointerCapture(e.pointerId)
|
||||
const forcePan = isMiddleMousePanning
|
||||
drawEnd(pointerToNodePoint(e), forcePan)
|
||||
isMiddleMousePanning = false
|
||||
}
|
||||
|
||||
canvas.onpointerenter = () => {
|
||||
if (!readonly && draw.isDrawingTool()) {
|
||||
@@ -472,20 +503,20 @@ export function drawing (
|
||||
return makeMouseScaledPoint(scaled.x, scaled.y)
|
||||
}
|
||||
|
||||
function drawStart (p: NodePoint): void {
|
||||
function drawStart (p: NodePoint, forcePan: boolean): void {
|
||||
const scaledPoint = rescaleWithCss(p)
|
||||
draw.on = true
|
||||
draw.points = []
|
||||
prevPos = scaledPoint
|
||||
if (draw.isDrawingTool()) {
|
||||
if (!forcePan && draw.isDrawingTool()) {
|
||||
draw.addPoint(scaledPoint)
|
||||
}
|
||||
}
|
||||
|
||||
function drawContinue (p: NodePoint): void {
|
||||
function drawContinue (p: NodePoint, forcePan: boolean): void {
|
||||
const scaledPoint = rescaleWithCss(p)
|
||||
|
||||
if (draw.isDrawingTool()) {
|
||||
if (!forcePan && draw.isDrawingTool()) {
|
||||
const cursorSize = draw.cursorWidth()
|
||||
|
||||
const canvasOffsetInParent = offsetInParent(node, canvas)
|
||||
@@ -501,7 +532,7 @@ export function drawing (
|
||||
}
|
||||
}
|
||||
|
||||
if (draw.on && draw.tool === 'pan') {
|
||||
if (draw.on && (draw.tool === 'pan' || forcePan)) {
|
||||
requestAnimationFrame(() => {
|
||||
draw.offset.x += scaledPoint.x - prevPos.x
|
||||
draw.offset.y += scaledPoint.y - prevPos.y
|
||||
@@ -513,22 +544,22 @@ export function drawing (
|
||||
})
|
||||
}
|
||||
|
||||
if (draw.on && draw.tool === 'text') {
|
||||
if (draw.on && draw.tool === 'text' && !forcePan) {
|
||||
prevPos = scaledPoint
|
||||
}
|
||||
|
||||
if (props.pointerMoved !== undefined) {
|
||||
if (props.pointerMoved !== undefined && !forcePan) {
|
||||
props.pointerMoved(draw.mouseToCanvasPoint(scaledPoint))
|
||||
}
|
||||
}
|
||||
|
||||
function drawEnd (p: NodePoint): void {
|
||||
function drawEnd (p: NodePoint, forcePan: boolean): void {
|
||||
const scaledPoint = rescaleWithCss(p)
|
||||
if (draw.on) {
|
||||
if (draw.isDrawingTool()) {
|
||||
if (!forcePan && draw.isDrawingTool()) {
|
||||
draw.drawLine(scaledPoint, 'last-point', props.getCurrentTheme())
|
||||
storeLineCommand()
|
||||
} else if (draw.tool === 'pan') {
|
||||
} else if (draw.tool === 'pan' || forcePan) {
|
||||
props.panned?.(draw.offset)
|
||||
} else if (draw.tool === 'text') {
|
||||
if (liveTextBox !== undefined) {
|
||||
@@ -1024,6 +1055,11 @@ export function drawing (
|
||||
}
|
||||
if (props.tool !== undefined) {
|
||||
if (draw.tool !== props.tool) {
|
||||
if (props.tool === 'pan') {
|
||||
draw.usedBeforePanningTool = draw.tool
|
||||
} else {
|
||||
draw.usedBeforePanningTool = props.tool
|
||||
}
|
||||
draw.tool = props.tool
|
||||
syncToolCursor = true
|
||||
toolChanged = true
|
||||
@@ -1031,6 +1067,10 @@ export function drawing (
|
||||
}
|
||||
if (props.penColor !== undefined) {
|
||||
if (draw.penColor !== props.penColor) {
|
||||
if (draw.tool === 'pan' && draw.usedBeforePanningTool != null) {
|
||||
draw.tool = draw.usedBeforePanningTool
|
||||
toolChanged = true
|
||||
}
|
||||
draw.penColor = props.penColor
|
||||
syncLiveTextBox = true
|
||||
syncToolCursor = true
|
||||
@@ -1084,6 +1124,9 @@ export function drawing (
|
||||
}
|
||||
}
|
||||
|
||||
if (toolChanged) {
|
||||
props.toolChanged?.(draw.tool)
|
||||
}
|
||||
if (syncToolCursor) {
|
||||
updateToolCursor()
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ import core, {
|
||||
type Client,
|
||||
type Collection,
|
||||
type Doc,
|
||||
groupByArrayAsync,
|
||||
groupByArray,
|
||||
type Hierarchy,
|
||||
type Mixin,
|
||||
type Ref,
|
||||
@@ -29,7 +29,6 @@ import view, { type AttributeModel } from '@hcengineering/view'
|
||||
import { getClient, getFiltredKeys } from '@hcengineering/presentation'
|
||||
import {
|
||||
buildRemovedDoc,
|
||||
checkIsObjectRemoved,
|
||||
getAttributePresenter,
|
||||
getDocLinkTitle,
|
||||
hasAttributePresenter
|
||||
@@ -37,7 +36,6 @@ import {
|
||||
import contact, { type Person } from '@hcengineering/contact'
|
||||
import { type IntlString } from '@hcengineering/platform'
|
||||
import { type AnyComponent } from '@hcengineering/ui'
|
||||
import { getPersonRefByPersonId } from '@hcengineering/contact-resources'
|
||||
import activity, {
|
||||
type ActivityMessage,
|
||||
type DisplayActivityMessage,
|
||||
@@ -241,10 +239,10 @@ function wrapMessages (
|
||||
return { toCombine, uncombined }
|
||||
}
|
||||
|
||||
export async function combineActivityMessages (
|
||||
export function combineActivityMessages (
|
||||
messages: ActivityMessage[],
|
||||
sortingOrder: SortingOrder = SortingOrder.Ascending
|
||||
): Promise<DisplayActivityMessage[]> {
|
||||
): DisplayActivityMessage[] {
|
||||
const client = getClient()
|
||||
|
||||
const { uncombined, toCombine } = wrapMessages(client.getHierarchy(), messages)
|
||||
@@ -256,10 +254,7 @@ export async function combineActivityMessages (
|
||||
|
||||
const result: Array<DisplayActivityMessage | undefined> = [...uncombined]
|
||||
|
||||
const groupedByType: Map<string, DocUpdateMessage[]> = await groupByArrayAsync(
|
||||
docUpdateMessages,
|
||||
getDocUpdateMessageKey
|
||||
)
|
||||
const groupedByType: Map<string, DocUpdateMessage[]> = groupByArray(docUpdateMessages, getDocUpdateMessageKey)
|
||||
|
||||
for (const [, groupedMessages] of groupedByType) {
|
||||
const cantMerge = groupedMessages.filter(
|
||||
@@ -280,40 +275,6 @@ export async function combineActivityMessages (
|
||||
result.push(...cantMerge)
|
||||
}
|
||||
|
||||
const viewlets = client.getModel().findAllSync(activity.class.DocUpdateMessageViewlet, {})
|
||||
|
||||
for (const [index, message] of result.entries()) {
|
||||
if (message?._class !== activity.class.DocUpdateMessage) {
|
||||
continue
|
||||
}
|
||||
const docUpdateMessage = message as DocUpdateMessage
|
||||
|
||||
if (
|
||||
docUpdateMessage.action === 'update' &&
|
||||
!hasAttributeModel(client, docUpdateMessage.attributeUpdates, docUpdateMessage.objectClass)
|
||||
) {
|
||||
result[index] = undefined
|
||||
continue
|
||||
}
|
||||
|
||||
const hideIfRemoved = viewlets.some(
|
||||
(viewlet) =>
|
||||
viewlet.action === docUpdateMessage.action &&
|
||||
viewlet.hideIfRemoved === true &&
|
||||
viewlet.objectClass === docUpdateMessage.objectClass
|
||||
)
|
||||
|
||||
if (!hideIfRemoved) {
|
||||
continue
|
||||
}
|
||||
|
||||
const isRemoved = await checkIsObjectRemoved(client, docUpdateMessage.objectId, docUpdateMessage.objectClass)
|
||||
|
||||
if (isRemoved) {
|
||||
result[index] = undefined
|
||||
}
|
||||
}
|
||||
|
||||
return sortActivityMessages(
|
||||
result.filter((msg): msg is DisplayActivityMessage => msg !== undefined),
|
||||
sortingOrder
|
||||
@@ -367,17 +328,21 @@ function groupByTime<T extends ActivityMessage> (messages: T[]): T[][] {
|
||||
return result
|
||||
}
|
||||
|
||||
async function getDocUpdateMessageKey (message: DocUpdateMessage): Promise<string> {
|
||||
const personRef = await getPersonRefByPersonId(message.createdBy as any)
|
||||
|
||||
function getDocUpdateMessageKey (message: DocUpdateMessage): string {
|
||||
if (message.action === 'update') {
|
||||
return [message._class, message.attachedTo, message.action, personRef, getAttributeUpdatesKey(message)].join('_')
|
||||
return [
|
||||
message._class,
|
||||
message.attachedTo,
|
||||
message.action,
|
||||
message.createdBy,
|
||||
getAttributeUpdatesKey(message)
|
||||
].join('_')
|
||||
}
|
||||
|
||||
return [
|
||||
message._class,
|
||||
message.attachedTo,
|
||||
personRef,
|
||||
message.createdBy,
|
||||
message.updateCollection,
|
||||
message.objectId === message.attachedTo
|
||||
].join('_')
|
||||
@@ -579,18 +544,6 @@ export function isActivityMessage (message?: Doc): message is ActivityMessage {
|
||||
return getClient().getHierarchy().isDerived(message._class, activity.class.ActivityMessage)
|
||||
}
|
||||
|
||||
export function isReactionMessage (message?: ActivityMessage): message is DocUpdateMessage {
|
||||
if (message === undefined) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (!isDocUpdateMessage(message)) {
|
||||
return false
|
||||
}
|
||||
|
||||
return message.objectClass === activity.class.Reaction
|
||||
}
|
||||
|
||||
export function isActivityMessageClass (_class?: Ref<Class<Doc>>): boolean {
|
||||
if (_class === undefined) {
|
||||
return false
|
||||
|
||||
@@ -215,17 +215,15 @@
|
||||
|
||||
$: allMessages = sortActivityMessages(messages.concat(refs))
|
||||
|
||||
async function updateActivityMessages (objectId: Ref<Doc>, order: SortingOrder): Promise<void> {
|
||||
function updateActivityMessages (objectId: Ref<Doc>, order: SortingOrder): void {
|
||||
isMessagesLoading = true
|
||||
|
||||
const res = activityMessagesQuery.query(
|
||||
activity.class.ActivityMessage,
|
||||
{ attachedTo: objectId, space: getSpace(object) },
|
||||
(result: ActivityMessage[]) => {
|
||||
void combineActivityMessages(result, order).then((res) => {
|
||||
messages = res
|
||||
isMessagesLoading = false
|
||||
})
|
||||
messages = combineActivityMessages(result, order)
|
||||
isMessagesLoading = false
|
||||
},
|
||||
{
|
||||
sort: {
|
||||
@@ -252,7 +250,7 @@
|
||||
void scrollToMessage(selectedMessageId)
|
||||
}
|
||||
|
||||
$: void updateActivityMessages(object._id, isNewestFirst ? SortingOrder.Descending : SortingOrder.Ascending)
|
||||
$: updateActivityMessages(object._id, isNewestFirst ? SortingOrder.Descending : SortingOrder.Ascending)
|
||||
|
||||
export function editLastMessage (): void {
|
||||
if (isMessagesLoading) return
|
||||
|
||||
@@ -36,6 +36,8 @@
|
||||
export let headerIcon: Asset | undefined = undefined
|
||||
export let header: IntlString | undefined = undefined
|
||||
export let headerParams: Record<string, any> = {}
|
||||
export let color: 'primary' | 'secondary' = 'primary'
|
||||
export let lower = false
|
||||
|
||||
const client = getClient()
|
||||
const limit = 300
|
||||
@@ -132,8 +134,9 @@
|
||||
|
||||
{#if text || intlLabel}
|
||||
<span
|
||||
class="textContent overflow-label font-normal"
|
||||
class="textContent overflow-label font-normal {color}"
|
||||
class:contentOnly={type === 'content-only'}
|
||||
class:lower
|
||||
use:tooltip={{ label: text ? getEmbeddedLabel(getTooltipText(text)) : intlLabel }}
|
||||
>
|
||||
{#if intlLabel}
|
||||
@@ -249,6 +252,9 @@
|
||||
max-height: 1.25rem;
|
||||
color: var(--global-primary-TextColor);
|
||||
|
||||
&.secondary {
|
||||
color: var(--global-secondary-TextColor);
|
||||
}
|
||||
&.contentOnly {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
@@ -64,9 +64,12 @@
|
||||
}
|
||||
|
||||
return (inboxNotificationsByContext?.get(context._id) ?? [])
|
||||
.filter((notification) => {
|
||||
const activityNotifications = notification as ActivityInboxNotification
|
||||
return activityNotifications.attachedToClass !== activity.class.DocUpdateMessage
|
||||
.filter((it) => {
|
||||
const activityNotifications = it as ActivityInboxNotification
|
||||
return (
|
||||
activityNotifications.attachedToClass !== activity.class.DocUpdateMessage &&
|
||||
it._class !== notification.class.ReactionInboxNotification
|
||||
)
|
||||
})
|
||||
.some(({ isViewed }) => !isViewed)
|
||||
}
|
||||
|
||||
+1
@@ -40,6 +40,7 @@
|
||||
{#if previewMixin}
|
||||
<Component
|
||||
is={previewMixin.presenter}
|
||||
showLoading={false}
|
||||
props={{
|
||||
value,
|
||||
type,
|
||||
|
||||
+1
-2
@@ -30,7 +30,6 @@
|
||||
import { getActions, restrictionStore, showMenu } from '@hcengineering/view-resources'
|
||||
|
||||
import { savedMessagesStore } from '../../activity'
|
||||
import { isReactionMessage } from '../../activityMessagesUtils'
|
||||
import { MessageInlineAction } from '../../types'
|
||||
import ActivityMessageActions from '../ActivityMessageActions.svelte'
|
||||
import MessageTimestamp from '../MessageTimestamp.svelte'
|
||||
@@ -276,7 +275,7 @@
|
||||
class:isShort
|
||||
>
|
||||
<ActivityMessageActions
|
||||
message={isReactionMessage(message) ? parentMessage : message}
|
||||
{message}
|
||||
{actions}
|
||||
{withActionMenu}
|
||||
{excludedActions}
|
||||
|
||||
@@ -341,7 +341,8 @@ export default plugin(activityId, {
|
||||
},
|
||||
ids: {
|
||||
AllFilter: '' as Ref<ActivityMessagesFilter>,
|
||||
MentionNotification: '' as Ref<Doc>
|
||||
MentionNotification: '' as Ref<Doc>,
|
||||
AddReactionNotification: '' as Ref<any>
|
||||
},
|
||||
extension: {
|
||||
ActivityEmployeePresenter: '' as ComponentExtensionId
|
||||
|
||||
@@ -119,7 +119,6 @@ async function generateLocation (loc: Location, id: string): Promise<ResolvedLoc
|
||||
}
|
||||
const appComponent = loc.path[0] ?? ''
|
||||
const workspace = loc.path[1] ?? ''
|
||||
const space = doc.space
|
||||
const special = doc._class
|
||||
|
||||
const objectPanel = client.getHierarchy().classHierarchyMixin(doc._class as Ref<Class<Doc>>, view.mixin.ObjectPanel)
|
||||
@@ -131,7 +130,7 @@ async function generateLocation (loc: Location, id: string): Promise<ResolvedLoc
|
||||
fragment: getPanelURI(component, doc._id, doc._class, 'content')
|
||||
},
|
||||
defaultLocation: {
|
||||
path: [appComponent, workspace, cardId, space, special],
|
||||
path: [appComponent, workspace, cardId, 'type', special],
|
||||
fragment: getPanelURI(component, doc._id, doc._class, 'content')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,31 +20,15 @@
|
||||
import chat, { chatId } from '@hcengineering/chat'
|
||||
import { Navigator } from '@hcengineering/card-resources'
|
||||
import communication from '@hcengineering/communication'
|
||||
import { ModernButton, NavGroup } from '@hcengineering/ui'
|
||||
import { NavGroup } from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
import { createNotificationsQuery } from '@hcengineering/presentation'
|
||||
|
||||
import NotifyMarker from './inbox/NotifyMarker.svelte'
|
||||
import NavigationHeader from './NavigationHeader.svelte'
|
||||
import InboxNavigation from './inbox/InboxNavigation.svelte'
|
||||
import InboxHeader from './inbox/InboxHeader.svelte'
|
||||
|
||||
export let card: Card | undefined = undefined
|
||||
export let type: Ref<MasterTag> | undefined = undefined
|
||||
export let special: 'favorites' | 'all' | string | undefined = undefined
|
||||
export let mode: 'chat' | 'inbox' = 'chat'
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let hasNewInboxNotifications: boolean = false
|
||||
|
||||
const notificationCountQuery = createNotificationsQuery()
|
||||
|
||||
notificationCountQuery.query({ read: false, limit: 1 }, (res) => {
|
||||
hasNewInboxNotifications = res.getResult().length > 0
|
||||
})
|
||||
|
||||
function getSpecial (special: 'favorites' | string | undefined): string | undefined {
|
||||
if (special === 'favorites') {
|
||||
return 'favorites'
|
||||
@@ -52,85 +36,52 @@
|
||||
return undefined
|
||||
}
|
||||
|
||||
function handleInboxClick (): void {
|
||||
mode = 'inbox'
|
||||
}
|
||||
|
||||
function handleAllUpdatesClick (): void {
|
||||
dispatch('selectAll')
|
||||
}
|
||||
|
||||
function selectInboxCard (event: CustomEvent): void {
|
||||
const card = event.detail?.card
|
||||
if (card != null) {
|
||||
dispatch('selectCard', card)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if mode === 'inbox'}
|
||||
<InboxHeader bind:mode />
|
||||
<InboxNavigation {card} on:select={selectInboxCard} />
|
||||
{:else}
|
||||
<NavigationHeader label={chat.string.Chat}>
|
||||
<div slot="actions" class="flex-row-center">
|
||||
<ModernButton
|
||||
icon={chat.icon.Inbox}
|
||||
label={chat.string.Inbox}
|
||||
size="small"
|
||||
iconSize="small"
|
||||
on:click={handleInboxClick}
|
||||
>
|
||||
{#if hasNewInboxNotifications}
|
||||
<div class="flex pl-0-5">
|
||||
<NotifyMarker kind="simple" size="xx-small" />
|
||||
</div>
|
||||
{/if}
|
||||
</ModernButton>
|
||||
</div>
|
||||
</NavigationHeader>
|
||||
<div class="navigation-section">
|
||||
<NavGroup
|
||||
_id="all"
|
||||
categoryName="all"
|
||||
type="selectable-header"
|
||||
icon={chat.icon.All}
|
||||
label={chat.string.All}
|
||||
empty={true}
|
||||
selected={special === 'all'}
|
||||
noDivider
|
||||
on:click={handleAllUpdatesClick}
|
||||
/>
|
||||
</div>
|
||||
<Navigator
|
||||
config={{
|
||||
variant: 'cards',
|
||||
types: [cardPlugin.class.Card],
|
||||
savedViews: true,
|
||||
groupBySpace: false,
|
||||
hideEmpty: true,
|
||||
limit: 5,
|
||||
labelFilter: [SubscriptionLabelID],
|
||||
preorder: [
|
||||
{ type: chat.masterTag.Thread, order: 1 },
|
||||
{ type: communication.type.Direct, order: 2 }
|
||||
],
|
||||
fixedTypes: [chat.masterTag.Thread, communication.type.Direct],
|
||||
allowCreate: true,
|
||||
defaultSorting: 'recent',
|
||||
lookback: '2w',
|
||||
showTypeIcon: false,
|
||||
showCardIcon: true
|
||||
}}
|
||||
applicationId={chatId}
|
||||
selectedType={type}
|
||||
selectedCard={card?._id}
|
||||
selectedSpecial={getSpecial(special)}
|
||||
on:selectType
|
||||
on:selectCard
|
||||
on:favorites
|
||||
<div class="navigation-section">
|
||||
<NavGroup
|
||||
_id="all"
|
||||
categoryName="all"
|
||||
type="selectable-header"
|
||||
icon={chat.icon.All}
|
||||
label={chat.string.All}
|
||||
empty={true}
|
||||
selected={special === 'all'}
|
||||
noDivider
|
||||
on:click={handleAllUpdatesClick}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
<Navigator
|
||||
config={{
|
||||
variant: 'cards',
|
||||
types: [cardPlugin.class.Card],
|
||||
savedViews: true,
|
||||
groupBySpace: false,
|
||||
hideEmpty: true,
|
||||
limit: 5,
|
||||
labelFilter: [SubscriptionLabelID],
|
||||
preorder: [
|
||||
{ type: chat.masterTag.Thread, order: 1 },
|
||||
{ type: communication.type.Direct, order: 2 }
|
||||
],
|
||||
fixedTypes: [chat.masterTag.Thread, communication.type.Direct],
|
||||
allowCreate: true,
|
||||
defaultSorting: 'recent',
|
||||
lookback: '2w',
|
||||
showTypeIcon: false,
|
||||
showCardIcon: true
|
||||
}}
|
||||
applicationId={chatId}
|
||||
selectedType={type}
|
||||
selectedCard={card?._id}
|
||||
selectedSpecial={getSpecial(special)}
|
||||
on:selectType
|
||||
on:selectCard
|
||||
on:favorites
|
||||
/>
|
||||
|
||||
<style lang="scss">
|
||||
.navigation-section {
|
||||
|
||||
@@ -1,181 +0,0 @@
|
||||
<!--
|
||||
// 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 { NotificationContext } from '@hcengineering/communication-types'
|
||||
import { Card } from '@hcengineering/card'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { createNotificationsQuery, getCommunicationClient } from '@hcengineering/presentation'
|
||||
import { CheckBox, Spinner } from '@hcengineering/ui'
|
||||
import { AccountRole, getCurrentAccount } from '@hcengineering/core'
|
||||
|
||||
import InboxNotification from './InboxNotification.svelte'
|
||||
import InboxCardIcon from './InboxCardIcon.svelte'
|
||||
|
||||
export let context: NotificationContext
|
||||
export let card: Card
|
||||
export let selected: boolean = false
|
||||
|
||||
const account = getCurrentAccount()
|
||||
const dispatch = createEventDispatcher()
|
||||
const communicationClient = getCommunicationClient()
|
||||
|
||||
const notificationsQuery = createNotificationsQuery()
|
||||
|
||||
let total = 0
|
||||
|
||||
notificationsQuery.query({ limit: 1, total: true, read: false, strict: true, contextId: context.id }, (res) => {
|
||||
total = res.getTotal()
|
||||
})
|
||||
|
||||
let isRemoving = false
|
||||
async function handleToggle (): Promise<void> {
|
||||
isRemoving = true
|
||||
try {
|
||||
await communicationClient.removeNotificationContext(context.id)
|
||||
} finally {
|
||||
isRemoving = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div class="inbox-card" class:selected on:click={() => dispatch('select', { context, card })}>
|
||||
<div class="inbox-card__header">
|
||||
<InboxCardIcon {card} count={total ?? 0} />
|
||||
<div class="inbox-card__labels">
|
||||
<span class="inbox-card__title overflow-label clear-mins" title={card.title}>
|
||||
{card.title}
|
||||
</span>
|
||||
</div>
|
||||
{#if account.role !== AccountRole.ReadOnlyGuest}
|
||||
<div class="inbox-card__remove">
|
||||
{#if isRemoving}
|
||||
<Spinner size="small" />
|
||||
{:else}
|
||||
<CheckBox kind="todo" size="medium" on:value={handleToggle} />
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="inbox-card__content">
|
||||
<div class="inbox-card__notifications">
|
||||
{#each context.notifications ?? [] as notification}
|
||||
<div class="inbox-card__notification">
|
||||
<div class="inbox-card__marker" />
|
||||
<InboxNotification {notification} {card} />
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.inbox-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
padding: 0.5rem;
|
||||
border-bottom: 1px solid var(--global-ui-BorderColor);
|
||||
min-height: 5.625rem;
|
||||
|
||||
&.selected {
|
||||
background-color: var(--global-ui-highlight-BackgroundColor);
|
||||
}
|
||||
|
||||
&:hover:not(.selected) {
|
||||
background-color: var(--global-ui-highlight-BackgroundColor);
|
||||
}
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-left: var(--spacing-0_5);
|
||||
}
|
||||
|
||||
&__labels {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
min-width: 0;
|
||||
color: var(--global-primary-TextColor);
|
||||
font-size: 0.875rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-weight: 400;
|
||||
color: var(--global-primary-TextColor);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
&__remove {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: auto;
|
||||
min-width: 1.5rem;
|
||||
}
|
||||
|
||||
&__content {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
padding-left: 0.5rem;
|
||||
}
|
||||
|
||||
&__notifications {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
margin-top: var(--spacing-1);
|
||||
}
|
||||
|
||||
&__notification {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
|
||||
&:first-child .inbox-card__marker {
|
||||
border-top-left-radius: 0.5rem;
|
||||
border-top-right-radius: 0.5rem;
|
||||
}
|
||||
|
||||
&:last-child .inbox-card__marker {
|
||||
border-bottom-left-radius: 0.5rem;
|
||||
border-bottom-right-radius: 0.5rem;
|
||||
}
|
||||
|
||||
&:hover .inbox-card__marker {
|
||||
border-radius: 0.5rem;
|
||||
background: var(--global-primary-LinkColor);
|
||||
}
|
||||
}
|
||||
|
||||
&__marker {
|
||||
position: absolute;
|
||||
width: 0.25rem;
|
||||
height: 100%;
|
||||
background: var(--global-ui-highlight-BackgroundColor);
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,68 +0,0 @@
|
||||
<!--
|
||||
// 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, { Card } from '@hcengineering/card'
|
||||
import { Component } from '@hcengineering/ui'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import communication from '@hcengineering/communication'
|
||||
|
||||
import NotifyMarker from './NotifyMarker.svelte'
|
||||
|
||||
export let card: Card
|
||||
export let count: number = 0
|
||||
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
|
||||
$: size = hierarchy.isDerived(card._class, communication.type.Direct) ? 'large' : 'medium'
|
||||
</script>
|
||||
|
||||
<div class="card-icon">
|
||||
<Component is={cardPlugin.component.CardIcon} props={{ value: card, size }} />
|
||||
|
||||
{#if count > 0}
|
||||
<div class="card-icon__marker">
|
||||
<NotifyMarker {count} size="small" />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.card-icon {
|
||||
display: inline-flex;
|
||||
position: relative;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
min-width: 2.5rem;
|
||||
min-height: 2.5rem;
|
||||
color: var(--global-secondary-TextColor);
|
||||
background-color: var(--global-ui-BackgroundColor);
|
||||
border: 1px solid var(--global-subtle-ui-BorderColor);
|
||||
border-radius: var(--medium-BorderRadius);
|
||||
fill: var(--global-secondary-TextColor);
|
||||
|
||||
&__marker {
|
||||
position: absolute;
|
||||
top: -0.375rem;
|
||||
right: 0;
|
||||
transform: translateX(calc(100% - 0.875rem));
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,77 +0,0 @@
|
||||
<!-- 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 { SortingOrder } from '@hcengineering/core'
|
||||
import { IconDelete, IconSettings, Label, ModernButton, showPopup, eventToHTMLElement } from '@hcengineering/ui'
|
||||
import { getCommunicationClient } from '@hcengineering/presentation'
|
||||
|
||||
import chat from '../../plugin'
|
||||
import InboxViewSettings from './InboxViewSettings.svelte'
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
|
||||
const communicationClient = getCommunicationClient()
|
||||
|
||||
export let mode: 'chat' | 'inbox' = 'chat'
|
||||
|
||||
let clearing = false
|
||||
async function clearInbox (): Promise<void> {
|
||||
if (clearing) return
|
||||
try {
|
||||
clearing = true
|
||||
const contexts = await communicationClient.findNotificationContexts({ order: SortingOrder.Ascending })
|
||||
await Promise.all(contexts.map((context) => communicationClient.removeNotificationContext(context.id)))
|
||||
clearing = false
|
||||
} catch (e: any) {
|
||||
clearing = false
|
||||
Analytics.handleError(e)
|
||||
}
|
||||
}
|
||||
|
||||
function click (e: MouseEvent): void {
|
||||
showPopup(InboxViewSettings, {}, eventToHTMLElement(e))
|
||||
}
|
||||
|
||||
function goToChat (): void {
|
||||
mode = 'chat'
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="hulyNavPanel-header withButton small inbox-header">
|
||||
<span class="overflow-label"><Label label={chat.string.Inbox} /></span>
|
||||
<div class="flex-row-center flex-gap-2">
|
||||
<ModernButton
|
||||
tooltip={{ label: chat.string.ClearAll }}
|
||||
icon={IconDelete}
|
||||
size="small"
|
||||
iconSize="small"
|
||||
on:click={clearInbox}
|
||||
loading={clearing}
|
||||
/>
|
||||
<ModernButton icon={IconSettings} on:click={click} size="small" iconSize="small" />
|
||||
<div class="spacer" />
|
||||
<ModernButton
|
||||
label={chat.string.Chat}
|
||||
icon={chat.icon.ChatBubble}
|
||||
size="small"
|
||||
iconSize="small"
|
||||
on:click={goToChat}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.inbox-header {
|
||||
background-color: var(--theme-popup-color);
|
||||
border-bottom: 1px solid var(--theme-navpanel-border);
|
||||
}
|
||||
</style>
|
||||
@@ -1,201 +0,0 @@
|
||||
<!--
|
||||
// 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, { Card } from '@hcengineering/card'
|
||||
import { Label, ListView, Loading, Scroller } from '@hcengineering/ui'
|
||||
import { SortingOrder } from '@hcengineering/core'
|
||||
import { createNotificationContextsQuery, createQuery, getCommunicationClient } from '@hcengineering/presentation'
|
||||
import { NotificationContext, Window } from '@hcengineering/communication-types'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
import InboxCard from './InboxCard.svelte'
|
||||
import chat from '../../plugin'
|
||||
|
||||
export let card: Card | undefined = undefined
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const limit = 20
|
||||
|
||||
let cards: Card[] = []
|
||||
let contexts: NotificationContext[] = []
|
||||
|
||||
const communicationClient = getCommunicationClient()
|
||||
const cardsQuery = createQuery()
|
||||
const query = createNotificationContextsQuery()
|
||||
|
||||
let divScroll: HTMLElement | undefined | null = undefined
|
||||
let contentDiv: HTMLElement | undefined | null = undefined
|
||||
let window: Window<NotificationContext> | undefined = undefined
|
||||
|
||||
let list: ListView
|
||||
let listSelection = 0
|
||||
|
||||
let isLoading = true
|
||||
|
||||
query.query(
|
||||
{
|
||||
notifications: {
|
||||
order: SortingOrder.Descending,
|
||||
limit: 3
|
||||
},
|
||||
order: SortingOrder.Descending,
|
||||
limit
|
||||
},
|
||||
(res) => {
|
||||
window = res
|
||||
contexts = res.getResult().filter((it) => (it.notifications?.length ?? 0) > 0)
|
||||
isLoading = false
|
||||
|
||||
if (contexts.length < limit && window.hasPrevPage()) {
|
||||
void window.loadPrevPage()
|
||||
}
|
||||
},
|
||||
{ message: true }
|
||||
)
|
||||
|
||||
$: cardsQuery.query(cardPlugin.class.Card, { _id: { $in: contexts.map((c) => c.cardId) } }, (res) => {
|
||||
cards = res
|
||||
})
|
||||
|
||||
function handleScroll (): void {
|
||||
if (divScroll != null && window != null && window.hasPrevPage()) {
|
||||
const isAtBottom = divScroll.scrollTop + divScroll.clientHeight >= divScroll.scrollHeight - 10
|
||||
if (isAtBottom) {
|
||||
void window.loadPrevPage()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function onKeydown (key: KeyboardEvent): Promise<void> {
|
||||
if (key.code === 'ArrowUp') {
|
||||
key.stopPropagation()
|
||||
key.preventDefault()
|
||||
list.select(listSelection - 1)
|
||||
}
|
||||
if (key.code === 'ArrowDown') {
|
||||
key.stopPropagation()
|
||||
key.preventDefault()
|
||||
list.select(listSelection + 1)
|
||||
}
|
||||
if (key.code === 'Backspace') {
|
||||
key.preventDefault()
|
||||
key.stopPropagation()
|
||||
const context = contexts[listSelection]
|
||||
if (context != null) {
|
||||
await communicationClient.removeNotificationContext(context.id)
|
||||
}
|
||||
}
|
||||
if (key.code === 'Enter') {
|
||||
key.preventDefault()
|
||||
key.stopPropagation()
|
||||
const context = contexts[listSelection]
|
||||
const card = cards.find((c) => c._id === context.cardId)
|
||||
if (card && context) {
|
||||
dispatch('select', { context, card })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$: if (contentDiv != null) {
|
||||
contentDiv.focus()
|
||||
}
|
||||
|
||||
function getContextKey (index: number): string {
|
||||
const contextId = contexts[index].id
|
||||
return contextId ?? index.toString()
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if contexts.length > 0}
|
||||
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<Scroller bind:divScroll padding="0" shrink onScroll={handleScroll}>
|
||||
<div class="inbox-list" bind:this={contentDiv} tabindex="0" on:keydown={onKeydown}>
|
||||
<ListView
|
||||
bind:this={list}
|
||||
bind:selection={listSelection}
|
||||
count={contexts.length}
|
||||
items={contexts}
|
||||
highlightIndex={contexts.findIndex((it) => it.cardId === card?._id)}
|
||||
noScroll
|
||||
kind="full-size"
|
||||
colorsSchema="lumia"
|
||||
lazy={true}
|
||||
getKey={getContextKey}
|
||||
>
|
||||
<svelte:fragment slot="item" let:item={itemIndex}>
|
||||
{@const context = contexts[itemIndex]}
|
||||
{@const contextCard = cards.find((c) => c._id === context.cardId)}
|
||||
{#if context && contextCard}
|
||||
<InboxCard
|
||||
{context}
|
||||
card={contextCard}
|
||||
on:select={(e) => {
|
||||
dispatch('select', e.detail)
|
||||
listSelection = itemIndex
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</ListView>
|
||||
</div>
|
||||
</Scroller>
|
||||
{:else if isLoading}
|
||||
<div class="placeholder">
|
||||
<Loading />
|
||||
</div>
|
||||
{:else}
|
||||
<div class="placeholder">
|
||||
<div class="placeholder__header">
|
||||
<Label label={chat.string.InboxIsClear} />
|
||||
</div>
|
||||
<span class="mb-4">
|
||||
<Label label={chat.string.YouDontHaveAnyNewMessages} />
|
||||
</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.inbox-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
|
||||
&:focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
:global(.list-item) {
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
display: flex;
|
||||
align-self: center;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
height: inherit;
|
||||
width: 100%;
|
||||
gap: 0.5rem;
|
||||
|
||||
&__header {
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -291,7 +291,7 @@ export class ChannelDataProvider implements IChannelDataProvider {
|
||||
...(this.tailStart !== undefined ? { createdOn: { $gte: this.tailStart } } : {})
|
||||
},
|
||||
async (res) => {
|
||||
const result = await combineActivityMessages(res.reverse())
|
||||
const result = combineActivityMessages(res.reverse())
|
||||
this.tailStore.set(result)
|
||||
|
||||
this.isTailLoaded.set(true)
|
||||
@@ -368,7 +368,7 @@ export class ChannelDataProvider implements IChannelDataProvider {
|
||||
return {
|
||||
from: from.createdOn ?? from.modifiedOn,
|
||||
to: to.createdOn ?? to.modifiedOn,
|
||||
data: isBackward ? await combineActivityMessages(messages.reverse()) : await combineActivityMessages(messages)
|
||||
data: isBackward ? combineActivityMessages(messages.reverse()) : combineActivityMessages(messages)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
export let loadingOverlay: boolean = false
|
||||
export let onScroll: () => void = () => {}
|
||||
export let onResize: () => void = () => {}
|
||||
export let key: string
|
||||
</script>
|
||||
|
||||
{#if loadingOverlay}
|
||||
@@ -41,7 +42,9 @@
|
||||
{onScroll}
|
||||
{onResize}
|
||||
>
|
||||
<slot />
|
||||
{#key key}
|
||||
<slot />
|
||||
{/key}
|
||||
</Scroller>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
@@ -611,6 +611,9 @@
|
||||
editLastMessage()
|
||||
}
|
||||
}
|
||||
function getKey (messages: ActivityMessage[]): string {
|
||||
return `${messages.length}-${Math.max(...messages.map((m) => m.modifiedOn))}`
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex-col relative" class:h-full={fullHeight}>
|
||||
@@ -627,6 +630,7 @@
|
||||
loadingOverlay={$isLoadingStore || !isScrollInitialized}
|
||||
onScroll={handleScroll}
|
||||
onResize={handleResize}
|
||||
key={getKey(messages)}
|
||||
>
|
||||
{#if showBlankView}
|
||||
<BlankView
|
||||
|
||||
@@ -48,17 +48,15 @@
|
||||
</script>
|
||||
|
||||
<BaseMessagePreview text={value.message} message={value} {type} {readonly} {actions} on:click>
|
||||
{#if value.attachments && type === 'full' && !isEmptyMarkup(value.message)}
|
||||
{#if value.attachments && !isEmptyMarkup(value.message)}
|
||||
<div class="attachments" use:tooltip={{ component: AttachmentsTooltip, props: { attachments } }}>
|
||||
{value.attachments}
|
||||
<Icon icon={attachment.icon.Attachment} size="small" />
|
||||
</div>
|
||||
{:else if attachments.length > 0 && isEmptyMarkup(value.message)}
|
||||
<span class="font-normal">
|
||||
<span class="font-normal secondaryColor">
|
||||
<Label label={attachment.string.Attachments} />:
|
||||
<span class="ml-1">
|
||||
{attachments.map(({ name }) => name).join(', ')}
|
||||
</span>
|
||||
{attachments.map(({ name }) => name).join(', ')}
|
||||
</span>
|
||||
{/if}
|
||||
</BaseMessagePreview>
|
||||
@@ -77,4 +75,9 @@
|
||||
color: var(--global-primary-TextColor);
|
||||
}
|
||||
}
|
||||
|
||||
.secondaryColor {
|
||||
color: var(--global-secondary-TextColor);
|
||||
margin-left: -0.5rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
+9
-2
@@ -61,7 +61,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<span class="flex-presenter flex-gap-1 font-semi-bold">
|
||||
<span class="flex-presenter flex-gap-1 header">
|
||||
{#if isThread || (object.replies ?? 0) > 0}
|
||||
<Label label={chunter.string.Thread} />
|
||||
{:else}
|
||||
@@ -84,10 +84,17 @@
|
||||
{/await}
|
||||
{/if}
|
||||
</span>
|
||||
<span class="font-normal">
|
||||
<span class="font-normal mt-1">
|
||||
{#if isThread}
|
||||
<ThreadMessagePreview value={toThread(object)} readonly type="content-only" />
|
||||
{:else}
|
||||
<ChatMessagePreview value={object} readonly type="content-only" />
|
||||
{/if}
|
||||
</span>
|
||||
|
||||
<style lang="scss">
|
||||
.header {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
|
||||
+3
-2
@@ -13,12 +13,13 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { DisplayDocUpdateMessage } from '@hcengineering/activity'
|
||||
import { ActivityMessagePreviewType, DisplayDocUpdateMessage } from '@hcengineering/activity'
|
||||
import { BaseMessagePreview } from '@hcengineering/activity-resources'
|
||||
|
||||
import chunter from '../../plugin'
|
||||
|
||||
export let message: DisplayDocUpdateMessage
|
||||
export let type: ActivityMessagePreviewType = 'full'
|
||||
</script>
|
||||
|
||||
<BaseMessagePreview intlLabel={chunter.string.YouJoinedChannel} {message} on:click />
|
||||
<BaseMessagePreview intlLabel={chunter.string.YouJoinedChannel} {message} {type} on:click />
|
||||
|
||||
+3
-1
@@ -15,8 +15,10 @@
|
||||
<script lang="ts">
|
||||
import { ThreadMessage } from '@hcengineering/chunter'
|
||||
import ThreadMessagePreview from '../threads/ThreadMessagePreview.svelte'
|
||||
import { ActivityMessagePreviewType } from '@hcengineering/activity'
|
||||
|
||||
export let message: ThreadMessage
|
||||
export let type: ActivityMessagePreviewType = 'full'
|
||||
</script>
|
||||
|
||||
<ThreadMessagePreview value={message} on:click />
|
||||
<ThreadMessagePreview value={message} {type} on:click />
|
||||
|
||||
@@ -19,7 +19,6 @@ import activity, {
|
||||
type DisplayDocUpdateMessage,
|
||||
type DocUpdateMessage
|
||||
} from '@hcengineering/activity'
|
||||
import { isReactionMessage } from '@hcengineering/activity-resources'
|
||||
import aiBot from '@hcengineering/ai-bot'
|
||||
import { summarizeMessages as aiSummarizeMessages, translate as aiTranslate } from '@hcengineering/ai-bot-resources'
|
||||
import { type Channel, type ChatMessage, type DirectMessage, type ThreadMessage } from '@hcengineering/chunter'
|
||||
@@ -42,7 +41,8 @@ import notification, { type DocNotifyContext, type InboxNotification } from '@hc
|
||||
import {
|
||||
InboxNotificationsClientImpl,
|
||||
isActivityNotification,
|
||||
isMentionNotification
|
||||
isMentionNotification,
|
||||
isReactionNotification
|
||||
} from '@hcengineering/notification-resources'
|
||||
import { type Asset, getMetadata, translate } from '@hcengineering/platform'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
@@ -414,13 +414,7 @@ export async function readChannelMessages (
|
||||
const notifications = get(inboxClient.activityInboxNotifications)
|
||||
.filter(({ attachedTo, $lookup, isViewed }) => {
|
||||
if (isViewed) return false
|
||||
const includes = allIds.includes(attachedTo)
|
||||
if (includes) return true
|
||||
const msg = $lookup?.attachedTo
|
||||
if (isReactionMessage(msg)) {
|
||||
return allIds.includes(msg.attachedTo as Ref<ActivityMessage>)
|
||||
}
|
||||
return false
|
||||
return allIds.includes(attachedTo)
|
||||
})
|
||||
.map((n) => n._id)
|
||||
|
||||
@@ -428,6 +422,10 @@ export async function readChannelMessages (
|
||||
.filter((n) => !n.isViewed && isMentionNotification(n) && allIds.includes(n.mentionedIn as Ref<ActivityMessage>))
|
||||
.map((n) => n._id)
|
||||
|
||||
const reactionNotifications = get(inboxClient.otherInboxNotifications)
|
||||
.filter((n) => !n.isViewed && isReactionNotification(n) && allIds.includes(n.attachedTo))
|
||||
.map((n) => n._id)
|
||||
|
||||
chatReadMessagesStore.update((store) => new Set([...store, ...allIds]))
|
||||
|
||||
const storedTimestampUpdates = get(contextsTimestampStore).get(context._id)
|
||||
@@ -441,7 +439,7 @@ export async function readChannelMessages (
|
||||
})
|
||||
await op.update(context, { lastViewedTimestamp: newTimestamp })
|
||||
}
|
||||
await inboxClient.readNotifications(op, [...notifications, ...relatedMentions])
|
||||
await inboxClient.readNotifications(op, [...notifications, ...relatedMentions, ...reactionNotifications])
|
||||
} finally {
|
||||
await op.commit()
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
"@types/snappyjs": "^0.7.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/analytics": "^0.7.3",
|
||||
"@hcengineering/analytics": "^0.7.5",
|
||||
"@hcengineering/client": "^0.7.3",
|
||||
"@hcengineering/communication-sdk-types": "^0.7.5",
|
||||
"@hcengineering/communication-types": "^0.7.5",
|
||||
|
||||
@@ -19,9 +19,10 @@
|
||||
import type { Ref } from '@hcengineering/core'
|
||||
import { Blob as PlatformBlob } from '@hcengineering/core'
|
||||
import { Asset } from '@hcengineering/platform'
|
||||
import presentation, { Card, getFileUrl } from '@hcengineering/presentation'
|
||||
import {
|
||||
import presentation, { Card, getFileUrl, uiContext } from '@hcengineering/presentation'
|
||||
import ui, {
|
||||
AnySvelteComponent,
|
||||
Button,
|
||||
ColorDefinition,
|
||||
Label,
|
||||
TabList,
|
||||
@@ -171,6 +172,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
function handleClearClick (): void {
|
||||
selectedAvatar = undefined
|
||||
selectedAvatarType = AvatarType.COLOR
|
||||
selectedAvatarProps = { color: getPlatformAvatarColorForTextDef(name ?? '', $themeStore.dark).name }
|
||||
}
|
||||
|
||||
const showColorPopup = (event: MouseEvent) => {
|
||||
showPopup(
|
||||
ColorsPopup,
|
||||
@@ -238,6 +245,9 @@
|
||||
on:select={handleDropdownSelection}
|
||||
/>
|
||||
</div>
|
||||
<svelte:fragment slot="buttons">
|
||||
<Button minWidth={'5rem'} label={ui.string.Clear} kind={'ghost'} size={'large'} on:click={handleClearClick} />
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="footer">
|
||||
{#if selectedAvatarType === AvatarType.GRAVATAR}
|
||||
<div class="flex-col">
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
extends: ['./node_modules/@hcengineering/platform-rig/profiles/assets/eslint.config.json'],
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: './tsconfig.json'
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
|
||||
<symbol id="inbox" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.5 1.5C8.5 1.22386 8.27614 1 8 1C7.72386 1 7.5 1.22386 7.5 1.5V2.02746C5.25002 2.27619 3.5 4.18372 3.5 6.5V7.44766C3.5 8.01534 3.3068 8.56611 2.95217 9.00939L2.09004 10.0871C1.70809 10.5645 1.5 11.1577 1.5 11.7691C1.5 12.4489 2.05108 13 2.73087 13H6C6 13.2626 6.05173 13.5227 6.15224 13.7654C6.25275 14.008 6.40007 14.2285 6.58579 14.4142C6.7715 14.5999 6.99198 14.7472 7.23463 14.8478C7.47728 14.9483 7.73736 15 8 15C8.26264 15 8.52272 14.9483 8.76537 14.8478C9.00802 14.7472 9.2285 14.5999 9.41421 14.4142C9.59993 14.2285 9.74725 14.008 9.84776 13.7654C9.94827 13.5227 10 13.2626 10 13H13.2691C13.9489 13 14.5 12.4489 14.5 11.7691C14.5 11.1577 14.2919 10.5645 13.91 10.0871L13.0478 9.00939C12.6932 8.56611 12.5 8.01534 12.5 7.44766V6.5C12.5 4.18372 10.75 2.27619 8.5 2.02746V1.5ZM9 13H7C7 13.1313 7.02587 13.2614 7.07612 13.3827C7.12638 13.504 7.20003 13.6142 7.29289 13.7071C7.38575 13.8 7.49599 13.8736 7.61732 13.9239C7.73864 13.9741 7.86868 14 8 14C8.13132 14 8.26136 13.9741 8.38268 13.9239C8.50401 13.8736 8.61425 13.8 8.70711 13.7071C8.79997 13.6142 8.87362 13.504 8.92388 13.3827C8.97413 13.2614 9 13.1313 9 13ZM2.5 11.7691C2.5 11.8966 2.60336 12 2.73087 12H13.2691C13.3966 12 13.5 11.8966 13.5 11.7691C13.5 11.3848 13.3692 11.0119 13.1291 10.7118L12.267 9.63409C11.7705 9.01349 11.5 8.24241 11.5 7.44766V6.5C11.5 4.567 9.933 3 8 3C6.067 3 4.5 4.567 4.5 6.5V7.44766C4.5 8.24241 4.22952 9.01349 3.73304 9.63409L2.87091 10.7118C2.63081 11.0119 2.5 11.3848 2.5 11.7691Z" />
|
||||
</symbol>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
|
||||
"rigPackageName": "@hcengineering/platform-rig",
|
||||
"rigProfile": "assets"
|
||||
}
|
||||
@@ -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,13 @@
|
||||
{
|
||||
"string": {
|
||||
"Inbox": "Doručená pošta",
|
||||
"ReactedToYourMessage": "reagoval na vaši zprávu",
|
||||
"ClearAll": "Vymazať všetko",
|
||||
"InboxIsClear": "Doručená pošta je vymazaná",
|
||||
"YouDontHaveAnyNewMessages": "Nemáte žiadne nové správy",
|
||||
"ReadAll": "Prečítať všetko",
|
||||
"Clearing": "Vymazávam...",
|
||||
"Reading": "Čítam...",
|
||||
"HideUserNames": "Skryť mená používateľov"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"string": {
|
||||
"Inbox": "Posteingang",
|
||||
"ReactedToYourMessage": "reagierte auf Ihre Nachricht",
|
||||
"ClearAll": "Alles löschen",
|
||||
"InboxIsClear": "Posteingang ist leer",
|
||||
"YouDontHaveAnyNewMessages": "Sie haben keine neuen Nachrichten",
|
||||
"ReadAll": "Alle lesen",
|
||||
"Clearing": "Lösche...",
|
||||
"Reading": "Lese...",
|
||||
"HideUserNames": "Benutzernamen ausblenden"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"string": {
|
||||
"Inbox": "Inbox",
|
||||
"ReactedToYourMessage": "reacted to your message",
|
||||
"ClearAll": "Clear all",
|
||||
"InboxIsClear": "Inbox is clear",
|
||||
"YouDontHaveAnyNewMessages": "You don’t have any new messages",
|
||||
"ReadAll": "Read all",
|
||||
"Clearing": "Clearing...",
|
||||
"Reading": "Reading...",
|
||||
"HideUserNames": "Hide user names"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"string": {
|
||||
"Inbox": "Bande",
|
||||
"ReactedToYourMessage": "reaccionó a tu mensaje",
|
||||
"ClearAll": "Borrar todo",
|
||||
"InboxIsClear": "Bande está vacía",
|
||||
"YouDontHaveAnyNewMessages": "No tienes ninguna nueva mensaje",
|
||||
"ReadAll": "Leer todo",
|
||||
"Clearing": "Borrando...",
|
||||
"Reading": "Leyendo...",
|
||||
"HideUserNames": "Ocultar nombres de usuario"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"string": {
|
||||
"Inbox": "Boîte de réception",
|
||||
"ReactedToYourMessage": "a réagi à votre message",
|
||||
"ClearAll": "Tout effacer",
|
||||
"InboxIsClear": "Boîte de réception est vide",
|
||||
"YouDontHaveAnyNewMessages": "Vous n'avez aucun nouveau message",
|
||||
"ReadAll": "Tout lire",
|
||||
"Clearing": "Effacement...",
|
||||
"Reading": "Lecture...",
|
||||
"HideUserNames": "Cacher les noms d'utilisateur"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"string": {
|
||||
"Inbox": "Inbox",
|
||||
"ReactedToYourMessage": "ha reagito al tuo messaggio",
|
||||
"ClearAll": "Cancella tutto",
|
||||
"InboxIsClear": "Inbox è vuoto",
|
||||
"YouDontHaveAnyNewMessages": "Non hai alcun nuovo messaggio",
|
||||
"ReadAll": "Leggi tutto",
|
||||
"Clearing": "Cancellazione in corso...",
|
||||
"Reading": "Lettura in corso...",
|
||||
"HideUserNames": "Nascondi nomi utente"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"string": {
|
||||
"Inbox": "受信箱",
|
||||
"ReactedToYourMessage": "あなたのメッセージに反応しました",
|
||||
"ClearAll": "すべてクリア",
|
||||
"InboxIsClear": "受信箱が空です",
|
||||
"YouDontHaveAnyNewMessages": "新しいメッセージはありません",
|
||||
"ReadAll": "すべて読む",
|
||||
"Clearing": "クリア中...",
|
||||
"Reading": "読み込み中...",
|
||||
"HideUserNames": "ユーザー名を非表示"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"string": {
|
||||
"Inbox": "Caixa de entrada",
|
||||
"ReactedToYourMessage": "reacionou ao seu mensagem",
|
||||
"ClearAll": "Limpar tudo",
|
||||
"InboxIsClear": "Caixa de entrada está vazia",
|
||||
"YouDontHaveAnyNewMessages": "Não tem nenhuma nova mensagem",
|
||||
"ReadAll": "Ler tudo",
|
||||
"Clearing": "Limpando...",
|
||||
"Reading": "Lendo...",
|
||||
"HideUserNames": "Esconder nomes de usuário"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"string": {
|
||||
"Inbox": "Входящие",
|
||||
"ReactedToYourMessage": "отреагировал(а) на ваше сообщение",
|
||||
"ClearAll": "Очистить всё",
|
||||
"InboxIsClear": "Входящие пусты",
|
||||
"YouDontHaveAnyNewMessages": "У вас нет новых сообщений",
|
||||
"ReadAll": "Прочитать всё",
|
||||
"Clearing": "Очистка...",
|
||||
"Reading": "Чтение...",
|
||||
"HideUserNames": "Скрыть имена пользователей"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"string": {
|
||||
"Inbox": "收件箱",
|
||||
"ReactedToYourMessage": "对您的消息做出了反应",
|
||||
"ClearAll": "清除全部",
|
||||
"InboxIsClear": "收件箱是空的",
|
||||
"YouDontHaveAnyNewMessages": "您没有任何新消息",
|
||||
"ReadAll": "全部已读",
|
||||
"Clearing": "正在清除...",
|
||||
"Reading": "正在读取...",
|
||||
"HideUserNames": "隐藏用户名"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"name": "@hcengineering/inbox-assets",
|
||||
"version": "0.7.0",
|
||||
"main": "src/index.ts",
|
||||
"author": "Hardcore Engineering Inc",
|
||||
"template": "@hcengineering/assets-package",
|
||||
"license": "EPL-2.0",
|
||||
"scripts": {
|
||||
"build": "compile",
|
||||
"test": "jest --passWithNoTests --silent",
|
||||
"build:docs": "",
|
||||
"format": "format src",
|
||||
"build:watch": "compile",
|
||||
"_phase:build": "compile transpile src",
|
||||
"_phase:test": "jest --passWithNoTests --silent",
|
||||
"_phase:format": "format src",
|
||||
"_phase:validate": "compile validate"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@hcengineering/platform-rig": "^0.7.19",
|
||||
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
||||
"@typescript-eslint/parser": "^6.21.0",
|
||||
"eslint-config-standard-with-typescript": "^40.0.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-n": "^15.4.0",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"eslint": "^8.54.0",
|
||||
"prettier": "^3.6.2",
|
||||
"@types/node": "^22.15.29",
|
||||
"jest": "^29.7.0",
|
||||
"ts-jest": "^29.1.1",
|
||||
"@types/jest": "^29.5.5",
|
||||
"typescript": "^5.9.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/platform": "^0.7.5",
|
||||
"@hcengineering/inbox": "^0.7.0"
|
||||
},
|
||||
"repository": "https://github.com/hcenginneing/anticrm",
|
||||
"publishConfig": {
|
||||
"registry": "https://npm.pkg.github.com"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
//
|
||||
// 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 { makeLocalesTest } from '@hcengineering/platform'
|
||||
|
||||
it(
|
||||
'Locales are equale',
|
||||
makeLocalesTest((lang) => import(`../../lang/${lang}.json`))
|
||||
)
|
||||
@@ -0,0 +1,22 @@
|
||||
//
|
||||
// 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 { loadMetadata } from '@hcengineering/platform'
|
||||
import inbox from '@hcengineering/inbox'
|
||||
|
||||
const icons = require('../assets/icons.svg') as string // eslint-disable-line
|
||||
loadMetadata(inbox.icon, {
|
||||
Inbox: `${icons}#inbox`
|
||||
})
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"extends": "./node_modules/@hcengineering/platform-rig/profiles/assets/tsconfig.json",
|
||||
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./lib",
|
||||
"declarationDir": "./types",
|
||||
"types": ["node", "jest"],
|
||||
"tsBuildInfoFile": ".build/build.tsbuildinfo"
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "lib", "dist", "types", "bundle"]
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
extends: ['./node_modules/@hcengineering/platform-rig/profiles/ui/eslint.config.json'],
|
||||
parserOptions: { tsconfigRootDir: __dirname }
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"trailingComma": "none",
|
||||
"tabWidth": 2,
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"printWidth": 120,
|
||||
"useTabs": false,
|
||||
"bracketSpacing": true,
|
||||
"proseWrap": "preserve",
|
||||
"plugins": [
|
||||
"prettier-plugin-svelte"
|
||||
],
|
||||
"overrides": [
|
||||
{
|
||||
"files": "*.svelte",
|
||||
"options": {
|
||||
"parser": "svelte"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
|
||||
"rigPackageName": "@hcengineering/platform-rig",
|
||||
"rigProfile": "ui"
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)']
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"name": "@hcengineering/inbox-resources",
|
||||
"version": "0.7.0",
|
||||
"main": "src/index.ts",
|
||||
"author": "Hardcore Engineering Inc",
|
||||
"license": "EPL-2.0",
|
||||
"scripts": {
|
||||
"build": "compile ui",
|
||||
"build:docs": "api-extractor run --local",
|
||||
"format": "format src",
|
||||
"svelte-check": "do-svelte-check",
|
||||
"_phase:svelte-check": "do-svelte-check",
|
||||
"build:watch": "compile ui",
|
||||
"_phase:build": "compile ui",
|
||||
"_phase:format": "format src",
|
||||
"_phase:validate": "compile validate"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@hcengineering/platform-rig": "^0.7.19",
|
||||
"@types/jest": "^29.5.5",
|
||||
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
||||
"@typescript-eslint/parser": "^6.21.0",
|
||||
"eslint": "^8.54.0",
|
||||
"eslint-config-standard-with-typescript": "^40.0.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-n": "^15.4.0",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"eslint-plugin-svelte": "^2.35.1",
|
||||
"jest": "^29.7.0",
|
||||
"prettier": "^3.6.2",
|
||||
"prettier-plugin-svelte": "^3.4.0",
|
||||
"sass": "^1.80.0",
|
||||
"svelte-check": "^3.6.9",
|
||||
"svelte-eslint-parser": "^0.33.1",
|
||||
"svelte-loader": "^3.2.0",
|
||||
"svelte-preprocess": "^5.1.4",
|
||||
"ts-jest": "^29.1.1",
|
||||
"typescript": "^5.9.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/analytics": "^0.7.5",
|
||||
"@hcengineering/card": "^0.7.0",
|
||||
"@hcengineering/communication": "^0.7.0",
|
||||
"@hcengineering/communication-resources": "^0.7.0",
|
||||
"@hcengineering/communication-types": "^0.7.5",
|
||||
"@hcengineering/communication-shared": "^0.7.5",
|
||||
"@hcengineering/contact": "^0.7.0",
|
||||
"@hcengineering/contact-resources": "^0.7.0",
|
||||
"@hcengineering/core": "^0.7.8",
|
||||
"@hcengineering/inbox": "^0.7.0",
|
||||
"@hcengineering/platform": "^0.7.5",
|
||||
"@hcengineering/presentation": "^0.7.0",
|
||||
"@hcengineering/rank": "^0.7.5",
|
||||
"@hcengineering/text": "^0.7.5",
|
||||
"@hcengineering/text-markdown": "^0.7.5",
|
||||
"@hcengineering/ui": "^0.7.0",
|
||||
"@hcengineering/view": "^0.7.0",
|
||||
"@hcengineering/view-resources": "^0.7.0",
|
||||
"@hcengineering/emoji-resources": "^0.7.0",
|
||||
"@hcengineering/activity": "^0.7.0",
|
||||
"@hcengineering/activity-resources": "^0.7.0",
|
||||
"@hcengineering/chunter": "^0.7.0",
|
||||
"@hcengineering/notification": "^0.7.0",
|
||||
"@hcengineering/notification-resources": "^0.7.0",
|
||||
"fast-equals": "^5.2.2",
|
||||
"svelte": "^4.2.20"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
plugins: [
|
||||
require('autoprefixer')
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
// 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 InboxNotificationsClient } from '@hcengineering/notification'
|
||||
import { writable, derived, get } from 'svelte/store'
|
||||
import {
|
||||
createNotificationContextsQuery,
|
||||
getCommunicationClient,
|
||||
onCommunicationClient
|
||||
} from '@hcengineering/presentation'
|
||||
import { notEmpty, SortingOrder } from '@hcengineering/core'
|
||||
import cardPlugin from '@hcengineering/card'
|
||||
import { type NotificationContext, type Window } from '@hcengineering/communication-types'
|
||||
import { getDisplayInboxData, removeContextNotifications } from '@hcengineering/notification-resources'
|
||||
|
||||
import { type NavigationItem } from './type'
|
||||
|
||||
const limit = 20
|
||||
|
||||
const isLoadingStore = writable<boolean>(true)
|
||||
|
||||
const contextsWindowStore = writable<Window<NotificationContext> | undefined>(undefined)
|
||||
const contextStore = writable<NotificationContext[]>([])
|
||||
|
||||
const contextsQuery = createNotificationContextsQuery(true)
|
||||
|
||||
export class NavigationClient {
|
||||
private readonly contextStore = contextStore
|
||||
private readonly contextsWindowStore = contextsWindowStore
|
||||
public readonly isLoadingStore = isLoadingStore
|
||||
|
||||
private readonly limitStore = writable<number>(limit)
|
||||
|
||||
private isPrevLoading = false
|
||||
|
||||
private readonly modernNavigationItemsStore = derived(
|
||||
[contextStore],
|
||||
([contexts]): NavigationItem[] => {
|
||||
return contexts
|
||||
.map((context): NavigationItem => {
|
||||
return {
|
||||
type: 'modern',
|
||||
_id: context.cardId,
|
||||
_class: cardPlugin.class.Card,
|
||||
context,
|
||||
date: context.lastNotify ?? context.lastUpdate
|
||||
}
|
||||
})
|
||||
.filter(notEmpty)
|
||||
},
|
||||
[] as NavigationItem[]
|
||||
)
|
||||
|
||||
private readonly legacyNavigationItemsStore
|
||||
|
||||
public readonly navigationItemsStore
|
||||
public readonly allNavigationItemsStore
|
||||
|
||||
constructor (private readonly oldClient: InboxNotificationsClient) {
|
||||
this.legacyNavigationItemsStore = derived(
|
||||
[this.oldClient.inboxNotificationsByContext, this.oldClient.contextById],
|
||||
([notificationsByContext, contextById]) => {
|
||||
const inboxData = getDisplayInboxData(notificationsByContext)
|
||||
return Array.from(inboxData.entries())
|
||||
.map(([ctx, notifications]): NavigationItem | undefined => {
|
||||
if (notifications.length === 0) return undefined
|
||||
const context = contextById.get(ctx)
|
||||
if (context == null) return undefined
|
||||
|
||||
return {
|
||||
type: 'legacy',
|
||||
_id: context.objectId,
|
||||
_class: context.objectClass,
|
||||
context,
|
||||
date: new Date(notifications[0].createdOn ?? Date.now()),
|
||||
notifications: notifications.slice(0, 3)
|
||||
}
|
||||
})
|
||||
.filter(notEmpty)
|
||||
}
|
||||
)
|
||||
|
||||
this.allNavigationItemsStore = derived(
|
||||
[this.modernNavigationItemsStore, this.legacyNavigationItemsStore],
|
||||
([modern, legacy]): NavigationItem[] =>
|
||||
[...modern, ...legacy].sort((a, b) => b.date.getTime() - a.date.getTime()),
|
||||
[] as NavigationItem[]
|
||||
)
|
||||
|
||||
this.navigationItemsStore = derived(
|
||||
[this.allNavigationItemsStore, this.isLoadingStore, this.limitStore],
|
||||
([items, isLoading, limit]): NavigationItem[] => (isLoading ? [] : items.slice(0, limit)),
|
||||
[] as NavigationItem[]
|
||||
)
|
||||
}
|
||||
|
||||
public hasPrevPage (): boolean {
|
||||
const allItems = get(this.allNavigationItemsStore)
|
||||
const navItems = get(this.navigationItemsStore)
|
||||
|
||||
const hasNextPage = get(contextsWindowStore)?.hasPrevPage() ?? false
|
||||
return hasNextPage || allItems.length > navItems.length
|
||||
}
|
||||
|
||||
public async prev (): Promise<void> {
|
||||
if (this.isPrevLoading || !this.hasPrevPage()) return
|
||||
const w = get(contextsWindowStore)
|
||||
if (w == null) return
|
||||
this.isPrevLoading = true
|
||||
await w.loadPrevPage()
|
||||
this.limitStore.update((it) => it + limit)
|
||||
this.isPrevLoading = false
|
||||
}
|
||||
|
||||
public async remove (item: NavigationItem): Promise<void> {
|
||||
if (item.type === 'modern') {
|
||||
const client = getCommunicationClient()
|
||||
await client.removeNotificationContext(item.context.id)
|
||||
} else {
|
||||
await removeContextNotifications(item.context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateContextsQuery (): void {
|
||||
contextsQuery.query(
|
||||
{
|
||||
notifications: {
|
||||
order: SortingOrder.Descending,
|
||||
limit: 3
|
||||
},
|
||||
order: SortingOrder.Descending,
|
||||
limit
|
||||
},
|
||||
(res) => {
|
||||
contextsWindowStore.set(res)
|
||||
const contexts = res.getResult().filter((it) => (it.notifications?.length ?? 0) > 0)
|
||||
contextStore.set(contexts)
|
||||
isLoadingStore.set(false)
|
||||
|
||||
if (contexts.length < limit && res.hasPrevPage()) {
|
||||
void res.loadPrevPage()
|
||||
}
|
||||
},
|
||||
{ message: true }
|
||||
)
|
||||
}
|
||||
|
||||
onCommunicationClient(() => {
|
||||
updateContextsQuery()
|
||||
})
|
||||
@@ -0,0 +1,274 @@
|
||||
<!--
|
||||
// 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 {
|
||||
defineSeparators,
|
||||
Separator,
|
||||
deviceOptionsStore as deviceInfo,
|
||||
resolvedLocationStore,
|
||||
Location,
|
||||
restoreLocation,
|
||||
Component,
|
||||
closePanel,
|
||||
getCurrentLocation
|
||||
} from '@hcengineering/ui'
|
||||
import { onDestroy } from 'svelte'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { inboxId } from '@hcengineering/inbox'
|
||||
import view from '@hcengineering/view'
|
||||
import { Class, Doc, getCurrentAccount, Ref } from '@hcengineering/core'
|
||||
import notification, { DocNotifyContext, InboxNotification } from '@hcengineering/notification'
|
||||
import { Notification } from '@hcengineering/communication-types'
|
||||
import chunter from '@hcengineering/chunter'
|
||||
import activity, { ActivityMessage } from '@hcengineering/activity'
|
||||
import { InboxNotificationsClientImpl } from '@hcengineering/notification-resources'
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
import { get } from 'svelte/store'
|
||||
import cardPlugin from '@hcengineering/card'
|
||||
|
||||
import InboxNavigation from './InboxNavigation.svelte'
|
||||
import { closeDoc, getDocInfoFromLocation, getMessageInfoFromLocation, navigateToDoc } from '../location'
|
||||
import InboxHeader from './InboxHeader.svelte'
|
||||
import { NavigationItem } from '../type'
|
||||
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
|
||||
const inboxClient = InboxNotificationsClientImpl.getClient()
|
||||
const notificationsByContextStore = inboxClient.inboxNotificationsByContext
|
||||
const contextByIdStore = inboxClient.contextById
|
||||
const contextByDocStore = inboxClient.contextByDoc
|
||||
|
||||
let replacedPanelElement: HTMLElement
|
||||
let doc: Doc | undefined = undefined
|
||||
let legacyContext: DocNotifyContext | undefined = undefined
|
||||
let legacyMessage: ActivityMessage | undefined = undefined
|
||||
let needRestoreLoc = true
|
||||
|
||||
let urlObjectId: Ref<Doc> | undefined = undefined
|
||||
let urlObjectClass: Ref<Class<Doc>> | undefined = undefined
|
||||
|
||||
async function syncLocation (loc: Location): Promise<void> {
|
||||
if (loc.path[2] !== inboxId) {
|
||||
return
|
||||
}
|
||||
|
||||
const docInfo = getDocInfoFromLocation(loc)
|
||||
|
||||
if (docInfo == null) {
|
||||
doc = undefined
|
||||
urlObjectId = undefined
|
||||
urlObjectClass = undefined
|
||||
legacyContext = undefined
|
||||
if (needRestoreLoc) {
|
||||
needRestoreLoc = false
|
||||
restoreLocation(loc, inboxId)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
urlObjectId = docInfo._id
|
||||
urlObjectClass = docInfo._class
|
||||
|
||||
needRestoreLoc = false
|
||||
|
||||
const thread = loc?.path[4] as Ref<ActivityMessage>
|
||||
|
||||
if (docInfo._id !== doc?._id) {
|
||||
doc = await client.findOne(docInfo._class, { _id: docInfo._id })
|
||||
|
||||
if (doc != null && !hierarchy.isDerived(doc._class, cardPlugin.class.Card)) {
|
||||
const queryContext = loc.query?.context as Ref<DocNotifyContext>
|
||||
const ctx =
|
||||
$contextByIdStore.get(queryContext) ?? $contextByDocStore.get(thread) ?? $contextByDocStore.get(urlObjectId)
|
||||
|
||||
legacyContext =
|
||||
ctx ??
|
||||
(await client.findOne(notification.class.DocNotifyContext, {
|
||||
objectId: doc._id,
|
||||
user: getCurrentAccount().uuid
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
const messageInfo = getMessageInfoFromLocation(loc)
|
||||
const messageId = messageInfo?.id
|
||||
|
||||
if (thread !== undefined) {
|
||||
const fn = await getResource(chunter.function.OpenThreadInSidebar)
|
||||
void fn(thread, undefined, undefined, messageInfo?.id as Ref<ActivityMessage>, { autofocus: false }, false)
|
||||
}
|
||||
|
||||
if (messageId != null && messageInfo?.date == null) {
|
||||
legacyMessage = get(inboxClient.activityInboxNotifications).find(({ attachedTo }) => attachedTo === messageId)
|
||||
?.$lookup?.attachedTo
|
||||
if (legacyMessage === undefined) {
|
||||
legacyMessage = await client.findOne(activity.class.ActivityMessage, { _id: messageId as Ref<ActivityMessage> })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let selectedNotificationId: string | undefined = undefined
|
||||
|
||||
function select (
|
||||
event: CustomEvent<{
|
||||
navItem: NavigationItem
|
||||
doc: Doc
|
||||
notification?: InboxNotification | Notification
|
||||
}>
|
||||
): void {
|
||||
const { navItem, notification, doc: ddoc } = event.detail
|
||||
if (ddoc == null) return
|
||||
|
||||
const loc = getCurrentLocation()
|
||||
|
||||
const notificationId = (notification as any)?._id ?? (notification as any)?.id
|
||||
if (
|
||||
navItem.type === 'modern' &&
|
||||
doc?._id === navItem._id &&
|
||||
loc.path[2] === inboxId &&
|
||||
selectedNotificationId === notificationId
|
||||
) {
|
||||
return
|
||||
}
|
||||
if (
|
||||
navItem.type === 'legacy' &&
|
||||
doc?._id === navItem._id &&
|
||||
legacyContext?._id === navItem.context._id &&
|
||||
loc.path[2] === inboxId &&
|
||||
selectedNotificationId === notificationId
|
||||
) {
|
||||
return
|
||||
}
|
||||
closePanel()
|
||||
selectedNotificationId = notificationId
|
||||
navigateToDoc(navItem, ddoc, notification)
|
||||
}
|
||||
|
||||
function handleClose (): void {
|
||||
closePanel()
|
||||
doc = undefined
|
||||
legacyContext = undefined
|
||||
legacyMessage = undefined
|
||||
closeDoc()
|
||||
}
|
||||
|
||||
function isChunterChannel (_class: Ref<Class<Doc>>, urlObjectClass?: Ref<Class<Doc>>): boolean {
|
||||
const isActivityMessageContext = hierarchy.isDerived(_class, activity.class.ActivityMessage)
|
||||
const chunterClass = isActivityMessageContext ? (urlObjectClass ?? _class) : _class
|
||||
return hierarchy.isDerived(chunterClass, chunter.class.ChunterSpace)
|
||||
}
|
||||
|
||||
$: void readLegacyDoc(doc, legacyContext, urlObjectClass)
|
||||
async function readLegacyDoc (
|
||||
doc: Doc | undefined,
|
||||
selectedContext?: DocNotifyContext,
|
||||
urlObjectClass?: Ref<Class<Doc>>
|
||||
): Promise<void> {
|
||||
if (doc == null) return
|
||||
const isChunter = isChunterChannel(doc._class, urlObjectClass)
|
||||
|
||||
const contextNotifications = $notificationsByContextStore.get(selectedContext?._id ?? ('' as any)) ?? []
|
||||
|
||||
const ops = getClient().apply(undefined, 'readNotifications')
|
||||
try {
|
||||
await inboxClient.readNotifications(
|
||||
ops,
|
||||
contextNotifications
|
||||
.filter(({ _class, isViewed }) =>
|
||||
isChunter ? _class === notification.class.CommonInboxNotification : !isViewed
|
||||
)
|
||||
.map(({ _id }) => _id)
|
||||
)
|
||||
} finally {
|
||||
await ops.commit()
|
||||
}
|
||||
}
|
||||
|
||||
onDestroy(
|
||||
resolvedLocationStore.subscribe((loc) => {
|
||||
void syncLocation(loc)
|
||||
})
|
||||
)
|
||||
|
||||
defineSeparators('new-inbox', [
|
||||
{ minSize: 15, maxSize: 60, size: 30, float: 'navigator' },
|
||||
{ size: 'auto', minSize: 20, maxSize: 'auto' }
|
||||
])
|
||||
|
||||
$: $deviceInfo.replacedPanel = replacedPanelElement
|
||||
onDestroy(() => ($deviceInfo.replacedPanel = undefined))
|
||||
</script>
|
||||
|
||||
<div class="hulyPanels-container inbox">
|
||||
{#if $deviceInfo.navigator.visible}
|
||||
<div
|
||||
class="antiPanel-navigator {$deviceInfo.navigator.direction === 'horizontal'
|
||||
? 'portrait'
|
||||
: 'landscape'} border-left inbox__navigator"
|
||||
class:fly={$deviceInfo.navigator.float}
|
||||
>
|
||||
<div class="antiPanel-wrap__content hulyNavPanel-container">
|
||||
<InboxHeader />
|
||||
<div class="antiPanel-wrap__content hulyNavPanel-container">
|
||||
<InboxNavigation {doc} {legacyContext} on:select={select} />
|
||||
</div>
|
||||
</div>
|
||||
{#if !($deviceInfo.isMobile && $deviceInfo.isPortrait && $deviceInfo.minWidth)}
|
||||
<Separator name="new-inbox" float={$deviceInfo.navigator.float ? 'navigator' : true} index={0} />
|
||||
{/if}
|
||||
</div>
|
||||
<Separator
|
||||
name="new-inbox"
|
||||
float={$deviceInfo.navigator.float}
|
||||
index={0}
|
||||
color={'transparent'}
|
||||
separatorSize={0}
|
||||
short
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<div bind:this={replacedPanelElement} class="hulyComponent inbox__panel">
|
||||
{#if doc}
|
||||
{@const panel = client.getHierarchy().classHierarchyMixin(doc._class, view.mixin.ObjectPanel)}
|
||||
<Component
|
||||
is={panel?.component ?? view.component.EditDoc}
|
||||
props={{
|
||||
_id: doc._id,
|
||||
_class: doc._class,
|
||||
context: legacyContext,
|
||||
autofocus: false,
|
||||
embedded: true,
|
||||
activityMessage: legacyMessage,
|
||||
props: { autofocus: false, context: legacyContext, activityMessage: legacyMessage }
|
||||
}}
|
||||
on:close={handleClose}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.inbox {
|
||||
&__navigator {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
&__panel {
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,230 @@
|
||||
<!-- 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 { createNotificationsQuery, createQuery } from '@hcengineering/presentation'
|
||||
import { CheckBox, Loading, Spinner } from '@hcengineering/ui'
|
||||
import { AccountRole, Doc, getCurrentAccount } from '@hcengineering/core'
|
||||
import notification, { ActivityNotificationViewlet, InboxNotification } from '@hcengineering/notification'
|
||||
import { Card } from '@hcengineering/card'
|
||||
import { Notification, NotificationType } from '@hcengineering/communication-types'
|
||||
|
||||
import InboxCardIcon from './InboxCardIcon.svelte'
|
||||
import InboxCardTitle from './InboxCardTitle.svelte'
|
||||
import ModernNotifications from './ModernNotifications.svelte'
|
||||
import LegacyNotifications from './legacy/LegacyNotifications.svelte'
|
||||
import { NavigationItem } from '../type'
|
||||
import { NavigationClient } from '../client'
|
||||
import { isReactionNotification } from '@hcengineering/notification-resources'
|
||||
|
||||
export let navClient: NavigationClient
|
||||
export let navItem: NavigationItem
|
||||
export let selected: boolean = false
|
||||
export let viewlets: ActivityNotificationViewlet[] = []
|
||||
|
||||
const account = getCurrentAccount()
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let total = 0
|
||||
|
||||
const modernNotificationsQuery = createNotificationsQuery()
|
||||
const legacyNotificationsQuery = createQuery()
|
||||
const query = createQuery()
|
||||
|
||||
let doc: Doc | undefined = undefined
|
||||
let isLoading = true
|
||||
|
||||
query.query(
|
||||
navItem._class,
|
||||
{ _id: navItem._id },
|
||||
(res) => {
|
||||
doc = res[0]
|
||||
dispatch('doc', doc)
|
||||
isLoading = false
|
||||
},
|
||||
{ limit: 1 }
|
||||
)
|
||||
|
||||
$: if (doc !== undefined && doc?._id !== navItem._id) {
|
||||
doc = undefined
|
||||
isLoading = true
|
||||
}
|
||||
|
||||
$: if (navItem.type === 'modern') {
|
||||
legacyNotificationsQuery.unsubscribe()
|
||||
modernNotificationsQuery.query(
|
||||
{ limit: 1, total: true, read: false, strict: true, contextId: navItem.context.id },
|
||||
(res) => {
|
||||
total = res.getTotal()
|
||||
}
|
||||
)
|
||||
} else {
|
||||
modernNotificationsQuery.unsubscribe()
|
||||
legacyNotificationsQuery.query(
|
||||
notification.class.InboxNotification,
|
||||
{ isViewed: false, docNotifyContext: navItem.context._id },
|
||||
(res) => {
|
||||
total = res.total
|
||||
},
|
||||
{
|
||||
total: true,
|
||||
limit: 1
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
let isRemoving = false
|
||||
async function handleToggle (): Promise<void> {
|
||||
isRemoving = true
|
||||
try {
|
||||
await navClient.remove(navItem)
|
||||
} finally {
|
||||
isRemoving = false
|
||||
}
|
||||
}
|
||||
|
||||
function asCard (doc: Doc): Card {
|
||||
return doc as Card
|
||||
}
|
||||
|
||||
function onNotification (event: CustomEvent<InboxNotification | Notification>): void {
|
||||
if (doc == null) return
|
||||
dispatch('select', { doc, notification: event.detail })
|
||||
}
|
||||
|
||||
function calcHeight (navItem: NavigationItem): number {
|
||||
const base = 4.125
|
||||
const messageHeightPx = 2
|
||||
const reactionsHeightPx = 4
|
||||
let res = base
|
||||
if (navItem.type === 'modern') {
|
||||
for (const n of navItem.context.notifications ?? []) {
|
||||
if (n.type === NotificationType.Message) {
|
||||
res += messageHeightPx
|
||||
} else if (n.type === NotificationType.Reaction) {
|
||||
res += reactionsHeightPx
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (const n of navItem.notifications) {
|
||||
if (isReactionNotification(n)) {
|
||||
res += reactionsHeightPx
|
||||
} else {
|
||||
res += messageHeightPx
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div
|
||||
class="inbox-card"
|
||||
class:selected
|
||||
style:height={`${calcHeight(navItem)}rem`}
|
||||
on:click={() => {
|
||||
if (doc == null) return
|
||||
dispatch('select', { doc })
|
||||
}}
|
||||
>
|
||||
{#if isLoading}
|
||||
<div class="loading">
|
||||
<Loading />
|
||||
</div>
|
||||
{:else if doc}
|
||||
<div class="inbox-card__header">
|
||||
<InboxCardIcon {doc} count={total ?? 0} />
|
||||
<InboxCardTitle {doc} {navItem} />
|
||||
{#if account.role !== AccountRole.ReadOnlyGuest}
|
||||
<div class="inbox-card__remove">
|
||||
{#if isRemoving}
|
||||
<Spinner size="small" />
|
||||
{:else}
|
||||
<CheckBox kind="todo" size="medium" on:value={handleToggle} />
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="inbox-card__content">
|
||||
<div class="inbox-card__notifications">
|
||||
{#if navItem.type === 'modern'}
|
||||
<ModernNotifications doc={asCard(doc)} context={navItem.context} on:click={onNotification} />
|
||||
{:else}
|
||||
<LegacyNotifications {doc} notifications={navItem.notifications} {viewlets} on:click={onNotification} />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.loading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.inbox-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
padding: 0.5rem;
|
||||
border-bottom: 1px solid var(--global-ui-BorderColor);
|
||||
min-height: 5.625rem;
|
||||
|
||||
&.selected {
|
||||
background-color: var(--global-ui-highlight-BackgroundColor);
|
||||
}
|
||||
|
||||
&:hover:not(.selected) {
|
||||
background-color: var(--global-ui-highlight-BackgroundColor);
|
||||
}
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-left: var(--spacing-0_5);
|
||||
height: 2.5rem;
|
||||
min-height: 2.5rem;
|
||||
max-height: 2.5rem;
|
||||
}
|
||||
|
||||
&__remove {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: auto;
|
||||
min-width: 1.5rem;
|
||||
}
|
||||
|
||||
&__content {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
padding-left: 0.5rem;
|
||||
}
|
||||
|
||||
&__notifications {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
margin-top: var(--spacing-1);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,75 @@
|
||||
<!-- 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 { Component, IconSize, Icon } from '@hcengineering/ui'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import communication from '@hcengineering/communication'
|
||||
import { Class, Doc, Ref } from '@hcengineering/core'
|
||||
import { ObjectIcon } from '@hcengineering/view-resources'
|
||||
import view from '@hcengineering/view'
|
||||
|
||||
import NotifyMarker from './NotifyMarker.svelte'
|
||||
|
||||
export let doc: Doc | undefined
|
||||
export let count: number = 0
|
||||
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
</script>
|
||||
|
||||
<div class="card-icon">
|
||||
{#if doc}
|
||||
{@const size = hierarchy.isDerived(doc._class, communication.type.Direct) ? 'large' : 'medium'}
|
||||
{#if hierarchy.isDerived(doc._class, cardPlugin.class.Card)}
|
||||
<Component is={cardPlugin.component.CardIcon} props={{ value: doc, size }} />
|
||||
{:else}
|
||||
<ObjectIcon value={doc} {size} showLoading={false} />
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
{#if count > 0}
|
||||
<div class="card-icon__marker">
|
||||
<NotifyMarker {count} size="small" />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.card-icon {
|
||||
display: inline-flex;
|
||||
position: relative;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
min-width: 2.5rem;
|
||||
min-height: 2.5rem;
|
||||
color: var(--global-secondary-TextColor);
|
||||
background-color: var(--global-ui-BackgroundColor);
|
||||
border: 1px solid var(--global-subtle-ui-BorderColor);
|
||||
border-radius: var(--medium-BorderRadius);
|
||||
fill: var(--global-secondary-TextColor);
|
||||
|
||||
&__marker {
|
||||
position: absolute;
|
||||
top: -0.375rem;
|
||||
right: 0;
|
||||
transform: translateX(calc(100% - 0.875rem));
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,120 @@
|
||||
<!-- 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 { getClient } from '@hcengineering/presentation'
|
||||
import { Component, Label, tooltip } from '@hcengineering/ui'
|
||||
import notification from '@hcengineering/notification'
|
||||
import { getDocIdentifier, getDocTitle } from '@hcengineering/view-resources'
|
||||
import { getEmbeddedLabel } from '@hcengineering/platform'
|
||||
import { Doc } from '@hcengineering/core'
|
||||
|
||||
import { NavigationItem } from '../type'
|
||||
import cardPlugin, { Card } from '@hcengineering/card'
|
||||
|
||||
export let doc: Doc | undefined
|
||||
export let navItem: NavigationItem
|
||||
|
||||
const client = getClient()
|
||||
|
||||
$: presenterMixin = client
|
||||
.getHierarchy()
|
||||
.classHierarchyMixin(navItem._class, notification.mixin.NotificationContextPresenter)
|
||||
|
||||
let idTitle: string | undefined
|
||||
let title: string | undefined
|
||||
|
||||
$: navItem.type === 'legacy' &&
|
||||
doc &&
|
||||
getDocIdentifier(client, doc._id, doc._class, doc).then((res) => {
|
||||
idTitle = res
|
||||
})
|
||||
|
||||
$: navItem.type === 'legacy' &&
|
||||
doc &&
|
||||
getDocTitle(client, doc._id, doc._class, doc).then((res) => {
|
||||
title = res
|
||||
})
|
||||
|
||||
function asCard (doc: Doc): Card {
|
||||
return doc as Card
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="labels">
|
||||
{#if client.getHierarchy().isDerived(navItem._class, cardPlugin.class.Card)}
|
||||
{@const label = client.getHierarchy().getClass(navItem._class).label}
|
||||
<span class="title--bold overflow-label clear-mins" use:tooltip={{ label }}>
|
||||
<Label {label} />
|
||||
</span>
|
||||
{#if doc}
|
||||
<span class="title overflow-label clear-mins" use:tooltip={{ label: getEmbeddedLabel(asCard(doc).title) }}>
|
||||
{asCard(doc).title}
|
||||
</span>
|
||||
{/if}
|
||||
{:else}
|
||||
{#if presenterMixin?.labelPresenter && navItem.type === 'legacy'}
|
||||
{#if doc}
|
||||
<Component
|
||||
is={presenterMixin.labelPresenter}
|
||||
props={{ context: navItem.context, object: doc }}
|
||||
showLoading={false}
|
||||
/>
|
||||
{/if}
|
||||
{:else if idTitle}
|
||||
<span class="title--bold overflow-label clear-mins" use:tooltip={{ label: getEmbeddedLabel(idTitle) }}>
|
||||
{idTitle}
|
||||
</span>
|
||||
{:else}
|
||||
{@const label = client.getHierarchy().getClass(navItem._class).label}
|
||||
<span class="title--bold overflow-label clear-mins" use:tooltip={{ label }}>
|
||||
<Label {label} />
|
||||
</span>
|
||||
{/if}
|
||||
{@const label = title != null ? getEmbeddedLabel(title) : client.getHierarchy().getClass(navItem._class).label}
|
||||
<span class="title overflow-label clear-mins" use:tooltip={{ label }}>
|
||||
<Label {label} />
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.labels {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
min-width: 0;
|
||||
color: var(--global-primary-TextColor);
|
||||
font-size: 0.875rem;
|
||||
overflow: hidden;
|
||||
height: 2.5rem;
|
||||
min-height: 2.5rem;
|
||||
max-height: 2.5rem;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-weight: 400;
|
||||
color: var(--global-primary-TextColor);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.title--bold {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
color: var(--global-primary-TextColor);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,139 @@
|
||||
<!-- 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 { SortingOrder } from '@hcengineering/core'
|
||||
import {
|
||||
IconDelete,
|
||||
IconSettings,
|
||||
Label,
|
||||
ModernButton,
|
||||
showPopup,
|
||||
eventToHTMLElement,
|
||||
IconMoreV,
|
||||
ButtonMenu,
|
||||
Loading
|
||||
} from '@hcengineering/ui'
|
||||
import { getCommunicationClient } from '@hcengineering/presentation'
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
import view from '@hcengineering/view'
|
||||
import { InboxNotificationsClientImpl } from '@hcengineering/notification-resources'
|
||||
|
||||
import inbox from '../plugin'
|
||||
import InboxViewSettings from './InboxViewSettings.svelte'
|
||||
|
||||
const communicationClient = getCommunicationClient()
|
||||
const oldNotificationClient = InboxNotificationsClientImpl.getClient()
|
||||
|
||||
let clearing = false
|
||||
let reading = false
|
||||
|
||||
async function clearInbox (): Promise<void> {
|
||||
if (clearing || reading) return
|
||||
try {
|
||||
clearing = true
|
||||
const contexts = await communicationClient.findNotificationContexts({ order: SortingOrder.Ascending })
|
||||
await Promise.all([
|
||||
...contexts.map((context) => communicationClient.removeNotificationContext(context.id)),
|
||||
oldNotificationClient.removeAllNotifications()
|
||||
])
|
||||
clearing = false
|
||||
} catch (e: any) {
|
||||
clearing = false
|
||||
Analytics.handleError(e)
|
||||
}
|
||||
}
|
||||
|
||||
async function readInbox (): Promise<void> {
|
||||
if (reading || clearing) return
|
||||
try {
|
||||
reading = true
|
||||
const contexts = await communicationClient.findNotificationContexts({ order: SortingOrder.Ascending })
|
||||
await Promise.all([
|
||||
...contexts.map((context) => communicationClient.updateNotifications(context.id, {}, true)),
|
||||
oldNotificationClient.readAllNotifications()
|
||||
])
|
||||
reading = false
|
||||
} catch (e: any) {
|
||||
reading = false
|
||||
Analytics.handleError(e)
|
||||
}
|
||||
}
|
||||
|
||||
function click (e: MouseEvent): void {
|
||||
showPopup(InboxViewSettings, {}, eventToHTMLElement(e))
|
||||
}
|
||||
|
||||
async function onSelect (id?: 'clear' | 'read'): Promise<void> {
|
||||
if (id == null) return
|
||||
|
||||
if (id === 'clear') {
|
||||
void clearInbox()
|
||||
}
|
||||
|
||||
if (id === 'read') {
|
||||
void readInbox()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="hulyNavPanel-header withButton small inbox-header">
|
||||
<span class="overflow-label"><Label label={inbox.string.Inbox} /></span>
|
||||
<div class="flex-row-center flex-gap-2">
|
||||
{#if clearing}
|
||||
<Loading size="small">
|
||||
<span class="loading-label uppercase">
|
||||
<Label label={inbox.string.Clearing} />
|
||||
</span>
|
||||
</Loading>
|
||||
{:else if reading}
|
||||
<Loading size="small">
|
||||
<span class="loading-label uppercase">
|
||||
<Label label={inbox.string.Reading} />
|
||||
</span>
|
||||
</Loading>
|
||||
{/if}
|
||||
<ModernButton icon={IconSettings} on:click={click} size="small" iconSize="small" />
|
||||
<ButtonMenu
|
||||
size="small"
|
||||
noSelection
|
||||
icon={IconMoreV}
|
||||
items={[
|
||||
{
|
||||
id: 'read',
|
||||
icon: view.icon.CheckCircle,
|
||||
label: inbox.string.ReadAll
|
||||
},
|
||||
{
|
||||
id: 'clear',
|
||||
icon: IconDelete,
|
||||
label: inbox.string.ClearAll
|
||||
}
|
||||
]}
|
||||
on:selected={(ev) => onSelect(ev.detail)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.inbox-header {
|
||||
background-color: var(--theme-popup-color);
|
||||
border-bottom: 1px solid var(--theme-navpanel-border);
|
||||
}
|
||||
|
||||
.loading-label {
|
||||
color: var(--global-secondary-TextColor);
|
||||
font-weight: 500;
|
||||
font-size: 0.625rem;
|
||||
margin-left: 0.25rem;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,220 @@
|
||||
<!-- 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 { Label, ListView, Loading, Scroller } from '@hcengineering/ui'
|
||||
import { Doc, Ref } from '@hcengineering/core'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import notification, { ActivityNotificationViewlet, DocNotifyContext } from '@hcengineering/notification'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { InboxNotificationsClientImpl } from '@hcengineering/notification-resources'
|
||||
import view from '@hcengineering/view'
|
||||
|
||||
import inbox from '../plugin'
|
||||
import { NavigationItem } from '../type'
|
||||
import { NavigationClient } from '../client'
|
||||
import InboxCard from './InboxCard.svelte'
|
||||
|
||||
export let doc: Doc | undefined = undefined
|
||||
export let legacyContext: DocNotifyContext | undefined = undefined
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const client = getClient()
|
||||
|
||||
const navClient: NavigationClient = new NavigationClient(InboxNotificationsClientImpl.getClient())
|
||||
const navigationItemsStore = navClient.navigationItemsStore
|
||||
const isLoadingStore = navClient.isLoadingStore
|
||||
|
||||
let navItems: NavigationItem[] = []
|
||||
|
||||
$: navItems = $navigationItemsStore
|
||||
|
||||
let divScroll: HTMLElement | undefined | null = undefined
|
||||
let contentDiv: HTMLElement | undefined | null = undefined
|
||||
|
||||
let list: ListView
|
||||
let listSelection = 0
|
||||
|
||||
let viewlets: ActivityNotificationViewlet[] = []
|
||||
|
||||
void client.findAll(notification.class.ActivityNotificationViewlet, {}).then((res) => {
|
||||
viewlets = res
|
||||
})
|
||||
|
||||
let isPrevPageLoading = false
|
||||
|
||||
function handleScroll (): void {
|
||||
if (divScroll != null && navClient.hasPrevPage() && !isPrevPageLoading) {
|
||||
const isAtBottom = divScroll.scrollTop + divScroll.clientHeight >= divScroll.scrollHeight - 10
|
||||
if (isAtBottom) {
|
||||
isPrevPageLoading = true
|
||||
void navClient.prev().then(() => {
|
||||
isPrevPageLoading = false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const docById = new Map<Ref<Doc>, Doc>()
|
||||
|
||||
async function onKeydown (key: KeyboardEvent): Promise<void> {
|
||||
if (key.code === 'ArrowUp') {
|
||||
key.stopPropagation()
|
||||
key.preventDefault()
|
||||
list.select(listSelection - 1)
|
||||
}
|
||||
if (key.code === 'ArrowDown') {
|
||||
key.stopPropagation()
|
||||
key.preventDefault()
|
||||
list.select(listSelection + 1)
|
||||
}
|
||||
if (key.code === 'Backspace') {
|
||||
key.preventDefault()
|
||||
key.stopPropagation()
|
||||
const navItem = navItems[listSelection]
|
||||
if (navItem != null) {
|
||||
await navClient.remove(navItem)
|
||||
}
|
||||
}
|
||||
if (key.code === 'Enter') {
|
||||
key.preventDefault()
|
||||
key.stopPropagation()
|
||||
const navItem = navItems[listSelection]
|
||||
const doc = docById.get(navItem?._id)
|
||||
|
||||
if (navItem != null && doc != null) {
|
||||
dispatch('select', {
|
||||
navItem,
|
||||
doc
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$: if (contentDiv != null) {
|
||||
contentDiv.focus()
|
||||
}
|
||||
|
||||
function getContextKey (index: number): string {
|
||||
const item = navItems[index]
|
||||
if (item == null) return index.toString()
|
||||
|
||||
return `${item._id}-${item.type}`
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if navItems.length > 0}
|
||||
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<Scroller bind:divScroll padding="0" shrink onScroll={handleScroll}>
|
||||
<div class="inbox-list" bind:this={contentDiv} tabindex="0" on:keydown={onKeydown}>
|
||||
<ListView
|
||||
bind:this={list}
|
||||
bind:selection={listSelection}
|
||||
count={navItems.length}
|
||||
items={navItems}
|
||||
highlightIndex={legacyContext
|
||||
? navItems.findIndex((it) => it._id === legacyContext.objectId)
|
||||
: navItems.findIndex((it) => it._id === doc?._id)}
|
||||
noScroll
|
||||
kind="full-size"
|
||||
colorsSchema="lumia"
|
||||
lazy={true}
|
||||
minHeight="6.125rem"
|
||||
getKey={getContextKey}
|
||||
>
|
||||
<svelte:fragment slot="item" let:item={itemIndex}>
|
||||
{@const navItem = navItems[itemIndex]}
|
||||
{#if navItem}
|
||||
<InboxCard
|
||||
{navClient}
|
||||
{navItem}
|
||||
{viewlets}
|
||||
selected={navItem._id === legacyContext?.objectId || navItem._id === doc?._id}
|
||||
on:doc={(e) => {
|
||||
const d = e.detail
|
||||
if (d == null) return
|
||||
docById.set(d._id, e.detail)
|
||||
}}
|
||||
on:select={(e) => {
|
||||
dispatch('select', { navItem, doc: e.detail.doc, notification: e.detail.notification })
|
||||
listSelection = itemIndex
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</ListView>
|
||||
</div>
|
||||
{#if isPrevPageLoading}
|
||||
<div class="loader">
|
||||
<Label label={view.string.Loading} />
|
||||
</div>
|
||||
{/if}
|
||||
</Scroller>
|
||||
{:else if $isLoadingStore}
|
||||
<div class="placeholder">
|
||||
<Loading />
|
||||
</div>
|
||||
{:else}
|
||||
<div class="placeholder">
|
||||
<div class="placeholder__header">
|
||||
<Label label={inbox.string.InboxIsClear} />
|
||||
</div>
|
||||
<span class="mb-4">
|
||||
<Label label={inbox.string.YouDontHaveAnyNewMessages} />
|
||||
</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.inbox-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
|
||||
&:focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
:global(.list-item) {
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
display: flex;
|
||||
align-self: center;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
height: inherit;
|
||||
width: 100%;
|
||||
gap: 0.5rem;
|
||||
|
||||
&__header {
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.loader {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--global-secondary-TextColor);
|
||||
font-weight: 500;
|
||||
height: 5rem;
|
||||
min-height: 5rem;
|
||||
}
|
||||
</style>
|
||||
+16
-17
@@ -1,23 +1,22 @@
|
||||
<!--
|
||||
// Copyright © 2023 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
<!-- 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 { createFocusManager, FocusHandler, Label, ListView, ModernToggle, resizeObserver } from '@hcengineering/ui'
|
||||
import { getEmbeddedLabel } from '@hcengineering/platform'
|
||||
|
||||
import { updateViewSetting, isViewSettingEnabled, viewSettingsStore, hideUserNamesSettingId } from '../../settings'
|
||||
import inbox from '../plugin'
|
||||
|
||||
import { updateViewSetting, isViewSettingEnabled, viewSettingsStore, hideUserNamesSettingId } from '../settings'
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
@@ -26,7 +25,7 @@
|
||||
const items = [
|
||||
{
|
||||
id: hideUserNamesSettingId,
|
||||
label: getEmbeddedLabel('Hide user names')
|
||||
label: inbox.string.HideUserNames
|
||||
}
|
||||
]
|
||||
|
||||
+6
-1
@@ -23,4 +23,9 @@
|
||||
export let card: Card
|
||||
</script>
|
||||
|
||||
<NotificationPreview {card} message={notification.message} creator={notification.creator} date={notification.created} />
|
||||
<NotificationPreview
|
||||
{card}
|
||||
message={notification.message}
|
||||
socialId={notification.message?.creator ?? notification.creator}
|
||||
date={notification.created}
|
||||
/>
|
||||
+17
-15
@@ -10,21 +10,23 @@
|
||||
<!-- -->
|
||||
<!-- See the License for the specific language governing permissions and -->
|
||||
<!-- limitations under the License. -->
|
||||
<script lang="ts">
|
||||
import { Label } from '@hcengineering/ui'
|
||||
import { IntlString } from '@hcengineering/platform'
|
||||
|
||||
export let label: IntlString
|
||||
<script lang="ts">
|
||||
import { Card } from '@hcengineering/card'
|
||||
import { NotificationContext } from '@hcengineering/communication-types'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
import InboxNotification from './InboxNotification.svelte'
|
||||
import NotificationTemplate from './NotificationTemplate.svelte'
|
||||
|
||||
export let doc: Card
|
||||
export let context: NotificationContext
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
</script>
|
||||
|
||||
<div class="hulyNavPanel-header withButton small navigation-header">
|
||||
<span class="overflow-label"><Label {label} /></span>
|
||||
<slot name="actions" />
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.navigation-header {
|
||||
background-color: var(--theme-popup-color);
|
||||
border-bottom: 1px solid var(--theme-navpanel-border);
|
||||
}
|
||||
</style>
|
||||
{#each context.notifications ?? [] as notification (notification.id)}
|
||||
<NotificationTemplate on:click={() => dispatch('click', notification)}>
|
||||
<InboxNotification {notification} card={doc} />
|
||||
</NotificationTemplate>
|
||||
{/each}
|
||||
@@ -0,0 +1,53 @@
|
||||
<!-- 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">
|
||||
</script>
|
||||
|
||||
<div class="notification" on:click|stopPropagation|preventDefault>
|
||||
<div class="marker" />
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.notification {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
|
||||
&:first-child .marker {
|
||||
border-top-left-radius: 0.5rem;
|
||||
border-top-right-radius: 0.5rem;
|
||||
}
|
||||
|
||||
&:last-child .marker {
|
||||
border-bottom-left-radius: 0.5rem;
|
||||
border-bottom-right-radius: 0.5rem;
|
||||
}
|
||||
|
||||
&:hover .marker {
|
||||
border-radius: 0.5rem;
|
||||
background: var(--global-primary-LinkColor);
|
||||
}
|
||||
}
|
||||
|
||||
.marker {
|
||||
position: absolute;
|
||||
width: 0.25rem;
|
||||
height: 100%;
|
||||
background: var(--global-ui-highlight-BackgroundColor);
|
||||
border-radius: 0;
|
||||
}
|
||||
</style>
|
||||
+34
-39
@@ -1,16 +1,15 @@
|
||||
<!--
|
||||
// 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.
|
||||
-->
|
||||
<!-- 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 { Message, Notification, ReactionNotificationContent, SocialID } from '@hcengineering/communication-types'
|
||||
@@ -22,7 +21,7 @@
|
||||
|
||||
import NotificationPreview from './preview/NotificationPreview.svelte'
|
||||
import PreviewTemplate from './preview/PreviewTemplate.svelte'
|
||||
import chat from '../../plugin'
|
||||
import inbox from '../plugin'
|
||||
|
||||
export let notification: Notification
|
||||
export let card: Card
|
||||
@@ -45,35 +44,30 @@
|
||||
$: message = notification.message
|
||||
</script>
|
||||
|
||||
{#if notification.message}
|
||||
<div class="reaction-notification">
|
||||
<PreviewTemplate
|
||||
socialId={notification.creator}
|
||||
date={notification.created}
|
||||
color="secondary"
|
||||
showSeparator={false}
|
||||
>
|
||||
<svelte:fragment slot="content">
|
||||
<span class="ml-1-5" />
|
||||
<Label label={chat.string.ReactedToYourMessage} />
|
||||
</svelte:fragment>
|
||||
</PreviewTemplate>
|
||||
<div class="reaction-notification">
|
||||
<PreviewTemplate socialId={notification.creator} date={notification.created} color="secondary" showSeparator={false}>
|
||||
<svelte:fragment slot="content">
|
||||
<Label label={inbox.string.ReactedToYourMessage} />
|
||||
</svelte:fragment>
|
||||
</PreviewTemplate>
|
||||
|
||||
<div class="reaction-notification__body">
|
||||
<div class="reaction-notification__emoji">
|
||||
<EmojiPresenter emoji={content.emoji} fitSize center />
|
||||
</div>
|
||||
<div class="reaction-notification__body">
|
||||
<div class="reaction-notification__emoji">
|
||||
<EmojiPresenter emoji={content.emoji} fitSize center />
|
||||
</div>
|
||||
{#if message}
|
||||
<NotificationPreview
|
||||
{card}
|
||||
{message}
|
||||
creator={notification.creator}
|
||||
socialId={message?.creator ?? notification.creator}
|
||||
date={notification.created}
|
||||
kind="column"
|
||||
padding="0"
|
||||
hideHeader
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.reaction-notification {
|
||||
@@ -93,12 +87,13 @@
|
||||
&__emoji {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 1.5rem;
|
||||
width: 2rem;
|
||||
min-width: 2rem;
|
||||
min-height: 2rem;
|
||||
height: 2rem;
|
||||
font-size: 1.25rem;
|
||||
width: 1.325rem;
|
||||
min-width: 1.325rem;
|
||||
min-height: 1.325rem;
|
||||
height: 1.325rem;
|
||||
overflow: hidden;
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
<!--
|
||||
// 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 { getClient } from '@hcengineering/presentation'
|
||||
import { matchQuery, Doc } from '@hcengineering/core'
|
||||
import { ActivityNotificationViewlet, DisplayActivityInboxNotification } from '@hcengineering/notification'
|
||||
import {
|
||||
ActivityMessagePreview,
|
||||
combineActivityMessages,
|
||||
sortActivityMessages
|
||||
} from '@hcengineering/activity-resources'
|
||||
import activity, { ActivityMessage, DisplayActivityMessage, DocUpdateMessage } from '@hcengineering/activity'
|
||||
import { Component } from '@hcengineering/ui'
|
||||
import { getEmbeddedLabel } from '@hcengineering/platform'
|
||||
import { Person } from '@hcengineering/contact'
|
||||
|
||||
import PreviewTemplate from '../preview/PreviewTemplate.svelte'
|
||||
|
||||
export let object: Doc
|
||||
export let value: DisplayActivityInboxNotification
|
||||
export let viewlets: ActivityNotificationViewlet[] = []
|
||||
|
||||
const client = getClient()
|
||||
|
||||
let viewlet: ActivityNotificationViewlet | undefined = undefined
|
||||
let displayMessage: DisplayActivityMessage | undefined = undefined
|
||||
|
||||
$: void updateDisplayMessage(value.combinedMessages)
|
||||
|
||||
async function updateDisplayMessage (messages: ActivityMessage[]): Promise<void> {
|
||||
const combinedMessages = combineActivityMessages(sortActivityMessages(messages))
|
||||
|
||||
displayMessage = combinedMessages[0]
|
||||
}
|
||||
|
||||
$: updateViewlet(viewlets, displayMessage)
|
||||
|
||||
function matchViewlet (viewlet: ActivityNotificationViewlet, message: DisplayActivityMessage): boolean {
|
||||
const hierarchy = client.getHierarchy()
|
||||
const matched = matchQuery([message], viewlet.messageMatch, message._class, hierarchy, true)[0]
|
||||
if (matched !== undefined) return true
|
||||
|
||||
if (hierarchy.isDerived(message._class, activity.class.DocUpdateMessage)) {
|
||||
const dum = message as DocUpdateMessage
|
||||
const dumUpdated: DocUpdateMessage = {
|
||||
...dum,
|
||||
objectClass: hierarchy.getParentClass(dum.objectClass)
|
||||
}
|
||||
const matched = matchQuery([dumUpdated], viewlet.messageMatch, message._class, hierarchy, true)[0]
|
||||
return matched !== undefined
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
function updateViewlet (viewlets: ActivityNotificationViewlet[], message?: DisplayActivityMessage): void {
|
||||
if (viewlets.length === 0 || message === undefined) {
|
||||
viewlet = undefined
|
||||
return
|
||||
}
|
||||
|
||||
for (const v of viewlets) {
|
||||
const matched = matchViewlet(v, message)
|
||||
if (matched) {
|
||||
viewlet = v
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
viewlet = undefined
|
||||
}
|
||||
|
||||
let person: Person | undefined = undefined
|
||||
</script>
|
||||
|
||||
{#if displayMessage !== undefined}
|
||||
<PreviewTemplate
|
||||
kind="default"
|
||||
color="primary"
|
||||
bind:person
|
||||
socialId={displayMessage.createdBy ?? displayMessage.modifiedBy}
|
||||
date={new Date(displayMessage.createdOn ?? displayMessage.modifiedOn)}
|
||||
fixHeight={true}
|
||||
>
|
||||
<svelte:fragment slot="content">
|
||||
{#if viewlet}
|
||||
<Component
|
||||
is={viewlet.presenter}
|
||||
showLoading={false}
|
||||
props={{
|
||||
message: displayMessage,
|
||||
notification: value,
|
||||
type: 'content-only'
|
||||
}}
|
||||
on:click
|
||||
/>
|
||||
{:else}
|
||||
<ActivityMessagePreview value={displayMessage} doc={object} type="content-only" />
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</PreviewTemplate>
|
||||
{/if}
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
<!--
|
||||
// Copyright © 2023 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import core, { Doc, Markup } from '@hcengineering/core'
|
||||
import { CommonInboxNotification } from '@hcengineering/notification'
|
||||
import { getEmbeddedLabel, IntlString, translateCB } from '@hcengineering/platform'
|
||||
import { Icon, Label, themeStore, tooltip } from '@hcengineering/ui'
|
||||
import { Person } from '@hcengineering/contact'
|
||||
import { getClient, LiteMessageViewer } from '@hcengineering/presentation'
|
||||
import { classIcon, DocNavLink } from '@hcengineering/view-resources'
|
||||
|
||||
import PreviewTemplate from '../preview/PreviewTemplate.svelte'
|
||||
|
||||
export let value: CommonInboxNotification
|
||||
|
||||
const client = getClient()
|
||||
|
||||
let content: Markup = ''
|
||||
let person: Person | undefined = undefined
|
||||
|
||||
$: void updateContent(value.message, value.messageHtml)
|
||||
|
||||
async function updateContent (message?: IntlString, messageHtml?: Markup): Promise<void> {
|
||||
if (messageHtml !== undefined) {
|
||||
content = messageHtml
|
||||
} else if (message !== undefined) {
|
||||
translateCB(message, value.props, $themeStore.language, (res) => {
|
||||
content = res
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
let headerObject: Doc | undefined = undefined
|
||||
|
||||
$: value.headerObjectId &&
|
||||
value.headerObjectClass &&
|
||||
client.findOne(value.headerObjectClass, { _id: value.headerObjectId }).then((doc) => {
|
||||
headerObject = doc
|
||||
})
|
||||
|
||||
let tooltipLabel: IntlString | undefined = undefined
|
||||
|
||||
$: if (headerObject !== undefined) {
|
||||
tooltipLabel = value.header ?? client.getHierarchy().getClass(headerObject._class).label
|
||||
} else if (person !== undefined) {
|
||||
tooltipLabel = getEmbeddedLabel(person.name)
|
||||
} else {
|
||||
tooltipLabel = core.string.System
|
||||
}
|
||||
</script>
|
||||
|
||||
<PreviewTemplate
|
||||
kind="default"
|
||||
color="primary"
|
||||
bind:person
|
||||
socialId={value.createdBy ?? value.modifiedBy}
|
||||
date={new Date(value.createdOn ?? value.modifiedOn)}
|
||||
fixHeight={true}
|
||||
>
|
||||
<svelte:fragment slot="content">
|
||||
<span class="row overflow-label">
|
||||
{#if headerObject}
|
||||
{@const icon = value.headerIcon ?? classIcon(client, headerObject._class)}
|
||||
<div class="header">
|
||||
{#if icon}
|
||||
<span class="icon" use:tooltip={{ label: tooltipLabel }}>
|
||||
<Icon {icon} size="small" />
|
||||
</span>
|
||||
{/if}
|
||||
|
||||
<DocNavLink object={headerObject} colorInherit>
|
||||
<Label
|
||||
label={value.header ?? client.getHierarchy().getClass(headerObject._class).label}
|
||||
params={value.intlParams}
|
||||
/>
|
||||
:
|
||||
</DocNavLink>
|
||||
</div>
|
||||
{/if}
|
||||
<LiteMessageViewer message={content} colorInherit />
|
||||
</span>
|
||||
</svelte:fragment>
|
||||
</PreviewTemplate>
|
||||
|
||||
<style lang="scss">
|
||||
.row {
|
||||
display: inline-flex;
|
||||
align-items: flex-start;
|
||||
gap: 0.25rem;
|
||||
max-height: 1.125rem;
|
||||
}
|
||||
|
||||
.icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
max-width: 1rem;
|
||||
min-width: 1rem;
|
||||
}
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-0_5);
|
||||
font-weight: 500;
|
||||
color: var(--global-primary-TextColor);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,63 @@
|
||||
<!-- 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 notificationPlugin, {
|
||||
ActivityNotificationViewlet,
|
||||
CommonInboxNotification,
|
||||
DisplayActivityInboxNotification,
|
||||
DisplayInboxNotification,
|
||||
MentionInboxNotification,
|
||||
ReactionInboxNotification
|
||||
} from '@hcengineering/notification'
|
||||
import { Doc } from '@hcengineering/core'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
|
||||
import ActivityInboxNotificationPresenter from './ActivityInboxNotificationPresenter.svelte'
|
||||
import MentionInboxNotificationPresenter from './MentionInboxNotificationPresenter.svelte'
|
||||
import CommonInboxNotificationPresenter from './CommonInboxNotificationPresenter.svelte'
|
||||
import ReactionInboxNotificationPresenter from './ReactionInboxNotificationPresenter.svelte'
|
||||
|
||||
export let notification: DisplayInboxNotification
|
||||
export let doc: Doc
|
||||
export let viewlets: ActivityNotificationViewlet[] = []
|
||||
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
|
||||
function asDisplayActivityNotification (notification: DisplayInboxNotification): DisplayActivityInboxNotification {
|
||||
return notification as DisplayActivityInboxNotification
|
||||
}
|
||||
|
||||
function asMentionNotification (notification: DisplayInboxNotification): MentionInboxNotification {
|
||||
return notification as MentionInboxNotification
|
||||
}
|
||||
|
||||
function asReactionNotification (notification: DisplayInboxNotification): ReactionInboxNotification {
|
||||
return notification as ReactionInboxNotification
|
||||
}
|
||||
|
||||
function asCommonNotification (notification: DisplayInboxNotification): CommonInboxNotification {
|
||||
return notification as CommonInboxNotification
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if hierarchy.isDerived(notification._class, notificationPlugin.class.ActivityInboxNotification)}
|
||||
<ActivityInboxNotificationPresenter value={asDisplayActivityNotification(notification)} object={doc} {viewlets} />
|
||||
{:else if hierarchy.isDerived(notification._class, notificationPlugin.class.MentionInboxNotification)}
|
||||
<MentionInboxNotificationPresenter value={asMentionNotification(notification)} object={doc} />
|
||||
{:else if hierarchy.isDerived(notification._class, notificationPlugin.class.ReactionInboxNotification)}
|
||||
<ReactionInboxNotificationPresenter value={asReactionNotification(notification)} object={doc} />
|
||||
{:else if hierarchy.isDerived(notification._class, notificationPlugin.class.CommonInboxNotification)}
|
||||
<CommonInboxNotificationPresenter value={asCommonNotification(notification)} />
|
||||
{/if}
|
||||
@@ -0,0 +1,33 @@
|
||||
<!-- 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 { Doc } from '@hcengineering/core'
|
||||
import { ActivityNotificationViewlet, DisplayInboxNotification } from '@hcengineering/notification'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
import NotificationTemplate from '../NotificationTemplate.svelte'
|
||||
import LegacyNotification from './LegacyNotification.svelte'
|
||||
|
||||
export let doc: Doc
|
||||
export let notifications: DisplayInboxNotification[] = []
|
||||
export let viewlets: ActivityNotificationViewlet[] = []
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
</script>
|
||||
|
||||
{#each notifications as notification (notification._id)}
|
||||
<NotificationTemplate on:click={() => dispatch('click', notification)}>
|
||||
<LegacyNotification {notification} {doc} {viewlets} />
|
||||
</NotificationTemplate>
|
||||
{/each}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
<!--
|
||||
// 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 { ActivityMessagePreview } from '@hcengineering/activity-resources'
|
||||
import { MentionInboxNotification } from '@hcengineering/notification'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import activity, { ActivityMessage } from '@hcengineering/activity'
|
||||
import { Doc } from '@hcengineering/core'
|
||||
import { getEmbeddedLabel } from '@hcengineering/platform'
|
||||
import { Person } from '@hcengineering/contact'
|
||||
|
||||
import CommonInboxNotificationPresenter from './CommonInboxNotificationPresenter.svelte'
|
||||
import PreviewTemplate from '../preview/PreviewTemplate.svelte'
|
||||
|
||||
export let object: Doc
|
||||
export let value: MentionInboxNotification
|
||||
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
const query = createQuery()
|
||||
|
||||
let message: ActivityMessage | undefined = undefined
|
||||
let person: Person | undefined = undefined
|
||||
|
||||
$: if (hierarchy.isDerived(value.mentionedInClass, activity.class.ActivityMessage)) {
|
||||
query.query(value.mentionedInClass, { _id: value.mentionedIn }, (res) => {
|
||||
message = res[0] as ActivityMessage
|
||||
})
|
||||
} else {
|
||||
query.unsubscribe()
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if message}
|
||||
<PreviewTemplate
|
||||
kind="default"
|
||||
color="primary"
|
||||
bind:person
|
||||
socialId={message.createdBy ?? message.modifiedBy}
|
||||
date={new Date(message.createdOn ?? message.modifiedOn)}
|
||||
fixHeight={true}
|
||||
>
|
||||
<svelte:fragment slot="content">
|
||||
<ActivityMessagePreview value={message} doc={object} type="content-only" />
|
||||
</svelte:fragment>
|
||||
</PreviewTemplate>
|
||||
{:else}
|
||||
<CommonInboxNotificationPresenter {value} on:click />
|
||||
{/if}
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
<!--
|
||||
// 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 { ActivityMessagePreview } from '@hcengineering/activity-resources'
|
||||
import { ReactionInboxNotification } from '@hcengineering/notification'
|
||||
import { createQuery } from '@hcengineering/presentation'
|
||||
import { ActivityMessage } from '@hcengineering/activity'
|
||||
import { Doc } from '@hcengineering/core'
|
||||
import { getEmbeddedLabel } from '@hcengineering/platform'
|
||||
import { Person } from '@hcengineering/contact'
|
||||
import { Label } from '@hcengineering/ui'
|
||||
import { EmojiPresenter } from '@hcengineering/emoji-resources'
|
||||
|
||||
import PreviewTemplate from '../preview/PreviewTemplate.svelte'
|
||||
import inbox from '../../plugin'
|
||||
|
||||
export let object: Doc
|
||||
export let value: ReactionInboxNotification
|
||||
|
||||
const query = createQuery()
|
||||
|
||||
let message: ActivityMessage | undefined = undefined
|
||||
let person: Person | undefined = undefined
|
||||
|
||||
query.query(value.attachedToClass, { _id: value.attachedTo }, (res) => {
|
||||
message = res[0]
|
||||
})
|
||||
$: socialId = value.createdBy ?? value.modifiedBy
|
||||
$: date = new Date(value.createdOn ?? value.modifiedOn)
|
||||
</script>
|
||||
|
||||
<div class="reaction-notification">
|
||||
<PreviewTemplate {socialId} {date} color="secondary" showSeparator={false}>
|
||||
<svelte:fragment slot="content">
|
||||
<Label label={inbox.string.ReactedToYourMessage} />
|
||||
</svelte:fragment>
|
||||
</PreviewTemplate>
|
||||
|
||||
<div class="reaction-notification__body">
|
||||
<div class="reaction-notification__emoji">
|
||||
<EmojiPresenter emoji={value.emoji} fitSize center />
|
||||
</div>
|
||||
{#if message}
|
||||
<PreviewTemplate
|
||||
kind="column"
|
||||
padding="0"
|
||||
hideHeader
|
||||
color="primary"
|
||||
bind:person
|
||||
socialId={message.createdBy ?? message.modifiedBy}
|
||||
date={new Date(message.createdOn ?? message.modifiedOn)}
|
||||
fixHeight={true}
|
||||
>
|
||||
<svelte:fragment slot="content">
|
||||
<ActivityMessagePreview value={message} doc={object} type="content-only" />
|
||||
</svelte:fragment>
|
||||
</PreviewTemplate>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.reaction-notification {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: var(--global-secondary-TextColor);
|
||||
white-space: nowrap;
|
||||
|
||||
&__body {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
padding-right: var(--spacing-0_75);
|
||||
padding-left: var(--spacing-1_25);
|
||||
}
|
||||
|
||||
&__emoji {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 1.25rem;
|
||||
width: 1.325rem;
|
||||
min-width: 1.325rem;
|
||||
min-height: 1.325rem;
|
||||
height: 1.325rem;
|
||||
overflow: hidden;
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
+5
-9
@@ -27,18 +27,17 @@
|
||||
|
||||
export let card: Card
|
||||
export let message: Message | undefined = undefined
|
||||
export let creator: SocialID
|
||||
export let socialId: SocialID
|
||||
export let date: Date
|
||||
export let color: 'primary' | 'secondary' = 'primary'
|
||||
export let kind: 'default' | 'column' = 'default'
|
||||
export let padding: string | undefined = undefined
|
||||
export let hideHeader: boolean = false
|
||||
|
||||
const tooltipLimit = 512
|
||||
|
||||
let person: WithLookup<Person> | undefined = undefined
|
||||
|
||||
$: void updatePerson(message?.creator ?? creator)
|
||||
|
||||
function getTooltipLabel (message: Message | undefined): IntlString {
|
||||
if (message == null) return getEmbeddedLabel('')
|
||||
const text = markupToText(jsonToMarkup(markdownToMarkup(message.content)))
|
||||
@@ -47,21 +46,18 @@
|
||||
}
|
||||
return getEmbeddedLabel(text)
|
||||
}
|
||||
|
||||
async function updatePerson (socialId: SocialID): Promise<void> {
|
||||
person = $employeeByPersonIdStore.get(socialId) ?? (await getPersonByPersonId(socialId)) ?? undefined
|
||||
}
|
||||
</script>
|
||||
|
||||
<PreviewTemplate
|
||||
{kind}
|
||||
{color}
|
||||
{person}
|
||||
bind:person
|
||||
{padding}
|
||||
socialId={creator}
|
||||
{socialId}
|
||||
{date}
|
||||
fixHeight={message == null || message.type !== MessageType.Activity}
|
||||
tooltipLabel={getTooltipLabel(message)}
|
||||
{hideHeader}
|
||||
>
|
||||
<svelte:fragment slot="content">
|
||||
{#if message}
|
||||
+26
-27
@@ -23,7 +23,7 @@
|
||||
getPersonByPersonId
|
||||
} from '@hcengineering/contact-resources'
|
||||
import { SocialID } from '@hcengineering/communication-types'
|
||||
import { isViewSettingEnabled, hideUserNamesSettingId, viewSettingsStore } from '../../../settings'
|
||||
import { isViewSettingEnabled, hideUserNamesSettingId, viewSettingsStore } from '../../settings'
|
||||
|
||||
export let tooltipLabel: IntlString | undefined = undefined
|
||||
export let person: Person | undefined = undefined
|
||||
@@ -34,19 +34,14 @@
|
||||
export let padding: string | undefined = undefined
|
||||
export let fixHeight: boolean = false
|
||||
export let showSeparator: boolean = true
|
||||
export let hideHeader: boolean = false
|
||||
|
||||
let clientWidth: number
|
||||
|
||||
$: showPersonName = clientWidth > 300 && !isViewSettingEnabled($viewSettingsStore, hideUserNamesSettingId)
|
||||
|
||||
let _person: Person | undefined
|
||||
$: void updatePerson(socialId, person)
|
||||
|
||||
async function updatePerson (socialId: SocialID, person: Person | undefined): Promise<void> {
|
||||
_person = person ?? $employeeByPersonIdStore.get(socialId)
|
||||
if (!_person) {
|
||||
_person = (await getPersonByPersonId(socialId)) ?? undefined
|
||||
}
|
||||
$: updatePerson(socialId)
|
||||
async function updatePerson (socialId: SocialID): Promise<void> {
|
||||
person = $employeeByPersonIdStore.get(socialId) ?? (await getPersonByPersonId(socialId)) ?? undefined
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -58,15 +53,17 @@
|
||||
style:padding
|
||||
use:resizeObserver={(element) => (clientWidth = element.clientWidth)}
|
||||
>
|
||||
<span class="message-preview-template__column-header">
|
||||
{#if _person}
|
||||
<span class="overflow-label clear-mins">
|
||||
<PersonPreviewProvider value={_person} inline>
|
||||
{formatName(_person?.name ?? '')}
|
||||
</PersonPreviewProvider>
|
||||
</span>
|
||||
{/if}
|
||||
</span>
|
||||
{#if !hideHeader}
|
||||
<span class="message-preview-template__column-header">
|
||||
{#if person}
|
||||
<span class="overflow-label clear-mins">
|
||||
<PersonPreviewProvider value={person} inline>
|
||||
{formatName(person?.name ?? '')}
|
||||
</PersonPreviewProvider>
|
||||
</span>
|
||||
{/if}
|
||||
</span>
|
||||
{/if}
|
||||
|
||||
<span class="message-preview-template__left">
|
||||
{#if $$slots.content}
|
||||
@@ -88,23 +85,21 @@
|
||||
use:resizeObserver={(element) => (clientWidth = element.clientWidth)}
|
||||
>
|
||||
<span class="message-preview-template__left">
|
||||
<PersonPreviewProvider value={_person} inline>
|
||||
<span class="message-preview-template__person">
|
||||
<PersonPreviewProvider value={person} inline>
|
||||
<span class="message-preview-template__person" class:noGap={!showPersonName}>
|
||||
<span class="message-preview-template__avatar">
|
||||
{#if _person}
|
||||
<Avatar size="card" person={_person} name={_person.name} />
|
||||
{#if person}
|
||||
<Avatar size="card" {person} name={person.name} />
|
||||
{:else}
|
||||
<SystemAvatar size="card" />
|
||||
{/if}
|
||||
</span>
|
||||
{#if showPersonName && _person}
|
||||
<span class="message-preview-template__name overflow-label {color}">{formatName(_person?.name ?? '')}</span>
|
||||
{#if showPersonName && person}
|
||||
<span class="message-preview-template__name overflow-label {color}">{formatName(person?.name ?? '')}</span>
|
||||
{/if}
|
||||
{#if showSeparator}
|
||||
{#if showPersonName}
|
||||
<span class="message-preview-template__separator"> • </span>
|
||||
{:else}
|
||||
<span class="mr-0-5" />
|
||||
{/if}
|
||||
{/if}
|
||||
</span>
|
||||
@@ -162,6 +157,10 @@
|
||||
gap: 0.25rem;
|
||||
color: var(--global-primary-TextColor);
|
||||
font-size: 0.875rem;
|
||||
|
||||
&.noGap {
|
||||
gap: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&__avatar {
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user