Notification (#934)

Signed-off-by: Denis Bykhov <80476319+BykhovDenis@users.noreply.github.com>
This commit is contained in:
Denis Bykhov 2022-02-07 15:03:14 +06:00 committed by GitHub
parent bf2e0aba30
commit e026683db0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
64 changed files with 847 additions and 140 deletions

View File

@ -56,6 +56,7 @@ specifiers:
'@rush-temp/model-gmail': file:./projects/model-gmail.tgz '@rush-temp/model-gmail': file:./projects/model-gmail.tgz
'@rush-temp/model-inventory': file:./projects/model-inventory.tgz '@rush-temp/model-inventory': file:./projects/model-inventory.tgz
'@rush-temp/model-lead': file:./projects/model-lead.tgz '@rush-temp/model-lead': file:./projects/model-lead.tgz
'@rush-temp/model-notification': file:./projects/model-notification.tgz
'@rush-temp/model-presentation': file:./projects/model-presentation.tgz '@rush-temp/model-presentation': file:./projects/model-presentation.tgz
'@rush-temp/model-recruit': file:./projects/model-recruit.tgz '@rush-temp/model-recruit': file:./projects/model-recruit.tgz
'@rush-temp/model-rig': file:./projects/model-rig.tgz '@rush-temp/model-rig': file:./projects/model-rig.tgz
@ -70,6 +71,8 @@ specifiers:
'@rush-temp/model-view': file:./projects/model-view.tgz '@rush-temp/model-view': file:./projects/model-view.tgz
'@rush-temp/model-workbench': file:./projects/model-workbench.tgz '@rush-temp/model-workbench': file:./projects/model-workbench.tgz
'@rush-temp/mongo': file:./projects/mongo.tgz '@rush-temp/mongo': file:./projects/mongo.tgz
'@rush-temp/notification': file:./projects/notification.tgz
'@rush-temp/notification-resources': file:./projects/notification-resources.tgz
'@rush-temp/panel': file:./projects/panel.tgz '@rush-temp/panel': file:./projects/panel.tgz
'@rush-temp/platform': file:./projects/platform.tgz '@rush-temp/platform': file:./projects/platform.tgz
'@rush-temp/platform-rig': file:./projects/platform-rig.tgz '@rush-temp/platform-rig': file:./projects/platform-rig.tgz
@ -251,6 +254,7 @@ dependencies:
'@rush-temp/model-gmail': file:projects/model-gmail.tgz_typescript@4.5.4 '@rush-temp/model-gmail': file:projects/model-gmail.tgz_typescript@4.5.4
'@rush-temp/model-inventory': file:projects/model-inventory.tgz_typescript@4.5.4 '@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-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-presentation': file:projects/model-presentation.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-recruit': file:projects/model-recruit.tgz_typescript@4.5.4
'@rush-temp/model-rig': file:projects/model-rig.tgz_37f79b97d0d86442e45d380c86f520c5 '@rush-temp/model-rig': file:projects/model-rig.tgz_37f79b97d0d86442e45d380c86f520c5
@ -265,6 +269,8 @@ dependencies:
'@rush-temp/model-view': file:projects/model-view.tgz_typescript@4.5.4 '@rush-temp/model-view': file:projects/model-view.tgz_typescript@4.5.4
'@rush-temp/model-workbench': file:projects/model-workbench.tgz_typescript@4.5.4 '@rush-temp/model-workbench': file:projects/model-workbench.tgz_typescript@4.5.4
'@rush-temp/mongo': file:projects/mongo.tgz '@rush-temp/mongo': file:projects/mongo.tgz
'@rush-temp/notification': file:projects/notification.tgz
'@rush-temp/notification-resources': file:projects/notification-resources.tgz_ac194b5590200ebf8338e0f86ec190f4
'@rush-temp/panel': file:projects/panel.tgz_096c09b0b673a57c275d9767a12070b1 '@rush-temp/panel': file:projects/panel.tgz_096c09b0b673a57c275d9767a12070b1
'@rush-temp/platform': file:projects/platform.tgz '@rush-temp/platform': file:projects/platform.tgz
'@rush-temp/platform-rig': file:projects/platform-rig.tgz_37f79b97d0d86442e45d380c86f520c5 '@rush-temp/platform-rig': file:projects/platform-rig.tgz_37f79b97d0d86442e45d380c86f520c5
@ -12094,7 +12100,7 @@ packages:
dev: false dev: false
file:projects/gmail-resources.tgz_096c09b0b673a57c275d9767a12070b1: file:projects/gmail-resources.tgz_096c09b0b673a57c275d9767a12070b1:
resolution: {integrity: sha512-zZWhkD/HZH3/ljH7WhD2IG9NhQ6b1JPTgG2Rc35pGwScC2uzyI8xT3cq4K/jbsYLZ3BXj31BYOno7Mqo6pFcZg==, tarball: file:projects/gmail-resources.tgz} resolution: {integrity: sha512-C7jgVQmN2v6HBKZgVAzWPYRaWrRHhfWpIRIRUyBUmspQreX2ArmCFCjq+1CHYBh9lGcnBZGgaNN1zQ1aTzSFLA==, tarball: file:projects/gmail-resources.tgz}
id: file:projects/gmail-resources.tgz id: file:projects/gmail-resources.tgz
name: '@rush-temp/gmail-resources' name: '@rush-temp/gmail-resources'
version: 0.0.0 version: 0.0.0
@ -12425,7 +12431,7 @@ packages:
dev: false dev: false
file:projects/model-all.tgz_typescript@4.5.4: file:projects/model-all.tgz_typescript@4.5.4:
resolution: {integrity: sha512-KRaYm65Scg01RukljQmwVKLg143zgzLtjWBJQizBtdAU0X8XoHFnW6cOK1Pa3O15Et812fs3HhWWTUj32GJnoQ==, tarball: file:projects/model-all.tgz} resolution: {integrity: sha512-Q1uN1ojy8Xc1Q2hsrrBqQrwdhkJgfGu90yb/mtHmDDb+WCguVe1JlPL9jbLV0f39wY5qnCzByDApK2FHftgVlA==, tarball: file:projects/model-all.tgz}
id: file:projects/model-all.tgz id: file:projects/model-all.tgz
name: '@rush-temp/model-all' name: '@rush-temp/model-all'
version: 0.0.0 version: 0.0.0
@ -12617,6 +12623,27 @@ packages:
- typescript - typescript
dev: false dev: false
file:projects/model-notification.tgz_typescript@4.5.4:
resolution: {integrity: sha512-q/JNneErW66xgCE5Yc/7bPkeUXwiEUJw95gbYt5uDTeuuzK0TrP77epbfSaWqkH7kP9SjqKrBwWPdm+M64q98g==, tarball: file:projects/model-notification.tgz}
id: file:projects/model-notification.tgz
name: '@rush-temp/model-notification'
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: file:projects/model-presentation.tgz_typescript@4.5.4:
resolution: {integrity: sha512-cBkfqfxIgaViuDI4bj0NDuVg1NnzoSjL2mxvRRRn1Dnqkq+2K00cKyx67Pil9dHwH636h2HEYKoM4VNsT3Os1w==, tarball: file:projects/model-presentation.tgz} resolution: {integrity: sha512-cBkfqfxIgaViuDI4bj0NDuVg1NnzoSjL2mxvRRRn1Dnqkq+2K00cKyx67Pil9dHwH636h2HEYKoM4VNsT3Os1w==, tarball: file:projects/model-presentation.tgz}
id: file:projects/model-presentation.tgz id: file:projects/model-presentation.tgz
@ -12931,6 +12958,59 @@ packages:
- supports-color - supports-color
dev: false dev: false
file:projects/notification-resources.tgz_ac194b5590200ebf8338e0f86ec190f4:
resolution: {integrity: sha512-TgP/VzfwZ+LGqaToY+rKYjH683GBuSHetD9bM85Jg6mtCtU09uogF3mwqKCAU1gyKtiVYLA63VmAb8MkxwV56A==, tarball: file:projects/notification-resources.tgz}
id: file:projects/notification-resources.tgz
name: '@rush-temp/notification-resources'
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
svelte: 3.44.3
svelte-check: 2.3.0_4374c622c67ed7479ff0e44c29d09bce
typescript: 4.5.4
transitivePeerDependencies:
- '@babel/core'
- coffeescript
- less
- node-sass
- postcss
- postcss-load-config
- pug
- sass
- stylus
- sugarss
- supports-color
dev: false
file:projects/notification.tgz:
resolution: {integrity: sha512-VOGvKSMA5Utp3zuIYzvaHujYwS2ObDBtjrzKJzWDn8RtQo8zW8nOYjEOIDDIvXKGZlpl/5v62pDsEBiCQ/yNhw==, tarball: file:projects/notification.tgz}
name: '@rush-temp/notification'
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/panel.tgz_096c09b0b673a57c275d9767a12070b1: file:projects/panel.tgz_096c09b0b673a57c275d9767a12070b1:
resolution: {integrity: sha512-RxiNEW/PUGlF6/JrOvYWaUVfmC4kVnrd1tUQBpUM9tcRQebTwjPIUF1+V19xT63Rer6sRmz4SWo9v6/BtRAJlw==, tarball: file:projects/panel.tgz} resolution: {integrity: sha512-RxiNEW/PUGlF6/JrOvYWaUVfmC4kVnrd1tUQBpUM9tcRQebTwjPIUF1+V19xT63Rer6sRmz4SWo9v6/BtRAJlw==, tarball: file:projects/panel.tgz}
id: file:projects/panel.tgz id: file:projects/panel.tgz
@ -13047,7 +13127,7 @@ packages:
dev: false dev: false
file:projects/presentation.tgz_096c09b0b673a57c275d9767a12070b1: file:projects/presentation.tgz_096c09b0b673a57c275d9767a12070b1:
resolution: {integrity: sha512-QhfoHLyegkl3JsleXaZkSWsVQgikJM9jdqkla2mRW3kHyQAYWPBITUnVVWu1qBADViJupNZdYmYEcm7oeMF5og==, tarball: file:projects/presentation.tgz} resolution: {integrity: sha512-jijMKCYTGrkmr9ShyFIPRnr3Uxyy8gyk8SDmwr9Wl4VevuSO9S/vHE0Pln+bEcKDtnzM/Vzh3GJmwUq3aPV3Aw==, tarball: file:projects/presentation.tgz}
id: file:projects/presentation.tgz id: file:projects/presentation.tgz
name: '@rush-temp/presentation' name: '@rush-temp/presentation'
version: 0.0.0 version: 0.0.0
@ -13083,7 +13163,7 @@ packages:
dev: false dev: false
file:projects/prod.tgz_sass@1.45.0+typescript@4.5.4: file:projects/prod.tgz_sass@1.45.0+typescript@4.5.4:
resolution: {integrity: sha512-mb0NOzQOQI/mZjzWLO+zp8F57x5koGPRF0qdrONOBHgKJ+wKpbSrVyW911S0nM8b/mVimVqaXCI2XdjAvYO1mg==, tarball: file:projects/prod.tgz} resolution: {integrity: sha512-8jdkx/UlSEQLfZMUQ/XwQoqOiGAZtR6yJehJqk8TrjkUWKD4y0kroErjmQZ1sv014LnaWuux3mjbte+9b79I7w==, tarball: file:projects/prod.tgz}
id: file:projects/prod.tgz id: file:projects/prod.tgz
name: '@rush-temp/prod' name: '@rush-temp/prod'
version: 0.0.0 version: 0.0.0
@ -13157,7 +13237,7 @@ packages:
dev: false dev: false
file:projects/recruit-resources.tgz_096c09b0b673a57c275d9767a12070b1: file:projects/recruit-resources.tgz_096c09b0b673a57c275d9767a12070b1:
resolution: {integrity: sha512-p9e/9INuzm1qqr+zo6pCU0XRtFlQPObX3USd3gRr4hSt/YnFOD5+flKwk4LMCPtLrubdFhTAPX+Ydu1cHJrnnQ==, tarball: file:projects/recruit-resources.tgz} resolution: {integrity: sha512-NgcfcFmKVD0ojJbtjT3yvD//m1x3koZpBdkLryhSKtQMhzcuZD9GpBmVbRnPtLGBrDMSALasx1NB5JMxxDPLFQ==, tarball: file:projects/recruit-resources.tgz}
id: file:projects/recruit-resources.tgz id: file:projects/recruit-resources.tgz
name: '@rush-temp/recruit-resources' name: '@rush-temp/recruit-resources'
version: 0.0.0 version: 0.0.0
@ -13611,7 +13691,7 @@ packages:
dev: false dev: false
file:projects/telegram-resources.tgz_096c09b0b673a57c275d9767a12070b1: file:projects/telegram-resources.tgz_096c09b0b673a57c275d9767a12070b1:
resolution: {integrity: sha512-ZneaA8uvCmjuSf+zjIrPM1lzhi1At5Giud4UOzKwz2o0lrC4+npRTtCIZ52ZKjjNoOTmgf/hr9RYrqwCMH+z4g==, tarball: file:projects/telegram-resources.tgz} resolution: {integrity: sha512-hreZkJ3tRwh2rXC7K/WlkA99TDatMLuHwtC2dTXqivRNEKhwH0RHDXgO1jrjulj7uSNj5+0ksOeQtlWBYmhRiw==, tarball: file:projects/telegram-resources.tgz}
id: file:projects/telegram-resources.tgz id: file:projects/telegram-resources.tgz
name: '@rush-temp/telegram-resources' name: '@rush-temp/telegram-resources'
version: 0.0.0 version: 0.0.0

View File

@ -97,6 +97,8 @@
"@anticrm/templates": "~0.6.0", "@anticrm/templates": "~0.6.0",
"@anticrm/templates-assets": "~0.6.0", "@anticrm/templates-assets": "~0.6.0",
"@anticrm/templates-resources": "~0.6.0", "@anticrm/templates-resources": "~0.6.0",
"@anticrm/notification": "~0.6.0",
"@anticrm/notification-resources": "~0.6.0",
"@anticrm/core": "~0.6.16", "@anticrm/core": "~0.6.16",
"@anticrm/rekoni": "~0.6.0" "@anticrm/rekoni": "~0.6.0"
} }

