diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index 86b4abd1e06..2ec66de0144 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -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 diff --git a/common/scripts/version.txt b/common/scripts/version.txt index 955ba890f03..e8cb017a7e6 100644 --- a/common/scripts/version.txt +++ b/common/scripts/version.txt @@ -1 +1 @@ -"0.7.269" +"0.7.278" diff --git a/desktop/package.json b/desktop/package.json index 941fd7df886..f9a8bdd2686 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -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", diff --git a/desktop/src/ui/index.ts b/desktop/src/ui/index.ts index 6aa383ed5c8..2b95a9084ca 100644 --- a/desktop/src/ui/index.ts +++ b/desktop/src/ui/index.ts @@ -33,10 +33,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 +49,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 { @@ -194,39 +196,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)) } }) diff --git a/desktop/src/ui/notifications.ts b/desktop/src/ui/notifications.ts index 579fe78c9a4..0ecc293c6a2 100644 --- a/desktop/src/ui/notifications.ts +++ b/desktop/src/ui/notifications.ts @@ -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, diff --git a/desktop/src/ui/platform.ts b/desktop/src/ui/platform.ts index abe1899a7b8..2ccdda96105 100644 --- a/desktop/src/ui/platform.ts +++ b/desktop/src/ui/platform.ts @@ -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 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')) diff --git a/dev/prod/package.json b/dev/prod/package.json index 4d5b89b69ee..d3810cf5dac 100644 --- a/dev/prod/package.json +++ b/dev/prod/package.json @@ -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" } diff --git a/dev/prod/src/platform.ts b/dev/prod/src/platform.ts index 24d1ea41abf..cba5f7d6ed0 100644 --- a/dev/prod/src/platform.ts +++ b/dev/prod/src/platform.ts @@ -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( diff --git a/models/activity/src/plugin.ts b/models/activity/src/plugin.ts index f8206723895..ac7757bd31a 100644 --- a/models/activity/src/plugin.ts +++ b/models/activity/src/plugin.ts @@ -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, ActivityNotificationGroup: '' as Ref, - AddReactionNotification: '' as Ref, AddReactionAction: '' as Ref, SaveForLaterAction: '' as Ref, RemoveFromLaterAction: '' as Ref, diff --git a/models/all/package.json b/models/all/package.json index a2ad5997042..12b6ad01b43 100644 --- a/models/all/package.json +++ b/models/all/package.json @@ -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" } } diff --git a/models/all/src/index.ts b/models/all/src/index.ts index 086a10e5b94..253e5e2d950 100644 --- a/models/all/src/index.ts +++ b/models/all/src/index.ts @@ -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], diff --git a/models/all/src/migration.ts b/models/all/src/migration.ts index 28aa45ff34c..fa757418088 100644 --- a/models/all/src/migration.ts +++ b/models/all/src/migration.ts @@ -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] diff --git a/models/inbox/.eslintrc.js b/models/inbox/.eslintrc.js new file mode 100644 index 00000000000..c1cf82cba08 --- /dev/null +++ b/models/inbox/.eslintrc.js @@ -0,0 +1,7 @@ +module.exports = { + extends: ['./node_modules/@hcengineering/platform-rig/profiles/model/eslint.config.json'], + parserOptions: { + tsconfigRootDir: __dirname, + project: './tsconfig.json' + } +} diff --git a/models/inbox/.npmignore b/models/inbox/.npmignore new file mode 100644 index 00000000000..e3ec093c383 --- /dev/null +++ b/models/inbox/.npmignore @@ -0,0 +1,4 @@ +* +!/lib/** +!CHANGELOG.md +/lib/**/__tests__/ diff --git a/models/inbox/config/rig.json b/models/inbox/config/rig.json new file mode 100644 index 00000000000..2f6be366054 --- /dev/null +++ b/models/inbox/config/rig.json @@ -0,0 +1,5 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json", + "rigPackageName": "@hcengineering/platform-rig", + "rigProfile": "model" +} diff --git a/models/inbox/jest.config.js b/models/inbox/jest.config.js new file mode 100644 index 00000000000..2cfd408b679 --- /dev/null +++ b/models/inbox/jest.config.js @@ -0,0 +1,7 @@ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'], + roots: ["./src"], + coverageReporters: ["text-summary", "html"] +} diff --git a/models/inbox/package.json b/models/inbox/package.json new file mode 100644 index 00000000000..23fea1cb233 --- /dev/null +++ b/models/inbox/package.json @@ -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" + } +} diff --git a/models/inbox/src/index.ts b/models/inbox/src/index.ts new file mode 100644 index 00000000000..bde12f03ac6 --- /dev/null +++ b/models/inbox/src/index.ts @@ -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 + ) +} diff --git a/models/inbox/src/migration.ts b/models/inbox/src/migration.ts new file mode 100644 index 00000000000..ab4d876d36b --- /dev/null +++ b/models/inbox/src/migration.ts @@ -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 {}, + async upgrade (state: Map>, client: () => Promise, mode): Promise {} +} diff --git a/models/inbox/src/plugin.ts b/models/inbox/src/plugin.ts new file mode 100644 index 00000000000..f3cf60aa6b9 --- /dev/null +++ b/models/inbox/src/plugin.ts @@ -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 + } +}) diff --git a/models/inbox/tsconfig.json b/models/inbox/tsconfig.json new file mode 100644 index 00000000000..367a8578c9f --- /dev/null +++ b/models/inbox/tsconfig.json @@ -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"] +} \ No newline at end of file diff --git a/models/notification/package.json b/models/notification/package.json index 2d3e5f9a1c6..006481947c9 100644 --- a/models/notification/package.json +++ b/models/notification/package.json @@ -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", diff --git a/models/notification/src/index.ts b/models/notification/src/index.ts index 1631b59d5b8..66184f4e894 100644 --- a/models/notification/src/index.ts +++ b/models/notification/src/index.ts @@ -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> } +@Model(notification.class.ReactionInboxNotification, notification.class.CommonInboxNotification) +export class TReactionInboxNotification extends TCommonInboxNotification implements ReactionInboxNotification { + emoji!: string + ref!: Ref + @Prop(TypeRef(activity.class.ActivityMessage), core.string.AttachedTo) + attachedTo!: Ref + + @Prop(TypeRef(activity.class.ActivityMessage), core.string.AttachedToClass) + attachedToClass!: Ref> +} + @Model(notification.class.ActivityNotificationViewlet, core.class.Doc, DOMAIN_MODEL) export class TActivityNotificationViewlet extends TDoc implements ActivityNotificationViewlet { messageMatch!: DocumentQuery @@ -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 } }], diff --git a/models/notification/src/migration.ts b/models/notification/src/migration.ts index a07c785206e..06fa9a35d4c 100644 --- a/models/notification/src/migration.ts +++ b/models/notification/src/migration.ts @@ -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 { client.logger.log('finished processing collaborators ', {}) } +async function migrateReactionNotifications (client: MigrationClient): Promise { + const hierarchy = client.hierarchy + const iterator = await client.traverse(DOMAIN_DOC_NOTIFY, { + _class: notification.class.DocNotifyContext + }) + + try { + while (true) { + const contexts = await iterator.next(500) + const res: ReactionInboxNotification[] = [] + const removeIds: Ref[] = [] + 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(DOMAIN_NOTIFICATION, { + docNotifyContext: context._id, + _class: notification.class.ActivityInboxNotification, + attachedToClass: activity.class.DocUpdateMessage + }) + if (notifications.length === 0) continue + const messages = await client.find(DOMAIN_ACTIVITY, { + _id: { $in: notifications.map((it) => it.attachedTo) as Ref[] }, + _class: activity.class.DocUpdateMessage, + objectClass: activity.class.Reaction + }) + const contextMessage = ( + await client.find(DOMAIN_ACTIVITY, { _id: context.objectId as any }) + )[0] + if (contextMessage == null) continue + const newContext = ( + await client.find(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(DOMAIN_REACTION, { _id: reactionMessage.objectId as Ref }))[0].emoji + if (emoji == null || emoji.trim() === '') continue + res.push({ + _id: it._id as Ref, + _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, + attachedTo: reactionMessage.attachedTo as Ref, + attachedToClass: reactionMessage.attachedToClass as Ref>, + 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 { client.logger.log('finished processing collaborators ', {}) client.logger.log('processing notifications fields ', {}) + function chunkArray (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 } ]) }, diff --git a/models/server-activity/src/index.ts b/models/server-activity/src/index.ts index 6a3d4a5fa3b..15d60a18229 100644 --- a/models/server-activity/src/index.ts +++ b/models/server-activity/src/index.ts @@ -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 }) diff --git a/plugins/activity-resources/src/activityMessagesUtils.ts b/plugins/activity-resources/src/activityMessagesUtils.ts index 0a901294e9c..680de676205 100644 --- a/plugins/activity-resources/src/activityMessagesUtils.ts +++ b/plugins/activity-resources/src/activityMessagesUtils.ts @@ -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[] { const client = getClient() const { uncombined, toCombine } = wrapMessages(client.getHierarchy(), messages) @@ -256,10 +254,7 @@ export async function combineActivityMessages ( const result: Array = [...uncombined] - const groupedByType: Map = await groupByArrayAsync( - docUpdateMessages, - getDocUpdateMessageKey - ) + const groupedByType: Map = 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 (messages: T[]): T[][] { return result } -async function getDocUpdateMessageKey (message: DocUpdateMessage): Promise { - 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>): boolean { if (_class === undefined) { return false diff --git a/plugins/activity-resources/src/components/Activity.svelte b/plugins/activity-resources/src/components/Activity.svelte index ae113b41e98..c5b3fd4b900 100644 --- a/plugins/activity-resources/src/components/Activity.svelte +++ b/plugins/activity-resources/src/components/Activity.svelte @@ -215,17 +215,15 @@ $: allMessages = sortActivityMessages(messages.concat(refs)) - async function updateActivityMessages (objectId: Ref, order: SortingOrder): Promise { + function updateActivityMessages (objectId: Ref, 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 diff --git a/plugins/activity-resources/src/components/BasePreview.svelte b/plugins/activity-resources/src/components/BasePreview.svelte index 13b01736645..ce45cc5b4aa 100644 --- a/plugins/activity-resources/src/components/BasePreview.svelte +++ b/plugins/activity-resources/src/components/BasePreview.svelte @@ -36,6 +36,8 @@ export let headerIcon: Asset | undefined = undefined export let header: IntlString | undefined = undefined export let headerParams: Record = {} + export let color: 'primary' | 'secondary' = 'primary' + export let lower = false const client = getClient() const limit = 300 @@ -132,8 +134,9 @@ {#if 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; } diff --git a/plugins/activity-resources/src/components/Replies.svelte b/plugins/activity-resources/src/components/Replies.svelte index 513d9ab3701..60415a948b7 100644 --- a/plugins/activity-resources/src/components/Replies.svelte +++ b/plugins/activity-resources/src/components/Replies.svelte @@ -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) } diff --git a/plugins/activity-resources/src/components/activity-message/ActivityMessagePreview.svelte b/plugins/activity-resources/src/components/activity-message/ActivityMessagePreview.svelte index efdb2716a9f..76ba62e5816 100644 --- a/plugins/activity-resources/src/components/activity-message/ActivityMessagePreview.svelte +++ b/plugins/activity-resources/src/components/activity-message/ActivityMessagePreview.svelte @@ -40,6 +40,7 @@ {#if previewMixin} , - MentionNotification: '' as Ref + MentionNotification: '' as Ref, + AddReactionNotification: '' as Ref }, extension: { ActivityEmployeePresenter: '' as ComponentExtensionId diff --git a/plugins/chat-resources/src/components/ChatNavigation.svelte b/plugins/chat-resources/src/components/ChatNavigation.svelte index 34cd5b34d8b..f95cde046b4 100644 --- a/plugins/chat-resources/src/components/ChatNavigation.svelte +++ b/plugins/chat-resources/src/components/ChatNavigation.svelte @@ -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 | 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) - } - } -{#if mode === 'inbox'} - - -{:else} - -
- - {#if hasNewInboxNotifications} -
- -
- {/if} -
-
-
- - + -{/if} + + diff --git a/plugins/chat-resources/src/components/inbox/InboxCardIcon.svelte b/plugins/chat-resources/src/components/inbox/InboxCardIcon.svelte deleted file mode 100644 index ec08235e6b9..00000000000 --- a/plugins/chat-resources/src/components/inbox/InboxCardIcon.svelte +++ /dev/null @@ -1,68 +0,0 @@ - - - - -
- - - {#if count > 0} -
- -
- {/if} -
- - diff --git a/plugins/chat-resources/src/components/inbox/InboxHeader.svelte b/plugins/chat-resources/src/components/inbox/InboxHeader.svelte deleted file mode 100644 index 0b29e53417f..00000000000 --- a/plugins/chat-resources/src/components/inbox/InboxHeader.svelte +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - - - - - - - - - -
- -
- - -
- -
-
- - diff --git a/plugins/chat-resources/src/components/inbox/InboxNavigation.svelte b/plugins/chat-resources/src/components/inbox/InboxNavigation.svelte deleted file mode 100644 index ed6d4cb8dab..00000000000 --- a/plugins/chat-resources/src/components/inbox/InboxNavigation.svelte +++ /dev/null @@ -1,201 +0,0 @@ - - - - -{#if contexts.length > 0} - - - -
- it.cardId === card?._id)} - noScroll - kind="full-size" - colorsSchema="lumia" - lazy={true} - getKey={getContextKey} - > - - {@const context = contexts[itemIndex]} - {@const contextCard = cards.find((c) => c._id === context.cardId)} - {#if context && contextCard} - { - dispatch('select', e.detail) - listSelection = itemIndex - }} - /> - {/if} - - -
-
-{:else if isLoading} -
- -
-{:else} -
-
-
- - -
-{/if} - - diff --git a/plugins/chunter-resources/src/channelDataProvider.ts b/plugins/chunter-resources/src/channelDataProvider.ts index 8d8fe3704ca..27e9b494b50 100644 --- a/plugins/chunter-resources/src/channelDataProvider.ts +++ b/plugins/chunter-resources/src/channelDataProvider.ts @@ -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) } } diff --git a/plugins/chunter-resources/src/components/BaseChatScroller.svelte b/plugins/chunter-resources/src/components/BaseChatScroller.svelte index a54eb7f434b..c65589499cb 100644 --- a/plugins/chunter-resources/src/components/BaseChatScroller.svelte +++ b/plugins/chunter-resources/src/components/BaseChatScroller.svelte @@ -22,6 +22,7 @@ export let loadingOverlay: boolean = false export let onScroll: () => void = () => {} export let onResize: () => void = () => {} + export let key: string {#if loadingOverlay} @@ -41,7 +42,9 @@ {onScroll} {onResize} > - + {#key key} + + {/key} diff --git a/plugins/chunter-resources/src/components/notification/ChatMessageNotificationLabel.svelte b/plugins/chunter-resources/src/components/notification/ChatMessageNotificationLabel.svelte index 56519aa0400..116fa07bd24 100644 --- a/plugins/chunter-resources/src/components/notification/ChatMessageNotificationLabel.svelte +++ b/plugins/chunter-resources/src/components/notification/ChatMessageNotificationLabel.svelte @@ -61,7 +61,7 @@ } - + {#if isThread || (object.replies ?? 0) > 0} - + {#if isThread} {:else} {/if} + + diff --git a/plugins/chunter-resources/src/components/notification/JoinChannelNotificationPresenter.svelte b/plugins/chunter-resources/src/components/notification/JoinChannelNotificationPresenter.svelte index ed0e0571498..62e8fd45606 100644 --- a/plugins/chunter-resources/src/components/notification/JoinChannelNotificationPresenter.svelte +++ b/plugins/chunter-resources/src/components/notification/JoinChannelNotificationPresenter.svelte @@ -13,12 +13,13 @@ // limitations under the License. --> - + diff --git a/plugins/chunter-resources/src/components/notification/ThreadNotificationPresenter.svelte b/plugins/chunter-resources/src/components/notification/ThreadNotificationPresenter.svelte index 7c9e59d2565..be01a1b1012 100644 --- a/plugins/chunter-resources/src/components/notification/ThreadNotificationPresenter.svelte +++ b/plugins/chunter-resources/src/components/notification/ThreadNotificationPresenter.svelte @@ -15,8 +15,10 @@ - + diff --git a/plugins/chunter-resources/src/utils.ts b/plugins/chunter-resources/src/utils.ts index d450b328b07..2efcbcc666a 100644 --- a/plugins/chunter-resources/src/utils.ts +++ b/plugins/chunter-resources/src/utils.ts @@ -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) - } - 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)) .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() } diff --git a/plugins/client-resources/package.json b/plugins/client-resources/package.json index ec941e712ec..ce36f4f2896 100644 --- a/plugins/client-resources/package.json +++ b/plugins/client-resources/package.json @@ -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", diff --git a/plugins/inbox-assets/.eslintrc.js b/plugins/inbox-assets/.eslintrc.js new file mode 100644 index 00000000000..e73094bc7e5 --- /dev/null +++ b/plugins/inbox-assets/.eslintrc.js @@ -0,0 +1,7 @@ +module.exports = { + extends: ['./node_modules/@hcengineering/platform-rig/profiles/assets/eslint.config.json'], + parserOptions: { + tsconfigRootDir: __dirname, + project: './tsconfig.json' + } +} diff --git a/plugins/inbox-assets/assets/icons.svg b/plugins/inbox-assets/assets/icons.svg new file mode 100644 index 00000000000..1337bb326e2 --- /dev/null +++ b/plugins/inbox-assets/assets/icons.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/plugins/inbox-assets/config/rig.json b/plugins/inbox-assets/config/rig.json new file mode 100644 index 00000000000..b75800b9b70 --- /dev/null +++ b/plugins/inbox-assets/config/rig.json @@ -0,0 +1,5 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json", + "rigPackageName": "@hcengineering/platform-rig", + "rigProfile": "assets" +} diff --git a/plugins/inbox-assets/jest.config.js b/plugins/inbox-assets/jest.config.js new file mode 100644 index 00000000000..2cfd408b679 --- /dev/null +++ b/plugins/inbox-assets/jest.config.js @@ -0,0 +1,7 @@ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'], + roots: ["./src"], + coverageReporters: ["text-summary", "html"] +} diff --git a/plugins/inbox-assets/lang/cs.json b/plugins/inbox-assets/lang/cs.json new file mode 100644 index 00000000000..526fd89c3fa --- /dev/null +++ b/plugins/inbox-assets/lang/cs.json @@ -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" + } +} diff --git a/plugins/inbox-assets/lang/de.json b/plugins/inbox-assets/lang/de.json new file mode 100644 index 00000000000..1318bd57d75 --- /dev/null +++ b/plugins/inbox-assets/lang/de.json @@ -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" + } +} diff --git a/plugins/inbox-assets/lang/en.json b/plugins/inbox-assets/lang/en.json new file mode 100644 index 00000000000..e1330535edf --- /dev/null +++ b/plugins/inbox-assets/lang/en.json @@ -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" + } +} diff --git a/plugins/inbox-assets/lang/es.json b/plugins/inbox-assets/lang/es.json new file mode 100644 index 00000000000..20cd82a2322 --- /dev/null +++ b/plugins/inbox-assets/lang/es.json @@ -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" + } +} diff --git a/plugins/inbox-assets/lang/fr.json b/plugins/inbox-assets/lang/fr.json new file mode 100644 index 00000000000..29ed93d1c3a --- /dev/null +++ b/plugins/inbox-assets/lang/fr.json @@ -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" + } +} diff --git a/plugins/inbox-assets/lang/it.json b/plugins/inbox-assets/lang/it.json new file mode 100644 index 00000000000..77d15086a8c --- /dev/null +++ b/plugins/inbox-assets/lang/it.json @@ -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" + } +} diff --git a/plugins/inbox-assets/lang/ja.json b/plugins/inbox-assets/lang/ja.json new file mode 100644 index 00000000000..d23904eeea3 --- /dev/null +++ b/plugins/inbox-assets/lang/ja.json @@ -0,0 +1,13 @@ +{ + "string": { + "Inbox": "受信箱", + "ReactedToYourMessage": "あなたのメッセージに反応しました", + "ClearAll": "すべてクリア", + "InboxIsClear": "受信箱が空です", + "YouDontHaveAnyNewMessages": "新しいメッセージはありません", + "ReadAll": "すべて読む", + "Clearing": "クリア中...", + "Reading": "読み込み中...", + "HideUserNames": "ユーザー名を非表示" + } +} diff --git a/plugins/inbox-assets/lang/pt.json b/plugins/inbox-assets/lang/pt.json new file mode 100644 index 00000000000..32ef00be446 --- /dev/null +++ b/plugins/inbox-assets/lang/pt.json @@ -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" + } +} diff --git a/plugins/inbox-assets/lang/ru.json b/plugins/inbox-assets/lang/ru.json new file mode 100644 index 00000000000..22e30465d92 --- /dev/null +++ b/plugins/inbox-assets/lang/ru.json @@ -0,0 +1,13 @@ +{ + "string": { + "Inbox": "Входящие", + "ReactedToYourMessage": "отреагировал(а) на ваше сообщение", + "ClearAll": "Очистить всё", + "InboxIsClear": "Входящие пусты", + "YouDontHaveAnyNewMessages": "У вас нет новых сообщений", + "ReadAll": "Прочитать всё", + "Clearing": "Очистка...", + "Reading": "Чтение...", + "HideUserNames": "Скрыть имена пользователей" + } +} diff --git a/plugins/inbox-assets/lang/zh.json b/plugins/inbox-assets/lang/zh.json new file mode 100644 index 00000000000..196eb2fa5f0 --- /dev/null +++ b/plugins/inbox-assets/lang/zh.json @@ -0,0 +1,13 @@ +{ + "string": { + "Inbox": "收件箱", + "ReactedToYourMessage": "对您的消息做出了反应", + "ClearAll": "清除全部", + "InboxIsClear": "收件箱是空的", + "YouDontHaveAnyNewMessages": "您没有任何新消息", + "ReadAll": "全部已读", + "Clearing": "正在清除...", + "Reading": "正在读取...", + "HideUserNames": "隐藏用户名" + } +} diff --git a/plugins/inbox-assets/package.json b/plugins/inbox-assets/package.json new file mode 100644 index 00000000000..51e3b50a31a --- /dev/null +++ b/plugins/inbox-assets/package.json @@ -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" + } +} diff --git a/plugins/inbox-assets/src/__tests__/lang.test.ts b/plugins/inbox-assets/src/__tests__/lang.test.ts new file mode 100644 index 00000000000..9098dbdab14 --- /dev/null +++ b/plugins/inbox-assets/src/__tests__/lang.test.ts @@ -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`)) +) diff --git a/plugins/inbox-assets/src/index.ts b/plugins/inbox-assets/src/index.ts new file mode 100644 index 00000000000..8b94957ff27 --- /dev/null +++ b/plugins/inbox-assets/src/index.ts @@ -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` +}) diff --git a/plugins/inbox-assets/tsconfig.json b/plugins/inbox-assets/tsconfig.json new file mode 100644 index 00000000000..2857a41ad29 --- /dev/null +++ b/plugins/inbox-assets/tsconfig.json @@ -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"] +} \ No newline at end of file diff --git a/plugins/inbox-resources/.eslintrc.js b/plugins/inbox-resources/.eslintrc.js new file mode 100644 index 00000000000..bb8fd7450d4 --- /dev/null +++ b/plugins/inbox-resources/.eslintrc.js @@ -0,0 +1,4 @@ +module.exports = { + extends: ['./node_modules/@hcengineering/platform-rig/profiles/ui/eslint.config.json'], + parserOptions: { tsconfigRootDir: __dirname } +} diff --git a/plugins/inbox-resources/.prettierrc b/plugins/inbox-resources/.prettierrc new file mode 100644 index 00000000000..792942803ac --- /dev/null +++ b/plugins/inbox-resources/.prettierrc @@ -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" + } + } + ] +} \ No newline at end of file diff --git a/plugins/inbox-resources/config/rig.json b/plugins/inbox-resources/config/rig.json new file mode 100644 index 00000000000..bcad6f7c33b --- /dev/null +++ b/plugins/inbox-resources/config/rig.json @@ -0,0 +1,5 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json", + "rigPackageName": "@hcengineering/platform-rig", + "rigProfile": "ui" +} diff --git a/plugins/inbox-resources/jest.config.js b/plugins/inbox-resources/jest.config.js new file mode 100644 index 00000000000..3656e284d33 --- /dev/null +++ b/plugins/inbox-resources/jest.config.js @@ -0,0 +1,5 @@ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'] +} diff --git a/plugins/inbox-resources/package.json b/plugins/inbox-resources/package.json new file mode 100644 index 00000000000..b9db51ee36b --- /dev/null +++ b/plugins/inbox-resources/package.json @@ -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" + } +} diff --git a/plugins/inbox-resources/postcss.config.js b/plugins/inbox-resources/postcss.config.js new file mode 100644 index 00000000000..88752c6cb06 --- /dev/null +++ b/plugins/inbox-resources/postcss.config.js @@ -0,0 +1,5 @@ +module.exports = { + plugins: [ + require('autoprefixer') + ] +} diff --git a/plugins/inbox-resources/src/client.ts b/plugins/inbox-resources/src/client.ts new file mode 100644 index 00000000000..f2003ff1726 --- /dev/null +++ b/plugins/inbox-resources/src/client.ts @@ -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(true) + +const contextsWindowStore = writable | undefined>(undefined) +const contextStore = writable([]) + +const contextsQuery = createNotificationContextsQuery(true) + +export class NavigationClient { + private readonly contextStore = contextStore + private readonly contextsWindowStore = contextsWindowStore + public readonly isLoadingStore = isLoadingStore + + private readonly limitStore = writable(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 { + 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 { + 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() +}) diff --git a/plugins/inbox-resources/src/components/InboxApplication.svelte b/plugins/inbox-resources/src/components/InboxApplication.svelte new file mode 100644 index 00000000000..fed8e6bd918 --- /dev/null +++ b/plugins/inbox-resources/src/components/InboxApplication.svelte @@ -0,0 +1,274 @@ + + + + +
+ {#if $deviceInfo.navigator.visible} +
+
+ +
+ +
+
+ {#if !($deviceInfo.isMobile && $deviceInfo.isPortrait && $deviceInfo.minWidth)} + + {/if} +
+ + {/if} + +
+ {#if doc} + {@const panel = client.getHierarchy().classHierarchyMixin(doc._class, view.mixin.ObjectPanel)} + + {/if} +
+
+ + diff --git a/plugins/inbox-resources/src/components/InboxCard.svelte b/plugins/inbox-resources/src/components/InboxCard.svelte new file mode 100644 index 00000000000..64aa5e6f0a6 --- /dev/null +++ b/plugins/inbox-resources/src/components/InboxCard.svelte @@ -0,0 +1,230 @@ + + + + + + + + + + + + + + + + + +
{ + if (doc == null) return + dispatch('select', { doc }) + }} +> + {#if isLoading} +
+ +
+ {:else if doc} +
+ + + {#if account.role !== AccountRole.ReadOnlyGuest} +
+ {#if isRemoving} + + {:else} + + {/if} +
+ {/if} +
+ +
+
+ {#if navItem.type === 'modern'} + + {:else} + + {/if} +
+
+ {/if} +
+ + diff --git a/plugins/inbox-resources/src/components/InboxCardIcon.svelte b/plugins/inbox-resources/src/components/InboxCardIcon.svelte new file mode 100644 index 00000000000..f444e07e789 --- /dev/null +++ b/plugins/inbox-resources/src/components/InboxCardIcon.svelte @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + +
+ {#if doc} + {@const size = hierarchy.isDerived(doc._class, communication.type.Direct) ? 'large' : 'medium'} + {#if hierarchy.isDerived(doc._class, cardPlugin.class.Card)} + + {:else} + + {/if} + {/if} + + {#if count > 0} +
+ +
+ {/if} +
+ + diff --git a/plugins/inbox-resources/src/components/InboxCardTitle.svelte b/plugins/inbox-resources/src/components/InboxCardTitle.svelte new file mode 100644 index 00000000000..5d55a272e35 --- /dev/null +++ b/plugins/inbox-resources/src/components/InboxCardTitle.svelte @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + +
+ {#if client.getHierarchy().isDerived(navItem._class, cardPlugin.class.Card)} + {@const label = client.getHierarchy().getClass(navItem._class).label} + + + {#if doc} + + {asCard(doc).title} + + {/if} + {:else} + {#if presenterMixin?.labelPresenter && navItem.type === 'legacy'} + {#if doc} + + {/if} + {:else if idTitle} + + {idTitle} + + {:else} + {@const label = client.getHierarchy().getClass(navItem._class).label} + + + {/if} + {@const label = title != null ? getEmbeddedLabel(title) : client.getHierarchy().getClass(navItem._class).label} + + + {/if} +
+ + diff --git a/plugins/inbox-resources/src/components/InboxHeader.svelte b/plugins/inbox-resources/src/components/InboxHeader.svelte new file mode 100644 index 00000000000..86d3d9070b2 --- /dev/null +++ b/plugins/inbox-resources/src/components/InboxHeader.svelte @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + +
+ +
+ {#if clearing} + + + + + {:else if reading} + + + + + {/if} + + onSelect(ev.detail)} + /> +
+
+ + diff --git a/plugins/inbox-resources/src/components/InboxNavigation.svelte b/plugins/inbox-resources/src/components/InboxNavigation.svelte new file mode 100644 index 00000000000..3634046d8db --- /dev/null +++ b/plugins/inbox-resources/src/components/InboxNavigation.svelte @@ -0,0 +1,220 @@ + + + + + + + + + + + + + + + +{#if navItems.length > 0} + + + +
+ it._id === legacyContext.objectId) + : navItems.findIndex((it) => it._id === doc?._id)} + noScroll + kind="full-size" + colorsSchema="lumia" + lazy={true} + minHeight="6.125rem" + getKey={getContextKey} + > + + {@const navItem = navItems[itemIndex]} + {#if navItem} + { + 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} + + +
+ {#if isPrevPageLoading} +
+
+ {/if} +
+{:else if $isLoadingStore} +
+ +
+{:else} +
+
+
+ + +
+{/if} + + diff --git a/plugins/chat-resources/src/components/inbox/InboxNotification.svelte b/plugins/inbox-resources/src/components/InboxNotification.svelte similarity index 100% rename from plugins/chat-resources/src/components/inbox/InboxNotification.svelte rename to plugins/inbox-resources/src/components/InboxNotification.svelte diff --git a/plugins/chat-resources/src/components/inbox/InboxViewSettings.svelte b/plugins/inbox-resources/src/components/InboxViewSettings.svelte similarity index 76% rename from plugins/chat-resources/src/components/inbox/InboxViewSettings.svelte rename to plugins/inbox-resources/src/components/InboxViewSettings.svelte index 4f8c8c9e955..93ff3811a47 100644 --- a/plugins/chat-resources/src/components/inbox/InboxViewSettings.svelte +++ b/plugins/inbox-resources/src/components/InboxViewSettings.svelte @@ -1,23 +1,22 @@ - + + + + + + + + + + + + - + diff --git a/plugins/chat-resources/src/components/NavigationHeader.svelte b/plugins/inbox-resources/src/components/ModernNotifications.svelte similarity index 52% rename from plugins/chat-resources/src/components/NavigationHeader.svelte rename to plugins/inbox-resources/src/components/ModernNotifications.svelte index 39017da63ec..3068026fc33 100644 --- a/plugins/chat-resources/src/components/NavigationHeader.svelte +++ b/plugins/inbox-resources/src/components/ModernNotifications.svelte @@ -10,21 +10,23 @@ + + import InboxNotification from './InboxNotification.svelte' + import NotificationTemplate from './NotificationTemplate.svelte' - + export let doc: Card + export let context: NotificationContext + + const dispatch = createEventDispatcher() + - +{#each context.notifications ?? [] as notification (notification.id)} + dispatch('click', notification)}> + + +{/each} diff --git a/plugins/inbox-resources/src/components/NotificationTemplate.svelte b/plugins/inbox-resources/src/components/NotificationTemplate.svelte new file mode 100644 index 00000000000..2c3b8de6a20 --- /dev/null +++ b/plugins/inbox-resources/src/components/NotificationTemplate.svelte @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + +
+
+ +
+ + diff --git a/plugins/chat-resources/src/components/inbox/NotifyMarker.svelte b/plugins/inbox-resources/src/components/NotifyMarker.svelte similarity index 100% rename from plugins/chat-resources/src/components/inbox/NotifyMarker.svelte rename to plugins/inbox-resources/src/components/NotifyMarker.svelte diff --git a/plugins/chat-resources/src/components/inbox/ReactionNotification.svelte b/plugins/inbox-resources/src/components/ReactionNotification.svelte similarity index 55% rename from plugins/chat-resources/src/components/inbox/ReactionNotification.svelte rename to plugins/inbox-resources/src/components/ReactionNotification.svelte index 5ad491ea320..1dcbe8ae8ea 100644 --- a/plugins/chat-resources/src/components/inbox/ReactionNotification.svelte +++ b/plugins/inbox-resources/src/components/ReactionNotification.svelte @@ -1,16 +1,15 @@ - + + + + + + + + + + + + -{#if notification.message} -
- - - - - +
+ + + + -
-
- -
+
+
+ +
+ {#if message} -
+ {/if}
-{/if} +
diff --git a/plugins/inbox-resources/src/components/legacy/ActivityInboxNotificationPresenter.svelte b/plugins/inbox-resources/src/components/legacy/ActivityInboxNotificationPresenter.svelte new file mode 100644 index 00000000000..bd15e1717a9 --- /dev/null +++ b/plugins/inbox-resources/src/components/legacy/ActivityInboxNotificationPresenter.svelte @@ -0,0 +1,114 @@ + + + +{#if displayMessage !== undefined} + + + {#if viewlet} + + {:else} + + {/if} + + +{/if} diff --git a/plugins/inbox-resources/src/components/legacy/CommonInboxNotificationPresenter.svelte b/plugins/inbox-resources/src/components/legacy/CommonInboxNotificationPresenter.svelte new file mode 100644 index 00000000000..7b31638d827 --- /dev/null +++ b/plugins/inbox-resources/src/components/legacy/CommonInboxNotificationPresenter.svelte @@ -0,0 +1,121 @@ + + + + + + + {#if headerObject} + {@const icon = value.headerIcon ?? classIcon(client, headerObject._class)} +
+ {#if icon} + + + + {/if} + + + +
+ {/if} + +
+
+
+ + diff --git a/plugins/inbox-resources/src/components/legacy/LegacyNotification.svelte b/plugins/inbox-resources/src/components/legacy/LegacyNotification.svelte new file mode 100644 index 00000000000..d04ff30d8b2 --- /dev/null +++ b/plugins/inbox-resources/src/components/legacy/LegacyNotification.svelte @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + +{#if hierarchy.isDerived(notification._class, notificationPlugin.class.ActivityInboxNotification)} + +{:else if hierarchy.isDerived(notification._class, notificationPlugin.class.MentionInboxNotification)} + +{:else if hierarchy.isDerived(notification._class, notificationPlugin.class.ReactionInboxNotification)} + +{:else if hierarchy.isDerived(notification._class, notificationPlugin.class.CommonInboxNotification)} + +{/if} diff --git a/plugins/inbox-resources/src/components/legacy/LegacyNotifications.svelte b/plugins/inbox-resources/src/components/legacy/LegacyNotifications.svelte new file mode 100644 index 00000000000..8317c6966a7 --- /dev/null +++ b/plugins/inbox-resources/src/components/legacy/LegacyNotifications.svelte @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + +{#each notifications as notification (notification._id)} + dispatch('click', notification)}> + + +{/each} diff --git a/plugins/inbox-resources/src/components/legacy/MentionInboxNotificationPresenter.svelte b/plugins/inbox-resources/src/components/legacy/MentionInboxNotificationPresenter.svelte new file mode 100644 index 00000000000..1502518f621 --- /dev/null +++ b/plugins/inbox-resources/src/components/legacy/MentionInboxNotificationPresenter.svelte @@ -0,0 +1,61 @@ + + + +{#if message} + + + + + +{:else} + +{/if} diff --git a/plugins/inbox-resources/src/components/legacy/ReactionInboxNotificationPresenter.svelte b/plugins/inbox-resources/src/components/legacy/ReactionInboxNotificationPresenter.svelte new file mode 100644 index 00000000000..aca1dc51191 --- /dev/null +++ b/plugins/inbox-resources/src/components/legacy/ReactionInboxNotificationPresenter.svelte @@ -0,0 +1,101 @@ + + + +
+ + + + + +
+
+ +
+ {#if message} + + + + + + {/if} +
+
+ + diff --git a/plugins/chat-resources/src/components/inbox/preview/NotificationPreview.svelte b/plugins/inbox-resources/src/components/preview/NotificationPreview.svelte similarity index 90% rename from plugins/chat-resources/src/components/inbox/preview/NotificationPreview.svelte rename to plugins/inbox-resources/src/components/preview/NotificationPreview.svelte index 38886e98907..3ad10f1fe15 100644 --- a/plugins/chat-resources/src/components/inbox/preview/NotificationPreview.svelte +++ b/plugins/inbox-resources/src/components/preview/NotificationPreview.svelte @@ -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 | 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 { - person = $employeeByPersonIdStore.get(socialId) ?? (await getPersonByPersonId(socialId)) ?? undefined - } {#if message} diff --git a/plugins/chat-resources/src/components/inbox/preview/PreviewTemplate.svelte b/plugins/inbox-resources/src/components/preview/PreviewTemplate.svelte similarity index 84% rename from plugins/chat-resources/src/components/inbox/preview/PreviewTemplate.svelte rename to plugins/inbox-resources/src/components/preview/PreviewTemplate.svelte index d581606fc5b..98ddc6bf3ac 100644 --- a/plugins/chat-resources/src/components/inbox/preview/PreviewTemplate.svelte +++ b/plugins/inbox-resources/src/components/preview/PreviewTemplate.svelte @@ -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 { - _person = person ?? $employeeByPersonIdStore.get(socialId) - if (!_person) { - _person = (await getPersonByPersonId(socialId)) ?? undefined - } + $: updatePerson(socialId) + async function updatePerson (socialId: SocialID): Promise { + person = $employeeByPersonIdStore.get(socialId) ?? (await getPersonByPersonId(socialId)) ?? undefined } @@ -58,15 +53,17 @@ style:padding use:resizeObserver={(element) => (clientWidth = element.clientWidth)} > - - {#if _person} - - - {formatName(_person?.name ?? '')} - - - {/if} - + {#if !hideHeader} + + {#if person} + + + {formatName(person?.name ?? '')} + + + {/if} + + {/if} {#if $$slots.content} @@ -88,23 +85,21 @@ use:resizeObserver={(element) => (clientWidth = element.clientWidth)} > - - + + - {#if _person} - + {#if person} + {:else} {/if} - {#if showPersonName && _person} - {formatName(_person?.name ?? '')} + {#if showPersonName && person} + {formatName(person?.name ?? '')} {/if} {#if showSeparator} {#if showPersonName} - {:else} - {/if} {/if} @@ -162,6 +157,10 @@ gap: 0.25rem; color: var(--global-primary-TextColor); font-size: 0.875rem; + + &.noGap { + gap: 0; + } } &__avatar { diff --git a/plugins/inbox-resources/src/index.ts b/plugins/inbox-resources/src/index.ts new file mode 100644 index 00000000000..3484325d51c --- /dev/null +++ b/plugins/inbox-resources/src/index.ts @@ -0,0 +1,24 @@ +// +// 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 Resources } from '@hcengineering/platform' + +import InboxApplication from './components/InboxApplication.svelte' + +export default async (): Promise => ({ + component: { + InboxApplication + } +}) diff --git a/plugins/inbox-resources/src/location.ts b/plugins/inbox-resources/src/location.ts new file mode 100644 index 00000000000..0a4f12c2f35 --- /dev/null +++ b/plugins/inbox-resources/src/location.ts @@ -0,0 +1,249 @@ +// +// 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 { Class, Doc, Ref, WithLookup } from '@hcengineering/core' +import { + navigate, + type Location, + getCurrentResolvedLocation, + getLocation, + locationStorageKeyId, + getCurrentLocation +} from '@hcengineering/ui' +import { inboxId } from '@hcengineering/inbox' +import { type MessageID, type Notification } from '@hcengineering/communication-types' +import { decodeObjectURI, encodeObjectURI } from '@hcengineering/view' +import activity, { type ActivityMessage } from '@hcengineering/activity' +import { + type ActivityInboxNotification, + type DocNotifyContext, + type InboxNotification +} from '@hcengineering/notification' +import { getResource } from '@hcengineering/platform' +import chunter, { type ThreadMessage } from '@hcengineering/chunter' +import { isActivityMessageClass, messageInFocus } from '@hcengineering/activity-resources' +import { getClient } from '@hcengineering/presentation' +import { isMentionNotification, isReactionNotification } from '@hcengineering/notification-resources' +import { type NavigationItem } from './type' + +// Url: /inbox/{_class}&{_id}/{thread}?message={messageId} + +export function getDocInfoFromLocation (loc: Location): Pick | undefined { + if (loc.path[2] !== inboxId) { + return undefined + } + + const [_id, _class] = decodeObjectURI(loc.path[3]) + + if (_id == null || _class == null) return undefined + if (_id === '' || _class === '') return undefined + + return { + _id, + _class + } +} + +export function getMessageInfoFromLocation (loc: Location): +| { + id: Ref | MessageID + date?: Date +} +| undefined { + if (loc.path[2] !== inboxId) return undefined + if (loc.query?.message == null || loc.query.message === '') return undefined + + const [id, date] = decodeURIComponent(loc.query.message).split(':') + + if (id == null || id === '') return undefined + + return { + id: id as any, + date: date != null ? new Date(date) : undefined + } +} + +export function navigateToDoc ( + navItem: NavigationItem, + doc: Doc, + notification?: InboxNotification | Notification +): void { + if (navItem.type === 'legacy') { + void selectInboxContext(navItem.context, doc, notification as InboxNotification) + return + } + + const loc = getCurrentResolvedLocation() + + loc.path[2] = inboxId + loc.path[3] = encodeObjectURI(navItem._id, navItem._class) + + loc.path[4] = '' + loc.path.length = 4 + + delete loc.fragment + + const nn = notification as Notification | undefined + if (nn?.message != null) { + loc.query = { + ...loc.query, + message: encodeURIComponent(`${nn.messageId}:${nn.message.created.toISOString()}`) + } + } else { + delete loc.query?.message + } + + navigate(loc) +} + +export function closeDoc (): void { + const loc = getCurrentResolvedLocation() + + loc.path[2] = inboxId + loc.path[3] = '' + loc.path[4] = '' + loc.path.length = 3 + + delete loc.query?.message + delete loc.fragment + + localStorage.setItem(`${locationStorageKeyId}_${inboxId}`, JSON.stringify(loc)) + + navigate(loc) +} + +async function navigateToInboxDoc ( + context: Ref, + _id: Ref, + _class: Ref>, + thread?: Ref, + message?: Ref +): Promise { + const loc = getLocation() + + if (loc.path[2] !== inboxId) { + return + } + + loc.path[3] = encodeObjectURI(_id, _class) + + if (thread !== undefined) { + loc.path[4] = thread + loc.path.length = 5 + const fn = await getResource(chunter.function.OpenThreadInSidebar) + void fn(thread, undefined, undefined, message, { autofocus: false }) + } else { + loc.path[4] = '' + loc.path.length = 4 + } + + loc.query = { ...loc.query, context, message: message ?? null } + messageInFocus.set(message) + navigate(loc) +} + +export async function selectInboxContext ( + context: DocNotifyContext, + doc?: Doc, + notification?: WithLookup +): Promise { + const client = getClient() + const hierarchy = client.getHierarchy() + const { objectId, objectClass } = context + const loc = getCurrentLocation() + + if (isMentionNotification(notification) && isActivityMessageClass(notification.mentionedInClass)) { + const selectedMsg = notification.mentionedIn as Ref + + void navigateToInboxDoc( + context._id, + objectId, + objectClass, + isActivityMessageClass(objectClass) ? (objectId as Ref) : undefined, + selectedMsg + ) + + return + } + + if (isReactionNotification(notification)) { + const thread = hierarchy.isDerived(context.objectClass, activity.class.ActivityMessage) + ? context.objectId + : undefined + const reactedTo = await client.findOne(activity.class.ActivityMessage, { _id: notification.attachedTo }) + const isThread = reactedTo != null && hierarchy.isDerived(reactedTo._class, chunter.class.ThreadMessage) + const channelId = isThread ? (reactedTo as ThreadMessage)?.objectId : (reactedTo?.attachedTo ?? objectId) + const channelClass = isThread + ? (reactedTo as ThreadMessage)?.objectClass + : (reactedTo?.attachedToClass ?? objectClass) + + void navigateToInboxDoc( + context._id, + channelId, + channelClass, + thread as Ref, + notification.attachedTo + ) + return + } + + if (hierarchy.isDerived(objectClass, activity.class.ActivityMessage)) { + const message = (notification as WithLookup)?.$lookup?.attachedTo + + if (objectClass === chunter.class.ThreadMessage) { + const thread = + doc?._id === objectId + ? (doc as ThreadMessage) + : await client.findOne( + chunter.class.ThreadMessage, + { + _id: objectId as Ref + }, + { projection: { _id: 1, attachedTo: 1 } } + ) + + void navigateToInboxDoc( + context._id, + thread?.objectId ?? objectId, + thread?.objectClass ?? objectClass, + thread?.attachedTo, + thread?._id + ) + return + } + + const selectedMsg = (notification as ActivityInboxNotification)?.attachedTo + const thread = selectedMsg !== objectId ? objectId : loc.path[4] === objectId ? objectId : undefined + const channelId = (doc as ActivityMessage)?.attachedTo ?? message?.attachedTo ?? objectId + const channelClass = (doc as ActivityMessage)?.attachedToClass ?? message?.attachedToClass ?? objectClass + + void navigateToInboxDoc( + context._id, + channelId, + channelClass, + thread as Ref, + selectedMsg ?? (objectId as Ref) + ) + return + } + + void navigateToInboxDoc( + context._id, + objectId, + objectClass, + undefined, + (notification as ActivityInboxNotification)?.attachedTo + ) +} diff --git a/plugins/inbox-resources/src/plugin.ts b/plugins/inbox-resources/src/plugin.ts new file mode 100644 index 00000000000..84c35cf59b7 --- /dev/null +++ b/plugins/inbox-resources/src/plugin.ts @@ -0,0 +1,34 @@ +// +// 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 inbox, { inboxId } from '@hcengineering/inbox' +import { type IntlString, mergeIds } from '@hcengineering/platform' +import type { AnyComponent } from '@hcengineering/ui' + +export default mergeIds(inboxId, inbox, { + component: { + InboxApplication: '' as AnyComponent + }, + string: { + ReactedToYourMessage: '' as IntlString, + ClearAll: '' as IntlString, + InboxIsClear: '' as IntlString, + YouDontHaveAnyNewMessages: '' as IntlString, + ReadAll: '' as IntlString, + Clearing: '' as IntlString, + Reading: '' as IntlString, + HideUserNames: '' as IntlString + } +}) diff --git a/plugins/inbox-resources/src/settings.ts b/plugins/inbox-resources/src/settings.ts new file mode 100644 index 00000000000..38d3040d83d --- /dev/null +++ b/plugins/inbox-resources/src/settings.ts @@ -0,0 +1,85 @@ +// Copyright © 2025 Hardcore Engineering Inc. +// +// Licensed under the Eclipse Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. You may +// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// See the License for the specific language governing permissions and +// limitations under the License. + +import { get, writable } from 'svelte/store' +import { getCurrentAccount } from '@hcengineering/core' +import { location } from '@hcengineering/ui' + +export interface ViewSetting { + id: string + on: boolean +} + +export const hideUserNamesSettingId = 'view-settings.hide-user-names' + +export const viewSettingsStore = writable([]) + +export function isViewSettingEnabled (settings: ViewSetting[], id: string): boolean { + const index = settings.findIndex((it) => it.id === id) + if (index === -1) return false + return settings[index].on +} + +export function updateViewSetting (id: string, on: boolean): void { + const settings = get(viewSettingsStore) + const index = settings.findIndex((it) => it.id === id) + if (index === -1) { + viewSettingsStore.set([...settings, { id, on }]) + } else { + viewSettingsStore.set(settings.map((it) => (it.id === id ? { ...it, on } : it))) + } +} + +let prevWorkspace: string | undefined + +location.subscribe((loc) => { + const workspace = loc.path[1] + if (prevWorkspace !== workspace) { + initViewSettings() + prevWorkspace = workspace + } +}) + +viewSettingsStore.subscribe((settings) => { + const key = geLocalStorageKey() + if (key === undefined) return + localStorage.setItem(key, JSON.stringify(settings)) +}) + +function geLocalStorageKey (): string | undefined { + const me = getCurrentAccount() + const loc = get(location) + const workspace = loc.path[1] ?? '' + if (me == null || workspace === '') return undefined + return `${workspace}.${me.uuid}.inbox.view-settings.v1` +} + +function initViewSettings (): void { + const key = geLocalStorageKey() + if (key === undefined) return + + const stored = localStorage.getItem(key) + + if (stored == null) { + viewSettingsStore.set([]) + return + } + + try { + const data = JSON.parse(stored) + Array.isArray(data) ? viewSettingsStore.set(data as ViewSetting[]) : viewSettingsStore.set([]) + } catch (e) { + console.error(e) + viewSettingsStore.set([]) + } +} diff --git a/plugins/inbox-resources/src/type.ts b/plugins/inbox-resources/src/type.ts new file mode 100644 index 00000000000..63e48a76e10 --- /dev/null +++ b/plugins/inbox-resources/src/type.ts @@ -0,0 +1,34 @@ +// 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 Card } from '@hcengineering/card' +import { type Class, type Doc, type Ref } from '@hcengineering/core' +import { type NotificationContext } from '@hcengineering/communication-types' +import { type DisplayInboxNotification, type DocNotifyContext } from '@hcengineering/notification' + +export type NavigationItem = + | { + type: 'modern' + _id: Ref + _class: Ref> + context: NotificationContext + date: Date + } + | { + type: 'legacy' + _id: Ref + _class: Ref> + context: DocNotifyContext + date: Date + notifications: DisplayInboxNotification[] + } diff --git a/plugins/inbox-resources/svelte.config.js b/plugins/inbox-resources/svelte.config.js new file mode 100644 index 00000000000..944a06f73ee --- /dev/null +++ b/plugins/inbox-resources/svelte.config.js @@ -0,0 +1,5 @@ +const sveltePreprocess = require('svelte-preprocess') + +module.exports = { + preprocess: sveltePreprocess() +}; \ No newline at end of file diff --git a/plugins/inbox-resources/tsconfig.json b/plugins/inbox-resources/tsconfig.json new file mode 100644 index 00000000000..14ada1dc344 --- /dev/null +++ b/plugins/inbox-resources/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "./node_modules/@hcengineering/platform-rig/profiles/ui/tsconfig.json", + + "compilerOptions": { + "rootDir": "./src", + "outDir": "./lib", + "declarationDir": "./types" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "lib", "dist", "types", "bundle"] +} \ No newline at end of file diff --git a/plugins/inbox/.eslintrc.js b/plugins/inbox/.eslintrc.js new file mode 100644 index 00000000000..72235dc2833 --- /dev/null +++ b/plugins/inbox/.eslintrc.js @@ -0,0 +1,7 @@ +module.exports = { + extends: ['./node_modules/@hcengineering/platform-rig/profiles/default/eslint.config.json'], + parserOptions: { + tsconfigRootDir: __dirname, + project: './tsconfig.json' + } +} diff --git a/plugins/inbox/.npmignore b/plugins/inbox/.npmignore new file mode 100644 index 00000000000..e3ec093c383 --- /dev/null +++ b/plugins/inbox/.npmignore @@ -0,0 +1,4 @@ +* +!/lib/** +!CHANGELOG.md +/lib/**/__tests__/ diff --git a/plugins/inbox/config/rig.json b/plugins/inbox/config/rig.json new file mode 100644 index 00000000000..0110930f55e --- /dev/null +++ b/plugins/inbox/config/rig.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json", + "rigPackageName": "@hcengineering/platform-rig" +} diff --git a/plugins/inbox/jest.config.js b/plugins/inbox/jest.config.js new file mode 100644 index 00000000000..2cfd408b679 --- /dev/null +++ b/plugins/inbox/jest.config.js @@ -0,0 +1,7 @@ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'], + roots: ["./src"], + coverageReporters: ["text-summary", "html"] +} diff --git a/plugins/inbox/package.json b/plugins/inbox/package.json new file mode 100644 index 00000000000..3704a5268bd --- /dev/null +++ b/plugins/inbox/package.json @@ -0,0 +1,47 @@ +{ + "name": "@hcengineering/inbox", + "version": "0.7.0", + "main": "lib/index.js", + "svelte": "src/index.ts", + "types": "types/index.d.ts", + "files": [ + "lib/**/*", + "types/**/*", + "tsconfig.json" + ], + "author": "Hardcore Engineering Inc", + "license": "EPL-2.0", + "scripts": { + "build": "compile", + "build:watch": "compile", + "format": "format src", + "test": "jest --passWithNoTests --silent", + "_phase:build": "compile transpile src", + "_phase:test": "jest --passWithNoTests --silent", + "_phase:format": "format src", + "_phase:validate": "compile validate" + }, + "devDependencies": { + "@hcengineering/platform-rig": "^0.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", + "jest": "^29.7.0", + "ts-jest": "^29.1.1", + "@types/jest": "^29.5.5" + }, + "dependencies": { + "@hcengineering/core": "^0.7.8", + "@hcengineering/platform": "^0.7.5" + }, + "repository": "https://github.com/hcengineering/platform", + "publishConfig": { + "registry": "https://npm.pkg.github.com" + } +} diff --git a/plugins/inbox/src/index.ts b/plugins/inbox/src/index.ts new file mode 100644 index 00000000000..b7e188ae916 --- /dev/null +++ b/plugins/inbox/src/index.ts @@ -0,0 +1,29 @@ +// +// 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 { Asset, IntlString, type Plugin, plugin } from '@hcengineering/platform' + +export const inboxId = 'inbox' as Plugin + +const inbox = plugin(inboxId, { + string: { + Inbox: '' as IntlString + }, + icon: { + Inbox: '' as Asset + } +}) + +export default inbox diff --git a/plugins/inbox/tsconfig.json b/plugins/inbox/tsconfig.json new file mode 100644 index 00000000000..b5ae22f6e46 --- /dev/null +++ b/plugins/inbox/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "./node_modules/@hcengineering/platform-rig/profiles/default/tsconfig.json", + + "compilerOptions": { + "rootDir": "./src", + "outDir": "./lib", + "declarationDir": "./types", + "tsBuildInfoFile": ".build/build.tsbuildinfo" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "lib", "dist", "types", "bundle"] +} \ No newline at end of file diff --git a/plugins/notification-resources/package.json b/plugins/notification-resources/package.json index 0b7ba93a87b..14443e1a2ed 100644 --- a/plugins/notification-resources/package.json +++ b/plugins/notification-resources/package.json @@ -56,6 +56,7 @@ "@hcengineering/view": "^0.7.0", "@hcengineering/view-resources": "^0.7.0", "@hcengineering/workbench": "^0.7.0", + "@hcengineering/emoji-resources": "^0.7.0", "svelte": "^4.2.20" } } diff --git a/plugins/notification-resources/src/components/ReactionNotificationPresenter.svelte b/plugins/notification-resources/src/components/ReactionNotificationPresenter.svelte deleted file mode 100644 index b36762290e3..00000000000 --- a/plugins/notification-resources/src/components/ReactionNotificationPresenter.svelte +++ /dev/null @@ -1,37 +0,0 @@ - - - - r.emoji).join('')} {message} on:click /> diff --git a/plugins/notification-resources/src/components/inbox/ActivityInboxNotificationPresenter.svelte b/plugins/notification-resources/src/components/inbox/ActivityInboxNotificationPresenter.svelte index 47bcc9c34a9..6c33a6f0133 100644 --- a/plugins/notification-resources/src/components/inbox/ActivityInboxNotificationPresenter.svelte +++ b/plugins/notification-resources/src/components/inbox/ActivityInboxNotificationPresenter.svelte @@ -44,7 +44,7 @@ $: void updateDisplayMessage(value.combinedMessages) async function updateDisplayMessage (messages: ActivityMessage[]): Promise { - const combinedMessages = await combineActivityMessages(sortActivityMessages(messages)) + const combinedMessages = combineActivityMessages(sortActivityMessages(messages)) displayMessage = combinedMessages[0] } @@ -119,6 +119,6 @@ on:click /> {:else} - + {/if} {/if} diff --git a/plugins/notification-resources/src/components/inbox/Inbox.svelte b/plugins/notification-resources/src/components/inbox/Inbox.svelte index e96842e924e..2b048490fe2 100644 --- a/plugins/notification-resources/src/components/inbox/Inbox.svelte +++ b/plugins/notification-resources/src/components/inbox/Inbox.svelte @@ -136,9 +136,9 @@ showArchive: boolean ): Promise { if (showArchive) { - inboxData = await getDisplayInboxData(groupByArray(archivedNotifications, (it) => it.docNotifyContext)) + inboxData = getDisplayInboxData(groupByArray(archivedNotifications, (it) => it.docNotifyContext)) } else { - inboxData = await getDisplayInboxData(notificationsByContext) + inboxData = getDisplayInboxData(notificationsByContext) } } diff --git a/plugins/notification-resources/src/components/inbox/InboxNotificationPresenter.svelte b/plugins/notification-resources/src/components/inbox/InboxNotificationPresenter.svelte index 3f485ae988e..22f99b500fe 100644 --- a/plugins/notification-resources/src/components/inbox/InboxNotificationPresenter.svelte +++ b/plugins/notification-resources/src/components/inbox/InboxNotificationPresenter.svelte @@ -13,11 +13,20 @@ // limitations under the License. --> -{#if objectPresenter} - +{#if hierarchy.isDerived(value._class, notification.class.ActivityInboxNotification)} + +{:else if hierarchy.isDerived(value._class, notification.class.MentionInboxNotification)} + +{:else if hierarchy.isDerived(value._class, notification.class.ReactionInboxNotification)} + +{:else if hierarchy.isDerived(value._class, notification.class.CommonInboxNotification)} + {/if} diff --git a/plugins/notification-resources/src/components/inbox/ReactionInboxNotificationPresenter.svelte b/plugins/notification-resources/src/components/inbox/ReactionInboxNotificationPresenter.svelte new file mode 100644 index 00000000000..bf1c562ab26 --- /dev/null +++ b/plugins/notification-resources/src/components/inbox/ReactionInboxNotificationPresenter.svelte @@ -0,0 +1,84 @@ + + + +
+ + +
+
+ +
+ {#if message} + + {/if} +
+
+ + diff --git a/plugins/notification-resources/src/inboxNotificationsClient.ts b/plugins/notification-resources/src/inboxNotificationsClient.ts index b9519e12117..bd219949b49 100644 --- a/plugins/notification-resources/src/inboxNotificationsClient.ts +++ b/plugins/notification-resources/src/inboxNotificationsClient.ts @@ -219,6 +219,31 @@ export class InboxNotificationsClientImpl implements InboxNotificationsClient { } } + async removeAllNotifications (): Promise { + const ops = getClient().apply(undefined, 'removeAllNotifications', true) + + try { + const inboxNotifications = await ops.findAll( + notification.class.InboxNotification, + { + user: getCurrentAccount().uuid, + archived: false + }, + { projection: { _id: 1, _class: 1, space: 1 } } + ) + const contexts = get(this.contexts) ?? [] + for (const notification of inboxNotifications) { + await ops.removeDoc(notification._class, notification.space, notification._id) + } + + for (const context of contexts) { + await ops.update(context, { lastViewedTimestamp: Date.now() }) + } + } finally { + await ops.commit() + } + } + async archiveAllNotifications (): Promise { const ops = getClient().apply(undefined, 'archiveAllNotifications', true) diff --git a/plugins/notification-resources/src/index.ts b/plugins/notification-resources/src/index.ts index eb17ebfbe76..1fafb1be992 100644 --- a/plugins/notification-resources/src/index.ts +++ b/plugins/notification-resources/src/index.ts @@ -25,7 +25,6 @@ import ActivityInboxNotificationPresenter from './components/inbox/ActivityInbox import CommonInboxNotificationPresenter from './components/inbox/CommonInboxNotificationPresenter.svelte' import MentionInboxNotificationPresenter from './components/inbox/MentionInboxNotificationPresenter.svelte' import NotificationCollaboratorsChanged from './components/NotificationCollaboratorsChanged.svelte' -import ReactionNotificationPresenter from './components/ReactionNotificationPresenter.svelte' import CollaboratorEditor from './components/CollaboratorEditor.svelte' import GeneralPreferencesGroup from './components/settings/GeneralPreferencesGroup.svelte' import { @@ -69,7 +68,6 @@ export default async (): Promise => ({ CommonInboxNotificationPresenter, MentionInboxNotificationPresenter, NotificationCollaboratorsChanged, - ReactionNotificationPresenter, GeneralPreferencesGroup, CollaboratorEditor }, diff --git a/plugins/notification-resources/src/utils.ts b/plugins/notification-resources/src/utils.ts index c3799c6a933..e0a1dd69910 100644 --- a/plugins/notification-resources/src/utils.ts +++ b/plugins/notification-resources/src/utils.ts @@ -22,7 +22,6 @@ import { activityMessagesComparator, combineActivityMessages, isActivityMessageClass, - isReactionMessage, messageInFocus } from '@hcengineering/activity-resources' import { Analytics } from '@hcengineering/analytics' @@ -47,7 +46,8 @@ import notification, { type NotificationProvider, type NotificationProviderSetting, type NotificationType, - type NotificationTypeSetting + type NotificationTypeSetting, + type ReactionInboxNotification } from '@hcengineering/notification' import { getMetadata, getResource } from '@hcengineering/platform' import { createQuery, getClient, MessageBox } from '@hcengineering/presentation' @@ -200,6 +200,27 @@ export async function archiveContextNotifications (doc?: DocNotifyContext): Prom } } +export async function removeContextNotifications (doc?: DocNotifyContext): Promise { + if (doc === undefined) return + + const ops = getClient().apply(undefined, 'removeContextNotifications', true) + + try { + const notifications = await ops.findAll( + notification.class.InboxNotification, + { docNotifyContext: doc._id, archived: false }, + { projection: { _id: 1, _class: 1, space: 1 } } + ) + + for (const notification of notifications) { + await ops.removeDoc(notification._class, notification.space, notification._id) + } + await ops.update(doc, { lastViewedTimestamp: Date.now() }) + } finally { + await ops.commit() + } +} + /** * @public */ @@ -324,11 +345,16 @@ export function isMentionNotification (doc?: InboxNotification): doc is MentionI return doc._class === notification.class.MentionInboxNotification } -export async function getDisplayInboxNotifications ( +export function isReactionNotification (doc?: InboxNotification): doc is ReactionInboxNotification { + if (doc === undefined) return false + return doc._class === notification.class.ReactionInboxNotification +} + +export function getDisplayInboxNotifications ( notifications: Array>, filter: InboxNotificationsFilter = 'all', objectClass?: Ref> -): Promise { +): DisplayInboxNotification[] { const result: DisplayInboxNotification[] = [] const activityNotifications: Array> = [] for (const notification of notifications) { @@ -359,10 +385,7 @@ export async function getDisplayInboxNotifications ( return (message as DocUpdateMessage).objectClass === objectClass }) - const combinedMessages = await combineActivityMessages( - messages.sort(activityMessagesComparator), - SortingOrder.Descending - ) + const combinedMessages = combineActivityMessages(messages.sort(activityMessagesComparator), SortingOrder.Descending) for (const message of combinedMessages) { if (message._class === activity.class.DocUpdateMessage) { @@ -403,17 +426,17 @@ export async function getDisplayInboxNotifications ( ) } -export async function getDisplayInboxData ( +export function getDisplayInboxData ( notificationsByContext: Map, InboxNotification[]>, filter: InboxNotificationsFilter = 'all', objectClass?: Ref> -): Promise { +): InboxData { const result: InboxData = new Map() for (const key of notificationsByContext.keys()) { const notifications = notificationsByContext.get(key) ?? [] - const displayNotifications = await getDisplayInboxNotifications(notifications, filter, objectClass) + const displayNotifications = getDisplayInboxNotifications(notifications, filter, objectClass) if (displayNotifications.length > 0) { result.set(key, displayNotifications) @@ -426,7 +449,7 @@ export async function getDisplayInboxData ( export async function hasInboxNotifications ( notificationsByContext: Map, InboxNotification[]> ): Promise { - const unreadInboxData = await getDisplayInboxData(notificationsByContext, 'unread') + const unreadInboxData = getDisplayInboxData(notificationsByContext, 'unread') return unreadInboxData.size > 0 } @@ -439,7 +462,7 @@ export async function getNotificationsCount ( return 0 } - const unreadNotifications = await getDisplayInboxNotifications(notifications, 'unread') + const unreadNotifications = getDisplayInboxNotifications(notifications, 'unread') return unreadNotifications.length } @@ -589,6 +612,27 @@ export async function selectInboxContext ( return } + + if (isReactionNotification(notification)) { + const thread = loc.path[4] === objectId ? objectId : undefined + const reactedTo = await client.findOne(activity.class.ActivityMessage, { _id: notification.attachedTo }) + const isThread = reactedTo != null && hierarchy.isDerived(reactedTo._class, chunter.class.ThreadMessage) + const channelId = isThread ? (reactedTo as ThreadMessage)?.objectId : (reactedTo?.attachedTo ?? objectId) + const channelClass = isThread + ? (reactedTo as ThreadMessage)?.objectClass + : (reactedTo?.attachedToClass ?? objectClass) + + void navigateToInboxDoc( + linkProviders, + context._id, + channelId, + channelClass, + thread as Ref, + objectId as Ref + ) + return + } + if (hierarchy.isDerived(objectClass, activity.class.ActivityMessage)) { const message = (notification as WithLookup)?.$lookup?.attachedTo @@ -615,28 +659,6 @@ export async function selectInboxContext ( return } - if (isReactionMessage(message)) { - const thread = loc.path[4] === objectId ? objectId : undefined - const reactedTo = - (object as ActivityMessage) ?? - (await client.findOne(activity.class.ActivityMessage, { _id: message.attachedTo as Ref })) - const isThread = hierarchy.isDerived(reactedTo._class, chunter.class.ThreadMessage) - const channelId = isThread ? (reactedTo as ThreadMessage)?.objectId : (reactedTo?.attachedTo ?? objectId) - const channelClass = isThread - ? (reactedTo as ThreadMessage)?.objectClass - : (reactedTo?.attachedToClass ?? objectClass) - - void navigateToInboxDoc( - linkProviders, - context._id, - channelId, - channelClass, - thread as Ref, - objectId as Ref - ) - return - } - const selectedMsg = (notification as ActivityInboxNotification)?.attachedTo const thread = selectedMsg !== objectId ? objectId : loc.path[4] === objectId ? objectId : undefined const channelId = (object as ActivityMessage)?.attachedTo ?? message?.attachedTo ?? objectId diff --git a/plugins/notification/src/index.ts b/plugins/notification/src/index.ts index 796f3e68d05..767125c41cc 100644 --- a/plugins/notification/src/index.ts +++ b/plugins/notification/src/index.ts @@ -13,7 +13,7 @@ // limitations under the License. // -import { ActivityMessage } from '@hcengineering/activity' +import { ActivityMessage, Reaction } from '@hcengineering/activity' import { PersonId, AnyAttribute, @@ -265,6 +265,13 @@ export interface CommonInboxNotification extends InboxNotification { iconProps?: Record } +export interface ReactionInboxNotification extends CommonInboxNotification { + emoji: string + ref: Ref + attachedTo: Ref + attachedToClass: Ref> +} + export interface MentionInboxNotification extends CommonInboxNotification { mentionedIn: Ref mentionedInClass: Ref> @@ -314,6 +321,7 @@ export interface InboxNotificationsClient { unreadNotifications: (client: TxOperations, ids: Array>) => Promise archiveNotifications: (client: TxOperations, ids: Array>) => Promise archiveAllNotifications: () => Promise + removeAllNotifications: () => Promise readAllNotifications: () => Promise unreadAllNotifications: () => Promise } @@ -361,7 +369,8 @@ const notification = plugin(notificationId, { NotificationProvider: '' as Ref>, NotificationTypeSetting: '' as Ref>, NotificationProviderSetting: '' as Ref>, - NotificationProviderDefaults: '' as Ref> + NotificationProviderDefaults: '' as Ref>, + ReactionInboxNotification: '' as Ref> }, ids: { NotificationSettings: '' as Ref, @@ -386,7 +395,6 @@ const notification = plugin(notificationId, { CollaboratorsChanged: '' as AnyComponent, DocNotifyContextPresenter: '' as AnyComponent, NotificationCollaboratorsChanged: '' as AnyComponent, - ReactionNotificationPresenter: '' as AnyComponent, GeneralPreferencesGroup: '' as AnyComponent, CollaboratorEditor: '' as AnyComponent }, diff --git a/plugins/request-resources/src/components/RequestedChangedNotification.svelte b/plugins/request-resources/src/components/RequestedChangedNotification.svelte index 99e4c45da50..cb84691fa93 100644 --- a/plugins/request-resources/src/components/RequestedChangedNotification.svelte +++ b/plugins/request-resources/src/components/RequestedChangedNotification.svelte @@ -13,7 +13,7 @@ // limitations under the License. --> - +
diff --git a/plugins/training-resources/src/components/TrainingRequestNotificationPresenter.svelte b/plugins/training-resources/src/components/TrainingRequestNotificationPresenter.svelte index d4c5ecb6f15..ceb602d2df4 100644 --- a/plugins/training-resources/src/components/TrainingRequestNotificationPresenter.svelte +++ b/plugins/training-resources/src/components/TrainingRequestNotificationPresenter.svelte @@ -16,13 +16,14 @@ --> - + diff --git a/plugins/view-resources/src/components/ObjectIcon.svelte b/plugins/view-resources/src/components/ObjectIcon.svelte index e4584a817c9..0500999fba0 100644 --- a/plugins/view-resources/src/components/ObjectIcon.svelte +++ b/plugins/view-resources/src/components/ObjectIcon.svelte @@ -25,6 +25,7 @@ export let size: IconSize = 'small' export let icon: Asset | AnySvelteComponent | undefined = undefined export let withObjectIcon = false + export let showLoading = true const client = getClient() const hierarchy = client.getHierarchy() @@ -38,7 +39,7 @@ {#if iconMixin} - + {:else} {@const objectIcon = icon ?? classIcon(client, value._class) ?? getObjectIcon(value)} {#if objectIcon} diff --git a/plugins/workbench-resources/package.json b/plugins/workbench-resources/package.json index da66e06e02f..4a5980fe7e4 100644 --- a/plugins/workbench-resources/package.json +++ b/plugins/workbench-resources/package.json @@ -57,6 +57,7 @@ "@hcengineering/support": "^0.7.0", "@hcengineering/support-resources": "^0.7.0", "@hcengineering/view-resources": "^0.7.0", + "@hcengineering/inbox": "^0.7.0", "fast-copy": "^3.0.2", "@hcengineering/analytics": "^0.7.5", "@hcengineering/account-client": "^0.7.6", diff --git a/plugins/workbench-resources/src/components/Applications.svelte b/plugins/workbench-resources/src/components/Applications.svelte index 38929125711..80f1265a4b9 100644 --- a/plugins/workbench-resources/src/components/Applications.svelte +++ b/plugins/workbench-resources/src/components/Applications.svelte @@ -21,7 +21,10 @@ import type { Application } from '@hcengineering/workbench' import workbench from '@hcengineering/workbench' import { chatId } from '@hcengineering/chat' - import { getMetadata } from '@hcengineering/platform' + import { inboxId } from '@hcengineering/inbox' + import { getMetadata, getResource } from '@hcengineering/platform' + import { InboxNotificationsClientImpl } from '@hcengineering/notification-resources' + import notification, { DocNotifyContext, InboxNotification } from '@hcengineering/notification' import AppItem from './AppItem.svelte' @@ -92,6 +95,31 @@ $: bottomApps = apps.filter( (it) => it.position === 'bottom' && !hiddenAppsIds.includes(it._id) && !excludedApps.includes(it.alias) ) + + const inboxClient = InboxNotificationsClientImpl.getClient() + const inboxNotificationsByContextStore = inboxClient.inboxNotificationsByContext + + let hasNotificationsFn: ((data: Map, InboxNotification[]>) => Promise) | undefined = + undefined + let hasInboxNotifications = false + + void getResource(notification.function.HasInboxNotifications).then((f) => { + hasNotificationsFn = f + }) + + $: void hasNotificationsFn?.($inboxNotificationsByContextStore).then((res) => { + hasInboxNotifications = res + }) + + function showNotify (alias: string, hasOldNotifications: boolean, hasNewNotifications: boolean): boolean { + if (alias === inboxId) { + return hasOldNotifications || hasNewNotifications + } + if (alias === chatId) { + return hasNewNotifications + } + return false + }
@@ -113,7 +141,7 @@ icon={app.icon} label={app.label} navigator={app._id === active && $deviceInfo.navigator.visible} - notify={app.alias === chatId && hasNewInboxNotifications} + notify={showNotify(app.alias, hasInboxNotifications, hasNewInboxNotifications)} {...customProps} on:click={getClickHandler(app, customProps)} /> diff --git a/plugins/workbench-resources/src/components/Workbench.svelte b/plugins/workbench-resources/src/components/Workbench.svelte index 16ef0af3373..380156d6b90 100644 --- a/plugins/workbench-resources/src/components/Workbench.svelte +++ b/plugins/workbench-resources/src/components/Workbench.svelte @@ -160,15 +160,6 @@ .findAllSync(workbench.class.Application, { hidden: false, _id: { $nin: excludedApps } }) .filter((it) => isAllowedToRole(it.accessLevel, account)) - if (isCommunicationEnabled) { - const notificationApp = client - .getModel() - .findAllSync(workbench.class.Application, { alias: notificationId })[0] - if (notificationApp && !apps.some((a) => a._id === notificationApp._id)) { - apps.push(notificationApp) - } - } - let panelInstance: PanelInstance let popupInstance: Popup diff --git a/pods/server/package.json b/pods/server/package.json index c3bbdb2c5c8..98e3bdcdf50 100644 --- a/pods/server/package.json +++ b/pods/server/package.json @@ -84,6 +84,7 @@ "@hcengineering/server-storage": "^0.7.8", "@hcengineering/server-telegram": "^0.7.0", "@hcengineering/server-token": "^0.7.5", + "@hcengineering/server-card": "^0.7.0", "utf-8-validate": "^6.0.4", "bufferutil": "^4.0.8", "msgpackr": "^1.11.2", diff --git a/pods/server/src/__start.ts b/pods/server/src/__start.ts index 6e68e4824bf..7ce5cc99b5d 100644 --- a/pods/server/src/__start.ts +++ b/pods/server/src/__start.ts @@ -13,6 +13,7 @@ import { setDBExtraOptions } from '@hcengineering/postgres' import { serverConfigFromEnv } from '@hcengineering/server' import serverAiBot from '@hcengineering/server-ai-bot' import serverCalendar from '@hcengineering/server-calendar' +import serverCard from '@hcengineering/server-card' import serverCore, { initStatisticsContext, loadBrandingMap, @@ -85,6 +86,7 @@ setMetadata(serverNotification.metadata.MailAuthToken, config.mailAuthToken) setMetadata(serverNotification.metadata.WebPushUrl, config.webPushUrl) setMetadata(serverAiBot.metadata.EndpointURL, process.env.AI_BOT_URL) setMetadata(serverCalendar.metadata.EndpointURL, process.env.CALENDAR_URL) +setMetadata(serverCard.metadata.CommunicationEnabled, process.env.COMMUNICATION_API_ENABLED === 'true') const { shutdown, sessionManager } = start(metricsContext, config.dbUrl, { fulltextUrl: config.fulltextUrl, diff --git a/rush.json b/rush.json index 374951fa9b2..8df64673b7c 100644 --- a/rush.json +++ b/rush.json @@ -2236,6 +2236,26 @@ "projectFolder": "server-plugins/process-resources", "shouldPublish": false }, + { + "packageName": "@hcengineering/inbox", + "projectFolder": "plugins/inbox", + "shouldPublish": false + }, + { + "packageName": "@hcengineering/inbox-assets", + "projectFolder": "plugins/inbox-assets", + "shouldPublish": false + }, + { + "packageName": "@hcengineering/inbox-resources", + "projectFolder": "plugins/inbox-resources", + "shouldPublish": false + }, + { + "packageName": "@hcengineering/model-inbox", + "projectFolder": "models/inbox", + "shouldPublish": false + }, { "packageName": "@hcengineering/pod-mail-worker", "projectFolder": "services/mail/pod-mail-worker", diff --git a/server-plugins/activity-resources/package.json b/server-plugins/activity-resources/package.json index 633d5a0110b..7b68c46e34e 100644 --- a/server-plugins/activity-resources/package.json +++ b/server-plugins/activity-resources/package.json @@ -46,6 +46,7 @@ "@hcengineering/server-notification-resources": "^0.7.0", "@hcengineering/text-core": "^0.7.5", "@hcengineering/communication-sdk-types": "^0.7.5", - "@hcengineering/communication-types": "^0.7.5" + "@hcengineering/communication-types": "^0.7.5", + "@hcengineering/server-card": "^0.7.0" } } diff --git a/server-plugins/activity-resources/src/index.ts b/server-plugins/activity-resources/src/index.ts index 249581af549..f73c7372e47 100644 --- a/server-plugins/activity-resources/src/index.ts +++ b/server-plugins/activity-resources/src/index.ts @@ -35,21 +35,28 @@ import core, { type Tx, type TxCreateDoc, type TxCUD, - TxProcessor + TxProcessor, + type TxRemoveDoc } from '@hcengineering/core' -import { getAccountBySocialId } from '@hcengineering/server-contact' -import notification, { type NotificationContent } from '@hcengineering/notification' -import { getResource, translate } from '@hcengineering/platform' +import { getAccountBySocialId, getPerson } from '@hcengineering/server-contact' +import notification, { + type NotificationContent, + type NotificationType, + type ReactionInboxNotification +} from '@hcengineering/notification' +import { getMetadata, getResource, translate } from '@hcengineering/platform' import { type ActivityControl, type DocObjectCache } from '@hcengineering/server-activity' import type { TriggerControl } from '@hcengineering/server-core' import { - createCollabDocInfo, createCollaboratorNotifications, - getTextPresenter, - removeDocInboxNotifications + getAllowedProviders, + getCommonNotificationTxes, + getNotificationProviderControl, + getReceiversInfo, + getTextPresenter } from '@hcengineering/server-notification-resources' -import { type Card } from '@hcengineering/card' -import { type Person } from '@hcengineering/contact' +import card, { type Card } from '@hcengineering/card' +import serverCard from '@hcengineering/server-card' import { ReferenceTrigger } from './references' import { getAttrName, getCollectionAttribute, getDocUpdateAction, getTxAttributesUpdates } from './utils' @@ -60,7 +67,7 @@ export async function OnReactionChanged (txes: Tx[], control: TriggerControl): P const innerTx = tx as TxCUD if (innerTx._class === core.class.TxCreateDoc) { - const txes = await createReactionNotifications(innerTx, control) + const txes = await createReactionNotifications(innerTx as TxCreateDoc, control) await control.apply(control.ctx, txes) continue @@ -76,85 +83,112 @@ export async function OnReactionChanged (txes: Tx[], control: TriggerControl): P return [] } -export async function removeReactionNotifications (tx: TxCUD, control: TriggerControl): Promise { - if (tx.attachedTo === undefined) return [] - const message = ( - await control.findAll( - control.ctx, - activity.class.ActivityMessage, - { objectId: tx.objectId }, - { projection: { _id: 1, _class: 1, space: 1 } } - ) - )[0] - - if (message === undefined) { - return [] - } - +export async function removeReactionNotifications (tx: TxRemoveDoc, control: TriggerControl): Promise { const res: Tx[] = [] - const txes = await removeDocInboxNotifications(message._id, control) - const removeTx = control.txFactory.createTxRemoveDoc(message._class, message.space, message._id) - - res.push(removeTx) - res.push(...txes) + const n = await control.findAll(control.ctx, notification.class.ReactionInboxNotification, { ref: tx.objectId }) + for (const nn of n) { + res.push(control.txFactory.createTxRemoveDoc(nn._class, nn.space, nn._id)) + } return res } -export async function createReactionNotifications (tx: TxCUD, control: TriggerControl): Promise { +export async function createReactionNotifications (tx: TxCreateDoc, control: TriggerControl): Promise { if (tx.attachedTo === undefined) return [] + + const reaction = TxProcessor.createDoc2Doc(tx) const parentMessage = ( - await control.findAll(control.ctx, activity.class.ActivityMessage, { _id: tx.attachedTo as Ref }) + await control.findAll(control.ctx, activity.class.ActivityMessage, { _id: reaction.attachedTo }) )[0] - if (parentMessage === undefined) { - return [] - } + if (parentMessage === undefined) return [] + + const doc = (await control.findAll(control.ctx, parentMessage.attachedToClass, { _id: parentMessage.attachedTo }))[0] + + if (doc === undefined) return [] const userSocialId = parentMessage.createdBy - if (userSocialId === undefined || userSocialId === core.account.System || userSocialId === tx.modifiedBy) { - return [] - } + if (userSocialId === undefined || userSocialId === core.account.System || userSocialId === tx.modifiedBy) return [] - let res: Tx[] = [] + const account = await getAccountBySocialId(control, userSocialId) - const rawMessage: Data = { - txId: tx._id, + if (account == null) return [] + + const receiver = (await getReceiversInfo(control.ctx, [account], control))[0] + if (receiver === undefined) return [] + + const res: Tx[] = [] + + const content = await reactionNotificationContentProvider(parentMessage, reaction, control) + const data: Partial> = { + emoji: reaction.emoji, attachedTo: parentMessage._id, attachedToClass: parentMessage._class, - objectId: tx.objectId, - objectClass: tx.objectClass, - action: 'create', - collection: 'docUpdateMessages', - updateCollection: tx.collection - } + ref: reaction._id, + ...content + } + + const senderPerson = await getPerson(control, tx.createdBy ?? tx.modifiedBy) + const sender = { + socialId: tx.createdBy ?? tx.modifiedBy, + person: senderPerson + } + const type: NotificationType = control.modelDb.findAllSync(notification.class.NotificationType, { + _id: activity.ids.AddReactionNotification + })[0] + + const notificationControl = await getNotificationProviderControl(control.ctx, control) + const allowedProviders = getAllowedProviders(control, receiver.socialIds, type, notificationControl) + const notifyResult = new Map(allowedProviders.map((it) => [it, [type]])) + + const txes = await getCommonNotificationTxes( + control.ctx, + control, + doc, + data, + receiver, + sender, + doc._id, + doc._class, + doc.space, + tx.modifiedOn, + notifyResult, + notification.class.ReactionInboxNotification, + tx + ) + res.push(...txes) - const messageTx = getDocUpdateMessageTx(control, tx, parentMessage, rawMessage, tx.modifiedBy) + return res +} - if (messageTx === undefined) { - return [] - } +async function reactionNotificationContentProvider ( + message: ActivityMessage, + reaction: Reaction, + control: TriggerControl +): Promise { + const presenter = getTextPresenter(message._class, control.hierarchy) - res.push(messageTx) + let text = '' - const docUpdateMessage = TxProcessor.createDoc2Doc(messageTx as TxCreateDoc) - const account = await getAccountBySocialId(control, userSocialId) + if (presenter !== undefined) { + const fn = await getResource(presenter.presenter) - if (account == null) { - return [] + text = await fn(message, control) + } else { + text = await translate(activity.string.Message, {}) } - res = res.concat( - await createCollabDocInfo(control.ctx, res, [account], control, tx, parentMessage, [docUpdateMessage], { - isOwn: true, - isSpace: false, - shouldUpdateTimestamp: false - }) - ) - - return res + return { + title: activity.string.ReactionNotificationTitle, + body: activity.string.ReactionNotificationBody, + data: reaction.emoji, + intlParams: { + title: text, + reaction: reaction.emoji + } + } } function isActivityDoc (_class: Ref>, hierarchy: Hierarchy): boolean { @@ -352,6 +386,8 @@ export async function generateDocUpdateMessages ( } async function ActivityMessagesHandler (_txes: TxCUD[], control: TriggerControl): Promise { + const isCommunicationEnabled = getMetadata(serverCard.metadata.CommunicationEnabled) ?? false + const ltxes = _txes.filter( (it) => !( @@ -369,6 +405,14 @@ async function ActivityMessagesHandler (_txes: TxCUD[], control: TriggerCon control.contextCache.set('ActivityMessagesHandler', cache) const result: Tx[] = [] for (const tx of ltxes) { + if (control.hierarchy.isDerived(tx.objectClass, card.class.Card) && isCommunicationEnabled) continue + if ( + tx.attachedToClass != null && + control.hierarchy.isDerived(tx.attachedToClass, card.class.Card) && + isCommunicationEnabled + ) { + continue + } const txes = tx.space === core.space.DerivedTx ? [] @@ -397,6 +441,13 @@ async function OnDocRemoved (txes: TxCUD[], control: TriggerControl): Promi continue } + if (control.hierarchy.isDerived(tx.objectClass, activity.class.ActivityMessage)) { + const reactionNotification = await control.findAll(control.ctx, notification.class.ReactionInboxNotification, { + attachedTo: tx.objectId as Ref + }) + result.push(...reactionNotification.map((it) => control.txFactory.createTxRemoveDoc(it._class, it.space, it._id))) + } + const activityDocMixin = control.hierarchy.classHierarchyMixin(tx.objectClass, activity.mixin.ActivityDoc) if (activityDocMixin === undefined) { @@ -417,37 +468,6 @@ async function OnDocRemoved (txes: TxCUD[], control: TriggerControl): Promi return result } -async function ReactionNotificationContentProvider ( - doc: ActivityMessage, - originTx: TxCUD, - _: Ref, - control: TriggerControl -): Promise { - const tx = originTx as TxCreateDoc - const presenter = getTextPresenter(doc._class, control.hierarchy) - const reaction = TxProcessor.createDoc2Doc(tx) - - let text = '' - - if (presenter !== undefined) { - const fn = await getResource(presenter.presenter) - - text = await fn(doc, control) - } else { - text = await translate(activity.string.Message, {}) - } - - return { - title: activity.string.ReactionNotificationTitle, - body: activity.string.ReactionNotificationBody, - data: reaction.emoji, - intlParams: { - title: text, - reaction: reaction.emoji - } - } -} - async function getAttributesUpdatesText ( attributeUpdates: DocAttributeUpdates, objectClass: Ref>, @@ -533,7 +553,6 @@ export default async () => ({ HandleCardActivity }, function: { - ReactionNotificationContentProvider, DocUpdateMessageTextPresenter } }) diff --git a/server-plugins/activity/src/index.ts b/server-plugins/activity/src/index.ts index cd8ce4bd8f3..f28fffb55b1 100644 --- a/server-plugins/activity/src/index.ts +++ b/server-plugins/activity/src/index.ts @@ -15,7 +15,7 @@ import { Plugin, Resource, plugin } from '@hcengineering/platform' import type { TriggerFunc } from '@hcengineering/server-core' -import { NotificationContentProvider, Presenter } from '@hcengineering/server-notification' +import { Presenter } from '@hcengineering/server-notification' export * from './types' export * from './utils' @@ -37,7 +37,6 @@ export default plugin(serverActivityId, { HandleCardActivity: '' as Resource }, function: { - ReactionNotificationContentProvider: '' as Resource, DocUpdateMessageTextPresenter: '' as Resource } }) diff --git a/server-plugins/card/src/index.ts b/server-plugins/card/src/index.ts index 78df7c6e6ec..39e2c597a3f 100644 --- a/server-plugins/card/src/index.ts +++ b/server-plugins/card/src/index.ts @@ -13,7 +13,7 @@ // limitations under the License. // -import type { Plugin, Resource } from '@hcengineering/platform' +import type { Metadata, Plugin, Resource } from '@hcengineering/platform' import { plugin } from '@hcengineering/platform' import type { TriggerFunc } from '@hcengineering/server-core' @@ -26,6 +26,9 @@ export const serverCardId = 'server-card' as Plugin * @public */ export default plugin(serverCardId, { + metadata: { + CommunicationEnabled: '' as Metadata + }, trigger: { OnAttribute: '' as Resource, OnAttributeRemove: '' as Resource, diff --git a/server-plugins/notification-resources/src/utils.ts b/server-plugins/notification-resources/src/utils.ts index 02bb2a56a84..8bcd5809da3 100644 --- a/server-plugins/notification-resources/src/utils.ts +++ b/server-plugins/notification-resources/src/utils.ts @@ -663,11 +663,3 @@ export async function getObjectSpace (control: TriggerControl, doc: Doc, cache: : ((cache.get(doc.space) as Space) ?? (await control.findAll(control.ctx, core.class.Space, { _id: doc.space }, { limit: 1 }))[0]) } - -export function isReactionMessage (message?: ActivityMessage): boolean { - return ( - message !== undefined && - message._class === activity.class.DocUpdateMessage && - (message as DocUpdateMessage).objectClass === activity.class.Reaction - ) -} diff --git a/server-plugins/telegram-resources/src/index.ts b/server-plugins/telegram-resources/src/index.ts index 674b2ca800e..7d362bddca7 100644 --- a/server-plugins/telegram-resources/src/index.ts +++ b/server-plugins/telegram-resources/src/index.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. // -import activity, { ActivityMessage, DocUpdateMessage } from '@hcengineering/activity' +import activity, { ActivityMessage } from '@hcengineering/activity' import chunter, { ChatMessage } from '@hcengineering/chunter' import contact, { Channel, Person } from '@hcengineering/contact' import core, { @@ -189,14 +189,6 @@ async function activityMessageToHtml (control: TriggerControl, message: Activity return undefined } -function isReactionMessage (message?: ActivityMessage): boolean { - return ( - message !== undefined && - message._class === activity.class.DocUpdateMessage && - (message as DocUpdateMessage).objectClass === activity.class.Reaction - ) -} - async function getTranslatedData ( data: InboxNotification, doc: Doc, @@ -216,6 +208,8 @@ async function getTranslatedData ( if (hierarchy.isDerived(data._class, notification.class.MentionInboxNotification)) { const text = (data as MentionInboxNotification).messageHtml body = text !== undefined ? jsonToHTML(markupToJSON(text)) : body + } else if (hierarchy.isDerived(data._class, notification.class.ReactionInboxNotification)) { + title = await translate(activity.string.Reacted, {}) } else if (data.data !== undefined) { body = jsonToHTML(markupToJSON(data.data)) } else if (message !== undefined) { @@ -232,10 +226,6 @@ async function getTranslatedData ( } } - if (isReactionMessage(message)) { - title = await translate(activity.string.Reacted, {}) - } - return { title, quote,