mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-08 02:37:44 +02:00
UBER-769 Implement backlinks in server plugin (#3626)
Signed-off-by: Alexander Onnikov <alexander.onnikov@xored.com>
This commit is contained in:
Generated
+73
-3
@@ -206,6 +206,7 @@ specifiers:
|
||||
'@rush-temp/templates-assets': file:./projects/templates-assets.tgz
|
||||
'@rush-temp/templates-resources': file:./projects/templates-resources.tgz
|
||||
'@rush-temp/tests-sanity': file:./projects/tests-sanity.tgz
|
||||
'@rush-temp/text': file:./projects/text.tgz
|
||||
'@rush-temp/text-editor': file:./projects/text-editor.tgz
|
||||
'@rush-temp/theme': file:./projects/theme.tgz
|
||||
'@rush-temp/tool': file:./projects/tool.tgz
|
||||
@@ -249,6 +250,7 @@ specifiers:
|
||||
'@tiptap/extension-task-item': ^2.0.3
|
||||
'@tiptap/extension-task-list': ^2.0.3
|
||||
'@tiptap/extension-typography': ^2.0.3
|
||||
'@tiptap/html': ^2.0.3
|
||||
'@tiptap/pm': ^2.0.3
|
||||
'@tiptap/prosemirror-tables': ^1.1.4
|
||||
'@tiptap/starter-kit': ^2.0.3
|
||||
@@ -601,6 +603,7 @@ dependencies:
|
||||
'@rush-temp/templates-assets': file:projects/templates-assets.tgz
|
||||
'@rush-temp/templates-resources': file:projects/templates-resources.tgz_a1d864769aaf53d09b76fe134ab55e60
|
||||
'@rush-temp/tests-sanity': file:projects/tests-sanity.tgz
|
||||
'@rush-temp/text': file:projects/text.tgz_a7abb371f17d2bc77abec7bc53398db5
|
||||
'@rush-temp/text-editor': file:projects/text-editor.tgz_1e646fd62a3adc9d0b41f256dafe3d38
|
||||
'@rush-temp/theme': file:projects/theme.tgz_a1d864769aaf53d09b76fe134ab55e60
|
||||
'@rush-temp/tool': file:projects/tool.tgz_bufferutil@4.0.7
|
||||
@@ -644,6 +647,7 @@ dependencies:
|
||||
'@tiptap/extension-task-item': 2.0.3_fc2f730f0ef238c57ef10dc1abce4607
|
||||
'@tiptap/extension-task-list': 2.0.3_@tiptap+core@2.0.3
|
||||
'@tiptap/extension-typography': 2.0.3_@tiptap+core@2.0.3
|
||||
'@tiptap/html': 2.1.6_fc2f730f0ef238c57ef10dc1abce4607
|
||||
'@tiptap/pm': 2.0.3_@tiptap+core@2.0.3
|
||||
'@tiptap/prosemirror-tables': 1.1.4_a7abb371f17d2bc77abec7bc53398db5
|
||||
'@tiptap/starter-kit': 2.0.3_@tiptap+pm@2.0.3
|
||||
@@ -6587,6 +6591,17 @@ packages:
|
||||
'@tiptap/core': 2.0.3_@tiptap+pm@2.0.3
|
||||
dev: false
|
||||
|
||||
/@tiptap/html/2.1.6_fc2f730f0ef238c57ef10dc1abce4607:
|
||||
resolution: {integrity: sha512-/7PcivMMty2Xw8oNEOqRi1W4WlD9afrmA3vlgNYGnO+knmwG5VzyhYV+oDlKAWvYqfrPxtfGnhNEQywzyHKbBg==}
|
||||
peerDependencies:
|
||||
'@tiptap/core': ^2.0.0
|
||||
'@tiptap/pm': ^2.0.0
|
||||
dependencies:
|
||||
'@tiptap/core': 2.0.3_@tiptap+pm@2.0.3
|
||||
'@tiptap/pm': 2.0.3_@tiptap+core@2.0.3
|
||||
zeed-dom: 0.9.26
|
||||
dev: false
|
||||
|
||||
/@tiptap/pm/2.0.3_@tiptap+core@2.0.3:
|
||||
resolution: {integrity: sha512-I9dsInD89Agdm1QjFRO9dmJtU1ldVSILNPW0pEhv9wYqYVvl4HUj/JMtYNqu2jWrCHNXQcaX/WkdSdvGJtmg5g==}
|
||||
peerDependencies:
|
||||
@@ -17210,6 +17225,13 @@ packages:
|
||||
commander: 2.20.3
|
||||
dev: false
|
||||
|
||||
/zeed-dom/0.9.26:
|
||||
resolution: {integrity: sha512-HWjX8rA3Y/RI32zby3KIN1D+mgskce+She4K7kRyyx62OiVxJ5FnYm8vWq0YVAja3Tf2S1M0XAc6O2lRFcMgcQ==}
|
||||
engines: {node: '>=14.13.1'}
|
||||
dependencies:
|
||||
css-what: 6.1.0
|
||||
dev: false
|
||||
|
||||
file:projects/account.tgz_bufferutil@4.0.7:
|
||||
resolution: {integrity: sha512-4tS8wBCO4BXrFfa11XEyJzCw6AG+7romshdy1O3kWS2by/z8IpsFcblvOk3Q3zaIExY6MSA0Wjdf+9od8vJMDA==, tarball: file:projects/account.tgz}
|
||||
id: file:projects/account.tgz
|
||||
@@ -20363,7 +20385,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/prod.tgz_91152849c259b1b3925f7bb3d415bc80:
|
||||
resolution: {integrity: sha512-dB1fl4vWrudVeRZJRFyOejr/xRHzIj4ZgkssAuS59WZIVVbyGvMgjPhC/tCGovi/O581wEJBBO1fgVLfOOXXiw==, tarball: file:projects/prod.tgz}
|
||||
resolution: {integrity: sha512-wZWIAN+CPlPommWoqqH2yf11giIQ+B7Gbx6NQ8hkQ3PeZS2zJWsN3Ki+4vYu+BOZHwthzEJn3Y9LhKsyTL9m2A==, tarball: file:projects/prod.tgz}
|
||||
id: file:projects/prod.tgz
|
||||
name: '@rush-temp/prod'
|
||||
version: 0.0.0
|
||||
@@ -20743,11 +20765,15 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/server-chunter-resources.tgz:
|
||||
resolution: {integrity: sha512-kNwG1PW31TnvKu/srs+aikf6idi3OZVIt5JTPnSBvGQ++A7ZUz/Cal5onvxUP72/zSfl2VIPSWUn6WViYx5xyg==, tarball: file:projects/server-chunter-resources.tgz}
|
||||
resolution: {integrity: sha512-/t2NvPZEV7xa90ZuDnc/Bi5KK9bXAozhOmghDjB0ztXFX9QRibqnObPNkR6lKP4QCHeihGFeow0gp5lqqb0DbQ==, tarball: file:projects/server-chunter-resources.tgz}
|
||||
name: '@rush-temp/server-chunter-resources'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
'@rushstack/heft': 0.47.11
|
||||
'@tiptap/core': 2.0.3_@tiptap+pm@2.0.3
|
||||
'@tiptap/html': 2.1.6_fc2f730f0ef238c57ef10dc1abce4607
|
||||
'@tiptap/pm': 2.0.3_@tiptap+core@2.0.3
|
||||
'@tiptap/starter-kit': 2.0.3_@tiptap+pm@2.0.3
|
||||
'@types/heft-jest': 1.0.3
|
||||
'@typescript-eslint/eslint-plugin': 5.42.1_d506b9be61cb4ac2646ecbc6e0680464
|
||||
'@typescript-eslint/parser': 5.42.1_eslint@8.27.0+typescript@4.8.4
|
||||
@@ -22052,7 +22078,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/text-editor.tgz_1e646fd62a3adc9d0b41f256dafe3d38:
|
||||
resolution: {integrity: sha512-veer1goACGzBNOXgkYDbqPx/5obONA8llhpWAv2M+M/I2tumjLZxG4ohodKBQNa0ijAZ+MUgRlcaS9c8MsKF6A==, tarball: file:projects/text-editor.tgz}
|
||||
resolution: {integrity: sha512-Omwv7uuoSX4eicxv4jQUEegyhpg0QWeFuCr+DxCR3j7yfsFfF5mUCleLcX7LWh+M9EXbdldBdcHE3OriJg5ECQ==, tarball: file:projects/text-editor.tgz}
|
||||
id: file:projects/text-editor.tgz
|
||||
name: '@rush-temp/text-editor'
|
||||
version: 0.0.0
|
||||
@@ -22128,6 +22154,50 @@ packages:
|
||||
- y-protocols
|
||||
dev: false
|
||||
|
||||
file:projects/text.tgz_a7abb371f17d2bc77abec7bc53398db5:
|
||||
resolution: {integrity: sha512-QfLKyYxcLXLoqBMnbP4k2ND5871b+ycKCz7+NZ7xBtPtEb3Ee6jPdFrf8wbmnIYX/dLV0VThNKXcJ4ZiWxNvbA==, tarball: file:projects/text.tgz}
|
||||
id: file:projects/text.tgz
|
||||
name: '@rush-temp/text'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
'@rushstack/heft': 0.47.11
|
||||
'@tiptap/core': 2.0.3_@tiptap+pm@2.0.3
|
||||
'@tiptap/extension-gapcursor': 2.0.3_fc2f730f0ef238c57ef10dc1abce4607
|
||||
'@tiptap/extension-heading': 2.0.3_@tiptap+core@2.0.3
|
||||
'@tiptap/extension-highlight': 2.0.3_@tiptap+core@2.0.3
|
||||
'@tiptap/extension-link': 2.0.3_fc2f730f0ef238c57ef10dc1abce4607
|
||||
'@tiptap/extension-mention': 2.0.3_dc69df04ca9f1acb766e21a98969ce2c
|
||||
'@tiptap/extension-table': 2.0.3_fc2f730f0ef238c57ef10dc1abce4607
|
||||
'@tiptap/extension-table-cell': 2.0.3_@tiptap+core@2.0.3
|
||||
'@tiptap/extension-table-header': 2.0.3_@tiptap+core@2.0.3
|
||||
'@tiptap/extension-table-row': 2.0.3_@tiptap+core@2.0.3
|
||||
'@tiptap/extension-task-item': 2.0.3_fc2f730f0ef238c57ef10dc1abce4607
|
||||
'@tiptap/extension-task-list': 2.0.3_@tiptap+core@2.0.3
|
||||
'@tiptap/extension-typography': 2.0.3_@tiptap+core@2.0.3
|
||||
'@tiptap/html': 2.1.6_fc2f730f0ef238c57ef10dc1abce4607
|
||||
'@tiptap/pm': 2.0.3_@tiptap+core@2.0.3
|
||||
'@tiptap/prosemirror-tables': 1.1.4_a7abb371f17d2bc77abec7bc53398db5
|
||||
'@tiptap/starter-kit': 2.0.3_@tiptap+pm@2.0.3
|
||||
'@tiptap/suggestion': 2.0.3_fc2f730f0ef238c57ef10dc1abce4607
|
||||
'@types/heft-jest': 1.0.3
|
||||
'@typescript-eslint/eslint-plugin': 5.42.1_d506b9be61cb4ac2646ecbc6e0680464
|
||||
'@typescript-eslint/parser': 5.42.1_eslint@8.27.0+typescript@4.8.4
|
||||
eslint: 8.27.0
|
||||
eslint-config-standard-with-typescript: 23.0.0_c9fe9619f50f4e82337a86c3af25e566
|
||||
eslint-plugin-import: 2.26.0_eslint@8.27.0
|
||||
eslint-plugin-n: 15.5.1_eslint@8.27.0
|
||||
eslint-plugin-promise: 6.1.1_eslint@8.27.0
|
||||
prettier: 2.8.8
|
||||
typescript: 4.8.4
|
||||
transitivePeerDependencies:
|
||||
- prosemirror-keymap
|
||||
- prosemirror-model
|
||||
- prosemirror-state
|
||||
- prosemirror-transform
|
||||
- prosemirror-view
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
file:projects/theme.tgz_a1d864769aaf53d09b76fe134ab55e60:
|
||||
resolution: {integrity: sha512-wnu6qKDGjkOGG0e/hcmvG2yHZv9oOFqO1UDrxFCa/+/oMuTnaRsSscLUjPiYWE3iHDFXZNaNJzVrZc7tCwXXQw==, tarball: file:projects/theme.tgz}
|
||||
id: file:projects/theme.tgz
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
"@hcengineering/platform": "^0.6.9",
|
||||
"@hcengineering/ui": "^0.6.10",
|
||||
"@hcengineering/theme": "^0.6.3",
|
||||
"@hcengineering/text": "^0.6.0",
|
||||
"svelte": "3.55.1",
|
||||
"@hcengineering/login": "^0.6.7",
|
||||
"@hcengineering/login-assets": "^0.6.0",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// Copyright © 2022 Hardcore Engineering Inc.
|
||||
// Copyright © 2022, 2023 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
@@ -41,6 +41,10 @@ export function createModel (builder: Builder): void {
|
||||
}
|
||||
)
|
||||
|
||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
trigger: serverChunter.trigger.BacklinkTrigger
|
||||
})
|
||||
|
||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
trigger: serverChunter.trigger.ChunterTrigger
|
||||
})
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
"@hcengineering/platform": "^0.6.9",
|
||||
"@hcengineering/core": "^0.6.27",
|
||||
"@hcengineering/ui": "^0.6.10",
|
||||
"@hcengineering/text": "^0.6.0",
|
||||
"svelte": "3.55.1",
|
||||
"@tiptap/core": "^2.0.3",
|
||||
"@tiptap/pm": "^2.0.3",
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
extends: ['./node_modules/@hcengineering/platform-rig/profiles/default/config/eslint.config.json'],
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: './tsconfig.json'
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
|
||||
"rigPackageName": "@hcengineering/platform-rig",
|
||||
"rigProfile": "default"
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"name": "@hcengineering/text",
|
||||
"version": "0.6.0",
|
||||
"main": "lib/index.js",
|
||||
"author": "Anticrm Platform Contributors",
|
||||
"license": "EPL-2.0",
|
||||
"scripts": {
|
||||
"build": "heft build",
|
||||
"test": "heft test",
|
||||
"build:watch": "tsc",
|
||||
"lint:fix": "eslint --fix src",
|
||||
"lint": "eslint src",
|
||||
"format": "prettier --write src && eslint --fix src"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@hcengineering/platform-rig": "^0.6.0",
|
||||
"@types/heft-jest": "^1.0.3",
|
||||
"@typescript-eslint/eslint-plugin": "^5.41.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"eslint-plugin-n": "^15.4.0",
|
||||
"eslint": "^8.26.0",
|
||||
"@typescript-eslint/parser": "^5.41.0",
|
||||
"eslint-config-standard-with-typescript": "^23.0.0",
|
||||
"prettier": "^2.7.1",
|
||||
"@rushstack/heft": "^0.47.9",
|
||||
"typescript": "^4.3.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tiptap/core": "^2.0.3",
|
||||
"@tiptap/html": "^2.0.3",
|
||||
"@tiptap/pm": "^2.0.3",
|
||||
"@tiptap/starter-kit": "^2.0.3",
|
||||
"@tiptap/extension-gapcursor": "^2.0.3",
|
||||
"@tiptap/extension-heading": "^2.0.3",
|
||||
"@tiptap/extension-highlight": "^2.0.3",
|
||||
"@tiptap/extension-link": "^2.0.3",
|
||||
"@tiptap/extension-mention": "^2.0.3",
|
||||
"@tiptap/extension-table": "^2.0.3",
|
||||
"@tiptap/extension-table-cell": "^2.0.3",
|
||||
"@tiptap/extension-table-header": "^2.0.3",
|
||||
"@tiptap/extension-table-row": "^2.0.3",
|
||||
"@tiptap/extension-task-item": "^2.0.3",
|
||||
"@tiptap/extension-task-list": "^2.0.3",
|
||||
"@tiptap/extension-typography": "^2.0.3",
|
||||
"@tiptap/suggestion": "^2.0.3",
|
||||
"@tiptap/prosemirror-tables": "^1.1.4"
|
||||
},
|
||||
"repository": "https://github.com/hcenginneing/anticrm",
|
||||
"publishConfig": {
|
||||
"registry": "https://npm.pkg.github.com"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
//
|
||||
// Copyright © 2023 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { AnyExtension } from '@tiptap/core'
|
||||
import { Level } from '@tiptap/extension-heading'
|
||||
import Highlight from '@tiptap/extension-highlight'
|
||||
import Link from '@tiptap/extension-link'
|
||||
import Table from '@tiptap/extension-table'
|
||||
import TableCell from '@tiptap/extension-table-cell'
|
||||
import TableHeader from '@tiptap/extension-table-header'
|
||||
import TableRow from '@tiptap/extension-table-row'
|
||||
import TaskItem from '@tiptap/extension-task-item'
|
||||
import TaskList from '@tiptap/extension-task-list'
|
||||
import Typography from '@tiptap/extension-typography'
|
||||
import StarterKit from '@tiptap/starter-kit'
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const headingLevels: Level[] = [1, 2, 3, 4, 5, 6]
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const tableExtensions = [
|
||||
Table.configure({
|
||||
resizable: false,
|
||||
HTMLAttributes: {
|
||||
class: 'proseTable'
|
||||
}
|
||||
}),
|
||||
TableRow.configure({}),
|
||||
TableHeader.configure({}),
|
||||
TableCell.configure({})
|
||||
]
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const taskListExtensions = [
|
||||
TaskList,
|
||||
TaskItem.configure({
|
||||
nested: true,
|
||||
HTMLAttributes: {
|
||||
class: 'flex flex-grow gap-1 checkbox_style'
|
||||
}
|
||||
})
|
||||
]
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const defaultExtensions: AnyExtension[] = [
|
||||
StarterKit.configure({
|
||||
code: {
|
||||
HTMLAttributes: {
|
||||
class: 'proseCode'
|
||||
}
|
||||
},
|
||||
codeBlock: {
|
||||
languageClassPrefix: 'language-',
|
||||
exitOnArrowDown: true,
|
||||
exitOnTripleEnter: true,
|
||||
HTMLAttributes: {
|
||||
class: 'proseCodeBlock'
|
||||
}
|
||||
},
|
||||
heading: {
|
||||
levels: headingLevels
|
||||
}
|
||||
}),
|
||||
Highlight.configure({
|
||||
multicolor: false
|
||||
}),
|
||||
Typography.configure({}),
|
||||
Link.configure({
|
||||
openOnClick: true,
|
||||
HTMLAttributes: { class: 'cursor-pointer', rel: 'noopener noreferrer', target: '_blank' }
|
||||
}),
|
||||
...tableExtensions,
|
||||
...taskListExtensions
|
||||
]
|
||||
@@ -0,0 +1,35 @@
|
||||
//
|
||||
// Copyright © 2023 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { Extensions, getSchema } from '@tiptap/core'
|
||||
import { generateJSON, generateHTML } from '@tiptap/html'
|
||||
import { Node as ProseMirrorNode } from '@tiptap/pm/model'
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export function getHTML (node: ProseMirrorNode, extensions: Extensions): string {
|
||||
return generateHTML(node.toJSON(), extensions)
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export function parseHTML (content: string, extensions: Extensions): ProseMirrorNode {
|
||||
const schema = getSchema(extensions)
|
||||
const json = generateJSON(content, extensions)
|
||||
|
||||
return ProseMirrorNode.fromJSON(schema, json)
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
//
|
||||
// Copyright © 2023 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
export * from './extensions'
|
||||
export * from './html'
|
||||
export * from './nodes'
|
||||
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// Copyright © 2023 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
export * from './reference'
|
||||
@@ -0,0 +1,88 @@
|
||||
//
|
||||
// Copyright © 2023 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { Node, mergeAttributes } from '@tiptap/core'
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const ReferenceNode = Node.create({
|
||||
name: 'reference',
|
||||
group: 'inline',
|
||||
content: 'inline*',
|
||||
inline: true,
|
||||
|
||||
addAttributes () {
|
||||
return {
|
||||
id: {
|
||||
default: null,
|
||||
parseHTML: (element) => element.getAttribute('data-id'),
|
||||
renderHTML: (attributes) => {
|
||||
if (attributes.id === null) {
|
||||
return {}
|
||||
}
|
||||
|
||||
return {
|
||||
'data-id': attributes.id
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
objectClass: {
|
||||
default: null,
|
||||
parseHTML: (element) => element.getAttribute('data-objectclass'),
|
||||
renderHTML: (attributes) => {
|
||||
if (attributes.objectClass === null) {
|
||||
return {}
|
||||
}
|
||||
|
||||
return {
|
||||
'data-objectclass': attributes.objectClass
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
label: {
|
||||
default: null,
|
||||
parseHTML: (element) => element.getAttribute('data-label'),
|
||||
renderHTML: (attributes) => {
|
||||
if (attributes.label === null) {
|
||||
return {}
|
||||
}
|
||||
|
||||
return {
|
||||
'data-label': attributes.label
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
class: {
|
||||
default: null
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
parseHTML () {
|
||||
return [
|
||||
{
|
||||
tag: `span[data-type="${this.name}"]`
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
renderHTML ({ HTMLAttributes }) {
|
||||
return ['span', mergeAttributes({ 'data-type': this.name }, HTMLAttributes), 0]
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "./node_modules/@hcengineering/platform-rig/profiles/default/tsconfig.json",
|
||||
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./lib"
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Class, Doc, Markup, Ref, updateAttribute } from '@hcengineering/core'
|
||||
import { Class, Doc, Ref, updateAttribute } from '@hcengineering/core'
|
||||
|
||||
import { IntlString } from '@hcengineering/platform'
|
||||
import { createQuery, getAttribute, getClient, KeyedAttribute } from '@hcengineering/presentation'
|
||||
@@ -27,7 +27,6 @@
|
||||
export let key: KeyedAttribute
|
||||
export let placeholder: IntlString
|
||||
export let focusIndex = -1
|
||||
export let updateBacklinks: ((doc: Doc, description: Markup) => void) | undefined = undefined
|
||||
let _id: Ref<Doc> | undefined = undefined
|
||||
let _class: Ref<Class<Doc>> | undefined = undefined
|
||||
const client = getClient()
|
||||
@@ -69,7 +68,6 @@
|
||||
setTimeout(() => {
|
||||
dispatch('saved', false)
|
||||
}, 2500)
|
||||
updateBacklinks?.(object, description)
|
||||
}
|
||||
|
||||
await descriptionBox.createAttachments()
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
"Content": "Content",
|
||||
"Comment": "Comment",
|
||||
"Message": "Message",
|
||||
"MessageOn": "Message on",
|
||||
"Reference": "Reference",
|
||||
"Chat": "Chat",
|
||||
"In": "In",
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
"Content": "Содержимое",
|
||||
"Comment": "Комментарий",
|
||||
"Message": "Сообщение",
|
||||
"MessageOn": "Сообщение в",
|
||||
"MentionNotification": "Упомянул",
|
||||
"Reference": "Ссылка",
|
||||
"Chat": "Чат",
|
||||
|
||||
@@ -1,56 +1,7 @@
|
||||
import { Backlink, getBacklinks } from '@hcengineering/chunter'
|
||||
import contact, { PersonAccount } from '@hcengineering/contact'
|
||||
import { Account, Class, Client, Data, Doc, DocumentQuery, Ref, TxOperations } from '@hcengineering/core'
|
||||
import { Backlink } from '@hcengineering/chunter'
|
||||
import { Data, DocumentQuery, TxOperations } from '@hcengineering/core'
|
||||
import chunter from './plugin'
|
||||
|
||||
export async function getUser (
|
||||
client: Client,
|
||||
user: Ref<PersonAccount> | Ref<Account>
|
||||
): Promise<PersonAccount | undefined> {
|
||||
return await client.findOne(contact.class.PersonAccount, { _id: user as Ref<PersonAccount> })
|
||||
}
|
||||
|
||||
export function getTime (time: number): string {
|
||||
let options: Intl.DateTimeFormatOptions = { hour: 'numeric', minute: 'numeric' }
|
||||
if (!isToday(time)) {
|
||||
options = {
|
||||
month: 'numeric',
|
||||
day: 'numeric',
|
||||
...options
|
||||
}
|
||||
}
|
||||
|
||||
return new Date(time).toLocaleString('default', options)
|
||||
}
|
||||
|
||||
export function isToday (time: number): boolean {
|
||||
const current = new Date()
|
||||
const target = new Date(time)
|
||||
return (
|
||||
current.getDate() === target.getDate() &&
|
||||
current.getMonth() === target.getMonth() &&
|
||||
current.getFullYear() === target.getFullYear()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function updateBacklinks (
|
||||
client: TxOperations,
|
||||
backlinkId: Ref<Doc>,
|
||||
backlinkClass: Ref<Class<Doc>>,
|
||||
attachedDocId: Ref<Doc> | undefined,
|
||||
content: string
|
||||
): Promise<void> {
|
||||
const q: DocumentQuery<Backlink> = { backlinkId, backlinkClass, collection: 'backlinks' }
|
||||
if (attachedDocId !== undefined) {
|
||||
q.attachedDocId = attachedDocId
|
||||
}
|
||||
const backlinks = getBacklinks(backlinkId, backlinkClass, attachedDocId, content)
|
||||
|
||||
await updateBacklinksList(client, q, backlinks)
|
||||
}
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<script lang="ts">
|
||||
import attachment, { Attachment } from '@hcengineering/attachment'
|
||||
import { AttachmentRefInput } from '@hcengineering/attachment-resources'
|
||||
import { ChunterMessage, ChunterSpace, Message, createBacklinks } from '@hcengineering/chunter'
|
||||
import { ChunterMessage, ChunterSpace, Message } from '@hcengineering/chunter'
|
||||
import { Ref, Space, generateId, getCurrentAccount } from '@hcengineering/core'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { getLocation, navigate } from '@hcengineering/ui'
|
||||
@@ -48,9 +48,6 @@
|
||||
_id
|
||||
)
|
||||
|
||||
// Create an backlink to document
|
||||
await createBacklinks(client, space, chunter.class.ChunterSpace, _id, message)
|
||||
|
||||
_id = generateId()
|
||||
isScrollForced = true
|
||||
loading = false
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { AttachmentRefInput } from '@hcengineering/attachment-resources'
|
||||
import { Comment, createBacklinks } from '@hcengineering/chunter'
|
||||
import { Comment } from '@hcengineering/chunter'
|
||||
import { AttachedData, Doc, generateId, Ref } from '@hcengineering/core'
|
||||
import { createQuery, DraftController, draftsStore, getClient } from '@hcengineering/presentation'
|
||||
import chunter from '../plugin'
|
||||
@@ -105,9 +105,6 @@
|
||||
_id
|
||||
)
|
||||
|
||||
// Create an backlink to document
|
||||
await createBacklinks(client, object._id, object._class, _id, message)
|
||||
|
||||
// Remove draft from Local Storage
|
||||
comment = getDefault()
|
||||
_id = comment._id
|
||||
|
||||
@@ -19,10 +19,11 @@
|
||||
import chunter from '@hcengineering/chunter'
|
||||
import { Person, PersonAccount, getName } from '@hcengineering/contact'
|
||||
import { Avatar, personByIdStore, personAccountByIdStore } from '@hcengineering/contact-resources'
|
||||
import { IdMap, Ref } from '@hcengineering/core'
|
||||
import { MessageViewer, getClient } from '@hcengineering/presentation'
|
||||
import { Icon, ShowMore, TimeSince } from '@hcengineering/ui'
|
||||
import { LinkPresenter } from '@hcengineering/view-resources'
|
||||
import { Doc, IdMap, Ref } from '@hcengineering/core'
|
||||
import { MessageViewer, createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { Icon, Label, ShowMore, TimeSince } from '@hcengineering/ui'
|
||||
import { AttributeModel } from '@hcengineering/view'
|
||||
import { LinkPresenter, getObjectPresenter } from '@hcengineering/view-resources'
|
||||
|
||||
export let value: Comment
|
||||
export let inline: boolean = false
|
||||
@@ -48,6 +49,17 @@
|
||||
|
||||
$: links = getLinks(value.message)
|
||||
|
||||
let presenter: AttributeModel | undefined
|
||||
getObjectPresenter(client, value.attachedToClass, { key: '' }).then((p) => {
|
||||
presenter = p
|
||||
})
|
||||
|
||||
let doc: Doc | undefined = undefined
|
||||
const docQuery = createQuery()
|
||||
$: docQuery.query(value.attachedToClass, { _id: value.attachedTo }, (res) => {
|
||||
;[doc] = res
|
||||
})
|
||||
|
||||
function getLinks (content: string): HTMLLinkElement[] {
|
||||
const parser = new DOMParser()
|
||||
const parent = parser.parseFromString(content, 'text/html').firstChild?.childNodes[1] as HTMLElement
|
||||
@@ -69,13 +81,21 @@
|
||||
</script>
|
||||
|
||||
{#if inline}
|
||||
<a class="flex-presenter inline-presenter" href="#{disabled ? null : ''}">
|
||||
<div class="icon">
|
||||
<Icon icon={chunter.icon.Thread} size={'small'} />
|
||||
</div>
|
||||
<span class="label nowrap">Message</span>
|
||||
</a>
|
||||
<span class="content-dark-color">#{cutId(value._id.toString())}</span>
|
||||
<div class="flex-presenter inline-presenter">
|
||||
{#if presenter && doc}
|
||||
<span class="labels-row" style:text-transform={'lowercase'}><Label label={chunter.string.MessageOn} /></span>
|
||||
|
||||
<div class="icon">
|
||||
<Icon icon={chunter.icon.Thread} size={'small'} />
|
||||
</div>
|
||||
|
||||
<svelte:component this={presenter.presenter} value={doc} {inline} {disabled} />
|
||||
{:else}
|
||||
<Label label={chunter.string.Message} />
|
||||
|
||||
<span class="content-dark-color">#{cutId(value._id.toString())}</span>
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex-row-top">
|
||||
{#await getEmployee(value, $personByIdStore, $personAccountByIdStore) then employee}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { AttachmentRefInput } from '@hcengineering/attachment-resources'
|
||||
import chunter, { DirectMessage, Message, createBacklinks, getDirectChannel } from '@hcengineering/chunter'
|
||||
import chunter, { DirectMessage, Message, getDirectChannel } from '@hcengineering/chunter'
|
||||
import { PersonAccount } from '@hcengineering/contact'
|
||||
import { Ref, generateId, getCurrentAccount } from '@hcengineering/core'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
@@ -46,8 +46,6 @@
|
||||
messageId
|
||||
)
|
||||
|
||||
await createBacklinks(client, space, chunter.class.ChunterSpace, messageId, message)
|
||||
|
||||
messageId = generateId()
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -13,10 +13,44 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Message } from '@hcengineering/chunter'
|
||||
import { MessageViewer } from '@hcengineering/presentation'
|
||||
import chunter, { Message } from '@hcengineering/chunter'
|
||||
import { Doc } from '@hcengineering/core'
|
||||
import { MessageViewer, createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { Icon, Label } from '@hcengineering/ui'
|
||||
import { AttributeModel } from '@hcengineering/view'
|
||||
import { getObjectPresenter } from '@hcengineering/view-resources'
|
||||
|
||||
export let value: Message
|
||||
export let inline: boolean = false
|
||||
export let disabled = false
|
||||
|
||||
const client = getClient()
|
||||
|
||||
let presenter: AttributeModel | undefined
|
||||
getObjectPresenter(client, value.attachedToClass, { key: '' }).then((p) => {
|
||||
presenter = p
|
||||
})
|
||||
|
||||
let doc: Doc | undefined = undefined
|
||||
const docQuery = createQuery()
|
||||
$: docQuery.query(value.attachedToClass, { _id: value.attachedTo }, (res) => {
|
||||
;[doc] = res
|
||||
})
|
||||
</script>
|
||||
|
||||
<div><MessageViewer message={value.content} /></div>
|
||||
{#if inline}
|
||||
{#if presenter && doc}
|
||||
<div class="flex-presenter inline-presenter">
|
||||
<div class="icon">
|
||||
<Icon icon={chunter.icon.Thread} size={'small'} />
|
||||
</div>
|
||||
<span class="labels-row" style:text-transform={'lowercase'}>
|
||||
<Label label={chunter.string.MessageOn} />
|
||||
</span>
|
||||
|
||||
<svelte:component this={presenter.presenter} value={doc} inline {disabled} />
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<div><MessageViewer message={value.content} /></div>
|
||||
{/if}
|
||||
|
||||
@@ -15,14 +15,13 @@
|
||||
<script lang="ts">
|
||||
import attachment, { Attachment } from '@hcengineering/attachment'
|
||||
import { AttachmentRefInput } from '@hcengineering/attachment-resources'
|
||||
import { createBacklinks, type ChunterSpace, type Message, type ThreadMessage } from '@hcengineering/chunter'
|
||||
import { type ChunterSpace, type Message, type ThreadMessage } from '@hcengineering/chunter'
|
||||
import contact, { Person, PersonAccount, getName } from '@hcengineering/contact'
|
||||
import { personByIdStore } from '@hcengineering/contact-resources'
|
||||
import core, { FindOptions, IdMap, Ref, SortingOrder, generateId, getCurrentAccount } from '@hcengineering/core'
|
||||
import { NotificationClientImpl } from '@hcengineering/notification-resources'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { Label } from '@hcengineering/ui'
|
||||
|
||||
import chunter from '../plugin'
|
||||
import ChannelPresenter from './ChannelPresenter.svelte'
|
||||
import DmPresenter from './DmPresenter.svelte'
|
||||
@@ -136,9 +135,6 @@
|
||||
commentId
|
||||
)
|
||||
|
||||
// Create an backlink to document
|
||||
await createBacklinks(client, parent._id, parent._class, commentId, message)
|
||||
|
||||
commentId = generateId()
|
||||
loading = false
|
||||
}
|
||||
|
||||
@@ -15,14 +15,13 @@
|
||||
<script lang="ts">
|
||||
import attachment, { Attachment } from '@hcengineering/attachment'
|
||||
import { AttachmentRefInput } from '@hcengineering/attachment-resources'
|
||||
import { createBacklinks, type ChunterMessage, type Message, type ThreadMessage } from '@hcengineering/chunter'
|
||||
import { type ChunterMessage, type Message, type ThreadMessage } from '@hcengineering/chunter'
|
||||
import core, { Doc, Ref, Space, generateId, getCurrentAccount } from '@hcengineering/core'
|
||||
import { DocUpdates } from '@hcengineering/notification'
|
||||
import { NotificationClientImpl } from '@hcengineering/notification-resources'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { IconClose, Label, getCurrentResolvedLocation, navigate } from '@hcengineering/ui'
|
||||
import { afterUpdate, beforeUpdate, createEventDispatcher } from 'svelte'
|
||||
|
||||
import chunter from '../plugin'
|
||||
import { isMessageHighlighted, messageIdForScroll, scrollAndHighLight, shouldScrollToMessage } from '../utils'
|
||||
import ChannelSeparator from './ChannelSeparator.svelte'
|
||||
@@ -149,9 +148,6 @@
|
||||
commentId
|
||||
)
|
||||
|
||||
// Create an backlink to document
|
||||
await createBacklinks(client, _id, chunter.class.Message, commentId, message)
|
||||
|
||||
commentId = generateId()
|
||||
isScrollForced = true
|
||||
loading = false
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
import { AttachmentDocList } from '@hcengineering/attachment-resources'
|
||||
import { Button } from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { updateBacklinks } from '../../backlinks'
|
||||
import chunter from '../../plugin'
|
||||
import { AttachmentRefInput } from '@hcengineering/attachment-resources'
|
||||
import { LinkPresenter } from '@hcengineering/view-resources'
|
||||
@@ -49,8 +48,6 @@
|
||||
attachments
|
||||
}
|
||||
)
|
||||
// We need to update backlinks before and after.
|
||||
await updateBacklinks(client, value.attachedTo, value.attachedToClass, value._id, message)
|
||||
} finally {
|
||||
loading = false
|
||||
}
|
||||
|
||||
@@ -67,7 +67,6 @@ import { getDmName, getLink, getTitle, resolveLocation } from './utils'
|
||||
export { default as Header } from './components/Header.svelte'
|
||||
export { classIcon } from './utils'
|
||||
export { CommentPopup, CommentsPresenter }
|
||||
export { updateBacklinks } from './backlinks'
|
||||
|
||||
async function MarkUnread (object: Message): Promise<void> {
|
||||
const client = NotificationClientImpl.getClient()
|
||||
|
||||
@@ -180,6 +180,7 @@ export default plugin(chunterId, {
|
||||
UnarchiveChannel: '' as IntlString,
|
||||
ArchiveConfirm: '' as IntlString,
|
||||
Message: '' as IntlString,
|
||||
MessageOn: '' as IntlString,
|
||||
UnarchiveConfirm: '' as IntlString,
|
||||
ConvertToPrivate: '' as IntlString
|
||||
},
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { AttachmentStyleBoxEditor } from '@hcengineering/attachment-resources'
|
||||
import { updateBacklinks } from '@hcengineering/chunter-resources'
|
||||
import core, { ClassifierKind, Data, Doc, Mixin, Ref } from '@hcengineering/core'
|
||||
import notification from '@hcengineering/notification'
|
||||
import { Panel } from '@hcengineering/panel'
|
||||
@@ -193,9 +192,6 @@
|
||||
on:saved={(evt) => {
|
||||
saved = evt.detail
|
||||
}}
|
||||
updateBacklinks={(doc, description) => {
|
||||
updateBacklinks(client, doc._id, doc._class, doc._id, description)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
<script lang="ts">
|
||||
import { AttachmentRefInput } from '@hcengineering/attachment-resources'
|
||||
import chunter, { Comment } from '@hcengineering/chunter'
|
||||
import { updateBacklinks } from '@hcengineering/chunter-resources'
|
||||
import { PersonAccount } from '@hcengineering/contact'
|
||||
import { AttachedData, getCurrentAccount, Ref } from '@hcengineering/core'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
@@ -69,8 +68,6 @@
|
||||
|
||||
await client.createMixin(_id, chunter.class.Comment, value.space, request.mixin.RequestDecisionComment, {})
|
||||
|
||||
// We need to update backlinks before and after.
|
||||
await updateBacklinks(client, value.attachedTo, value.attachedToClass, value._id, message)
|
||||
refInput.createAttachments()
|
||||
loading = false
|
||||
}
|
||||
@@ -81,8 +78,6 @@
|
||||
attachments
|
||||
})
|
||||
|
||||
// We need to update backlinks before and after.
|
||||
await updateBacklinks(client, value.attachedTo, value.attachedToClass, value._id, message)
|
||||
loading = false
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { AttachmentStyledBox } from '@hcengineering/attachment-resources'
|
||||
import chunter, { createBacklinks } from '@hcengineering/chunter'
|
||||
import chunter from '@hcengineering/chunter'
|
||||
import { Employee } from '@hcengineering/contact'
|
||||
import core, { Account, AttachedData, Doc, fillDefaults, generateId, Ref, SortingOrder } from '@hcengineering/core'
|
||||
import { getResource, translate } from '@hcengineering/platform'
|
||||
@@ -429,9 +429,6 @@
|
||||
}
|
||||
)
|
||||
|
||||
// Create an backlink to document
|
||||
await createBacklinks(client, _id, tracker.class.Issue, _id, object.description)
|
||||
|
||||
draftController.remove()
|
||||
resetObject()
|
||||
descriptionBox?.removeDraft(false)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { AttachmentStyleBoxEditor } from '@hcengineering/attachment-resources'
|
||||
import { updateBacklinks } from '@hcengineering/chunter-resources'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { Component } from '@hcengineering/tracker'
|
||||
import { EditBox } from '@hcengineering/ui'
|
||||
@@ -52,8 +51,5 @@
|
||||
key={{ key: 'description', attr: descriptionKey }}
|
||||
bind:this={descriptionBox}
|
||||
placeholder={tracker.string.IssueDescriptionPlaceholder}
|
||||
updateBacklinks={(doc, description) => {
|
||||
updateBacklinks(client, doc._id, doc._class, doc._id, description)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -46,7 +46,6 @@
|
||||
import CopyToClipboard from './CopyToClipboard.svelte'
|
||||
import SubIssueSelector from './SubIssueSelector.svelte'
|
||||
import SubIssues from './SubIssues.svelte'
|
||||
import { updateBacklinks } from '@hcengineering/chunter-resources'
|
||||
|
||||
export let _id: Ref<Issue>
|
||||
export let _class: Ref<Class<Issue>>
|
||||
@@ -227,9 +226,6 @@
|
||||
on:saved={(evt) => {
|
||||
saved = evt.detail
|
||||
}}
|
||||
updateBacklinks={(doc, description) => {
|
||||
updateBacklinks(client, doc._id, doc._class, doc._id, description)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
import { createEventDispatcher, onMount } from 'svelte'
|
||||
import tracker from '../../plugin'
|
||||
import { AttachmentStyleBoxEditor } from '@hcengineering/attachment-resources'
|
||||
import { updateBacklinks } from '@hcengineering/chunter-resources'
|
||||
|
||||
export let object: Milestone
|
||||
|
||||
@@ -65,8 +64,5 @@
|
||||
key={{ key: 'description', attr: descriptionKey }}
|
||||
bind:this={descriptionBox}
|
||||
placeholder={tracker.string.IssueDescriptionPlaceholder}
|
||||
updateBacklinks={(doc, description) => {
|
||||
updateBacklinks(client, doc._id, doc._class, doc._id, description)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
<script lang="ts">
|
||||
import { Data, Ref } from '@hcengineering/core'
|
||||
import { IntlString } from '@hcengineering/platform'
|
||||
import { createBacklinks } from '@hcengineering/chunter'
|
||||
import { Card, getClient, SpaceSelector } from '@hcengineering/presentation'
|
||||
import { Milestone, MilestoneStatus, Project } from '@hcengineering/tracker'
|
||||
import ui, { DatePresenter, EditBox } from '@hcengineering/ui'
|
||||
@@ -39,9 +38,7 @@
|
||||
}
|
||||
|
||||
async function onSave () {
|
||||
const _id = await client.createDoc(tracker.class.Milestone, space, object)
|
||||
// Create an backlink to document
|
||||
await createBacklinks(client, _id, tracker.class.Milestone, _id, object.description ?? '')
|
||||
await client.createDoc(tracker.class.Milestone, space, object)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -37,7 +37,6 @@
|
||||
import { createEventDispatcher, onDestroy, onMount } from 'svelte'
|
||||
import tracker from '../../plugin'
|
||||
|
||||
import { updateBacklinks } from '@hcengineering/chunter-resources'
|
||||
import SubIssueTemplates from './IssueTemplateChilds.svelte'
|
||||
import TemplateControlPanel from './TemplateControlPanel.svelte'
|
||||
|
||||
@@ -185,9 +184,6 @@
|
||||
on:saved={(evt) => {
|
||||
saved = evt.detail
|
||||
}}
|
||||
updateBacklinks={(doc, description) => {
|
||||
updateBacklinks(client, doc._id, doc._class, doc._id, description)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div class="mt-6">
|
||||
|
||||
@@ -481,6 +481,11 @@
|
||||
"projectFolder": "packages/theme",
|
||||
"shouldPublish": true
|
||||
},
|
||||
{
|
||||
"packageName": "@hcengineering/text",
|
||||
"projectFolder": "packages/text",
|
||||
"shouldPublish": true
|
||||
},
|
||||
{
|
||||
"packageName": "@hcengineering/ui",
|
||||
"projectFolder": "packages/ui",
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"license": "EPL-2.0",
|
||||
"scripts": {
|
||||
"build": "heft build",
|
||||
"test": "heft test",
|
||||
"build:watch": "tsc",
|
||||
"lint:fix": "eslint --fix src",
|
||||
"lint": "eslint src",
|
||||
@@ -36,6 +37,8 @@
|
||||
"@hcengineering/workbench": "^0.6.8",
|
||||
"@hcengineering/notification": "^0.6.14",
|
||||
"@hcengineering/server-notification": "^0.6.1",
|
||||
"@hcengineering/server-notification-resources": "^0.6.0"
|
||||
"@hcengineering/server-notification-resources": "^0.6.0",
|
||||
"@hcengineering/server": "^0.6.4",
|
||||
"@hcengineering/text": "^0.6.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
//
|
||||
// Copyright © 2023 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { Class, Doc, Ref } from '@hcengineering/core'
|
||||
import { getBacklinks } from '../backlinks'
|
||||
|
||||
describe('extractBacklinks', () => {
|
||||
it('should return no backlinks for empty document', () => {
|
||||
const content = '<p></p>'
|
||||
|
||||
const backlinks = getBacklinks(
|
||||
'backlinkId' as Ref<Doc>,
|
||||
'backlinkClass' as Ref<Class<Doc>>,
|
||||
'attachedDocId' as Ref<Doc>,
|
||||
content
|
||||
)
|
||||
|
||||
expect(backlinks).toEqual([])
|
||||
})
|
||||
|
||||
it('should parse single backlink', () => {
|
||||
const content =
|
||||
'<p>hello <span class="reference" data-type="reference" data-id="id" data-objectclass="contact:class:Person" data-label="Appleseed John">@Appleseed John</span> </p>'
|
||||
|
||||
const backlinks = getBacklinks(
|
||||
'backlinkId' as Ref<Doc>,
|
||||
'backlinkClass' as Ref<Class<Doc>>,
|
||||
'attachedDocId' as Ref<Doc>,
|
||||
content
|
||||
)
|
||||
|
||||
expect(backlinks.length).toBe(1)
|
||||
expect(backlinks).toEqual([
|
||||
{
|
||||
attachedTo: 'id',
|
||||
attachedToClass: 'contact:class:Person',
|
||||
collection: 'backlinks',
|
||||
backlinkId: 'backlinkId',
|
||||
backlinkClass: 'backlinkClass',
|
||||
message:
|
||||
'hello <span data-type="reference" data-id="id" data-objectclass="contact:class:Person" data-label="Appleseed John" class="reference">@Appleseed John</span>',
|
||||
attachedDocId: 'attachedDocId'
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('should parse single backlink for multiple references', () => {
|
||||
const content =
|
||||
'<p><span class="reference" data-type="reference" data-id="id" data-label="Appleseed John" data-objectclass="contact:class:Person">@Appleseed John</span> <span data-type="reference" class="reference" data-id="id" data-label="Appleseed John" data-objectclass="contact:class:Person">@Appleseed John</span> </p>'
|
||||
|
||||
const backlinks = getBacklinks(
|
||||
'backlinkId' as Ref<Doc>,
|
||||
'backlinkClass' as Ref<Class<Doc>>,
|
||||
'attachedDocId' as Ref<Doc>,
|
||||
content
|
||||
)
|
||||
|
||||
expect(backlinks.length).toBe(1)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,54 @@
|
||||
//
|
||||
// Copyright © 2023 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { Backlink } from '@hcengineering/chunter'
|
||||
import { Class, Data, Doc, Ref } from '@hcengineering/core'
|
||||
import { defaultExtensions, getHTML, parseHTML, ReferenceNode } from '@hcengineering/text'
|
||||
|
||||
const extensions = [...defaultExtensions, ReferenceNode]
|
||||
|
||||
export function getBacklinks (
|
||||
backlinkId: Ref<Doc>,
|
||||
backlinkClass: Ref<Class<Doc>>,
|
||||
attachedDocId: Ref<Doc> | undefined,
|
||||
content: string
|
||||
): Array<Data<Backlink>> {
|
||||
const doc = parseHTML(content, extensions)
|
||||
|
||||
const result: Array<Data<Backlink>> = []
|
||||
|
||||
doc.descendants((node, _pos, parent): boolean => {
|
||||
if (node.type.name === ReferenceNode.name) {
|
||||
const ato = node.attrs.id as Ref<Doc>
|
||||
const atoClass = node.attrs.objectClass as Ref<Class<Doc>>
|
||||
const e = result.find((e) => e.attachedTo === ato && e.attachedToClass === atoClass)
|
||||
if (e === undefined && ato !== attachedDocId && ato !== backlinkId) {
|
||||
result.push({
|
||||
attachedTo: ato,
|
||||
attachedToClass: atoClass,
|
||||
collection: 'backlinks',
|
||||
backlinkId,
|
||||
backlinkClass,
|
||||
message: parent !== null ? getHTML(parent, extensions) : '',
|
||||
attachedDocId
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// Copyright © 2022 Hardcore Engineering Inc.
|
||||
// Copyright © 2022, 2023 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
@@ -29,6 +29,7 @@ import core, {
|
||||
TxCollectionCUD,
|
||||
TxCreateDoc,
|
||||
TxCUD,
|
||||
TxFactory,
|
||||
TxProcessor,
|
||||
TxRemoveDoc,
|
||||
TxUpdateDoc
|
||||
@@ -38,6 +39,130 @@ import { getMetadata } from '@hcengineering/platform'
|
||||
import serverCore, { TriggerControl } from '@hcengineering/server-core'
|
||||
import { getDocCollaborators, getMixinTx, pushNotification } from '@hcengineering/server-notification-resources'
|
||||
import { workbenchId } from '@hcengineering/workbench'
|
||||
import { getBacklinks } from './backlinks'
|
||||
|
||||
function getCreateBacklinksTxes (control: TriggerControl, txFactory: TxFactory, doc: Doc): Tx[] {
|
||||
const txes: Tx[] = []
|
||||
|
||||
const backlinkId = doc._id
|
||||
const backlinkClass = doc._class
|
||||
const attachedDocId = doc._id
|
||||
|
||||
const attributes = control.hierarchy.getAllAttributes(doc._class)
|
||||
for (const attr of attributes.values()) {
|
||||
if (attr.type._class === core.class.TypeMarkup) {
|
||||
const content = (doc as any)[attr.name]?.toString() ?? ''
|
||||
const backlinks = getBacklinks(backlinkId, backlinkClass, attachedDocId, content)
|
||||
|
||||
for (const backlink of backlinks) {
|
||||
const innerTx = txFactory.createTxCreateDoc(chunter.class.Backlink, chunter.space.Backlinks, backlink)
|
||||
|
||||
const collectionTx = txFactory.createTxCollectionCUD(
|
||||
backlink.attachedToClass,
|
||||
backlink.attachedTo,
|
||||
chunter.space.Backlinks,
|
||||
backlink.collection,
|
||||
innerTx
|
||||
)
|
||||
|
||||
txes.push(collectionTx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return txes
|
||||
}
|
||||
|
||||
async function getUpdateBacklinksTxes (control: TriggerControl, txFactory: TxFactory, doc: Doc): Promise<Tx[]> {
|
||||
const txes: Tx[] = []
|
||||
|
||||
const backlinkId = doc._id
|
||||
const backlinkClass = doc._class
|
||||
const attachedDocId = doc._id
|
||||
|
||||
const attributes = control.hierarchy.getAllAttributes(doc._class)
|
||||
for (const attr of attributes.values()) {
|
||||
if (attr.type._class === core.class.TypeMarkup) {
|
||||
const content = (doc as any)[attr.name]?.toString() ?? ''
|
||||
const backlinks = getBacklinks(backlinkId, backlinkClass, attachedDocId, content)
|
||||
|
||||
const current = await control.findAll(chunter.class.Backlink, {
|
||||
backlinkId,
|
||||
backlinkClass,
|
||||
attachedDocId,
|
||||
collection: 'backlinks'
|
||||
})
|
||||
|
||||
for (const c of current) {
|
||||
// Find existing and check if we need to update message
|
||||
const pos = backlinks.findIndex(
|
||||
(b) => b.backlinkId === c.backlinkId && b.backlinkClass === c.backlinkClass && b.attachedTo === c.attachedTo
|
||||
)
|
||||
if (pos !== -1) {
|
||||
// Update existing backlinks when message changed
|
||||
const data = backlinks[pos]
|
||||
if (c.message !== data.message) {
|
||||
const innerTx = txFactory.createTxUpdateDoc(c._class, c.space, c._id, {
|
||||
message: data.message
|
||||
})
|
||||
txes.push(
|
||||
txFactory.createTxCollectionCUD(
|
||||
c.attachedToClass,
|
||||
c.attachedTo,
|
||||
chunter.space.Backlinks,
|
||||
c.collection,
|
||||
innerTx
|
||||
)
|
||||
)
|
||||
}
|
||||
backlinks.splice(pos, 1)
|
||||
} else {
|
||||
// Remove not found backlinks
|
||||
const innerTx = txFactory.createTxRemoveDoc(c._class, c.space, c._id)
|
||||
txes.push(
|
||||
txFactory.createTxCollectionCUD(
|
||||
c.attachedToClass,
|
||||
c.attachedTo,
|
||||
chunter.space.Backlinks,
|
||||
c.collection,
|
||||
innerTx
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Add missing backlinks
|
||||
for (const backlink of backlinks) {
|
||||
const backlinkTx = txFactory.createTxCreateDoc(chunter.class.Backlink, chunter.space.Backlinks, backlink)
|
||||
txes.push(
|
||||
txFactory.createTxCollectionCUD(
|
||||
backlink.attachedToClass,
|
||||
backlink.attachedTo,
|
||||
chunter.space.Backlinks,
|
||||
backlink.collection,
|
||||
backlinkTx
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return txes
|
||||
}
|
||||
|
||||
async function getRemoveBacklinksTxes (control: TriggerControl, txFactory: TxFactory, doc: Ref<Doc>): Promise<Tx[]> {
|
||||
const txes: Tx[] = []
|
||||
|
||||
const backlinks = await control.findAll(chunter.class.Backlink, { attachedDocId: doc, collection: 'backlinks' })
|
||||
for (const b of backlinks) {
|
||||
const innerTx = txFactory.createTxRemoveDoc(b._class, b.space, b._id)
|
||||
txes.push(
|
||||
txFactory.createTxCollectionCUD(b.attachedToClass, b.attachedTo, chunter.space.Backlinks, b.collection, innerTx)
|
||||
)
|
||||
}
|
||||
|
||||
return txes
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
@@ -193,6 +318,62 @@ async function ThreadMessageDelete (tx: Tx, control: TriggerControl): Promise<Tx
|
||||
return [updateTx]
|
||||
}
|
||||
|
||||
async function BacklinksCreate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
const hierarchy = control.hierarchy
|
||||
const ctx = TxProcessor.extractTx(tx) as TxCreateDoc<Doc>
|
||||
if (ctx._class !== core.class.TxCreateDoc) return []
|
||||
if (hierarchy.isDerived(ctx.objectClass, chunter.class.Backlink)) return []
|
||||
|
||||
const txFactory = new TxFactory(control.txFactory.account)
|
||||
const doc = TxProcessor.createDoc2Doc(ctx)
|
||||
|
||||
const txes: Tx[] = getCreateBacklinksTxes(control, txFactory, doc)
|
||||
if (txes.length !== 0) {
|
||||
await control.apply(txes, true)
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
async function BacklinksUpdate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
const hierarchy = control.hierarchy
|
||||
const ctx = TxProcessor.extractTx(tx) as TxUpdateDoc<Doc>
|
||||
if (ctx._class !== core.class.TxUpdateDoc) return []
|
||||
if (hierarchy.isDerived(ctx.objectClass, chunter.class.Backlink)) return []
|
||||
|
||||
const rawDoc = (await control.findAll(ctx.objectClass, { _id: ctx.objectId }))[0]
|
||||
if (rawDoc === undefined) return []
|
||||
|
||||
const txFactory = new TxFactory(control.txFactory.account)
|
||||
|
||||
if (rawDoc !== undefined) {
|
||||
const doc = TxProcessor.updateDoc2Doc(rawDoc, ctx)
|
||||
const txes: Tx[] = await getUpdateBacklinksTxes(control, txFactory, doc)
|
||||
|
||||
if (txes.length !== 0) {
|
||||
await control.apply(txes, true)
|
||||
}
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
async function BacklinksRemove (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
const hierarchy = control.hierarchy
|
||||
const ctx = TxProcessor.extractTx(tx) as TxRemoveDoc<Doc>
|
||||
if (ctx._class !== core.class.TxRemoveDoc) return []
|
||||
if (hierarchy.isDerived(ctx.objectClass, chunter.class.Backlink)) return []
|
||||
|
||||
const txFactory = new TxFactory(control.txFactory.account)
|
||||
|
||||
const txes: Tx[] = await getRemoveBacklinksTxes(control, txFactory, ctx.objectId)
|
||||
if (txes.length !== 0) {
|
||||
await control.apply(txes, true)
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
@@ -255,6 +436,15 @@ export async function OnMessageSent (tx: Tx, control: TriggerControl): Promise<T
|
||||
return res
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function BacklinkTrigger (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
const promises = [BacklinksCreate(tx, control), BacklinksUpdate(tx, control), BacklinksRemove(tx, control)]
|
||||
const res = await Promise.all(promises)
|
||||
return res.flat()
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
@@ -321,6 +511,7 @@ export async function IsChannelMessage (
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||
export default async () => ({
|
||||
trigger: {
|
||||
BacklinkTrigger,
|
||||
ChunterTrigger,
|
||||
OnMessageSent
|
||||
},
|
||||
|
||||
@@ -29,6 +29,7 @@ export const serverChunterId = 'server-chunter' as Plugin
|
||||
*/
|
||||
export default plugin(serverChunterId, {
|
||||
trigger: {
|
||||
BacklinkTrigger: '' as Resource<TriggerFunc>,
|
||||
ChunterTrigger: '' as Resource<TriggerFunc>,
|
||||
OnMessageSent: '' as Resource<TriggerFunc>
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user