View File

@ -33,6 +33,7 @@ import { gmailId } from '@anticrm/gmail'
import { imageCropperId } from '@anticrm/image-cropper' import { imageCropperId } from '@anticrm/image-cropper'
import { inventoryId } from '@anticrm/inventory' import { inventoryId } from '@anticrm/inventory'
import { templatesId } from '@anticrm/templates' import { templatesId } from '@anticrm/templates'
import { notificationId } from '@anticrm/notification'
import rekoni from '@anticrm/rekoni' import rekoni from '@anticrm/rekoni'
import '@anticrm/login-assets' import '@anticrm/login-assets'
@ -86,4 +87,6 @@ export async function configurePlatform() {
addLocation(imageCropperId, () => import(/* webpackChunkName: "image-cropper" */ '@anticrm/image-cropper-resources')) addLocation(imageCropperId, () => import(/* webpackChunkName: "image-cropper" */ '@anticrm/image-cropper-resources'))
addLocation(inventoryId, () => import(/* webpackChunkName: "inventory" */ '@anticrm/inventory-resources')) addLocation(inventoryId, () => import(/* webpackChunkName: "inventory" */ '@anticrm/inventory-resources'))
addLocation(templatesId, () => import(/* webpackChunkName: "templates" */ '@anticrm/templates-resources')) addLocation(templatesId, () => import(/* webpackChunkName: "templates" */ '@anticrm/templates-resources'))
addLocation(templatesId, () => import(/* webpackChunkName: "templates" */ '@anticrm/templates-resources'))
addLocation(notificationId, () => import(/* webpackChunkName: "notification" */ '@anticrm/notification-resources'))
} }

View File

@ -50,6 +50,7 @@
"@anticrm/model-inventory": "~0.6.0", "@anticrm/model-inventory": "~0.6.0",
"@anticrm/model-presentation": "~0.6.0", "@anticrm/model-presentation": "~0.6.0",
"@anticrm/model-templates": "~0.6.0", "@anticrm/model-templates": "~0.6.0",
"@anticrm/model-notification": "~0.6.0",
"@anticrm/model-text-editor": "~0.6.0", "@anticrm/model-text-editor": "~0.6.0",
"@anticrm/core": "~0.6.13" "@anticrm/core": "~0.6.13"
} }

View File

@ -36,6 +36,7 @@ import { createModel as templatesModel } from '@anticrm/model-templates'
import { createModel as textEditorModel } from '@anticrm/model-text-editor' import { createModel as textEditorModel } from '@anticrm/model-text-editor'
import { createModel as viewModel } from '@anticrm/model-view' import { createModel as viewModel } from '@anticrm/model-view'
import { createModel as workbenchModel } from '@anticrm/model-workbench' import { createModel as workbenchModel } from '@anticrm/model-workbench'
import { createModel as notificationModel } from '@anticrm/model-notification'
const builder = new Builder() const builder = new Builder()
@ -57,7 +58,7 @@ const builders = [
presentationModel, presentationModel,
templatesModel, templatesModel,
textEditorModel, textEditorModel,
notificationModel,
serverCoreModel, serverCoreModel,
serverAttachmentModel, serverAttachmentModel,
serverContactModel, serverContactModel,

View File

@ -70,6 +70,8 @@ export class TChannel extends TAttachedDoc implements Channel {
@Prop(TypeString(), 'Value' as IntlString) @Prop(TypeString(), 'Value' as IntlString)
value!: string value!: string
items?: number
} }
@Model(contact.class.Person, contact.class.Contact) @Model(contact.class.Person, contact.class.Contact)

View File

@ -0,0 +1,7 @@
module.exports = {
extends: ['./node_modules/@anticrm/model-rig/profiles/default/config/eslint.config.json'],
parserOptions: {
tsconfigRootDir: __dirname,
project: './tsconfig.json'
}
}

View File

@ -0,0 +1,4 @@
*
!/lib/**
!CHANGELOG.md
/lib/**/__tests__/

