Merge remote-tracking branch 'origin/develop'

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev
2024-08-07 13:43:30 +07:00
270 changed files with 6420 additions and 2027 deletions
+27 -1
View File
@@ -60,6 +60,7 @@
"runtimeArgs": ["--nolazy", "-r", "ts-node/register"],
"runtimeVersion": "20",
"showAsyncStacks": true,
"outputCapture": "std",
"sourceMaps": true,
"cwd": "${workspaceRoot}/pods/server",
"protocol": "inspector"
@@ -75,6 +76,7 @@
"TRANSACTOR_URL": "ws://localhost:3333",
"ACCOUNT_PORT": "3000",
"FRONT_URL": "http://localhost:8080",
"outputCapture": "std",
"SES_URL": "http://localhost:8091",
"MINIO_ACCESS_KEY": "minioadmin",
"MINIO_SECRET_KEY": "minioadmin",
@@ -186,12 +188,13 @@
"name": "Debug tool upgrade",
"type": "node",
"request": "launch",
"args": ["src/__start.ts", "upgrade"],
"args": ["src/__start.ts", "upgrade-workspace", "platform"],
"env": {
"SERVER_SECRET": "secret",
"MINIO_ACCESS_KEY": "minioadmin",
"MINIO_SECRET_KEY": "minioadmin",
"MINIO_ENDPOINT": "localhost",
"TRANSACTOR_URL": "ws://localhost:3333",
"MONGO_URL": "mongodb://localhost:27017",
"ACCOUNTS_URL": "http://localhost:3000",
"TELEGRAM_DATABASE": "telegram-service",
@@ -386,6 +389,29 @@
"showAsyncStacks": true,
"outputCapture": "std",
"cwd": "${workspaceRoot}/services/analytics-collector/pod-analytics-collector"
},
{
"name": "Debug AI bot",
"type": "node",
"request": "launch",
"args": ["src/index.ts"],
"env": {
"ACCOUNTS_URL": "http://localhost:3000",
"MONGO_URL": "mongodb://localhost:27017",
"PORT": "4008",
"SERVER_SECRET": "secret",
"SUPPORT_WORKSPACE": "support",
"FIRST_NAME": "Jolie",
"LAST_NAME": "AI",
"PASSWORD": "password",
"AVATAR_PATH": "./assets/avatar.png",
"AVATAR_CONTENT_TYPE": ".png"
},
"runtimeArgs": ["--nolazy", "-r", "ts-node/register"],
"sourceMaps": true,
"cwd": "${workspaceRoot}/services/ai-bot/pod-ai-bot",
"protocol": "inspector",
"outputCapture": "std"
}
]
}
+1 -1
View File
@@ -238,7 +238,7 @@
"summary": "Build docker with platform",
"description": "use to build all docker containers required for platform",
"safeForSimultaneousRushProcesses": true,
"shellCommand": "rush docker:build -p 20 --to @hcengineering/pod-server --to @hcengineering/pod-front --to @hcengineering/prod --to @hcengineering/pod-account --to @hcengineering/pod-collaborator --to @hcengineering/tool --to @hcengineering/pod-print --to @hcengineering/pod-sign --to @hcengineering/pod-analytics-collector --to @hcengineering/rekoni-service"
"shellCommand": "rush docker:build -p 20 --to @hcengineering/pod-server --to @hcengineering/pod-front --to @hcengineering/prod --to @hcengineering/pod-account --to @hcengineering/pod-collaborator --to @hcengineering/tool --to @hcengineering/pod-print --to @hcengineering/pod-sign --to @hcengineering/pod-analytics-collector --to @hcengineering/rekoni-service --to @hcengineering/pod-ai-bot"
},
{
"commandKind": "global",
+222 -14
View File
@@ -71,6 +71,9 @@ dependencies:
'@rush-temp/activity-resources':
specifier: file:./projects/activity-resources.tgz
version: file:projects/activity-resources.tgz(@types/node@20.11.19)(esbuild@0.20.1)(postcss-load-config@4.0.2)(postcss@8.4.35)(ts-node@10.9.2)
'@rush-temp/ai-bot':
specifier: file:./projects/ai-bot.tgz
version: file:projects/ai-bot.tgz(@types/node@20.11.19)(esbuild@0.20.1)(ts-node@10.9.2)
'@rush-temp/analytics':
specifier: file:./projects/analytics.tgz
version: file:projects/analytics.tgz(@types/node@20.11.19)(esbuild@0.20.1)(ts-node@10.9.2)
@@ -407,6 +410,9 @@ dependencies:
'@rush-temp/model-server-activity':
specifier: file:./projects/model-server-activity.tgz
version: file:projects/model-server-activity.tgz
'@rush-temp/model-server-ai-bot':
specifier: file:./projects/model-server-ai-bot.tgz
version: file:projects/model-server-ai-bot.tgz
'@rush-temp/model-server-attachment':
specifier: file:./projects/model-server-attachment.tgz
version: file:projects/model-server-attachment.tgz
@@ -572,6 +578,9 @@ dependencies:
'@rush-temp/pod-account':
specifier: file:./projects/pod-account.tgz
version: file:projects/pod-account.tgz
'@rush-temp/pod-ai-bot':
specifier: file:./projects/pod-ai-bot.tgz
version: file:projects/pod-ai-bot.tgz(bufferutil@4.0.8)(utf-8-validate@6.0.4)
'@rush-temp/pod-analytics-collector':
specifier: file:./projects/pod-analytics-collector.tgz
version: file:projects/pod-analytics-collector.tgz(bufferutil@4.0.8)(utf-8-validate@6.0.4)
@@ -701,6 +710,12 @@ dependencies:
'@rush-temp/server-activity-resources':
specifier: file:./projects/server-activity-resources.tgz
version: file:projects/server-activity-resources.tgz(@types/node@20.11.19)(esbuild@0.20.1)(ts-node@10.9.2)
'@rush-temp/server-ai-bot':
specifier: file:./projects/server-ai-bot.tgz
version: file:projects/server-ai-bot.tgz(esbuild@0.20.1)(ts-node@10.9.2)
'@rush-temp/server-ai-bot-resources':
specifier: file:./projects/server-ai-bot-resources.tgz
version: file:projects/server-ai-bot-resources.tgz(@types/node@20.11.19)(esbuild@0.20.1)(ts-node@10.9.2)
'@rush-temp/server-analytics-collector':
specifier: file:./projects/server-analytics-collector.tgz
version: file:projects/server-analytics-collector.tgz(esbuild@0.20.1)(ts-node@10.9.2)
@@ -1271,6 +1286,9 @@ dependencies:
'@types/qs':
specifier: ~6.9.7
version: 6.9.11
'@types/querystringify':
specifier: ^2.0.2
version: 2.0.2
'@types/request':
specifier: ~2.48.8
version: 2.48.12
@@ -1694,6 +1712,9 @@ dependencies:
qs:
specifier: ~6.11.0
version: 6.11.2
querystringify:
specifier: ^2.2.0
version: 2.2.0
react:
specifier: ^18.2.0
version: 18.2.0
@@ -9307,6 +9328,10 @@ packages:
resolution: {integrity: sha512-oGk0gmhnEJK4Yyk+oI7EfXsLayXatCWPHary1MtcmbAifkobT9cM9yutG/hZKIseOU0MqbIwQ/u2nn/Gb+ltuQ==}
dev: false
/@types/querystringify@2.0.2:
resolution: {integrity: sha512-7d6OQK6pJ//zE32XLK3vI6GHYhBDcYooaRco9cKFGNu59GVatL5+u7rkiAViq44DxDTd/7QQNBWSDHfJGBz/Pw==}
dev: false
/@types/range-parser@1.2.7:
resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==}
dev: false
@@ -23758,7 +23783,7 @@ packages:
dev: false
file:projects/account-service.tgz:
resolution: {integrity: sha512-m2wnBUxpiATGM4uSuNprCebldYmLYweixWs/lkbmn2oxzV797WaTrigIQEEPowYOydBskozS7uc/PgBj9WhkCw==, tarball: file:projects/account-service.tgz}
resolution: {integrity: sha512-ezhFLrsQ8CjpPUIvobegFaStE0c6PJyHqd4V8731uyNwVe+Vhm+Wx5Qoa/VOVqdCWOixjZdP9sBWILsRU9nKkA==, tarball: file:projects/account-service.tgz}
name: '@rush-temp/account-service'
version: 0.0.0
dependencies:
@@ -23961,6 +23986,37 @@ packages:
- ts-node
dev: false
file:projects/ai-bot.tgz(@types/node@20.11.19)(esbuild@0.20.1)(ts-node@10.9.2):
resolution: {integrity: sha512-TH/kDh1LKw+pJRxLZZIjR7da6BQslR9S9Uy9HHXDtvtu5es55OmjRZEWPUnkBjKRmY6hTPBmaO4veq7RiaNz7g==, tarball: file:projects/ai-bot.tgz}
id: file:projects/ai-bot.tgz
name: '@rush-temp/ai-bot'
version: 0.0.0
dependencies:
'@types/jest': 29.5.12
'@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.56.0)(typescript@5.3.3)
'@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.3.3)
eslint: 8.56.0
eslint-config-prettier: 8.10.0(eslint@8.56.0)
eslint-config-standard-with-typescript: 40.0.0(@typescript-eslint/eslint-plugin@6.21.0)(eslint-plugin-import@2.29.1)(eslint-plugin-n@15.7.0)(eslint-plugin-promise@6.1.1)(eslint@8.56.0)(typescript@5.3.3)
eslint-plugin-import: 2.29.1(eslint@8.56.0)
eslint-plugin-n: 15.7.0(eslint@8.56.0)
eslint-plugin-promise: 6.1.1(eslint@8.56.0)
jest: 29.7.0(@types/node@20.11.19)(ts-node@10.9.2)
prettier: 3.2.5
ts-jest: 29.1.2(esbuild@0.20.1)(jest@29.7.0)(typescript@5.3.3)
typescript: 5.3.3
transitivePeerDependencies:
- '@babel/core'
- '@jest/types'
- '@types/node'
- babel-jest
- babel-plugin-macros
- esbuild
- node-notifier
- supports-color
- ts-node
dev: false
file:projects/analytics-collector-assets.tgz(esbuild@0.20.1)(svelte@4.2.12)(ts-node@10.9.2):
resolution: {integrity: sha512-ThEhCV2dJchrcM0mfUjNFdD0/qOzl3qC7glianWUl4Qsj03IrYyjbxvdsV7Fy/49UhQ+YUt46X39kgIWVBnWiw==, tarball: file:projects/analytics-collector-assets.tgz}
id: file:projects/analytics-collector-assets.tgz
@@ -24236,7 +24292,7 @@ packages:
dev: false
file:projects/auth-providers.tgz(@types/node@20.11.19)(esbuild@0.20.1)(ts-node@10.9.2):
resolution: {integrity: sha512-ixfEzxmzLbrHghRymDcvdyz35q9DsDnA1qqheztJNa2zLH5102sfEMEooVLShU3cD+uf/KhNbcTyea1on8GlfA==, tarball: file:projects/auth-providers.tgz}
resolution: {integrity: sha512-5ii9QDKZ+v5awKhzsUOf7Y83PSCUzX6BKFZjY21w8oy+pJRZPTVbKYJhQC4i1AuvahXCObkMbCc3ljs0L+vgIw==, tarball: file:projects/auth-providers.tgz}
id: file:projects/auth-providers.tgz
name: '@rush-temp/auth-providers'
version: 0.0.0
@@ -24249,6 +24305,7 @@ packages:
'@types/node-fetch': 2.6.11
'@types/passport-github2': 1.2.9
'@types/passport-google-oauth20': 2.0.14
'@types/querystringify': 2.0.2
'@types/ws': 8.5.11
'@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.56.0)(typescript@5.3.3)
'@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.3.3)
@@ -24268,6 +24325,7 @@ packages:
passport-google-oauth20: 2.0.0
prettier: 3.2.5
prettier-plugin-svelte: 3.2.1(prettier@3.2.5)(svelte@4.2.11)
querystringify: 2.2.0
ts-jest: 29.1.2(esbuild@0.20.1)(jest@29.7.0)(typescript@5.3.3)
typescript: 5.3.3
transitivePeerDependencies:
@@ -25340,7 +25398,7 @@ packages:
dev: false
file:projects/desktop.tgz(bufferutil@4.0.8)(sass@1.71.1)(utf-8-validate@6.0.4):
resolution: {integrity: sha512-Zqlhi9UprYvF9L0ANw8sSkxTuqMYWTDmIUV6wYDTHd+NMt8NKshXy9J39mll8dW0h93TeY538RePwutrdvM4kA==, tarball: file:projects/desktop.tgz}
resolution: {integrity: sha512-j8onQxy51K29b/9q8g+lOG8e8dOFO2WpQQTdtnhP2vy2EcvIuSgckenQpg++0r1AtJ1G7FC7hr5QYNsrTPWkBA==, tarball: file:projects/desktop.tgz}
id: file:projects/desktop.tgz
name: '@rush-temp/desktop'
version: 0.0.0
@@ -26601,7 +26659,7 @@ packages:
dev: false
file:projects/lead-resources.tgz(@types/node@20.11.19)(esbuild@0.20.1)(postcss-load-config@4.0.2)(postcss@8.4.35)(ts-node@10.9.2):
resolution: {integrity: sha512-YFe9zCBoR5pPRxsoG3Oc9qVVHEWGDlmQrw27HY+3P0SMBXz8xWVjgM6SrL2458y83t/Xsj30krK1zEZkWORfpQ==, tarball: file:projects/lead-resources.tgz}
resolution: {integrity: sha512-DIL0rCz2r3nKYgE2fOrEdVSwLGq1Zz2MjsU1HvDxnIs4IE1Q4s8oNMBtvtunERIQ2fNnJHcE0XXijawtYilrjQ==, tarball: file:projects/lead-resources.tgz}
id: file:projects/lead-resources.tgz
name: '@rush-temp/lead-resources'
version: 0.0.0
@@ -26820,7 +26878,7 @@ packages:
dev: false
file:projects/love-resources.tgz(@types/node@20.11.19)(esbuild@0.20.1)(postcss-load-config@4.0.2)(postcss@8.4.35)(ts-node@10.9.2):
resolution: {integrity: sha512-+jxDfVCF8l8WnKd4Xmaau521zbt9KBRubhiB6N8gXjePkAFSiFXdiQGPnB7VGyYLnImqDuCCnfQNJJpomb4TNQ==, tarball: file:projects/love-resources.tgz}
resolution: {integrity: sha512-e49VrhVkk6U8nj/ZyKfYVj/gK/3rTSrVCicDM5Luxw/f/8pPEVf9uEzmdAV11uiXuXxmj5t0BmHdG5eeVhiK6A==, tarball: file:projects/love-resources.tgz}
id: file:projects/love-resources.tgz
name: '@rush-temp/love-resources'
version: 0.0.0
@@ -26868,7 +26926,7 @@ packages:
dev: false
file:projects/love.tgz(@types/node@20.11.19)(esbuild@0.20.1)(svelte@4.2.12)(ts-node@10.9.2):
resolution: {integrity: sha512-qWAfcld2pm2sf/2kZM1RLYGnhIJWRC42pBsrjQUmztuosCriG9qFa8fdymEsTp4TJaFG9zAqlci7/NKHN9ldvA==, tarball: file:projects/love.tgz}
resolution: {integrity: sha512-IA1DOwfYE00ACQ47zgXq3yOcNghk1oU1nPcq15bqdjOxJeKaE5sa4w/yJ7WoGn1Dm5F0TmxKc67GGDxcTT6kDQ==, tarball: file:projects/love.tgz}
id: file:projects/love.tgz
name: '@rush-temp/love'
version: 0.0.0
@@ -27347,7 +27405,7 @@ packages:
dev: false
file:projects/model-love.tgz:
resolution: {integrity: sha512-iYxy2iQSrCHIlzZLKrDvP5yGrpJrVopD79gegg6iVsqnDbRqrTbsN2H69Xn1KeDbOJllKgVw5y8yVFO+AMq41Q==, tarball: file:projects/model-love.tgz}
resolution: {integrity: sha512-vHa71QNYOwrIifwIvVUmNWd+qlSgxZ0ErEgrx9uBSnJCxff9iqRhtmrFlfJ27bvi/jiBWOro9XYt5liL7gOGhg==, tarball: file:projects/model-love.tgz}
name: '@rush-temp/model-love'
version: 0.0.0
dependencies:
@@ -27550,6 +27608,24 @@ packages:
- supports-color
dev: false
file:projects/model-server-ai-bot.tgz:
resolution: {integrity: sha512-eo56EIdT6JLS0upi95IDRPBEXyASWLEpUdH9cfK2lLtpslJYlJkRPSqImEA5xk1fUUwexRPqVPW+bYFoIMsUxQ==, tarball: file:projects/model-server-ai-bot.tgz}
name: '@rush-temp/model-server-ai-bot'
version: 0.0.0
dependencies:
'@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.56.0)(typescript@5.3.3)
'@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.3.3)
eslint: 8.56.0
eslint-config-standard-with-typescript: 40.0.0(@typescript-eslint/eslint-plugin@6.21.0)(eslint-plugin-import@2.29.1)(eslint-plugin-n@15.7.0)(eslint-plugin-promise@6.1.1)(eslint@8.56.0)(typescript@5.3.3)
eslint-plugin-import: 2.29.1(eslint@8.56.0)
eslint-plugin-n: 15.7.0(eslint@8.56.0)
eslint-plugin-promise: 6.1.1(eslint@8.56.0)
prettier: 3.2.5
typescript: 5.3.3
transitivePeerDependencies:
- supports-color
dev: false
file:projects/model-server-attachment.tgz:
resolution: {integrity: sha512-F45oxBvR4Gh7Y70CqozTOyXQ/lWOkP/EDURRVxxETMWbtXej37/QCtWXlLJ2YNWmoG6ildVMOZlK/IaMSdU9mA==, tarball: file:projects/model-server-attachment.tgz}
name: '@rush-temp/model-server-attachment'
@@ -28588,7 +28664,7 @@ packages:
dev: false
file:projects/onboard-resources.tgz(@types/node@20.11.19)(esbuild@0.20.1)(file-loader@6.2.0)(postcss-load-config@4.0.2)(postcss@8.4.35)(ts-node@10.9.2)(webpack@5.90.3):
resolution: {integrity: sha512-3Gt9PEcA7T44mLBBC0KwwZQ6Qs7pXYbqQaagWFjfT+4MTvR9XVk4L+67U0yFYqpS/EeODSc+dJklk3QBY2O36g==, tarball: file:projects/onboard-resources.tgz}
resolution: {integrity: sha512-xbIj2phiY1rWzXxo5gqyucDzJS7IKMWhgTS4qZ5V9Rh02mCECL2Fs6N5OiiHFJH66jllxThPdX7SIDNBVPcfmA==, tarball: file:projects/onboard-resources.tgz}
id: file:projects/onboard-resources.tgz
name: '@rush-temp/onboard-resources'
version: 0.0.0
@@ -28849,6 +28925,62 @@ packages:
- supports-color
dev: false
file:projects/pod-ai-bot.tgz(bufferutil@4.0.8)(utf-8-validate@6.0.4):
resolution: {integrity: sha512-5Upjw52FvxxuZY9eSN8N5FUKoNMhqME7DwEAp4TTQEKR/Rs+Fnxi7S5ZQ6G2G40RmZ0F0mWfXfWTcyhFlyio5Q==, tarball: file:projects/pod-ai-bot.tgz}
id: file:projects/pod-ai-bot.tgz
name: '@rush-temp/pod-ai-bot'
version: 0.0.0
dependencies:
'@tsconfig/node16': 1.0.4
'@types/cors': 2.8.17
'@types/express': 4.17.21
'@types/jest': 29.5.12
'@types/node': 20.11.19
'@types/node-fetch': 2.6.11
'@types/ws': 8.5.11
'@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.56.0)(typescript@5.3.3)
'@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.3.3)
cors: 2.8.5
dotenv: 16.0.3
esbuild: 0.20.1
eslint: 8.56.0
eslint-config-standard-with-typescript: 40.0.0(@typescript-eslint/eslint-plugin@6.21.0)(eslint-plugin-import@2.29.1)(eslint-plugin-n@15.7.0)(eslint-plugin-promise@6.1.1)(eslint@8.56.0)(typescript@5.3.3)
eslint-plugin-import: 2.29.1(eslint@8.56.0)
eslint-plugin-n: 15.7.0(eslint@8.56.0)
eslint-plugin-node: 11.1.0(eslint@8.56.0)
eslint-plugin-promise: 6.1.1(eslint@8.56.0)
express: 4.19.2
fast-equals: 5.0.1
form-data: 4.0.0
jest: 29.7.0(@types/node@20.11.19)(ts-node@10.9.2)
mongodb: 6.8.0
node-fetch: 2.7.0
prettier: 3.2.5
ts-jest: 29.1.2(esbuild@0.20.1)(jest@29.7.0)(typescript@5.3.3)
ts-node: 10.9.2(@types/node@20.11.19)(typescript@5.3.3)
typescript: 5.3.3
ws: 8.18.0(bufferutil@4.0.8)(utf-8-validate@6.0.4)
transitivePeerDependencies:
- '@aws-sdk/credential-providers'
- '@babel/core'
- '@jest/types'
- '@mongodb-js/zstd'
- '@swc/core'
- '@swc/wasm'
- babel-jest
- babel-plugin-macros
- bufferutil
- encoding
- gcp-metadata
- kerberos
- mongodb-client-encryption
- node-notifier
- snappy
- socks
- supports-color
- utf-8-validate
dev: false
file:projects/pod-analytics-collector.tgz(bufferutil@4.0.8)(utf-8-validate@6.0.4):
resolution: {integrity: sha512-Sx9mTOCsgDTcoC8h9VXeemVHerKLMDzdW4aJ08fdsF9UA35tLWmbDXw+fn3gPUNj2l+QT5ExEF9rV1Iez1FIbw==, tarball: file:projects/pod-analytics-collector.tgz}
id: file:projects/pod-analytics-collector.tgz
@@ -28930,7 +29062,7 @@ packages:
dev: false
file:projects/pod-calendar.tgz(bufferutil@4.0.8)(ts-node@10.9.2)(utf-8-validate@6.0.4):
resolution: {integrity: sha512-DOvMMTPpDOAuO8Vi8gEBTXBMQATYdZgtNWxvUyG0xbCArW9jEmKbrOVBV1ydZGOJqfqhennjv09+uWR/YUR+SQ==, tarball: file:projects/pod-calendar.tgz}
resolution: {integrity: sha512-rnCLCkBGWm2o9YTk6AnoYlNCACtncXiYw+CUkhVFJmBNTE1l90C74Uci4ldJBpxEOyCHVBzJIjsp6sMbQwafkw==, tarball: file:projects/pod-calendar.tgz}
id: file:projects/pod-calendar.tgz
name: '@rush-temp/pod-calendar'
version: 0.0.0
@@ -29160,7 +29292,7 @@ packages:
dev: false
file:projects/pod-gmail.tgz(bufferutil@4.0.8)(ts-node@10.9.2)(utf-8-validate@6.0.4):
resolution: {integrity: sha512-ybpB+uVlKzWvEVZnrp6iPuDYR7OwWBEsHl3ivSmte8BhfH9Q5QfzBm/FrEUsyEBIN4KS/cGDUoNGv5Us5OVWVw==, tarball: file:projects/pod-gmail.tgz}
resolution: {integrity: sha512-qeyt7Pl1TwEo/JojBg4R4LR3uWEdxOfqfz6tYB5kB27oEdE+jBJx0LGgsrDYoQSsv7A0LLnaFVfS+XT17EoKeQ==, tarball: file:projects/pod-gmail.tgz}
id: file:projects/pod-gmail.tgz
name: '@rush-temp/pod-gmail'
version: 0.0.0
@@ -29448,7 +29580,7 @@ packages:
dev: false
file:projects/pod-telegram.tgz(bufferutil@4.0.8)(ts-node@10.9.2)(utf-8-validate@6.0.4):
resolution: {integrity: sha512-mBbMXZhgRl0R9gvFN+uxfdFHgXdF9cFyC200rXnS4PgGkGVm9jRmE8lJQTxiOOJyX8rPDm8qlLhCrlKyof+pEQ==, tarball: file:projects/pod-telegram.tgz}
resolution: {integrity: sha512-MF+eEeVhFR4XQj2YaAP6gjvm1tijtnRUMDrQt48vZBD8mwQA8B0drOVHCkOQ+NJOYqi1c3pRG/+kPKWqswbplQ==, tarball: file:projects/pod-telegram.tgz}
id: file:projects/pod-telegram.tgz
name: '@rush-temp/pod-telegram'
version: 0.0.0
@@ -30578,6 +30710,74 @@ packages:
- ts-node
dev: false
file:projects/server-ai-bot-resources.tgz(@types/node@20.11.19)(esbuild@0.20.1)(ts-node@10.9.2):
resolution: {integrity: sha512-YOa1XOLVz87AjouR9Qn4Wkiib/9nXkIiMIW7XVzCl+lVqfdjXDRn4kjxXj363Wh8bw5Ato7kpuj0M9C1Vsh+zw==, tarball: file:projects/server-ai-bot-resources.tgz}
id: file:projects/server-ai-bot-resources.tgz
name: '@rush-temp/server-ai-bot-resources'
version: 0.0.0
dependencies:
'@types/jest': 29.5.12
'@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.56.0)(typescript@5.3.3)
'@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.3.3)
eslint: 8.56.0
eslint-config-standard-with-typescript: 40.0.0(@typescript-eslint/eslint-plugin@6.21.0)(eslint-plugin-import@2.29.1)(eslint-plugin-n@15.7.0)(eslint-plugin-promise@6.1.1)(eslint@8.56.0)(typescript@5.3.3)
eslint-plugin-import: 2.29.1(eslint@8.56.0)
eslint-plugin-n: 15.7.0(eslint@8.56.0)
eslint-plugin-promise: 6.1.1(eslint@8.56.0)
jest: 29.7.0(@types/node@20.11.19)(ts-node@10.9.2)
mongodb: 6.8.0
prettier: 3.2.5
ts-jest: 29.1.2(esbuild@0.20.1)(jest@29.7.0)(typescript@5.3.3)
typescript: 5.3.3
transitivePeerDependencies:
- '@aws-sdk/credential-providers'
- '@babel/core'
- '@jest/types'
- '@mongodb-js/zstd'
- '@types/node'
- babel-jest
- babel-plugin-macros
- esbuild
- gcp-metadata
- kerberos
- mongodb-client-encryption
- node-notifier
- snappy
- socks
- supports-color
- ts-node
dev: false
file:projects/server-ai-bot.tgz(esbuild@0.20.1)(ts-node@10.9.2):
resolution: {integrity: sha512-9rB0TFsDFJ2KgmJwTCJRQG/rh+NORXysidGZq8v77VEN7Sh/xQlB33k4XgHutF3U1Bd0KUdRC/pg9grlgezIdA==, tarball: file:projects/server-ai-bot.tgz}
id: file:projects/server-ai-bot.tgz
name: '@rush-temp/server-ai-bot'
version: 0.0.0
dependencies:
'@types/jest': 29.5.12
'@types/node': 20.11.19
'@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.56.0)(typescript@5.3.3)
'@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.3.3)
eslint: 8.56.0
eslint-config-standard-with-typescript: 40.0.0(@typescript-eslint/eslint-plugin@6.21.0)(eslint-plugin-import@2.29.1)(eslint-plugin-n@15.7.0)(eslint-plugin-promise@6.1.1)(eslint@8.56.0)(typescript@5.3.3)
eslint-plugin-import: 2.29.1(eslint@8.56.0)
eslint-plugin-n: 15.7.0(eslint@8.56.0)
eslint-plugin-promise: 6.1.1(eslint@8.56.0)
jest: 29.7.0(@types/node@20.11.19)(ts-node@10.9.2)
prettier: 3.2.5
ts-jest: 29.1.2(esbuild@0.20.1)(jest@29.7.0)(typescript@5.3.3)
typescript: 5.3.3
transitivePeerDependencies:
- '@babel/core'
- '@jest/types'
- babel-jest
- babel-plugin-macros
- esbuild
- node-notifier
- supports-color
- ts-node
dev: false
file:projects/server-analytics-collector-resources.tgz(@types/node@20.11.19)(esbuild@0.20.1)(ts-node@10.9.2):
resolution: {integrity: sha512-cLqoyrE7bZmjUeQSenTvg0S2XdEUUulUTMYvYephOUR5orzG71xy3Z+HgV92YapcZMzi9RVdaataHwYhxTRDPw==, tarball: file:projects/server-analytics-collector-resources.tgz}
id: file:projects/server-analytics-collector-resources.tgz
@@ -31756,7 +31956,7 @@ packages:
dev: false
file:projects/server-pipeline.tgz:
resolution: {integrity: sha512-DpU8h5YS6u+d1uVTSyALfcGof3p2hXuSd8f1RyFI/YvNHxLHLrTu0AzquL8uhwxliB1bpNJtCWh1RWphdVJD4Q==, tarball: file:projects/server-pipeline.tgz}
resolution: {integrity: sha512-VMd/X1M3HotOPN51cVDAHjKrHhNm8/5GMMiZ4WklnQVtDfmDHocXKsC+FgOLXpb9RHYhtsz0Arwi7pW7xIar5A==, tarball: file:projects/server-pipeline.tgz}
name: '@rush-temp/server-pipeline'
version: 0.0.0
dependencies:
@@ -32167,7 +32367,7 @@ packages:
dev: false
file:projects/server-telegram-resources.tgz(@types/node@20.11.19)(esbuild@0.20.1)(ts-node@10.9.2):
resolution: {integrity: sha512-jlZjP4X1pBtvaMNdCcrjmyAOJeVk/AQbQUwa0f28Oc4VU6OaktCMmG+0+GvnvMVAcpjUmb2fL3rnLxI2ckB2yQ==, tarball: file:projects/server-telegram-resources.tgz}
resolution: {integrity: sha512-4Pob/P+5sdtU7LGgb1jJbqVms72MXOpAvOZ+GivgI3j9JvqvbOfHn87O1jVhrWa865xZrdGlV9+T9grap3W3xg==, tarball: file:projects/server-telegram-resources.tgz}
id: file:projects/server-telegram-resources.tgz
name: '@rush-temp/server-telegram-resources'
version: 0.0.0
@@ -32181,18 +32381,26 @@ packages:
eslint-plugin-n: 15.7.0(eslint@8.56.0)
eslint-plugin-promise: 6.1.1(eslint@8.56.0)
jest: 29.7.0(@types/node@20.11.19)(ts-node@10.9.2)
mongodb: 6.8.0
prettier: 3.2.5
prettier-plugin-svelte: 3.2.1(prettier@3.2.5)(svelte@4.2.11)
ts-jest: 29.1.2(esbuild@0.20.1)(jest@29.7.0)(typescript@5.3.3)
typescript: 5.3.3
transitivePeerDependencies:
- '@aws-sdk/credential-providers'
- '@babel/core'
- '@jest/types'
- '@mongodb-js/zstd'
- '@types/node'
- babel-jest
- babel-plugin-macros
- esbuild
- gcp-metadata
- kerberos
- mongodb-client-encryption
- node-notifier
- snappy
- socks
- supports-color
- ts-node
dev: false
@@ -33244,7 +33452,7 @@ packages:
dev: false
file:projects/telegram-resources.tgz(@types/node@20.11.19)(esbuild@0.20.1)(postcss-load-config@4.0.2)(postcss@8.4.35)(ts-node@10.9.2):
resolution: {integrity: sha512-TakwKag4NWDevTKXic9xdAmzPjjw9ezjHVHZYyYcweVBNgloGjjVfGqfBzWyrzcuGCFJRMddfygAHqvSUsuRiQ==, tarball: file:projects/telegram-resources.tgz}
resolution: {integrity: sha512-a2P52BNrZcs4YavNcVmsBRpfa2o2rq787sDtT2xxNqMhYn74+D3G6ZGkJGbeGJSm3GD4ZS1ELKfdrf5MXvgSkg==, tarball: file:projects/telegram-resources.tgz}
id: file:projects/telegram-resources.tgz
name: '@rush-temp/telegram-resources'
version: 0.0.0
+1
View File
@@ -92,6 +92,7 @@ import '@hcengineering/training-assets'
import '@hcengineering/products-assets'
import '@hcengineering/controlled-documents-assets'
import '@hcengineering/analytics-collector-assets'
import '@hcengineering/text-editor-assets'
import { coreId } from '@hcengineering/core'
import presentation, { parsePreviewConfig, presentationId } from '@hcengineering/presentation'
+26 -2
View File
@@ -1,7 +1,6 @@
// preload.js
import { BrandingMap, Config, IPCMainExposed, NotificationParams } from './types'
import { contextBridge, ipcRenderer } from 'electron'
/**
@@ -18,6 +17,31 @@ export function concatLink (host: string, path: string): string {
}
}
async function loadServerConfig (url: string): Promise<any> {
let retries = 5
let res: Response | undefined
do {
try {
res = await fetch(url)
break
} catch (e) {
retries--
if (retries === 0) {
throw new Error(`Failed to load server config: ${e}`)
}
await new Promise((resolve) => setTimeout(resolve, 1000 * (5 - retries)))
}
} while (retries > 0)
if (res === undefined) {
// In theory should never get here
throw new Error('Failed to load server config')
}
return await res.json()
}
const openArg = (process.argv.find((it) => it.startsWith('--open=')) ?? '').split('--open=')[1]
console.log('Open passed', openArg)
let configPromise: Promise<Config> | undefined
@@ -41,7 +65,7 @@ const expose: IPCMainExposed = {
configPromise = new Promise((resolve, reject) => {
ipcRenderer.invoke('get-main-config').then(
async (mainConfig) => {
const serverConfig = await (await fetch(concatLink(mainConfig.FRONT_URL, mainConfig.CONFIG_URL))).json()
const serverConfig = await loadServerConfig(concatLink(mainConfig.FRONT_URL, mainConfig.CONFIG_URL))
const combinedConfig = {
...serverConfig,
...mainConfig,
+18 -1
View File
@@ -213,7 +213,24 @@ services:
deploy:
resources:
limits:
memory: 300M
memory: 300M
aiBot:
image: hardcoreeng/ai-bot
restart: unless-stopped
environment:
- SERVER_SECRET=secret
- MONGO_URL=mongodb://mongodb:27017
- ACCOUNTS_URL=http://account:3000
- SUPPORT_WORKSPACE=support
- FIRST_NAME=Jolie
- LAST_NAME=AI
- PASSWORD=password
- AVATAR_PATH=./avatar.png
- AVATAR_CONTENT_TYPE=.png
deploy:
resources:
limits:
memory: 300M
volumes:
db:
files:
+6 -4
View File
@@ -135,13 +135,14 @@ export interface Config {
GITHUB_URL: string
SENTRY_DSN?: string
LOVE_ENDPOINT?: string
LIVEKIT_WS?: string,
SIGN_URL?: string,
PRINT_URL?: string,
POSTHOG_API_KEY?: string,
LIVEKIT_WS?: string
SIGN_URL?: string
PRINT_URL?: string
POSTHOG_API_KEY?: string
POSTHOG_HOST?: string
ANALYTICS_COLLECTOR_URL?:string
BRANDING_URL?: string
TELEGRAM_BOT_URL?: string
// Could be defined for dev environment
FRONT_URL?: string
@@ -301,6 +302,7 @@ export async function configurePlatform() {
setMetadata(presentation.metadata.FrontVersion, config.VERSION)
}
setMetadata(telegram.metadata.TelegramURL, config.TELEGRAM_URL ?? 'http://localhost:8086')
setMetadata(telegram.metadata.BotUrl, config.TELEGRAM_BOT_URL)
setMetadata(gmail.metadata.GmailURL, config.GMAIL_URL ?? 'http://localhost:8087')
setMetadata(calendar.metadata.CalendarServiceURL, config.CALENDAR_URL ?? 'http://localhost:8095')
setMetadata(notification.metadata.PushPublicKey, config.PUSH_PUBLIC_KEY)
+2
View File
@@ -133,6 +133,8 @@
"@hcengineering/server-guest-resources": "^0.6.0",
"@hcengineering/server-time": "^0.6.0",
"@hcengineering/server-time-resources": "^0.6.0",
"@hcengineering/server-ai-bot": "^0.6.0",
"@hcengineering/server-ai-bot-resources": "^0.6.0",
"@hcengineering/setting": "^0.6.17",
"@hcengineering/tags": "^0.6.16",
"@hcengineering/task": "^0.6.20",
+2
View File
@@ -44,6 +44,7 @@ import { serverTelegramId } from '@hcengineering/server-telegram'
import { serverTimeId } from '@hcengineering/server-time'
import { serverTrackerId } from '@hcengineering/server-tracker'
import { serverViewId } from '@hcengineering/server-view'
import { serverAiBotId } from '@hcengineering/server-ai-bot'
addLocation(serverActivityId, () => import('@hcengineering/server-activity-resources'))
addLocation(serverAttachmentId, () => import('@hcengineering/server-attachment-resources'))
@@ -68,6 +69,7 @@ addLocation(serverDocumentId, () => import('@hcengineering/server-document-resou
addLocation(serverTimeId, () => import('@hcengineering/server-time-resources'))
addLocation(serverGuestId, () => import('@hcengineering/server-guest-resources'))
addLocation(serverDriveId, () => import('@hcengineering/server-drive-resources'))
addLocation(serverAiBotId, () => import('@hcengineering/server-ai-bot-resources'))
function prepareTools (): {
mongodbUri: string
+1 -1
View File
@@ -1242,7 +1242,7 @@ async function updateId (
stages: {},
removed: false
})
await txop.update(docIndexState, { removed: true })
await txop.update(docIndexState, { removed: true, needIndex: true })
}
if (domain !== DOMAIN_MODEL) {
+11
View File
@@ -25,6 +25,7 @@ import {
tryMigrate
} from '@hcengineering/model'
import { htmlToMarkup } from '@hcengineering/text'
import contact from '@hcengineering/contact'
import activity from './plugin'
import { activityId, DOMAIN_ACTIVITY } from './index'
@@ -197,6 +198,16 @@ export const activityOperation: MigrateOperation = {
({ attachedToClass }) => attachedToClass
)
}
},
{
state: 'migrate-employee-space-v1',
func: async () => {
await client.update<ActivityMessage>(
DOMAIN_ACTIVITY,
{ space: 'contact:space:Employee' as Ref<Space> },
{ space: contact.space.Contacts }
)
}
}
])
},
+2 -1
View File
@@ -105,6 +105,7 @@
"@hcengineering/model-server-products": "^0.1.0",
"@hcengineering/model-server-training": "^0.1.0",
"@hcengineering/model-server-controlled-documents": "^0.1.0",
"@hcengineering/model-analytics-collector": "^0.6.0"
"@hcengineering/model-analytics-collector": "^0.6.0",
"@hcengineering/model-server-ai-bot": "^0.6.0"
}
}
+3 -1
View File
@@ -60,6 +60,7 @@ import { serverTelegramId, createModel as serverTelegramModel } from '@hcenginee
import { serverTemplatesId, createModel as serverTemplatesModel } from '@hcengineering/model-server-templates'
import { serverTrackerId, createModel as serverTrackerModel } from '@hcengineering/model-server-tracker'
import { serverViewId, createModel as serverViewModel } from '@hcengineering/model-server-view'
import { serverAiBotId, createModel as serverAiBotModel } from '@hcengineering/model-server-ai-bot'
import setting, { settingId, createModel as settingModel } from '@hcengineering/model-setting'
import { driveId, createModel as driveModel } from '@hcengineering/model-drive'
import { supportId, createModel as supportModel } from '@hcengineering/model-support'
@@ -424,7 +425,8 @@ export default function buildModel (enabled: string[] = ['*'], disabled: string[
[serverDriveModel, serverDriveId],
[serverProductsModel, serverProductsId],
[serverTrainingModel, serverTrainingId],
[serverDocumentsModel, serverDocumentsId]
[serverDocumentsModel, serverDocumentsId],
[serverAiBotModel, serverAiBotId]
]
for (const [b, id, config] of builders) {
+43 -106
View File
@@ -13,7 +13,7 @@
// limitations under the License.
//
import activity, { type ActivityMessage } from '@hcengineering/activity'
import activity, { type ActivityMessage, type ActivityMessageControl } from '@hcengineering/activity'
import {
type Channel,
chunterId,
@@ -62,6 +62,7 @@ import { type DocNotifyContext } from '@hcengineering/notification'
import chunter from './plugin'
import { defineActions } from './actions'
import { defineNotifications } from './notifications'
export { chunterId } from '@hcengineering/chunter'
export { chunterOperation } from './migration'
@@ -197,37 +198,14 @@ export function createModel (builder: Builder): void {
titleProvider: chunter.function.ChannelTitleProvider
})
builder.mixin(chunter.class.DirectMessage, core.class.Class, notification.mixin.ClassCollaborators, {
fields: ['members']
})
builder.mixin(chunter.class.Channel, core.class.Class, notification.mixin.ClassCollaborators, {
fields: ['members']
})
builder.mixin(chunter.class.DirectMessage, core.class.Class, view.mixin.ObjectPresenter, {
presenter: chunter.component.DmPresenter
})
builder.mixin(chunter.class.DirectMessage, core.class.Class, notification.mixin.NotificationPreview, {
presenter: chunter.component.ChannelPreview
})
builder.mixin(chunter.class.Channel, core.class.Class, view.mixin.ObjectPresenter, {
presenter: chunter.component.ChannelPresenter
})
builder.mixin(chunter.class.ChatMessage, core.class.Class, notification.mixin.NotificationContextPresenter, {
labelPresenter: chunter.component.ChatMessageNotificationLabel
})
builder.createDoc(notification.class.ActivityNotificationViewlet, core.space.Model, {
messageMatch: {
_class: chunter.class.ThreadMessage
},
presenter: chunter.component.ThreadNotificationPresenter
})
builder.mixin(chunter.class.DirectMessage, core.class.Class, view.mixin.SpaceHeader, {
header: chunter.component.DmHeader
})
@@ -284,78 +262,6 @@ export function createModel (builder: Builder): void {
filters: []
})
builder.createDoc(
notification.class.NotificationGroup,
core.space.Model,
{
label: chunter.string.ApplicationLabelChunter,
icon: chunter.icon.Chunter
},
chunter.ids.ChunterNotificationGroup
)
builder.createDoc(
notification.class.NotificationType,
core.space.Model,
{
label: chunter.string.DM,
generated: false,
hidden: false,
txClasses: [core.class.TxCreateDoc],
objectClass: chunter.class.ChatMessage,
attachedToClass: chunter.class.DirectMessage,
defaultEnabled: false,
group: chunter.ids.ChunterNotificationGroup,
templates: {
textTemplate: '{sender} has sent you a message: {doc} {message}',
htmlTemplate: '<p><b>{sender}</b> has sent you a message {doc}</p> {message}',
subjectTemplate: 'You have new direct message in {doc}'
}
},
chunter.ids.DMNotification
)
builder.createDoc(
notification.class.NotificationType,
core.space.Model,
{
label: chunter.string.Message,
generated: false,
hidden: false,
txClasses: [core.class.TxCreateDoc],
objectClass: chunter.class.ChatMessage,
attachedToClass: chunter.class.Channel,
defaultEnabled: false,
group: chunter.ids.ChunterNotificationGroup,
templates: {
textTemplate: '{sender} has sent a message in {doc}: {message}',
htmlTemplate: '<p><b>{sender}</b> has sent a message in {doc}</p> {message}',
subjectTemplate: 'You have new message in {doc}'
}
},
chunter.ids.ChannelNotification
)
builder.createDoc(
notification.class.NotificationType,
core.space.Model,
{
label: chunter.string.ThreadMessage,
generated: false,
hidden: false,
txClasses: [core.class.TxCreateDoc],
objectClass: chunter.class.ThreadMessage,
defaultEnabled: false,
group: chunter.ids.ChunterNotificationGroup,
templates: {
textTemplate: '{body}',
htmlTemplate: '<p>{body}</p>',
subjectTemplate: '{title}'
}
},
chunter.ids.ThreadNotification
)
builder.createDoc(activity.class.ActivityMessagesFilter, core.space.Model, {
label: chunter.string.Comments,
position: 60,
@@ -419,11 +325,11 @@ export function createModel (builder: Builder): void {
})
builder.mixin(chunter.class.Channel, core.class.Class, chunter.mixin.ObjectChatPanel, {
ignoreKeys: ['archived', 'collaborators', 'lastMessage', 'pinned', 'topic', 'description']
ignoreKeys: ['archived', 'collaborators', 'lastMessage', 'pinned', 'topic', 'description', 'members', 'owners']
})
builder.mixin(chunter.class.DirectMessage, core.class.Class, chunter.mixin.ObjectChatPanel, {
ignoreKeys: ['archived', 'collaborators', 'lastMessage', 'pinned', 'topic', 'description']
ignoreKeys: ['archived', 'collaborators', 'lastMessage', 'pinned', 'topic', 'description', 'members', 'owners']
})
builder.mixin(chunter.class.ChatMessage, core.class.Class, activity.mixin.ActivityMessagePreview, {
@@ -448,19 +354,50 @@ export function createModel (builder: Builder): void {
strict: true
})
builder.createDoc(notification.class.NotificationProviderDefaults, core.space.Model, {
provider: notification.providers.InboxNotificationProvider,
ignoredTypes: [],
enabledTypes: [chunter.ids.DMNotification, chunter.ids.ChannelNotification, chunter.ids.ThreadNotification]
builder.createDoc<ActivityMessageControl<ChunterSpace>>(activity.class.ActivityMessageControl, core.space.Model, {
objectClass: chunter.class.Channel,
skip: [
{ _class: core.class.TxMixin },
{ _class: core.class.TxCreateDoc, objectClass: { $ne: chunter.class.Channel } },
{ _class: core.class.TxRemoveDoc }
],
allowedFields: ['members']
})
builder.createDoc(notification.class.NotificationProviderDefaults, core.space.Model, {
provider: notification.providers.PushNotificationProvider,
ignoredTypes: [],
enabledTypes: [chunter.ids.DMNotification, chunter.ids.ChannelNotification, chunter.ids.ThreadNotification]
builder.createDoc<ActivityMessageControl<ChunterSpace>>(activity.class.ActivityMessageControl, core.space.Model, {
objectClass: chunter.class.DirectMessage,
skip: [{ _class: core.class.TxMixin }, { _class: core.class.TxCreateDoc }, { _class: core.class.TxRemoveDoc }],
allowedFields: ['members']
})
builder.createDoc(activity.class.DocUpdateMessageViewlet, core.space.Model, {
objectClass: chunter.class.Channel,
action: 'create',
component: chunter.activity.ChannelCreatedMessage
})
builder.createDoc(activity.class.DocUpdateMessageViewlet, core.space.Model, {
objectClass: chunter.class.Channel,
action: 'update',
config: {
members: {
presenter: chunter.activity.MembersChangedMessage
}
}
})
builder.createDoc(activity.class.DocUpdateMessageViewlet, core.space.Model, {
objectClass: chunter.class.DirectMessage,
action: 'update',
config: {
members: {
presenter: chunter.activity.MembersChangedMessage
}
}
})
defineActions(builder)
defineNotifications(builder)
}
export default chunter
+62 -12
View File
@@ -38,20 +38,21 @@ import { DOMAIN_NOTIFICATION } from '@hcengineering/model-notification'
import chunter from './plugin'
import { DOMAIN_CHUNTER } from './index'
import { type DocUpdateMessage } from '@hcengineering/activity'
export const DOMAIN_COMMENT = 'comment' as Domain
export async function createDocNotifyContexts (
client: MigrationUpgradeClient,
tx: TxOperations,
attachedTo: Ref<Doc>,
attachedToClass: Ref<Class<Doc>>
objectId: Ref<Doc>,
objectClass: Ref<Class<Doc>>,
objectSpace: Ref<Space>
): Promise<void> {
const users = await client.findAll(core.class.Account, {})
const docNotifyContexts = await client.findAll(notification.class.DocNotifyContext, {
user: { $in: users.map((it) => it._id) },
attachedTo,
attachedToClass
objectId
})
for (const user of users) {
if (user._id === core.account.System) {
@@ -62,8 +63,10 @@ export async function createDocNotifyContexts (
if (docNotifyContext === undefined) {
await tx.createDoc(notification.class.DocNotifyContext, core.space.Space, {
user: user._id,
attachedTo,
attachedToClass
objectId,
objectClass,
objectSpace,
isPinned: false
})
}
}
@@ -93,7 +96,7 @@ export async function createGeneral (client: MigrationUpgradeClient, tx: TxOpera
topic: 'General Channel',
private: false,
archived: false,
members: await getAllEmployeeAccounts(tx),
members: await getAllPersonAccounts(tx),
autoJoin: true
},
chunter.space.General
@@ -101,10 +104,10 @@ export async function createGeneral (client: MigrationUpgradeClient, tx: TxOpera
}
}
await createDocNotifyContexts(client, tx, chunter.space.General, chunter.class.Channel)
await createDocNotifyContexts(client, tx, chunter.space.General, chunter.class.Channel, core.space.Space)
}
async function getAllEmployeeAccounts (tx: TxOperations): Promise<Ref<PersonAccount>[]> {
async function getAllPersonAccounts (tx: TxOperations): Promise<Ref<PersonAccount>[]> {
const employees = await tx.findAll(contactPlugin.mixin.Employee, { active: true })
const accounts = await tx.findAll(contactPlugin.class.PersonAccount, {
person: { $in: employees.map((it) => it._id) }
@@ -113,7 +116,7 @@ async function getAllEmployeeAccounts (tx: TxOperations): Promise<Ref<PersonAcco
}
async function joinEmployees (current: Space, tx: TxOperations): Promise<void> {
const accs = await getAllEmployeeAccounts(tx)
const accs = await getAllPersonAccounts(tx)
const newMembers: Ref<Account>[] = [...current.members]
for (const acc of accs) {
if (!newMembers.includes(acc)) {
@@ -149,7 +152,7 @@ export async function createRandom (client: MigrationUpgradeClient, tx: TxOperat
topic: 'Random Talks',
private: false,
archived: false,
members: await getAllEmployeeAccounts(tx),
members: await getAllPersonAccounts(tx),
autoJoin: true
},
chunter.space.Random
@@ -157,7 +160,7 @@ export async function createRandom (client: MigrationUpgradeClient, tx: TxOperat
}
}
await createDocNotifyContexts(client, tx, chunter.space.Random, chunter.class.Channel)
await createDocNotifyContexts(client, tx, chunter.space.Random, chunter.class.Channel, core.space.Space)
}
async function convertCommentsToChatMessages (client: MigrationClient): Promise<void> {
@@ -195,6 +198,47 @@ async function removeOldClasses (client: MigrationClient): Promise<void> {
}
}
async function removeWrongActivity (client: MigrationClient): Promise<void> {
await client.deleteMany<DocUpdateMessage>(DOMAIN_ACTIVITY, {
_class: activity.class.DocUpdateMessage,
attachedToClass: chunter.class.Channel,
action: 'update',
'attributeUpdates.attrKey': { $ne: 'members' }
})
await client.deleteMany<DocUpdateMessage>(DOMAIN_ACTIVITY, {
_class: activity.class.DocUpdateMessage,
attachedToClass: chunter.class.Channel,
action: 'create',
objectClass: { $ne: chunter.class.Channel }
})
await client.deleteMany<DocUpdateMessage>(DOMAIN_ACTIVITY, {
_class: activity.class.DocUpdateMessage,
attachedToClass: chunter.class.Channel,
action: 'remove'
})
await client.deleteMany<DocUpdateMessage>(DOMAIN_ACTIVITY, {
_class: activity.class.DocUpdateMessage,
attachedToClass: chunter.class.DirectMessage,
action: 'update',
'attributeUpdates.attrKey': { $ne: 'members' }
})
await client.deleteMany<DocUpdateMessage>(DOMAIN_ACTIVITY, {
_class: activity.class.DocUpdateMessage,
attachedToClass: chunter.class.DirectMessage,
action: 'create'
})
await client.deleteMany<DocUpdateMessage>(DOMAIN_ACTIVITY, {
_class: activity.class.DocUpdateMessage,
attachedToClass: chunter.class.DirectMessage,
action: 'remove'
})
}
export const chunterOperation: MigrateOperation = {
async migrate (client: MigrationClient): Promise<void> {
await tryMigrate(client, chunterId, [
@@ -233,6 +277,12 @@ export const chunterOperation: MigrateOperation = {
func: async (client) => {
await removeOldClasses(client)
}
},
{
state: 'remove-wrong-activity-v1',
func: async (client) => {
await removeWrongActivity(client)
}
}
])
},
+171
View File
@@ -0,0 +1,171 @@
//
// Copyright © 2024 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 notification from '@hcengineering/model-notification'
import core from '@hcengineering/model-core'
import activity from '@hcengineering/activity'
import chunter from './plugin'
export function defineNotifications (builder: Builder): void {
builder.mixin(chunter.class.DirectMessage, core.class.Class, notification.mixin.ClassCollaborators, {
fields: ['members']
})
builder.mixin(chunter.class.Channel, core.class.Class, notification.mixin.ClassCollaborators, {
fields: ['members']
})
builder.mixin(chunter.class.DirectMessage, core.class.Class, notification.mixin.NotificationPreview, {
presenter: chunter.component.ChannelPreview
})
builder.mixin(chunter.class.ChatMessage, core.class.Class, notification.mixin.NotificationContextPresenter, {
labelPresenter: chunter.component.ChatMessageNotificationLabel
})
builder.createDoc(notification.class.ActivityNotificationViewlet, core.space.Model, {
messageMatch: {
_class: chunter.class.ThreadMessage
},
presenter: chunter.component.ThreadNotificationPresenter
})
builder.createDoc(
notification.class.NotificationGroup,
core.space.Model,
{
label: chunter.string.ApplicationLabelChunter,
icon: chunter.icon.Chunter
},
chunter.ids.ChunterNotificationGroup
)
builder.createDoc(
notification.class.NotificationType,
core.space.Model,
{
label: chunter.string.DM,
generated: false,
hidden: false,
txClasses: [core.class.TxCreateDoc],
objectClass: chunter.class.ChatMessage,
attachedToClass: chunter.class.DirectMessage,
defaultEnabled: false,
group: chunter.ids.ChunterNotificationGroup,
templates: {
textTemplate: '{sender} has sent you a message: {doc} {message}',
htmlTemplate: '<p><b>{sender}</b> has sent you a message {doc}</p> {message}',
subjectTemplate: 'You have new direct message in {doc}'
}
},
chunter.ids.DMNotification
)
builder.createDoc(
notification.class.NotificationType,
core.space.Model,
{
label: chunter.string.ChannelMessages,
generated: false,
hidden: false,
txClasses: [core.class.TxCreateDoc],
objectClass: chunter.class.ChatMessage,
attachedToClass: chunter.class.Channel,
defaultEnabled: false,
group: chunter.ids.ChunterNotificationGroup,
templates: {
textTemplate: '{sender} has sent a message in {doc}: {message}',
htmlTemplate: '<p><b>{sender}</b> has sent a message in {doc}</p> {message}',
subjectTemplate: 'You have new message in {doc}'
}
},
chunter.ids.ChannelNotification
)
builder.createDoc(
notification.class.NotificationType,
core.space.Model,
{
label: chunter.string.JoinChannel,
generated: false,
hidden: false,
txClasses: [core.class.TxUpdateDoc],
objectClass: chunter.class.Channel,
defaultEnabled: false,
field: 'members',
group: chunter.ids.ChunterNotificationGroup,
templates: {
textTemplate: 'You have been added to #{doc}',
htmlTemplate: '<p>You have been added to <b>#{doc}</b></p>',
subjectTemplate: 'You have been added to #{doc}'
}
},
chunter.ids.JoinChannelNotification
)
builder.createDoc(
notification.class.NotificationType,
core.space.Model,
{
label: chunter.string.ThreadMessage,
generated: false,
hidden: false,
txClasses: [core.class.TxCreateDoc],
objectClass: chunter.class.ThreadMessage,
defaultEnabled: false,
group: chunter.ids.ChunterNotificationGroup,
templates: {
textTemplate: '{body}',
htmlTemplate: '<p>{body}</p>',
subjectTemplate: '{title}'
}
},
chunter.ids.ThreadNotification
)
builder.createDoc(notification.class.NotificationProviderDefaults, core.space.Model, {
provider: notification.providers.InboxNotificationProvider,
ignoredTypes: [],
enabledTypes: [
chunter.ids.DMNotification,
chunter.ids.ChannelNotification,
chunter.ids.ThreadNotification,
chunter.ids.JoinChannelNotification
]
})
builder.createDoc(notification.class.NotificationProviderDefaults, core.space.Model, {
provider: notification.providers.PushNotificationProvider,
ignoredTypes: [],
enabledTypes: [
chunter.ids.DMNotification,
chunter.ids.ChannelNotification,
chunter.ids.ThreadNotification,
chunter.ids.JoinChannelNotification
]
})
builder.createDoc(notification.class.ActivityNotificationViewlet, core.space.Model, {
messageMatch: {
_class: activity.class.DocUpdateMessage,
objectClass: chunter.class.Channel,
action: 'update',
'attributeUpdates.attrKey': 'members'
},
presenter: chunter.component.JoinChannelNotificationPresenter
})
}
+9 -2
View File
@@ -30,7 +30,8 @@ export default mergeIds(chunterId, chunter, {
ChannelsPanel: '' as AnyComponent,
Chat: '' as AnyComponent,
ChatMessageNotificationLabel: '' as AnyComponent,
ThreadNotificationPresenter: '' as AnyComponent
ThreadNotificationPresenter: '' as AnyComponent,
JoinChannelNotificationPresenter: '' as AnyComponent
},
action: {
MarkCommentUnread: '' as Ref<Action>,
@@ -51,6 +52,10 @@ export default mergeIds(chunterId, chunter, {
category: {
Chunter: '' as Ref<ActionCategory>
},
activity: {
ChannelCreatedMessage: '' as AnyComponent,
MembersChangedMessage: '' as AnyComponent
},
string: {
ApplicationLabelChunter: '' as IntlString,
MentionedIn: '' as IntlString,
@@ -71,7 +76,9 @@ export default mergeIds(chunterId, chunter, {
ConfigLabel: '' as IntlString,
ConfigDescription: '' as IntlString,
Reacted: '' as IntlString,
RepliedToThread: '' as IntlString
RepliedToThread: '' as IntlString,
ChannelMessages: '' as IntlString,
JoinChannel: '' as IntlString
},
viewlet: {
Chat: '' as Ref<ViewletDescriptor>,
+11 -13
View File
@@ -29,7 +29,8 @@ import {
type Organization,
type Person,
type PersonAccount,
type Status
type Status,
type PersonSpace
} from '@hcengineering/contact'
import {
AccountRole,
@@ -64,7 +65,7 @@ import {
} from '@hcengineering/model'
import attachment from '@hcengineering/model-attachment'
import chunter from '@hcengineering/model-chunter'
import core, { TAccount, TAttachedDoc, TDoc } from '@hcengineering/model-core'
import core, { TAccount, TAttachedDoc, TDoc, TSpace } from '@hcengineering/model-core'
import { createPublicLinkAction } from '@hcengineering/model-guest'
import { generateClassNotificationTypes } from '@hcengineering/model-notification'
import presentation from '@hcengineering/model-presentation'
@@ -219,6 +220,12 @@ export class TContactsTab extends TDoc implements ContactsTab {
index!: number
}
@Model(contact.class.PersonSpace, core.class.Space)
export class TPersonSpace extends TSpace implements PersonSpace {
@Prop(TypeRef(contact.class.Person), contact.string.Person)
person!: Ref<Person>
}
export function createModel (builder: Builder): void {
builder.createModel(
TAvatarProvider,
@@ -231,7 +238,8 @@ export function createModel (builder: Builder): void {
TChannel,
TStatus,
TMember,
TContactsTab
TContactsTab,
TPersonSpace
)
builder.mixin(contact.class.Contact, core.class.Class, activity.mixin.ActivityDoc, {})
@@ -246,16 +254,6 @@ export function createModel (builder: Builder): void {
builder.mixin(contact.class.Organization, core.class.Class, activity.mixin.ActivityDoc, {})
builder.createDoc(activity.class.ActivityMessageControl, core.space.Model, {
objectClass: contact.class.Contact,
skip: [
{
_class: core.class.TxCollectionCUD,
collection: 'comments'
}
]
})
builder.mixin(contact.class.Channel, core.class.Class, activity.mixin.ActivityDoc, {})
builder.mixin(contact.class.Person, core.class.Class, view.mixin.ObjectIcon, {
+63 -18
View File
@@ -1,23 +1,32 @@
//
import { DOMAIN_TX, TxOperations, type Class, type Doc, type Domain, type Ref, type Space } from '@hcengineering/core'
import {
type Class,
type Doc,
type Domain,
DOMAIN_TX,
generateId,
type Ref,
type Space,
TxOperations
} from '@hcengineering/core'
import {
createDefaultSpace,
tryMigrate,
tryUpgrade,
type MigrateOperation,
type MigrateUpdate,
type MigrationClient,
type MigrationDocumentQuery,
type MigrationUpgradeClient,
type ModelLogger
type ModelLogger,
tryMigrate,
tryUpgrade
} from '@hcengineering/model'
import activity, { DOMAIN_ACTIVITY } from '@hcengineering/model-activity'
import core from '@hcengineering/model-core'
import core, { DOMAIN_SPACE } from '@hcengineering/model-core'
import { DOMAIN_VIEW } from '@hcengineering/model-view'
import { AvatarType, type Contact, type Person, type PersonSpace } from '@hcengineering/contact'
import { AvatarType, type Contact } from '@hcengineering/contact'
import contact, { DOMAIN_CONTACT, contactId } from './index'
import contact, { contactId, DOMAIN_CONTACT } from './index'
async function createEmployeeEmail (client: TxOperations): Promise<void> {
const employees = await client.findAll(contact.mixin.Employee, {})
@@ -100,23 +109,55 @@ async function migrateAvatars (client: MigrationClient): Promise<void> {
)
}
async function createPersonSpaces (client: MigrationClient): Promise<void> {
const spaces = await client.find<PersonSpace>(DOMAIN_SPACE, { _class: contact.class.PersonSpace })
if (spaces.length > 0) {
return
}
const accounts = await client.model.findAll(contact.class.PersonAccount, {})
const employees = await client.find(DOMAIN_CONTACT, { [contact.mixin.Employee]: { $exists: true } })
const newSpaces = new Map<Ref<Person>, PersonSpace>()
const now = Date.now()
for (const account of accounts) {
const employee = employees.find(({ _id }) => _id === account.person)
if (employee === undefined) continue
const space = newSpaces.get(account.person)
if (space !== undefined) {
space.members.push(account._id)
} else {
newSpaces.set(account.person, {
_id: generateId(),
_class: contact.class.PersonSpace,
space: core.space.Space,
name: 'Personal space',
description: '',
private: true,
archived: false,
members: [account._id],
person: account.person,
modifiedBy: core.account.System,
createdBy: core.account.System,
modifiedOn: now,
createdOn: now
})
}
}
await client.create(DOMAIN_SPACE, Array.from(newSpaces.values()))
}
export const contactOperation: MigrateOperation = {
async migrate (client: MigrationClient, logger: ModelLogger): Promise<void> {
await tryMigrate(client, contactId, [
{
state: 'employees',
func: async (client) => {
await client.update(
DOMAIN_TX,
{
objectClass: 'contact:class:EmployeeAccount'
},
{
$rename: { 'attributes.employee': 'attributes.person' },
$set: { objectClass: contact.class.PersonAccount }
}
)
await client.update(
DOMAIN_TX,
{
@@ -251,6 +292,10 @@ export const contactOperation: MigrateOperation = {
{ $rename: { avatarKind: 'avatarType' } }
)
}
},
{
state: 'create-person-spaces-v1',
func: createPersonSpaces
}
])
},
+5 -9
View File
@@ -38,7 +38,6 @@ import {
type EnumOf,
type FieldIndexConfig,
type FullTextSearchContext,
type IndexStageState,
type IndexingConfiguration,
type Interface,
type MigrationState,
@@ -329,27 +328,24 @@ export class TDocIndexState extends TDoc implements DocIndexState {
attributes!: Record<string, any>
@Prop(TypeBoolean(), getEmbeddedLabel('Removed'))
// @Index(IndexKind.Indexed)
@Hidden()
removed!: boolean
@Prop(TypeBoolean(), getEmbeddedLabel('NeedIndexing'))
@Hidden()
needIndex!: boolean
// States for different stages
@Prop(TypeRecord(), getEmbeddedLabel('Stages'))
// @Index(IndexKind.Indexed)
@Hidden()
stages!: Record<string, boolean | string>
stages!: Record<string, boolean>
@Prop(TypeString(), getEmbeddedLabel('Generation'))
@Hidden()
generationId?: string
}
@Model(core.class.IndexStageState, core.class.Doc, DOMAIN_DOC_INDEX_STATE)
export class TIndexStageState extends TDoc implements IndexStageState {
stageId!: string
attributes!: Record<string, any>
}
@MMixin(core.mixin.FullTextSearchContext, core.class.Class)
export class TFullTextSearchContext extends TClass implements FullTextSearchContext {}
+5 -7
View File
@@ -49,7 +49,6 @@ import {
TEnumOf,
TFullTextSearchContext,
TIndexConfiguration,
TIndexStageState,
TInterface,
TMigrationState,
TMixin,
@@ -94,9 +93,9 @@ import { TUserStatus } from './transient'
import {
TTx,
TTxApplyIf,
TTxCUD,
TTxCollectionCUD,
TTxCreateDoc,
TTxCUD,
TTxMixin,
TTxRemoveDoc,
TTxUpdateDoc,
@@ -164,7 +163,6 @@ export function createModel (builder: Builder): void {
TTypeAny,
TTypeRelatedDocument,
TDocIndexState,
TIndexStageState,
TFullTextSearchContext,
TConfiguration,
TConfigurationElement,
@@ -284,8 +282,9 @@ export function createModel (builder: Builder): void {
builder.createDoc(core.class.DomainIndexConfiguration, core.space.Model, {
domain: DOMAIN_DOC_INDEX_STATE,
indexes: [
{ keys: { removed: 1 }, filter: { removed: true } },
{ keys: { _class: 1 }, filter: { _class: core.class.DocIndexState } }
{
keys: { needIndex: 1 }
}
],
disabled: [
{ attachedToClass: 1 },
@@ -297,8 +296,7 @@ export function createModel (builder: Builder): void {
{ createdBy: 1 },
{ createdBy: -1 },
{ createdOn: -1 }
],
skip: ['stages.']
]
})
builder.mixin(core.class.Space, core.class.Class, core.mixin.FullTextSearchContext, {
+8 -1
View File
@@ -155,7 +155,8 @@ export const coreOperation: MigrateOperation = {
{ objectClass: { $nin: allIndexed } },
{
$set: {
removed: true
removed: true,
needIndex: true
}
}
)
@@ -182,6 +183,12 @@ export const coreOperation: MigrateOperation = {
{
state: 'old-statuses-transactions',
func: migrateStatusTransactions
},
{
state: 'add-need-index',
func: async (client: MigrationClient) => {
await client.update(DOMAIN_DOC_INDEX_STATE, {}, { $set: { needIndex: true } })
}
}
])
},
+1 -1
View File
@@ -268,7 +268,7 @@ export function createModel (builder: Builder): void {
defaultEnabled: true,
canDisable: true,
depends: notification.providers.InboxNotificationProvider,
order: 30
order: 300
},
gmail.providers.EmailNotificationProvider
)
-10
View File
@@ -47,16 +47,6 @@ export function createModel (builder: Builder): void {
builder.mixin(lead.mixin.Customer, core.class.Class, activity.mixin.ActivityDoc, {})
builder.createDoc(activity.class.ActivityMessageControl, core.space.Model, {
objectClass: lead.class.Lead,
skip: [
{
_class: core.class.TxCollectionCUD,
collection: 'comments'
}
]
})
builder.mixin(lead.class.Funnel, core.class.Class, activity.mixin.ActivityDoc, {})
builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
+2
View File
@@ -87,6 +87,8 @@ export class TParticipantInfo extends TDoc implements ParticipantInfo {
x!: number
y!: number
sessionId!: string | null
}
@Model(love.class.JoinRequest, core.class.Doc, DOMAIN_TRANSIENT)
+3 -2
View File
@@ -30,6 +30,7 @@
"dependencies": {
"@hcengineering/activity": "^0.6.0",
"@hcengineering/chunter": "^0.6.20",
"@hcengineering/contact": "^0.6.24",
"@hcengineering/core": "^0.6.32",
"@hcengineering/model": "^0.6.11",
"@hcengineering/model-attachment": "^0.6.0",
@@ -39,10 +40,10 @@
"@hcengineering/model-workbench": "^0.6.1",
"@hcengineering/notification": "^0.6.23",
"@hcengineering/platform": "^0.6.11",
"@hcengineering/preference": "^0.6.13",
"@hcengineering/setting": "^0.6.17",
"@hcengineering/ui": "^0.6.15",
"@hcengineering/view": "^0.6.13",
"@hcengineering/workbench": "^0.6.16",
"@hcengineering/preference": "^0.6.13"
"@hcengineering/workbench": "^0.6.16"
}
}
+20 -10
View File
@@ -49,6 +49,7 @@ import {
UX,
type Builder
} from '@hcengineering/model'
import { type PersonSpace } from '@hcengineering/contact'
import core, { TClass, TDoc } from '@hcengineering/model-core'
import preference, { TPreference } from '@hcengineering/model-preference'
import view, { createAction, template } from '@hcengineering/model-view'
@@ -79,7 +80,7 @@ import {
type PushSubscription,
type PushSubscriptionKeys
} from '@hcengineering/notification'
import { type Asset, type IntlString } from '@hcengineering/platform'
import { type Asset, type IntlString, type Resource } from '@hcengineering/platform'
import setting from '@hcengineering/setting'
import { type AnyComponent, type Location } from '@hcengineering/ui/src/types'
@@ -195,13 +196,18 @@ export class TDocNotifyContext extends TDoc implements DocNotifyContext {
@Index(IndexKind.Indexed)
user!: Ref<Account>
@Prop(TypeRef(core.class.Doc), core.string.AttachedTo)
@Prop(TypeRef(core.class.Doc), core.string.Object)
@Index(IndexKind.Indexed)
attachedTo!: Ref<Doc>
objectId!: Ref<Doc>
@Prop(TypeRef(core.class.Class), core.string.AttachedToClass)
@Prop(TypeRef(core.class.Class), core.string.Class)
@Index(IndexKind.Indexed)
attachedToClass!: Ref<Class<Doc>>
objectClass!: Ref<Class<Doc>>
@Prop(TypeRef(core.class.Space), core.string.Space)
objectSpace!: Ref<Space>
declare space: Ref<PersonSpace>
@Prop(TypeDate(), core.string.Date)
lastViewedTimestamp?: Timestamp
@@ -210,7 +216,7 @@ export class TDocNotifyContext extends TDoc implements DocNotifyContext {
lastUpdateTimestamp?: Timestamp
@Prop(TypeBoolean(), notification.string.Pinned)
isPinned?: boolean
isPinned!: boolean
}
@Model(notification.class.InboxNotification, core.class.Doc, DOMAIN_NOTIFICATION)
@@ -228,7 +234,9 @@ export class TInboxNotification extends TDoc implements InboxNotification {
isViewed!: boolean
@Prop(TypeBoolean(), core.string.Boolean)
archived?: boolean
archived!: boolean
declare space: Ref<PersonSpace>
title?: IntlString
body?: IntlString
@@ -295,6 +303,8 @@ export class TNotificationProvider extends TDoc implements NotificationProvider
depends?: Ref<NotificationProvider>
ignoreAll?: boolean
canDisable!: boolean
presenter?: AnyComponent
isAvailableFn?: Resource<() => boolean>
}
@Model(notification.class.NotificationProviderDefaults, core.class.Doc)
@@ -723,7 +733,7 @@ export function createModel (builder: Builder): void {
description: notification.string.InboxNotificationsDescription,
defaultEnabled: true,
canDisable: false,
order: 10
order: 100
},
notification.providers.InboxNotificationProvider
)
@@ -738,7 +748,7 @@ export function createModel (builder: Builder): void {
depends: notification.providers.InboxNotificationProvider,
defaultEnabled: true,
canDisable: true,
order: 20
order: 200
},
notification.providers.PushNotificationProvider
)
@@ -754,7 +764,7 @@ export function createModel (builder: Builder): void {
defaultEnabled: true,
canDisable: true,
ignoreAll: true,
order: 25
order: 250
},
notification.providers.SoundNotificationProvider
)
+158 -1
View File
@@ -25,11 +25,14 @@ import notification, {
notificationId,
NotificationStatus,
type BrowserNotification,
type DocNotifyContext
type DocNotifyContext,
type InboxNotification
} from '@hcengineering/notification'
import { DOMAIN_PREFERENCE } from '@hcengineering/preference'
import contact, { type PersonSpace } from '@hcengineering/contact'
import { DOMAIN_DOC_NOTIFY, DOMAIN_NOTIFICATION, DOMAIN_USER_NOTIFY } from './index'
import { DOMAIN_SPACE } from '@hcengineering/model-core'
export async function removeNotifications (
client: MigrationClient,
@@ -73,6 +76,115 @@ export async function removeNotifications (
}
}
export async function migrateNotificationsSpace (client: MigrationClient): Promise<void> {
const personSpaces = await client.find<PersonSpace>(DOMAIN_SPACE, { _class: contact.class.PersonSpace }, {})
await client.update(
DOMAIN_DOC_NOTIFY,
{
_class: notification.class.DocNotifyContext,
objectSpace: { $exists: false }
},
{ $rename: { space: 'objectSpace' } }
)
for (const space of personSpaces) {
await client.update(
DOMAIN_DOC_NOTIFY,
{
_class: notification.class.DocNotifyContext,
user: { $in: space.members }
},
{ space: space._id }
)
await client.update(
DOMAIN_NOTIFICATION,
{
_class: notification.class.ActivityInboxNotification,
user: { $in: space.members }
},
{ space: space._id }
)
await client.update(
DOMAIN_NOTIFICATION,
{
_class: notification.class.CommonInboxNotification,
user: { $in: space.members }
},
{ space: space._id }
)
await client.update(
DOMAIN_NOTIFICATION,
{
_class: notification.class.MentionInboxNotification,
user: { $in: space.members }
},
{ space: space._id }
)
}
await client.deleteMany(DOMAIN_DOC_NOTIFY, { space: { $nin: personSpaces.map(({ _id }) => _id) } })
await client.deleteMany(DOMAIN_NOTIFICATION, {
_class: notification.class.ActivityInboxNotification,
space: { $nin: personSpaces.map(({ _id }) => _id) }
})
await client.deleteMany(DOMAIN_NOTIFICATION, {
_class: notification.class.CommonInboxNotification,
space: { $nin: personSpaces.map(({ _id }) => _id) }
})
await client.deleteMany(DOMAIN_NOTIFICATION, {
_class: notification.class.MentionInboxNotification,
space: { $nin: personSpaces.map(({ _id }) => _id) }
})
while (true) {
const contexts = await client.find<DocNotifyContext>(
DOMAIN_DOC_NOTIFY,
{
_class: notification.class.DocNotifyContext,
attachedTo: { $exists: true }
},
{ limit: 500 }
)
if (contexts.length === 0) {
break
}
const classesOfSpace = new Set<Ref<Class<Doc>>>()
for (const context of contexts) {
const _class = (context as any).attachedToClass
if (client.hierarchy.isDerived(_class, core.class.Space)) {
classesOfSpace.add(_class)
}
}
if (classesOfSpace.size > 0) {
await client.update<DocNotifyContext>(
DOMAIN_DOC_NOTIFY,
{ objectClass: { $in: Array.from(classesOfSpace) } },
{ objectSpace: core.space.Space }
)
await client.update<DocNotifyContext>(
DOMAIN_DOC_NOTIFY,
{ objectClass: { $in: Array.from(classesOfSpace) } },
{ $rename: { attachedTo: 'objectId', attachedToClass: 'objectClass' } }
)
}
await client.update(
DOMAIN_DOC_NOTIFY,
{
_class: notification.class.DocNotifyContext,
_id: { $in: contexts.map(({ _id }) => _id) }
},
{ $rename: { attachedTo: 'objectId', attachedToClass: 'objectClass' } }
)
}
await client.deleteMany(DOMAIN_NOTIFICATION, { _class: notification.class.BrowserNotification })
await client.deleteMany(DOMAIN_USER_NOTIFY, { _class: notification.class.BrowserNotification })
}
export async function migrateSettings (client: MigrationClient): Promise<void> {
await client.update(
DOMAIN_PREFERENCE,
@@ -166,8 +278,53 @@ export const notificationOperation: MigrateOperation = {
DOMAIN_USER_NOTIFY
)
}
},
{
state: 'fill-notification-archived-field-v1',
func: async (client) => {
await client.update<InboxNotification>(
DOMAIN_NOTIFICATION,
{ _class: notification.class.ActivityInboxNotification, archived: { $exists: false } },
{ archived: false }
)
await client.update<InboxNotification>(
DOMAIN_NOTIFICATION,
{ _class: notification.class.CommonInboxNotification, archived: { $exists: false } },
{ archived: false }
)
await client.update<InboxNotification>(
DOMAIN_NOTIFICATION,
{ _class: notification.class.MentionInboxNotification, archived: { $exists: false } },
{ archived: false }
)
}
},
{
state: 'fill-contexts-pinned-field-v1',
func: async (client) => {
await client.update<DocNotifyContext>(
DOMAIN_DOC_NOTIFY,
{ _class: notification.class.DocNotifyContext, isPinned: { $exists: false } },
{ isPinned: false }
)
}
},
{
state: 'migrate-notifications-space-v1',
func: migrateNotificationsSpace
},
{
state: 'migrate-employee-space-v1',
func: async () => {
await client.update<DocNotifyContext>(
DOMAIN_DOC_NOTIFY,
{ _class: notification.class.DocNotifyContext, objectSpace: 'contact:space:Employee' as Ref<Space> },
{ objectSpace: contact.space.Contacts }
)
}
}
])
await client.deleteMany<BrowserNotification>(DOMAIN_USER_NOTIFY, {
_class: notification.class.BrowserNotification,
status: NotificationStatus.Notified
-20
View File
@@ -54,26 +54,6 @@ export function createModel (builder: Builder): void {
builder.mixin(recruit.class.Review, core.class.Class, activity.mixin.ActivityDoc, {})
builder.mixin(recruit.mixin.Candidate, core.class.Class, activity.mixin.ActivityDoc, {})
builder.createDoc(activity.class.ActivityMessageControl, core.space.Model, {
objectClass: recruit.class.Vacancy,
skip: [
{
_class: core.class.TxCollectionCUD,
collection: 'comments'
}
]
})
builder.createDoc(activity.class.ActivityMessageControl, core.space.Model, {
objectClass: recruit.class.Applicant,
skip: [
{
_class: core.class.TxCollectionCUD,
collection: 'comments'
}
]
})
builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: recruit.class.Vacancy,
components: { input: chunter.component.ChatMessageInput }
+6 -1
View File
@@ -28,6 +28,10 @@ export function createModel (builder: Builder): void {
presenter: serverActivity.function.ReactionNotificationContentProvider
})
builder.mixin(activity.class.DocUpdateMessage, core.class.Class, serverNotification.mixin.TextPresenter, {
presenter: serverActivity.function.DocUpdateMessageTextPresenter
})
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverActivity.trigger.OnReactionChanged,
txMatch: {
@@ -38,7 +42,8 @@ export function createModel (builder: Builder): void {
})
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverActivity.trigger.ActivityMessagesHandler
trigger: serverActivity.trigger.ActivityMessagesHandler,
isAsync: true
})
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
+7
View File
@@ -0,0 +1,7 @@
module.exports = {
extends: ['./node_modules/@hcengineering/platform-rig/profiles/model/eslint.config.json'],
parserOptions: {
tsconfigRootDir: __dirname,
project: './tsconfig.json'
}
}
+4
View File
@@ -0,0 +1,4 @@
*
!/lib/**
!CHANGELOG.md
/lib/**/__tests__/
+5
View File
@@ -0,0 +1,5 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
"rigPackageName": "@hcengineering/platform-rig",
"rigProfile": "model"
}
+49
View File
@@ -0,0 +1,49 @@
{
"name": "@hcengineering/model-server-ai-bot",
"version": "0.6.0",
"main": "lib/index.js",
"svelte": "src/index.ts",
"types": "types/index.d.ts",
"author": "Hardcore Engineering Inc.",
"template": "@hcengineering/model-package",
"scripts": {
"build": "compile",
"build:watch": "compile",
"format": "format src",
"_phase:build": "compile transpile src",
"_phase:format": "format src",
"_phase:validate": "compile validate"
},
"devDependencies": {
"@hcengineering/platform-rig": "^0.6.0",
"@typescript-eslint/eslint-plugin": "^6.11.0",
"@typescript-eslint/parser": "^6.11.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",
"prettier": "^3.1.0",
"typescript": "^5.3.3"
},
"dependencies": {
"@hcengineering/activity": "^0.6.0",
"@hcengineering/ai-bot": "^0.6.0",
"@hcengineering/chunter": "^0.6.20",
"@hcengineering/contact": "^0.6.24",
"@hcengineering/core": "^0.6.32",
"@hcengineering/model": "^0.6.11",
"@hcengineering/model-activity": "^0.6.0",
"@hcengineering/model-chunter": "^0.6.0",
"@hcengineering/model-contact": "^0.6.1",
"@hcengineering/model-core": "^0.6.0",
"@hcengineering/model-notification": "^0.6.0",
"@hcengineering/model-presentation": "^0.6.0",
"@hcengineering/model-view": "^0.6.0",
"@hcengineering/notification": "^0.6.23",
"@hcengineering/platform": "^0.6.11",
"@hcengineering/server-ai-bot": "^0.6.0",
"@hcengineering/server-core": "^0.6.1",
"@hcengineering/view": "^0.6.13"
}
}
+111
View File
@@ -0,0 +1,111 @@
//
// Copyright © 2024 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, Mixin, Model, Prop, TypeRef, TypeString } from '@hcengineering/model'
import core, { type Account, type Class, type Doc, type Domain, type Ref, type Space } from '@hcengineering/core'
import serverCore from '@hcengineering/server-core'
import serverAiBot from '@hcengineering/server-ai-bot'
import { TDoc } from '@hcengineering/model-core'
import { getEmbeddedLabel } from '@hcengineering/platform'
import aiBot, {
type AIBotEvent,
type AIBotTransferEvent,
type AIBotResponseEvent,
type TransferredMessage
} from '@hcengineering/ai-bot'
import chunter, { type ChatMessage } from '@hcengineering/chunter'
import notification from '@hcengineering/notification'
import { TChatMessage } from '@hcengineering/model-chunter'
export { serverAiBotId } from '@hcengineering/server-ai-bot'
export const DOMAIN_AI_BOT = 'ai_bot' as Domain
@Model(aiBot.class.AIBotEvent, core.class.Doc, DOMAIN_AI_BOT)
export class TAIBotEvent extends TDoc implements AIBotEvent {
@Prop(TypeRef(chunter.class.ChatMessage), core.string.Class)
messageClass!: Ref<Class<ChatMessage>>
@Prop(TypeString(), getEmbeddedLabel('Collection'))
collection!: string
@Prop(TypeString(), getEmbeddedLabel('Message'))
message!: string
}
@Model(aiBot.class.AIBotResponseEvent, core.class.Doc)
export class TAIBotResponseEvent extends TAIBotEvent implements AIBotResponseEvent {
@Prop(TypeRef(core.class.Doc), core.string.Object)
objectId!: Ref<Doc>
@Prop(TypeRef(core.class.Class), core.string.Class)
objectClass!: Ref<Class<Doc>>
@Prop(TypeRef(core.class.Space), core.string.Space)
objectSpace!: Ref<Space>
@Prop(TypeRef(core.class.Account), core.string.Account)
user!: Ref<Account>
}
@Model(aiBot.class.AIBotTransferEvent, aiBot.class.AIBotEvent)
export class TAIBotTransferEvent extends TAIBotEvent implements AIBotTransferEvent {
toEmail!: string
toWorkspace!: string
fromWorkspace!: string
messageId!: Ref<ChatMessage>
parentMessageId?: Ref<ChatMessage>
}
@Mixin(aiBot.mixin.TransferredMessage, chunter.class.ChatMessage)
export class TTransferredMessage extends TChatMessage implements TransferredMessage {
messageId!: Ref<ChatMessage>
parentMessageId?: Ref<ChatMessage>
}
export function createModel (builder: Builder): void {
builder.createModel(TAIBotEvent, TAIBotTransferEvent, TAIBotResponseEvent, TTransferredMessage)
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverAiBot.trigger.OnMessageSend,
isAsync: true
})
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverAiBot.trigger.OnMention,
txMatch: {
_class: core.class.TxCreateDoc,
objectClass: notification.class.MentionInboxNotification
},
isAsync: true
})
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverAiBot.trigger.OnMessageNotified,
txMatch: {
_class: core.class.TxCreateDoc,
objectClass: notification.class.ActivityInboxNotification
},
isAsync: true
})
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverAiBot.trigger.OnUserStatus,
txMatch: {
objectClass: core.class.UserStatus
},
isAsync: true
})
}
+10
View File
@@ -0,0 +1,10 @@
{
"extends": "./node_modules/@hcengineering/platform-rig/profiles/model/tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib",
"declarationDir": "./types",
"tsBuildInfoFile": ".build/build.tsbuildinfo"
}
}
+9 -8
View File
@@ -54,14 +54,6 @@ export function createModel (builder: Builder): void {
trigger: serverChunter.trigger.ChunterTrigger
})
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverChunter.trigger.OnChannelMembersChanged,
txMatch: {
_class: core.class.TxUpdateDoc,
objectClass: chunter.class.Channel
}
})
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverChunter.trigger.OnUserStatus,
txMatch: {
@@ -70,6 +62,15 @@ export function createModel (builder: Builder): void {
isAsync: true
})
builder.mixin(
chunter.ids.JoinChannelNotification,
notification.class.NotificationType,
serverNotification.mixin.TypeMatch,
{
func: serverChunter.function.JoinChannelTypeMatch
}
)
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverChunter.trigger.OnContextUpdate,
txMatch: {
+5
View File
@@ -37,6 +37,11 @@ export function createModel (builder: Builder): void {
}
)
builder.createDoc(serverNotification.class.NotificationProviderResources, core.space.Model, {
provider: telegram.providers.TelegramNotificationProvider,
fn: serverTelegram.function.SendTelegramNotifications
})
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverTelegram.trigger.OnMessageCreate,
txMatch: {
+4 -33
View File
@@ -41,8 +41,9 @@ import type {
} from '@hcengineering/telegram'
import templates from '@hcengineering/templates'
import view from '@hcengineering/view'
import telegram from './plugin'
import notification from '@hcengineering/model-notification'
import { defineNotifications } from './notification'
export { telegramId } from '@hcengineering/telegram'
export { telegramOperation } from './migration'
@@ -178,38 +179,6 @@ export function createModel (builder: Builder): void {
telegram.ids.TelegramMessageSharedActivityViewlet
)
builder.createDoc(
notification.class.NotificationGroup,
core.space.Model,
{
label: telegram.string.Telegram,
icon: contact.icon.Telegram
},
telegram.ids.NotificationGroup
)
builder.createDoc(
notification.class.NotificationType,
core.space.Model,
{
label: telegram.string.NewMessage,
generated: false,
allowedForAuthor: true,
hidden: false,
txClasses: [core.class.TxCreateDoc],
objectClass: telegram.class.Message,
group: telegram.ids.NotificationGroup,
defaultEnabled: false
},
telegram.ids.NewMessageNotification
)
builder.createDoc(notification.class.NotificationProviderDefaults, core.space.Model, {
provider: notification.providers.InboxNotificationProvider,
ignoredTypes: [],
enabledTypes: [telegram.ids.NewMessageNotification]
})
builder.mixin(telegram.class.Message, core.class.Class, core.mixin.FullTextSearchContext, {
parentPropagate: false,
childProcessingAllowed: true
@@ -227,4 +196,6 @@ export function createModel (builder: Builder): void {
{ createdOn: -1 }
]
})
defineNotifications(builder)
}
+72
View File
@@ -0,0 +1,72 @@
//
// Copyright © 2024 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 notification from '@hcengineering/model-notification'
import core from '@hcengineering/model-core'
import contact from '@hcengineering/model-contact'
import telegram from './plugin'
export function defineNotifications (builder: Builder): void {
builder.createDoc(
notification.class.NotificationGroup,
core.space.Model,
{
label: telegram.string.Telegram,
icon: contact.icon.Telegram
},
telegram.ids.NotificationGroup
)
builder.createDoc(
notification.class.NotificationProvider,
core.space.Model,
{
label: telegram.string.Telegram,
icon: contact.icon.Telegram,
depends: notification.providers.InboxNotificationProvider,
defaultEnabled: false,
canDisable: true,
description: telegram.string.TelegramNotificationDescription,
presenter: telegram.component.NotificationProviderPresenter,
order: 400,
isAvailableFn: telegram.function.IsTelegramNotificationsAvailable
},
telegram.providers.TelegramNotificationProvider
)
builder.createDoc(
notification.class.NotificationType,
core.space.Model,
{
label: telegram.string.NewMessage,
generated: false,
allowedForAuthor: true,
hidden: false,
txClasses: [core.class.TxCreateDoc],
objectClass: telegram.class.Message,
group: telegram.ids.NotificationGroup,
defaultEnabled: false
},
telegram.ids.NewMessageNotification
)
builder.createDoc(notification.class.NotificationProviderDefaults, core.space.Model, {
provider: notification.providers.InboxNotificationProvider,
ignoredTypes: [notification.ids.CollaboratoAddNotification],
enabledTypes: [telegram.ids.NewMessageNotification]
})
}
+7 -2
View File
@@ -36,7 +36,8 @@ export default mergeIds(telegramId, telegram, {
ConfigLabel: '' as IntlString,
ConfigDescription: '' as IntlString,
NewMessage: '' as IntlString,
NewIncomingMessage: '' as IntlString
NewIncomingMessage: '' as IntlString,
TelegramNotificationDescription: '' as IntlString
},
ids: {
NotificationGroup: '' as Ref<NotificationGroup>,
@@ -45,9 +46,13 @@ export default mergeIds(telegramId, telegram, {
},
function: {
GetCurrentEmployeeTG: '' as Resource<TemplateFieldFunc>,
GetIntegrationOwnerTG: '' as Resource<TemplateFieldFunc>
GetIntegrationOwnerTG: '' as Resource<TemplateFieldFunc>,
IsTelegramNotificationsAvailable: '' as Resource<() => boolean>
},
activity: {
TelegramMessageCreated: '' as AnyComponent
},
component: {
NotificationProviderPresenter: '' as AnyComponent
}
})
-40
View File
@@ -449,46 +449,6 @@ export function createModel (builder: Builder): void {
decode: tracker.function.GetIssueIdByIdentifier
})
builder.createDoc(activity.class.ActivityMessageControl, core.space.Model, {
objectClass: tracker.class.Issue,
skip: [
{
_class: core.class.TxCollectionCUD,
collection: 'comments'
}
]
})
builder.createDoc(activity.class.ActivityMessageControl, core.space.Model, {
objectClass: tracker.class.Milestone,
skip: [
{
_class: core.class.TxCollectionCUD,
collection: 'comments'
}
]
})
builder.createDoc(activity.class.ActivityMessageControl, core.space.Model, {
objectClass: tracker.class.Component,
skip: [
{
_class: core.class.TxCollectionCUD,
collection: 'comments'
}
]
})
builder.createDoc(activity.class.ActivityMessageControl, core.space.Model, {
objectClass: tracker.class.IssueTemplate,
skip: [
{
_class: core.class.TxCollectionCUD,
collection: 'comments'
}
]
})
builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: tracker.class.Issue,
components: { input: chunter.component.ChatMessageInput }
+3 -9
View File
@@ -510,7 +510,9 @@ export interface DocIndexState extends Doc {
generationId?: string
// States for stages
stages: Record<string, boolean | string>
stages: Record<string, boolean>
needIndex: boolean
removed: boolean
@@ -522,14 +524,6 @@ export interface DocIndexState extends Doc {
shortSummary?: string | null
}
/**
* @public
*/
export interface IndexStageState extends Doc {
stageId: string
attributes: Record<string, any>
}
/**
* @public
*
-2
View File
@@ -32,7 +32,6 @@ import type {
EnumOf,
FullTextSearchContext,
Hyperlink,
IndexStageState,
IndexingConfiguration,
Interface,
Markup,
@@ -135,7 +134,6 @@ export default plugin(coreId, {
UserStatus: '' as Ref<Class<UserStatus>>,
TypeRelatedDocument: '' as Ref<Class<Type<RelatedDocument>>>,
DocIndexState: '' as Ref<Class<DocIndexState>>,
IndexStageState: '' as Ref<Class<IndexStageState>>,
DomainIndexConfiguration: '' as Ref<Class<DomainIndexConfiguration>>,
Configuration: '' as Ref<Class<Configuration>>,
+14
View File
@@ -116,6 +116,20 @@ export class MeasureMetricsContext implements MeasureContext {
}
}
withSync<T>(
name: string,
params: ParamsType,
op: (ctx: MeasureContext) => T,
fullParams?: ParamsType | (() => FullParamsType)
): T {
const c = this.newChild(name, params, fullParams, this.logger)
try {
return op(c)
} finally {
c.end()
}
}
async withLog<T>(
name: string,
params: ParamsType,
+7
View File
@@ -63,6 +63,13 @@ export interface MeasureContext {
fullParams?: FullParamsType | (() => FullParamsType)
) => Promise<T>
withSync: <T>(
name: string,
params: ParamsType,
op: (ctx: MeasureContext) => T,
fullParams?: FullParamsType | (() => FullParamsType)
) => T
withLog: <T>(
name: string,
params: ParamsType,
+9 -4
View File
@@ -337,13 +337,18 @@ export class ModelDb extends MemDb {
}
protected async txUpdateDoc (tx: TxUpdateDoc<Doc>): Promise<TxResult> {
const doc = this.getObject(tx.objectId) as any
TxProcessor.updateDoc2Doc(doc, tx)
return tx.retrieve === true ? { object: doc } : {}
try {
const doc = this.getObject(tx.objectId) as any
TxProcessor.updateDoc2Doc(doc, tx)
return tx.retrieve === true ? { object: doc } : {}
} catch (err: any) {}
return {}
}
protected async txRemoveDoc (tx: TxRemoveDoc<Doc>): Promise<TxResult> {
this.delDoc(tx.objectId)
try {
this.delDoc(tx.objectId)
} catch (err: any) {}
return {}
}
+2 -1
View File
@@ -136,7 +136,8 @@ export default plugin(presentationId, {
Endpoint: '' as Metadata<string>,
FrontUrl: '' as Asset,
PreviewConfig: '' as Metadata<PreviewConfig | undefined>,
ClientHook: '' as Metadata<ClientHook>
ClientHook: '' as Metadata<ClientHook>,
SessionId: '' as Metadata<string>
},
status: {
FileTooLarge: '' as StatusCode
+1
View File
@@ -25,6 +25,7 @@
--primary-button-hovered: #3575DE;
--primary-button-pressed: #1C52AB;
--primary-button-focused: #205DC2;
--secondary-button-color: rgba(0, 0, 0, .8);
--secondary-button-default: #D3E1F8;
--secondary-button-hovered: #BDD2F5;
--secondary-button-pressed: #A7C3F1;
+1
View File
@@ -241,6 +241,7 @@
}
}
&.secondary {
color: var(--secondary-button-color);
background-color: var(--secondary-button-default);
&:hover { background-color: var(--secondary-button-hovered); }
&:active,
@@ -110,6 +110,7 @@
}
}
&.primary {
color: var(--secondary-button-color);
background-color: var(--secondary-button-default);
&:hover {
background-color: var(--secondary-button-hovered);
+161
View File
@@ -0,0 +1,161 @@
<!--
// Copyright © 2024 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { createEventDispatcher, onDestroy } from 'svelte'
import CodeInput from './CodeInput.svelte'
export let fields: { id: string, name: string, optional: boolean }[] = []
export let size: 'small' | 'medium' = 'small'
export let kind: 'primary' | 'secondary' = 'primary'
export let padding: string | null = null
export let minHeight: string | null = null
const dispatch = createEventDispatcher()
const formData: Record<string, string> = Object.fromEntries(fields.map(({ name }) => [name, '']))
let formElement: HTMLFormElement | undefined
function trim (field: string): void {
formData[field] = formData[field].trim()
}
async function validateCode (): Promise<void> {
const code = Object.values(formData).join('')
dispatch('submit', code)
}
function onInput (e: Event): void {
if (e.target == null) return
const target = e.target as HTMLInputElement
const { value } = target
if (value == null || value === '') return
const index = fields.findIndex(({ id }) => id === target.id)
if (index === -1) return
if (Object.values(formData).every((v) => v !== '')) {
void validateCode()
return
}
const nextField = fields[index + 1]
if (nextField === undefined) return
const nextInput = formElement?.querySelector(`input[name="${nextField.name}"]`) as HTMLInputElement
if (nextInput != null) {
nextInput.focus()
}
}
function onKeydown (e: KeyboardEvent): void {
const key = e.key.toLowerCase()
const target = e.target as HTMLInputElement
if (key !== 'backspace' && key !== 'delete') return
const index = fields.findIndex(({ id }) => id === target.id)
if (index === -1) return
const { value } = target
target.value = ''
formData[fields[index].name] = ''
if (value === '') {
const prevField = fields[index - 1]
if (prevField === undefined) return
const prevInput = formElement?.querySelector(`input[name="${prevField.name}"]`) as HTMLInputElement
if (prevInput != null) {
prevInput.focus()
}
}
}
function onPaste (e: ClipboardEvent): void {
e.preventDefault()
if (e.clipboardData == null) return
const text = e.clipboardData.getData('text')
const digits = text.split('').filter((it) => it !== ' ')
if (digits.length !== fields.length) return
let focusName: string | undefined = undefined
for (const field of fields) {
const digit = digits.shift()
if (digit === undefined) break
formData[field.name] = digit
focusName = field.name
}
if (focusName !== undefined && focusName !== '' && formElement) {
const input = formElement.querySelector(`input[name="${focusName}"]`) as HTMLInputElement | undefined
input?.focus()
}
if (Object.values(formData).every((v) => v !== '')) {
void validateCode()
}
}
$: if (formElement != null) {
formElement.addEventListener('input', onInput)
formElement.addEventListener('keydown', onKeydown)
formElement.addEventListener('paste', onPaste)
const firstInput = formElement.querySelector(`input[name="${fields[0].name}"]`) as HTMLInputElement | undefined
firstInput?.focus()
}
onDestroy(() => {
if (formElement !== undefined) {
formElement.removeEventListener('input', onInput)
formElement.removeEventListener('keydown', onKeydown)
formElement.removeEventListener('paste', onPaste)
}
})
</script>
<form bind:this={formElement} class="container" style:padding style:min-height={minHeight}>
<div class="form">
{#each fields as field, index (field.name)}
{#if index === fields.length / 2}
<div class="separator" />
{/if}
<div class={'form-row'}>
<CodeInput
id={field.id}
name={field.name}
{size}
{kind}
bind:value={formData[field.name]}
on:blur={() => {
trim(field.name)
}}
/>
</div>
{/each}
</div>
</form>
<style lang="scss">
.separator {
display: block;
width: 0.75rem;
height: 1px;
background-color: var(--theme-button-border);
flex-shrink: 0;
}
.container {
overflow: hidden;
display: flex;
flex-direction: column;
.form {
display: flex;
gap: 1rem;
margin-top: 1.5rem;
align-items: center;
}
}
</style>
+11 -4
View File
@@ -18,9 +18,10 @@
export let id: string | undefined = undefined
export let name: string | undefined = undefined
export let size: 'small' | 'medium' = 'small'
export let kind: 'primary' | 'secondary' = 'primary'
</script>
<div class="container">
<div class="container {kind}">
<input
class={size}
type="text"
@@ -45,14 +46,20 @@
display: flex;
flex-direction: column;
padding: 0;
background-color: var(--theme-button-default);
border: 1px solid var(--theme-button-border);
caret-color: var(--theme-caret-color);
&.primary {
caret-color: var(--theme-content-color);
}
&.secondary {
caret-color: var(--theme-caret-color);
}
&:focus-within {
background-color: var(--theme-button-focused);
border-color: var(--theme-list-divider-color);
background-color: var(--theme-button-focused);
}
input {
+32 -29
View File
@@ -26,6 +26,7 @@
export let colorsSchema: 'default' | 'lumia' = 'default'
export let updateOnMouse = true
export let lazy = false
export let minHeight: string | null = null
export let highlightIndex: number | undefined = undefined
const getKey: (index: number) => string = (index) => index.toString()
@@ -76,35 +77,37 @@
>
{#each Array(count) as _, row (getKey(row))}
{#if lazy}
<Lazy>
<ListViewItem
bind:element={refs[row]}
{colorsSchema}
{addClass}
{row}
{kind}
isHighlighted={row === highlightIndex}
selected={row === selection}
on:click={() => dispatch('click', row)}
on:mouseover={mouseAttractor(() => {
if (updateOnMouse) {
onRow(row)
}
})}
on:mouseenter={mouseAttractor(() => {
if (updateOnMouse) {
onRow(row)
}
})}
>
<svelte:fragment slot="category" let:item={itemIndex}>
<slot name="category" item={itemIndex} />
</svelte:fragment>
<svelte:fragment slot="item" let:item={itemIndex}>
<slot name="item" item={itemIndex} />
</svelte:fragment>
</ListViewItem>
</Lazy>
<div style="min-height: {minHeight}">
<Lazy>
<ListViewItem
bind:element={refs[row]}
{colorsSchema}
{addClass}
{row}
{kind}
isHighlighted={row === highlightIndex}
selected={row === selection}
on:click={() => dispatch('click', row)}
on:mouseover={mouseAttractor(() => {
if (updateOnMouse) {
onRow(row)
}
})}
on:mouseenter={mouseAttractor(() => {
if (updateOnMouse) {
onRow(row)
}
})}
>
<svelte:fragment slot="category" let:item={itemIndex}>
<slot name="category" item={itemIndex} />
</svelte:fragment>
<svelte:fragment slot="item" let:item={itemIndex}>
<slot name="item" item={itemIndex} />
</svelte:fragment>
</ListViewItem>
</Lazy>
</div>
{:else}
<ListViewItem
bind:element={refs[row]}
+1
View File
@@ -265,6 +265,7 @@ export { default as SimpleTimePopup } from './components/calendar/SimpleTimePopu
export { default as NumberInput } from './components/NumberInput.svelte'
export { default as Lazy } from './components/Lazy.svelte'
export { default as TimeZonesPopup } from './components/TimeZonesPopup.svelte'
export { default as CodeForm } from './components/CodeForm.svelte'
export { default as CodeInput } from './components/CodeInput.svelte'
export { default as TimeLeft } from './components/TimeLeft.svelte'
+6 -1
View File
@@ -45,6 +45,11 @@
"SaveForLater": "Save for later",
"RemoveFromLater": "Remove from saved",
"ReactionNotificationTitle": "Reaction on {title}",
"ReactionNotificationBody": "{senderName}: {reaction}"
"ReactionNotificationBody": "{senderName}: {reaction}",
"NewObject": "New {object}",
"RemovedObject": "Removed {object}",
"ChangedObject": "Changed {object}",
"UnsetObject": "Unset {object}",
"UpdatedObject": "Updated {object}"
}
}
+6 -1
View File
@@ -44,6 +44,11 @@
"SaveForLater": "Guardar para mas tarde",
"RemoveFromLater": "Remover de los Guardados",
"ReactionNotificationTitle": "Reacción sobre {title}",
"ReactionNotificationBody": "{senderName}: {reaction}"
"ReactionNotificationBody": "{senderName}: {reaction}",
"NewObject": "Nuevo {object}",
"RemovedObject": "Eliminado {object}",
"ChangedObject": "Cambiado {object}",
"UnsetObject": "No establecido {object}",
"UpdatedObject": "Actualizado {object}"
}
}
+6 -1
View File
@@ -45,6 +45,11 @@
"SaveForLater": "Enregistrer pour plus tard",
"RemoveFromLater": "Retirer des enregistrements",
"ReactionNotificationTitle": "Réaction sur {title}",
"ReactionNotificationBody": "{senderName}: {reaction}"
"ReactionNotificationBody": "{senderName}: {reaction}",
"NewObject": "Nouveau {object}",
"RemovedObject": "Supprimé {object}",
"ChangedObject": "Modifié {object}",
"UnsetObject": "Non défini {object}",
"UpdatedObject": "Mis à jour {object}"
}
}
+6 -1
View File
@@ -44,6 +44,11 @@
"SaveForLater": "Guardar para mais tarde",
"RemoveFromLater": "Remover dos Guardados",
"ReactionNotificationTitle": "Reação em {title}",
"ReactionNotificationBody": "{senderName}: {reaction}"
"ReactionNotificationBody": "{senderName}: {reaction}",
"NewObject": "Novo {object}",
"RemovedObject": "Removido {object}",
"ChangedObject": "Alterado {object}",
"UnsetObject": "Não definido {object}",
"UpdatedObject": "Atualizado {object}"
}
}
+6 -1
View File
@@ -45,6 +45,11 @@
"SaveForLater": "Cохранить",
"RemoveFromLater": "Удалить из сохраненных",
"ReactionNotificationTitle": "Реакция на {title}",
"ReactionNotificationBody": "{senderName}: {reaction}"
"ReactionNotificationBody": "{senderName}: {reaction}",
"NewObject": "Новый(ые) {object}",
"RemovedObject": "Удален(ы) {object}",
"ChangedObject": "Изменил(а) {object}",
"UnsetObject": "Сбросил(а) {object}",
"UpdatedObject": "Обновил(а) {object}"
}
}
+6 -1
View File
@@ -45,6 +45,11 @@
"SaveForLater": "稍后保存",
"RemoveFromLater": "从保存中移除",
"ReactionNotificationTitle": "反應於 {title}",
"ReactionNotificationBody": "{senderName}: {reaction}"
"ReactionNotificationBody": "{senderName}: {reaction}",
"NewObject": "新 {object}",
"RemovedObject": "移除 {object}",
"ChangedObject": "更改 {object}",
"UnsetObject": "取消设置 {object}",
"UpdatedObject": "更新 {object}"
}
}
@@ -16,20 +16,22 @@
import { Component } from '@hcengineering/ui'
import { AttributeModel } from '@hcengineering/view'
import { getClient } from '@hcengineering/presentation'
import { DocAttributeUpdates, DocUpdateMessageViewlet } from '@hcengineering/activity'
import { DocAttributeUpdates, DocUpdateMessage, DocUpdateMessageViewlet } from '@hcengineering/activity'
import { Doc, Ref, Space } from '@hcengineering/core'
import activity from '../../plugin'
import AddedAttributesPresenter from './attributes/AddedAttributesPresenter.svelte'
import RemovedAttributesPresenter from './attributes/RemovedAttributesPresenter.svelte'
import SetAttributesPresenter from './attributes/SetAttributesPresenter.svelte'
import { Ref, Space } from '@hcengineering/core'
export let viewlet: DocUpdateMessageViewlet | undefined
export let attributeUpdates: DocAttributeUpdates
export let attributeModel: AttributeModel
export let preview = false
export let space: Ref<Space> | undefined = undefined
export let object: Doc | undefined
export let message: DocUpdateMessage
const client = getClient()
const hierarchy = client.getHierarchy()
@@ -41,7 +43,7 @@
</script>
{#if presenter}
<Component is={presenter} props={{ value: attributeUpdates, space }} />
<Component is={presenter} props={{ value: attributeUpdates, space, object, message }} />
{:else}
{#if attributeUpdates.added.length}
<AddedAttributesPresenter {viewlet} {attributeModel} values={attributeUpdates.added} {preview} />
@@ -58,30 +58,40 @@
{@const removeMessages = valueMessages.filter(({ action }) => action === 'remove')}
{@const createMessages = valueMessages.filter(({ action }) => action === 'create')}
{@const createMessagesLen = createMessages.length}
{@const removeMessagesLen = removeMessages.length}
{#each createMessages as valueMessage, index}
<DocUpdateMessageObjectValue
message={valueMessage}
objectClass={valueMessage.objectClass}
objectId={valueMessage.objectId}
action={valueMessage.action}
{viewlet}
withIcon={index === 0}
hasSeparator={createMessages.length > 1 && index !== createMessages.length - 1}
hasSeparator={createMessagesLen > 1 && index !== createMessagesLen - 1}
{preview}
/>
{/each}
{#each removeMessages as valueMessage, index}
<DocUpdateMessageObjectValue
message={valueMessage}
objectClass={valueMessage.objectClass}
objectId={valueMessage.objectId}
action={valueMessage.action}
{viewlet}
withIcon={index === 0}
hasSeparator={removeMessages.length > 1 && index !== removeMessages.length - 1}
hasSeparator={removeMessagesLen > 1 && index !== removeMessagesLen - 1}
{preview}
/>
{/each}
{:else}
{@const len = valueMessages.length}
{#each valueMessages as valueMessage, index}
<DocUpdateMessageObjectValue
message={valueMessage}
objectClass={valueMessage.objectClass}
objectId={valueMessage.objectId}
action={valueMessage.action}
{viewlet}
hasSeparator={valueMessages.length > 1 && index !== valueMessages.length - 1}
hasSeparator={len > 1 && index !== len - 1}
{preview}
/>
{/each}
@@ -13,14 +13,16 @@
// limitations under the License.
-->
<script lang="ts">
import { buildRemovedDoc, checkIsObjectRemoved, DocNavLink, getDocLinkTitle } from '@hcengineering/view-resources'
import { Component, Icon, IconAdd, IconDelete } from '@hcengineering/ui'
import { createQuery, getClient } from '@hcengineering/presentation'
import view from '@hcengineering/view'
import { Class, Doc, Ref } from '@hcengineering/core'
import { DisplayDocUpdateMessage, DocUpdateMessageViewlet } from '@hcengineering/activity'
import { Class, Doc, Ref } from '@hcengineering/core'
import { createQuery, getClient } from '@hcengineering/presentation'
import { Component, Icon, IconAdd, IconDelete } from '@hcengineering/ui'
import view from '@hcengineering/view'
import { buildRemovedDoc, checkIsObjectRemoved, DocNavLink, getDocLinkTitle } from '@hcengineering/view-resources'
export let message: DisplayDocUpdateMessage
export let objectClass: DisplayDocUpdateMessage['objectClass']
export let objectId: DisplayDocUpdateMessage['objectId']
export let action: DisplayDocUpdateMessage['action']
export let viewlet: DocUpdateMessageViewlet | undefined
export let withIcon: boolean = false
export let hasSeparator: boolean = false
@@ -32,8 +34,8 @@
let object: Doc | undefined = undefined
$: objectPanel = hierarchy.classHierarchyMixin(message.objectClass, view.mixin.ObjectPanel)
$: objectPresenter = hierarchy.classHierarchyMixin(message.objectClass, view.mixin.ObjectPresenter)
$: objectPanel = hierarchy.classHierarchyMixin(objectClass, view.mixin.ObjectPanel)
$: objectPresenter = hierarchy.classHierarchyMixin(objectClass, view.mixin.ObjectPresenter)
async function getValue (object: Doc): Promise<string | undefined> {
if (viewlet?.valueAttr) {
@@ -48,6 +50,7 @@
if (isRemoved) {
object = await buildRemovedDoc(client, _id, _class)
objectQuery.unsubscribe()
} else {
objectQuery.query(_class, { _id }, (res) => {
object = res[0]
@@ -55,14 +58,14 @@
}
}
$: void loadObject(message.objectId, message.objectClass)
$: void loadObject(objectId, objectClass)
</script>
{#if object}
{#if withIcon && message.action === 'create'}
{#if withIcon && action === 'create'}
<Icon icon={IconAdd} size="x-small" />
{/if}
{#if withIcon && message.action === 'remove'}
{#if withIcon && action === 'remove'}
<Icon icon={IconDelete} size="x-small" />
{/if}
@@ -80,7 +83,7 @@
<DocNavLink
{object}
colorInherit
disabled={message.action === 'remove'}
disabled={action === 'remove'}
component={objectPanel?.component ?? view.component.EditDoc}
shrink={0}
>
@@ -26,7 +26,7 @@
import { Account, AttachedDoc, Class, Collection, Doc, Ref, Space } from '@hcengineering/core'
import { IntlString } from '@hcengineering/platform'
import { createQuery, getClient } from '@hcengineering/presentation'
import { Component, ShowMore, Action } from '@hcengineering/ui'
import { Action, Component, ShowMore } from '@hcengineering/ui'
import { AttributeModel } from '@hcengineering/view'
import { buildRemovedDoc, checkIsObjectRemoved } from '@hcengineering/view-resources'
@@ -144,17 +144,17 @@
}
async function loadParentObject (
message: DocUpdateMessage,
parentMessage?: ActivityMessage,
message: Pick<DocUpdateMessage, 'attachedTo' | 'attachedToClass' | 'objectId' | 'space'>,
parentMessage?: Pick<ActivityMessage, 'attachedTo' | 'space' | 'attachedToClass'>,
doc?: Doc
): Promise<void> {
if (!parentMessage && message.objectId === message.attachedTo) {
if (parentMessage === undefined && message.objectId === message.attachedTo) {
return
}
const _id = parentMessage ? parentMessage.attachedTo : message.attachedTo
const _class = parentMessage ? parentMessage.attachedToClass : message.attachedToClass
const space = parentMessage ? parentMessage.space : message.space
const _id = parentMessage !== undefined ? parentMessage.attachedTo : message.attachedTo
const _class = parentMessage !== undefined ? parentMessage.attachedToClass : message.attachedToClass
const space = parentMessage !== undefined ? parentMessage.space : message.space
if (doc !== undefined && doc._id === _id) {
parentObject = doc
@@ -229,7 +229,14 @@
/>
</ShowMore>
{:else if value.attributeUpdates && attributeModel}
<DocUpdateMessageAttributes attributeUpdates={value.attributeUpdates} {attributeModel} {viewlet} {space} />
<DocUpdateMessageAttributes
attributeUpdates={value.attributeUpdates}
{attributeModel}
{viewlet}
{space}
{object}
message={value}
/>
{/if}
</svelte:fragment>
</ActivityMessageTemplate>
@@ -130,6 +130,8 @@
{attributeModel}
{space}
{viewlet}
{object}
message={value}
preview
/>
{/if}
+7 -1
View File
@@ -67,6 +67,7 @@ export interface ActivityMessageControl<T extends Doc = Doc> extends Doc {
// Skip field activity operations.
skipFields?: (keyof T)[]
allowedFields?: (keyof T)[]
}
/**
@@ -308,7 +309,12 @@ export default plugin(activityId, {
Messages: '' as IntlString,
Thread: '' as IntlString,
ReactionNotificationTitle: '' as IntlString,
ReactionNotificationBody: '' as IntlString
ReactionNotificationBody: '' as IntlString,
NewObject: '' as IntlString,
RemovedObject: '' as IntlString,
ChangedObject: '' as IntlString,
UnsetObject: '' as IntlString,
UpdatedObject: '' as IntlString
},
component: {
Activity: '' as AnyComponent,
+7
View File
@@ -0,0 +1,7 @@
module.exports = {
extends: ['./node_modules/@hcengineering/platform-rig/profiles/default/eslint.config.json'],
parserOptions: {
tsconfigRootDir: __dirname,
project: './tsconfig.json'
}
}
+4
View File
@@ -0,0 +1,4 @@
*
!/lib/**
!CHANGELOG.md
/lib/**/__tests__/
+4
View File
@@ -0,0 +1,4 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
"rigPackageName": "@hcengineering/platform-rig"
}
+7
View File
@@ -0,0 +1,7 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'],
roots: ["./src"],
coverageReporters: ["text-summary", "html"]
}
+47
View File
@@ -0,0 +1,47 @@
{
"name": "@hcengineering/ai-bot",
"version": "0.6.0",
"main": "lib/index.js",
"svelte": "src/index.ts",
"types": "types/index.d.ts",
"files": [
"lib/**/*",
"types/**/*",
"tsconfig.json"
],
"author": "Hardcore Engineering Inc",
"scripts": {
"build": "compile",
"build:watch": "compile",
"format": "format src",
"test": "jest --passWithNoTests --silent",
"_phase:build": "compile transpile src",
"_phase:test": "jest --passWithNoTests --silent",
"_phase:format": "format src",
"_phase:validate": "compile validate"
},
"devDependencies": {
"@hcengineering/platform-rig": "^0.6.0",
"@types/jest": "^29.5.5",
"@typescript-eslint/eslint-plugin": "^6.11.0",
"@typescript-eslint/parser": "^6.11.0",
"eslint": "^8.54.0",
"eslint-config-prettier": "^8.8.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",
"jest": "^29.7.0",
"prettier": "^3.1.0",
"ts-jest": "^29.1.1",
"typescript": "^5.3.3"
},
"dependencies": {
"@hcengineering/chunter": "^0.6.20",
"@hcengineering/contact": "^0.6.24",
"@hcengineering/core": "^0.6.32",
"@hcengineering/platform": "^0.6.11",
"@hcengineering/server-core": "^0.6.1",
"@hcengineering/view": "^0.6.13"
}
}
+65
View File
@@ -0,0 +1,65 @@
//
// Copyright © 2024 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 { Account, Class, Doc, type Mixin, Ref, Space } from '@hcengineering/core'
import type { Plugin } from '@hcengineering/platform'
import { plugin } from '@hcengineering/platform'
import { ChatMessage } from '@hcengineering/chunter'
export const aiBotId = 'ai-bot' as Plugin
export const aiBotAccountEmail = 'huly.ai.bot@hc.engineering'
export interface AIBotEvent extends Doc {
collection: string
messageClass: Ref<Class<ChatMessage>>
message: string
}
export interface AIBotResponseEvent extends AIBotEvent {
objectId: Ref<Doc>
objectClass: Ref<Class<Doc>>
objectSpace: Ref<Space>
user: Ref<Account>
}
export interface AIBotTransferEvent extends AIBotEvent {
toEmail: string
toWorkspace: string
fromWorkspace: string
messageId: Ref<ChatMessage>
parentMessageId?: Ref<ChatMessage>
}
export interface TransferredMessage extends ChatMessage {
messageId: Ref<ChatMessage>
parentMessageId?: Ref<ChatMessage>
}
const aiBot = plugin(aiBotId, {
class: {
AIBotEvent: '' as Ref<Class<AIBotEvent>>,
AIBotTransferEvent: '' as Ref<Class<AIBotTransferEvent>>,
AIBotResponseEvent: '' as Ref<Class<AIBotResponseEvent>>
},
mixin: {
TransferredMessage: '' as Ref<Mixin<TransferredMessage>>
},
account: {
AIBot: '' as Ref<Account>
}
})
export default aiBot
+10
View File
@@ -0,0 +1,10 @@
{
"extends": "./node_modules/@hcengineering/platform-rig/profiles/default/tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib",
"declarationDir": "./types",
"tsBuildInfoFile": ".build/build.tsbuildinfo"
}
}
+9 -1
View File
@@ -111,6 +111,14 @@
"JoinChannelText": "Once you've joined, you'll be able to read all messages and contribute to the discussion.",
"NoMessagesInChannel": "Currently there are no messages",
"SendMessagesInChannel": "Send the first message to start the conversation",
"LatestMessages": "↓ Latest messages"
"LatestMessages": "↓ Latest messages",
"Joined": "Joined",
"Left": "Left",
"Added": "Added",
"Removed": "Removed",
"CreatedChannelOn": "Created this channel on {date}",
"ChannelMessages": "Channel messages",
"JoinChannel": "Join channel",
"YouJoinedChannel": "You have been joined to channel"
}
}
+9 -1
View File
@@ -111,6 +111,14 @@
"JoinChannelText": "Una vez que se haya unido, podrá leer todos los mensajes y contribuir a la discusión.",
"NoMessagesInChannel": "No hay mensajes en este canal todavía.",
"SendMessagesInChannel": "Envíe mensajes en este canal para comenzar la conversación.",
"LatestMessages": "↓ Últimos mensajes"
"LatestMessages": "↓ Últimos mensajes",
"Joined": "Unido",
"Left": "Abandonado",
"Added": "Añadido",
"Removed": "Eliminado",
"CreatedChannelOn": "Creó este canal el {date}",
"ChannelMessages": "Mensajes del canal",
"JoinChannel": "Unirse",
"YouJoinedChannel": "Te has unido al canal"
}
}
+9 -1
View File
@@ -111,6 +111,14 @@
"JoinChannelText": "Une fois que vous avez rejoint, vous pourrez lire tous les messages et participer à la discussion.",
"NoMessagesInChannel": "Il n'y a pas encore de messages dans ce canal.",
"SendMessagesInChannel": "Envoyez des messages pour commencer la conversation.",
"LatestMessages": "↓ Derniers messages"
"LatestMessages": "↓ Derniers messages",
"Joined": "Rejoint",
"Left": "Quitté",
"Added": "Ajouté",
"Removed": "Supprimé",
"CreatedChannelOn": "A créé ce canal le {date}",
"ChannelMessages": "Messages du canal",
"JoinChannel": "Rejoindre",
"YouJoinedChannel": "Vous avez rejoint le canal"
}
}
+9 -1
View File
@@ -111,6 +111,14 @@
"JoinChannelText": "Depois de entrar, você poderá ler todas as mensagens e contribuir na discussão.",
"NoMessagesInChannel": "Ainda não existem mensagens neste canal.",
"SendMessagesInChannel": "Envie a sua primeira mensagem!",
"LatestMessages": "↓ Últimas mensagens"
"LatestMessages": "↓ Últimas mensagens",
"Joined": "Entrou",
"Left": "Saiu",
"Added": "Adicionado(a)",
"Removed": "Removido(a)",
"CreatedChannelOn": "Criou este canal em {date}",
"ChannelMessages": "Mensagens do canal",
"JoinChannel": "Participar no canal",
"YouJoinedChannel": "Entrou no canal"
}
}
+9 -1
View File
@@ -111,6 +111,14 @@
"JoinChannelText": "Присоединившись, вы сможете читать все сообщения и участвовать в обсуждении.",
"NoMessagesInChannel": "В этом канале пока нет сообщений",
"SendMessagesInChannel": "Отправьте первое сообщение, чтобы начать общение",
"LatestMessages": "↓ Последние сообщения"
"LatestMessages": "↓ Последние сообщения",
"Joined": "Присоединился",
"Left": "Покинул",
"Added": "Добавил(а)",
"Removed": "Исключил(а)",
"CreatedChannelOn": "Создал этот канал {date}",
"ChannelMessages": "Сообщения каналов",
"JoinChannel": "Приссоединение к каналу",
"YouJoinedChannel": "Вы присоединились к каналу"
}
}
+9 -1
View File
@@ -111,6 +111,14 @@
"JoinChannelText": "加入后,你将能够阅读所有消息并参与讨论。",
"NoMessagesInChannel": "此频道中没有消息。",
"SendMessagesInChannel": "在此频道中发送消息。",
"LatestMessages": "↓ 最新消息"
"LatestMessages": "↓ 最新消息",
"Joined": "已加入",
"Left": "已离开",
"Added": "已添加",
"Removed": "已移除",
"CreatedChannelOn": "于 {date} 创建此频道",
"ChannelMessages": "频道消息",
"JoinChannel": "加入频道",
"YouJoinedChannel": "你已加入频道"
}
}
@@ -29,11 +29,8 @@ import { derived, get, type Readable, writable } from 'svelte/store'
import activity, { type ActivityMessage, type ActivityReference } from '@hcengineering/activity'
import attachment from '@hcengineering/attachment'
import { combineActivityMessages, sortActivityMessages } from '@hcengineering/activity-resources'
import { type ChatMessage } from '@hcengineering/chunter'
import notification, { type DocNotifyContext } from '@hcengineering/notification'
import chunter from './plugin'
export type LoadMode = 'forward' | 'backward'
export interface MessageMetadata {
@@ -241,7 +238,7 @@ export class ChannelDataProvider implements IChannelDataProvider {
if (loadAll) {
this.isTailLoading.set(true)
this.loadTail(undefined, combineActivityMessages)
this.loadTail()
} else if (isLoadingLatest) {
const startIndex = Math.max(0, count - this.limit)
this.isTailLoading.set(true)
@@ -260,11 +257,7 @@ export class ChannelDataProvider implements IChannelDataProvider {
this.isInitialLoadedStore.set(true)
}
private loadTail (
start?: Timestamp,
afterLoad?: (msgs: ActivityMessage[]) => Promise<ActivityMessage[]>,
query?: DocumentQuery<ActivityMessage>
): void {
private loadTail (start?: Timestamp, query?: DocumentQuery<ActivityMessage>): void {
if (this.chatId === undefined) {
this.isTailLoading.set(false)
return
@@ -283,12 +276,8 @@ export class ChannelDataProvider implements IChannelDataProvider {
...(this.tailStart !== undefined ? { createdOn: { $gte: this.tailStart } } : {})
},
async (res) => {
if (afterLoad !== undefined) {
const result = await afterLoad(res.reverse())
this.tailStore.set(result)
} else {
this.tailStore.set(res.reverse())
}
const result = await combineActivityMessages(res.reverse())
this.tailStore.set(result)
this.isTailLoaded.set(true)
this.isTailLoading.set(false)
@@ -325,7 +314,7 @@ export class ChannelDataProvider implements IChannelDataProvider {
const skipIds = this.getChunkSkipIds(loadAfter)
const messages = await client.findAll(
chunter.class.ChatMessage,
this.msgClass,
{
attachedTo: this.chatId,
space: this.space,
@@ -351,11 +340,11 @@ export class ChannelDataProvider implements IChannelDataProvider {
return {
from: from.createdOn ?? from.modifiedOn,
to: to.createdOn ?? to.modifiedOn,
data: isBackward ? messages.reverse() : messages
data: isBackward ? await combineActivityMessages(messages.reverse()) : await combineActivityMessages(messages)
}
}
getChunkSkipIds (after: Timestamp, loadTail = false): Array<Ref<ChatMessage>> {
getChunkSkipIds (after: Timestamp, loadTail = false): Array<Ref<ActivityMessage>> {
const chunks = get(this.chunksStore)
const metadata = get(this.metadataStore)
const tail = get(this.tailStore)
@@ -367,7 +356,7 @@ export class ChannelDataProvider implements IChannelDataProvider {
.flat()
.concat(loadTail ? [] : tailData)
.filter(({ createdOn }) => createdOn === after)
.map(({ _id }) => _id) as Array<Ref<ChatMessage>>
.map(({ _id }) => _id)
}
async loadNext (mode: LoadMode, loadAfter?: Timestamp, limit?: number): Promise<void> {
@@ -467,7 +456,7 @@ export class ChannelDataProvider implements IChannelDataProvider {
if (tailAfter !== undefined) {
const skipIds = chunks[chunks.length - 1]?.data.map(({ _id }) => _id) ?? []
this.loadTail(tailAfter, undefined, { _id: { $nin: skipIds } })
this.loadTail(tailAfter, { _id: { $nin: skipIds } })
this.isLoadingMoreStore.set(false)
return
}
@@ -13,7 +13,7 @@
// limitations under the License.
-->
<script lang="ts">
import { Class, Doc, getCurrentAccount, Ref } from '@hcengineering/core'
import { Doc, getCurrentAccount, Ref } from '@hcengineering/core'
import notification, { DocNotifyContext } from '@hcengineering/notification'
import activity, { ActivityMessage, ActivityMessagesFilter, WithReferences } from '@hcengineering/activity'
import { getClient, isSpace } from '@hcengineering/presentation'
@@ -60,16 +60,11 @@
let refsLoaded = false
$: isDocChannel = !hierarchy.isDerived(object._class, chunter.class.ChunterSpace)
$: _class = isDocChannel ? activity.class.ActivityMessage : chunter.class.ChatMessage
$: collection = isDocChannel ? 'comments' : 'messages'
$: void updateDataProvider(object._id, _class, selectedMessageId)
$: void updateDataProvider(object._id, selectedMessageId)
async function updateDataProvider (
attachedTo: Ref<Doc>,
_class: Ref<Class<ActivityMessage>>,
selectedMessageId?: Ref<ActivityMessage>
): Promise<void> {
async function updateDataProvider (attachedTo: Ref<Doc>, selectedMessageId?: Ref<ActivityMessage>): Promise<void> {
if (dataProvider === undefined) {
// For now loading all messages for documents with activity. Need to correct handle aggregation with pagination.
// Perhaps we should load all activity messages once, and keep loading in chunks only for ChatMessages then merge them correctly with activity messages
@@ -77,13 +72,21 @@
const ctx =
context ??
(await client.findOne(notification.class.DocNotifyContext, {
attachedTo: object._id,
objectId: object._id,
user: getCurrentAccount()._id
}))
const hasRefs = ((object as WithReferences<Doc>).references ?? 0) > 0
refsLoaded = hasRefs
const space = isSpace(object) ? object._id : object.space
dataProvider = new ChannelDataProvider(ctx, space, attachedTo, _class, selectedMessageId, loadAll, hasRefs)
dataProvider = new ChannelDataProvider(
ctx,
space,
attachedTo,
activity.class.ActivityMessage,
selectedMessageId,
loadAll,
hasRefs
)
}
}
@@ -47,7 +47,7 @@
/>
</div>
<Scroller>
{#each persons as person, index}
{#each persons as person, index (person._id)}
<div class="item" class:withoutBorder={index === persons.length - 1}>
<div class="item__content" class:disabled={disableRemoveFor.includes(person._id)}>
<UserDetails {person} showStatus />
@@ -123,7 +123,7 @@
<Label label={chunter.string.JoinChannelText} />
</span>
<span class="mt-4"> </span>
<ModernButton label={view.string.Join} kind="primary" on:click={join} />
<ModernButton label={view.string.Join} kind={'primary'} dataId={'btnJoin'} on:click={join} />
</div>
</div>
{:else}
@@ -43,19 +43,19 @@
$: pinnedQuery.query(
activity.class.ActivityMessage,
{ attachedTo: _id, isPinned: true, space: channelSpace },
(res: ActivityMessage[]) => {
pinnedMessagesCount = res.length
(res) => {
pinnedMessagesCount = res.total
},
{ projection: { _id: 1, space: 1, attachedTo: 1, isPinned: 1 } }
{ total: true, limit: 1 }
)
$: pinnedThreadsQuery.query(
chunter.class.ThreadMessage,
{ objectId: _id, isPinned: true, space: channelSpace },
(res: ThreadMessage[]) => {
pinnedThreadsCount = res.length
(res) => {
pinnedThreadsCount = res.total
},
{ projection: { _id: 1, space: 1, objectId: 1, isPinned: 1 } }
{ total: true, limit: 1 }
)
$: if (withRefs) {
@@ -0,0 +1,46 @@
<!--
// Copyright © 2024 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { DocUpdateMessage } from '@hcengineering/activity'
import chunter, { Channel } from '@hcengineering/chunter'
import { Label } from '@hcengineering/ui'
import ChannelIcon from '../ChannelIcon.svelte'
export let message: DocUpdateMessage
export let value: Channel | undefined
$: date = new Date(message.createdOn ?? message.modifiedOn).toLocaleString('default', {
day: '2-digit',
month: 'short',
year: 'numeric'
})
</script>
{#if value}
<span class="text flex-gap-1 overflow-label">
<ChannelIcon {value} size="x-small" />
<Label label={chunter.string.CreatedChannelOn} params={{ date }} />
</span>
{/if}
<style lang="scss">
.text {
display: inline-flex;
align-items: center;
color: var(--global-secondary-TextColor);
}
</style>
@@ -0,0 +1,128 @@
<!--
// Copyright © 2024 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { DocAttributeUpdates, DocUpdateMessage } from '@hcengineering/activity'
import { Person, PersonAccount } from '@hcengineering/contact'
import { Ref } from '@hcengineering/core'
import { personAccountByIdStore, personByIdStore, PersonPresenter } from '@hcengineering/contact-resources'
import { ChunterSpace } from '@hcengineering/chunter'
import { Label } from '@hcengineering/ui'
import view from '@hcengineering/view'
import chunter from '../../plugin'
import ChannelIcon from '../ChannelIcon.svelte'
export let message: DocUpdateMessage
export let value: DocAttributeUpdates
export let object: ChunterSpace | undefined
let addedPersons: Person[] = []
let removedPersons: Person[] = []
$: removedPersons = getPersons(value.removed)
$: addedPersons = getPersons(value.added.length > 0 ? value.added : value.set)
function getPersons (accounts: DocAttributeUpdates['removed' | 'added' | 'set']): Person[] {
const persons = new Set<Ref<Person>>()
for (const accountRef of accounts) {
const account = $personAccountByIdStore.get(accountRef as Ref<PersonAccount>)
if (account === undefined) continue
if (persons.has(account.person)) continue
persons.add(account.person)
}
return Array.from(persons)
.map((personRef) => $personByIdStore.get(personRef))
.filter((person): person is Person => person !== undefined)
}
$: account = $personAccountByIdStore.get(message.createdBy as Ref<PersonAccount>)
$: isJoined =
account !== undefined &&
removedPersons.length === 0 &&
addedPersons.length === 1 &&
addedPersons[0]._id === account.person
$: isLeave =
account !== undefined &&
removedPersons.length === 1 &&
addedPersons.length === 0 &&
removedPersons[0]._id === account.person
$: differentActions = addedPersons.length > 0 && removedPersons.length > 0
</script>
<span class="text overflow-label">
{#if addedPersons.length > 0}
<span class="inline-flex flex-gap-1">
{#if isJoined}
<span class="lower">
<Label label={chunter.string.Joined} />
</span>
<span class="inline-flex"><ChannelIcon value={object} size="x-small" /> {object?.name}</span>
{:else}
<Label label={chunter.string.Added} />
{#each addedPersons as person, index}
<PersonPresenter value={person} shouldShowAvatar={false} colorInherit overflowLabel={false} />
{#if index < addedPersons.length - 1}
<span class="separator"> , </span>
{/if}
{/each}
{/if}
</span>
{/if}
{#if differentActions}
<Label label={view.string.And} />
{/if}
{#if removedPersons.length > 0}
<span class="inline-flex flex-gap-1">
{#if isLeave}
<span class="lower">
<Label label={chunter.string.Left} />
</span>
<span class="inline-flex"><ChannelIcon value={object} size="x-small" /> {object?.name}</span>
{:else}
<span class:lower={differentActions}>
<Label label={chunter.string.Removed} />
</span>
{#each removedPersons as person, index}
<PersonPresenter value={person} shouldShowAvatar={false} colorInherit overflowLabel={false} />
{#if index < removedPersons.length - 1}
<span class="separator"> , </span>
{/if}
{/each}
{/if}
</span>
{/if}
</span>
<style lang="scss">
.inline-flex {
display: inline-flex;
align-items: center;
}
.text {
color: var(--global-secondary-TextColor);
}
.separator {
margin-left: -0.25rem;
}
</style>
@@ -36,7 +36,7 @@
$: threadId = context ? undefined : (_id as Ref<ActivityMessage>)
$: context &&
objectQuery.query(context.attachedToClass, { _id: context.attachedTo }, (res) => {
objectQuery.query(context.objectClass, { _id: context.objectId }, (res) => {
;[object] = res
})
</script>
@@ -18,6 +18,7 @@
import presentation, { getClient } from '@hcengineering/presentation'
import core, { getCurrentAccount } from '@hcengineering/core'
import notification from '@hcengineering/notification'
import contact, { PersonAccount } from '@hcengineering/contact'
import Lock from '../../icons/Lock.svelte'
import chunter from '../../../plugin'
@@ -51,19 +52,28 @@
$: canSave = !!channelName
async function save (): Promise<void> {
const accountId = getCurrentAccount()._id
const account = getCurrentAccount() as PersonAccount
const space = await client.findOne(
contact.class.PersonSpace,
{ person: account.person },
{ projection: { _id: 1 } }
)
if (!space) return
const channelId = await client.createDoc(chunter.class.Channel, core.space.Space, {
name: channelName,
description: '',
private: selectedVisibilityId === 'private',
archived: false,
members: [accountId],
members: [account._id],
topic: description
})
await client.createDoc(notification.class.DocNotifyContext, channelId, {
user: accountId,
attachedTo: channelId,
attachedToClass: chunter.class.Channel
await client.createDoc(notification.class.DocNotifyContext, space._id, {
user: account._id,
objectId: channelId,
objectClass: chunter.class.Channel,
objectSpace: core.space.Space,
isPinned: false
})
openChannel(channelId, chunter.class.Channel)
@@ -31,7 +31,7 @@
const dispatch = createEventDispatcher()
const client = getClient()
const myAccId = getCurrentAccount()._id
const myAcc = getCurrentAccount() as PersonAccount
const query = createQuery()
let employeeIds: Ref<Employee>[] = []
@@ -52,7 +52,7 @@
async function createDirectMessage (): Promise<void> {
const employeeAccounts = await client.findAll(contact.class.PersonAccount, { person: { $in: employeeIds } })
const accIds = [myAccId, ...employeeAccounts.filter(({ _id }) => _id !== myAccId).map(({ _id }) => _id)].sort()
const accIds = [myAcc._id, ...employeeAccounts.filter(({ _id }) => _id !== myAcc._id).map(({ _id }) => _id)].sort()
const existingDms = await client.findAll(chunter.class.DirectMessage, {})
@@ -75,9 +75,9 @@
}))
const context = await client.findOne(notification.class.DocNotifyContext, {
user: myAccId,
attachedTo: dmId,
attachedToClass: chunter.class.DirectMessage
person: myAcc.person,
objectId: dmId,
objectClass: chunter.class.DirectMessage
})
if (context !== undefined) {
@@ -86,10 +86,14 @@
return
}
await client.createDoc(notification.class.DocNotifyContext, dmId, {
user: myAccId,
attachedTo: dmId,
attachedToClass: chunter.class.DirectMessage
const space = await client.findOne(contact.class.PersonSpace, { person: myAcc.person }, { projection: { _id: 1 } })
if (!space) return
await client.createDoc(notification.class.DocNotifyContext, space._id, {
user: myAcc._id,
objectId: dmId,
objectClass: chunter.class.DirectMessage,
objectSpace: core.space.Space,
isPinned: false
})
openChannel(dmId, chunter.class.DirectMessage)
@@ -14,8 +14,8 @@
-->
<script lang="ts">
import activity from '@hcengineering/activity'
import { Class, Doc, getCurrentAccount, groupByArray, reduceCalls, Ref, SortingOrder } from '@hcengineering/core'
import notification, { DocNotifyContext } from '@hcengineering/notification'
import core, { Class, Doc, groupByArray, reduceCalls, Ref } from '@hcengineering/core'
import { DocNotifyContext } from '@hcengineering/notification'
import { InboxNotificationsClientImpl } from '@hcengineering/notification-resources'
import { IntlString } from '@hcengineering/platform'
import { createQuery, getClient, LiveQuery } from '@hcengineering/presentation'
@@ -40,32 +40,23 @@
const hierarchy = client.getHierarchy()
const inboxClient = InboxNotificationsClientImpl.getClient()
const contextByDocStore = inboxClient.contextByDoc
const contextsQuery = createQuery()
const contextsStore = inboxClient.contexts
const objectsQueryByClass = new Map<Ref<Class<Doc>>, { query: LiveQuery, limit: number }>()
let objectsByClass = new Map<Ref<Class<Doc>>, { docs: Doc[], total: number }>()
let contexts: DocNotifyContext[] = []
let objectsByClass = new Map<Ref<Class<Doc>>, { docs: Doc[], total: number }>()
let shouldPushObject = false
let sections: Section[] = []
$: contextsQuery.query(
notification.class.DocNotifyContext,
{
...model.query,
[`${chunter.mixin.ChannelInfo}.hidden`]: { $ne: true },
user: getCurrentAccount()._id
},
(res: DocNotifyContext[]) => {
contexts = res.filter(
({ attachedToClass }) =>
hierarchy.classHierarchyMixin(attachedToClass, activity.mixin.ActivityDoc) !== undefined
)
},
{ sort: { createdOn: SortingOrder.Ascending } }
)
$: contexts = $contextsStore.filter(({ objectClass, isPinned }) => {
if (model.isPinned !== isPinned) return false
if (model._class !== undefined && model._class !== objectClass) return false
if (model.skipClasses !== undefined && model.skipClasses.includes(objectClass)) return false
if (hierarchy.classHierarchyMixin(objectClass, activity.mixin.ActivityDoc) === undefined) return false
return true
})
$: loadObjects(contexts)
@@ -81,20 +72,29 @@
object !== undefined && getObjectGroup(object) === model.id && !$contextByDocStore.has(object._id)
function loadObjects (contexts: DocNotifyContext[]): void {
const contextsByClass = groupByArray(contexts, ({ attachedToClass }) => attachedToClass)
const contextsByClass = groupByArray(contexts, ({ objectClass }) => objectClass)
for (const [_class, ctx] of contextsByClass.entries()) {
const ids = ctx.map(({ attachedTo }) => attachedTo)
const isChunterSpace = hierarchy.isDerived(_class, chunter.class.ChunterSpace)
const ids = ctx.map(({ objectId }) => objectId)
const { query, limit } = objectsQueryByClass.get(_class) ?? {
query: createQuery(),
limit: hierarchy.isDerived(_class, chunter.class.ChunterSpace) ? -1 : model.maxSectionItems ?? 5
limit: isChunterSpace ? -1 : model.maxSectionItems ?? 5
}
objectsQueryByClass.set(_class, { query, limit: limit ?? model.maxSectionItems ?? 5 })
query.query(_class, { _id: { $in: limit !== -1 ? ids.slice(0, limit) : ids } }, (res: Doc[]) => {
objectsByClass = objectsByClass.set(_class, { docs: res, total: ids.length })
})
query.query(
_class,
{
_id: { $in: limit !== -1 ? ids.slice(0, limit) : ids },
space: isChunterSpace ? core.space.Space : undefined
},
(res) => {
objectsByClass = objectsByClass.set(_class, { docs: res, total: res.total })
},
{ total: true }
)
}
for (const [classRef, query] of objectsQueryByClass.entries()) {
@@ -196,7 +196,7 @@
if (_class === undefined) {
return model.getActionsFn(contexts)
} else {
return model.getActionsFn(contexts.filter(({ attachedToClass }) => attachedToClass === _class))
return model.getActionsFn(contexts.filter(({ objectClass }) => objectClass === _class))
}
}
</script>
@@ -120,7 +120,7 @@
noDivider
>
{#each sortedItems as item (item.id)}
{@const context = contexts.find(({ attachedTo }) => attachedTo === item.id)}
{@const context = contexts.find(({ objectId }) => objectId === item.id)}
<ChatNavItem {context} isSelected={objectId === item.id} {item} type={'type-object'} on:select />
{/each}
{#if canShowMore}
@@ -130,7 +130,7 @@
{/if}
<svelte:fragment slot="visible" let:isOpen>
{#if visibleItem !== undefined && !isOpen}
{@const context = contexts.find(({ attachedTo }) => attachedTo === visibleItem?.id)}
{@const context = contexts.find(({ objectId }) => objectId === visibleItem?.id)}
<ChatNavItem {context} isSelected item={visibleItem} type={'type-object'} on:select />
{/if}
</svelte:fragment>
@@ -13,7 +13,7 @@
// limitations under the License.
-->
<script lang="ts">
import { AccountRole, Doc, Ref, getCurrentAccount, hasAccountRole } from '@hcengineering/core'
import { AccountRole, Doc, getCurrentAccount, hasAccountRole } from '@hcengineering/core'
import { Scroller, SearchEdit, Label, ButtonIcon, IconAdd, showPopup, Menu } from '@hcengineering/ui'
import { DocNotifyContext } from '@hcengineering/notification'
import { SpecialNavModel } from '@hcengineering/workbench'
@@ -13,7 +13,7 @@
// limitations under the License.
//
import { type Asset, type IntlString } from '@hcengineering/platform'
import { type Account, type Doc, type DocumentQuery, type IdMap, type Ref, type UserStatus } from '@hcengineering/core'
import { type Account, type Class, type Doc, type IdMap, type Ref, type UserStatus } from '@hcengineering/core'
import { type DocNotifyContext } from '@hcengineering/notification'
import { type AnySvelteComponent, type IconSize, type Action } from '@hcengineering/ui'
import { type PersonAccount } from '@hcengineering/contact'
@@ -29,11 +29,13 @@ export interface SortFnOptions {
export interface ChatNavGroupModel {
id: ChatGroup
label?: IntlString
query: DocumentQuery<DocNotifyContext>
sortFn: (items: ChatNavItemModel[], options: SortFnOptions) => ChatNavItemModel[]
wrap: boolean
getActionsFn?: (contexts: DocNotifyContext[]) => Action[]
maxSectionItems?: number
isPinned: boolean
_class?: Ref<Class<Doc>>
skipClasses?: Array<Ref<Class<Doc>>>
}
export interface ChatNavItemModel {
@@ -13,7 +13,7 @@
// limitations under the License.
//
import notification, { type DocNotifyContext } from '@hcengineering/notification'
import {
import core, {
generateId,
type Ref,
SortingOrder,
@@ -139,29 +139,23 @@ export const chatNavGroupModels: ChatNavGroupModel[] = [
sortFn: sortAlphabetically,
wrap: false,
getActionsFn: getPinnedActions,
query: {
isPinned: true
}
isPinned: true
},
{
id: 'channels',
sortFn: sortAlphabetically,
wrap: true,
getActionsFn: getChannelsActions,
query: {
isPinned: { $ne: true },
attachedToClass: { $in: [chunter.class.Channel] }
}
isPinned: false,
_class: chunter.class.Channel
},
{
id: 'direct',
sortFn: sortDirects,
wrap: true,
getActionsFn: getDirectActions,
query: {
isPinned: { $ne: true },
attachedToClass: { $in: [chunter.class.DirectMessage] }
}
isPinned: false,
_class: chunter.class.DirectMessage
},
{
id: 'activity',
@@ -169,13 +163,8 @@ export const chatNavGroupModels: ChatNavGroupModel[] = [
wrap: true,
getActionsFn: getActivityActions,
maxSectionItems: 5,
query: {
isPinned: { $ne: true },
attachedToClass: {
// Ignore external channels until support is provided for them
$nin: [chunter.class.DirectMessage, chunter.class.Channel, contact.class.Channel]
}
}
isPinned: false,
skipClasses: [chunter.class.DirectMessage, chunter.class.Channel, contact.class.Channel]
}
]
@@ -262,7 +251,7 @@ function sortDirects (items: ChatNavItemModel[], option: SortFnOptions): ChatNav
function sortActivityChannels (items: ChatNavItemModel[], option: SortFnOptions): ChatNavItemModel[] {
const { contexts } = option
const contextByDoc = new Map(contexts.map((context) => [context.attachedTo, context]))
const contextByDoc = new Map(contexts.map((context) => [context.objectId, context]))
return items.sort((i1, i2) => {
const context1 = contextByDoc.get(i1.id)
@@ -386,7 +375,7 @@ export function loadSavedAttachments (): void {
savedAttachmentsQuery.query(
attachment.class.SavedAttachments,
{},
{ space: core.space.Workspace },
(res) => {
savedAttachmentsStore.set(res.filter(({ $lookup }) => $lookup?.attachedTo !== undefined))
},
@@ -35,7 +35,7 @@
let title: string | undefined = undefined
let channel: Doc | undefined = undefined
$: isThread = hierarchy.isDerived(context.attachedToClass, chunter.class.ThreadMessage)
$: isThread = hierarchy.isDerived(context.objectClass, chunter.class.ThreadMessage)
$: loadChannel(object, isThread)
$: channel &&
@@ -46,7 +46,7 @@
function loadChannel (object: ChatMessage, isThread: boolean): void {
const _class = isThread ? (object as ThreadMessage).objectClass : object.attachedToClass
const _id = isThread ? (object as ThreadMessage).objectId : object.attachedTo
console.log({ _class, _id, isThread, object })
void client.findOne(_class, { _id, ...(isThread ? { space: object.space } : {}) }).then((res) => {
channel = res
})
@@ -0,0 +1,24 @@
<!--
// Copyright © 2024 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { DisplayDocUpdateMessage } from '@hcengineering/activity'
import { BaseMessagePreview } from '@hcengineering/activity-resources'
import chunter from '../../plugin'
export let message: DisplayDocUpdateMessage
</script>
<BaseMessagePreview intlLabel={chunter.string.YouJoinedChannel} {message} on:click />
+9 -1
View File
@@ -48,6 +48,9 @@ import ChatMessageNotificationLabel from './components/notification/ChatMessageN
import ChatAside from './components/chat/ChatAside.svelte'
import ThreadMessagePreview from './components/threads/ThreadMessagePreview.svelte'
import ChatMessagePreview from './components/chat-message/ChatMessagePreview.svelte'
import ChannelCreatedMessage from './components/activity/ChannelCreatedMessage.svelte'
import MembersChangedMessage from './components/activity/MembersChangedMessage.svelte'
import JoinChannelNotificationPresenter from './components/notification/JoinChannelNotificationPresenter.svelte'
import {
ChannelTitleProvider,
@@ -177,7 +180,12 @@ export default async (): Promise<Resources> => ({
ThreadNotificationPresenter,
ChatAside,
ThreadMessagePreview,
ChatMessagePreview
ChatMessagePreview,
JoinChannelNotificationPresenter
},
activity: {
ChannelCreatedMessage,
MembersChangedMessage
},
function: {
GetDmName: getDmName,
+9 -1
View File
@@ -1,4 +1,11 @@
import { getCurrentLocation, getCurrentResolvedLocation, getLocation, type Location, navigate } from '@hcengineering/ui'
import {
closePanel,
getCurrentLocation,
getCurrentResolvedLocation,
getLocation,
type Location,
navigate
} from '@hcengineering/ui'
import { type Ref, type Doc, type Class } from '@hcengineering/core'
import type { ActivityMessage } from '@hcengineering/activity'
import { chunterId, type ChunterSpace, type ThreadMessage } from '@hcengineering/chunter'
@@ -184,5 +191,6 @@ export async function resetChunterLocIfEqual (_id: Ref<Doc>, _class: Ref<Class<D
loc.path[4] = ''
loc.query = {}
loc.path.length = 3
closePanel()
navigate(loc)
}
+17 -20
View File
@@ -491,47 +491,44 @@ export async function leaveChannelAction (
}
const client = getClient()
const channel =
props?.object ?? (await client.findOne(chunter.class.Channel, { _id: context.attachedTo as Ref<Channel> }))
props?.object ?? (await client.findOne(chunter.class.Channel, { _id: context.objectId as Ref<Channel> }))
if (channel === undefined) {
return
}
await leaveChannel(channel, getCurrentAccount()._id)
await client.remove(context)
await resetChunterLocIfEqual(channel._id, channel._class, channel)
}
export async function removeChannelAction (
context?: DocNotifyContext,
_?: Event,
props?: { object?: Doc }
): Promise<void> {
export async function removeChannelAction (context?: DocNotifyContext, _?: Event): Promise<void> {
if (context === undefined) {
return
}
const client = getClient()
const hierarchy = client.getHierarchy()
const inboxClient = InboxNotificationsClientImpl.getClient()
const { objectId, objectClass, objectSpace } = context
if (hierarchy.isDerived(context.attachedToClass, chunter.class.Channel)) {
const channel = await client.findOne(chunter.class.Channel, { _id: context.attachedTo as Ref<Channel> })
if (hierarchy.isDerived(objectClass, chunter.class.Channel)) {
const channel = await client.findOne(chunter.class.Channel, { _id: objectId as Ref<Channel>, space: objectSpace })
await leaveChannel(channel, getCurrentAccount()._id)
} else {
const object = await client.findOne(context.attachedToClass, { _id: context.attachedTo })
const account = getCurrentAccount() as PersonAccount
const object = await client.findOne(objectClass, { _id: objectId, space: objectSpace })
// const account = getCurrentAccount() as PersonAccount
await client.createMixin(context._id, context._class, context.space, chunter.mixin.ChannelInfo, { hidden: true })
const chatInfo = await client.findOne(chunter.class.ChatInfo, { user: account.person })
if (chatInfo !== undefined) {
await client.update(chatInfo, { hidden: chatInfo.hidden.concat([context._id]) })
}
await resetChunterLocIfEqual(context.attachedTo, context.attachedToClass, object)
// await client.createMixin(context._id, context._class, context.space, chunter.mixin.ChannelInfo, { hidden: true })
//
// const chatInfo = await client.findOne(chunter.class.ChatInfo, { user: account.person })
//
// if (chatInfo !== undefined) {
// await client.update(chatInfo, { hidden: chatInfo.hidden.concat([context._id]) })
// }
await resetChunterLocIfEqual(objectId, objectClass, object)
}
void inboxClient.readDoc(client, context.attachedTo)
await client.remove(context)
}
export function isThreadMessage (message: ActivityMessage): message is ThreadMessage {

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