mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-12 04:37:44 +02:00
Merge remote-tracking branch 'origin/develop' into staging
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Generated
+110
-14
@@ -272,6 +272,9 @@ dependencies:
|
||||
'@rush-temp/guest-resources':
|
||||
specifier: file:./projects/guest-resources.tgz
|
||||
version: file:projects/guest-resources.tgz(@types/node@20.11.19)(esbuild@0.20.1)(postcss-load-config@4.0.2)(postcss@8.4.35)(ts-node@10.9.2)
|
||||
'@rush-temp/highlight':
|
||||
specifier: file:./projects/highlight.tgz
|
||||
version: file:projects/highlight.tgz(@types/node@20.11.19)(esbuild@0.20.1)(postcss-load-config@4.0.2)(postcss@8.4.35)(ts-node@10.9.2)
|
||||
'@rush-temp/hr':
|
||||
specifier: file:./projects/hr.tgz
|
||||
version: file:projects/hr.tgz(@types/node@20.11.19)(esbuild@0.20.1)(ts-node@10.9.2)
|
||||
@@ -1232,6 +1235,9 @@ dependencies:
|
||||
'@types/crypto-js':
|
||||
specifier: ^4.1.1
|
||||
version: 4.2.2
|
||||
'@types/csvtojson':
|
||||
specifier: ^2.0.0
|
||||
version: 2.0.0
|
||||
'@types/diff':
|
||||
specifier: ~5.0.2
|
||||
version: 5.0.9
|
||||
@@ -23178,6 +23184,56 @@ packages:
|
||||
typescript: 5.3.3
|
||||
dev: false
|
||||
|
||||
/svelte-preprocess@5.1.3(postcss-load-config@4.0.2)(postcss@8.4.35)(sass@1.71.1)(svelte@4.2.12)(typescript@5.6.2):
|
||||
resolution: {integrity: sha512-xxAkmxGHT+J/GourS5mVJeOXZzne1FR5ljeOUAMXUkfEhkLEllRreXpbl3dIYJlcJRfL1LO1uIAPpBpBfiqGPw==}
|
||||
engines: {node: '>= 16.0.0', pnpm: ^8.0.0}
|
||||
requiresBuild: true
|
||||
peerDependencies:
|
||||
'@babel/core': ^7.10.2
|
||||
coffeescript: ^2.5.1
|
||||
less: ^3.11.3 || ^4.0.0
|
||||
postcss: ^7 || ^8
|
||||
postcss-load-config: ^2.1.0 || ^3.0.0 || ^4.0.0 || ^5.0.0
|
||||
pug: ^3.0.0
|
||||
sass: ^1.26.8
|
||||
stylus: ^0.55.0
|
||||
sugarss: ^2.0.0 || ^3.0.0 || ^4.0.0
|
||||
svelte: ^3.23.0 || ^4.0.0-next.0 || ^4.0.0 || ^5.0.0-next.0
|
||||
typescript: '>=3.9.5 || ^4.0.0 || ^5.0.0'
|
||||
peerDependenciesMeta:
|
||||
'@babel/core':
|
||||
optional: true
|
||||
coffeescript:
|
||||
optional: true
|
||||
less:
|
||||
optional: true
|
||||
postcss:
|
||||
optional: true
|
||||
postcss-load-config:
|
||||
optional: true
|
||||
pug:
|
||||
optional: true
|
||||
sass:
|
||||
optional: true
|
||||
stylus:
|
||||
optional: true
|
||||
sugarss:
|
||||
optional: true
|
||||
typescript:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@types/pug': 2.0.10
|
||||
detect-indent: 6.1.0
|
||||
magic-string: 0.30.7
|
||||
postcss: 8.4.35
|
||||
postcss-load-config: 4.0.2(postcss@8.4.35)(ts-node@10.9.2)
|
||||
sass: 1.71.1
|
||||
sorcery: 0.11.0
|
||||
strip-indent: 3.0.0
|
||||
svelte: 4.2.12
|
||||
typescript: 5.6.2
|
||||
dev: false
|
||||
|
||||
/svelte@4.2.11:
|
||||
resolution: {integrity: sha512-YIQk3J4X89wOLhjsqIW8tqY3JHPuBdtdOIkASP2PZeAMcSW9RsIjQzMesCrxOF3gdWYC0mKknlKF7OqmLM+Zqg==}
|
||||
engines: {node: '>=16'}
|
||||
@@ -27467,7 +27523,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/diffview-resources.tgz(@types/node@20.11.19)(esbuild@0.20.1)(postcss-load-config@4.0.2)(postcss@8.4.35)(ts-node@10.9.2):
|
||||
resolution: {integrity: sha512-fi0UUvQTrAAt70IEF8CC7O6h5UfknnXO0xHylpL/Wn7h6oOQHlfPl9VhAx0PJW6TDvw3CltEE35pVSLyDml1CA==, tarball: file:projects/diffview-resources.tgz}
|
||||
resolution: {integrity: sha512-KZ6MRvsPCXYmTb05Jv0YgDwYMREnlmcyVC06L20SivCexX7nymqt6ul7oKdfu2keE9+qrIJjYtqHxAQUKQ4gFA==, tarball: file:projects/diffview-resources.tgz}
|
||||
id: file:projects/diffview-resources.tgz
|
||||
name: '@rush-temp/diffview-resources'
|
||||
version: 0.0.0
|
||||
@@ -28170,6 +28226,53 @@ packages:
|
||||
- ts-node
|
||||
dev: false
|
||||
|
||||
file:projects/highlight.tgz(@types/node@20.11.19)(esbuild@0.20.1)(postcss-load-config@4.0.2)(postcss@8.4.35)(ts-node@10.9.2):
|
||||
resolution: {integrity: sha512-xj/v9VAzl0qNSFarmoAujWSXP28ZkYEnnOBqx55GxBMULn54HimvBalNi/tymMwJV0LUSQbPyJbCY+HHV1tcuQ==, tarball: file:projects/highlight.tgz}
|
||||
id: file:projects/highlight.tgz
|
||||
name: '@rush-temp/highlight'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
'@types/jest': 29.5.12
|
||||
'@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.56.0)(typescript@5.6.2)
|
||||
'@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.6.2)
|
||||
eslint: 8.56.0
|
||||
eslint-config-standard-with-typescript: 40.0.0(@typescript-eslint/eslint-plugin@6.21.0)(eslint-plugin-import@2.29.1)(eslint-plugin-n@15.7.0)(eslint-plugin-promise@6.1.1)(eslint@8.56.0)(typescript@5.6.2)
|
||||
eslint-plugin-import: 2.29.1(eslint@8.56.0)
|
||||
eslint-plugin-n: 15.7.0(eslint@8.56.0)
|
||||
eslint-plugin-promise: 6.1.1(eslint@8.56.0)
|
||||
eslint-plugin-svelte: 2.35.1(eslint@8.56.0)(svelte@4.2.12)(ts-node@10.9.2)
|
||||
highlight.js: 11.8.0
|
||||
jest: 29.7.0(@types/node@20.11.19)(ts-node@10.9.2)
|
||||
lowlight: 3.1.0
|
||||
prettier: 3.2.5
|
||||
prettier-plugin-svelte: 3.2.2(prettier@3.2.5)(svelte@4.2.12)
|
||||
sass: 1.71.1
|
||||
svelte: 4.2.12
|
||||
svelte-check: 3.6.9(postcss-load-config@4.0.2)(postcss@8.4.35)(sass@1.71.1)(svelte@4.2.12)
|
||||
svelte-eslint-parser: 0.33.1(svelte@4.2.12)
|
||||
svelte-loader: 3.2.0(svelte@4.2.12)
|
||||
svelte-preprocess: 5.1.3(postcss-load-config@4.0.2)(postcss@8.4.35)(sass@1.71.1)(svelte@4.2.12)(typescript@5.6.2)
|
||||
ts-jest: 29.1.2(esbuild@0.20.1)(jest@29.7.0)(typescript@5.6.2)
|
||||
typescript: 5.6.2
|
||||
transitivePeerDependencies:
|
||||
- '@babel/core'
|
||||
- '@jest/types'
|
||||
- '@types/node'
|
||||
- babel-jest
|
||||
- babel-plugin-macros
|
||||
- coffeescript
|
||||
- esbuild
|
||||
- less
|
||||
- node-notifier
|
||||
- postcss
|
||||
- postcss-load-config
|
||||
- pug
|
||||
- stylus
|
||||
- sugarss
|
||||
- supports-color
|
||||
- ts-node
|
||||
dev: false
|
||||
|
||||
file:projects/hr-assets.tgz(esbuild@0.20.1)(ts-node@10.9.2):
|
||||
resolution: {integrity: sha512-NHRl6EHEnHdUWlcw0CbP7m1ZIKiRytNEO0fXtbXW8aREXmUcplLyHsE6lf0f7Y3iDxtPUm0V7DdfKtmzkuMNtA==, tarball: file:projects/hr-assets.tgz}
|
||||
id: file:projects/hr-assets.tgz
|
||||
@@ -28356,7 +28459,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/import-tool.tgz:
|
||||
resolution: {integrity: sha512-QBo/RgAGnie95pESqQNJUQSCpQi1rEpXqZz1G1zZMfhAvisZtW2XutmaGMS1oDu2ZB05pk8WOO8x6bfdc+keZg==, tarball: file:projects/import-tool.tgz}
|
||||
resolution: {integrity: sha512-KD9EB2XXo43QYnx8xp4j9z62JGValTBx2uTS4Egw11rsdAoENIxNxI+68CIKASfY8DMKMMuTmUZ6N6nK/6XSiw==, tarball: file:projects/import-tool.tgz}
|
||||
name: '@rush-temp/import-tool'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
@@ -30465,7 +30568,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/notification-resources.tgz(@types/node@20.11.19)(esbuild@0.20.1)(postcss-load-config@4.0.2)(postcss@8.4.35)(ts-node@10.9.2):
|
||||
resolution: {integrity: sha512-xpvJbpFBrKB3RPG8SgT9+cJj2IkysBNcl4jQAV69ACUvnTQMh4RS9P31zpaO7CObYXrm9/ImRF0fXbH+AkiJsg==, tarball: file:projects/notification-resources.tgz}
|
||||
resolution: {integrity: sha512-7qZrSN1845Nti1pousixaoWtM8ISlPYqgw5pK4UtuhEPdrfSq2NkCSSMnl3pOeSU6bVFzAeJ6YlTZs8Jl2wJ7A==, tarball: file:projects/notification-resources.tgz}
|
||||
id: file:projects/notification-resources.tgz
|
||||
name: '@rush-temp/notification-resources'
|
||||
version: 0.0.0
|
||||
@@ -31360,7 +31463,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/pod-server.tgz(utf-8-validate@6.0.4):
|
||||
resolution: {integrity: sha512-R0fObVPXSToiK7ioPeEbZ1gZFFjswVFLPCojWYsoNsHg9TL+kTNUdST63PJr4GCr8XVv9VplfYYcEs7qSARvAw==, tarball: file:projects/pod-server.tgz}
|
||||
resolution: {integrity: sha512-boGyT8HkEJkWqcXXAAZ869HB68/GpHcMTUBZlhkUS9oztHADMfpAbGQ+yVVvypye4e9nsYTF1LhpsDrToOtPYw==, tarball: file:projects/pod-server.tgz}
|
||||
id: file:projects/pod-server.tgz
|
||||
name: '@rush-temp/pod-server'
|
||||
version: 0.0.0
|
||||
@@ -32804,7 +32907,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/server-ai-bot-resources.tgz(@types/node@20.11.19)(esbuild@0.20.1)(ts-node@10.9.2):
|
||||
resolution: {integrity: sha512-m+ZYZxCuYOJxhC31c3misAQofis4BG32ek1OPxXXdpOOxPQUFI1WjmwVqJlZDTz3Yt9EXTGv3qkvrNwJuIyWZA==, tarball: file:projects/server-ai-bot-resources.tgz}
|
||||
resolution: {integrity: sha512-UChmRA0uWxgRKIx9jl1+P4wlGdsdLjsMkR3VOFR3Ya/sYwYV78JOCBIvnsTox/vJha8hx4mynqxaTCSe2nqshA==, tarball: file:projects/server-ai-bot-resources.tgz}
|
||||
id: file:projects/server-ai-bot-resources.tgz
|
||||
name: '@rush-temp/server-ai-bot-resources'
|
||||
version: 0.0.0
|
||||
@@ -32823,20 +32926,13 @@ packages:
|
||||
ts-jest: 29.1.2(esbuild@0.20.1)(jest@29.7.0)(typescript@5.3.3)
|
||||
typescript: 5.3.3
|
||||
transitivePeerDependencies:
|
||||
- '@aws-sdk/credential-providers'
|
||||
- '@babel/core'
|
||||
- '@jest/types'
|
||||
- '@mongodb-js/zstd'
|
||||
- '@types/node'
|
||||
- babel-jest
|
||||
- babel-plugin-macros
|
||||
- esbuild
|
||||
- gcp-metadata
|
||||
- kerberos
|
||||
- mongodb-client-encryption
|
||||
- node-notifier
|
||||
- snappy
|
||||
- socks
|
||||
- supports-color
|
||||
- ts-node
|
||||
dev: false
|
||||
@@ -33152,7 +33248,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/server-client.tgz(bufferutil@4.0.8)(esbuild@0.20.1)(ts-node@10.9.2)(utf-8-validate@6.0.4):
|
||||
resolution: {integrity: sha512-L1a1mgOD2Qd1S0E6twotWEVeiFeZfvIK5v93gaPH+A5LkkQoOi4qXoEQv/Udul/cDP/MxPFA9HQ3WbdMIn8XYg==, tarball: file:projects/server-client.tgz}
|
||||
resolution: {integrity: sha512-ixXx7EdCjt/KMKk6pVedAj/27hWqDwCBnHhmTJc694OR8wTEHyIaohP9t8gcjka/ugJRJJjOmAsF1sd8HphW4g==, tarball: file:projects/server-client.tgz}
|
||||
id: file:projects/server-client.tgz
|
||||
name: '@rush-temp/server-client'
|
||||
version: 0.0.0
|
||||
@@ -35802,7 +35898,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/text-editor-resources.tgz(@types/node@20.11.19)(bufferutil@4.0.8)(esbuild@0.20.1)(highlight.js@11.8.0)(postcss-load-config@4.0.2)(postcss@8.4.35)(ts-node@10.9.2)(utf-8-validate@6.0.4):
|
||||
resolution: {integrity: sha512-uffVYh80gs1A2XBETq/xa9TBTeZRZxTMb7sD/mjAvolpsXBd/cPCSKqhytZaoEFqIAFnZ6KfEMUqTlNIki4MoQ==, tarball: file:projects/text-editor-resources.tgz}
|
||||
resolution: {integrity: sha512-0IJEwSBOHUuSL2q4FSBnhVcfzwK0qOc5KcM7cKXbOM8GO/+WgyGRtKULl1b3LDjuBC21wst3eZY3o03qFIBDIQ==, tarball: file:projects/text-editor-resources.tgz}
|
||||
id: file:projects/text-editor-resources.tgz
|
||||
name: '@rush-temp/text-editor-resources'
|
||||
version: 0.0.0
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
### Supported Import Options
|
||||
|
||||
1. **Notion**: see [Import from Notion Guide](./notion/README.md)
|
||||
2. **ClickUp**: see [Import from ClickUp Guide](./clickup/README.md)
|
||||
1. **Notion**: see [Import from Notion Guide](./src/notion/README.md)
|
||||
2. **ClickUp**: see [Import from ClickUp Guide](./src/clickup/README.md)
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ interface ImportIssueEx extends ImportIssue {
|
||||
}
|
||||
|
||||
class ClickupMarkdownPreprocessor implements MarkdownPreprocessor {
|
||||
private readonly MENTION_REGEX = /@([A-Za-z]+ [A-Za-z]+)/g
|
||||
private readonly MENTION_REGEX = /@([\p{L}\p{M}]+ [\p{L}\p{M}]+)/gu
|
||||
constructor (private readonly personsByName: Map<string, Ref<Person>>) {}
|
||||
|
||||
process (json: MarkupNode): MarkupNode {
|
||||
|
||||
@@ -21,7 +21,7 @@ import { type Resource } from '@hcengineering/platform'
|
||||
import serverCore, { type TriggerControl } from '@hcengineering/server-core'
|
||||
import tracker from '@hcengineering/tracker'
|
||||
import serverTime, { type ToDoFactory, type OnToDo } from '@hcengineering/server-time'
|
||||
import { type ToDo, type WorkSlot } from '@hcengineering/time'
|
||||
import time, { type ToDo, type WorkSlot } from '@hcengineering/time'
|
||||
|
||||
@Mixin(serverTime.mixin.ToDoFactory, core.class.Class)
|
||||
export class TToDoFactory extends TClass implements ToDoFactory {
|
||||
@@ -45,6 +45,7 @@ export function createModel (builder: Builder): void {
|
||||
trigger: serverTime.trigger.OnToDoUpdate,
|
||||
txMatch: {
|
||||
_class: core.class.TxCollectionCUD,
|
||||
'tx.objectClass': time.class.ToDo,
|
||||
'tx._class': core.class.TxUpdateDoc
|
||||
}
|
||||
})
|
||||
@@ -53,6 +54,7 @@ export function createModel (builder: Builder): void {
|
||||
trigger: serverTime.trigger.OnToDoRemove,
|
||||
txMatch: {
|
||||
_class: core.class.TxCollectionCUD,
|
||||
'tx.objectClass': time.class.ToDo,
|
||||
'tx._class': core.class.TxRemoveDoc
|
||||
}
|
||||
})
|
||||
@@ -61,6 +63,7 @@ export function createModel (builder: Builder): void {
|
||||
trigger: serverTime.trigger.OnToDoCreate,
|
||||
txMatch: {
|
||||
_class: core.class.TxCollectionCUD,
|
||||
'tx.objectClass': time.class.ToDo,
|
||||
'tx._class': core.class.TxCreateDoc
|
||||
},
|
||||
isAsync: true
|
||||
@@ -70,10 +73,20 @@ export function createModel (builder: Builder): void {
|
||||
trigger: serverTime.trigger.OnWorkSlotCreate,
|
||||
txMatch: {
|
||||
_class: core.class.TxCollectionCUD,
|
||||
'tx.objectClass': time.class.WorkSlot,
|
||||
'tx._class': core.class.TxCreateDoc
|
||||
}
|
||||
})
|
||||
|
||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
trigger: serverTime.trigger.OnWorkSlotUpdate,
|
||||
txMatch: {
|
||||
_class: core.class.TxCollectionCUD,
|
||||
'tx.objectClass': time.class.WorkSlot,
|
||||
'tx._class': core.class.TxUpdateDoc
|
||||
}
|
||||
})
|
||||
|
||||
builder.mixin(tracker.class.Issue, core.class.Class, serverTime.mixin.ToDoFactory, {
|
||||
factory: serverTime.function.IssueToDoFactory
|
||||
})
|
||||
|
||||
@@ -0,0 +1,398 @@
|
||||
//
|
||||
// Copyright © 2024 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { Timestamp } from '../classes'
|
||||
import { getDay, convertToDay } from '..'
|
||||
|
||||
const supportedTimezones: string[] = [
|
||||
'Europe/Andorra',
|
||||
'Asia/Dubai',
|
||||
'Asia/Kabul',
|
||||
'Europe/Tirane',
|
||||
'Asia/Yerevan',
|
||||
'Antarctica/Casey',
|
||||
'Antarctica/Davis',
|
||||
'Antarctica/DumontDUrville',
|
||||
'Antarctica/Mawson',
|
||||
'Antarctica/Palmer',
|
||||
'Antarctica/Rothera',
|
||||
'Antarctica/Syowa',
|
||||
'Antarctica/Troll',
|
||||
'Antarctica/Vostok',
|
||||
'America/Argentina/Buenos_Aires',
|
||||
'America/Argentina/Cordoba',
|
||||
'America/Argentina/Salta',
|
||||
'America/Argentina/Jujuy',
|
||||
'America/Argentina/Tucuman',
|
||||
'America/Argentina/Catamarca',
|
||||
'America/Argentina/La_Rioja',
|
||||
'America/Argentina/San_Juan',
|
||||
'America/Argentina/Mendoza',
|
||||
'America/Argentina/San_Luis',
|
||||
'America/Argentina/Rio_Gallegos',
|
||||
'America/Argentina/Ushuaia',
|
||||
'Pacific/Pago_Pago',
|
||||
'Europe/Vienna',
|
||||
'Australia/Lord_Howe',
|
||||
'Antarctica/Macquarie',
|
||||
'Australia/Hobart',
|
||||
'Australia/Currie',
|
||||
'Australia/Melbourne',
|
||||
'Australia/Sydney',
|
||||
'Australia/Broken_Hill',
|
||||
'Australia/Brisbane',
|
||||
'Australia/Lindeman',
|
||||
'Australia/Adelaide',
|
||||
'Australia/Darwin',
|
||||
'Australia/Perth',
|
||||
'Australia/Eucla',
|
||||
'Asia/Baku',
|
||||
'America/Barbados',
|
||||
'Asia/Dhaka',
|
||||
'Europe/Brussels',
|
||||
'Europe/Sofia',
|
||||
'Atlantic/Bermuda',
|
||||
'Asia/Brunei',
|
||||
'America/La_Paz',
|
||||
'America/Noronha',
|
||||
'America/Belem',
|
||||
'America/Fortaleza',
|
||||
'America/Recife',
|
||||
'America/Araguaina',
|
||||
'America/Maceio',
|
||||
'America/Bahia',
|
||||
'America/Sao_Paulo',
|
||||
'America/Campo_Grande',
|
||||
'America/Cuiaba',
|
||||
'America/Santarem',
|
||||
'America/Porto_Velho',
|
||||
'America/Boa_Vista',
|
||||
'America/Manaus',
|
||||
'America/Eirunepe',
|
||||
'America/Rio_Branco',
|
||||
'America/Nassau',
|
||||
'Asia/Thimphu',
|
||||
'Europe/Minsk',
|
||||
'America/Belize',
|
||||
'America/St_Johns',
|
||||
'America/Halifax',
|
||||
'America/Glace_Bay',
|
||||
'America/Moncton',
|
||||
'America/Goose_Bay',
|
||||
'America/Blanc-Sablon',
|
||||
'America/Toronto',
|
||||
'America/Nipigon',
|
||||
'America/Thunder_Bay',
|
||||
'America/Iqaluit',
|
||||
'America/Pangnirtung',
|
||||
'America/Atikokan',
|
||||
'America/Winnipeg',
|
||||
'America/Rainy_River',
|
||||
'America/Resolute',
|
||||
'America/Rankin_Inlet',
|
||||
'America/Regina',
|
||||
'America/Swift_Current',
|
||||
'America/Edmonton',
|
||||
'America/Cambridge_Bay',
|
||||
'America/Yellowknife',
|
||||
'America/Inuvik',
|
||||
'America/Creston',
|
||||
'America/Dawson_Creek',
|
||||
'America/Fort_Nelson',
|
||||
'America/Vancouver',
|
||||
'America/Whitehorse',
|
||||
'America/Dawson',
|
||||
'Indian/Cocos',
|
||||
'Europe/Zurich',
|
||||
'Africa/Abidjan',
|
||||
'Pacific/Rarotonga',
|
||||
'America/Santiago',
|
||||
'America/Punta_Arenas',
|
||||
'Pacific/Easter',
|
||||
'Asia/Shanghai',
|
||||
'Asia/Urumqi',
|
||||
'America/Bogota',
|
||||
'America/Costa_Rica',
|
||||
'America/Havana',
|
||||
'Atlantic/Cape_Verde',
|
||||
'America/Curacao',
|
||||
'Indian/Christmas',
|
||||
'Asia/Nicosia',
|
||||
'Asia/Famagusta',
|
||||
'Europe/Prague',
|
||||
'Europe/Berlin',
|
||||
'Europe/Copenhagen',
|
||||
'America/Santo_Domingo',
|
||||
'Africa/Algiers',
|
||||
'America/Guayaquil',
|
||||
'Pacific/Galapagos',
|
||||
'Europe/Tallinn',
|
||||
'Africa/Cairo',
|
||||
'Africa/El_Aaiun',
|
||||
'Europe/Madrid',
|
||||
'Africa/Ceuta',
|
||||
'Atlantic/Canary',
|
||||
'Europe/Helsinki',
|
||||
'Atlantic/Stanley',
|
||||
'Pacific/Chuuk',
|
||||
'Pacific/Pohnpei',
|
||||
'Atlantic/Faroe',
|
||||
'Europe/Paris',
|
||||
'Europe/London',
|
||||
'Asia/Tbilisi',
|
||||
'America/Cayenne',
|
||||
'Africa/Accra',
|
||||
'Europe/Gibraltar',
|
||||
'America/Godthab',
|
||||
'America/Danmarkshavn',
|
||||
'America/Scoresbysund',
|
||||
'America/Thule',
|
||||
'Europe/Athens',
|
||||
'Atlantic/South_Georgia',
|
||||
'America/Guatemala',
|
||||
'Pacific/Guam',
|
||||
'Africa/Bissau',
|
||||
'America/Guyana',
|
||||
'Asia/Hong_Kong',
|
||||
'America/Tegucigalpa',
|
||||
'America/Port-au-Prince',
|
||||
'Europe/Budapest',
|
||||
'Asia/Jakarta',
|
||||
'Asia/Pontianak',
|
||||
'Asia/Makassar',
|
||||
'Asia/Jayapura',
|
||||
'Europe/Dublin',
|
||||
'Asia/Jerusalem',
|
||||
'Asia/Kolkata',
|
||||
'Indian/Chagos',
|
||||
'Asia/Baghdad',
|
||||
'Asia/Tehran',
|
||||
'Atlantic/Reykjavik',
|
||||
'Europe/Rome',
|
||||
'America/Jamaica',
|
||||
'Asia/Amman',
|
||||
'Asia/Tokyo',
|
||||
'Africa/Nairobi',
|
||||
'Asia/Bishkek',
|
||||
'Asia/Pyongyang',
|
||||
'Asia/Seoul',
|
||||
'Asia/Almaty',
|
||||
'Asia/Qyzylorda',
|
||||
'Asia/Qostanay',
|
||||
'Asia/Aqtobe',
|
||||
'Asia/Aqtau',
|
||||
'Asia/Atyrau',
|
||||
'Asia/Oral',
|
||||
'Asia/Beirut',
|
||||
'Asia/Colombo',
|
||||
'Africa/Monrovia',
|
||||
'Europe/Vilnius',
|
||||
'Europe/Luxembourg',
|
||||
'Europe/Riga',
|
||||
'Africa/Tripoli',
|
||||
'Africa/Casablanca',
|
||||
'Europe/Monaco',
|
||||
'Europe/Chisinau',
|
||||
'Asia/Yangon',
|
||||
'Asia/Ulaanbaatar',
|
||||
'Asia/Hovd',
|
||||
'Asia/Choibalsan',
|
||||
'Asia/Macau',
|
||||
'America/Martinique',
|
||||
'Europe/Malta',
|
||||
'Indian/Mauritius',
|
||||
'Indian/Maldives',
|
||||
'America/Mexico_City',
|
||||
'America/Cancun',
|
||||
'America/Merida',
|
||||
'America/Monterrey',
|
||||
'America/Matamoros',
|
||||
'America/Mazatlan',
|
||||
'America/Chihuahua',
|
||||
'America/Ojinaga',
|
||||
'America/Hermosillo',
|
||||
'America/Tijuana',
|
||||
'America/Bahia_Banderas',
|
||||
'Asia/Kuala_Lumpur',
|
||||
'Asia/Kuching',
|
||||
'Africa/Maputo',
|
||||
'Africa/Windhoek',
|
||||
'Pacific/Noumea',
|
||||
'Africa/Lagos',
|
||||
'America/Managua',
|
||||
'Europe/Amsterdam',
|
||||
'Europe/Oslo',
|
||||
'Asia/Kathmandu',
|
||||
'Pacific/Niue',
|
||||
'America/Panama',
|
||||
'America/Lima',
|
||||
'Pacific/Tahiti',
|
||||
'Pacific/Marquesas',
|
||||
'Pacific/Gambier',
|
||||
'Pacific/Port_Moresby',
|
||||
'Pacific/Bougainville',
|
||||
'Asia/Manila',
|
||||
'Asia/Karachi',
|
||||
'Europe/Warsaw',
|
||||
'America/Miquelon',
|
||||
'Pacific/Pitcairn',
|
||||
'America/Puerto_Rico',
|
||||
'Asia/Gaza',
|
||||
'Asia/Hebron',
|
||||
'Europe/Lisbon',
|
||||
'Atlantic/Madeira',
|
||||
'Atlantic/Azores',
|
||||
'Pacific/Palau',
|
||||
'America/Asuncion',
|
||||
'Asia/Qatar',
|
||||
'Indian/Reunion',
|
||||
'Europe/Bucharest',
|
||||
'Europe/Belgrade',
|
||||
'Europe/Kaliningrad',
|
||||
'Europe/Moscow',
|
||||
'Europe/Simferopol',
|
||||
'Europe/Kirov',
|
||||
'Europe/Astrakhan',
|
||||
'Europe/Volgograd',
|
||||
'Europe/Saratov',
|
||||
'Europe/Ulyanovsk',
|
||||
'Europe/Samara',
|
||||
'Asia/Yekaterinburg',
|
||||
'Asia/Omsk',
|
||||
'Asia/Novosibirsk',
|
||||
'Asia/Barnaul',
|
||||
'Asia/Tomsk',
|
||||
'Asia/Novokuznetsk',
|
||||
'Asia/Krasnoyarsk',
|
||||
'Asia/Irkutsk',
|
||||
'Asia/Chita',
|
||||
'Asia/Yakutsk',
|
||||
'Asia/Khandyga',
|
||||
'Asia/Vladivostok',
|
||||
'Asia/Ust-Nera',
|
||||
'Asia/Magadan',
|
||||
'Asia/Sakhalin',
|
||||
'Asia/Srednekolymsk',
|
||||
'Asia/Riyadh',
|
||||
'Pacific/Guadalcanal',
|
||||
'Indian/Mahe',
|
||||
'Africa/Khartoum',
|
||||
'Europe/Stockholm',
|
||||
'Asia/Singapore',
|
||||
'America/Paramaribo',
|
||||
'Africa/Juba',
|
||||
'Africa/Sao_Tome',
|
||||
'America/El_Salvador',
|
||||
'Asia/Damascus',
|
||||
'America/Grand_Turk',
|
||||
'Africa/Ndjamena',
|
||||
'Indian/Kerguelen',
|
||||
'Asia/Bangkok',
|
||||
'Asia/Dushanbe',
|
||||
'Asia/Dili',
|
||||
'Asia/Ashgabat',
|
||||
'Africa/Tunis',
|
||||
'Europe/Istanbul',
|
||||
'America/Port_of_Spain',
|
||||
'Asia/Taipei',
|
||||
'Europe/Kiev',
|
||||
'Europe/Uzhgorod',
|
||||
'Europe/Zaporozhye',
|
||||
'America/New_York',
|
||||
'America/Detroit',
|
||||
'America/Kentucky/Louisville',
|
||||
'America/Kentucky/Monticello',
|
||||
'America/Indiana/Indianapolis',
|
||||
'America/Indiana/Vincennes',
|
||||
'America/Indiana/Winamac',
|
||||
'America/Indiana/Marengo',
|
||||
'America/Indiana/Petersburg',
|
||||
'America/Indiana/Vevay',
|
||||
'America/Chicago',
|
||||
'America/Indiana/Tell_City',
|
||||
'America/Indiana/Knox',
|
||||
'America/Menominee',
|
||||
'America/North_Dakota/Center',
|
||||
'America/North_Dakota/New_Salem',
|
||||
'America/North_Dakota/Beulah',
|
||||
'America/Denver',
|
||||
'America/Boise',
|
||||
'America/Phoenix',
|
||||
'America/Los_Angeles',
|
||||
'America/Anchorage',
|
||||
'America/Juneau',
|
||||
'America/Sitka',
|
||||
'America/Metlakatla',
|
||||
'America/Yakutat',
|
||||
'America/Nome',
|
||||
'America/Adak',
|
||||
'Pacific/Honolulu',
|
||||
'America/Montevideo',
|
||||
'Asia/Samarkand',
|
||||
'Asia/Tashkent',
|
||||
'America/Caracas',
|
||||
'Asia/Ho_Chi_Minh',
|
||||
'Pacific/Efate',
|
||||
'Africa/Johannesburg'
|
||||
]
|
||||
|
||||
function testGetDay (date: Date, timezone: string): void {
|
||||
const timestamp: Timestamp = getDay(date.getTime())
|
||||
const convertedDate: Date = new Date(timestamp)
|
||||
const originalLocaleDate: string = date.toLocaleDateString('en-US', { timeZone: 'Europe/London' })
|
||||
const convertedLocaleDate: string = convertedDate.toLocaleDateString('en-US', { timeZone: timezone })
|
||||
expect(convertedLocaleDate).toEqual(originalLocaleDate)
|
||||
}
|
||||
|
||||
function testConvertToDay (date: Date, timezone: string): void {
|
||||
const convertedDate: Date = convertToDay(date)
|
||||
const originalLocaleDate: string = date.toLocaleDateString('en-US', { timeZone: 'Europe/London' })
|
||||
const convertedLocaleDate: string = convertedDate.toLocaleDateString('en-US', { timeZone: timezone })
|
||||
expect(convertedLocaleDate).toEqual(originalLocaleDate)
|
||||
}
|
||||
|
||||
describe('time', () => {
|
||||
it.each(supportedTimezones)('dates are matched in `getDay` for locale %p', (timezone: string) => {
|
||||
testGetDay(new Date(Date.UTC(1995, 11, 3, 12, 0)), timezone)
|
||||
testGetDay(new Date(Date.UTC(2025, 0, 23, 55, 0)), timezone)
|
||||
testGetDay(new Date(Date.UTC(2020, 4, 12, 30, 50)), timezone)
|
||||
testGetDay(new Date(Date.UTC(2024, 5, 12, 30, 50)), timezone)
|
||||
testGetDay(new Date(Date.UTC(2024, 7, 0, 0, 0)), timezone)
|
||||
})
|
||||
|
||||
it.each(supportedTimezones)('dates are matched in `convertToDay` for locale %p', (timezone: string) => {
|
||||
testConvertToDay(new Date(Date.UTC(1995, 11, 3, 12, 0)), timezone)
|
||||
testConvertToDay(new Date(Date.UTC(2025, 0, 23, 55, 0)), timezone)
|
||||
testConvertToDay(new Date(Date.UTC(2020, 4, 12, 30, 50)), timezone)
|
||||
testConvertToDay(new Date(Date.UTC(2024, 5, 12, 30, 50)), timezone)
|
||||
testConvertToDay(new Date(Date.UTC(2024, 7, 0, 0, 0)), timezone)
|
||||
})
|
||||
|
||||
it.each([
|
||||
[0, 0, 0, 0],
|
||||
[7, 59, 59, 999],
|
||||
[12, 0, 0, 0],
|
||||
[23, 59, 59, 999]
|
||||
])(
|
||||
'dates are matched for time [h: %p, m: %p, s: %p, ms: %p]',
|
||||
(hours: number, minutes: number, seconds: number, milliSeconds: number) => {
|
||||
const date: Date = new Date()
|
||||
const expectedDay: number = date.getDate()
|
||||
date.setHours(hours, minutes, seconds, milliSeconds)
|
||||
const convertedDate: Date = convertToDay(date)
|
||||
expect(convertedDate.getDate()).toEqual(expectedDay)
|
||||
}
|
||||
)
|
||||
})
|
||||
@@ -16,8 +16,18 @@ import { Timestamp } from './classes'
|
||||
|
||||
export function getDay (time: Timestamp): Timestamp {
|
||||
const date: Date = new Date(time)
|
||||
date.setHours(0, 0, 0, 0)
|
||||
return date.getTime()
|
||||
return convertToDay(date).getTime()
|
||||
}
|
||||
|
||||
export function convertToDay (date: Date): Date {
|
||||
const originalDay: number = date.getDate()
|
||||
const convertedDate: Date = new Date(date)
|
||||
// Set 12 AM UTC time, since it will be the same day in most timezones
|
||||
convertedDate.setUTCHours(12, 0, 0, 0)
|
||||
if (convertedDate.getDate() !== originalDay) {
|
||||
convertedDate.setDate(originalDay)
|
||||
}
|
||||
return convertedDate
|
||||
}
|
||||
|
||||
export function getHour (time: Timestamp): Timestamp {
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
extends: ['./node_modules/@hcengineering/platform-rig/profiles/ui/eslint.config.json'],
|
||||
parserOptions: { tsconfigRootDir: __dirname }
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"trailingComma": "none",
|
||||
"tabWidth": 2,
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"printWidth": 120,
|
||||
"useTabs": false,
|
||||
"bracketSpacing": true,
|
||||
"proseWrap": "preserve",
|
||||
"plugins": [
|
||||
"prettier-plugin-svelte"
|
||||
],
|
||||
"overrides": [
|
||||
{
|
||||
"files": "*.svelte",
|
||||
"options": {
|
||||
"parser": "svelte"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
|
||||
"rigPackageName": "@hcengineering/platform-rig",
|
||||
"rigProfile": "ui"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'],
|
||||
roots: ["./src"],
|
||||
coverageReporters: ["text-summary", "html"]
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"name": "@hcengineering/highlight",
|
||||
"version": "0.6.0",
|
||||
"main": "src/index.ts",
|
||||
"author": "Hardcore Engineering Inc.",
|
||||
"license": "EPL-2.0",
|
||||
"scripts": {
|
||||
"build": "compile ui",
|
||||
"build:docs": "api-extractor run --local",
|
||||
"format": "format src",
|
||||
"svelte-check": "do-svelte-check",
|
||||
"_phase:svelte-check": "do-svelte-check",
|
||||
"build:watch": "compile ui",
|
||||
"_phase:build": "compile ui",
|
||||
"_phase:format": "format src",
|
||||
"_phase:validate": "compile validate"
|
||||
},
|
||||
"devDependencies": {
|
||||
"svelte-loader": "^3.2.0",
|
||||
"sass": "^1.53.0",
|
||||
"svelte-preprocess": "^5.1.3",
|
||||
"@hcengineering/platform-rig": "^0.6.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.11.0",
|
||||
"@typescript-eslint/parser": "^6.11.0",
|
||||
"eslint-config-standard-with-typescript": "^40.0.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-n": "^15.4.0",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"prettier-plugin-svelte": "^3.2.2",
|
||||
"eslint": "^8.54.0",
|
||||
"prettier": "^3.1.0",
|
||||
"svelte-check": "^3.6.9",
|
||||
"typescript": "^5.3.3",
|
||||
"jest": "^29.7.0",
|
||||
"ts-jest": "^29.1.1",
|
||||
"@types/jest": "^29.5.5",
|
||||
"eslint-plugin-svelte": "^2.35.1",
|
||||
"svelte-eslint-parser": "^0.33.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"svelte": "^4.2.12",
|
||||
"@hcengineering/ui": "^0.6.15",
|
||||
"highlight.js": "~11.8.0",
|
||||
"lowlight": "^3.1.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
plugins: [
|
||||
require('autoprefixer')
|
||||
]
|
||||
}
|
||||
+18
-4
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// Copyright © 2023 Hardcore Engineering Inc.
|
||||
// Copyright © 2024 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
@@ -13,16 +13,29 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import hljs from 'highlight.js'
|
||||
import { hljsDefineSvelte } from './languages/svelte-hljs'
|
||||
import hljs, { type LanguageFn } from 'highlight.js'
|
||||
import { all } from 'lowlight'
|
||||
import { svelte } from './languages/svelte'
|
||||
import { vlang } from './languages/vlang'
|
||||
|
||||
hljs.registerLanguage('svelte', hljsDefineSvelte)
|
||||
/** @public */
|
||||
export const grammars: Record<string, LanguageFn> = {
|
||||
...all,
|
||||
svelte,
|
||||
vlang
|
||||
}
|
||||
|
||||
Object.entries(grammars).forEach((grammar) => {
|
||||
hljs.registerLanguage(...grammar)
|
||||
})
|
||||
|
||||
/** @public */
|
||||
export interface HighlightOptions {
|
||||
auto?: boolean
|
||||
language: string | undefined
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export function highlightText (text: string, options: HighlightOptions): string {
|
||||
// We should always use highlighter because it sanitizes the input
|
||||
// We have to always use highlighter to ensure that the input is sanitized
|
||||
@@ -38,6 +51,7 @@ export function highlightText (text: string, options: HighlightOptions): string
|
||||
return normalizeHighlightTags(highlighted)
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export function highlightLines (lines: string[], options: HighlightOptions): string[] {
|
||||
const highlighted = highlightText(lines.join('\n'), options)
|
||||
return highlighted.split('\n')
|
||||
+16
-1
@@ -1,4 +1,19 @@
|
||||
export function hljsDefineSvelte (hljs: any): any {
|
||||
//
|
||||
// Copyright © 2024 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
export function svelte (hljs: any): any {
|
||||
return {
|
||||
subLanguage: 'xml',
|
||||
contains: [
|
||||
@@ -0,0 +1,161 @@
|
||||
//
|
||||
// Copyright © 2024 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
const LITERALS = ['true', 'false', 'nil']
|
||||
|
||||
const BUILT_INS = [
|
||||
'close',
|
||||
'dump',
|
||||
'eprint',
|
||||
'eprintln',
|
||||
'error',
|
||||
'exit',
|
||||
'it',
|
||||
'panic',
|
||||
'print_backtrace',
|
||||
'print',
|
||||
'println'
|
||||
]
|
||||
|
||||
const TYPES = [
|
||||
'bool',
|
||||
'string',
|
||||
'i8',
|
||||
'i16',
|
||||
'i32',
|
||||
'i64',
|
||||
'i128',
|
||||
'int',
|
||||
'u8',
|
||||
'u16',
|
||||
'u32',
|
||||
'u64',
|
||||
'u128',
|
||||
'rune',
|
||||
'f32',
|
||||
'f64',
|
||||
'isize',
|
||||
'usize',
|
||||
'voidptr',
|
||||
'charptr',
|
||||
'byteptr',
|
||||
'thread'
|
||||
]
|
||||
|
||||
const KWS = [
|
||||
'as',
|
||||
'asm',
|
||||
'assert',
|
||||
'atomic',
|
||||
'break',
|
||||
'const',
|
||||
'continue',
|
||||
'defer',
|
||||
'else',
|
||||
'enum',
|
||||
'fn',
|
||||
'for',
|
||||
'go',
|
||||
'goto',
|
||||
'if',
|
||||
'import',
|
||||
'in',
|
||||
'interface',
|
||||
'is',
|
||||
'isreftype',
|
||||
'lock',
|
||||
'match',
|
||||
'module',
|
||||
'mut',
|
||||
'nil',
|
||||
'none',
|
||||
'or',
|
||||
'pub',
|
||||
'return',
|
||||
'rlock',
|
||||
'select',
|
||||
'shared',
|
||||
'sizeof',
|
||||
'spawn',
|
||||
'static',
|
||||
'struct',
|
||||
'type',
|
||||
'typeof',
|
||||
'union',
|
||||
'unsafe',
|
||||
'volatile',
|
||||
'__global',
|
||||
'__offsetof'
|
||||
]
|
||||
|
||||
export function vlang (hljs: any): any {
|
||||
const KEYWORDS = {
|
||||
keyword: KWS,
|
||||
type: TYPES,
|
||||
literal: LITERALS,
|
||||
built_in: BUILT_INS
|
||||
}
|
||||
return {
|
||||
name: 'V',
|
||||
aliases: ['vlang'],
|
||||
keywords: KEYWORDS,
|
||||
illegal: '</',
|
||||
contains: [
|
||||
hljs.C_LINE_COMMENT_MODE,
|
||||
hljs.C_BLOCK_COMMENT_MODE,
|
||||
{
|
||||
className: 'string',
|
||||
variants: [
|
||||
hljs.QUOTE_STRING_MODE,
|
||||
hljs.APOS_STRING_MODE,
|
||||
{
|
||||
begin: '`',
|
||||
end: '`'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
className: 'number',
|
||||
variants: [
|
||||
{
|
||||
begin: hljs.C_NUMBER_RE + '[i]',
|
||||
relevance: 1
|
||||
},
|
||||
hljs.C_NUMBER_MODE
|
||||
]
|
||||
},
|
||||
{
|
||||
begin: /:=/ // relevance booster
|
||||
},
|
||||
{
|
||||
className: 'function',
|
||||
beginKeywords: 'fn',
|
||||
end: '\\s*(\\{|$)',
|
||||
excludeEnd: true,
|
||||
contains: [
|
||||
hljs.TITLE_MODE,
|
||||
{
|
||||
className: 'params',
|
||||
begin: /\(/,
|
||||
end: /\)/,
|
||||
endsParent: true,
|
||||
keywords: KEYWORDS,
|
||||
illegal: /['']/
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
const sveltePreprocess = require('svelte-preprocess')
|
||||
|
||||
module.exports = {
|
||||
preprocess: sveltePreprocess()
|
||||
};
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "./node_modules/@hcengineering/platform-rig/profiles/ui/tsconfig.json",
|
||||
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./lib",
|
||||
"declarationDir": "./types"
|
||||
}
|
||||
}
|
||||
@@ -58,5 +58,8 @@
|
||||
"@typescript-eslint/no-floating-promises": "warn"
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
"rules": {
|
||||
"svelte/no-at-html-tags": "error"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1053,6 +1053,7 @@ a.no-line {
|
||||
.bottom-divider { border-bottom: 1px solid var(--theme-divider-color); }
|
||||
.left-divider { border-left: 1px solid var(--theme-divider-color); }
|
||||
.right-divider { border-right: 1px solid var(--theme-divider-color); }
|
||||
.right-navpanel-border { border-right: 1px solid var(--theme-navpanel-border); }
|
||||
.bottom-highlight-select { border-bottom: 1px solid var(--highlight-select); }
|
||||
|
||||
|
||||
|
||||
@@ -13,11 +13,12 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import dompurify from 'dompurify'
|
||||
import DOMPurify from 'dompurify'
|
||||
|
||||
export let value: string
|
||||
|
||||
$: sanitized = dompurify.sanitize(value)
|
||||
$: sanitized = DOMPurify.sanitize(value)
|
||||
</script>
|
||||
|
||||
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||
{@html sanitized}
|
||||
|
||||
@@ -30,5 +30,6 @@
|
||||
|
||||
{#if text}
|
||||
<!-- "replaceURLs" produces sanitazed string -->
|
||||
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||
{@html replaceURLs(text)}
|
||||
{/if}
|
||||
|
||||
@@ -38,6 +38,8 @@
|
||||
|
||||
let sState: SeparatorState
|
||||
$: sState = typeof float === 'string' ? SeparatorState.FLOAT : float ? SeparatorState.HIDDEN : SeparatorState.NORMAL
|
||||
const checkFullWidth = (): boolean =>
|
||||
sState === SeparatorState.FLOAT && $deviceInfo.isMobile && $deviceInfo.isPortrait
|
||||
|
||||
const direction: 'horizontal' | 'vertical' = 'horizontal'
|
||||
let separators: SeparatedItem[] | null = null
|
||||
@@ -67,6 +69,10 @@
|
||||
let disabled: boolean = false
|
||||
let side: 'start' | 'end' | undefined = undefined
|
||||
|
||||
$: fs = $deviceInfo.fontSize
|
||||
const remToPx = (rem: number): number => rem * fs
|
||||
const pxToRem = (px: number): number => px / fs
|
||||
|
||||
const fetchSeparators = (): void => {
|
||||
const res = getSeparators(name, float)
|
||||
if (res !== null && !Array.isArray(res)) panel = res
|
||||
@@ -90,10 +96,6 @@
|
||||
checkSizes()
|
||||
}
|
||||
|
||||
$: fs = $deviceInfo.fontSize
|
||||
const remToPx = (rem: number): number => rem * fs
|
||||
const pxToRem = (px: number): number => px / fs
|
||||
|
||||
const convertSize = (prop: TSeparatedItem): string => (typeof prop === 'number' ? `${prop}px` : '')
|
||||
|
||||
const setSize = (element: HTMLElement, size: TSeparatedItem, next: boolean = false): void => {
|
||||
@@ -229,6 +231,12 @@
|
||||
|
||||
const checkSizes = (): void => {
|
||||
if (sState === SeparatorState.FLOAT) {
|
||||
if (checkFullWidth() && panel != null) {
|
||||
const s = pxToRem(window.innerWidth)
|
||||
panel.size = s
|
||||
panel.maxSize = s
|
||||
panel.minSize = s
|
||||
}
|
||||
if (parentElement != null && panel != null) initSize(parentElement, panel)
|
||||
} else if (sState === SeparatorState.NORMAL) {
|
||||
if (prevElement != null && prevElSize != null) initSize(prevElement, prevElSize)
|
||||
@@ -442,12 +450,13 @@
|
||||
}
|
||||
} else if (sState === SeparatorState.FLOAT && parentElement != null) {
|
||||
parentElement.style.pointerEvents = 'all'
|
||||
saveSeparator(name, float, panel)
|
||||
if (!checkFullWidth()) saveSeparator(name, float, panel)
|
||||
}
|
||||
document.body.style.cursor = ''
|
||||
}
|
||||
|
||||
function pointerDown (event: PointerEvent): void {
|
||||
if (checkFullWidth()) return
|
||||
prepareSeparation(event)
|
||||
document.addEventListener('pointermove', pointerMove)
|
||||
document.addEventListener('pointerup', pointerUp)
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { DateRangeMode } from '@hcengineering/core'
|
||||
import { DateRangeMode, convertToDay } from '@hcengineering/core'
|
||||
import { IntlString } from '@hcengineering/platform'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import {
|
||||
@@ -56,10 +56,10 @@
|
||||
const saveDate = (withTime: boolean = false): void => {
|
||||
if (currentDate) {
|
||||
if (!withTime) {
|
||||
currentDate.setHours(0)
|
||||
currentDate.setMinutes(0)
|
||||
currentDate = convertToDay(currentDate)
|
||||
} else {
|
||||
currentDate.setSeconds(0, 0)
|
||||
}
|
||||
currentDate.setSeconds(0, 0)
|
||||
viewDate = currentDate = currentDate
|
||||
dispatch('update', currentDate)
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import Label from '../Label.svelte'
|
||||
import IconCheck from '../icons/Check.svelte'
|
||||
import Html from '../Html.svelte'
|
||||
|
||||
export let langs: any
|
||||
export let selected: string
|
||||
@@ -34,7 +35,7 @@
|
||||
dispatch('close', lang.id)
|
||||
}}
|
||||
>
|
||||
<div class="svg-16px flex-no-shrink text-16px mr-2">{@html lang.logo}</div>
|
||||
<div class="svg-16px flex-no-shrink text-16px mr-2"><Html value={lang.logo} /></div>
|
||||
<span class="overflow-label flex-grow"><Label label={lang.label} /></span>
|
||||
<div class="ap-check">
|
||||
{#if lang.id === selected}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
import { getMetadata } from '@hcengineering/platform'
|
||||
import ui, { showPopup, deviceOptionsStore as deviceInfo } from '../..'
|
||||
import LangPopup from './LangPopup.svelte'
|
||||
import Html from '../Html.svelte'
|
||||
|
||||
let pressed: boolean = false
|
||||
|
||||
@@ -70,5 +71,7 @@
|
||||
class:pressed
|
||||
on:click={selectLanguage}
|
||||
>
|
||||
{@html selected?.logo}
|
||||
{#if selected}
|
||||
<Html value={selected.logo} />
|
||||
{/if}
|
||||
</button>
|
||||
|
||||
@@ -148,7 +148,7 @@ export function saveSeparator (
|
||||
}
|
||||
|
||||
export const panelSeparators: DefSeparators = [
|
||||
{ minSize: 30, size: 'auto', maxSize: 'auto' },
|
||||
{ minSize: 20, size: 'auto', maxSize: 'auto' },
|
||||
{ minSize: 17, size: 25, maxSize: 35, float: 'aside' }
|
||||
]
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { getMetadata, translate } from '@hcengineering/platform'
|
||||
import { Button, IconClose, Label, themeStore } from '@hcengineering/ui'
|
||||
import { Button, Html, IconClose, Label, themeStore } from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import calendar from '../plugin'
|
||||
import { concatLink } from '@hcengineering/core'
|
||||
@@ -72,7 +72,7 @@
|
||||
<Label label={calendar.string.RedirectGoogle} />
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
{@html label}
|
||||
<Html value={label} />
|
||||
</div>
|
||||
|
||||
<img class="image" src={getMetadata(calendar.image.Permissions)} alt="" />
|
||||
|
||||
@@ -184,7 +184,7 @@
|
||||
{getName(client.getHierarchy(), selected)}
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="flex-presenter not-selected">
|
||||
<div class="flex-presenter not-selected" class:cursor-default={readonly}>
|
||||
{#if icon}
|
||||
<div class="icon w-4 flex-no-shrink" class:small-gap={size === 'inline' || size === 'small'}>
|
||||
<Icon {icon} size={'small'} />
|
||||
|
||||
@@ -97,6 +97,7 @@
|
||||
on:close={() => {
|
||||
dispatch('close')
|
||||
}}
|
||||
withoutInput={readonly}
|
||||
>
|
||||
<svelte:fragment slot="title">
|
||||
<DocNavLink noUnderline {object}>
|
||||
@@ -106,7 +107,7 @@
|
||||
|
||||
<svelte:fragment slot="attributes" let:direction={dir}>
|
||||
{#if dir === 'column'}
|
||||
<DocAttributeBar {object} {mixins} {ignoreKeys} />
|
||||
<DocAttributeBar {object} {mixins} {ignoreKeys} {readonly} />
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
|
||||
@@ -117,14 +118,16 @@
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="utils">
|
||||
<Button
|
||||
icon={IconMoreH}
|
||||
iconProps={{ size: 'medium' }}
|
||||
kind={'icon'}
|
||||
on:click={(e) => {
|
||||
showMenu(e, { object, excludedActions: [view.action.Open] })
|
||||
}}
|
||||
/>
|
||||
{#if !readonly}
|
||||
<Button
|
||||
icon={IconMoreH}
|
||||
iconProps={{ size: 'medium' }}
|
||||
kind={'icon'}
|
||||
on:click={(e) => {
|
||||
showMenu(e, { object, excludedActions: [view.action.Open] })
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
<Button
|
||||
icon={IconMixin}
|
||||
kind={'icon'}
|
||||
@@ -137,7 +140,7 @@
|
||||
</svelte:fragment>
|
||||
|
||||
<div class="flex-col flex-grow flex-no-shrink step-tb-6">
|
||||
<EditOrganization {object} />
|
||||
<EditOrganization {object} {readonly} />
|
||||
<div class="flex-col flex-grow w-full mt-6 relative">
|
||||
<AttachmentStyleBoxCollabEditor
|
||||
focusIndex={30}
|
||||
@@ -145,6 +148,7 @@
|
||||
key={{ key: 'description', attr: descriptionKey }}
|
||||
placeholder={core.string.Description}
|
||||
enableAttachments={false}
|
||||
{readonly}
|
||||
on:saved={(evt) => {
|
||||
saved = evt.detail
|
||||
}}
|
||||
|
||||
@@ -103,6 +103,7 @@
|
||||
{kind}
|
||||
{size}
|
||||
{justify}
|
||||
disabled={readonly}
|
||||
showTooltip={label ? { label, direction: labelDirection } : undefined}
|
||||
on:click={addPerson}
|
||||
>
|
||||
|
||||
+1
-1
@@ -148,7 +148,7 @@
|
||||
{/key}
|
||||
</div>
|
||||
{#if rejectingMessage !== undefined && approver.approved === 'rejected'}
|
||||
<div class="reject-message">{@html rejectingMessage}</div>
|
||||
<div class="reject-message">{rejectingMessage}</div>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
@@ -45,9 +45,9 @@
|
||||
"@hcengineering/ui": "^0.6.15",
|
||||
"@hcengineering/view": "^0.6.13",
|
||||
"@hcengineering/view-resources": "^0.6.0",
|
||||
"@hcengineering/highlight": "^0.6.0",
|
||||
"@hcengineering/diffview": "^0.6.0",
|
||||
"fast-equals": "^5.0.1",
|
||||
"highlight.js": "~11.8.0",
|
||||
"diff2html": "~3.4.35"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,10 +13,10 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Loading, themeStore } from '@hcengineering/ui'
|
||||
import { Html, Loading, themeStore } from '@hcengineering/ui'
|
||||
import { DiffFile, DiffLine, DiffLineType, DiffViewMode } from '@hcengineering/diffview'
|
||||
|
||||
import { DiffLineRenderResult, RenderOptions, renderHunk } from '../highlight'
|
||||
import { DiffLineRenderResult, RenderOptions, renderHunk } from '../render'
|
||||
|
||||
export let file: DiffFile
|
||||
export let mode: DiffViewMode
|
||||
@@ -110,7 +110,7 @@
|
||||
<td class="num-line {lineClass}">{line.oldNumber ?? ''}</td>
|
||||
<td class="num-line {lineClass}">{line.newNumber ?? ''}</td>
|
||||
<td class="code-line select-text {lineClass}" data-code-marker={line.prefix}>
|
||||
{@html line.content}
|
||||
<Html value={line.content} />
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
@@ -130,11 +130,11 @@
|
||||
<tr>
|
||||
<td class="num-line {beforeLineClass}">{before.oldNumber ?? ''}</td>
|
||||
<td class="code-line select-text {beforeLineClass}" data-code-marker={before.prefix}>
|
||||
{@html before.content}
|
||||
<Html value={before.content} />
|
||||
</td>
|
||||
<td class="num-line {afterLineClass}">{after.newNumber ?? ''}</td>
|
||||
<td class="code-line select-text {afterLineClass}" data-code-marker={after.prefix}>
|
||||
{@html after.content}
|
||||
<Html value={after.content} />
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
|
||||
@@ -13,10 +13,9 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { highlightText } from '@hcengineering/highlight'
|
||||
import { Html } from '@hcengineering/ui'
|
||||
|
||||
import { highlightText } from '../highlight'
|
||||
|
||||
export let value: string
|
||||
export let language: string | undefined = undefined
|
||||
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
//
|
||||
// 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 './highlight'
|
||||
export * from './render'
|
||||
@@ -14,9 +14,11 @@
|
||||
//
|
||||
|
||||
import { type Resources } from '@hcengineering/platform'
|
||||
|
||||
import DiffView from './components/DiffView.svelte'
|
||||
import Highlight from './components/Highlight.svelte'
|
||||
import InlineDiffView from './components/InlineDiffView.svelte'
|
||||
|
||||
export default async (): Promise<Resources> => ({
|
||||
component: {
|
||||
DiffView,
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@
|
||||
//
|
||||
|
||||
import { type DiffHunk, type DiffLine, DiffLineType, EmptyLine } from '@hcengineering/diffview'
|
||||
import { type HighlightOptions, highlightLines } from './highlight'
|
||||
import { type HighlightOptions, highlightLines } from '@hcengineering/highlight'
|
||||
|
||||
export interface RenderOptions {
|
||||
syntaxHighlight: {
|
||||
@@ -15,7 +15,7 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { getMetadata, translate } from '@hcengineering/platform'
|
||||
import { Button, IconClose, Label, themeStore } from '@hcengineering/ui'
|
||||
import { Button, Html, IconClose, Label, themeStore } from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import gmail from '../plugin'
|
||||
import { concatLink } from '@hcengineering/core'
|
||||
@@ -70,7 +70,7 @@
|
||||
<div class="content">
|
||||
<Label label={gmail.string.RedirectGoogle} />
|
||||
<div class="mt-2">
|
||||
{@html label}
|
||||
<Html value={label} />
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
|
||||
@@ -28,7 +28,8 @@
|
||||
getCurrentLocation,
|
||||
showPopup,
|
||||
type AnySvelteComponent,
|
||||
type CompAndProps
|
||||
type CompAndProps,
|
||||
resizeObserver
|
||||
} from '@hcengineering/ui'
|
||||
import view from '@hcengineering/view'
|
||||
import plugin from '../plugin'
|
||||
@@ -51,6 +52,7 @@
|
||||
import CamSettingPopup from './CamSettingPopup.svelte'
|
||||
import MicSettingPopup from './MicSettingPopup.svelte'
|
||||
import RoomAccessPopup from './RoomAccessPopup.svelte'
|
||||
import { afterUpdate } from 'svelte'
|
||||
|
||||
export let room: Room
|
||||
export let fullScreen: boolean = false
|
||||
@@ -59,6 +61,11 @@
|
||||
const allowShare: boolean = true
|
||||
let allowLeave: boolean = false
|
||||
let popup: CompAndProps | undefined = undefined
|
||||
let grow: HTMLElement
|
||||
let leftPanel: HTMLElement
|
||||
let leftPanelSize: number = 0
|
||||
let noLabel: boolean = false
|
||||
let combinePanel: boolean = false
|
||||
|
||||
$: allowCam = $currentRoom?.type === RoomType.Video
|
||||
$: allowLeave = $myInfo?.room !== ($myOffice?._id ?? plugin.ids.Reception)
|
||||
@@ -149,9 +156,22 @@
|
||||
|
||||
const camKeys = client.getModel().findAllSync(view.class.Action, { _id: plugin.action.ToggleVideo })?.[0]?.keyBinding
|
||||
const micKeys = client.getModel().findAllSync(view.class.Action, { _id: plugin.action.ToggleMic })?.[0]?.keyBinding
|
||||
|
||||
const checkBar = (): void => {
|
||||
if (grow === undefined || leftPanel === undefined) return
|
||||
if (!noLabel && leftPanel.clientWidth > leftPanelSize) leftPanelSize = leftPanel.clientWidth
|
||||
if (grow.clientWidth - 16 < leftPanel.clientWidth && !noLabel && !combinePanel) noLabel = true
|
||||
else if (grow.clientWidth - 16 < leftPanel.clientWidth && noLabel && !combinePanel) combinePanel = true
|
||||
else if (grow.clientWidth * 2 - 32 > leftPanel.clientWidth && noLabel && combinePanel) combinePanel = false
|
||||
else if (grow.clientWidth - 32 >= leftPanelSize && noLabel && !combinePanel) noLabel = false
|
||||
}
|
||||
afterUpdate(() => {
|
||||
checkBar()
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="bar w-full flex-center flex-gap-2 flex-no-shrink">
|
||||
<div class="bar w-full flex-center flex-gap-2 flex-no-shrink" class:combinePanel use:resizeObserver={checkBar}>
|
||||
<div bind:this={grow} class="flex-grow" />
|
||||
{#if room._id !== plugin.ids.Reception}
|
||||
<ModernButton
|
||||
icon={roomAccessIcon[room.access]}
|
||||
@@ -205,7 +225,7 @@
|
||||
/>
|
||||
{/if}
|
||||
{/if}
|
||||
<div class="bar__left-panel flex-gap-2 flex-center">
|
||||
<div bind:this={leftPanel} class="bar__left-panel flex-gap-2 flex-center">
|
||||
{#if $isConnected}
|
||||
<ModernButton
|
||||
icon={$isFullScreen ? love.icon.ExitFullScreen : love.icon.FullScreen}
|
||||
@@ -232,7 +252,7 @@
|
||||
{#if allowLeave}
|
||||
<ModernButton
|
||||
icon={plugin.icon.LeaveRoom}
|
||||
label={plugin.string.LeaveRoom}
|
||||
label={noLabel ? undefined : plugin.string.LeaveRoom}
|
||||
tooltip={{ label: plugin.string.LeaveRoom, direction: 'top' }}
|
||||
kind={'negative'}
|
||||
size={'large'}
|
||||
@@ -240,6 +260,7 @@
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex-grow" />
|
||||
{#if popup && fullScreen}
|
||||
<PopupInstance
|
||||
is={popup.is}
|
||||
@@ -259,6 +280,7 @@
|
||||
|
||||
<style lang="scss">
|
||||
.bar {
|
||||
overflow-x: auto;
|
||||
position: relative;
|
||||
padding: 1rem;
|
||||
border-top: 1px solid var(--theme-divider-color);
|
||||
@@ -270,5 +292,9 @@
|
||||
right: 1rem;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&.combinePanel .bar__left-panel {
|
||||
position: static;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import { personByIdStore } from '@hcengineering/contact-resources'
|
||||
import { Room as TypeRoom } from '@hcengineering/love'
|
||||
import { getMetadata } from '@hcengineering/platform'
|
||||
import { Label, Loading, resizeObserver } from '@hcengineering/ui'
|
||||
import { Label, Loading, resizeObserver, deviceOptionsStore as deviceInfo } from '@hcengineering/ui'
|
||||
import {
|
||||
LocalParticipant,
|
||||
LocalTrackPublication,
|
||||
@@ -333,7 +333,7 @@
|
||||
$: if (((document.fullscreenElement && !$isFullScreen) || $isFullScreen) && roomEl) toggleFullscreen()
|
||||
</script>
|
||||
|
||||
<div bind:this={roomEl} class="flex-col-center w-full h-full" class:theme-dark={$isFullScreen}>
|
||||
<div bind:this={roomEl} class="flex-col-center w-full h-full right-navpanel-border" class:theme-dark={$isFullScreen}>
|
||||
{#if $isConnected && !$isCurrentInstanceConnected}
|
||||
<div class="flex justify-center error h-full w-full clear-mins">
|
||||
<Label label={love.string.AnotherWindowError} />
|
||||
@@ -345,7 +345,13 @@
|
||||
{:else if loading}
|
||||
<Loading />
|
||||
{/if}
|
||||
<div class="room-container" class:sharing={$screenSharing} class:many={columns > 3} class:hidden={loading}>
|
||||
<div
|
||||
class="room-container"
|
||||
class:sharing={$screenSharing}
|
||||
class:many={columns > 3}
|
||||
class:hidden={loading}
|
||||
class:mobile={$deviceInfo.isMobile}
|
||||
>
|
||||
<div class="screenContainer">
|
||||
<video class="screen" bind:this={screen}></video>
|
||||
</div>
|
||||
@@ -452,6 +458,15 @@
|
||||
gap: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
&.mobile {
|
||||
padding: var(--spacing-0_5);
|
||||
|
||||
&:not(.sharing) .videoGrid,
|
||||
&.sharing {
|
||||
gap: var(--spacing-0_5);
|
||||
}
|
||||
}
|
||||
}
|
||||
.hidden {
|
||||
display: none;
|
||||
|
||||
@@ -24,8 +24,8 @@
|
||||
import SectionEmpty from './SectionEmpty.svelte'
|
||||
|
||||
export let objectId: Ref<Doc>
|
||||
|
||||
export let applications: number
|
||||
export let readonly: boolean = false
|
||||
|
||||
const createApp = (ev: MouseEvent): void => {
|
||||
showPopup(CreateApplication, { candidate: objectId, preserveCandidate: true }, ev.target as HTMLElement)
|
||||
@@ -46,7 +46,9 @@
|
||||
bind:loading
|
||||
bind:preference
|
||||
/>
|
||||
<Button id="appls.add" icon={IconAdd} kind={'ghost'} on:click={createApp} />
|
||||
{#if !readonly}
|
||||
<Button id="appls.add" icon={IconAdd} kind={'ghost'} on:click={createApp} />
|
||||
{/if}
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
|
||||
@@ -58,15 +60,18 @@
|
||||
config={preference?.config ?? viewlet.config}
|
||||
query={{ attachedTo: objectId, ...(viewlet?.baseQuery ?? {}) }}
|
||||
loadingProps={{ length: applications }}
|
||||
{readonly}
|
||||
/>
|
||||
</Scroller>
|
||||
{:else}
|
||||
<SectionEmpty icon={FileDuo} label={recruit.string.NoApplicationsForTalent}>
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<span class="over-underline content-color" on:click={createApp}>
|
||||
<Label label={recruit.string.CreateAnApplication} />
|
||||
</span>
|
||||
{#if !readonly}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<span class="over-underline content-color" on:click={createApp}>
|
||||
<Label label={recruit.string.CreateAnApplication} />
|
||||
</span>
|
||||
{/if}
|
||||
</SectionEmpty>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
|
||||
export let _id: Ref<Vacancy>
|
||||
export let embedded: boolean = false
|
||||
export let readonly = false
|
||||
|
||||
let object: Required<Vacancy>
|
||||
let rawName: string = ''
|
||||
@@ -140,6 +141,7 @@
|
||||
<DocAttributeBar
|
||||
{object}
|
||||
{mixins}
|
||||
{readonly}
|
||||
ignoreKeys={['name', 'fullDescription', 'private', 'archived', 'type', 'owners']}
|
||||
/>
|
||||
{/if}
|
||||
@@ -151,6 +153,7 @@
|
||||
kind={'large-style'}
|
||||
focusable
|
||||
autoFocus={!embedded}
|
||||
disabled={readonly}
|
||||
on:blur={save}
|
||||
/>
|
||||
|
||||
@@ -160,14 +163,16 @@
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="utils">
|
||||
<Button
|
||||
icon={IconMoreH}
|
||||
iconProps={{ size: 'medium' }}
|
||||
kind={'icon'}
|
||||
on:click={(e) => {
|
||||
showMenu(e, { object, excludedActions: [view.action.Open] })
|
||||
}}
|
||||
/>
|
||||
{#if !readonly}
|
||||
<Button
|
||||
icon={IconMoreH}
|
||||
iconProps={{ size: 'medium' }}
|
||||
kind={'icon'}
|
||||
on:click={(e) => {
|
||||
showMenu(e, { object, excludedActions: [view.action.Open] })
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
<Button
|
||||
icon={IconMixin}
|
||||
kind={'icon'}
|
||||
@@ -187,6 +192,7 @@
|
||||
key={{ key: 'fullDescription', attr: descriptionKey }}
|
||||
bind:this={descriptionBox}
|
||||
placeholder={recruit.string.FullDescription}
|
||||
{readonly}
|
||||
on:saved={(evt) => {
|
||||
saved = evt.detail
|
||||
}}
|
||||
@@ -194,7 +200,7 @@
|
||||
</div>
|
||||
|
||||
<div class="w-full mt-6">
|
||||
<VacancyApplications objectId={object._id} />
|
||||
<VacancyApplications objectId={object._id} {readonly} />
|
||||
</div>
|
||||
<div class="w-full mt-6">
|
||||
<Component is={tracker.component.RelatedIssuesSection} props={{ object, label: tracker.string.RelatedIssues }} />
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
import SectionEmpty from './SectionEmpty.svelte'
|
||||
|
||||
export let objectId: Ref<Vacancy>
|
||||
export let readonly = false
|
||||
let applications: number
|
||||
|
||||
const query = createQuery()
|
||||
@@ -60,7 +61,9 @@
|
||||
bind:preference
|
||||
bind:loading
|
||||
/>
|
||||
<Button id="appls.add" icon={IconAdd} kind={'ghost'} on:click={createApp} />
|
||||
{#if !readonly}
|
||||
<Button id="appls.add" icon={IconAdd} kind={'ghost'} on:click={createApp} />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{#if applications > 0}
|
||||
@@ -71,6 +74,7 @@
|
||||
config={preference?.config ?? viewlet.config}
|
||||
query={{ space: objectId }}
|
||||
loadingProps={{ length: applications }}
|
||||
{readonly}
|
||||
/>
|
||||
</Scroller>
|
||||
{:else}
|
||||
@@ -80,9 +84,11 @@
|
||||
<SectionEmpty icon={FileDuo} label={recruit.string.NoApplicationsForVacancy}>
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<span class="over-underline content-color" on:click={createApp}>
|
||||
<Label label={recruit.string.CreateAnApplication} />
|
||||
</span>
|
||||
{#if !readonly}
|
||||
<span class="over-underline content-color" on:click={createApp}>
|
||||
<Label label={recruit.string.CreateAnApplication} />
|
||||
</span>
|
||||
{/if}
|
||||
</SectionEmpty>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -72,11 +72,13 @@
|
||||
</Scroller>
|
||||
{:else}
|
||||
<SectionEmpty icon={FileDuo} label={recruit.string.NoVacancies}>
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<span class="over-underline content-color" on:click={createApp}>
|
||||
<Label label={recruit.string.CreateVacancy} />
|
||||
</span>
|
||||
{#if !readonly}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<span class="over-underline content-color" on:click={createApp}>
|
||||
<Label label={recruit.string.CreateVacancy} />
|
||||
</span>
|
||||
{/if}
|
||||
</SectionEmpty>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
export let object: Doc
|
||||
export let _class: Ref<Class<Doc>>
|
||||
export let key: KeyedAttribute
|
||||
export let readonly: boolean = false
|
||||
|
||||
const client = getClient()
|
||||
|
||||
@@ -56,6 +57,7 @@
|
||||
|
||||
<TagsEditor
|
||||
{key}
|
||||
{readonly}
|
||||
bind:items
|
||||
targetClass={_class}
|
||||
on:open={(evt) => addRef(evt.detail)}
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
export let key: KeyedAttribute
|
||||
export let showTitle = true
|
||||
export let schema: '0' | '3' | '9' = key.attr.schema ?? '0'
|
||||
export let readonly = false
|
||||
|
||||
let elements: IdMap<TagElement> = new Map()
|
||||
const elementQuery = createQuery()
|
||||
@@ -113,13 +114,15 @@
|
||||
<Label label={key.attr.label} />
|
||||
</span>
|
||||
<div class="buttons-group x-small">
|
||||
<Button
|
||||
icon={IconAdd}
|
||||
kind={'ghost'}
|
||||
showTooltip={{ label: tags.string.AddTagTooltip, props: { word: keyLabel } }}
|
||||
id={'add-tag'}
|
||||
on:click={addTag}
|
||||
/>
|
||||
{#if !readonly}
|
||||
<Button
|
||||
icon={IconAdd}
|
||||
kind={'ghost'}
|
||||
showTooltip={{ label: tags.string.AddTagTooltip, props: { word: keyLabel } }}
|
||||
id={'add-tag'}
|
||||
on:click={addTag}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
"@hcengineering/analytics": "^0.6.0",
|
||||
"@hcengineering/presentation": "^0.6.3",
|
||||
"@hcengineering/core": "^0.6.32",
|
||||
"@hcengineering/highlight": "^0.6.0",
|
||||
"@hcengineering/view": "^0.6.13",
|
||||
"@hcengineering/text": "^0.6.5",
|
||||
"@hcengineering/text-editor": "^0.6.0",
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { grammars } from '@hcengineering/highlight'
|
||||
import { backtickInputRegex, codeBlockOptions, tildeInputRegex } from '@hcengineering/text'
|
||||
import { DropdownLabelsPopup, getEventPositionElement, showPopup } from '@hcengineering/ui'
|
||||
import { isActive, textblockTypeInputRule } from '@tiptap/core'
|
||||
@@ -20,7 +21,7 @@ import { type CodeBlockLowlightOptions, CodeBlockLowlight } from '@tiptap/extens
|
||||
import { type Node as ProseMirrorNode } from '@tiptap/pm/model'
|
||||
import { Plugin, PluginKey } from '@tiptap/pm/state'
|
||||
import { Decoration, DecorationSet, type EditorView } from '@tiptap/pm/view'
|
||||
import { common, createLowlight } from 'lowlight'
|
||||
import { createLowlight } from 'lowlight'
|
||||
import { isChangeEditable } from './editable'
|
||||
|
||||
type Lowlight = ReturnType<typeof createLowlight>
|
||||
@@ -31,7 +32,7 @@ const chevronSvg = `<svg width="16" height="16" viewBox="0 0 32 32" fill="curren
|
||||
|
||||
export const codeBlockHighlightOptions: CodeBlockLowlightOptions = {
|
||||
...codeBlockOptions,
|
||||
lowlight: createLowlight(common)
|
||||
lowlight: createLowlight(grammars)
|
||||
}
|
||||
|
||||
export const CodeBlockHighlighExtension = CodeBlockLowlight.extend<CodeBlockLowlightOptions>({
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
import { createQuery } from '@hcengineering/presentation'
|
||||
import { Label } from '@hcengineering/ui'
|
||||
import { ToDo, WorkSlot } from '@hcengineering/time'
|
||||
import time from '../plugin'
|
||||
import ToDoPresenter from './ToDoPresenter.svelte'
|
||||
import calendar from '@hcengineering/calendar'
|
||||
|
||||
export let event: WorkSlot
|
||||
export let oneRow: boolean = false
|
||||
@@ -26,13 +26,15 @@
|
||||
let todo: ToDo
|
||||
|
||||
const query = createQuery()
|
||||
$: query.query(event.attachedToClass, { _id: event.attachedTo }, (res) => {
|
||||
todo = res[0]
|
||||
})
|
||||
$: !hideDetails
|
||||
? query.query(event.attachedToClass, { _id: event.attachedTo }, (res) => {
|
||||
todo = res[0]
|
||||
})
|
||||
: query.unsubscribe()
|
||||
</script>
|
||||
|
||||
{#if hideDetails}
|
||||
<Label label={time.string.WorkSlot} />
|
||||
<Label label={calendar.string.Busy} />
|
||||
{:else if todo}
|
||||
<ToDoPresenter value={todo} withoutSpace={oneRow} />
|
||||
{/if}
|
||||
|
||||
+7
-6
@@ -106,14 +106,15 @@
|
||||
|
||||
&:not(.readonly) {
|
||||
cursor: pointer;
|
||||
}
|
||||
.add-action {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.add-action {
|
||||
visibility: visible;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.add-action {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import Play from '../icons/Play.svelte'
|
||||
import { getPlatformColor, themeStore } from '@hcengineering/ui'
|
||||
import { Html, getPlatformColor, themeStore } from '@hcengineering/ui'
|
||||
|
||||
export let href: string
|
||||
const maxWidth = 400
|
||||
@@ -75,7 +75,7 @@
|
||||
</div>
|
||||
{:else}
|
||||
<div bind:this={emb}>
|
||||
{@html data.html}
|
||||
<Html value={data.html} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -647,6 +647,18 @@
|
||||
const checkOnHide = (): void => {
|
||||
if ($deviceInfo.navigator.visible && $deviceInfo.docWidth <= 1024) $deviceInfo.navigator.visible = false
|
||||
}
|
||||
let oldNavVisible: boolean = $deviceInfo.navigator.visible
|
||||
let oldASideVisible: boolean = $deviceInfo.aside.visible
|
||||
$: if (oldNavVisible !== $deviceInfo.navigator.visible || oldASideVisible !== $deviceInfo.aside.visible) {
|
||||
if ($deviceInfo.isMobile && $deviceInfo.isPortrait && $deviceInfo.navigator.float) {
|
||||
if ($deviceInfo.navigator.visible && $deviceInfo.aside.visible) {
|
||||
if (oldNavVisible) $deviceInfo.navigator.visible = false
|
||||
else $deviceInfo.aside.visible = false
|
||||
}
|
||||
}
|
||||
oldNavVisible = $deviceInfo.navigator.visible
|
||||
oldASideVisible = $deviceInfo.aside.visible
|
||||
}
|
||||
$: $deviceInfo.navigator.direction = $deviceInfo.isMobile && $deviceInfo.isPortrait ? 'horizontal' : 'vertical'
|
||||
let appsMini: boolean
|
||||
$: appsMini =
|
||||
|
||||
@@ -57,23 +57,25 @@
|
||||
</div>
|
||||
</div>
|
||||
{:else if !mini}
|
||||
<ScrollerBar bind:scroller padding={'.25rem 0'}>
|
||||
<div class="flex-row-center gap-1 py-1 overflow-x-auto">
|
||||
{#each $tabsStore as tab (tab._id)}
|
||||
<WorkbenchTabPresenter {tab} />
|
||||
{/each}
|
||||
</ScrollerBar>
|
||||
</div>
|
||||
{:else if selectedTab !== undefined}
|
||||
<WorkbenchTabPresenter tab={selectedTab} />
|
||||
<ButtonIcon
|
||||
bind:element
|
||||
icon={IconMoreH}
|
||||
iconProps={{ fill: 'var(--theme-dark-color)' }}
|
||||
size={'extra-small'}
|
||||
kind={'tertiary'}
|
||||
hasMenu
|
||||
{pressed}
|
||||
on:click={showTabs}
|
||||
/>
|
||||
{#if $tabsStore.length > 1}
|
||||
<ButtonIcon
|
||||
bind:element
|
||||
icon={IconMoreH}
|
||||
iconProps={{ fill: 'var(--theme-dark-color)' }}
|
||||
size={'extra-small'}
|
||||
kind={'tertiary'}
|
||||
hasMenu
|
||||
{pressed}
|
||||
on:click={showTabs}
|
||||
/>
|
||||
{/if}
|
||||
{/if}
|
||||
{#if !popup}
|
||||
<ButtonIcon
|
||||
|
||||
@@ -445,6 +445,11 @@
|
||||
"projectFolder": "packages/storage",
|
||||
"shouldPublish": false
|
||||
},
|
||||
{
|
||||
"packageName": "@hcengineering/highlight",
|
||||
"projectFolder": "packages/highlight",
|
||||
"shouldPublish": false
|
||||
},
|
||||
{
|
||||
"packageName": "@hcengineering/collaboration",
|
||||
"projectFolder": "server/collaboration",
|
||||
|
||||
@@ -49,7 +49,7 @@ import {
|
||||
} from '@hcengineering/server-notification-resources'
|
||||
|
||||
async function getOldDepartment (
|
||||
currentTx: TxMixin<Employee, Staff> | TxUpdateDoc<Employee>,
|
||||
currentTx: TxMixin<Employee, Staff>,
|
||||
control: TriggerControl
|
||||
): Promise<Ref<Department> | undefined> {
|
||||
const txes = await control.findAll<TxMixin<Employee, Staff>>(
|
||||
@@ -73,7 +73,7 @@ async function getOldDepartment (
|
||||
async function buildHierarchy (_id: Ref<Department>, control: TriggerControl): Promise<Department[]> {
|
||||
const res: Department[] = []
|
||||
const ancestors = new Map<Ref<Department>, Ref<Department>>()
|
||||
const departments = await control.findAll(control.ctx, hr.class.Department, {})
|
||||
const departments = await control.queryFind(control.ctx, hr.class.Department, {})
|
||||
for (const department of departments) {
|
||||
if (department._id === hr.ids.Head || department.parent === undefined) continue
|
||||
ancestors.set(department._id, department.parent)
|
||||
@@ -240,20 +240,20 @@ export async function OnEmployee (tx: Tx, control: TriggerControl): Promise<Tx[]
|
||||
*/
|
||||
export async function OnEmployeeDeactivate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
const actualTx = TxProcessor.extractTx(tx)
|
||||
if (core.class.TxUpdateDoc !== actualTx._class) {
|
||||
if (core.class.TxMixin !== actualTx._class) {
|
||||
return []
|
||||
}
|
||||
const ctx = actualTx as TxUpdateDoc<Employee>
|
||||
if (ctx.objectClass !== contact.mixin.Employee || ctx.operations.active !== false) {
|
||||
const ctx = actualTx as TxMixin<Person, Employee>
|
||||
if (ctx.mixin !== contact.mixin.Employee || ctx.attributes.active !== false) {
|
||||
return []
|
||||
}
|
||||
|
||||
const targetAccount = control.modelDb.getAccountByPersonId(ctx.objectId) as PersonAccount[]
|
||||
if (targetAccount.length === 0) return []
|
||||
const lastDepartment = await getOldDepartment(ctx, control)
|
||||
if (lastDepartment === undefined) return []
|
||||
const set = new Set(targetAccount.map((it) => it._id))
|
||||
|
||||
const removed = await buildHierarchy(lastDepartment, control)
|
||||
const departments = await control.queryFind(control.ctx, hr.class.Department, {})
|
||||
const removed = departments.filter((dep) => dep.members.some((p) => set.has(p)))
|
||||
return getTxes(
|
||||
control.txFactory,
|
||||
targetAccount.map((it) => it._id),
|
||||
|
||||
@@ -607,6 +607,8 @@ export async function createPushNotification (
|
||||
}
|
||||
}
|
||||
|
||||
const errorMessages = ['expired', 'Unregistered', 'No such subscription']
|
||||
|
||||
async function sendPushToSubscription (
|
||||
control: TriggerControl,
|
||||
targetUser: Ref<Account>,
|
||||
@@ -617,9 +619,11 @@ async function sendPushToSubscription (
|
||||
await webpush.sendNotification(subscription, JSON.stringify(data))
|
||||
} catch (err) {
|
||||
control.ctx.info('Cannot send push notification to', { user: targetUser, err })
|
||||
if (err instanceof WebPushError && err.body.includes('expired')) {
|
||||
const tx = control.txFactory.createTxRemoveDoc(subscription._class, subscription.space, subscription._id)
|
||||
await control.apply(control.ctx, [tx])
|
||||
if (err instanceof WebPushError) {
|
||||
if (errorMessages.some((p) => (err as WebPushError).body.includes(p))) {
|
||||
const tx = control.txFactory.createTxRemoveDoc(subscription._class, subscription.space, subscription._id)
|
||||
await control.apply(control.ctx, [tx])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,20 +85,19 @@ export async function applicationTextPresenter (doc: Doc, control: TriggerContro
|
||||
* @public
|
||||
*/
|
||||
export async function OnRecruitUpdate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
const actualTx = TxProcessor.extractTx(tx)
|
||||
const cud = actualTx as TxCUD<Doc>
|
||||
if (!control.hierarchy.isDerived(cud.objectClass, recruit.class.Vacancy)) {
|
||||
const actualTx = TxProcessor.extractTx(tx) as TxCUD<Doc>
|
||||
if (!control.hierarchy.isDerived(actualTx.objectClass, recruit.class.Vacancy)) {
|
||||
return []
|
||||
}
|
||||
|
||||
const res: Tx[] = []
|
||||
|
||||
if (actualTx._class === core.class.TxCreateDoc) {
|
||||
handleVacancyCreate(control, cud, actualTx, res)
|
||||
handleVacancyCreate(control, actualTx, res)
|
||||
} else if (actualTx._class === core.class.TxUpdateDoc) {
|
||||
await handleVacancyUpdate(control, cud, res)
|
||||
await handleVacancyUpdate(control, actualTx, res)
|
||||
} else if (actualTx._class === core.class.TxRemoveDoc) {
|
||||
await handleVacancyRemove(control, cud, actualTx)
|
||||
handleVacancyRemove(control, actualTx, res)
|
||||
}
|
||||
return res
|
||||
}
|
||||
@@ -156,16 +155,13 @@ async function handleVacancyUpdate (control: TriggerControl, cud: TxCUD<Doc>, re
|
||||
}
|
||||
}
|
||||
|
||||
async function handleVacancyRemove (control: TriggerControl, cud: TxCUD<Doc>, actualTx: Tx): Promise<void> {
|
||||
const removeTx = actualTx as TxRemoveDoc<Vacancy>
|
||||
function handleVacancyRemove (control: TriggerControl, cud: TxCUD<Doc>, res: Tx[]): void {
|
||||
const removeTx = cud as TxRemoveDoc<Vacancy>
|
||||
// It could be null or new value
|
||||
const txes = (
|
||||
await control.findAll(control.ctx, core.class.TxCUD, {
|
||||
objectId: removeTx.objectId
|
||||
})
|
||||
).filter((it) => it._id !== removeTx._id)
|
||||
const vacancy = TxProcessor.buildDoc2Doc(txes) as Vacancy
|
||||
const res: Tx[] = []
|
||||
const vacancy = control.removedMap.get(removeTx.objectId) as Vacancy
|
||||
if (vacancy === undefined) {
|
||||
return
|
||||
}
|
||||
if (vacancy.company != null) {
|
||||
// We have old value
|
||||
res.push(
|
||||
@@ -182,8 +178,8 @@ async function handleVacancyRemove (control: TriggerControl, cud: TxCUD<Doc>, ac
|
||||
}
|
||||
}
|
||||
|
||||
function handleVacancyCreate (control: TriggerControl, cud: TxCUD<Doc>, actualTx: Tx, res: Tx[]): void {
|
||||
const createTx = actualTx as TxCreateDoc<Vacancy>
|
||||
function handleVacancyCreate (control: TriggerControl, cud: TxCUD<Doc>, res: Tx[]): void {
|
||||
const createTx = cud as TxCreateDoc<Vacancy>
|
||||
const vacancy = TxProcessor.createDoc2Doc(createTx)
|
||||
if (vacancy.company !== undefined) {
|
||||
res.push(
|
||||
|
||||
@@ -69,6 +69,21 @@ export async function OnTask (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
return []
|
||||
}
|
||||
|
||||
export async function OnWorkSlotUpdate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
const actualTx = TxProcessor.extractTx(tx) as TxCUD<WorkSlot>
|
||||
if (!control.hierarchy.isDerived(actualTx.objectClass, time.class.WorkSlot)) return []
|
||||
if (!control.hierarchy.isDerived(actualTx._class, core.class.TxUpdateDoc)) return []
|
||||
const updTx = actualTx as TxUpdateDoc<WorkSlot>
|
||||
const visibility = updTx.operations.visibility
|
||||
if (visibility !== undefined) {
|
||||
const workslot = (await control.findAll(control.ctx, time.class.WorkSlot, { _id: updTx.objectId }, { limit: 1 }))[0]
|
||||
if (workslot === undefined) return []
|
||||
const todo = (await control.findAll(control.ctx, time.class.ToDo, { _id: workslot.attachedTo }))[0]
|
||||
return [control.txFactory.createTxUpdateDoc(todo._class, todo.space, todo._id, { visibility })]
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
export async function OnWorkSlotCreate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
const actualTx = TxProcessor.extractTx(tx) as TxCUD<WorkSlot>
|
||||
if (!control.hierarchy.isDerived(actualTx.objectClass, time.class.WorkSlot)) return []
|
||||
@@ -578,6 +593,22 @@ async function changeIssueStatusHandler (
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (status.category === task.statusCategory.Won || status.category === task.statusCategory.Lost) {
|
||||
const issue = (await control.findAll(control.ctx, tracker.class.Issue, { _id: issueId }))[0]
|
||||
if (issue !== undefined) {
|
||||
const todos = await control.findAll(control.ctx, time.class.ToDo, {
|
||||
attachedTo: issue._id,
|
||||
doneOn: null
|
||||
})
|
||||
const res: Tx[] = []
|
||||
const now = Date.now()
|
||||
for (const todo of todos) {
|
||||
if (todo.doneOn == null) {
|
||||
res.push(control.txFactory.createTxUpdateDoc(todo._class, todo.space, todo._id, { doneOn: now }))
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
}
|
||||
return []
|
||||
}
|
||||
@@ -648,6 +679,7 @@ export default async () => ({
|
||||
OnToDoUpdate,
|
||||
OnToDoRemove,
|
||||
OnToDoCreate,
|
||||
OnWorkSlotCreate
|
||||
OnWorkSlotCreate,
|
||||
OnWorkSlotUpdate
|
||||
}
|
||||
})
|
||||
|
||||
@@ -56,6 +56,7 @@ export default plugin(serverTimeId, {
|
||||
OnToDoUpdate: '' as Resource<TriggerFunc>,
|
||||
OnToDoRemove: '' as Resource<TriggerFunc>,
|
||||
OnToDoCreate: '' as Resource<TriggerFunc>,
|
||||
OnWorkSlotCreate: '' as Resource<TriggerFunc>
|
||||
OnWorkSlotCreate: '' as Resource<TriggerFunc>,
|
||||
OnWorkSlotUpdate: '' as Resource<TriggerFunc>
|
||||
}
|
||||
})
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import core, { AnyAttribute, Hierarchy, Tx, TxCUD, TxProcessor, TxRemoveDoc } from '@hcengineering/core'
|
||||
import core, { AnyAttribute, Hierarchy, Tx, TxRemoveDoc } from '@hcengineering/core'
|
||||
import type { TriggerControl } from '@hcengineering/server-core'
|
||||
import view from '@hcengineering/view'
|
||||
|
||||
@@ -24,8 +24,7 @@ export async function OnCustomAttributeRemove (tx: Tx, control: TriggerControl):
|
||||
const hierarchy = control.hierarchy
|
||||
const ptx = tx as TxRemoveDoc<AnyAttribute>
|
||||
if (!checkTx(ptx, hierarchy)) return []
|
||||
const txes = await control.findAll<TxCUD<AnyAttribute>>(control.ctx, core.class.TxCUD, { objectId: ptx.objectId })
|
||||
const attribute = TxProcessor.buildDoc2Doc<AnyAttribute>(txes)
|
||||
const attribute = control.removedMap.get(ptx.objectId) as AnyAttribute
|
||||
if (attribute === undefined) return []
|
||||
const preferences = await control.findAll(control.ctx, view.class.ViewletPreference, {
|
||||
config: attribute.name,
|
||||
|
||||
@@ -35,7 +35,7 @@ import fs, { createReadStream, mkdtempSync } from 'fs'
|
||||
import { rm, writeFile } from 'fs/promises'
|
||||
import { tmpdir } from 'os'
|
||||
|
||||
const cacheControlValue = 'public, max-age=365d'
|
||||
const cacheControlValue = 'public, no-cache, must-revalidate, max-age=365d'
|
||||
const cacheControlNoCache = 'public, no-store, no-cache, must-revalidate, max-age=0'
|
||||
|
||||
async function storageUpload (
|
||||
@@ -385,14 +385,14 @@ export function start (
|
||||
'handle-file',
|
||||
{},
|
||||
async (ctx) => {
|
||||
let payload: Token = { email: 'guest', workspace: { name: req.query.workspace as string } }
|
||||
try {
|
||||
const cookies = ((req?.headers?.cookie as string) ?? '').split(';').map((it) => it.trim().split('='))
|
||||
|
||||
const token =
|
||||
cookies.find((it) => it[0] === 'presentation-metadata-Token')?.[1] ??
|
||||
(req.query.token as string | undefined)
|
||||
payload = token !== undefined ? decodeToken(token) : payload
|
||||
(req.query.token as string | undefined) ??
|
||||
''
|
||||
const payload = decodeToken(token)
|
||||
|
||||
const uuid = req.params.file ?? req.query.file
|
||||
if (uuid === undefined) {
|
||||
@@ -412,20 +412,6 @@ export function start (
|
||||
return
|
||||
}
|
||||
|
||||
// try image and octet streams
|
||||
const isImage =
|
||||
blobInfo.contentType.includes('image/') || blobInfo.contentType.includes('application/octet-stream')
|
||||
|
||||
if (token === undefined) {
|
||||
if (blobInfo !== undefined && !isImage) {
|
||||
// Do not allow to return non images with no token.
|
||||
if (token === undefined) {
|
||||
res.status(403).send()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (req.method === 'HEAD') {
|
||||
res.writeHead(200, {
|
||||
'accept-ranges': 'bytes',
|
||||
@@ -438,6 +424,9 @@ export function start (
|
||||
res.end()
|
||||
return
|
||||
}
|
||||
// try image and octet streams
|
||||
const isImage =
|
||||
blobInfo.contentType.includes('image/') || blobInfo.contentType.includes('application/octet-stream')
|
||||
|
||||
const size = req.query.size !== undefined ? parseInt(req.query.size as string) : undefined
|
||||
const accept = req.headers.accept
|
||||
@@ -475,9 +464,7 @@ export function start (
|
||||
error?.Code === 'NoSuchKey'
|
||||
) {
|
||||
ctx.error('No such storage key', {
|
||||
file: req.query.file,
|
||||
workspace: payload?.workspace,
|
||||
email: payload?.email
|
||||
file: req.query.file
|
||||
})
|
||||
res.status(404).send()
|
||||
return
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
DO $$
|
||||
DECLARE
|
||||
tbl_name text;
|
||||
hash_col_not_exists boolean;
|
||||
data_col_exists boolean;
|
||||
BEGIN
|
||||
FOR tbl_name IN
|
||||
SELECT table_name
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = 'public'
|
||||
AND table_type = 'BASE TABLE'
|
||||
LOOP
|
||||
EXECUTE format('
|
||||
SELECT EXISTS (
|
||||
SELECT 1
|
||||
FROM information_schema.columns
|
||||
WHERE table_name = %L
|
||||
AND column_name = ''data''
|
||||
AND data_type = ''jsonb''
|
||||
);', tbl_name) INTO data_col_exists;
|
||||
|
||||
EXECUTE format('
|
||||
SELECT NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM information_schema.columns
|
||||
WHERE table_name = %L
|
||||
AND column_name = ''"%%hash%%"''
|
||||
);', tbl_name) INTO hash_col_not_exists;
|
||||
|
||||
IF data_col_exists AND hash_col_not_exists THEN
|
||||
EXECUTE format('
|
||||
ALTER TABLE %I ADD COLUMN "%%hash%%" text;', tbl_name);
|
||||
EXECUTE format('
|
||||
UPDATE %I SET "%%hash%%" = data->>''%%data%%'';', tbl_name);
|
||||
END IF;
|
||||
END LOOP;
|
||||
END $$;
|
||||
@@ -0,0 +1,25 @@
|
||||
ALTER TABLE notification_dnc
|
||||
ADD "objectId" text,
|
||||
ADD "objectClass" text,
|
||||
ADD "user" text;
|
||||
|
||||
ALTER TABLE notification_dnc
|
||||
DROP COLUMN "attachedTo";
|
||||
|
||||
UPDATE notification_dnc
|
||||
SET "objectId" = (data->>'objectId');
|
||||
|
||||
UPDATE notification_dnc
|
||||
SET "objectClass" = (data->>'objectClass');
|
||||
|
||||
UPDATE notification_dnc
|
||||
SET "user" = (data->>'user');
|
||||
|
||||
ALTER TABLE notification_dnc
|
||||
ALTER COLUMN "objectId" SET NOT NULL;
|
||||
|
||||
ALTER TABLE notification_dnc
|
||||
ALTER COLUMN "objectClass" SET NOT NULL;
|
||||
|
||||
ALTER TABLE notification_dnc
|
||||
ALTER COLUMN "user" SET NOT NULL;
|
||||
@@ -0,0 +1,11 @@
|
||||
ALTER TABLE doc_index_state
|
||||
ADD "needIndex" bool;
|
||||
|
||||
ALTER TABLE doc_index_state
|
||||
DROP COLUMN "attachedTo";
|
||||
|
||||
UPDATE doc_index_state
|
||||
SET "needIndex" = (data->>'needIndex')::boolean;
|
||||
|
||||
ALTER TABLE doc_index_state
|
||||
ALTER COLUMN "needIndex" SET NOT NULL;
|
||||
@@ -0,0 +1,25 @@
|
||||
ALTER TABLE notification
|
||||
ADD "isViewed" bool,
|
||||
ADD archived bool,
|
||||
ADD "user" text;
|
||||
|
||||
ALTER TABLE notification
|
||||
DROP COLUMN "attachedTo";
|
||||
|
||||
UPDATE notification
|
||||
SET "isViewed" = (data->>'isViewed')::boolean;
|
||||
|
||||
UPDATE notification
|
||||
SET "archived" = (data->>'archived')::boolean;
|
||||
|
||||
UPDATE notification
|
||||
SET "user" = (data->>'user');
|
||||
|
||||
ALTER TABLE notification
|
||||
ALTER COLUMN "isViewed" SET NOT NULL;
|
||||
|
||||
ALTER TABLE notification
|
||||
ALTER COLUMN archived SET NOT NULL;
|
||||
|
||||
ALTER TABLE notification
|
||||
ALTER COLUMN "user" SET NOT NULL;
|
||||
@@ -0,0 +1,15 @@
|
||||
ALTER TABLE tx
|
||||
ADD "objectSpace" text,
|
||||
ADD "objectId" text;
|
||||
|
||||
ALTER TABLE tx
|
||||
DROP COLUMN "attachedTo";
|
||||
|
||||
UPDATE tx
|
||||
SET "objectId" = (data->>'objectId');
|
||||
|
||||
UPDATE tx
|
||||
SET "objectSpace" = (data->>'objectSpace');
|
||||
|
||||
ALTER TABLE tx
|
||||
ALTER COLUMN "objectSpace" SET NOT NULL;
|
||||
@@ -0,0 +1,11 @@
|
||||
ALTER TABLE notification_user
|
||||
ADD "user" text;
|
||||
|
||||
ALTER TABLE notification_user
|
||||
DROP COLUMN "attachedTo";
|
||||
|
||||
UPDATE notification_user
|
||||
SET "user" = (data->>'user');
|
||||
|
||||
ALTER TABLE notification_user
|
||||
ALTER COLUMN "user" SET NOT NULL;
|
||||
@@ -14,4 +14,5 @@
|
||||
//
|
||||
|
||||
export * from './storage'
|
||||
export { getDBClient, convertDoc, createTable, retryTxn, translateDomain, getDocFieldsByDomains } from './utils'
|
||||
export { getDBClient, convertDoc, createTable, retryTxn } from './utils'
|
||||
export { getDocFieldsByDomains, translateDomain } from './schemas'
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { DOMAIN_SPACE } from '@hcengineering/core'
|
||||
import { DOMAIN_DOC_INDEX_STATE, DOMAIN_SPACE, DOMAIN_TX } from '@hcengineering/core'
|
||||
|
||||
type DataType = 'bigint' | 'bool' | 'text' | 'text[]'
|
||||
|
||||
type Schema = Record<string, [DataType, boolean]>
|
||||
|
||||
export const defaultSchema: Schema = {
|
||||
const baseSchema: Schema = {
|
||||
_id: ['text', true],
|
||||
_class: ['text', true],
|
||||
space: ['text', true],
|
||||
@@ -12,25 +12,68 @@ export const defaultSchema: Schema = {
|
||||
createdBy: ['text', false],
|
||||
modifiedOn: ['bigint', true],
|
||||
createdOn: ['bigint', false],
|
||||
'%hash%': ['text', false]
|
||||
}
|
||||
|
||||
const defaultSchema: Schema = {
|
||||
...baseSchema,
|
||||
attachedTo: ['text', false]
|
||||
}
|
||||
|
||||
export const spaceSchema: Schema = {
|
||||
_id: ['text', true],
|
||||
_class: ['text', true],
|
||||
space: ['text', true],
|
||||
modifiedBy: ['text', true],
|
||||
createdBy: ['text', false],
|
||||
modifiedOn: ['bigint', true],
|
||||
createdOn: ['bigint', false],
|
||||
const spaceSchema: Schema = {
|
||||
...baseSchema,
|
||||
private: ['bool', true],
|
||||
members: ['text[]', true]
|
||||
}
|
||||
|
||||
const txSchema: Schema = {
|
||||
...baseSchema,
|
||||
objectSpace: ['text', true],
|
||||
objectId: ['text', false]
|
||||
}
|
||||
|
||||
const notificationSchema: Schema = {
|
||||
...baseSchema,
|
||||
isViewed: ['bool', true],
|
||||
archived: ['bool', true],
|
||||
user: ['text', true]
|
||||
}
|
||||
|
||||
const dncSchema: Schema = {
|
||||
...baseSchema,
|
||||
objectId: ['text', true],
|
||||
objectClass: ['text', true],
|
||||
user: ['text', true]
|
||||
}
|
||||
|
||||
const userNotificationSchema: Schema = {
|
||||
...baseSchema,
|
||||
user: ['text', true]
|
||||
}
|
||||
|
||||
const docIndexStateSchema: Schema = {
|
||||
...baseSchema,
|
||||
needIndex: ['bool', true]
|
||||
}
|
||||
|
||||
export function translateDomain (domain: string): string {
|
||||
return domain.replaceAll('-', '_')
|
||||
}
|
||||
|
||||
export const domainSchemas: Record<string, Schema> = {
|
||||
[DOMAIN_SPACE]: spaceSchema
|
||||
[DOMAIN_SPACE]: spaceSchema,
|
||||
[DOMAIN_TX]: txSchema,
|
||||
[translateDomain(DOMAIN_DOC_INDEX_STATE)]: docIndexStateSchema,
|
||||
notification: notificationSchema,
|
||||
[translateDomain('notification-dnc')]: dncSchema,
|
||||
[translateDomain('notification-user')]: userNotificationSchema
|
||||
}
|
||||
|
||||
export function getSchema (domain: string): Schema {
|
||||
return domainSchemas[domain] ?? defaultSchema
|
||||
return domainSchemas[translateDomain(domain)] ?? defaultSchema
|
||||
}
|
||||
|
||||
export function getDocFieldsByDomains (domain: string): string[] {
|
||||
const schema = domainSchemas[translateDomain(domain)] ?? defaultSchema
|
||||
return Object.keys(schema)
|
||||
}
|
||||
|
||||
@@ -74,7 +74,6 @@ import {
|
||||
type DBDoc,
|
||||
escapeBackticks,
|
||||
getDBClient,
|
||||
getDocFieldsByDomains,
|
||||
inferType,
|
||||
isDataField,
|
||||
isOwner,
|
||||
@@ -82,9 +81,9 @@ import {
|
||||
parseDoc,
|
||||
parseDocWithProjection,
|
||||
parseUpdate,
|
||||
type PostgresClientReference,
|
||||
translateDomain
|
||||
type PostgresClientReference
|
||||
} from './utils'
|
||||
import { getDocFieldsByDomains, translateDomain } from './schemas'
|
||||
|
||||
abstract class PostgresAdapterBase implements DbAdapter {
|
||||
protected readonly _helper: DBCollectionHelper
|
||||
@@ -1550,7 +1549,7 @@ class PostgresTxAdapter extends PostgresAdapterBase implements TxAdapter {
|
||||
|
||||
async getModel (ctx: MeasureContext): Promise<Tx[]> {
|
||||
const res = await this
|
||||
.client`SELECT * FROM ${this.client(translateDomain(DOMAIN_TX))} WHERE "workspaceId" = ${this.workspaceId.name} AND data->>'objectSpace' = ${core.space.Model} ORDER BY _id ASC, "modifiedOn" ASC`
|
||||
.client`SELECT * FROM ${this.client(translateDomain(DOMAIN_TX))} WHERE "workspaceId" = ${this.workspaceId.name} AND "objectSpace" = ${core.space.Model} ORDER BY _id ASC, "modifiedOn" ASC`
|
||||
|
||||
const model = res.map((p) => parseDoc<Tx>(p as any))
|
||||
// We need to put all core.account.System transactions first
|
||||
|
||||
@@ -30,7 +30,7 @@ import core, {
|
||||
import { PlatformError, unknownStatus } from '@hcengineering/platform'
|
||||
import { type DomainHelperOperations } from '@hcengineering/server-core'
|
||||
import postgres from 'postgres'
|
||||
import { defaultSchema, domainSchemas, getSchema } from './schemas'
|
||||
import { getDocFieldsByDomains, getSchema, translateDomain } from './schemas'
|
||||
|
||||
const connections = new Map<string, PostgresClientReferenceImpl>()
|
||||
|
||||
@@ -329,10 +329,6 @@ export class DBCollectionHelper implements DomainHelperOperations {
|
||||
}
|
||||
}
|
||||
|
||||
export function translateDomain (domain: string): string {
|
||||
return domain.replaceAll('-', '_')
|
||||
}
|
||||
|
||||
export function parseDocWithProjection<T extends Doc> (doc: DBDoc, projection: Projection<T> | undefined): T {
|
||||
const { workspaceId, data, ...rest } = doc
|
||||
for (const key in rest) {
|
||||
@@ -397,11 +393,6 @@ export function isDataField (domain: string, field: string): boolean {
|
||||
return !getDocFieldsByDomains(domain).includes(field)
|
||||
}
|
||||
|
||||
export function getDocFieldsByDomains (domain: string): string[] {
|
||||
const schema = domainSchemas[domain] ?? defaultSchema
|
||||
return Object.keys(schema)
|
||||
}
|
||||
|
||||
export interface JoinProps {
|
||||
table: string // table to join
|
||||
path: string // _id.roles, attachedTo.attachedTo, space...
|
||||
|
||||
+10
-10
@@ -33,7 +33,6 @@ import core, {
|
||||
WorkspaceId,
|
||||
WorkspaceIdWithUrl,
|
||||
type Client,
|
||||
type Doc,
|
||||
type Ref,
|
||||
type WithLookup
|
||||
} from '@hcengineering/core'
|
||||
@@ -112,15 +111,16 @@ export async function initModel (
|
||||
|
||||
try {
|
||||
logger.log('creating database...', workspaceId)
|
||||
await adapter.upload(ctx, DOMAIN_TX, [
|
||||
{
|
||||
_class: core.class.Tx,
|
||||
_id: 'first-tx' as Ref<Doc>,
|
||||
modifiedBy: core.account.System,
|
||||
modifiedOn: Date.now(),
|
||||
space: core.space.DerivedTx
|
||||
}
|
||||
])
|
||||
const firstTx: Tx = {
|
||||
_class: core.class.Tx,
|
||||
_id: 'first-tx' as Ref<Tx>,
|
||||
modifiedBy: core.account.System,
|
||||
modifiedOn: Date.now(),
|
||||
space: core.space.DerivedTx,
|
||||
objectSpace: core.space.DerivedTx
|
||||
}
|
||||
|
||||
await adapter.upload(ctx, DOMAIN_TX, [firstTx])
|
||||
|
||||
await progress(30)
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# Restore workspace contents in mongo/elastic
|
||||
./tool.sh backup-restore ./sanity-ws sanity-ws
|
||||
|
||||
./tool.sh upgrade-workspace sanity-ws --indexes
|
||||
./tool.sh upgrade-workspace sanity-ws --indexes --force
|
||||
|
||||
# Re-assign user to workspace.
|
||||
./tool.sh assign-workspace user1 sanity-ws
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user