View File

@ -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"
}

View File

@ -0,0 +1,34 @@
{
"name": "@anticrm/model-notification",
"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.11",
"@anticrm/model": "~0.6.0",
"@anticrm/platform": "~0.6.5",
"@anticrm/model-core": "~0.6.0",
"@anticrm/notification": "~0.6.0"
}
}

View File

@ -0,0 +1,36 @@
//
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021, 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 { Account, Domain, Ref, Timestamp } from '@anticrm/core'
import { Builder, Model, Prop, TypeRef, TypeTimestamp } from '@anticrm/model'
import core, { TAttachedDoc } from '@anticrm/model-core'
import notificaton, { LastView } from '@anticrm/notification'
import type { IntlString } from '@anticrm/platform'
export const DOMAIN_NOTIFICATION = 'notification' as Domain
@Model(notificaton.class.LastView, core.class.AttachedDoc, DOMAIN_NOTIFICATION)
export class TLastView extends TAttachedDoc implements LastView {
@Prop(TypeTimestamp(), 'Last View' as IntlString)
lastView!: Timestamp
@Prop(TypeRef(core.class.Account), 'Modified By' as IntlString)
user!: Ref<Account>
}
export function createModel (builder: Builder): void {
builder.createModel(TLastView)
}

View File

@ -0,0 +1,8 @@
{
"extends": "./node_modules/@anticrm/model-rig/profiles/default/tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib",
}
}

View File

