From e205f313912dc90e408027d51084c6a95f051708 Mon Sep 17 00:00:00 2001 From: Denis Bunakalya Date: Wed, 13 Apr 2022 13:30:54 +0300 Subject: [PATCH 1/8] Chunter: Update channel last message and close thread on deletion from other user (#1389) * 1300 Update channel last message on deletion Signed-off-by: Denis Bunakalya * 1300 Close thread on deletion even from other user Signed-off-by: Denis Bunakalya --- .../src/components/Message.svelte | 14 +---- .../src/components/ThreadView.svelte | 12 ++++- server-plugins/chunter-resources/src/index.ts | 54 ++++++++++++++++++- 3 files changed, 65 insertions(+), 15 deletions(-) diff --git a/plugins/chunter-resources/src/components/Message.svelte b/plugins/chunter-resources/src/components/Message.svelte index 9f13f9871f..09cf44db81 100644 --- a/plugins/chunter-resources/src/components/Message.svelte +++ b/plugins/chunter-resources/src/components/Message.svelte @@ -21,7 +21,7 @@ import { NotificationClientImpl } from '@anticrm/notification-resources' import { getResource } from '@anticrm/platform' import { Avatar, getClient, MessageViewer } from '@anticrm/presentation' - import { ActionIcon, IconMoreH, Menu, showPopup, getCurrentLocation, navigate } from '@anticrm/ui' + import { ActionIcon, IconMoreH, Menu, showPopup } from '@anticrm/ui' import { Action } from '@anticrm/view' import { getActions } from '@anticrm/view-resources' import { createEventDispatcher } from 'svelte' @@ -59,19 +59,9 @@ action: chunter.actionImpl.SubscribeMessage } as Action) - async function deleteMessage () { - await client.remove(message) - const loc = getCurrentLocation() - - if (loc.path[3] === message._id) { - loc.path.length = 3 - navigate(loc) - } - } - const deleteAction = { label: chunter.string.DeleteMessage, - action: deleteMessage + action: async () => await client.remove(message) } const showMenu = async (ev: Event): Promise => { diff --git a/plugins/chunter-resources/src/components/ThreadView.svelte b/plugins/chunter-resources/src/components/ThreadView.svelte index b3de9b44fd..cb0bf625f2 100644 --- a/plugins/chunter-resources/src/components/ThreadView.svelte +++ b/plugins/chunter-resources/src/components/ThreadView.svelte @@ -20,7 +20,7 @@ import core, { Doc, generateId, getCurrentAccount, Ref, Space, TxFactory } from '@anticrm/core' import { NotificationClientImpl } from '@anticrm/notification-resources' import { createQuery, getClient } from '@anticrm/presentation' - import { IconClose, Label } from '@anticrm/ui' + import { IconClose, Label, getCurrentLocation, navigate } from '@anticrm/ui' import { afterUpdate, beforeUpdate, createEventDispatcher } from 'svelte' import { createBacklinks } from '../backlinks' import chunter from '../plugin' @@ -65,7 +65,15 @@ { _id: id }, - (res) => (message = res[0]), + (res) => { + message = res[0] + + if (!message) { + const loc = getCurrentLocation() + loc.path.length = 3 + navigate(loc) + } + }, { lookup: { _id: { attachments: attachment.class.Attachment }, diff --git a/server-plugins/chunter-resources/src/index.ts b/server-plugins/chunter-resources/src/index.ts index fbc79374e6..afe1de7110 100644 --- a/server-plugins/chunter-resources/src/index.ts +++ b/server-plugins/chunter-resources/src/index.ts @@ -15,7 +15,21 @@ import chunter, { Channel, Comment, Message, ThreadMessage } from '@anticrm/chunter' import { EmployeeAccount } from '@anticrm/contact' -import core, { Class, Doc, DocumentQuery, FindOptions, FindResult, Hierarchy, Ref, Tx, TxCreateDoc, TxProcessor, TxUpdateDoc, TxRemoveDoc } from '@anticrm/core' +import core, { + Class, + Doc, + DocumentQuery, + FindOptions, + FindResult, + Hierarchy, + Ref, + Tx, + TxCreateDoc, + TxProcessor, + TxUpdateDoc, + TxRemoveDoc, + TxCollectionCUD +} from '@anticrm/core' import login from '@anticrm/login' import { getMetadata } from '@anticrm/platform' import { TriggerControl } from '@anticrm/server-core' @@ -144,12 +158,50 @@ export async function MessageCreate (tx: Tx, control: TriggerControl): Promise { + const hierarchy = control.hierarchy + if (tx._class !== core.class.TxCollectionCUD) return [] + + const rmTx = (tx as TxCollectionCUD).tx + if (!hierarchy.isDerived(rmTx.objectClass, chunter.class.Message)) { + return [] + } + const createTx = (await control.findAll(core.class.TxCreateDoc, { + objectId: rmTx.objectId + }, { limit: 1 }))[0] + + const message = TxProcessor.createDoc2Doc(createTx as TxCreateDoc) + + const channel = (await control.findAll(chunter.class.Channel, { + _id: message.space + }, { limit: 1 }))[0] + + if (channel.lastMessage === message.createOn) { + const messages = await control.findAll(chunter.class.Message, { + attachedTo: channel._id + }) + const lastMessageDate = messages.reduce((maxDate, mess) => mess.createOn > maxDate ? mess.createOn : maxDate, 0) + + const updateTx = control.txFactory.createTxUpdateDoc(channel._class, channel.space, channel._id, { + lastMessage: lastMessageDate > 0 ? lastMessageDate : undefined + }) + + return [updateTx] + } + + return [] +} + /** * @public */ export async function ChunterTrigger (tx: Tx, control: TriggerControl): Promise { const promises = [ MessageCreate(tx, control), + MessageDelete(tx, control), CommentCreate(tx, control), CommentDelete(tx, control) ] From 4ba579b517772a2394b3129a72e7a4f6b96f1c62 Mon Sep 17 00:00:00 2001 From: Denis Bykhov <80476319+BykhovDenis@users.noreply.github.com> Date: Thu, 14 Apr 2022 11:30:30 +0600 Subject: [PATCH 2/8] Preferences (#1380) Signed-off-by: Denis Bykhov <80476319+BykhovDenis@users.noreply.github.com> --- common/config/rush/pnpm-lock.yaml | 131 ++++++++++++++++-- dev/prod/package.json | 2 + dev/prod/src/platform.ts | 4 +- dev/server/src/server.ts | 5 +- models/all/package.json | 4 +- models/all/src/index.ts | 4 +- models/preference/.eslintrc.js | 7 + models/preference/.npmignore | 4 + models/preference/config/rig.json | 18 +++ models/preference/package.json | 35 +++++ models/preference/src/index.ts | 37 +++++ models/preference/tsconfig.json | 8 ++ packages/core/src/server.ts | 7 +- plugins/preference-assets/.eslintrc.js | 7 + plugins/preference-assets/assets/icons.svg | 7 + plugins/preference-assets/config/rig.json | 18 +++ plugins/preference-assets/lang/en.json | 8 ++ plugins/preference-assets/lang/ru.json | 8 ++ plugins/preference-assets/package.json | 33 +++++ plugins/preference-assets/src/index.ts | 24 ++++ plugins/preference-assets/tsconfig.json | 15 ++ plugins/preference/.eslintrc.js | 7 + plugins/preference/.npmignore | 4 + plugins/preference/config/rig.json | 18 +++ plugins/preference/package.json | 33 +++++ plugins/preference/src/index.ts | 66 +++++++++ plugins/preference/tsconfig.json | 9 ++ plugins/workbench-resources/package.json | 1 + .../src/components/Navigator.svelte | 49 ++++--- .../src/components/navigator/SpacesNav.svelte | 19 ++- .../components/navigator/StarredNav.svelte | 113 +++++++++++++++ products/tracker/package.json | 2 + products/tracker/src/platform.ts | 2 + rush.json | 25 ++++ server-plugins/preference/.eslintrc.js | 7 + server-plugins/preference/.npmignore | 4 + server-plugins/preference/config/rig.json | 18 +++ server-plugins/preference/package.json | 35 +++++ server-plugins/preference/src/index.ts | 29 ++++ server-plugins/preference/tsconfig.json | 9 ++ server/core/src/fulltext.ts | 4 +- server/core/src/index.ts | 1 + server/core/src/pipeline.ts | 62 +++++++++ server/core/src/storage.ts | 23 +-- server/core/src/types.ts | 53 ++++++- server/middleware/.eslintrc.js | 7 + server/middleware/.npmignore | 4 + server/middleware/config/rig.json | 18 +++ server/middleware/package.json | 34 +++++ server/middleware/src/index.ts | 16 +++ server/middleware/src/private.ts | 88 ++++++++++++ server/middleware/tsconfig.json | 8 ++ server/mongo/src/storage.ts | 16 +-- server/server/package.json | 4 +- server/server/src/server.ts | 37 +++-- server/ws/src/__tests__/server.test.ts | 8 +- server/ws/src/server.ts | 58 +++++--- 57 files changed, 1163 insertions(+), 114 deletions(-) create mode 100644 models/preference/.eslintrc.js create mode 100644 models/preference/.npmignore create mode 100644 models/preference/config/rig.json create mode 100644 models/preference/package.json create mode 100644 models/preference/src/index.ts create mode 100644 models/preference/tsconfig.json create mode 100644 plugins/preference-assets/.eslintrc.js create mode 100644 plugins/preference-assets/assets/icons.svg create mode 100644 plugins/preference-assets/config/rig.json create mode 100644 plugins/preference-assets/lang/en.json create mode 100644 plugins/preference-assets/lang/ru.json create mode 100644 plugins/preference-assets/package.json create mode 100644 plugins/preference-assets/src/index.ts create mode 100644 plugins/preference-assets/tsconfig.json create mode 100644 plugins/preference/.eslintrc.js create mode 100644 plugins/preference/.npmignore create mode 100644 plugins/preference/config/rig.json create mode 100644 plugins/preference/package.json create mode 100644 plugins/preference/src/index.ts create mode 100644 plugins/preference/tsconfig.json create mode 100644 plugins/workbench-resources/src/components/navigator/StarredNav.svelte create mode 100644 server-plugins/preference/.eslintrc.js create mode 100644 server-plugins/preference/.npmignore create mode 100644 server-plugins/preference/config/rig.json create mode 100644 server-plugins/preference/package.json create mode 100644 server-plugins/preference/src/index.ts create mode 100644 server-plugins/preference/tsconfig.json create mode 100644 server/core/src/pipeline.ts create mode 100644 server/middleware/.eslintrc.js create mode 100644 server/middleware/.npmignore create mode 100644 server/middleware/config/rig.json create mode 100644 server/middleware/package.json create mode 100644 server/middleware/src/index.ts create mode 100644 server/middleware/src/private.ts create mode 100644 server/middleware/tsconfig.json diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index 3583581fb2..de81e6d05a 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -53,6 +53,7 @@ specifiers: '@rush-temp/login': file:./projects/login.tgz '@rush-temp/login-assets': file:./projects/login-assets.tgz '@rush-temp/login-resources': file:./projects/login-resources.tgz + '@rush-temp/middleware': file:./projects/middleware.tgz '@rush-temp/model': file:./projects/model.tgz '@rush-temp/model-activity': file:./projects/model-activity.tgz '@rush-temp/model-all': file:./projects/model-all.tgz @@ -67,6 +68,7 @@ specifiers: '@rush-temp/model-inventory': file:./projects/model-inventory.tgz '@rush-temp/model-lead': file:./projects/model-lead.tgz '@rush-temp/model-notification': file:./projects/model-notification.tgz + '@rush-temp/model-preference': file:./projects/model-preference.tgz '@rush-temp/model-presentation': file:./projects/model-presentation.tgz '@rush-temp/model-recruit': file:./projects/model-recruit.tgz '@rush-temp/model-rig': file:./projects/model-rig.tgz @@ -101,6 +103,8 @@ specifiers: '@rush-temp/platform': file:./projects/platform.tgz '@rush-temp/platform-rig': file:./projects/platform-rig.tgz '@rush-temp/pod-account': file:./projects/pod-account.tgz + '@rush-temp/preference': file:./projects/preference.tgz + '@rush-temp/preference-assets': file:./projects/preference-assets.tgz '@rush-temp/presentation': file:./projects/presentation.tgz '@rush-temp/prod': file:./projects/prod.tgz '@rush-temp/prod-tracker': file:./projects/prod-tracker.tgz @@ -127,6 +131,7 @@ specifiers: '@rush-temp/server-lead-resources': file:./projects/server-lead-resources.tgz '@rush-temp/server-notification': file:./projects/server-notification.tgz '@rush-temp/server-notification-resources': file:./projects/server-notification-resources.tgz + '@rush-temp/server-preference': file:./projects/server-preference.tgz '@rush-temp/server-recruit': file:./projects/server-recruit.tgz '@rush-temp/server-recruit-resources': file:./projects/server-recruit-resources.tgz '@rush-temp/server-setting': file:./projects/server-setting.tgz @@ -320,6 +325,7 @@ dependencies: '@rush-temp/login': file:projects/login.tgz '@rush-temp/login-assets': file:projects/login-assets.tgz_typescript@4.5.4 '@rush-temp/login-resources': file:projects/login-resources.tgz_096c09b0b673a57c275d9767a12070b1 + '@rush-temp/middleware': file:projects/middleware.tgz '@rush-temp/model': file:projects/model.tgz '@rush-temp/model-activity': file:projects/model-activity.tgz_typescript@4.5.4 '@rush-temp/model-all': file:projects/model-all.tgz_typescript@4.5.4 @@ -334,6 +340,7 @@ dependencies: '@rush-temp/model-inventory': file:projects/model-inventory.tgz_typescript@4.5.4 '@rush-temp/model-lead': file:projects/model-lead.tgz_typescript@4.5.4 '@rush-temp/model-notification': file:projects/model-notification.tgz_typescript@4.5.4 + '@rush-temp/model-preference': file:projects/model-preference.tgz_typescript@4.5.4 '@rush-temp/model-presentation': file:projects/model-presentation.tgz_typescript@4.5.4 '@rush-temp/model-recruit': file:projects/model-recruit.tgz_typescript@4.5.4 '@rush-temp/model-rig': file:projects/model-rig.tgz_37f79b97d0d86442e45d380c86f520c5 @@ -368,6 +375,8 @@ dependencies: '@rush-temp/platform': file:projects/platform.tgz '@rush-temp/platform-rig': file:projects/platform-rig.tgz_37f79b97d0d86442e45d380c86f520c5 '@rush-temp/pod-account': file:projects/pod-account.tgz + '@rush-temp/preference': file:projects/preference.tgz + '@rush-temp/preference-assets': file:projects/preference-assets.tgz '@rush-temp/presentation': file:projects/presentation.tgz_096c09b0b673a57c275d9767a12070b1 '@rush-temp/prod': file:projects/prod.tgz_a07ec81d4d975778878ca12202ea119e '@rush-temp/prod-tracker': file:projects/prod-tracker.tgz_a07ec81d4d975778878ca12202ea119e @@ -394,6 +403,7 @@ dependencies: '@rush-temp/server-lead-resources': file:projects/server-lead-resources.tgz '@rush-temp/server-notification': file:projects/server-notification.tgz '@rush-temp/server-notification-resources': file:projects/server-notification-resources.tgz + '@rush-temp/server-preference': file:projects/server-preference.tgz '@rush-temp/server-recruit': file:projects/server-recruit.tgz '@rush-temp/server-recruit-resources': file:projects/server-recruit-resources.tgz '@rush-temp/server-setting': file:projects/server-setting.tgz @@ -11738,7 +11748,7 @@ packages: dev: false file:projects/board-resources.tgz_096c09b0b673a57c275d9767a12070b1: - resolution: {integrity: sha512-vNNtH4in7D54mv4ckcYvE+iLxqt10x0kpToPg+xMRl4LuXIehuKDM+x9uiSb61y7FBesLyrJ3+606D99cc+1fQ==, tarball: file:projects/board-resources.tgz} + resolution: {integrity: sha512-nI0XMUXXwKiWWIwh1LHsD6P+VjNPy1M1NN3VBxLzPFsG6TGmEX/RHZhR9a6Ao6YwY783n8TfdrajFABs8IrGPg==, tarball: file:projects/board-resources.tgz} id: file:projects/board-resources.tgz name: '@rush-temp/board-resources' version: 0.0.0 @@ -11773,7 +11783,7 @@ packages: dev: false file:projects/board.tgz: - resolution: {integrity: sha512-K6XdiXv9w+xvcegm1Ql7XmBXIR/nMElWROWkVabz/SGhrvmENtjOa6mKVZploQf5cTafItd1V2e/NxZu8DnrgA==, tarball: file:projects/board.tgz} + resolution: {integrity: sha512-Jgyvcqd3njP8w/I6a1M7PNNCQWojSyNBzzVauBja3IRaFLMgFv7g3zqbilHkJw1IoYpsLab8wZoJ7X4+TM1s6w==, tarball: file:projects/board.tgz} name: '@rush-temp/board' version: 0.0.0 dependencies: @@ -12770,6 +12780,26 @@ packages: - supports-color dev: false + file:projects/middleware.tgz: + resolution: {integrity: sha512-beRGnoVJZVP/5mEU9bxmXLikwWCRLXBWaeFyIGIZbgjHypFCY57237qoOpLEgXwmBzEUk2jhophOYiT+Xd4qLQ==, tarball: file:projects/middleware.tgz} + name: '@rush-temp/middleware' + version: 0.0.0 + dependencies: + '@rushstack/heft': 0.41.8 + '@types/heft-jest': 1.0.2 + '@typescript-eslint/eslint-plugin': 5.7.0_c25e8c1f4f4f7aaed27aa6f9ce042237 + '@typescript-eslint/parser': 5.7.0_eslint@7.32.0+typescript@4.5.4 + eslint: 7.32.0 + eslint-config-standard-with-typescript: 21.0.1_ce2fa0c4dfa1c256100cababd749a13a + eslint-plugin-import: 2.25.3_eslint@7.32.0 + eslint-plugin-node: 11.1.0_eslint@7.32.0 + eslint-plugin-promise: 5.2.0_eslint@7.32.0 + prettier: 2.5.1 + typescript: 4.5.4 + transitivePeerDependencies: + - supports-color + dev: false + file:projects/model-activity.tgz_typescript@4.5.4: resolution: {integrity: sha512-TslFvrgSosU016NXZCahvZcTkVGONQ37mfJCHJ/PRA9I33M++MaIlw/ZIhWl9Z/e8dCuYW8PVrlijoYwCmc+kg==, tarball: file:projects/model-activity.tgz} id: file:projects/model-activity.tgz @@ -12792,7 +12822,7 @@ packages: dev: false file:projects/model-all.tgz_typescript@4.5.4: - resolution: {integrity: sha512-RLEP3hoSiGNZeIxc4bRN7FQFcFFz4Sv6E2rF1tQHkBIIFJdUSVxDk4xwkopcU5UNNZyoW1UrwlG5jXOzWV9Pkg==, tarball: file:projects/model-all.tgz} + resolution: {integrity: sha512-NxbYGWKtCvK3tUbHGFiHRNdc/AmAFb9WWma26yTTzkRBM0ZLgXFfNqNaloQad+dXa1NR0SqpCKswdhelZ1QYgg==, tarball: file:projects/model-all.tgz} id: file:projects/model-all.tgz name: '@rush-temp/model-all' version: 0.0.0 @@ -13047,6 +13077,27 @@ packages: - typescript dev: false + file:projects/model-preference.tgz_typescript@4.5.4: + resolution: {integrity: sha512-VA7EbuQe0imqaTsLhaXC83m/EB+vTdMRhCDU6AsPsU+Ay0eXTuebZucwwFh92lQ0fDbu1KDJTFcyPxpWwscSXw==, tarball: file:projects/model-preference.tgz} + id: file:projects/model-preference.tgz + name: '@rush-temp/model-preference' + version: 0.0.0 + dependencies: + '@rushstack/heft': 0.41.8 + '@types/heft-jest': 1.0.2 + '@typescript-eslint/eslint-plugin': 5.7.0_c25e8c1f4f4f7aaed27aa6f9ce042237 + '@typescript-eslint/parser': 5.7.0_eslint@7.32.0+typescript@4.5.4 + eslint: 7.32.0 + eslint-config-standard-with-typescript: 21.0.1_ce2fa0c4dfa1c256100cababd749a13a + eslint-plugin-import: 2.25.3_eslint@7.32.0 + eslint-plugin-node: 11.1.0_eslint@7.32.0 + eslint-plugin-promise: 5.2.0_eslint@7.32.0 + prettier: 2.5.1 + transitivePeerDependencies: + - supports-color + - typescript + dev: false + file:projects/model-presentation.tgz_typescript@4.5.4: resolution: {integrity: sha512-nJF6vTxtGM/Q3nvEyhNk7LBiTrnCQpDLMco/zvSyLsRhjrA8yrAZwo3hdIK9K22Sic8Rl1vw6et/DH7CjGXjAA==, tarball: file:projects/model-presentation.tgz} id: file:projects/model-presentation.tgz @@ -13832,6 +13883,47 @@ packages: - supports-color dev: false + file:projects/preference-assets.tgz: + resolution: {integrity: sha512-ppUED31aeD7XmNevsK2XDz527e+gbmV43y68t7bM/DfypniAvcPaNBZ20CzQETmHsBrHSdUNTGFMv3HKMtmzNA==, tarball: file:projects/preference-assets.tgz} + name: '@rush-temp/preference-assets' + version: 0.0.0 + dependencies: + '@rushstack/heft': 0.41.8 + '@types/heft-jest': 1.0.2 + '@types/node': 16.11.14 + '@typescript-eslint/eslint-plugin': 5.7.0_c25e8c1f4f4f7aaed27aa6f9ce042237 + '@typescript-eslint/parser': 5.7.0_eslint@7.32.0+typescript@4.5.4 + eslint: 7.32.0 + eslint-config-standard-with-typescript: 21.0.1_ce2fa0c4dfa1c256100cababd749a13a + eslint-plugin-import: 2.25.3_eslint@7.32.0 + eslint-plugin-node: 11.1.0_eslint@7.32.0 + eslint-plugin-promise: 5.2.0_eslint@7.32.0 + prettier: 2.5.1 + typescript: 4.5.4 + transitivePeerDependencies: + - supports-color + dev: false + + file:projects/preference.tgz: + resolution: {integrity: sha512-WGzTspMfF/YBgM5AlF26hbbPD9Qbj4Vp7AL0DJeescfvY2MF4rFQ+wJK5ewuFWbDYGVu0kMJiszRP/7OVXqHFQ==, tarball: file:projects/preference.tgz} + name: '@rush-temp/preference' + version: 0.0.0 + dependencies: + '@rushstack/heft': 0.41.8 + '@types/heft-jest': 1.0.2 + '@typescript-eslint/eslint-plugin': 5.7.0_c25e8c1f4f4f7aaed27aa6f9ce042237 + '@typescript-eslint/parser': 5.7.0_eslint@7.32.0+typescript@4.5.4 + eslint: 7.32.0 + eslint-config-standard-with-typescript: 21.0.1_ce2fa0c4dfa1c256100cababd749a13a + eslint-plugin-import: 2.25.3_eslint@7.32.0 + eslint-plugin-node: 11.1.0_eslint@7.32.0 + eslint-plugin-promise: 5.2.0_eslint@7.32.0 + prettier: 2.5.1 + typescript: 4.5.4 + transitivePeerDependencies: + - supports-color + dev: false + file:projects/presentation.tgz_096c09b0b673a57c275d9767a12070b1: resolution: {integrity: sha512-wc5U6pu+2JFrN7NknB2nCs3qbDEh5AgNhRfoTFBPHOxsBplCEtNtUvHtt91pY2lGlgQVZvNsLKEhaDKycVF19w==, tarball: file:projects/presentation.tgz} id: file:projects/presentation.tgz @@ -13869,7 +13961,7 @@ packages: dev: false file:projects/prod-tracker.tgz_a07ec81d4d975778878ca12202ea119e: - resolution: {integrity: sha512-dl5eoirwPVY6dcl4eNTnk0BUpGnXVxppdq2EQd1S81HGk0zT+2X76KJaZ3sUf7byks3kNFl4rFmWOn+JLrpXNA==, tarball: file:projects/prod-tracker.tgz} + resolution: {integrity: sha512-DegdZVFP21HUwqbhSqazxR2nu9OqSlJ8QxOn1S3IOD9AGi7H5OOSvw6E/RItOlGAts0bi261Z81Dgi9EJvTtvA==, tarball: file:projects/prod-tracker.tgz} id: file:projects/prod-tracker.tgz name: '@rush-temp/prod-tracker' version: 0.0.0 @@ -13915,7 +14007,7 @@ packages: dev: false file:projects/prod.tgz_a07ec81d4d975778878ca12202ea119e: - resolution: {integrity: sha512-VvhwhIktsLPXGK+aXdKgBVK9CbQkWIVyYy+MmgjEDIpMcoNWbZzL/fdu+Dw/F1OT80Vq01Y7aDeN8TaoHJkWZg==, tarball: file:projects/prod.tgz} + resolution: {integrity: sha512-WaWihWZzBZ9WDVbJtB+fFusKyv8pR4hbBAZGkpcZ0yiOQL4tL5R0ZFsh0TxDy1TD68XKFqF2FnM3ngNQQr8bKA==, tarball: file:projects/prod.tgz} id: file:projects/prod.tgz name: '@rush-temp/prod' version: 0.0.0 @@ -14444,6 +14536,27 @@ packages: - supports-color dev: false + file:projects/server-preference.tgz: + resolution: {integrity: sha512-YYnxaZnUVlY/TL829SCP22F6jd03uQROfLRH7oqTH9tnBPaDvr66Zpb4Qyyw/lK2zsNFuZRbJHSGPF45fsa+Zg==, tarball: file:projects/server-preference.tgz} + name: '@rush-temp/server-preference' + version: 0.0.0 + dependencies: + '@rushstack/heft': 0.41.8 + '@types/heft-jest': 1.0.2 + '@types/node': 16.11.14 + '@typescript-eslint/eslint-plugin': 5.7.0_c25e8c1f4f4f7aaed27aa6f9ce042237 + '@typescript-eslint/parser': 5.7.0_eslint@7.32.0+typescript@4.5.4 + eslint: 7.32.0 + eslint-config-standard-with-typescript: 21.0.1_ce2fa0c4dfa1c256100cababd749a13a + eslint-plugin-import: 2.25.3_eslint@7.32.0 + eslint-plugin-node: 11.1.0_eslint@7.32.0 + eslint-plugin-promise: 5.2.0_eslint@7.32.0 + prettier: 2.5.1 + typescript: 4.5.4 + transitivePeerDependencies: + - supports-color + dev: false + file:projects/server-recruit-resources.tgz: resolution: {integrity: sha512-TM38Nr2D+for8rBa4J83JMrgVC6wAFoOhXjCaBZTLceLtec+43JObZjd27ZE+7N0fyB57ONxZOADN381res5wg==, tarball: file:projects/server-recruit-resources.tgz} name: '@rush-temp/server-recruit-resources' @@ -14728,7 +14841,7 @@ packages: dev: false file:projects/server.tgz: - resolution: {integrity: sha512-TEYNvDfpWbWWHcltHwEyC1B81v5c9UUyGmNx9C9sdItiWxLb8P/lQ+a+xca+5eJ+FQmRxm3VJywrvscmttTfPg==, tarball: file:projects/server.tgz} + resolution: {integrity: sha512-6vh78sMqcZsw0xfPyn3wQfOBG0e2jWhVLU8zsSvN1m9bFOuuf7strkWQWun+c5dkgtd6vvd57yh0ihz4BDMkZw==, tarball: file:projects/server.tgz} name: '@rush-temp/server' version: 0.0.0 dependencies: @@ -14934,7 +15047,7 @@ packages: dev: false file:projects/task-resources.tgz_6cae74ea501386c76c405ad0408e0339: - resolution: {integrity: sha512-YOuOktJH1qu9wET1Zf204iCCzaL8yT6HjNuQr/u3ib921oc0nz0kdrw9UVWsMGabA2xkzJxT1BjGQFPPGgFTvA==, tarball: file:projects/task-resources.tgz} + resolution: {integrity: sha512-RGVNKBNCTeRoBjCTl05vX1s7rHV02HUFdxSd3QjZIUYSUo+09fAnaVSeohOq0aXy+R4vvd8shkZuppJupufu7A==, tarball: file:projects/task-resources.tgz} id: file:projects/task-resources.tgz name: '@rush-temp/task-resources' version: 0.0.0 @@ -15312,7 +15425,7 @@ packages: dev: false file:projects/tracker-resources.tgz_096c09b0b673a57c275d9767a12070b1: - resolution: {integrity: sha512-am69PuPAqWdxUR/7fLKNvmEvfAxMPptRLgABjeQmvBFfS7/RTRFsjz2y+aiLUoMVUktcfHIIMWXxWx/otsmPVw==, tarball: file:projects/tracker-resources.tgz} + resolution: {integrity: sha512-2Ap2E6dyQwwxZqOqSHVvQzjCVVuisPK/llz6Qe9I24qb7QvZk4sZrccxpVYNhj4n1FrvNkk9tjccb13eegCAow==, tarball: file:projects/tracker-resources.tgz} id: file:projects/tracker-resources.tgz name: '@rush-temp/tracker-resources' version: 0.0.0 @@ -15502,7 +15615,7 @@ packages: dev: false file:projects/workbench-resources.tgz_096c09b0b673a57c275d9767a12070b1: - resolution: {integrity: sha512-PB/gIO0hbQwlQ3b3RlHWf0ZcsjET1b4nuzS8sTOs6xDdtVZbWH3xTbms89OiLW0znBpHTsKRV5BIacXKe9cc+g==, tarball: file:projects/workbench-resources.tgz} + resolution: {integrity: sha512-VMERIjagr6R/HiIj4wyaMCkQBe3K5Lck4JLs9qBw6U025TSH+6aJeJCpo7+lF/yEfANR+Y5HMbCSlohp9GMgaA==, tarball: file:projects/workbench-resources.tgz} id: file:projects/workbench-resources.tgz name: '@rush-temp/workbench-resources' version: 0.0.0 diff --git a/dev/prod/package.json b/dev/prod/package.json index 6523b3557a..7ada0fd898 100644 --- a/dev/prod/package.json +++ b/dev/prod/package.json @@ -105,6 +105,8 @@ "@anticrm/notification": "~0.6.0", "@anticrm/notification-assets": "~0.6.0", "@anticrm/notification-resources": "~0.6.0", + "@anticrm/preference": "~0.6.0", + "@anticrm/preference-assets": "~0.6.0", "@anticrm/core": "~0.6.16", "@anticrm/rekoni": "~0.6.0", "@anticrm/tags-assets": "~0.6.0", diff --git a/dev/prod/src/platform.ts b/dev/prod/src/platform.ts index 2fb922e85e..345d13564f 100644 --- a/dev/prod/src/platform.ts +++ b/dev/prod/src/platform.ts @@ -1,5 +1,5 @@ // -// Copyright © 2020 Anticrm Platform Contributors. +// Copyright © 2022 Hardcore Engineering Inc. // // Licensed under the Eclipse Public License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. You may @@ -34,6 +34,7 @@ import { imageCropperId } from '@anticrm/image-cropper' import { inventoryId } from '@anticrm/inventory' import { templatesId } from '@anticrm/templates' import { notificationId } from '@anticrm/notification' +import { preferenceId } from '@anticrm/preference' import { tagsId } from '@anticrm/tags' import { calendarId } from '@anticrm/calendar' import { trackerId } from '@anticrm/tracker' @@ -60,6 +61,7 @@ import '@anticrm/tags-assets' import '@anticrm/calendar-assets' import '@anticrm/tracker-assets' import '@anticrm/board-assets' +import '@anticrm/preference-assets' import presentation, { presentationId } from '@anticrm/presentation' import { coreId } from '@anticrm/core' import { textEditorId } from '@anticrm/text-editor' diff --git a/dev/server/src/server.ts b/dev/server/src/server.ts index 134089b390..0f53aa22ce 100644 --- a/dev/server/src/server.ts +++ b/dev/server/src/server.ts @@ -17,8 +17,7 @@ import type { Doc, Ref, TxResult } from '@anticrm/core' import { DOMAIN_TX, MeasureMetricsContext } from '@anticrm/core' import { createInMemoryAdapter, createInMemoryTxAdapter } from '@anticrm/dev-storage' -import type { DbConfiguration } from '@anticrm/server-core' -import { createServerStorage, FullTextAdapter, IndexedDoc } from '@anticrm/server-core' +import { createPipeline, DbConfiguration, FullTextAdapter, IndexedDoc } from '@anticrm/server-core' import { start as startJsonRpc } from '@anticrm/server-ws' class NullFullTextAdapter implements FullTextAdapter { @@ -71,6 +70,6 @@ export async function start (port: number, host?: string): Promise { }, workspace: '' } - return createServerStorage(conf) + return createPipeline(conf, []) }, port, host) } diff --git a/models/all/package.json b/models/all/package.json index 1c36b3943c..8ce54f34c1 100644 --- a/models/all/package.json +++ b/models/all/package.json @@ -68,6 +68,8 @@ "@anticrm/model-server-gmail": "~0.6.0", "@anticrm/model-server-telegram": "~0.6.0", "@anticrm/model-tracker": "~0.6.0", - "@anticrm/model-board": "~0.6.0" + "@anticrm/model-board": "~0.6.0", + "@anticrm/model-preference": "~0.6.0" + } } diff --git a/models/all/src/index.ts b/models/all/src/index.ts index bf9784e8f6..94192d38f0 100644 --- a/models/all/src/index.ts +++ b/models/all/src/index.ts @@ -1,5 +1,5 @@ // -// Copyright © 2020 Anticrm Platform Contributors. +// Copyright © 2022 Hardcore Engineering Inc. // // Licensed under the Eclipse Public License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. You may @@ -54,6 +54,7 @@ import { createModel as serverGmailModel } from '@anticrm/model-server-gmail' import { createModel as serverTelegramModel } from '@anticrm/model-server-telegram' import { createModel as trackerModel } from '@anticrm/model-tracker' import { createModel as boardModel } from '@anticrm/model-board' +import { createModel as preferenceModel } from '@anticrm/model-preference' export const version: Data = jsonVersion as Data @@ -78,6 +79,7 @@ const builders: [(b: Builder) => void, string][] = [ [templatesModel, 'templates'], [textEditorModel, 'text-editor'], [notificationModel, 'notification'], + [preferenceModel, 'preference'], [serverCoreModel, 'server-core'], [serverAttachmentModel, 'server-attachment'], diff --git a/models/preference/.eslintrc.js b/models/preference/.eslintrc.js new file mode 100644 index 0000000000..c3c2c49417 --- /dev/null +++ b/models/preference/.eslintrc.js @@ -0,0 +1,7 @@ +module.exports = { + extends: ['./node_modules/@anticrm/model-rig/profiles/default/config/eslint.config.json'], + parserOptions: { + tsconfigRootDir: __dirname, + project: './tsconfig.json' + } +} diff --git a/models/preference/.npmignore b/models/preference/.npmignore new file mode 100644 index 0000000000..e3ec093c38 --- /dev/null +++ b/models/preference/.npmignore @@ -0,0 +1,4 @@ +* +!/lib/** +!CHANGELOG.md +/lib/**/__tests__/ diff --git a/models/preference/config/rig.json b/models/preference/config/rig.json new file mode 100644 index 0000000000..e9a9ee9add --- /dev/null +++ b/models/preference/config/rig.json @@ -0,0 +1,18 @@ +// The "rig.json" file directs tools to look for their config files in an external package. +// Documentation for this system: https://www.npmjs.com/package/@rushstack/rig-package +{ + "$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json", + + /** + * (Required) The name of the rig package to inherit from. + * It should be an NPM package name with the "-rig" suffix. + */ + "rigPackageName": "@anticrm/model-rig" + + /** + * (Optional) Selects a config profile from the rig package. The name must consist of + * lowercase alphanumeric words separated by hyphens, for example "sample-profile". + * If omitted, then the "default" profile will be used." + */ + // "rigProfile": "your-profile-name" +} diff --git a/models/preference/package.json b/models/preference/package.json new file mode 100644 index 0000000000..75d6d810f3 --- /dev/null +++ b/models/preference/package.json @@ -0,0 +1,35 @@ +{ + "name": "@anticrm/model-preference", + "version": "0.6.0", + "main": "lib/index.js", + "author": "Anticrm Platform Contributors", + "license": "EPL-2.0", + "scripts": { + "build": "heft build", + "build:watch": "tsc", + "lint:fix": "eslint --fix src", + "lint": "eslint src", + "format": "prettier --write src && eslint --fix src" + }, + "devDependencies": { + "@anticrm/model-rig": "~0.6.0", + "@typescript-eslint/eslint-plugin": "^5.4.0", + "eslint-plugin-import": "^2.25.3", + "eslint-plugin-promise": "^5.1.1", + "eslint-plugin-node": "^11.1.0", + "eslint": "^7.32.0", + "@types/heft-jest": "^1.0.2", + "@typescript-eslint/parser": "^5.4.0", + "eslint-config-standard-with-typescript": "^21.0.1", + "prettier": "^2.4.1", + "@rushstack/heft": "^0.41.1" + }, + "dependencies": { + "@anticrm/core": "~0.6.16", + "@anticrm/model": "~0.6.0", + "@anticrm/ui": "~0.6.0", + "@anticrm/platform": "~0.6.5", + "@anticrm/model-core": "~0.6.0", + "@anticrm/preference": "~0.6.0" + } +} diff --git a/models/preference/src/index.ts b/models/preference/src/index.ts new file mode 100644 index 0000000000..bf949eac72 --- /dev/null +++ b/models/preference/src/index.ts @@ -0,0 +1,37 @@ +// +// Copyright © 2022 Hardcore Engineering Inc. +// +// Licensed under the Eclipse Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. You may +// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import { Doc, Ref, Space } from '@anticrm/core' +import { Builder, Model, Prop, TypeRef } from '@anticrm/model' +import core, { TDoc } from '@anticrm/model-core' +import preference, { DOMAIN_PREFERENCE, Preference, SpacePreference } from '@anticrm/preference' + +@Model(preference.class.Preference, core.class.Doc, DOMAIN_PREFERENCE) +export class TPreference extends TDoc implements Preference { + @Prop(TypeRef(core.class.Doc), core.string.AttachedTo) + attachedTo!: Ref +} + +@Model(preference.class.SpacePreference, preference.class.Preference) +export class TSpacePreference extends TPreference implements SpacePreference { + @Prop(TypeRef(core.class.Space), core.string.Space) + declare attachedTo: Ref +} + +export function createModel (builder: Builder): void { + builder.createModel(TPreference, TSpacePreference) +} + +export { preference as default } diff --git a/models/preference/tsconfig.json b/models/preference/tsconfig.json new file mode 100644 index 0000000000..1d60db76b4 --- /dev/null +++ b/models/preference/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "./node_modules/@anticrm/model-rig/profiles/default/tsconfig.json", + + "compilerOptions": { + "rootDir": "./src", + "outDir": "./lib", + } +} \ No newline at end of file diff --git a/packages/core/src/server.ts b/packages/core/src/server.ts index da61032841..26b9fcd18c 100644 --- a/packages/core/src/server.ts +++ b/packages/core/src/server.ts @@ -1,6 +1,5 @@ // -// Copyright © 2020, 2021 Anticrm Platform Contributors. -// Copyright © 2021 Hardcore Engineering Inc. +// Copyright © 2022 Hardcore Engineering Inc. // // Licensed under the Eclipse Public License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. You may @@ -16,6 +15,8 @@ import { MeasureContext } from '.' import type { Doc, Class, Ref } from './classes' +import { Hierarchy } from './hierarchy' +import { ModelDb } from './memdb' import type { DocumentQuery, FindOptions, FindResult, TxResult } from './storage' import type { Tx } from './tx' @@ -23,6 +24,8 @@ import type { Tx } from './tx' * @public */ export interface ServerStorage { + hierarchy: Hierarchy + modelDb: ModelDb findAll: ( ctx: MeasureContext, _class: Ref>, diff --git a/plugins/preference-assets/.eslintrc.js b/plugins/preference-assets/.eslintrc.js new file mode 100644 index 0000000000..5da5872d4a --- /dev/null +++ b/plugins/preference-assets/.eslintrc.js @@ -0,0 +1,7 @@ +module.exports = { + extends: ['./node_modules/@anticrm/platform-rig/profiles/default/config/eslint.config.json'], + parserOptions: { + tsconfigRootDir: __dirname, + project: './tsconfig.json' + } +} diff --git a/plugins/preference-assets/assets/icons.svg b/plugins/preference-assets/assets/icons.svg new file mode 100644 index 0000000000..9fe905c419 --- /dev/null +++ b/plugins/preference-assets/assets/icons.svg @@ -0,0 +1,7 @@ + + + + + diff --git a/plugins/preference-assets/config/rig.json b/plugins/preference-assets/config/rig.json new file mode 100644 index 0000000000..af1257a896 --- /dev/null +++ b/plugins/preference-assets/config/rig.json @@ -0,0 +1,18 @@ +// The "rig.json" file directs tools to look for their config files in an external package. +// Documentation for this system: https://www.npmjs.com/package/@rushstack/rig-package +{ + "$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json", + + /** + * (Required) The name of the rig package to inherit from. + * It should be an NPM package name with the "-rig" suffix. + */ + "rigPackageName": "@anticrm/platform-rig" + + /** + * (Optional) Selects a config profile from the rig package. The name must consist of + * lowercase alphanumeric words separated by hyphens, for example "sample-profile". + * If omitted, then the "default" profile will be used." + */ + // "rigProfile": "your-profile-name" +} diff --git a/plugins/preference-assets/lang/en.json b/plugins/preference-assets/lang/en.json new file mode 100644 index 0000000000..3643c7f689 --- /dev/null +++ b/plugins/preference-assets/lang/en.json @@ -0,0 +1,8 @@ +{ + "string": { + "DeleteStarred": "Delete starred", + "Starred": "Starred", + "Star": "Star", + "Unstar": "Unstar" + } +} \ No newline at end of file diff --git a/plugins/preference-assets/lang/ru.json b/plugins/preference-assets/lang/ru.json new file mode 100644 index 0000000000..cd3257151a --- /dev/null +++ b/plugins/preference-assets/lang/ru.json @@ -0,0 +1,8 @@ +{ + "string": { + "DeleteStarred": "Удалить закладки", + "Starred": "Закладки", + "Star": "Добавить в закладки", + "Unstar": "Удалить из закладок" + } +} \ No newline at end of file diff --git a/plugins/preference-assets/package.json b/plugins/preference-assets/package.json new file mode 100644 index 0000000000..09d92191dc --- /dev/null +++ b/plugins/preference-assets/package.json @@ -0,0 +1,33 @@ +{ + "name": "@anticrm/preference-assets", + "version": "0.6.0", + "main": "lib/index.js", + "author": "Anticrm Platform Contributors", + "license": "EPL-2.0", + "scripts": { + "build": "heft build", + "lint": "eslint src", + "lint:fix": "eslint --fix src", + "format": "prettier --write src && eslint --fix src", + "build:watch": "tsc" + }, + "devDependencies": { + "@anticrm/platform-rig": "~0.6.0", + "@typescript-eslint/eslint-plugin": "^5.4.0", + "@typescript-eslint/parser": "^5.4.0", + "eslint-config-standard-with-typescript": "^21.0.1", + "eslint-plugin-import": "^2.25.3", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-promise": "^5.1.1", + "eslint": "^7.32.0", + "prettier": "^2.4.1", + "@types/heft-jest": "^1.0.2", + "@rushstack/heft": "^0.41.1", + "typescript": "^4.3.5", + "@types/node": "~16.11.12" + }, + "dependencies": { + "@anticrm/platform": "~0.6.5", + "@anticrm/preference": "~0.6.0" + } +} diff --git a/plugins/preference-assets/src/index.ts b/plugins/preference-assets/src/index.ts new file mode 100644 index 0000000000..d73c44a40f --- /dev/null +++ b/plugins/preference-assets/src/index.ts @@ -0,0 +1,24 @@ +// +// Copyright © 2022 Hardcore Engineering Inc. +// +// Licensed under the Eclipse Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. You may +// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import { addStringsLoader, loadMetadata } from '@anticrm/platform' +import preference, { preferenceId } from '@anticrm/preference' + +const icons = require('../assets/icons.svg') as string // eslint-disable-line +loadMetadata(preference.icon, { + Star: `${icons}#star` +}) + +addStringsLoader(preferenceId, async (lang: string) => await import(`../lang/${lang}.json`)) diff --git a/plugins/preference-assets/tsconfig.json b/plugins/preference-assets/tsconfig.json new file mode 100644 index 0000000000..cabe5aefad --- /dev/null +++ b/plugins/preference-assets/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "moduleResolution": "node", + "target": "esnext", + "module": "esnext", + "declaration": true, + "outDir": "./lib", + "strict": true, + "esModuleInterop": true, + "lib": [ + "esnext", + "dom" + ] + } +} \ No newline at end of file diff --git a/plugins/preference/.eslintrc.js b/plugins/preference/.eslintrc.js new file mode 100644 index 0000000000..5da5872d4a --- /dev/null +++ b/plugins/preference/.eslintrc.js @@ -0,0 +1,7 @@ +module.exports = { + extends: ['./node_modules/@anticrm/platform-rig/profiles/default/config/eslint.config.json'], + parserOptions: { + tsconfigRootDir: __dirname, + project: './tsconfig.json' + } +} diff --git a/plugins/preference/.npmignore b/plugins/preference/.npmignore new file mode 100644 index 0000000000..e3ec093c38 --- /dev/null +++ b/plugins/preference/.npmignore @@ -0,0 +1,4 @@ +* +!/lib/** +!CHANGELOG.md +/lib/**/__tests__/ diff --git a/plugins/preference/config/rig.json b/plugins/preference/config/rig.json new file mode 100644 index 0000000000..af1257a896 --- /dev/null +++ b/plugins/preference/config/rig.json @@ -0,0 +1,18 @@ +// The "rig.json" file directs tools to look for their config files in an external package. +// Documentation for this system: https://www.npmjs.com/package/@rushstack/rig-package +{ + "$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json", + + /** + * (Required) The name of the rig package to inherit from. + * It should be an NPM package name with the "-rig" suffix. + */ + "rigPackageName": "@anticrm/platform-rig" + + /** + * (Optional) Selects a config profile from the rig package. The name must consist of + * lowercase alphanumeric words separated by hyphens, for example "sample-profile". + * If omitted, then the "default" profile will be used." + */ + // "rigProfile": "your-profile-name" +} diff --git a/plugins/preference/package.json b/plugins/preference/package.json new file mode 100644 index 0000000000..35f30466e3 --- /dev/null +++ b/plugins/preference/package.json @@ -0,0 +1,33 @@ +{ + "name": "@anticrm/preference", + "version": "0.6.0", + "main": "lib/index.js", + "author": "Anticrm Platform Contributors", + "license": "EPL-2.0", + "scripts": { + "build": "heft build", + "build:watch": "tsc", + "lint:fix": "eslint --fix src", + "lint": "eslint src", + "format": "prettier --write src && eslint --fix src" + }, + "devDependencies": { + "@anticrm/platform-rig": "~0.6.0", + "@types/heft-jest": "^1.0.2", + "@typescript-eslint/eslint-plugin": "^5.4.0", + "eslint-plugin-import": "^2.25.3", + "eslint-plugin-promise": "^5.1.1", + "eslint-plugin-node": "^11.1.0", + "eslint": "^7.32.0", + "@typescript-eslint/parser": "^5.4.0", + "eslint-config-standard-with-typescript": "^21.0.1", + "prettier": "^2.4.1", + "@rushstack/heft": "^0.41.1", + "typescript": "^4.3.5" + }, + "dependencies": { + "@anticrm/platform": "~0.6.5", + "@anticrm/core": "~0.6.16", + "@anticrm/ui": "~0.6.0" + } +} diff --git a/plugins/preference/src/index.ts b/plugins/preference/src/index.ts new file mode 100644 index 0000000000..914ccb4d02 --- /dev/null +++ b/plugins/preference/src/index.ts @@ -0,0 +1,66 @@ +// +// Copyright © 2022 Hardcore Engineering Inc. +// +// Licensed under the Eclipse Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. You may +// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import type { Class, Doc, Domain, Ref, Space } from '@anticrm/core' +import type { Asset, IntlString, Plugin } from '@anticrm/platform' +import { plugin } from '@anticrm/platform' + +/** + * @public + */ +export interface Preference extends Doc { + attachedTo: Ref +} + +/** + * @public + */ +export interface SpacePreference extends Preference { + attachedTo: Ref +} + +/** + * @public + */ +export const preferenceId = 'preference' as Plugin + +/** + * @public + */ +export const DOMAIN_PREFERENCE = 'preference' as Domain + +/** + * @public + */ +const preference = plugin(preferenceId, { + class: { + Preference: '' as Ref>, + SpacePreference: '' as Ref> + }, + space: { + Preference: '' as Ref + }, + icon: { + Star: '' as Asset + }, + string: { + DeleteStarred: '' as IntlString, + Starred: '' as IntlString, + Star: '' as IntlString, + Unstar: '' as IntlString + } +}) + +export default preference diff --git a/plugins/preference/tsconfig.json b/plugins/preference/tsconfig.json new file mode 100644 index 0000000000..32045300ce --- /dev/null +++ b/plugins/preference/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "./node_modules/@anticrm/platform-rig/profiles/default/tsconfig.json", + + "compilerOptions": { + "rootDir": "./src", + "outDir": "./lib", + "lib": ["esnext", "dom"] + } +} \ No newline at end of file diff --git a/plugins/workbench-resources/package.json b/plugins/workbench-resources/package.json index b17892b00c..4cd1a47a38 100644 --- a/plugins/workbench-resources/package.json +++ b/plugins/workbench-resources/package.json @@ -43,6 +43,7 @@ "@anticrm/calendar": "~0.6.0", "@anticrm/notification": "~0.6.0", "@anticrm/notification-resources": "~0.6.0", + "@anticrm/preference": "~0.6.0", "@anticrm/contact": "~0.6.5", "@anticrm/view-resources": "~0.6.0" } diff --git a/plugins/workbench-resources/src/components/Navigator.svelte b/plugins/workbench-resources/src/components/Navigator.svelte index 56ea427425..8e29846697 100644 --- a/plugins/workbench-resources/src/components/Navigator.svelte +++ b/plugins/workbench-resources/src/components/Navigator.svelte @@ -1,40 +1,45 @@ + + [unStarAll]} indent={'ml-2'}> + {#each spaces as space (space._id)} + getActions(space)} + bold={isChanged(space, $lastViews)} + on:click={() => { + selectSpace(space._id) + }} + /> + {/each} + diff --git a/products/tracker/package.json b/products/tracker/package.json index 04839c5860..e9f9dd8925 100644 --- a/products/tracker/package.json +++ b/products/tracker/package.json @@ -98,6 +98,8 @@ "@anticrm/notification": "~0.6.0", "@anticrm/notification-assets": "~0.6.0", "@anticrm/notification-resources": "~0.6.0", + "@anticrm/preference": "~0.6.0", + "@anticrm/preference-assets": "~0.6.0", "@anticrm/calendar": "~0.6.0", "@anticrm/calendar-assets": "~0.6.0", "@anticrm/calendar-resources": "~0.6.0", diff --git a/products/tracker/src/platform.ts b/products/tracker/src/platform.ts index 2515322f1e..7c0f026355 100644 --- a/products/tracker/src/platform.ts +++ b/products/tracker/src/platform.ts @@ -33,6 +33,7 @@ import { templatesId } from '@anticrm/templates' import { notificationId } from '@anticrm/notification' import { calendarId } from '@anticrm/calendar' import { trackerId } from '@anticrm/tracker' +import { preferenceId } from '@anticrm/preference' import '@anticrm/login-assets' import '@anticrm/task-assets' @@ -47,6 +48,7 @@ import '@anticrm/gmail-assets' import '@anticrm/workbench-assets' import '@anticrm/templates-assets' import '@anticrm/notification-assets' +import '@anticrm/preference-assets' import '@anticrm/tracker-assets' import presentation, { presentationId } from '@anticrm/presentation' import { coreId } from '@anticrm/core' diff --git a/rush.json b/rush.json index 341b7ca5a9..9902f24c30 100644 --- a/rush.json +++ b/rush.json @@ -1262,6 +1262,31 @@ "packageName": "@anticrm/kanban", "projectFolder": "packages/kanban", "shouldPublish": true + }, + { + "packageName": "@anticrm/preference", + "projectFolder": "plugins/preference", + "shouldPublish": true + }, + { + "packageName": "@anticrm/preference-assets", + "projectFolder": "plugins/preference-assets", + "shouldPublish": true + }, + { + "packageName": "@anticrm/model-preference", + "projectFolder": "models/preference", + "shouldPublish": true + }, + { + "packageName": "@anticrm/server-preference", + "projectFolder": "server-plugins/preference", + "shouldPublish": true + }, + { + "packageName": "@anticrm/middleware", + "projectFolder": "server/middleware", + "shouldPublish": true } ] } diff --git a/server-plugins/preference/.eslintrc.js b/server-plugins/preference/.eslintrc.js new file mode 100644 index 0000000000..5da5872d4a --- /dev/null +++ b/server-plugins/preference/.eslintrc.js @@ -0,0 +1,7 @@ +module.exports = { + extends: ['./node_modules/@anticrm/platform-rig/profiles/default/config/eslint.config.json'], + parserOptions: { + tsconfigRootDir: __dirname, + project: './tsconfig.json' + } +} diff --git a/server-plugins/preference/.npmignore b/server-plugins/preference/.npmignore new file mode 100644 index 0000000000..e3ec093c38 --- /dev/null +++ b/server-plugins/preference/.npmignore @@ -0,0 +1,4 @@ +* +!/lib/** +!CHANGELOG.md +/lib/**/__tests__/ diff --git a/server-plugins/preference/config/rig.json b/server-plugins/preference/config/rig.json new file mode 100644 index 0000000000..af1257a896 --- /dev/null +++ b/server-plugins/preference/config/rig.json @@ -0,0 +1,18 @@ +// The "rig.json" file directs tools to look for their config files in an external package. +// Documentation for this system: https://www.npmjs.com/package/@rushstack/rig-package +{ + "$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json", + + /** + * (Required) The name of the rig package to inherit from. + * It should be an NPM package name with the "-rig" suffix. + */ + "rigPackageName": "@anticrm/platform-rig" + + /** + * (Optional) Selects a config profile from the rig package. The name must consist of + * lowercase alphanumeric words separated by hyphens, for example "sample-profile". + * If omitted, then the "default" profile will be used." + */ + // "rigProfile": "your-profile-name" +} diff --git a/server-plugins/preference/package.json b/server-plugins/preference/package.json new file mode 100644 index 0000000000..34e8407b6d --- /dev/null +++ b/server-plugins/preference/package.json @@ -0,0 +1,35 @@ +{ + "name": "@anticrm/server-preference", + "version": "0.6.0", + "main": "lib/index.js", + "author": "Anticrm Platform Contributors", + "license": "EPL-2.0", + "scripts": { + "build": "heft build", + "build:watch": "tsc", + "lint:fix": "eslint --fix src", + "lint": "eslint src", + "format": "prettier --write src && eslint --fix src" + }, + "devDependencies": { + "@anticrm/platform-rig": "~0.6.0", + "@types/heft-jest": "^1.0.2", + "@types/node": "~16.11.12", + "@typescript-eslint/eslint-plugin": "^5.4.0", + "eslint-plugin-import": "^2.25.3", + "eslint-plugin-promise": "^5.1.1", + "eslint-plugin-node": "^11.1.0", + "eslint": "^7.32.0", + "@typescript-eslint/parser": "^5.4.0", + "eslint-config-standard-with-typescript": "^21.0.1", + "prettier": "^2.4.1", + "@rushstack/heft": "^0.41.1", + "typescript": "^4.3.5" + }, + "dependencies": { + "@anticrm/core": "~0.6.16", + "@anticrm/preference": "~0.6.0", + "@anticrm/platform": "~0.6.5", + "@anticrm/server-core": "~0.6.1" + } +} diff --git a/server-plugins/preference/src/index.ts b/server-plugins/preference/src/index.ts new file mode 100644 index 0000000000..3a4f7f3bcb --- /dev/null +++ b/server-plugins/preference/src/index.ts @@ -0,0 +1,29 @@ +// +// Copyright © 2022 Hardcore Engineering Inc. +// +// Licensed under the Eclipse Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. You may +// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import type { Plugin } from '@anticrm/platform' +import { plugin } from '@anticrm/platform' + +export { DOMAIN_PREFERENCE } from '@anticrm/preference' + +/** + * @public + */ +export const serverPreferenceId = 'server-preference' as Plugin + +/** + * @public + */ +export default plugin(serverPreferenceId, { }) diff --git a/server-plugins/preference/tsconfig.json b/server-plugins/preference/tsconfig.json new file mode 100644 index 0000000000..fffbf4c341 --- /dev/null +++ b/server-plugins/preference/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "./node_modules/@anticrm/platform-rig/profiles/default/tsconfig.json", + + "compilerOptions": { + "rootDir": "./src", + "outDir": "./lib", + "esModuleInterop": true + } +} \ No newline at end of file diff --git a/server/core/src/fulltext.ts b/server/core/src/fulltext.ts index 7b13bec4e9..58bf116366 100644 --- a/server/core/src/fulltext.ts +++ b/server/core/src/fulltext.ts @@ -31,6 +31,7 @@ import core, { ObjQueryType, PropertyType, Ref, + toFindResult, Tx, TxBulkWrite, TxCollectionCUD, @@ -40,8 +41,7 @@ import core, { TxPutBag, TxRemoveDoc, TxResult, - TxUpdateDoc, - toFindResult + TxUpdateDoc } from '@anticrm/core' import type { FullTextAdapter, IndexedDoc, WithFind } from './types' diff --git a/server/core/src/index.ts b/server/core/src/index.ts index b926b4fb82..6a42e27d8e 100644 --- a/server/core/src/index.ts +++ b/server/core/src/index.ts @@ -17,4 +17,5 @@ export * from './types' export * from './fulltext' export * from './storage' +export * from './pipeline' export { default } from './plugin' diff --git a/server/core/src/pipeline.ts b/server/core/src/pipeline.ts new file mode 100644 index 0000000000..2df640166a --- /dev/null +++ b/server/core/src/pipeline.ts @@ -0,0 +1,62 @@ +// +// Copyright © 2022 Hardcore Engineering Inc. +// +// Licensed under the Eclipse Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. You may +// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import { Class, Doc, DocumentQuery, FindOptions, FindResult, Ref, ServerStorage, Tx, TxResult } from '@anticrm/core' +import { Pipeline, Middleware, MiddlewareCreator, SessionContext } from './types' +import { createServerStorage, DbConfiguration } from './storage' + +/** + * @public + */ +export async function createPipeline (conf: DbConfiguration, constructors: MiddlewareCreator[]): Promise { + const storage = await createServerStorage(conf) + return new TPipeline(storage, constructors) +} + +class TPipeline implements Pipeline { + private readonly head: Middleware | undefined + constructor (private readonly storage: ServerStorage, constructors: MiddlewareCreator[]) { + this.head = this.buildChain(constructors) + } + + private buildChain (constructors: MiddlewareCreator[]): Middleware | undefined { + let current: Middleware | undefined + for (let index = constructors.length - 1; index >= 0; index--) { + const element = constructors[index] + current = element(this.storage, current) + } + return current + } + + async findAll ( + ctx: SessionContext, + _class: Ref>, + query: DocumentQuery, + options?: FindOptions + ): Promise> { + const [session, resClass, resQuery, resOptions] = this.head === undefined ? [ctx, _class, query, options] : await this.head.findAll(ctx, _class, query, options) + return await this.storage.findAll(session, resClass, resQuery, resOptions) + } + + async tx (ctx: SessionContext, tx: Tx): Promise<[TxResult, Tx[], string | undefined]> { + const [session, resTx] = this.head === undefined ? [ctx, tx] : await this.head.tx(ctx, tx) + const res = await this.storage.tx(session, resTx) + return [...res, undefined] + } + + async close (): Promise { + await this.storage.close() + } +} diff --git a/server/core/src/storage.ts b/server/core/src/storage.ts index e27d8d9f62..e486faa4a2 100644 --- a/server/core/src/storage.ts +++ b/server/core/src/storage.ts @@ -29,9 +29,7 @@ import core, { Hierarchy, MeasureContext, Mixin, ModelDb, Ref, - ServerStorage, - Storage, - Tx, + ServerStorage, Tx, TxBulkWrite, TxCollectionCUD, TxCreateDoc, @@ -53,12 +51,14 @@ import type { FullTextAdapter, FullTextAdapterFactory, ObjectDDParticipant } fro /** * @public */ -export interface DbAdapter extends Storage { +export interface DbAdapter { /** * Method called after hierarchy is ready to use. */ init: (model: Tx[]) => Promise close: () => Promise + findAll: (_class: Ref>, query: DocumentQuery, options?: FindOptions) => Promise> + tx: (tx: Tx) => Promise } /** @@ -98,19 +98,21 @@ export interface DbConfiguration { class TServerStorage implements ServerStorage { private readonly fulltext: FullTextIndex + hierarchy: Hierarchy constructor ( private readonly domains: Record, private readonly defaultAdapter: string, private readonly adapters: Map, - private readonly hierarchy: Hierarchy, + hierarchy: Hierarchy, private readonly triggers: Triggers, private readonly fulltextAdapter: FullTextAdapter, private readonly storageAdapter: MinioClient | undefined, - private readonly modelDb: ModelDb, + readonly modelDb: ModelDb, private readonly workspace: string, options?: ServerStorageOptions ) { + this.hierarchy = hierarchy this.fulltext = new FullTextIndex(hierarchy, fulltextAdapter, this, options?.skipUpdateAttached ?? false) } @@ -134,7 +136,9 @@ class TServerStorage implements ServerStorage { if (this.hierarchy.isDerived(tx._class, core.class.TxCUD)) { const txCUD = tx as TxCUD const domain = this.hierarchy.getDomain(txCUD.objectClass) - return await this.getAdapter(domain).tx(txCUD) + const adapter = this.getAdapter(domain) + const res = await adapter.tx(txCUD) + return res } else { if (this.hierarchy.isDerived(tx._class, core.class.TxBulkWrite)) { const bulkWrite = tx as TxBulkWrite @@ -144,7 +148,7 @@ class TServerStorage implements ServerStorage { } else { throw new Error('not implemented (routeTx)') } - return {} + return [{}, false] } } @@ -357,9 +361,8 @@ class TServerStorage implements ServerStorage { const triggerFx = new Effects() let derived: Tx[] = [] - let result: TxResult = {} // store object - result = await ctx.with('route-tx', { _class, objClass }, (ctx) => this.routeTx(ctx, tx)) + const result = await ctx.with('route-tx', { _class, objClass }, (ctx) => this.routeTx(ctx, tx)) // invoke triggers and store derived objects derived = [ ...(await ctx.with('process-collection', { _class }, () => this.processCollection(ctx, tx))), diff --git a/server/core/src/types.ts b/server/core/src/types.ts index 12b8368bfc..5934f87be6 100644 --- a/server/core/src/types.ts +++ b/server/core/src/types.ts @@ -1,6 +1,5 @@ // -// Copyright © 2020, 2021 Anticrm Platform Contributors. -// Copyright © 2021, 2022 Hardcore Engineering Inc. +// Copyright © 2022 Hardcore Engineering Inc. // // Licensed under the Eclipse Public License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. You may @@ -14,10 +13,54 @@ // limitations under the License. // -import type { Client as MinioClient } from 'minio' -import type { Tx, Ref, Doc, Class, Storage, Space, Timestamp, Account, FindResult, DocumentQuery, FindOptions, TxResult, MeasureContext, ModelDb, Obj } from '@anticrm/core' -import { TxFactory, Hierarchy } from '@anticrm/core' +import type { Account, Class, Doc, DocumentQuery, FindOptions, FindResult, MeasureContext, ModelDb, Obj, Ref, ServerStorage, Space, Storage, Timestamp, Tx, TxResult } from '@anticrm/core' +import { Hierarchy, TxFactory } from '@anticrm/core' import type { Resource } from '@anticrm/platform' +import type { Client as MinioClient } from 'minio' + +/** + * @public + */ +export interface SessionContext extends MeasureContext { + userEmail: string +} + +/** + * @public + */ +export interface Middleware { + tx: (ctx: SessionContext, tx: Tx) => Promise + findAll: (ctx: SessionContext, _class: Ref>, query: DocumentQuery, options?: FindOptions) => Promise> +} + +/** + * @public + */ +export type MiddlewareCreator = (storage: ServerStorage, next?: Middleware) => Middleware + +/** + * @public + */ +export type TxMiddlewareResult = [SessionContext, Tx, string | undefined] + +/** + * @public + */ +export type FindAllMiddlewareResult = [SessionContext, Ref>, DocumentQuery, FindOptions | undefined] + +/** + * @public + */ +export interface Pipeline { + findAll: ( + ctx: SessionContext, + _class: Ref>, + query: DocumentQuery, + options?: FindOptions + ) => Promise> + tx: (ctx: SessionContext, tx: Tx) => Promise<[TxResult, Tx[], string | undefined]> + close: () => Promise +} /** * @public diff --git a/server/middleware/.eslintrc.js b/server/middleware/.eslintrc.js new file mode 100644 index 0000000000..5da5872d4a --- /dev/null +++ b/server/middleware/.eslintrc.js @@ -0,0 +1,7 @@ +module.exports = { + extends: ['./node_modules/@anticrm/platform-rig/profiles/default/config/eslint.config.json'], + parserOptions: { + tsconfigRootDir: __dirname, + project: './tsconfig.json' + } +} diff --git a/server/middleware/.npmignore b/server/middleware/.npmignore new file mode 100644 index 0000000000..e3ec093c38 --- /dev/null +++ b/server/middleware/.npmignore @@ -0,0 +1,4 @@ +* +!/lib/** +!CHANGELOG.md +/lib/**/__tests__/ diff --git a/server/middleware/config/rig.json b/server/middleware/config/rig.json new file mode 100644 index 0000000000..af1257a896 --- /dev/null +++ b/server/middleware/config/rig.json @@ -0,0 +1,18 @@ +// The "rig.json" file directs tools to look for their config files in an external package. +// Documentation for this system: https://www.npmjs.com/package/@rushstack/rig-package +{ + "$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json", + + /** + * (Required) The name of the rig package to inherit from. + * It should be an NPM package name with the "-rig" suffix. + */ + "rigPackageName": "@anticrm/platform-rig" + + /** + * (Optional) Selects a config profile from the rig package. The name must consist of + * lowercase alphanumeric words separated by hyphens, for example "sample-profile". + * If omitted, then the "default" profile will be used." + */ + // "rigProfile": "your-profile-name" +} diff --git a/server/middleware/package.json b/server/middleware/package.json new file mode 100644 index 0000000000..6ac119c2fc --- /dev/null +++ b/server/middleware/package.json @@ -0,0 +1,34 @@ +{ + "name": "@anticrm/middleware", + "version": "0.6.0", + "main": "lib/index.js", + "author": "Anticrm Platform Contributors", + "license": "EPL-2.0", + "scripts": { + "build": "heft build", + "build:watch": "tsc", + "lint:fix": "eslint --fix src", + "lint": "eslint src", + "format": "prettier --write src && eslint --fix src" + }, + "devDependencies": { + "@anticrm/platform-rig": "~0.6.0", + "@types/heft-jest": "^1.0.2", + "@typescript-eslint/eslint-plugin": "^5.4.0", + "eslint-plugin-import": "^2.25.3", + "eslint-plugin-promise": "^5.1.1", + "eslint-plugin-node": "^11.1.0", + "eslint": "^7.32.0", + "@typescript-eslint/parser": "^5.4.0", + "eslint-config-standard-with-typescript": "^21.0.1", + "prettier": "^2.4.1", + "@rushstack/heft": "^0.41.1", + "typescript": "^4.3.5" + }, + "dependencies": { + "@anticrm/core": "~0.6.16", + "@anticrm/platform": "~0.6.5", + "@anticrm/server-core": "~0.6.1", + "@anticrm/server-preference": "~0.6.0" + } +} diff --git a/server/middleware/src/index.ts b/server/middleware/src/index.ts new file mode 100644 index 0000000000..9e3a93bd26 --- /dev/null +++ b/server/middleware/src/index.ts @@ -0,0 +1,16 @@ +// +// Copyright © 2022 Hardcore Engineering Inc. +// +// Licensed under the Eclipse Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. You may +// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// + +export * from './private' diff --git a/server/middleware/src/private.ts b/server/middleware/src/private.ts new file mode 100644 index 0000000000..c3b7b4dba8 --- /dev/null +++ b/server/middleware/src/private.ts @@ -0,0 +1,88 @@ +// +// Copyright © 2022 Hardcore Engineering Inc. +// +// Licensed under the Eclipse Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. You may +// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import core, { Tx, Doc, Ref, Class, DocumentQuery, FindOptions, ServerStorage, Account, TxCUD } from '@anticrm/core' +import platform, { PlatformError, Severity, Status } from '@anticrm/platform' +import { Middleware, SessionContext, TxMiddlewareResult, FindAllMiddlewareResult } from '@anticrm/server-core' +import { DOMAIN_PREFERENCE } from '@anticrm/server-preference' + +/** + * @public + */ +export class PrivateMiddleware implements Middleware { + private readonly targetDomains = [DOMAIN_PREFERENCE] + + private constructor (private readonly storage: ServerStorage, private readonly next?: Middleware) { + } + + static create (storage: ServerStorage, next?: Middleware): PrivateMiddleware { + return new PrivateMiddleware(storage, next) + } + + async tx (ctx: SessionContext, tx: Tx): Promise { + let target: string | undefined + if (this.storage.hierarchy.isDerived(tx._class, core.class.TxCUD)) { + const txCUD = tx as TxCUD + const domain = this.storage.hierarchy.getDomain(txCUD.objectClass) + if (this.targetDomains.includes(domain)) { + const account = await this.getUser(ctx) + if (account !== tx.modifiedBy) { + throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {})) + } + target = ctx.userEmail + } + } + const res = await this.provideTx(ctx, tx) + return [res[0], res[1], res[2] ?? target] + } + + private async provideTx (ctx: SessionContext, tx: Tx): Promise { + if (this.next !== undefined) { + return await this.next.tx(ctx, tx) + } + return [ctx, tx, undefined] + } + + async findAll (ctx: SessionContext, _class: Ref>, query: DocumentQuery, options?: FindOptions): Promise> { + let newQuery = query + const domain = this.storage.hierarchy.getDomain(_class) + if (this.targetDomains.includes(domain)) { + const account = await this.getUser(ctx) + newQuery = { + ...query, + modifiedBy: account + } + } + return await this.provideFindAll(ctx, _class, newQuery, options) + } + + private async provideFindAll (ctx: SessionContext, _class: Ref>, query: DocumentQuery, options?: FindOptions): Promise> { + if (this.next !== undefined) { + return await this.next.findAll(ctx, _class, query, options) + } + return [ctx, _class, query, options] + } + + private async getUser (ctx: SessionContext): Promise> { + if (ctx.userEmail === undefined) { + throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {})) + } + const account = (await this.storage.modelDb.findAll(core.class.Account, { email: ctx.userEmail }))[0] + if (account === undefined) { + throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {})) + } + return account._id + } +} diff --git a/server/middleware/tsconfig.json b/server/middleware/tsconfig.json new file mode 100644 index 0000000000..aeb0517b13 --- /dev/null +++ b/server/middleware/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "./node_modules/@anticrm/platform-rig/profiles/default/tsconfig.json", + + "compilerOptions": { + "rootDir": "./src", + "outDir": "./lib" + } +} \ No newline at end of file diff --git a/server/mongo/src/storage.ts b/server/mongo/src/storage.ts index 247e9a927b..84ab8a5b30 100644 --- a/server/mongo/src/storage.ts +++ b/server/mongo/src/storage.ts @@ -29,17 +29,14 @@ import core, { ModelDb, Ref, ReverseLookups, - SortingOrder, - Tx, + SortingOrder, toFindResult, Tx, TxCreateDoc, TxMixin, TxProcessor, TxPutBag, TxRemoveDoc, TxResult, - TxUpdateDoc, - toFindResult, - WithLookup + TxUpdateDoc, WithLookup } from '@anticrm/core' import type { DbAdapter, TxAdapter } from '@anticrm/server-core' import { Collection, Db, Document, Filter, MongoClient, Sort } from 'mongodb' @@ -576,10 +573,6 @@ class MongoAdapter extends MongoAdapterBase { } } } - - override async tx (tx: Tx): Promise { - return await super.tx(tx) - } } class MongoTxAdapter extends MongoAdapterBase implements TxAdapter { @@ -604,7 +597,10 @@ class MongoTxAdapter extends MongoAdapterBase implements TxAdapter { throw new Error('Method not implemented.') } - override async tx (tx: Tx): Promise { + override async tx (tx: Tx, user: string): Promise + override async tx (tx: Tx): Promise + + override async tx (tx: Tx, user?: string): Promise { await this.txCollection().insertOne(translateDoc(tx)) return {} } diff --git a/server/server/package.json b/server/server/package.json index af3ea350e6..d82c622388 100644 --- a/server/server/package.json +++ b/server/server/package.json @@ -69,8 +69,10 @@ "@anticrm/server-calendar-resources": "~0.6.0", "@anticrm/server-gmail": "~0.6.0", "@anticrm/server-gmail-resources": "~0.6.0", + "@anticrm/server-preference": "~0.6.0", "@anticrm/server-telegram": "~0.6.0", "@anticrm/server-telegram-resources": "~0.6.0", - "@anticrm/server-token": "~0.6.0" + "@anticrm/server-token": "~0.6.0", + "@anticrm/middleware": "~0.6.0" } } diff --git a/server/server/src/server.ts b/server/server/src/server.ts index 378e6f136b..40e52638ac 100644 --- a/server/server/src/server.ts +++ b/server/server/src/server.ts @@ -1,6 +1,5 @@ // -// Copyright © 2020, 2021 Anticrm Platform Contributors. -// Copyright © 2021 Hardcore Engineering Inc. +// Copyright © 2022 Hardcore Engineering Inc. // // Licensed under the Eclipse Public License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. You may @@ -14,7 +13,6 @@ // limitations under the License. // -import { Client as MinioClient } from 'minio' import { Class, Doc, @@ -25,32 +23,29 @@ import { FindResult, Hierarchy, ModelDb, - Ref, - Tx, - TxResult, - toFindResult + Ref, toFindResult, Tx, + TxResult } from '@anticrm/core' import { createElasticAdapter } from '@anticrm/elastic' +import { PrivateMiddleware } from '@anticrm/middleware' import { createMongoAdapter, createMongoTxAdapter } from '@anticrm/mongo' -import type { DbAdapter, DbConfiguration } from '@anticrm/server-core' -import { createServerStorage } from '@anticrm/server-core' -import { start as startJsonRpc } from '@anticrm/server-ws' - import { addLocation } from '@anticrm/platform' import { serverAttachmentId } from '@anticrm/server-attachment' -import { serverContactId } from '@anticrm/server-contact' -import { serverNotificationId } from '@anticrm/server-notification' -import { serverSettingId } from '@anticrm/server-setting' +import { serverCalendarId } from '@anticrm/server-calendar' import { serverChunterId } from '@anticrm/server-chunter' +import { serverContactId } from '@anticrm/server-contact' +import { createPipeline, DbAdapter, DbConfiguration, MiddlewareCreator } from '@anticrm/server-core' +import { serverGmailId } from '@anticrm/server-gmail' import { serverInventoryId } from '@anticrm/server-inventory' import { serverLeadId } from '@anticrm/server-lead' +import { serverNotificationId } from '@anticrm/server-notification' import { serverRecruitId } from '@anticrm/server-recruit' -import { serverTaskId } from '@anticrm/server-task' +import { serverSettingId } from '@anticrm/server-setting' import { serverTagsId } from '@anticrm/server-tags' -import { serverCalendarId } from '@anticrm/server-calendar' -import { serverGmailId } from '@anticrm/server-gmail' +import { serverTaskId } from '@anticrm/server-task' import { serverTelegramId } from '@anticrm/server-telegram' - +import { start as startJsonRpc } from '@anticrm/server-ws' +import { Client as MinioClient } from 'minio' import { metricsContext } from './metrics' class NullDbAdapter implements DbAdapter { @@ -107,6 +102,10 @@ export function start ( addLocation(serverGmailId, () => import('@anticrm/server-gmail-resources')) addLocation(serverTelegramId, () => import('@anticrm/server-telegram-resources')) + const middlewares: MiddlewareCreator[] = [ + PrivateMiddleware.create + ] + return startJsonRpc( metricsContext, (workspace: string) => { @@ -142,7 +141,7 @@ export function start ( }), workspace } - return createServerStorage(conf) + return createPipeline(conf, middlewares) }, port, host diff --git a/server/ws/src/__tests__/server.test.ts b/server/ws/src/__tests__/server.test.ts index 4cc0e23a0f..db2ec23d6a 100644 --- a/server/ws/src/__tests__/server.test.ts +++ b/server/ws/src/__tests__/server.test.ts @@ -27,10 +27,10 @@ import type { FindOptions, FindResult, Tx, - TxResult, - MeasureContext + TxResult } from '@anticrm/core' import { MeasureMetricsContext, toFindResult } from '@anticrm/core' +import { SessionContext } from '@anticrm/server-core' describe('server', () => { disableLogging() @@ -39,12 +39,12 @@ describe('server', () => { new MeasureMetricsContext('test', {}), async () => ({ findAll: async ( - ctx: MeasureContext, + ctx: SessionContext, _class: Ref>, query: DocumentQuery, options?: FindOptions ): Promise> => toFindResult([]), - tx: async (ctx: MeasureContext, tx: Tx): Promise<[TxResult, Tx[]]> => [{}, []], + tx: async (ctx: SessionContext, tx: Tx): Promise<[TxResult, Tx[], string | undefined]> => [{}, [], undefined], close: async () => {} }), 3333 diff --git a/server/ws/src/server.ts b/server/ws/src/server.ts index 413f15f227..faea629af3 100644 --- a/server/ws/src/server.ts +++ b/server/ws/src/server.ts @@ -1,6 +1,5 @@ // -// Copyright © 2020, 2021 Anticrm Platform Contributors. -// Copyright © 2021 Hardcore Engineering Inc. +// Copyright © 2022 Hardcore Engineering Inc. // // Licensed under the Eclipse Public License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. You may @@ -14,8 +13,9 @@ // limitations under the License. // -import { Class, Doc, DocumentQuery, FindOptions, FindResult, MeasureContext, Ref, ServerStorage, Tx, TxResult } from '@anticrm/core' +import { Class, Doc, DocumentQuery, FindOptions, FindResult, MeasureContext, Ref, Tx, TxResult } from '@anticrm/core' import { readRequest, Response, serialize, unknownError } from '@anticrm/platform' +import type { Pipeline, SessionContext } from '@anticrm/server-core' import { decodeToken, Token } from '@anticrm/server-token' import { createServer, IncomingMessage } from 'http' import WebSocket, { Server } from 'ws' @@ -28,38 +28,46 @@ class Session { constructor ( private readonly manager: SessionManager, private readonly token: Token, - private readonly storage: ServerStorage + private readonly pipeline: Pipeline ) {} + getUser (): string { + return this.token.email + } + async ping (): Promise { console.log('ping'); return 'pong!' } async findAll (ctx: MeasureContext, _class: Ref>, query: DocumentQuery, options?: FindOptions): Promise> { - return await this.storage.findAll(ctx, _class, query, options) + const context = ctx as SessionContext + context.userEmail = this.token.email + return await this.pipeline.findAll(context, _class, query, options) } async tx (ctx: MeasureContext, tx: Tx): Promise { - const [result, derived] = await this.storage.tx(ctx, tx) + const context = ctx as SessionContext + context.userEmail = this.token.email + const [result, derived, target] = await this.pipeline.tx(context, tx) - this.manager.broadcast(this, this.token, { result: tx }) + this.manager.broadcast(this, this.token, { result: tx }, target) for (const dtx of derived) { - this.manager.broadcast(null, this.token, { result: dtx }) + this.manager.broadcast(null, this.token, { result: dtx }, target) } return result } } interface Workspace { - storage: ServerStorage + pipeline: Pipeline sessions: [Session, WebSocket][] } class SessionManager { private readonly workspaces = new Map() - async addSession (ws: WebSocket, token: Token, storageFactory: (ws: string) => Promise): Promise { + async addSession (ws: WebSocket, token: Token, pipelineFactory: (ws: string) => Promise): Promise { const workspace = this.workspaces.get(token.workspace) if (workspace === undefined) { - return await this.createWorkspace(storageFactory, token, ws) + return await this.createWorkspace(pipelineFactory, token, ws) } else { if (token.extra?.model === 'reload') { console.log('reloading workspace', JSON.stringify(token)) @@ -70,20 +78,20 @@ class SessionManager { this.close(s[1], token.workspace, 0, 'upgrade') } } - return await this.createWorkspace(storageFactory, token, ws) + return await this.createWorkspace(pipelineFactory, token, ws) } - const session = new Session(this, token, workspace.storage) + const session = new Session(this, token, workspace.pipeline) workspace.sessions.push([session, ws]) return session } } - private async createWorkspace (storageFactory: (ws: string) => Promise, token: Token, ws: WebSocket): Promise { - const storage = await storageFactory(token.workspace) - const session = new Session(this, token, storage) + private async createWorkspace (pipelineFactory: (ws: string) => Promise, token: Token, ws: WebSocket): Promise { + const pipeline = await pipelineFactory(token.workspace) + const session = new Session(this, token, pipeline) const workspace: Workspace = { - storage, + pipeline, sessions: [[session, ws]] } this.workspaces.set(token.workspace, workspace) @@ -101,11 +109,11 @@ class SessionManager { if (workspace.sessions.length === 0) { if (LOGGING_ENABLED) console.log('no sessions for workspace', workspaceId) this.workspaces.delete(workspaceId) - workspace.storage.close().catch(err => console.error(err)) + workspace.pipeline.close().catch(err => console.error(err)) } } - broadcast (from: Session | null, token: Token, resp: Response): void { + broadcast (from: Session | null, token: Token, resp: Response, target?: string): void { const workspace = this.workspaces.get(token.workspace) if (workspace === undefined) { console.error(new Error('internal: cannot find sessions')) @@ -114,7 +122,13 @@ class SessionManager { if (LOGGING_ENABLED) console.log(`server broadcasting to ${workspace.sessions.length} clients...`) const msg = serialize(resp) for (const session of workspace.sessions) { - if (session[0] !== from) { session[1].send(msg) } + if (session[0] !== from) { + if (target === undefined) { + session[1].send(msg) + } else if (session[0].getUser() === target) { + session[1].send(msg) + } + } } } } @@ -142,7 +156,7 @@ async function handleRequest (ctx: MeasureContext, service: S * @param port - * @param host - */ -export function start (ctx: MeasureContext, storageFactory: (workspace: string) => Promise, port: number, host?: string): () => void { +export function start (ctx: MeasureContext, pipelineFactory: (workspace: string) => Promise, port: number, host?: string): () => void { console.log(`starting server on port ${port} ...`) const sessions = new SessionManager() @@ -153,7 +167,7 @@ export function start (ctx: MeasureContext, storageFactory: (workspace: string) const buffer: string[] = [] ws.on('message', (msg: string) => { buffer.push(msg) }) - const session = await sessions.addSession(ws, token, storageFactory) + const session = await sessions.addSession(ws, token, pipelineFactory) // eslint-disable-next-line @typescript-eslint/no-misused-promises ws.on('message', async (msg: string) => await handleRequest(ctx, session, ws, msg)) ws.on('close', (code: number, reason: string) => sessions.close(ws, token.workspace, code, reason)) From e83ffd3804130f24ef2ac1a49ac5282fec681035 Mon Sep 17 00:00:00 2001 From: Artyom Grigorovich Date: Thu, 14 Apr 2022 13:07:07 +0700 Subject: [PATCH 3/8] Tracker: Issue List - DueDate presenter (#1393) --- .../src/components/UserBox.svelte | 4 +- packages/ui/src/components/ActionIcon.svelte | 4 +- packages/ui/src/components/Dropdown.svelte | 4 +- .../ui/src/components/DropdownLabels.svelte | 4 +- packages/ui/src/components/Tooltip.svelte | 4 +- .../ui/src/components/TooltipInstance.svelte | 4 +- .../components/calendar/DatePresenter.svelte | 104 +++++++++---- .../components/calendar/internal/DateUtils.ts | 8 + packages/ui/src/index.ts | 5 +- packages/ui/src/tooltips.ts | 4 +- packages/ui/src/types.ts | 4 +- .../components/OrganizationSelector.svelte | 4 +- plugins/tracker-assets/lang/en.json | 9 +- .../src/components/CreateIssue.svelte | 4 +- .../src/components/PrioritySelector.svelte | 14 +- .../src/components/StatusSelector.svelte | 9 +- .../issues/CategoryPresenter.svelte | 7 +- .../src/components/issues/DueDatePopup.svelte | 85 ++++++++++ .../components/issues/DueDatePresenter.svelte | 89 +++++++++++ .../src/components/issues/IssuesList.svelte | 147 ++++++++++-------- .../issues/PriorityPresenter.svelte | 15 +- .../components/issues/StatusPresenter.svelte | 5 +- plugins/tracker-resources/src/index.ts | 2 + plugins/tracker-resources/src/plugin.ts | 6 + 24 files changed, 416 insertions(+), 129 deletions(-) create mode 100644 plugins/tracker-resources/src/components/issues/DueDatePopup.svelte create mode 100644 plugins/tracker-resources/src/components/issues/DueDatePresenter.svelte diff --git a/packages/presentation/src/components/UserBox.svelte b/packages/presentation/src/components/UserBox.svelte index fa079d085d..2a7c31d231 100644 --- a/packages/presentation/src/components/UserBox.svelte +++ b/packages/presentation/src/components/UserBox.svelte @@ -18,7 +18,7 @@ import contact, { Contact, formatName } from '@anticrm/contact' import type { Class, Ref } from '@anticrm/core' import type { IntlString } from '@anticrm/platform' - import type { TooltipAligment } from '@anticrm/ui' + import type { TooltipAlignment } from '@anticrm/ui' import { Button, Label, showPopup, Tooltip } from '@anticrm/ui' import { createEventDispatcher } from 'svelte' import presentation from '..' @@ -39,7 +39,7 @@ export let size: 'small' | 'medium' | 'large' | 'x-large' = 'small' export let justify: 'left' | 'center' = 'center' export let width: string | undefined = undefined - export let labelDirection: TooltipAligment | undefined = undefined + export let labelDirection: TooltipAlignment | undefined = undefined const dispatch = createEventDispatcher() diff --git a/packages/ui/src/components/ActionIcon.svelte b/packages/ui/src/components/ActionIcon.svelte index caa25bebbf..26afab2509 100644 --- a/packages/ui/src/components/ActionIcon.svelte +++ b/packages/ui/src/components/ActionIcon.svelte @@ -15,14 +15,14 @@ + +{#if formattedDate} +
+
+ +
+
+
+
+
+
+
+
+{/if} + + diff --git a/plugins/tracker-resources/src/components/issues/DueDatePresenter.svelte b/plugins/tracker-resources/src/components/issues/DueDatePresenter.svelte new file mode 100644 index 0000000000..ab4ff560fe --- /dev/null +++ b/plugins/tracker-resources/src/components/issues/DueDatePresenter.svelte @@ -0,0 +1,89 @@ + + + +{#if shouldRenderPresenter} + + + +{/if} diff --git a/plugins/tracker-resources/src/components/issues/IssuesList.svelte b/plugins/tracker-resources/src/components/issues/IssuesList.svelte index a990c54a45..cd337bf09f 100644 --- a/plugins/tracker-resources/src/components/issues/IssuesList.svelte +++ b/plugins/tracker-resources/src/components/issues/IssuesList.svelte @@ -16,14 +16,16 @@ import { Class, Doc, DocumentQuery, FindOptions, Ref, getObjectValue } from '@anticrm/core' import { SortingOrder } from '@anticrm/core' import { createQuery, getClient } from '@anticrm/presentation' - import { CheckBox, Loading, showPopup, Spinner, IconMoreV } from '@anticrm/ui' + import { CheckBox, Loading, showPopup, Spinner, IconMoreV, Tooltip } from '@anticrm/ui' import { BuildModelKey } from '@anticrm/view' import { createEventDispatcher } from 'svelte' import { buildModel, LoadingProps, Menu } from '@anticrm/view-resources' + import tracker from '../../plugin' export let _class: Ref> export let baseMenuClass: Ref> | undefined = undefined - export let config: (BuildModelKey | string)[] + export let leftItemsConfig: (BuildModelKey | string)[] + export let rightItemsConfig: (BuildModelKey | string)[] | undefined = undefined export let options: FindOptions | undefined = undefined export let query: DocumentQuery @@ -92,13 +94,20 @@ return props.length } + + const buildItemModels = async () => { + const leftModels = await buildModel({ client, _class, keys: leftItemsConfig, options }) + const rightModels = rightItemsConfig && (await buildModel({ client, _class, keys: rightItemsConfig, options })) + + return { leftModels, rightModels } + } -{#await buildModel({ client, _class, keys: config, options })} +{#await buildItemModels()} {#if !isLoading} {/if} -{:then attributeModels} +{:then itemModels}
{#if docObjects} {#each docObjects as docObject, rowIndex (docObject._id)} @@ -107,65 +116,80 @@ class:mListGridChecked={selectedIssueIds.has(docObject._id)} class:mListGridFixed={rowIndex === selectedRowIndex} > - {#each attributeModels as attributeModel, attributeModelIndex} - {#if attributeModelIndex === 0} -
-
- { - handleIssueSelected(docObject._id, event) - }} - /> +
+ {#each itemModels.leftModels as attributeModel, attributeModelIndex} + {#if attributeModelIndex === 0} +
+ +
+ { + handleIssueSelected(docObject._id, event) + }} + /> +
+
+
+ +
-
+ {:else if attributeModelIndex === 1} +
+ +
showMenu(event, docObject, rowIndex)} + > + +
+
+ {:else} +
-
- {:else if attributeModelIndex === 1} -
- -
showMenu(event, docObject, rowIndex)} - > - + {/if} + {/each} +
+ {#if itemModels.rightModels} +
+ {#each itemModels.rightModels as attributeModel} +
+
-
- {:else} -
- -
- {/if} - {/each} + {/each} +
+ {/if}
{/each} {:else if loadingProps !== undefined} {#each Array(getLoadingElementsLength(loadingProps, options)) as _, rowIndex}
- {#each attributeModels as _, attributeModelIndex} - {#if attributeModelIndex === 0} -
- -
- -
+
+
+ +
+
- {/if} - {/each} +
+
{/each} {/if} @@ -182,8 +206,9 @@ } .listGrid { - display: grid; - grid-template-columns: 4rem 5rem 2rem auto 4rem 2rem; + display: flex; + align-items: center; + justify-content: space-between; height: 3.25rem; color: var(--theme-caption-color); border-bottom: 1px solid var(--theme-button-border-hovered); @@ -203,7 +228,7 @@ } &.mListGridIsLoading { - grid-template-columns: auto; + justify-content: flex-start; } &:hover { @@ -225,24 +250,20 @@ } } - .checkBox { + .modelsContainer { display: flex; align-items: center; - justify-content: center; - padding: 0.03rem; - border-radius: 0.25rem; - background-color: rgba(247, 248, 248, 0.5); - opacity: 0; - - &:hover { - opacity: 1; - } } .gridElement { display: flex; align-items: center; justify-content: start; + margin-left: 0.5rem; + + &:first-child { + margin-left: 0; + } } .priorityPresenter { @@ -252,7 +273,7 @@ .issuePresenter { display: flex; align-items: center; - padding-right: 1rem; + margin-left: 0.5rem; .eIssuePresenterContextMenu { visibility: hidden; diff --git a/plugins/tracker-resources/src/components/issues/PriorityPresenter.svelte b/plugins/tracker-resources/src/components/issues/PriorityPresenter.svelte index 243f69be92..47a5011d0d 100644 --- a/plugins/tracker-resources/src/components/issues/PriorityPresenter.svelte +++ b/plugins/tracker-resources/src/components/issues/PriorityPresenter.svelte @@ -16,6 +16,7 @@ import { Ref } from '@anticrm/core' import { Issue, IssuePriority, Team } from '@anticrm/tracker' import { getClient } from '@anticrm/presentation' + import { Tooltip } from '@anticrm/ui' import tracker from '../../plugin' import PrioritySelector from '../PrioritySelector.svelte' @@ -40,10 +41,12 @@ {#if value} - + + + {/if} diff --git a/plugins/tracker-resources/src/components/issues/StatusPresenter.svelte b/plugins/tracker-resources/src/components/issues/StatusPresenter.svelte index 1a2614ab66..ea309319d8 100644 --- a/plugins/tracker-resources/src/components/issues/StatusPresenter.svelte +++ b/plugins/tracker-resources/src/components/issues/StatusPresenter.svelte @@ -16,6 +16,7 @@ import { Ref } from '@anticrm/core' import { Issue, IssueStatus, Team } from '@anticrm/tracker' import { getClient } from '@anticrm/presentation' + import { Tooltip } from '@anticrm/ui' import tracker from '../../plugin' import StatusSelector from '../StatusSelector.svelte' @@ -40,5 +41,7 @@ {#if value} - + + + {/if} diff --git a/plugins/tracker-resources/src/index.ts b/plugins/tracker-resources/src/index.ts index 5fa780183f..7541089a4c 100644 --- a/plugins/tracker-resources/src/index.ts +++ b/plugins/tracker-resources/src/index.ts @@ -29,6 +29,7 @@ import IssuePresenter from './components/issues/IssuePresenter.svelte' import TitlePresenter from './components/issues/TitlePresenter.svelte' import PriorityPresenter from './components/issues/PriorityPresenter.svelte' import StatusPresenter from './components/issues/StatusPresenter.svelte' +import DueDatePresenter from './components/issues/DueDatePresenter.svelte' import AssigneePresenter from './components/issues/AssigneePresenter.svelte' import ModificationDatePresenter from './components/issues/ModificationDatePresenter.svelte' @@ -52,6 +53,7 @@ export default async (): Promise => ({ PriorityPresenter, StatusPresenter, AssigneePresenter, + DueDatePresenter, EditIssue, NewIssueHeader } diff --git a/plugins/tracker-resources/src/plugin.ts b/plugins/tracker-resources/src/plugin.ts index 9e6e2dc4c0..d40bc60e81 100644 --- a/plugins/tracker-resources/src/plugin.ts +++ b/plugins/tracker-resources/src/plugin.ts @@ -40,6 +40,7 @@ export default mergeIds(trackerId, tracker, { AddIssue: '' as IntlString, NewIssue: '' as IntlString, Team: '' as IntlString, + SelectIssue: '' as IntlString, SelectTeam: '' as IntlString, SaveIssue: '' as IntlString, Todo: '' as IntlString, @@ -76,6 +77,10 @@ export default mergeIds(trackerId, tracker, { DocumentIcon: '' as IntlString, DocumentColor: '' as IntlString, Rank: '' as IntlString, + DueDatePopupTitle: '' as IntlString, + DueDatePopupOverdueTitle: '' as IntlString, + DueDatePopupDescription: '' as IntlString, + DueDatePopupOverdueDescription: '' as IntlString, IssueTitlePlaceholder: '' as IntlString, IssueDescriptionPlaceholder: '' as IntlString, @@ -98,6 +103,7 @@ export default mergeIds(trackerId, tracker, { PriorityPresenter: '' as AnyComponent, StatusPresenter: '' as AnyComponent, AssigneePresenter: '' as AnyComponent, + DueDatePresenter: '' as AnyComponent, EditIssue: '' as AnyComponent, CreateTeam: '' as AnyComponent, NewIssueHeader: '' as AnyComponent From da45faf7d18cb015e22e4938b0e9e86503a861be Mon Sep 17 00:00:00 2001 From: Denis Bunakalya Date: Thu, 14 Apr 2022 10:36:11 +0300 Subject: [PATCH 4/8] 1304 Edit message (#1395) Signed-off-by: Denis Bunakalya --- plugins/chunter-assets/lang/en.json | 1 + plugins/chunter-assets/lang/ru.json | 1 + .../src/components/Message.svelte | 40 +++++++++++++++++-- .../src/components/ThreadComment.svelte | 40 +++++++++++++++++-- plugins/chunter-resources/src/plugin.ts | 3 +- 5 files changed, 76 insertions(+), 9 deletions(-) diff --git a/plugins/chunter-assets/lang/en.json b/plugins/chunter-assets/lang/en.json index 725ebbbc8b..c3a14eebc4 100644 --- a/plugins/chunter-assets/lang/en.json +++ b/plugins/chunter-assets/lang/en.json @@ -31,6 +31,7 @@ "MarkUnread": "Mark unread", "GetNewReplies": "Get notified about new replies", "TurnOffReplies": "Turn off notifications for replies", + "EditMessage": "Edit message", "DeleteMessage": "Delete message" } } \ No newline at end of file diff --git a/plugins/chunter-assets/lang/ru.json b/plugins/chunter-assets/lang/ru.json index eccff39861..398effdd84 100644 --- a/plugins/chunter-assets/lang/ru.json +++ b/plugins/chunter-assets/lang/ru.json @@ -30,6 +30,7 @@ "MarkUnread": "Отметить как непрочитанное", "GetNewReplies": "Получать уведомления о новых ответах", "TurnOffReplies": "Выключить уведомления об ответах", + "EditMessage": "Редактировать сообщение", "DeleteMessage": "Удалить сообщение" } } \ No newline at end of file diff --git a/plugins/chunter-resources/src/components/Message.svelte b/plugins/chunter-resources/src/components/Message.svelte index 09cf44db81..48c8bf5e72 100644 --- a/plugins/chunter-resources/src/components/Message.svelte +++ b/plugins/chunter-resources/src/components/Message.svelte @@ -14,7 +14,7 @@ --> + { openThread(e.detail) }} + {pinnedIds} />
diff --git a/plugins/chunter-resources/src/components/Message.svelte b/plugins/chunter-resources/src/components/Message.svelte index 48c8bf5e72..28e56e26f2 100644 --- a/plugins/chunter-resources/src/components/Message.svelte +++ b/plugins/chunter-resources/src/components/Message.svelte @@ -25,6 +25,7 @@ import { Action } from '@anticrm/view' import { getActions } from '@anticrm/view-resources' import { createEventDispatcher } from 'svelte' + import { UnpinMessage } from '../index'; import chunter from '../plugin' import { getTime } from '../utils' // import Share from './icons/Share.svelte' @@ -37,6 +38,7 @@ export let message: WithLookup export let employees: Map, Employee> export let thread: boolean = false + export let isPinned: boolean = false $: employee = getEmployee(message) $: attachments = (message.$lookup?.attachments ?? []) as Attachment[] @@ -59,6 +61,16 @@ action: chunter.actionImpl.SubscribeMessage } as Action) + $: pinActions = isPinned + ? ({ + label: chunter.string.UnpinMessage, + action: chunter.actionImpl.UnpinMessage + } as Action) + : ({ + label: chunter.string.PinMessage, + action: chunter.actionImpl.PinMessage + } as Action) + $: isEditing = false; const editAction = { @@ -68,12 +80,20 @@ const deleteAction = { label: chunter.string.DeleteMessage, - action: async () => await client.remove(message) + action: async () => { + (await client.findAll(chunter.class.ThreadMessage, {attachedTo: message._id})).forEach(c => { + UnpinMessage(c) + }) + UnpinMessage(message) + await client.remove(message) + } } const showMenu = async (ev: Event): Promise => { const actions = await getActions(client, message, chunter.class.Message) actions.push(subscribeAction) + actions.push(pinActions) + showPopup( Menu, { diff --git a/plugins/chunter-resources/src/components/PinnedMessages.svelte b/plugins/chunter-resources/src/components/PinnedMessages.svelte new file mode 100644 index 0000000000..25e929faee --- /dev/null +++ b/plugins/chunter-resources/src/components/PinnedMessages.svelte @@ -0,0 +1,29 @@ + + +{#if pinnedIds.length > 0} +
+
showMessages(ev)}> +
+
+{/if} + + diff --git a/plugins/chunter-resources/src/components/PinnedMessagesPopup.svelte b/plugins/chunter-resources/src/components/PinnedMessagesPopup.svelte new file mode 100644 index 0000000000..02e1df6138 --- /dev/null +++ b/plugins/chunter-resources/src/components/PinnedMessagesPopup.svelte @@ -0,0 +1,116 @@ + + + + + diff --git a/plugins/chunter-resources/src/components/ThreadComment.svelte b/plugins/chunter-resources/src/components/ThreadComment.svelte index 45447ba018..c7483749c7 100644 --- a/plugins/chunter-resources/src/components/ThreadComment.svelte +++ b/plugins/chunter-resources/src/components/ThreadComment.svelte @@ -24,6 +24,7 @@ import { ActionIcon, IconMoreH, Menu, showPopup } from '@anticrm/ui' import { Action } from '@anticrm/view' import { getActions } from '@anticrm/view-resources' + import { UnpinMessage } from '../index'; import chunter from '../plugin' import { getTime } from '../utils' // import Share from './icons/Share.svelte' @@ -33,6 +34,7 @@ export let message: WithLookup export let employees: Map, Employee> + export let isPinned: boolean = false $: attachments = (message.$lookup?.attachments ?? []) as Attachment[] @@ -53,6 +55,16 @@ action: chunter.actionImpl.SubscribeComment } as Action) + $: pinActions = isPinned + ? ({ + label: chunter.string.UnpinMessage, + action: chunter.actionImpl.UnpinMessage + } as Action) + : ({ + label: chunter.string.PinMessage, + action: chunter.actionImpl.PinMessage + } as Action) + $: isEditing = false; const editAction = { @@ -62,12 +74,16 @@ const deleteAction = { label: chunter.string.DeleteMessage, - action: async () => await client.removeDoc(message._class, message.space, message._id) + action: async () => { + await client.removeDoc(message._class, message.space, message._id) + UnpinMessage(message) + } } const showMenu = async (ev: Event): Promise => { const actions = await getActions(client, message, chunter.class.ThreadMessage) actions.push(subscribeAction) + actions.push(pinActions) showPopup( Menu, { diff --git a/plugins/chunter-resources/src/components/ThreadView.svelte b/plugins/chunter-resources/src/components/ThreadView.svelte index cb0bf625f2..122c6e1f7f 100644 --- a/plugins/chunter-resources/src/components/ThreadView.svelte +++ b/plugins/chunter-resources/src/components/ThreadView.svelte @@ -15,7 +15,7 @@ {#if value} diff --git a/plugins/board-resources/src/utils/CardUtils.ts b/plugins/board-resources/src/utils/CardUtils.ts index 7d567f8357..136643f71a 100644 --- a/plugins/board-resources/src/utils/CardUtils.ts +++ b/plugins/board-resources/src/utils/CardUtils.ts @@ -36,12 +36,12 @@ export function hasDate (card: Card): boolean { export function addCurrentUser (card: Card, client: Client): void { const employee = (getCurrentAccount() as EmployeeAccount).employee - card.members = card.members ?? [] + const members = card.members ?? [] - if (card.members.includes(employee)) { + if (members.includes(employee)) { return } - card.members.push(employee) - updateCard(client, card, 'members', card.members) + members.push(employee) + updateCard(client, card, 'members', members) } From 942295b26b10af15e9fa8b70fa15dc8dc49d76c4 Mon Sep 17 00:00:00 2001 From: Alexander Platov Date: Thu, 14 Apr 2022 14:03:49 +0300 Subject: [PATCH 7/8] Fix SelectPopup (#1400) --- packages/ui/src/components/SelectPopup.svelte | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/ui/src/components/SelectPopup.svelte b/packages/ui/src/components/SelectPopup.svelte index 68aeb27808..1ab69256fe 100644 --- a/packages/ui/src/components/SelectPopup.svelte +++ b/packages/ui/src/components/SelectPopup.svelte @@ -16,12 +16,12 @@ import type { Asset, IntlString } from '@anticrm/platform' import { translate } from '@anticrm/platform' import { createEventDispatcher } from 'svelte' - import { Icon, Label, getPlatformColor } from '..' + import { Icon, Label } from '..' export let placeholder: IntlString | undefined = undefined export let placeholderParam: any | undefined = undefined export let searchable: boolean = false - export let value: Array<{id: number | string, color: number, label: IntlString}> + export let value: Array<{id: number | string, icon: Asset, label: IntlString}> let search: string = '' @@ -41,7 +41,7 @@
{#each value.filter(el => el.label.toLowerCase().includes(search.toLowerCase())) as item} {/each} From b204009eab9feee03750ed8e7d95d91ee09bb8e6 Mon Sep 17 00:00:00 2001 From: Alexander Platov Date: Thu, 14 Apr 2022 14:04:24 +0300 Subject: [PATCH 8/8] Update UserBoxList layout. Fix AccountPopup. (#1397) --- packages/presentation/lang/en.json | 3 +- packages/presentation/lang/ru.json | 3 +- .../presentation/src/components/Avatar.svelte | 9 +- .../src/components/CombineAvatars.svelte | 53 +++++++++++ .../src/components/SpaceSelect.svelte | 1 - .../src/components/SpacesPopup.svelte | 2 +- .../src/components/UserBox.svelte | 13 +-- .../src/components/UserBoxList.svelte | 94 ++++++++----------- .../src/components/UsersPopup.svelte | 43 +++++++-- .../src/components/icons/Avatar.svelte | 6 +- .../src/components/icons/Members.svelte | 29 ++++++ packages/presentation/src/index.ts | 1 + packages/presentation/src/plugin.ts | 3 +- packages/theme/styles/_colors.scss | 1 + packages/theme/styles/_layouts.scss | 4 +- packages/theme/styles/popups.scss | 8 +- packages/ui/src/components/Button.svelte | 9 +- packages/ui/src/components/CheckBox.svelte | 54 ++++------- packages/ui/src/components/Dropdown.svelte | 6 +- .../ui/src/components/DropdownLabels.svelte | 49 +--------- packages/ui/src/index.ts | 2 +- packages/ui/src/popups.ts | 4 +- packages/ui/src/types.ts | 3 +- .../src/components/CreateReminder.svelte | 2 +- .../src/components/EditEvent.svelte | 2 +- .../src/components/EditReminder.svelte | 2 +- .../components/OrganizationSelector.svelte | 6 +- .../src/components/review/CreateReview.svelte | 21 ++--- .../src/components/review/EditReview.svelte | 2 +- .../src/components/AccountPopup.svelte | 14 ++- 30 files changed, 245 insertions(+), 204 deletions(-) create mode 100644 packages/presentation/src/components/CombineAvatars.svelte create mode 100644 packages/presentation/src/components/icons/Members.svelte diff --git a/packages/presentation/lang/en.json b/packages/presentation/lang/en.json index 4454b56c7a..69d8aea00f 100644 --- a/packages/presentation/lang/en.json +++ b/packages/presentation/lang/en.json @@ -14,6 +14,7 @@ "Members": "Members", "Search": "Search...", "Unassigned": "Unassigned", - "CreateMore": "Create more" + "CreateMore": "Create more", + "NumberMembers": "{lenght, plural, =0 {no members} =1 {1 member} other {# members}}" } } diff --git a/packages/presentation/lang/ru.json b/packages/presentation/lang/ru.json index f9dc485814..396f6e8235 100644 --- a/packages/presentation/lang/ru.json +++ b/packages/presentation/lang/ru.json @@ -14,6 +14,7 @@ "Members": "Участники", "Search": "Поиск...", "Unassigned": "Не назначен", - "CreateMore": "Создать еще" + "CreateMore": "Создать еще", + "NumberMembers": "{lenght, plural, =0 {нет участников} =1 {1 участик} other {# участника}}" } } diff --git a/packages/presentation/src/components/Avatar.svelte b/packages/presentation/src/components/Avatar.svelte index 34440a6079..fc05967098 100644 --- a/packages/presentation/src/components/Avatar.svelte +++ b/packages/presentation/src/components/Avatar.svelte @@ -20,7 +20,7 @@ export let avatar: string | null | undefined = undefined export let direct: Blob | undefined = undefined - export let size: 'x-small' | 'small' | 'medium' | 'large' | 'x-large' + export let size: 'inline' | 'x-small' | 'small' | 'medium' | 'large' | 'x-large' let url: string | undefined $: if (direct !== undefined) { @@ -50,7 +50,7 @@ flex-shrink: 0; position: relative; overflow: hidden; - background-color: var(--popup-bg-hover); + background-color: var(--avatar-bg-color); border-radius: 50%; pointer-events: none; @@ -61,6 +61,10 @@ &.no-img { border-color: transparent; } } + .ava-inline { + width: .875rem; // 24 + height: .875rem; + } .ava-x-small { width: 1.5rem; // 24 height: 1.5rem; @@ -92,6 +96,7 @@ border-radius: 50%; } + .ava-inline .ava-mask, .ava-inline.no-img, .ava-x-small .ava-mask, .ava-x-small.no-img, .ava-small .ava-mask, .ava-small.no-img, .ava-medium .ava-mask, .ava-medium.no-img { border-style: none; } diff --git a/packages/presentation/src/components/CombineAvatars.svelte b/packages/presentation/src/components/CombineAvatars.svelte new file mode 100644 index 0000000000..12a7922a7a --- /dev/null +++ b/packages/presentation/src/components/CombineAvatars.svelte @@ -0,0 +1,53 @@ + + + + +
+ {#each persons as person} +
+ +
+ {/each} +
+ + \ No newline at end of file diff --git a/packages/presentation/src/components/SpaceSelect.svelte b/packages/presentation/src/components/SpaceSelect.svelte index 3c25ee67dc..3b85feeb8d 100644 --- a/packages/presentation/src/components/SpaceSelect.svelte +++ b/packages/presentation/src/components/SpaceSelect.svelte @@ -13,7 +13,6 @@ // limitations under the License. -->
- +
-
- -
- - + {/if} + + + diff --git a/packages/presentation/src/components/UsersPopup.svelte b/packages/presentation/src/components/UsersPopup.svelte index 31a2784f1a..fdff50ef59 100644 --- a/packages/presentation/src/components/UsersPopup.svelte +++ b/packages/presentation/src/components/UsersPopup.svelte @@ -15,7 +15,7 @@
- { }} on:change/> +
{#each objects as person} - diff --git a/packages/presentation/src/components/icons/Avatar.svelte b/packages/presentation/src/components/icons/Avatar.svelte index b5f9f7f4d1..f9afd85579 100644 --- a/packages/presentation/src/components/icons/Avatar.svelte +++ b/packages/presentation/src/components/icons/Avatar.svelte @@ -15,7 +15,7 @@ --> @@ -30,6 +30,10 @@ .svg-avatar { .op { opacity: .05; } } + .inline { + width: .75rem; + height: .75rem; + } .x-small { width: 1rem; height: 1rem; diff --git a/packages/presentation/src/components/icons/Members.svelte b/packages/presentation/src/components/icons/Members.svelte new file mode 100644 index 0000000000..72d886a6f6 --- /dev/null +++ b/packages/presentation/src/components/icons/Members.svelte @@ -0,0 +1,29 @@ + + + + + + + + + + + + diff --git a/packages/presentation/src/index.ts b/packages/presentation/src/index.ts index fc31f5d333..2412487e1f 100644 --- a/packages/presentation/src/index.ts +++ b/packages/presentation/src/index.ts @@ -21,6 +21,7 @@ export { default as AttributeBarEditor } from './components/AttributeBarEditor.s export { default as AttributeEditor } from './components/AttributeEditor.svelte' export { default as AttributesBar } from './components/AttributesBar.svelte' export { default as Avatar } from './components/Avatar.svelte' +export { default as CombineAvatars } from './components/CombineAvatars.svelte' export { default as Card } from './components/Card.svelte' export { default as EditableAvatar } from './components/EditableAvatar.svelte' export { default as Members } from './components/Members.svelte' diff --git a/packages/presentation/src/plugin.ts b/packages/presentation/src/plugin.ts index bff0f11e1a..903670cea1 100644 --- a/packages/presentation/src/plugin.ts +++ b/packages/presentation/src/plugin.ts @@ -43,7 +43,8 @@ export default plugin(presentationId, { Members: '' as IntlString, Search: '' as IntlString, Unassigned: '' as IntlString, - CreateMore: '' as IntlString + CreateMore: '' as IntlString, + NumberMembers: '' as IntlString }, metadata: { RequiredVersion: '' as Metadata diff --git a/packages/theme/styles/_colors.scss b/packages/theme/styles/_colors.scss index f63d1dce9f..12246469dc 100644 --- a/packages/theme/styles/_colors.scss +++ b/packages/theme/styles/_colors.scss @@ -79,6 +79,7 @@ --popup-shadow: rgb(0 0 0 / 20%) 0px 4px 24px; --card-shadow: rgb(0 0 0 / 50%) 0px 16px 70px; --card-overlay-color: rgba(28, 29, 31, .5); + --avatar-bg-color: #4f5358; --button-bg-color: #303236; --button-bg-hover: #37383b; diff --git a/packages/theme/styles/_layouts.scss b/packages/theme/styles/_layouts.scss index 4d96da5c59..f43ae247c3 100644 --- a/packages/theme/styles/_layouts.scss +++ b/packages/theme/styles/_layouts.scss @@ -185,7 +185,7 @@ p:last-child { margin-block-end: 0; } &.circle { padding: .25rem; - background-color: var(--popup-bg-hover); + background-color: var(--avatar-bg-color); border-radius: 50%; } } @@ -286,6 +286,7 @@ p:last-child { margin-block-end: 0; } .step-tb75 + .step-tb75 { margin-top: .75rem; } .ml-1 { margin-left: .25rem; } +.ml-1-5 { margin-left: .375rem; } .ml-2 { margin-left: .5rem; } .ml-3 { margin-left: .75rem; } .ml-4 { margin-left: 1rem; } @@ -378,6 +379,7 @@ p:last-child { margin-block-end: 0; } .h-full { height: 100%; } .h-2 { height: .5rem; } .h-9 { height: 2.25rem; } +.h-16 { height: 4rem; } .h-18 { height: 4.5rem; } .w-full { width: 100%; } .w-9 { width: 2.25rem; } diff --git a/packages/theme/styles/popups.scss b/packages/theme/styles/popups.scss index 12ab51b071..e71146e1a2 100644 --- a/packages/theme/styles/popups.scss +++ b/packages/theme/styles/popups.scss @@ -65,6 +65,7 @@ color: var(--caption-color); cursor: pointer; + &.high { height: 3rem; } .icon { margin-right: .75rem; width: 1rem; @@ -84,14 +85,13 @@ text-overflow: ellipsis; overflow: hidden; } - .check { + .check, .check-right { display: flex; - justify-content: flex-end; align-items: center; - margin-left: 1rem; - width: 1rem; + margin-right: .75rem; height: 2rem; } + .check-right { margin: 0 0 0 2rem; } &:hover { background-color: var(--popup-bg-hover); } } } diff --git a/packages/ui/src/components/Button.svelte b/packages/ui/src/components/Button.svelte index 23ccdda2a0..dd4b56c861 100644 --- a/packages/ui/src/components/Button.svelte +++ b/packages/ui/src/components/Button.svelte @@ -14,7 +14,7 @@ --> -
-
-
+
+
{#await getItems() then items} -
{ editProfile(items) }}> + {#if items} {#each filterItems(items) as item } - {/each} {/if} - - {/await}
-