@ -55,6 +55,7 @@ export default plugin(coreId, {
}, },
space: { space: {
Tx: '' as Ref<Space>, Tx: '' as Ref<Space>,
DerivedTx: '' as Ref<Space>,
Model: '' as Ref<Space> Model: '' as Ref<Space>
}, },
account: { account: {

View File

@ -30,7 +30,6 @@ export { default as AttributesBar } from './components/AttributesBar.svelte'
export { default as AttributeBarEditor } from './components/AttributeBarEditor.svelte' export { default as AttributeBarEditor } from './components/AttributeBarEditor.svelte'
export { default as AttributeEditor } from './components/AttributeEditor.svelte' export { default as AttributeEditor } from './components/AttributeEditor.svelte'
export { default as Card } from './components/Card.svelte' export { default as Card } from './components/Card.svelte'
export { default as Channels } from './components/Channels.svelte'
export { default as PDFViewer } from './components/PDFViewer.svelte' export { default as PDFViewer } from './components/PDFViewer.svelte'
export { default as MessageBox } from './components/MessageBox.svelte' export { default as MessageBox } from './components/MessageBox.svelte'
export { default as SpaceCreateCard } from './components/SpaceCreateCard.svelte' export { default as SpaceCreateCard } from './components/SpaceCreateCard.svelte'

View File

@ -22,11 +22,9 @@ import login from '@anticrm/login'
import { getMetadata } from '@anticrm/platform' import { getMetadata } from '@anticrm/platform'
import { LiveQuery as LQ } from '@anticrm/query' import { LiveQuery as LQ } from '@anticrm/query'
import { onDestroy } from 'svelte' import { onDestroy } from 'svelte'
import contact, { ChannelProvider } from '@anticrm/contact'
let liveQuery: LQ let liveQuery: LQ
let client: TxOperations let client: TxOperations
let channelProviders: Promise<ChannelProvider[]> | undefined
class UIClient extends TxOperations implements Client { class UIClient extends TxOperations implements Client {
constructor (client: Client, private readonly liveQuery: LQ) { constructor (client: Client, private readonly liveQuery: LQ) {
@ -49,7 +47,6 @@ export function setClient (_client: Client): void {
_client.notify = (tx: Tx) => { _client.notify = (tx: Tx) => {
liveQuery.tx(tx).catch((err) => console.log(err)) liveQuery.tx(tx).catch((err) => console.log(err))
} }
channelProviders = client.findAll(contact.class.ChannelProvider, {})
} }
export class LiveQuery { export class LiveQuery {
@ -113,12 +110,3 @@ export function getAttributePresenterClass (attribute: AnyAttribute): Ref<Class<
} }
return attrClass return attrClass
} }
export async function getChannelProviders (): Promise<Map<Ref<ChannelProvider>, ChannelProvider>> {
const cp = (await channelProviders) ?? []
const map = new Map<Ref<ChannelProvider>, ChannelProvider>()
for (const provider of cp) {
map.set(provider._id, provider)
}
return map
}

View File

@ -116,6 +116,7 @@
bind:this={componentInstance} bind:this={componentInstance}
_id={props._id} _id={props._id}
_class={props._class} _class={props._class}
rightSection={props.rightSection}
on:update={fitPopup} on:update={fitPopup}
on:close={_close} on:close={_close}
/> />

View File

@ -7,6 +7,7 @@ export interface PanelProps {
_id: string _id: string
_class: string _class: string
element?: PopupAlignment element?: PopupAlignment
rightSection?: AnyComponent
} }
export const panelstore = writable < {panel?: PanelProps|undefined}>({ panel: undefined }) export const panelstore = writable < {panel?: PanelProps|undefined}>({ panel: undefined })
@ -23,7 +24,8 @@ export function showPanel (
component: AnyComponent, component: AnyComponent,
_id: string, _id: string,
_class: string, _class: string,
element?: PopupAlignment element?: PopupAlignment,
rightSection?: AnyComponent
): void { ): void {
const newLoc = encodeURIComponent([component, _id, _class].join('|')) const newLoc = encodeURIComponent([component, _id, _class].join('|'))
if (currentLocation === newLoc) { if (currentLocation === newLoc) {
@ -31,7 +33,7 @@ export function showPanel (
} }
currentLocation = newLoc currentLocation = newLoc
panelstore.update(() => { panelstore.update(() => {
return { panel: { component, _id, _class, element } } return { panel: { component, _id, _class, element, rightSection } }
}) })
const location = getCurrentLocation() const location = getCurrentLocation()
if (location.fragment !== currentLocation) { if (location.fragment !== currentLocation) {

View File

@ -39,6 +39,7 @@
"svelte": "^3.37.0", "svelte": "^3.37.0",
"@anticrm/text-editor": "~0.6.0", "@anticrm/text-editor": "~0.6.0",
"@anticrm/contact": "~0.6.2", "@anticrm/contact": "~0.6.2",
"@anticrm/contact-resources": "~0.6.0",
"@anticrm/view-resources": "~0.6.0", "@anticrm/view-resources": "~0.6.0",
"@anticrm/view": "~0.6.0" "@anticrm/view": "~0.6.0"
} }

View File

@ -40,6 +40,7 @@
"@anticrm/core": "~0.6.11", "@anticrm/core": "~0.6.11",
"@anticrm/view": "~0.6.0", "@anticrm/view": "~0.6.0",
"@anticrm/attachment-resources": "~0.6.0", "@anticrm/attachment-resources": "~0.6.0",
"@anticrm/notification-resources": "~0.6.0",
"@anticrm/panel": "~0.6.0", "@anticrm/panel": "~0.6.0",
"@anticrm/view-resources": "~0.6.0", "@anticrm/view-resources": "~0.6.0",
"@anticrm/attachment": "~0.6.1" "@anticrm/attachment": "~0.6.1"

View File

@ -16,9 +16,10 @@
<script lang="ts"> <script lang="ts">
import { Channel } from '@anticrm/contact' import { Channel } from '@anticrm/contact'
import type { AttachedData, Doc, Ref } from '@anticrm/core' import type { AttachedData, Doc, Ref } from '@anticrm/core'
import presentation, { Channels } from '@anticrm/presentation' import presentation from '@anticrm/presentation'
import { CircleButton, IconAdd, Label, showPopup } from '@anticrm/ui' import { CircleButton, IconAdd, Label, showPopup } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte' import { createEventDispatcher } from 'svelte'
import ChannelsView from './ChannelsView.svelte'
import contact from '../plugin' import contact from '../plugin'
export let integrations: Set<Ref<Doc>> | undefined = undefined export let integrations: Set<Ref<Doc>> | undefined = undefined
@ -40,7 +41,7 @@
/> />
<span><Label label={presentation.string.AddSocialLinks} /></span> <span><Label label={presentation.string.AddSocialLinks} /></span>
{:else} {:else}
<Channels value={channels} size={'small'} {integrations} on:click /> <ChannelsView value={channels} size={'small'} {integrations} on:click />
<div class="ml-1"> <div class="ml-1">
<CircleButton <CircleButton
icon={contact.icon.Edit} icon={contact.icon.Edit}

View File

@ -14,7 +14,7 @@
// limitations under the License. // limitations under the License.
--> -->
<script lang="ts"> <script lang="ts">
import type { AttachedData, Class, Doc, Ref } from '@anticrm/core' import type { Class, Doc, Ref } from '@anticrm/core'
import { createQuery, getClient } from '@anticrm/presentation' import { createQuery, getClient } from '@anticrm/presentation'
import { ChannelProvider, Channel } from '@anticrm/contact' import { ChannelProvider, Channel } from '@anticrm/contact'
@ -35,40 +35,72 @@
} }
const query = createQuery() const query = createQuery()
query.query(contact.class.Channel, { query.query(
attachedTo: attachedTo contact.class.Channel,
}, (res) => { {
channels = res attachedTo: attachedTo
}) },
(res) => {
channels = res
}
)
const client = getClient() const client = getClient()
async function save (newValues: AttachedData<Channel>[]): Promise<void> { async function save (newValues: Channel[]): Promise<void> {
const currentProviders = new Set(channels.map((p) => p.provider)) const currentProviders = new Set(channels.map((p) => p.provider))
const promises = [] const promises = []
for (const value of newValues) { for (const value of newValues) {
const oldChannel = findValue(value.provider) const oldChannel = findValue(value.provider)
if (oldChannel === undefined) { if (oldChannel === undefined) {
if (value.value.length === 0) continue if (value.value.length === 0) continue
promises.push(client.addCollection(contact.class.Channel, contact.space.Contacts, attachedTo, attachedClass, 'channels', { promises.push(
value: value.value, client.addCollection(contact.class.Channel, contact.space.Contacts, attachedTo, attachedClass, 'channels', {
provider: value.provider value: value.value,
})) provider: value.provider
})
)
} else { } else {
currentProviders.delete(value.provider) currentProviders.delete(value.provider)
if (value.value === oldChannel.value) continue if (value.value === oldChannel.value) continue
promises.push(client.updateCollection(oldChannel._class, oldChannel.space, oldChannel._id, oldChannel.attachedTo, oldChannel.attachedToClass, oldChannel.collection, { promises.push(
value: value.value client.updateCollection(
})) oldChannel._class,
oldChannel.space,
oldChannel._id,
oldChannel.attachedTo,
oldChannel.attachedToClass,
oldChannel.collection,
{
value: value.value
}
)
)
} }
} }
for (const value of currentProviders) { for (const value of currentProviders) {
const oldChannel = findValue(value) const oldChannel = findValue(value)
if (oldChannel === undefined) continue if (oldChannel === undefined) continue
promises.push(client.removeCollection(oldChannel._class, oldChannel.space, oldChannel._id, oldChannel.attachedTo, oldChannel.attachedToClass, oldChannel.collection)) promises.push(
client.removeCollection(
oldChannel._class,
oldChannel.space,
oldChannel._id,
oldChannel.attachedTo,
oldChannel.attachedToClass,
oldChannel.collection
)
)
} }
Promise.all(promises) Promise.all(promises)
} }
</script> </script>
<Channels {channels} {integrations} on:change={(e) => { save(e.detail) }} on:click /> <Channels
{channels}
{integrations}
on:change={(e) => {
save(e.detail)
}}
on:click
/>

View File

@ -15,9 +15,26 @@
--> -->
<script lang="ts"> <script lang="ts">
import type { Channel } from '@anticrm/contact' import type { Channel } from '@anticrm/contact'
import { Channels } from '@anticrm/presentation' import ChannelsView from './ChannelsView.svelte'
import { showPanel } from '@anticrm/ui'
import view from '@anticrm/view'
export let value: Channel[] | Channel | null export let value: Channel[] | Channel | null
function click (ev: any) {
if (ev.detail.presenter !== undefined && Array.isArray(value)) {
const channel = value[0]
if (channel !== undefined) {
showPanel(
view.component.EditDoc,
channel.attachedTo,
channel.attachedToClass,
'full',
ev.detail.presenter
)
}
}
}
</script> </script>
<Channels {value} size={'small'} reverse /> <ChannelsView {value} size={'small'} reverse on:click={click} />

View File

@ -15,19 +15,21 @@
--> -->
<script lang="ts"> <script lang="ts">
import type { Channel, ChannelProvider } from '@anticrm/contact' import type { Channel, ChannelProvider } from '@anticrm/contact'
import type { AttachedData, Doc, Ref } from '@anticrm/core' import type { AttachedData, Doc, Ref, Timestamp } from '@anticrm/core'
import type { Asset, IntlString } from '@anticrm/platform' import type { Asset, IntlString } from '@anticrm/platform'
import type { AnyComponent } from '@anticrm/ui' import type { AnyComponent } from '@anticrm/ui'
import { CircleButton, Tooltip } from '@anticrm/ui' import { CircleButton, Tooltip } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte' import { createEventDispatcher } from 'svelte'
import { getClient } from '..'
import { getChannelProviders } from '../utils' import { getChannelProviders } from '../utils'
import ChannelsPopup from './ChannelsPopup.svelte' import ChannelsPopup from './ChannelsPopup.svelte'
import { NotificationClient } from '@anticrm/notification-resources'
export let value: AttachedData<Channel>[] | AttachedData<Channel> | null export let value: AttachedData<Channel>[] | Channel | null
export let size: 'small' | 'medium' | 'large' | 'x-large' = 'large' export let size: 'small' | 'medium' | 'large' | 'x-large' = 'large'
export let reverse: boolean = false export let reverse: boolean = false
export let integrations: Set<Ref<Doc>> = new Set<Ref<Doc>>() export let integrations: Set<Ref<Doc>> = new Set<Ref<Doc>>()
const notificationClient = NotificationClient.getClient()
const lastViews = notificationClient.getLastViews()
interface Item { interface Item {
label: IntlString label: IntlString
@ -35,19 +37,25 @@
value: string value: string
presenter?: AnyComponent presenter?: AnyComponent
integration: boolean integration: boolean
notification: boolean
} }
const client = getClient()
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
function getProvider (item: AttachedData<Channel>, map: Map<Ref<ChannelProvider>, ChannelProvider>): any | undefined { function getProvider (
item: AttachedData<Channel>,
map: Map<Ref<ChannelProvider>, ChannelProvider>,
lastViews: Map<Ref<Doc>, Timestamp>
): any | undefined {
const provider = map.get(item.provider) const provider = map.get(item.provider)
if (provider) { if (provider) {
const notification = (item as Channel)._id !== undefined ? isNew((item as Channel), lastViews) : false
return { return {
label: provider.label as IntlString, label: provider.label as IntlString,
icon: provider.icon as Asset, icon: provider.icon as Asset,
value: item.value, value: item.value,
presenter: provider.presenter, presenter: provider.presenter,
notification,
integration: provider.integrationType !== undefined ? integrations.has(provider.integrationType) : false integration: provider.integrationType !== undefined ? integrations.has(provider.integrationType) : false
} }
} else { } else {
@ -55,18 +63,27 @@
} }
} }
async function update (value: AttachedData<Channel>[] | AttachedData<Channel>) { function isNew (item: Channel, lastViews: Map<Ref<Doc>, Timestamp>): boolean {
const lastView = (item as Channel)._id !== undefined ? lastViews.get((item as Channel)._id) : undefined
return lastView ? lastView < item.modifiedOn : (item.items ?? 0) > 0
}
async function update (value: AttachedData<Channel>[] | Channel | null, lastViews: Map<Ref<Doc>, Timestamp>) {
if (value === null) {
displayItems = []
return
}
const result = [] const result = []
const map = await getChannelProviders() const map = await getChannelProviders()
if (Array.isArray(value)) { if (Array.isArray(value)) {
for (const item of value) { for (const item of value) {
const provider = getProvider(item, map) const provider = getProvider(item, map, lastViews)
if (provider !== undefined) { if (provider !== undefined) {
result.push(provider) result.push(provider)
} }
} }
} else { } else {
const provider = getProvider(value, map) const provider = getProvider(value, map, lastViews)
if (provider !== undefined) { if (provider !== undefined) {
result.push(provider) result.push(provider)
} }
@ -74,7 +91,7 @@
displayItems = result displayItems = result
} }
$: if (value) update(value) $: if (value) update(value, $lastViews)
let displayItems: Item[] = [] let displayItems: Item[] = []
let divHTML: HTMLElement let divHTML: HTMLElement
@ -94,7 +111,7 @@
}} }}
> >
<Tooltip component={ChannelsPopup} props={{ value: item }} label={undefined} anchor={divHTML}> <Tooltip component={ChannelsPopup} props={{ value: item }} label={undefined} anchor={divHTML}>
<CircleButton icon={item.icon} {size} primary={item.integration} /> <CircleButton icon={item.icon} {size} primary={item.integration || item.notification} />
</Tooltip> </Tooltip>
</div> </div>
{/each} {/each}

View File

@ -23,7 +23,7 @@
import { Channel, Organization } from '@anticrm/contact' import { Channel, Organization } from '@anticrm/contact'
import contact from '../plugin' import contact from '../plugin'
import Company from './icons/Company.svelte' import Company from './icons/Company.svelte'
import { generateId } from '@anticrm/core' import { AttachedData, generateId } from '@anticrm/core'
import Channels from './Channels.svelte' import Channels from './Channels.svelte'
export function canClose (): boolean { export function canClose (): boolean {
@ -51,7 +51,7 @@
dispatch('close') dispatch('close')
} }
let channels: Channel[] = [] let channels: AttachedData<Channel>[] = []
</script> </script>
<Card <Card

View File

@ -15,7 +15,7 @@
--> -->
<script lang="ts"> <script lang="ts">
import { createEventDispatcher } from 'svelte' import { createEventDispatcher } from 'svelte'
import { Data, generateId } from '@anticrm/core' import { AttachedData, Data, generateId } from '@anticrm/core'
import { getResource } from '@anticrm/platform' import { getResource } from '@anticrm/platform'
import { getClient, Card, EditableAvatar } from '@anticrm/presentation' import { getClient, Card, EditableAvatar } from '@anticrm/presentation'
@ -72,7 +72,7 @@
dispatch('close') dispatch('close')
} }
let channels: Channel[] = [] let channels: AttachedData<Channel>[] = []
</script> </script>
<Card <Card

View File

@ -14,7 +14,7 @@
// limitations under the License. // limitations under the License.
--> -->
<script lang="ts"> <script lang="ts">
import type { AttachedData, Ref } from '@anticrm/core' import { AttachedData, Ref } from '@anticrm/core'
import { createEventDispatcher } from 'svelte' import { createEventDispatcher } from 'svelte'
import { EditBox, Button, ScrollBox } from '@anticrm/ui' import { EditBox, Button, ScrollBox } from '@anticrm/ui'
import { getClient } from '@anticrm/presentation' import { getClient } from '@anticrm/presentation'
@ -42,14 +42,14 @@
for (const provider of providers) { for (const provider of providers) {
const i = findValue(provider._id) const i = findValue(provider._id)
if (i !== -1) { if (i !== -1) {
newValues.push({ provider: provider._id, value: values[i].value }) newValues.push(values[i])
} else { } else {
newValues.push({ provider: provider._id, value: '' }) newValues.push({ provider: provider._id, value: '' })
} }
} }
}) })
function filterUndefined (channels: AttachedData<Channel>[]): AttachedData<Channel>[] { function filterUndefined (channels: AttachedData<Channel>[]): AttachedData<Channel>[] {
return channels.filter((channel) => channel.value !== undefined && channel.value.length > 0) return channels.filter((channel) => channel.value !== undefined && channel.value.length > 0)
} }
</script> </script>

View File

@ -21,6 +21,7 @@ import { Avatar, ObjectSearchResult, UserInfo } from '@anticrm/presentation'
import ChannelsEditor from './components/ChannelsEditor.svelte' import ChannelsEditor from './components/ChannelsEditor.svelte'
import Channels from './components/Channels.svelte' import Channels from './components/Channels.svelte'
import ChannelsPresenter from './components/ChannelsPresenter.svelte' import ChannelsPresenter from './components/ChannelsPresenter.svelte'
import ChannelsView from './components/ChannelsView.svelte'
import ContactPresenter from './components/ContactPresenter.svelte' import ContactPresenter from './components/ContactPresenter.svelte'
import Contacts from './components/Contacts.svelte' import Contacts from './components/Contacts.svelte'
import CreateOrganization from './components/CreateOrganization.svelte' import CreateOrganization from './components/CreateOrganization.svelte'
@ -34,7 +35,7 @@ import PersonPresenter from './components/PersonPresenter.svelte'
import SocialEditor from './components/SocialEditor.svelte' import SocialEditor from './components/SocialEditor.svelte'
import contact from './plugin' import contact from './plugin'
export { Channels, ChannelsEditor, ContactPresenter } export { Channels, ChannelsEditor, ContactPresenter, ChannelsView }
async function queryContact (_class: Ref<Class<Contact>>, client: Client, search: string): Promise<ObjectSearchResult[]> { async function queryContact (_class: Ref<Class<Contact>>, client: Client, search: string): Promise<ObjectSearchResult[]> {
return (await client.findAll(_class, { name: { $like: `%${search}%` } }, { limit: 200 })).map(e => ({ return (await client.findAll(_class, { name: { $like: `%${search}%` } }, { limit: 200 })).map(e => ({

View File

@ -0,0 +1,33 @@
//
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 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 contact, { ChannelProvider } from '@anticrm/contact'
import { Ref } from '@anticrm/core'
import { getClient } from '@anticrm/presentation'
let channelProviders: Promise<ChannelProvider[]> | undefined
const client = getClient()
channelProviders = client.findAll(contact.class.ChannelProvider, {})
export async function getChannelProviders (): Promise<Map<Ref<ChannelProvider>, ChannelProvider>> {
const cp = (await channelProviders) ?? []
const map = new Map<Ref<ChannelProvider>, ChannelProvider>()
for (const provider of cp) {
map.set(provider._id, provider)
}
return map
}

View File

@ -43,6 +43,7 @@ export interface ChannelProvider extends Doc, UXObject {
export interface Channel extends AttachedDoc { export interface Channel extends AttachedDoc {
provider: Ref<ChannelProvider> provider: Ref<ChannelProvider>
value: string value: string
items?: number
} }
/** /**

View File

@ -41,6 +41,7 @@
"@anticrm/setting": "~0.6.0", "@anticrm/setting": "~0.6.0",
"@anticrm/chunter": "~0.6.0", "@anticrm/chunter": "~0.6.0",
"@anticrm/chunter-resources": "~0.6.0", "@anticrm/chunter-resources": "~0.6.0",
"@anticrm/notification-resources": "~0.6.0",
"@anticrm/login": "~0.6.1", "@anticrm/login": "~0.6.1",
"@anticrm/core": "~0.6.11" "@anticrm/core": "~0.6.11"
} }

View File

@ -13,7 +13,6 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
--> -->
<script lang="ts"> <script lang="ts">
import { createQuery, getClient } from '@anticrm/presentation' import { createQuery, getClient } from '@anticrm/presentation'
import { Message, SharedMessage } from '@anticrm/gmail' import { Message, SharedMessage } from '@anticrm/gmail'
@ -25,12 +24,14 @@
import setting from '@anticrm/setting' import setting from '@anticrm/setting'
import Connect from './Connect.svelte' import Connect from './Connect.svelte'
import Messages from './Messages.svelte' import Messages from './Messages.svelte'
import { NotificationClient } from '@anticrm/notification-resources'
export let object: Contact export let object: Contact
export let channel: Channel export let channel: Channel
export let newMessage: boolean export let newMessage: boolean
const EMAIL_REGEX = /(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))/ const EMAIL_REGEX =
/(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))/
let messages: Message[] = [] let messages: Message[] = []
let accounts: EmployeeAccount[] = [] let accounts: EmployeeAccount[] = []
@ -42,22 +43,31 @@
const accauntsQuery = createQuery() const accauntsQuery = createQuery()
const settingsQuery = createQuery() const settingsQuery = createQuery()
const accountId = getCurrentAccount()._id const accountId = getCurrentAccount()._id
const notificationClient = NotificationClient.getClient()
$: messagesQuery.query( function updateMessagesQuery (channelId: Ref<Channel>): void {
gmail.class.Message, messagesQuery.query(
{ attachedTo: channel._id }, gmail.class.Message,
(res) => { { attachedTo: channelId },
messages = res (res) => {
}, messages = res
{ sort: { modifiedOn: SortingOrder.Descending } } notificationClient.updateLastView(channelId, channel._class)
) const accountsIds = new Set(messages.map((p) => p.modifiedBy as Ref<EmployeeAccount>))
updateAccountsQuery(accountsIds)
},
{ sort: { modifiedOn: SortingOrder.Descending } }
)
}
$: accountsIds = messages.map((p) => p.modifiedBy as Ref<EmployeeAccount>) $: updateMessagesQuery(channel._id)
$: accauntsQuery.query(contact.class.EmployeeAccount, { _id: { $in: accountsIds }}, (result) => {
accounts = result
})
$: settingsQuery.query( function updateAccountsQuery (accountsIds: Set<Ref<EmployeeAccount>>): void {
accauntsQuery.query(contact.class.EmployeeAccount, { _id: { $in: Array.from(accountsIds) } }, (result) => {
accounts = result
})
}
settingsQuery.query(
setting.class.Integration, setting.class.Integration,
{ type: gmail.integrationType.Gmail, space: accountId as string as Ref<Space> }, { type: gmail.integrationType.Gmail, space: accountId as string as Ref<Space> },
(res) => { (res) => {
@ -68,9 +78,17 @@
async function share (): Promise<void> { async function share (): Promise<void> {
const selectedMessages = messages.filter((m) => selected.has(m._id as string as Ref<SharedMessage>)) const selectedMessages = messages.filter((m) => selected.has(m._id as string as Ref<SharedMessage>))
await client.addCollection(gmail.class.SharedMessages, object.space, object._id, object._class, 'gmailSharedMessages', { await client.addCollection(
messages: convertMessages(selectedMessages, accounts) gmail.class.SharedMessages,
}) object.space,
object._id,
object._class,
'gmailSharedMessages',
{
messages: convertMessages(selectedMessages, accounts)
}
)
await notificationClient.updateLastView(channel._id, channel._class, undefined, true)
clear() clear()
} }
@ -91,7 +109,6 @@
}) })
} }
function getName (message: Message, accounts: EmployeeAccount[], sender: boolean): string { function getName (message: Message, accounts: EmployeeAccount[], sender: boolean): string {
if (message.incoming === sender) { if (message.incoming === sender) {
return `${formatName(object.name)} (${channel.value})` return `${formatName(object.name)} (${channel.value})`

View File

@ -66,7 +66,7 @@
{#if currentMessage.copy?.length} {#if currentMessage.copy?.length}
<Label label={gmail.string.Copy} />: {currentMessage.copy.join(', ')} <Label label={gmail.string.Copy} />: {currentMessage.copy.join(', ')}
{/if} {/if}
<div class="flex-col clear-mins mt-9"> <div class="flex-col h-full clear-mins mt-9">
<FullMessageContent content={currentMessage.content} /> <FullMessageContent content={currentMessage.content} />
</div> </div>
</div> </div>

View File

@ -15,20 +15,18 @@
--> -->
<script lang="ts"> <script lang="ts">
export let content: string export let content: string
let editor: HTMLDivElement
$: if (editor) editor.innerHTML = content
</script> </script>
<div bind:this={editor} class="input clear-mins" /> <iframe srcdoc={content} title="e-mail" />
<style lang="scss"> <style lang="scss">
.input { iframe {
overflow: auto; overflow: auto;
padding: 1rem; border: none;
border-radius: 0.5rem;
height: 100%;
background-color: #fff; background-color: #fff;
color: #1f212b; color: #1f212b;
border-radius: .5rem;
:global(a) { :global(a) {
font: inherit; font: inherit;

View File

@ -40,7 +40,7 @@
{#if message.copy?.length} {#if message.copy?.length}
<Label label={gmail.string.Copy} />: {message.copy.join(', ')} <Label label={gmail.string.Copy} />: {message.copy.join(', ')}
{/if} {/if}
<div class="flex-col clear-mins mt-5"> <div class="flex-col h-full clear-mins mt-5">
<FullMessageContent content={message.content} /> <FullMessageContent content={message.content} />
</div> </div>
</div> </div>
@ -51,7 +51,7 @@
max-height: 500px; max-height: 500px;
background-color: var(--theme-button-bg-focused); background-color: var(--theme-button-bg-focused);
border: 1px solid var(--theme-button-border-enabled); border: 1px solid var(--theme-button-border-enabled);
border-radius: .75rem; border-radius: 0.75rem;
box-shadow: 0px 10px 20px rgba(0, 0, 0, .2); box-shadow: 0px 10px 20px rgba(0, 0, 0, 0.2);
} }
</style> </style>

View File

@ -20,18 +20,24 @@
import FullMessage from './FullMessage.svelte' import FullMessage from './FullMessage.svelte'
import Chats from './Chats.svelte' import Chats from './Chats.svelte'
import { getClient } from '@anticrm/presentation' import { getClient } from '@anticrm/presentation'
import { NotificationClient } from '@anticrm/notification-resources'
export let object: Contact export let object: Contact
let newMessage: boolean = false let newMessage: boolean = false
let currentMessage: SharedMessage | undefined = undefined let currentMessage: SharedMessage | undefined = undefined
let channel: Channel | undefined = undefined let channel: Channel | undefined = undefined
const notificationClient = NotificationClient.getClient()
const client = getClient() const client = getClient()
client.findOne(contact.class.Channel, { client
attachedTo: object._id, .findOne(contact.class.Channel, {
provider: contact.channelProvider.Email attachedTo: object._id,
}).then((res) => channel = res) provider: contact.channelProvider.Email
})
.then((res) => {
channel = res
})
function back () { function back () {
if (newMessage) { if (newMessage) {
@ -40,8 +46,11 @@
return (currentMessage = undefined) return (currentMessage = undefined)
} }
function selectHandler (e: CustomEvent) { async function selectHandler (e: CustomEvent): Promise<void> {
currentMessage = e.detail currentMessage = e.detail
if (channel !== undefined) {
await notificationClient.updateLastView(channel._id, channel._class, undefined, true)
}
} }
</script> </script>

View File

@ -15,9 +15,10 @@
--> -->
<script lang="ts"> <script lang="ts">
import type { SharedMessage } from '@anticrm/gmail' import type { SharedMessage } from '@anticrm/gmail'
import { CheckBox } from '@anticrm/ui' import { CheckBox, Label } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte' import { createEventDispatcher } from 'svelte'
import { getTime } from '../utils' import { getTime } from '../utils'
import gmail from '../plugin'
export let message: SharedMessage export let message: SharedMessage
export let selected: boolean = false export let selected: boolean = false
@ -37,10 +38,14 @@
{/if} {/if}
<div class="flex-col message" class:selected> <div class="flex-col message" class:selected>
<div class="flex-between text-sm mb-1"> <div class="flex-between text-sm mb-1">
<div class="content-trans-color overflow-label mr-4">From: <span class="content-accent-color">{message.sender}</span></div> <div class="content-trans-color overflow-label mr-4">
<Label label={gmail.string.From} /><span class="content-accent-color">{message.sender}</span>
</div>
<div class="content-trans-color">{getTime(message.modifiedOn)}</div> <div class="content-trans-color">{getTime(message.modifiedOn)}</div>
</div> </div>
<div class="content-trans-color text-sm overflow-label mr-4 mb-4">To: <span class="content-accent-color">{message.receiver}</span></div> <div class="content-trans-color text-sm overflow-label mr-4 mb-4">
<Label label={gmail.string.To} /><span class="content-accent-color">{message.receiver}</span>
</div>
<div class="fs-title overflow-label mb-1"> <div class="fs-title overflow-label mb-1">
{message.subject} {message.subject}
</div> </div>
@ -60,10 +65,12 @@
padding: 1rem; padding: 1rem;
min-width: 0; min-width: 0;
background-color: var(--theme-incoming-msg); background-color: var(--theme-incoming-msg);
border-radius: .75rem; border-radius: 0.75rem;
white-space: nowrap; white-space: nowrap;
flex-grow: 1; flex-grow: 1;
&.selected { background-color: var(--primary-button-enabled); } &.selected {
background-color: var(--primary-button-enabled);
}
} }
</style> </style>

View File

@ -24,10 +24,12 @@
import { Channel, Contact, formatName } from '@anticrm/contact' import { Channel, Contact, formatName } from '@anticrm/contact'
import { TextEditor } from '@anticrm/text-editor' import { TextEditor } from '@anticrm/text-editor'
import plugin from '../plugin' import plugin from '../plugin'
import { NotificationClient } from '@anticrm/notification-resources'
export let object: Contact export let object: Contact
export let channel: Channel export let channel: Channel
export let currentMessage: SharedMessage | undefined export let currentMessage: SharedMessage | undefined
const notificationClient = NotificationClient.getClient()
let editor: TextEditor let editor: TextEditor
let copy: string = '' let copy: string = ''
@ -42,7 +44,7 @@
const url = getMetadata(login.metadata.GmailUrl) ?? '' const url = getMetadata(login.metadata.GmailUrl) ?? ''
async function sendMsg () { async function sendMsg () {
fetch(url + '/send', { await fetch(url + '/send', {
method: 'POST', method: 'POST',
headers: { headers: {
Authorization: 'Bearer ' + getMetadata(login.metadata.LoginToken), Authorization: 'Bearer ' + getMetadata(login.metadata.LoginToken),
@ -53,6 +55,7 @@
copy: copy.split(',').map((m) => m.trim()) copy: copy.split(',').map((m) => m.trim())
}) })
}) })
await notificationClient.updateLastView(channel._id, channel._class, undefined, true)
dispatch('close') dispatch('close')
} }
@ -130,7 +133,7 @@
background-color: #fff; background-color: #fff;
color: #1f212b; color: #1f212b;
height: 100%; height: 100%;
border-radius: .5rem; border-radius: 0.5rem;
:global(.ProseMirror) { :global(.ProseMirror) {
min-height: 0; min-height: 0;

View File

@ -0,0 +1,7 @@
module.exports = {
extends: ['./node_modules/@anticrm/platform-rig/profiles/default/config/eslint.config.json'],
parserOptions: {
tsconfigRootDir: __dirname,
project: './tsconfig.json'
}
}

View File

@ -0,0 +1,4 @@
*
!/lib/**
!CHANGELOG.md
/lib/**/__tests__/

View File

@ -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"
}

View File

@ -0,0 +1,37 @@
{
"name": "@anticrm/notification-resources",
"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",
"svelte-check": "svelte-check"
},
"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",
"svelte-check": "~2.3.0"
},
"dependencies": {
"@anticrm/presentation": "~0.6.2",
"@anticrm/platform": "~0.6.5",
"@anticrm/core": "~0.6.11",
"@anticrm/notification": "~0.6.0",
"svelte": "^3.37.0"
}
}

View File

@ -0,0 +1,21 @@
//
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021, 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 { Resources } from '@anticrm/platform'
export default async (): Promise<Resources> => ({})
export * from './utils'

View File

@ -0,0 +1,89 @@
//
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021, 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, getCurrentAccount, Ref, Timestamp } from '@anticrm/core'
import notification, { LastView } from '@anticrm/notification'
import { createQuery, getClient } from '@anticrm/presentation'
import { writable, Writable } from 'svelte/store'
/**
* @public
*/
export class NotificationClient {
protected static _instance: NotificationClient | undefined = undefined
private readonly lastViews = new Map<Ref<Doc>, LastView>()
private readonly lastViewsStore = writable(new Map<Ref<Doc>, Timestamp>())
private readonly lastViewQuery = createQuery()
private constructor () {
this.lastViewQuery.query(notification.class.LastView, { user: getCurrentAccount()._id }, (result) => {
const res: Map<Ref<Doc>, Timestamp> = new Map<Ref<Doc>, Timestamp>()
result.forEach((p) => {
res.set(p.attachedTo, p.lastView)
this.lastViews.set(p.attachedTo, p)
})
this.lastViewsStore.set(res)
})
}
static getClient (): NotificationClient {
if (NotificationClient._instance === undefined) {
NotificationClient._instance = new NotificationClient()
}
return NotificationClient._instance
}
getLastViews (): Writable<Map<Ref<Doc>, Timestamp>> {
return this.lastViewsStore
}
async updateLastView (
_id: Ref<Doc>,
_class: Ref<Class<Doc>>,
time?: Timestamp,
force: boolean = false
): Promise<void> {
const client = getClient()
const user = getCurrentAccount()._id
const lastView = time ?? new Date().getTime()
const current = this.lastViews.get(_id)
if (current !== undefined) {
if (current.lastView < lastView || force) {
await client.updateDoc(current._class, current.space, current._id, {
lastView: lastView
})
}
} else if (force) {
await client.createDoc(notification.class.LastView, notification.space.Notifications, {
user,
lastView,
attachedTo: _id,
attachedToClass: _class,
collection: 'lastViews'
})
}
}
async unsubscribe (_id: Ref<Doc>): Promise<void> {
const client = getClient()
const user = getCurrentAccount()._id
const current = await client.findOne(notification.class.LastView, { attachedTo: _id, user })
if (current !== undefined) {
await client.removeDoc(current._class, current.space, current._id)
}
}
}

View File

@ -0,0 +1,5 @@
const sveltePreprocess = require('svelte-preprocess')
module.exports = {
preprocess: sveltePreprocess()
};

View File

@ -0,0 +1,8 @@
{
"extends": "./node_modules/@anticrm/platform-rig/profiles/default/tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib",
}
}

View File

@ -0,0 +1,7 @@
module.exports = {
extends: ['./node_modules/@anticrm/platform-rig/profiles/default/config/eslint.config.json'],
parserOptions: {
tsconfigRootDir: __dirname,
project: './tsconfig.json'
}
}

View File

@ -0,0 +1,4 @@
*
!/lib/**
!CHANGELOG.md
/lib/**/__tests__/

View File

@ -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"
}

View File

@ -0,0 +1,32 @@
{
"name": "@anticrm/notification",
"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.11"
}
}

View File

@ -0,0 +1,46 @@
//
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021, 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 { Account, AttachedDoc, Class, Ref, Space, Timestamp } from '@anticrm/core'
import type { Plugin } from '@anticrm/platform'
import { plugin } from '@anticrm/platform'
/**
* @public
*/
export interface LastView extends AttachedDoc {
lastView: Timestamp
user: Ref<Account>
}
/**
* @public
*/
export const notificationId = 'notification' as Plugin
/**
* @public
*/
const notification = plugin(notificationId, {
class: {
LastView: '' as Ref<Class<LastView>>
},
space: {
Notifications: '' as Ref<Space>
}
})
export default notification

View File

@ -0,0 +1,9 @@
{
"extends": "./node_modules/@anticrm/platform-rig/profiles/default/tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib",
"lib": ["esnext", "dom"]
}
}

View File

@ -14,12 +14,24 @@
--> -->
<script lang="ts"> <script lang="ts">
import { Avatar } from '@anticrm/presentation' import { Avatar, createQuery } from '@anticrm/presentation'
import type { Candidate } from '@anticrm/recruit' import type { Candidate } from '@anticrm/recruit'
import { Channels } from '@anticrm/presentation' import { ChannelsView } from '@anticrm/contact-resources'
import { formatName } from '@anticrm/contact' import contact, { Channel, formatName } from '@anticrm/contact'
export let candidate: Candidate export let candidate: Candidate
let channels: Channel[] = []
const channelsQuery = createQuery()
channelsQuery.query(
contact.class.Channel,
{
attachedTo: candidate._id
},
(res) => {
channels = res
}
)
</script> </script>
<div class="flex-col h-full card-container"> <div class="flex-col h-full card-container">
@ -29,7 +41,7 @@
<div class="name">{formatName(candidate.name)}</div> <div class="name">{formatName(candidate.name)}</div>
<div class="description">{candidate.title ?? ''}</div> <div class="description">{candidate.title ?? ''}</div>
<div class="description">{candidate.city ?? ''}</div> <div class="description">{candidate.city ?? ''}</div>
<div class="footer"><Channels value={candidate.channels} size={'small'} /></div> <div class="footer"><ChannelsView value={channels} size={'small'} on:click /></div>
{/if} {/if}
</div> </div>

View File

@ -14,16 +14,15 @@
--> -->
<script lang="ts"> <script lang="ts">
import attachment from '@anticrm/attachment' import attachment from '@anticrm/attachment'
import contact, { Channel, ChannelProvider, combineName, Person } from '@anticrm/contact' import contact,{ Channel,ChannelProvider,combineName,Person } from '@anticrm/contact'
import { Channels } from '@anticrm/contact-resources' import { Channels } from '@anticrm/contact-resources'
import type { AttachedData, Data, MixinData, Ref } from '@anticrm/core' import { AttachedData, Data,generateId,MixinData,Ref } from '@anticrm/core'
import { generateId } from '@anticrm/core'
import login from '@anticrm/login' import login from '@anticrm/login'
import { getMetadata, getResource, setPlatformStatus, unknownError } from '@anticrm/platform' import { getMetadata,getResource,setPlatformStatus,unknownError } from '@anticrm/platform'
import { Card, EditableAvatar, getClient, getFileUrl, PDFViewer } from '@anticrm/presentation' import { Card,EditableAvatar,getClient,getFileUrl,PDFViewer } from '@anticrm/presentation'
import type { Candidate } from '@anticrm/recruit' import type { Candidate } from '@anticrm/recruit'
import { recognizeDocument } from '@anticrm/rekoni' import { recognizeDocument } from '@anticrm/rekoni'
import { EditBox, IconFile as FileIcon, Label, Link, showPopup, Spinner } from '@anticrm/ui' import { EditBox,IconFile as FileIcon,Label,Link,showPopup,Spinner } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte' import { createEventDispatcher } from 'svelte'
import recruit from '../plugin' import recruit from '../plugin'
import FileUpload from './icons/FileUpload.svelte' import FileUpload from './icons/FileUpload.svelte'

View File

@ -51,7 +51,7 @@
{#if object !== undefined && candidate !== undefined} {#if object !== undefined && candidate !== undefined}
<div class="flex-between"> <div class="flex-between">
<div class="card"><CandidateCard {candidate} /></div> <div class="card"><CandidateCard {candidate} on:click /></div>
<div class="arrows"><ExpandRightDouble /></div> <div class="arrows"><ExpandRightDouble /></div>
<div class="card"><VacancyCard {vacancy} /></div> <div class="card"><VacancyCard {vacancy} /></div>
</div> </div>

View File

@ -40,6 +40,7 @@
"@anticrm/setting": "~0.6.0", "@anticrm/setting": "~0.6.0",
"@anticrm/chunter": "~0.6.0", "@anticrm/chunter": "~0.6.0",
"@anticrm/login": "~0.6.1", "@anticrm/login": "~0.6.1",
"@anticrm/core": "~0.6.11" "@anticrm/core": "~0.6.11",
"@anticrm/notification-resources": "~0.6.0"
} }
} }

View File

@ -14,28 +14,34 @@
// limitations under the License. // limitations under the License.
--> -->
<script lang="ts"> <script lang="ts">
import contact,{ Channel,Contact,EmployeeAccount,formatName } from '@anticrm/contact' import contact, { Channel, Contact, EmployeeAccount, formatName } from '@anticrm/contact'
import { getCurrentAccount,Ref,SortingOrder,Space } from '@anticrm/core' import { getCurrentAccount, Ref, SortingOrder, Space } from '@anticrm/core'
import login from '@anticrm/login' import login from '@anticrm/login'
import { getMetadata } from '@anticrm/platform' import { getMetadata } from '@anticrm/platform'
import { createQuery,getClient } from '@anticrm/presentation' import { createQuery, getClient } from '@anticrm/presentation'
import setting from '@anticrm/setting' import setting from '@anticrm/setting'
import type { SharedTelegramMessage, TelegramMessage } from '@anticrm/telegram' import type { SharedTelegramMessage, TelegramMessage } from '@anticrm/telegram'
import { ReferenceInput } from '@anticrm/text-editor' import { ReferenceInput } from '@anticrm/text-editor'
import { ActionIcon,Button,IconShare,ScrollBox,showPopup } from '@anticrm/ui' import { ActionIcon, Button, IconShare, ScrollBox, showPopup } from '@anticrm/ui'
import telegram from '../plugin' import telegram from '../plugin'
import Connect from './Connect.svelte' import Connect from './Connect.svelte'
import TelegramIcon from './icons/Telegram.svelte' import TelegramIcon from './icons/Telegram.svelte'
import Messages from './Messages.svelte' import Messages from './Messages.svelte'
import { NotificationClient } from '@anticrm/notification-resources'
export let object: Contact export let object: Contact
let channel: Channel | undefined = undefined let channel: Channel | undefined = undefined
const client = getClient() const client = getClient()
const notificationClient = NotificationClient.getClient()
client.findOne(contact.class.Channel, { client
attachedTo: object._id, .findOne(contact.class.Channel, {
provider: contact.channelProvider.Telegram attachedTo: object._id,
}).then((res) => channel = res) provider: contact.channelProvider.Telegram
})
.then((res) => {
channel = res
})
let messages: TelegramMessage[] = [] let messages: TelegramMessage[] = []
let accounts: EmployeeAccount[] = [] let accounts: EmployeeAccount[] = []
@ -49,16 +55,31 @@
const settingsQuery = createQuery() const settingsQuery = createQuery()
const accountId = getCurrentAccount()._id const accountId = getCurrentAccount()._id
$: channel && messagesQuery.query(telegram.class.Message, { modifiedBy: accountId, attachedTo: channel._id }, (res) => { function updateMessagesQuery (channelId: Ref<Channel>): void {
messages = res messagesQuery.query(
}, { sort: { modifiedOn: SortingOrder.Ascending }}) telegram.class.Message,
{ attachedTo: channelId },
(res) => {
messages = res.reverse()
if (channel !== undefined) {
notificationClient.updateLastView(channel._id, channel._class)
}
const accountsIds = new Set(messages.map((p) => p.modifiedBy as Ref<EmployeeAccount>))
updateAccountsQuery(accountsIds)
},
{ sort: { modifiedOn: SortingOrder.Descending }, limit: 500 }
)
}
$: accountsIds = messages.map((p) => p.modifiedBy as Ref<EmployeeAccount>) $: channel && updateMessagesQuery(channel._id)
$: accauntsQuery.query(contact.class.EmployeeAccount, { _id: { $in: accountsIds }}, (result) => {
accounts = result
})
$: settingsQuery.query( function updateAccountsQuery (accountsIds: Set<Ref<EmployeeAccount>>): void {
accauntsQuery.query(contact.class.EmployeeAccount, { _id: { $in: Array.from(accountsIds) } }, (result) => {
accounts = result
})
}
settingsQuery.query(
setting.class.Integration, setting.class.Integration,
{ type: telegram.integrationType.Telegram, space: accountId as string as Ref<Space> }, { type: telegram.integrationType.Telegram, space: accountId as string as Ref<Space> },
(res) => { (res) => {
@ -67,7 +88,7 @@
) )
async function sendMsg (to: string, msg: string) { async function sendMsg (to: string, msg: string) {
return await fetch(url + '/send-msg', { const res = await fetch(url + '/send-msg', {
method: 'POST', method: 'POST',
headers: { headers: {
Authorization: 'Bearer ' + getMetadata(login.metadata.LoginToken), Authorization: 'Bearer ' + getMetadata(login.metadata.LoginToken),
@ -78,6 +99,10 @@
msg msg
}) })
}) })
if (channel !== undefined) {
await notificationClient.updateLastView(channel._id, channel._class, undefined, true)
}
return res
} }
async function addContact (phone: string) { async function addContact (phone: string) {
@ -137,6 +162,9 @@
messages: convertMessages(selectedMessages, accounts) messages: convertMessages(selectedMessages, accounts)
} }
) )
if (channel !== undefined) {
await notificationClient.updateLastView(channel._id, channel._class, channel.modifiedOn, true)
}
clear() clear()
} }
@ -199,7 +227,13 @@
<Button label={telegram.string.Cancel} size={'small'} on:click={clear} /> <Button label={telegram.string.Cancel} size={'small'} on:click={clear} />
</div> </div>
<div class="ml-3"> <div class="ml-3">
<Button label={telegram.string.PublishSelected} size={'small'} primary disabled={!selected.size} on:click={share} /> <Button
label={telegram.string.PublishSelected}
size={'small'}
primary
disabled={!selected.size}
on:click={share}
/>
</div> </div>
</div> </div>
</div> </div>

View File

@ -16,10 +16,23 @@
import type { SharedTelegramMessage } from '@anticrm/telegram' import type { SharedTelegramMessage } from '@anticrm/telegram'
export let message: SharedTelegramMessage export let message: SharedTelegramMessage
const current = new Date()
const target = new Date(message.modifiedOn)
let options: Intl.DateTimeFormatOptions = {
day: 'numeric',
month: 'long'
}
if (current.getFullYear() !== target.getFullYear()) {
options = {
...options,
year: '2-digit'
}
}
</script> </script>
<div class="datetime-container"> <div class="datetime-container">
{new Intl.DateTimeFormat('default', { day: 'numeric', month: 'long' }).format(message.modifiedOn)} {new Intl.DateTimeFormat('default', options).format(message.modifiedOn)}
</div> </div>
<style lang="scss"> <style lang="scss">

View File

@ -32,10 +32,10 @@
export let _id: Ref<Doc> export let _id: Ref<Doc>
export let _class: Ref<Class<Doc>> export let _class: Ref<Class<Doc>>
export let rightSection: AnyComponent | undefined = undefined
let object: Doc let object: Doc
let objectClass: Class<Doc> let objectClass: Class<Doc>
let parentClass: Ref<Class<Doc>> let parentClass: Ref<Class<Doc>>
let rightSection: AnyComponent | undefined
let fullSize: boolean = true let fullSize: boolean = true
const client = getClient() const client = getClient()

View File

@ -30,7 +30,7 @@ import core, {
} from '@anticrm/core' } from '@anticrm/core'
import type { IntlString } from '@anticrm/platform' import type { IntlString } from '@anticrm/platform'
import { getResource } from '@anticrm/platform' import { getResource } from '@anticrm/platform'
import { Channels, getAttributePresenterClass } from '@anticrm/presentation' import { getAttributePresenterClass } from '@anticrm/presentation'
import { ErrorPresenter, getPlatformColorForText } from '@anticrm/ui' import { ErrorPresenter, getPlatformColorForText } from '@anticrm/ui'
import type { Action, ActionTarget, BuildModelOptions, ObjectDDParticipant } from '@anticrm/view' import type { Action, ActionTarget, BuildModelOptions, ObjectDDParticipant } from '@anticrm/view'
import view, { AttributeModel, BuildModelKey } from '@anticrm/view' import view, { AttributeModel, BuildModelKey } from '@anticrm/view'

View File

@ -14,7 +14,7 @@
--> -->
<script lang="ts"> <script lang="ts">
import contact, { Employee, EmployeeAccount } from '@anticrm/contact' import contact, { Employee, EmployeeAccount } from '@anticrm/contact'
import core, { Client, getCurrentAccount, Ref, Space } from '@anticrm/core' import core, { Client, Doc, getCurrentAccount, Ref, Space } from '@anticrm/core'
import { Avatar, createQuery, setClient } from '@anticrm/presentation' import { Avatar, createQuery, setClient } from '@anticrm/presentation'
import { import {
AnyComponent, AnyComponent,
@ -235,7 +235,7 @@
</div> </div>
<!-- <div class="aside"><Chat thread/></div> --> <!-- <div class="aside"><Chat thread/></div> -->
</div> </div>
<PanelInstance/> <PanelInstance />
<Popup /> <Popup />
<TooltipInstance /> <TooltipInstance />
{:else} {:else}

View File

@ -985,6 +985,21 @@
"packageName": "@anticrm/rekoni", "packageName": "@anticrm/rekoni",
"projectFolder": "packages/rekoni", "projectFolder": "packages/rekoni",
"shouldPublish": true "shouldPublish": true
}, },
{
"packageName": "@anticrm/model-notification",
"projectFolder": "models/notification",
"shouldPublish": true
},
{
"packageName": "@anticrm/notification",
"projectFolder": "plugins/notification",
"shouldPublish": true
},
{
"packageName": "@anticrm/notification-resources",
"projectFolder": "plugins/notification-resources",
"shouldPublish": true
}
] ]
} }

View File

@ -199,7 +199,9 @@ class TServerStorage implements ServerStorage {
const _class = txClass(tx) const _class = txClass(tx)
const objClass = txObjectClass(tx) const objClass = txObjectClass(tx)
return await ctx.with('tx', { _class, objClass }, async (ctx) => { return await ctx.with('tx', { _class, objClass }, async (ctx) => {
await ctx.with('domain-tx', { _class, objClass }, async () => await this.getAdapter(DOMAIN_TX).tx(tx)) if (tx.objectSpace !== core.space.DerivedTx) {
await ctx.with('domain-tx', { _class, objClass }, async () => await this.getAdapter(DOMAIN_TX).tx(tx))
}
if (tx.objectSpace === core.space.Model) { if (tx.objectSpace === core.space.Model) {
// maintain hiearachy and triggers // maintain hiearachy and triggers