From 15aa8fb7b055a166b6f578ecdbf0d4faf5163f01 Mon Sep 17 00:00:00 2001 From: Denis Bykhov Date: Sun, 10 May 2026 18:40:56 +0500 Subject: [PATCH 01/52] =?UTF-8?q?add=20readonly=20support=20to=20MarkupEdi?= =?UTF-8?q?tor=20and=20improve=20hierarchy-based=20attr=E2=80=A6=20(#10835?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add readonly support to MarkupEditor and improve hierarchy-based attribute resolution and locking logic Signed-off-by: Denis Bykhov * perf: implement caching for card lookups in process-resources functions Signed-off-by: Denis Bykhov * refactor: update TagSelector to use tag IDs in selection set and optimize hierarchy access Signed-off-by: Denis Bykhov --------- Signed-off-by: Denis Bykhov --- .../src/components/MarkupProperties.svelte | 1 + .../src/components/MasterTagAttributes.svelte | 2 +- .../components/settings/TagSelector.svelte | 14 +++++----- .../src/components/MarkupEditor.svelte | 1 + plugins/view-resources/src/utils.ts | 26 ++++++++++++------- .../process-resources/src/functions.ts | 12 ++++++--- 6 files changed, 36 insertions(+), 20 deletions(-) diff --git a/plugins/card-resources/src/components/MarkupProperties.svelte b/plugins/card-resources/src/components/MarkupProperties.svelte index d0a6b1d8f4..15fc8e9bdc 100644 --- a/plugins/card-resources/src/components/MarkupProperties.svelte +++ b/plugins/card-resources/src/components/MarkupProperties.svelte @@ -66,6 +66,7 @@
value.readonlySections?.includes(p)) ?? false $: canLock = canLockSection(value.space, $permissionsStore) $: canUnlock = canUnlockSection(value.space, $permissionsStore) diff --git a/plugins/process-resources/src/components/settings/TagSelector.svelte b/plugins/process-resources/src/components/settings/TagSelector.svelte index 0dd58d95d3..0c9e8fcc9b 100644 --- a/plugins/process-resources/src/components/settings/TagSelector.svelte +++ b/plugins/process-resources/src/components/settings/TagSelector.svelte @@ -25,26 +25,28 @@ export let includeBase: boolean = false const client = getClient() - const hierarchy = client.getHierarchy() + const h = client.getHierarchy() const dispatch = createEventDispatcher() function open (e: MouseEvent): void { - const res = new Set(includeBase ? [hierarchy.getClass(process.masterTag)] : []) - const ancestors = hierarchy.getAncestors(process.masterTag) + const res = new Set>() + if (includeBase) res.add(h.getBaseClass(process.masterTag)) + const ancestors = h.getAncestors(process.masterTag) const tags = client.getModel().findAllSync(cardPlugin.class.Tag, {}) for (const p of tags) { try { - const base = hierarchy.getBaseClass(p._id) + const base = h.getBaseClass(p._id) if (process.masterTag === base || ancestors.includes(base)) { - res.add(p) + res.add(p._id) } } catch (err) { console.log('error', err, p._id) } } const items: SelectPopupValueType[] = [] - res.forEach((cl) => { + res.forEach((_id) => { + const cl = h.getClass(_id) items.push({ id: cl._id, label: cl.label, diff --git a/plugins/view-resources/src/components/MarkupEditor.svelte b/plugins/view-resources/src/components/MarkupEditor.svelte index d7885bc114..4dfeccb678 100644 --- a/plugins/view-resources/src/components/MarkupEditor.svelte +++ b/plugins/view-resources/src/components/MarkupEditor.svelte @@ -73,6 +73,7 @@ alwaysEdit {kitOptions} mode={2} + {readonly} on:value={(e) => { onChange(e.detail) }} diff --git a/plugins/view-resources/src/utils.ts b/plugins/view-resources/src/utils.ts index 107d40ee0e..29e1f6c160 100644 --- a/plugins/view-resources/src/utils.ts +++ b/plugins/view-resources/src/utils.ts @@ -1245,7 +1245,7 @@ export async function sortCategories ( * @public */ export function canResolveAttribute ( - hierarchy: Hierarchy, + h: Hierarchy, _class: Ref>, key: string, lookup: Lookup | undefined @@ -1263,9 +1263,13 @@ export function canResolveAttribute ( if (key.length === 0) return true const parts = key.split('.') if (parts.length === 1) { - return hierarchy.findAttribute(_class, key) !== undefined - } else if (hierarchy.isDerived(parts[0] as Ref>, _class)) { - return hierarchy.findAttribute(parts[0] as Ref>, parts[1]) !== undefined + return h.findAttribute(_class, key) !== undefined + } else { + const target = parts[0] as Ref> + const attr = parts[1] + if (h.isDerived(target, _class) || (h.isMixin(target) && h.isDerived(_class, h.getBaseClass(target)))) { + return h.findAttribute(target, attr) !== undefined + } } return false } @@ -1276,6 +1280,7 @@ export function getKeyLabel ( key: string, lookup: Lookup | undefined ): IntlString { + const h = client.getHierarchy() if (key.startsWith('$relation')) { // Handle association: $relation.[associationId] const parts = key.split('.') @@ -1311,15 +1316,18 @@ export function getKeyLabel ( } return getEmbeddedLabel(assocName) } else if (key.length === 0) { - const clazz = client.getHierarchy().getClass(_class) + const clazz = h.getClass(_class) return clazz.label } else { const parts = key.split('.') - if (parts.length === 2 && client.getHierarchy().isDerived(parts[0] as Ref>, _class)) { - const attribute = client.getHierarchy().getAttribute(parts[0] as Ref>, parts[1]) - return attribute.label + if (parts.length === 2) { + const target = parts[0] as Ref> + if (h.isDerived(target, _class) || (h.isMixin(target) && h.isDerived(_class, h.getBaseClass(target)))) { + const attribute = h.getAttribute(target, parts[1]) + return attribute.label + } } - const attribute = client.getHierarchy().getAttribute(_class, key) + const attribute = h.getAttribute(_class, key) return attribute.label } } diff --git a/server-plugins/process-resources/src/functions.ts b/server-plugins/process-resources/src/functions.ts index faf0e7a8ac..ad9e4dc271 100644 --- a/server-plugins/process-resources/src/functions.ts +++ b/server-plugins/process-resources/src/functions.ts @@ -574,7 +574,8 @@ export async function LockSection ( if (params._id === undefined) throw processError(process.error.RequiredParamsNotProvided, { params: '_id' }) const res: Tx[] = [] const rollback: Tx[] = [] - const card = await control.client.findOne(cardPlugin.class.Card, { _id: execution.card }) + const card: Card = + control.cache.get(execution.card) ?? (await control.client.findOne(cardPlugin.class.Card, { _id: execution.card })) if (card === undefined) throw processError(process.error.ObjectNotFound, { _id: execution.card }) const readonlySections = new Set(card.readonlySections ?? []) const target = params._id as Ref @@ -618,7 +619,8 @@ export async function UnlockSection ( if (params._id === undefined) throw processError(process.error.RequiredParamsNotProvided, { params: '_id' }) const res: Tx[] = [] const rollback: Tx[] = [] - const card = await control.client.findOne(cardPlugin.class.Card, { _id: execution.card }) + const card: Card = + control.cache.get(execution.card) ?? (await control.client.findOne(cardPlugin.class.Card, { _id: execution.card })) if (card === undefined) throw processError(process.error.ObjectNotFound, { _id: execution.card }) const target = params._id as Ref const readonlySections = new Set(card.readonlySections ?? []) @@ -643,7 +645,8 @@ export async function LockField ( if (params.value === undefined) throw processError(process.error.RequiredParamsNotProvided, { params: 'value' }) const res: Tx[] = [] const rollback: Tx[] = [] - const card = await control.client.findOne(cardPlugin.class.Card, { _id: execution.card }) + const card: Card = + control.cache.get(execution.card) ?? (await control.client.findOne(cardPlugin.class.Card, { _id: execution.card })) if (card === undefined) throw processError(process.error.ObjectNotFound, { _id: execution.card }) const oldReadonlyFields = card.readonlyFields ?? [] const readonlyFields = [...oldReadonlyFields] @@ -678,7 +681,8 @@ export async function UnlockField ( if (params.value === undefined) throw processError(process.error.RequiredParamsNotProvided, { params: 'value' }) const res: Tx[] = [] const rollback: Tx[] = [] - const card = await control.client.findOne(cardPlugin.class.Card, { _id: execution.card }) + const card: Card = + control.cache.get(execution.card) ?? (await control.client.findOne(cardPlugin.class.Card, { _id: execution.card })) if (card === undefined) throw processError(process.error.ObjectNotFound, { _id: execution.card }) const oldReadonlyFields = card.readonlyFields ?? [] const targets = Array.isArray(params.value) ? params.value : [params.value] From 81ffbd36785017b5e2d63ae555fc5e88f579795d Mon Sep 17 00:00:00 2001 From: Denis Bykhov Date: Sun, 10 May 2026 21:51:01 +0500 Subject: [PATCH 02/52] Fix markup check (#10836) Signed-off-by: Denis Bykhov --- .../src/components/CardArrayEditor.svelte | 16 +++++++++-- .../src/components/MarkupProperties.svelte | 1 - plugins/process-resources/src/utils.ts | 15 ++++++---- .../process-resources/src/functions.ts | 28 +++++++++++++------ 4 files changed, 42 insertions(+), 18 deletions(-) diff --git a/plugins/card-resources/src/components/CardArrayEditor.svelte b/plugins/card-resources/src/components/CardArrayEditor.svelte index 2c5b422723..b7c3d3d6b5 100644 --- a/plugins/card-resources/src/components/CardArrayEditor.svelte +++ b/plugins/card-resources/src/components/CardArrayEditor.svelte @@ -24,7 +24,7 @@ import CardsPopup from './CardsPopup.svelte' export let _class: Ref> | undefined = undefined - export let value: Ref[] | undefined + export let value: Ref[] | Ref | undefined export let readonly: boolean = false export let label: IntlString | undefined export let onChange: ((value: any) => void) | undefined @@ -60,13 +60,23 @@ showPopup( CardsPopup, - { selectedObjects: value, _class, multiSelect: true }, + { selectedObjects: toArray(value), _class, multiSelect: true }, eventToHTMLElement(event), undefined, change ) } + function toArray (value: Ref[] | Ref | undefined): Ref[] { + if (Array.isArray(value)) { + return value + } + if (value === undefined) { + return [] + } + return [value] + } + const change = (value: Ref[]): void => { onChange?.(value) dispatch('change', value) @@ -75,7 +85,7 @@ let docs: Card[] = [] const query = createQuery() - $: query.query(card.class.Card, { _id: { $in: value ?? [] } }, (res) => { + $: query.query(card.class.Card, { _id: { $in: toArray(value) } }, (res) => { docs = res }) diff --git a/plugins/card-resources/src/components/MarkupProperties.svelte b/plugins/card-resources/src/components/MarkupProperties.svelte index 15fc8e9bdc..d0a6b1d8f4 100644 --- a/plugins/card-resources/src/components/MarkupProperties.svelte +++ b/plugins/card-resources/src/components/MarkupProperties.svelte @@ -66,7 +66,6 @@
, client: Client): Record { +function getMarkupParams (process: Process, params: Record, client: Client): Record { const markup: Record = {} for (const [key, value] of Object.entries(params)) { - const attr = client.getHierarchy().findAttribute(card._class, key) + const attr = client.getHierarchy().findAttribute(process.masterTag, key) if (attr?.type?._class === core.class.TypeMarkup) { markup[key] = value } @@ -846,7 +846,7 @@ export function matchCardCheck ( if (client.getHierarchy().isMixin(process.masterTag)) { doc = client.getHierarchy().as(doc, process.masterTag) } - const markup = getMarkupParams(doc, params, client) + const markup = getMarkupParams(process, params, client) for (const key of Object.keys(markup)) { if (isEmptyMarkup(doc[key])) return false } @@ -861,12 +861,17 @@ export function fieldChangesCheck ( params: Record, context: Record ): boolean { - const doc = context.card + let doc = context.card if (doc === undefined) return false + const process = client.getModel().findObject(execution.process) + if (process === undefined) return false + if (client.getHierarchy().isMixin(process.masterTag)) { + doc = client.getHierarchy().as(doc, process.masterTag) + } const operations = (context.operations ?? {}) as DocumentUpdate const target = Object.keys(params)[0] if (!TxProcessor.hasUpdate(operations, target)) return false - const markup = getMarkupParams(doc, params, client) + const markup = getMarkupParams(process, params, client) for (const key of Object.keys(markup)) { if (isEmptyMarkup(doc[key])) return false } diff --git a/server-plugins/process-resources/src/functions.ts b/server-plugins/process-resources/src/functions.ts index ad9e4dc271..0797c99114 100644 --- a/server-plugins/process-resources/src/functions.ts +++ b/server-plugins/process-resources/src/functions.ts @@ -144,22 +144,27 @@ export function MatchCardCheck ( params: Record, context: Record ): boolean { - if (context.card === undefined) return false + let card = context.card + if (card === undefined) return false const process = control.client.getModel().findObject(execution.process) if (process === undefined) return false - const markup = getMarkupParams(context.card, params, control) + const markup = getMarkupParams(process, params, control) + const h = control.client.getHierarchy() + if (h.isMixin(process.masterTag)) { + card = h.as(card, process.masterTag) + } for (const key of Object.keys(markup)) { - if (isEmptyMarkup(context.card[key])) return false + if (isEmptyMarkup(card[key])) return false } - const res = matchQuery([context.card], params, process.masterTag, control.client.getHierarchy(), true) + const res = matchQuery([card], params, process.masterTag, control.client.getHierarchy(), true) return res.length > 0 } -function getMarkupParams (card: Card, params: Record, control: ProcessControl): Record { +function getMarkupParams (process: Process, params: Record, control: ProcessControl): Record { const markup: Record = {} for (const [key, value] of Object.entries(params)) { - const attr = control.client.getHierarchy().findAttribute(card._class, key) + const attr = control.client.getHierarchy().findAttribute(process.masterTag, key) if (attr?.type?._class === core.class.TypeMarkup) { markup[key] = value } @@ -190,12 +195,17 @@ export function FieldChangedCheck ( const operations = context.operations as DocumentUpdate const target = Object.keys(params)[0] if (!TxProcessor.hasUpdate(operations, target)) return false - const markup = getMarkupParams(context.card, params, control) + const markup = getMarkupParams(process, params, control) + const h = control.client.getHierarchy() + let card = context.card + if (h.isMixin(process.masterTag)) { + card = h.as(card, process.masterTag) + } for (const key of Object.keys(markup)) { - if (isEmptyMarkup(context.card[key])) return false + if (isEmptyMarkup(card[key])) return false } - const res = matchQuery([context.card], params, process.masterTag, control.client.getHierarchy(), true) + const res = matchQuery([card], params, process.masterTag, control.client.getHierarchy(), true) return res.length > 0 } From fb7a4618a559b5f8907c3bbe7f3d705af8e2fdd9 Mon Sep 17 00:00:00 2001 From: Artyom Savchenko Date: Mon, 11 May 2026 00:34:35 +0700 Subject: [PATCH 03/52] Update changelog (#10834) Signed-off-by: Artem Savchenko --- changelog.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index 1ac3343321..d8f43b11bd 100644 --- a/changelog.md +++ b/changelog.md @@ -2,10 +2,22 @@ Changelog. +## [0.7.423] - 2026-05-10 + +* 🚀 FEATURES: · Print documents by class and id ([#10753](https://github.com/hcengineering/platform/issues/10753)) · Add tool to create missing SocialIdentity ([#10758](https://github.com/hcengineering/platform/issues/10758)) · User statuses ([#10774](https://github.com/hcengineering/platform/issues/10774)) · Add ability to configure guest spaces in settings ([#10770](https://github.com/hcengineering/platform/issues/10770)) · Add min and max transformation functions ([#10782](https://github.com/hcengineering/platform/issues/10782)) · Add trigger to remove associated execution data when a card is deleted ([#10795](https://github.com/hcengineering/platform/issues/10795)) · Add found elements count in desktop ([#10799](https://github.com/hcengineering/platform/issues/10799)) +* 🐛 BUG FIXES: · Apps customization ([#10755](https://github.com/hcengineering/platform/issues/10755)) · Grid migration ([#10762](https://github.com/hcengineering/platform/issues/10762)) · Card viewlet setting for associations ([#10761](https://github.com/hcengineering/platform/issues/10761)) · *(ci)* Emit TypeScript declarations before npm publish ([#10768](https://github.com/hcengineering/platform/issues/10768)) · Translation errors in copy table action ([#10769](https://github.com/hcengineering/platform/issues/10769)) · Calendar connection on small screens ([#10777](https://github.com/hcengineering/platform/issues/10777)) · Controlled document folder export ([#10775](https://github.com/hcengineering/platform/issues/10775)) · Tracker project actions ([#10786](https://github.com/hcengineering/platform/issues/10786)) · Training question actions ([#10780](https://github.com/hcengineering/platform/issues/10780)) · UI freeze on controlled doc update view ([#10778](https://github.com/hcengineering/platform/issues/10778)) · Auto join translations ([#10787](https://github.com/hcengineering/platform/issues/10787)) · Published packages ([#10788](https://github.com/hcengineering/platform/issues/10788)) · Re-enable model version check in CI ([#10796](https://github.com/hcengineering/platform/issues/10796)) · Login form layout on small screens ([#10798](https://github.com/hcengineering/platform/issues/10798)) · Allow to assign guest as space owner ([#10803](https://github.com/hcengineering/platform/issues/10803)) +* ⚡ PERFORMANCE: · Memory usage for rekoni service ([#10797](https://github.com/hcengineering/platform/issues/10797)) +* ⚙️ MISCELLANEOUS TASKS: · Changelog ([#10752](https://github.com/hcengineering/platform/issues/10752)) · Huly link ([#10754](https://github.com/hcengineering/platform/issues/10754)) · Auto-translate and mailbox ([#10800](https://github.com/hcengineering/platform/issues/10800)) · Version + +## [0.7.413] - 2026-04-14 + +* 🐛 BUG FIXES: · Apps customization ([#10755](https://github.com/hcengineering/platform/issues/10755)) · Model version +* ⚙️ MISCELLANEOUS TASKS: · Huly link ([#10754](https://github.com/hcengineering/platform/issues/10754)) · 413 · Dependencies + ## [0.7.411] - 2026-04-12 * 🚀 FEATURES: · To launch publish npm manually ([#10580](https://github.com/hcengineering/platform/issues/10580)) · Auto generate rank in middleware ([#10577](https://github.com/hcengineering/platform/issues/10577)) · Add action to export project documents ([#10592](https://github.com/hcengineering/platform/issues/10592)) · Add PostgreSQL profile for dev mode ([#10582](https://github.com/hcengineering/platform/issues/10582)) · Add notifications for reviewed documents ([#10601](https://github.com/hcengineering/platform/issues/10601)) · *(notification)* Improve inbox list keyboard navigation and a11y ([#10612](https://github.com/hcengineering/platform/issues/10612)) · *(communication)* Improve messages loading state accessibility ([#10613](https://github.com/hcengineering/platform/issues/10613)) · *(notification)* Add inbox settings keyboard shortcuts ([#10614](https://github.com/hcengineering/platform/issues/10614)) · *(text-editor)* Add highlight, subscript, superscript and mathematics toolbar actions ([#10627](https://github.com/hcengineering/platform/issues/10627)) · To add social links if there are no attached messages ([#10657](https://github.com/hcengineering/platform/issues/10657)) · *(password)* Email-confirmed password setup for SSO accounts ([#10649](https://github.com/hcengineering/platform/issues/10649)) · Error handling in notification service ([#10662](https://github.com/hcengineering/platform/issues/10662)) · Add new process functions for data type conversion, including s… ([#10670](https://github.com/hcengineering/platform/issues/10670)) · Add 15/30 minutes time spent reports ([#10675](https://github.com/hcengineering/platform/issues/10675)) · *(setting)* Display version in Help & Support button ([#9796](https://github.com/hcengineering/platform/issues/9796)) ([#10667](https://github.com/hcengineering/platform/issues/10667)) · Guest users edit documents created by them ([#10695](https://github.com/hcengineering/platform/issues/10695)) · Add spellcheck prop to text editor components ([#10694](https://github.com/hcengineering/platform/issues/10694)) · Add configurable card layout modes ([#10724](https://github.com/hcengineering/platform/issues/10724)) · Add ability to configure guest permissions ([#10708](https://github.com/hcengineering/platform/issues/10708)) · Support find in desktop client ([#10723](https://github.com/hcengineering/platform/issues/10723)) · Add automationOnly flag to associations and restrict manual management in UI ([#10730](https://github.com/hcengineering/platform/issues/10730)) · Anonymous guest permissions ([#10726](https://github.com/hcengineering/platform/issues/10726)) · Add script to verify changed packages ([#10738](https://github.com/hcengineering/platform/issues/10738)) · To hide product and planner for guests ([#10741](https://github.com/hcengineering/platform/issues/10741)) · To answer questions in training for guests ([#10746](https://github.com/hcengineering/platform/issues/10746)) · Guest auto-join ([#10751](https://github.com/hcengineering/platform/issues/10751)) -* 🐛 BUG FIXES: · Npm publish ([#10575](https://github.com/hcengineering/platform/issues/10575)) · LockFieldEditor.svelte formatting ([#10578](https://github.com/hcengineering/platform/issues/10578)) · *(account)* Handle single-word names in OIDC and GitHub auth providers ([#10631](https://github.com/hcengineering/platform/issues/10631)) · Npm packages to not use legacy repository format ([#10576](https://github.com/hcengineering/platform/issues/10576)) · Import tool launch ([#10596](https://github.com/hcengineering/platform/issues/10596)) · Improve cookie token selection ([#10599](https://github.com/hcengineering/platform/issues/10599)) · Stripe webhook ([#10600](https://github.com/hcengineering/platform/issues/10600)) · Exporter ([#10583](https://github.com/hcengineering/platform/issues/10583)) · Stripe events ([#10602](https://github.com/hcengineering/platform/issues/10602)) · Stripe metadata ([#10603](https://github.com/hcengineering/platform/issues/10603)) · *(tracker)* Make Project.defaultIssueStatus optional ([#10598](https://github.com/hcengineering/platform/issues/10598)) · Payment server config ([#10607](https://github.com/hcengineering/platform/issues/10607)) · Stripe success URL and account id ([#10608](https://github.com/hcengineering/platform/issues/10608)) · Subscription status mapping ([#10611](https://github.com/hcengineering/platform/issues/10611)) · Logging for transformStripeSubscriptionToData ([#10609](https://github.com/hcengineering/platform/issues/10609)) · Svelte error in sub-issues ([#10618](https://github.com/hcengineering/platform/issues/10618)) · *(tracker)* ParentIssue prop overrides draft when reopening create issue modal ([#10616](https://github.com/hcengineering/platform/issues/10616)) · Convert ws event data to array buffer ([#10635](https://github.com/hcengineering/platform/issues/10635)) · Translation error loop ([#10636](https://github.com/hcengineering/platform/issues/10636)) · Request section visibility and hide card activity for guests ([#10641](https://github.com/hcengineering/platform/issues/10641)) · Publish config ([#10644](https://github.com/hcengineering/platform/issues/10644)) · Export translations ([#10652](https://github.com/hcengineering/platform/issues/10652)) · *(tracker)* Truncate long milestone titles on issue cards ([#10646](https://github.com/hcengineering/platform/issues/10646)) · Social ID owner check ([#10653](https://github.com/hcengineering/platform/issues/10653)) · *(login)* Guard logIn() behind token check in password signup ([#10518](https://github.com/hcengineering/platform/issues/10518)) ([#10650](https://github.com/hcengineering/platform/issues/10650)) · Intermittent PDF print failures ([#10654](https://github.com/hcengineering/platform/issues/10654)) · *(account)* Handle single-word names in OIDC and GitHub auth providers ([#10631](https://github.com/hcengineering/platform/issues/10631)) ([#10656](https://github.com/hcengineering/platform/issues/10656)) · Social link save ([#10655](https://github.com/hcengineering/platform/issues/10655)) · *(view)* "Not Specified" group shows assigned issues ([#10606](https://github.com/hcengineering/platform/issues/10606)) ([#10666](https://github.com/hcengineering/platform/issues/10666)) · Formatting ([#10668](https://github.com/hcengineering/platform/issues/10668)) · Message links ([#10660](https://github.com/hcengineering/platform/issues/10660)) · Process card update check for tags ([#10671](https://github.com/hcengineering/platform/issues/10671)) · View setting bug ([#10674](https://github.com/hcengineering/platform/issues/10674)) · Display collaboration errors in case of reconnects ([#10678](https://github.com/hcengineering/platform/issues/10678)) · Markdown link escaping ([#10685](https://github.com/hcengineering/platform/issues/10685)) · Create Github worker for deleting workspace ([#10661](https://github.com/hcengineering/platform/issues/10661)) · Github UI issues ([#10684](https://github.com/hcengineering/platform/issues/10684)) · *(github)* Apply guest token to images in markdown sync ([#10659](https://github.com/hcengineering/platform/issues/10659)) · Hyperlink in contoll doc comment ([#10690](https://github.com/hcengineering/platform/issues/10690)) · Todo reassign ([#10693](https://github.com/hcengineering/platform/issues/10693)) · Markdown links in table ([#10688](https://github.com/hcengineering/platform/issues/10688)) · SSRF in Link Preview Service ([#10689](https://github.com/hcengineering/platform/issues/10689)) · *(github)* Guard REST octokit in comment sync to prevent createComment crash ([#10692](https://github.com/hcengineering/platform/issues/10692)) · Formatting ([#10697](https://github.com/hcengineering/platform/issues/10697)) · *(calendar)* Add error handling for outbound Google Calendar sync ([#10696](https://github.com/hcengineering/platform/issues/10696)) · Github login update ([#10683](https://github.com/hcengineering/platform/issues/10683)) · Ws connection hang in case of workspace not found ([#10682](https://github.com/hcengineering/platform/issues/10682)) · View setting freeze ([#10700](https://github.com/hcengineering/platform/issues/10700)) · Filter removed attribute from viewlet ([#10701](https://github.com/hcengineering/platform/issues/10701)) · Handle markdown table as KaTeX ([#10699](https://github.com/hcengineering/platform/issues/10699)) · View settings ([#10706](https://github.com/hcengineering/platform/issues/10706)) · Select space not accessible to the user ([#10707](https://github.com/hcengineering/platform/issues/10707)) · Log workspace info ([#10712](https://github.com/hcengineering/platform/issues/10712)) · Mermaid diagram paste ([#10714](https://github.com/hcengineering/platform/issues/10714)) · Run-local for print service ([#10719](https://github.com/hcengineering/platform/issues/10719)) · SubProcessMatchCheck ([#10731](https://github.com/hcengineering/platform/issues/10731)) · UI freeze on missing translation params ([#10735](https://github.com/hcengineering/platform/issues/10735)) · Markdown links in table refresh ([#10732](https://github.com/hcengineering/platform/issues/10732)) · Rename twitter to x ([#10744](https://github.com/hcengineering/platform/issues/10744)) +* 🐛 BUG FIXES: · Npm publish ([#10575](https://github.com/hcengineering/platform/issues/10575)) · LockFieldEditor.svelte formatting ([#10578](https://github.com/hcengineering/platform/issues/10578)) · *(account)* Handle single-word names in OIDC and GitHub auth providers ([#10631](https://github.com/hcengineering/platform/issues/10631)) · Npm packages to not use legacy repository format ([#10576](https://github.com/hcengineering/platform/issues/10576)) · Import tool launch ([#10596](https://github.com/hcengineering/platform/issues/10596)) · Improve cookie token selection ([#10599](https://github.com/hcengineering/platform/issues/10599)) · Stripe webhook ([#10600](https://github.com/hcengineering/platform/issues/10600)) · Exporter ([#10583](https://github.com/hcengineering/platform/issues/10583)) · Stripe events ([#10602](https://github.com/hcengineering/platform/issues/10602)) · Stripe metadata ([#10603](https://github.com/hcengineering/platform/issues/10603)) · *(tracker)* Make Project.defaultIssueStatus optional ([#10598](https://github.com/hcengineering/platform/issues/10598)) · Payment server config ([#10607](https://github.com/hcengineering/platform/issues/10607)) · Stripe success URL and account id ([#10608](https://github.com/hcengineering/platform/issues/10608)) · Subscription status mapping ([#10611](https://github.com/hcengineering/platform/issues/10611)) · Logging for transformStripeSubscriptionToData ([#10609](https://github.com/hcengineering/platform/issues/10609)) · Svelte error in sub-issues ([#10618](https://github.com/hcengineering/platform/issues/10618)) · *(tracker)* ParentIssue prop overrides draft when reopening create issue modal ([#10616](https://github.com/hcengineering/platform/issues/10616)) · Convert ws event data to array buffer ([#10635](https://github.com/hcengineering/platform/issues/10635)) · Translation error loop ([#10636](https://github.com/hcengineering/platform/issues/10636)) · Request section visibility and hide card activity for guests ([#10641](https://github.com/hcengineering/platform/issues/10641)) · Publish config ([#10644](https://github.com/hcengineering/platform/issues/10644)) · Export translations ([#10652](https://github.com/hcengineering/platform/issues/10652)) · *(tracker)* Truncate long milestone titles on issue cards ([#10646](https://github.com/hcengineering/platform/issues/10646)) · Social ID owner check ([#10653](https://github.com/hcengineering/platform/issues/10653)) · *(login)* Guard logIn() behind token check in password signup ([#10518](https://github.com/hcengineering/platform/issues/10518)) ([#10650](https://github.com/hcengineering/platform/issues/10650)) · Intermittent PDF print failures ([#10654](https://github.com/hcengineering/platform/issues/10654)) · *(account)* Handle single-word names in OIDC and GitHub auth providers ([#10631](https://github.com/hcengineering/platform/issues/10631)) ([#10656](https://github.com/hcengineering/platform/issues/10656)) · Social link save ([#10655](https://github.com/hcengineering/platform/issues/10655)) · *(view)* "Not Specified" group shows assigned issues ([#10606](https://github.com/hcengineering/platform/issues/10606)) ([#10666](https://github.com/hcengineering/platform/issues/10666)) · Formatting ([#10668](https://github.com/hcengineering/platform/issues/10668)) · Message links ([#10660](https://github.com/hcengineering/platform/issues/10660)) · Process card update check for tags ([#10671](https://github.com/hcengineering/platform/issues/10671)) · View setting bug ([#10674](https://github.com/hcengineering/platform/issues/10674)) · Display collaboration errors in case of reconnects ([#10678](https://github.com/hcengineering/platform/issues/10678)) · Markdown link escaping ([#10685](https://github.com/hcengineering/platform/issues/10685)) · Create Github worker for deleting workspace ([#10661](https://github.com/hcengineering/platform/issues/10661)) · Github UI issues ([#10684](https://github.com/hcengineering/platform/issues/10684)) · *(github)* Apply guest token to images in markdown sync ([#10659](https://github.com/hcengineering/platform/issues/10659)) · Hyperlink in contoll doc comment ([#10690](https://github.com/hcengineering/platform/issues/10690)) · Todo reassign ([#10693](https://github.com/hcengineering/platform/issues/10693)) · Markdown links in table ([#10688](https://github.com/hcengineering/platform/issues/10688)) · SSRF in Link Preview Service ([#10689](https://github.com/hcengineering/platform/issues/10689)) · *(github)* Guard REST octokit in comment sync to prevent createComment crash ([#10692](https://github.com/hcengineering/platform/issues/10692)) · Formatting ([#10697](https://github.com/hcengineering/platform/issues/10697)) · *(calendar)* Add error handling for outbound Google Calendar sync ([#10696](https://github.com/hcengineering/platform/issues/10696)) · Github login update ([#10683](https://github.com/hcengineering/platform/issues/10683)) · Ws connection hang in case of workspace not found ([#10682](https://github.com/hcengineering/platform/issues/10682)) · View setting freeze ([#10700](https://github.com/hcengineering/platform/issues/10700)) · Filter removed attribute from viewlet ([#10701](https://github.com/hcengineering/platform/issues/10701)) · Handle markdown table as KaTeX ([#10699](https://github.com/hcengineering/platform/issues/10699)) · View settings ([#10706](https://github.com/hcengineering/platform/issues/10706)) · Select space not accessible to the user ([#10707](https://github.com/hcengineering/platform/issues/10707)) · Log workspace info ([#10712](https://github.com/hcengineering/platform/issues/10712)) · Mermaid diagram paste ([#10714](https://github.com/hcengineering/platform/issues/10714)) · Run-local for print service ([#10719](https://github.com/hcengineering/platform/issues/10719)) · SubProcessMatchCheck ([#10731](https://github.com/hcengineering/platform/issues/10731)) · UI freeze on missing translation params ([#10735](https://github.com/hcengineering/platform/issues/10735)) · Markdown links in table refresh ([#10732](https://github.com/hcengineering/platform/issues/10732)) · Rename twitter to x ([#10744](https://github.com/hcengineering/platform/issues/10744)) · Validation * 🚜 REFACTOR: · Default invite settings in link generation ([#10651](https://github.com/hcengineering/platform/issues/10651)) * ⚙️ MISCELLANEOUS TASKS: · Changelog ([#10579](https://github.com/hcengineering/platform/issues/10579)) · KVS version ([#10633](https://github.com/hcengineering/platform/issues/10633)) · Unused field ([#10686](https://github.com/hcengineering/platform/issues/10686)) · Employee role on account role change ([#10709](https://github.com/hcengineering/platform/issues/10709)) · Mobile not supported placeholder ([#10718](https://github.com/hcengineering/platform/issues/10718)) · Dark theme ([#10728](https://github.com/hcengineering/platform/issues/10728)) · Guests icon ([#10733](https://github.com/hcengineering/platform/issues/10733)) * BUILD: · *(deps)* Bump docker/setup-docker-action from 4 to 5 ([#10584](https://github.com/hcengineering/platform/issues/10584)) · *(deps)* Bump crazy-max/ghaction-setup-docker from 3 to 5 ([#10585](https://github.com/hcengineering/platform/issues/10585)) · *(deps)* Bump docker/login-action from 3 to 4 ([#10589](https://github.com/hcengineering/platform/issues/10589)) · *(deps)* Bump docker/setup-qemu-action from 3 to 4 ([#10590](https://github.com/hcengineering/platform/issues/10590)) · *(deps)* Bump pnpm/action-setup from 4 to 5 ([#10648](https://github.com/hcengineering/platform/issues/10648)) From 9e8efb26c2a364700b4c35af7bf0317d9e7cb3fc Mon Sep 17 00:00:00 2001 From: Denis Bykhov Date: Mon, 11 May 2026 00:00:43 +0500 Subject: [PATCH 04/52] Fix main section lock (#10837) * Fix main section lock Signed-off-by: Denis Bykhov * Fix formatting Signed-off-by: Denis Bykhov --------- Signed-off-by: Denis Bykhov --- .../src/components/MasterTagAttributes.svelte | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/plugins/card-resources/src/components/MasterTagAttributes.svelte b/plugins/card-resources/src/components/MasterTagAttributes.svelte index 61142779b6..e8826859f2 100644 --- a/plugins/card-resources/src/components/MasterTagAttributes.svelte +++ b/plugins/card-resources/src/components/MasterTagAttributes.svelte @@ -63,7 +63,12 @@ async function toggleLock (ev: MouseEvent): Promise { ev.stopPropagation() const op = isLocked ? '$pull' : '$push' - await client.update(value, { [op]: { readonlySections: value._class } }) + const targets = isLocked + ? hierarchy.getAncestors(value._class).filter((p) => value.readonlySections?.includes(p)) + : [value._class] + for (const target of targets) { + await client.update(value, { [op]: { readonlySections: target } }) + } } From fa2930d54e2e841af3ee857b5217a31d944ee7f5 Mon Sep 17 00:00:00 2001 From: Denis Bykhov Date: Mon, 11 May 2026 00:18:56 +0500 Subject: [PATCH 05/52] Nested view setting (#10838) Signed-off-by: Denis Bykhov --- .../src/components/settings/view/ViewSetting.svelte | 2 +- plugins/view-resources/src/components/ViewletSetting.svelte | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/card-resources/src/components/settings/view/ViewSetting.svelte b/plugins/card-resources/src/components/settings/view/ViewSetting.svelte index 7e9cc174d2..eb3ba92261 100644 --- a/plugins/card-resources/src/components/settings/view/ViewSetting.svelte +++ b/plugins/card-resources/src/components/settings/view/ViewSetting.svelte @@ -259,7 +259,7 @@ const parentMixins = hierarchy .getDescendants(parent) .map((p) => hierarchy.getClass(p)) - .filter((p) => hierarchy.isMixin(p._id) && p.extends && ancestors.has(p.extends)) + .filter((p) => hierarchy.isMixin(p._id) && ancestors.has(hierarchy.getBaseClass(p._id))) parentMixins.forEach((it) => { hierarchy.getOwnAttributes(it._id).forEach((attr) => { diff --git a/plugins/view-resources/src/components/ViewletSetting.svelte b/plugins/view-resources/src/components/ViewletSetting.svelte index c878aed3a6..468f2fb23a 100644 --- a/plugins/view-resources/src/components/ViewletSetting.svelte +++ b/plugins/view-resources/src/components/ViewletSetting.svelte @@ -402,7 +402,7 @@ const parentMixins = hierarchy .getDescendants(parent) .map((p) => hierarchy.getClass(p)) - .filter((p) => hierarchy.isMixin(p._id) && p.extends && ancestors.has(p.extends)) + .filter((p) => hierarchy.isMixin(p._id) && ancestors.has(hierarchy.getBaseClass(p._id))) parentMixins.forEach((it) => { hierarchy.getOwnAttributes(it._id).forEach((attr) => { From 23faf14ccaa075ffa12a6b314e8cbd2b4656ef6d Mon Sep 17 00:00:00 2001 From: Denis Bykhov Date: Sun, 17 May 2026 14:53:34 +0500 Subject: [PATCH 06/52] Card version settings (#10843) Signed-off-by: Denis Bykhov --- .../core/packages/core/src/hierarchy.ts | 17 ++ .../core/packages/core/src/versioning.ts | 5 +- models/core/src/core.ts | 3 + models/process/src/functions.ts | 13 ++ models/server-process/src/index.ts | 4 + .../src/components/DropdownLabelsIntl.svelte | 72 ++++-- .../components/DropdownLabelsPopupIntl.svelte | 29 ++- .../src/components/CardVersionSelector.svelte | 12 +- .../src/components/NewVersionPopup.svelte | 106 --------- .../components/settings/GeneralSection.svelte | 27 ++- .../settings/VersioningSetting.svelte | 206 ++++++++++++++++++ plugins/card-resources/src/utils.ts | 57 +++-- plugins/process-resources/src/plugin.ts | 1 + plugins/process/src/index.ts | 1 + server-plugins/process-resources/src/index.ts | 2 + .../process-resources/src/transform.ts | 4 + server-plugins/process/src/index.ts | 1 + 17 files changed, 401 insertions(+), 159 deletions(-) delete mode 100644 plugins/card-resources/src/components/NewVersionPopup.svelte create mode 100644 plugins/card-resources/src/components/settings/VersioningSetting.svelte diff --git a/foundations/core/packages/core/src/hierarchy.ts b/foundations/core/packages/core/src/hierarchy.ts index 9b8fc3665c..32d055c7a4 100644 --- a/foundations/core/packages/core/src/hierarchy.ts +++ b/foundations/core/packages/core/src/hierarchy.ts @@ -162,6 +162,23 @@ export class Hierarchy { return Array.from(resultSet) } + getAllPossibleMixins (_class: Ref>, to: Ref> = core.class.Doc): Ref>[] { + const result = new Set>>() + let c = this.getClass(_class) + + while (c._id !== to && c.extends !== undefined) { + const _descendants = this.descendants.get(c._id) + for (const d of _descendants ?? []) { + if (this.isMixin(d) && this.getBaseClass(d) === c._id) { + result.add(d) + } + } + c = this.getClass(c.extends) + } + + return [...result] + } + isMixin (_class: Ref>): boolean { const data = this.classifiers.get(_class) return data !== undefined && this._isMixin(data) diff --git a/foundations/core/packages/core/src/versioning.ts b/foundations/core/packages/core/src/versioning.ts index 34db2fd7bb..6ab64893a5 100644 --- a/foundations/core/packages/core/src/versioning.ts +++ b/foundations/core/packages/core/src/versioning.ts @@ -1,4 +1,4 @@ -import { Class, Doc, PersonId, Ref } from './classes' +import { Class, Doc, Mixin, PersonId, Ref } from './classes' export interface VersionableDoc extends Doc { baseId?: Ref @@ -10,4 +10,7 @@ export interface VersionableDoc extends Doc { export interface VersionableClass extends Class { enabled: boolean + excludedProperties?: string[] + excludedRelations?: string[] // ${associationId}_${a|b} + excludeMixins?: Ref>[] } diff --git a/models/core/src/core.ts b/models/core/src/core.ts index 02e69d551b..f0feb13166 100644 --- a/models/core/src/core.ts +++ b/models/core/src/core.ts @@ -447,4 +447,7 @@ export class TCollaborator extends TAttachedDoc implements Collaborator { @MMixin(core.mixin.VersionableClass, core.class.Class) export class TVersionableClass extends TClass implements VersionableClass { enabled!: boolean + excludedProperties?: string[] + excludedRelations?: string[] // ${associationId}_${a|b} + excludeMixins?: Ref>[] } diff --git a/models/process/src/functions.ts b/models/process/src/functions.ts index ba7c6f932f..11d982c12c 100644 --- a/models/process/src/functions.ts +++ b/models/process/src/functions.ts @@ -600,6 +600,19 @@ export function defineFunctions (builder: Builder): void { process.function.StringFromNumber ) + builder.createDoc( + process.class.ProcessFunction, + core.space.Model, + { + of: core.class.TypeIdentifier, + to: core.class.TypeString, + category: 'attribute', + label: process.string.TextFromIdentifier, + type: 'convert' + }, + process.function.StringFromIdentifier + ) + builder.createDoc( process.class.ProcessFunction, core.space.Model, diff --git a/models/server-process/src/index.ts b/models/server-process/src/index.ts index dafae6227b..334528e3c0 100644 --- a/models/server-process/src/index.ts +++ b/models/server-process/src/index.ts @@ -207,6 +207,10 @@ export function createModel (builder: Builder): void { func: serverProcess.transform.DateFromNumber }) + builder.mixin(process.function.StringFromIdentifier, process.class.ProcessFunction, serverProcess.mixin.FuncImpl, { + func: serverProcess.transform.StringFromIdentifier + }) + builder.mixin(process.function.NumberFromString, process.class.ProcessFunction, serverProcess.mixin.FuncImpl, { func: serverProcess.transform.NumberFromString }) diff --git a/packages/ui/src/components/DropdownLabelsIntl.svelte b/packages/ui/src/components/DropdownLabelsIntl.svelte index ceafeacb97..47d8fecde3 100644 --- a/packages/ui/src/components/DropdownLabelsIntl.svelte +++ b/packages/ui/src/components/DropdownLabelsIntl.svelte @@ -30,7 +30,8 @@ export let label: IntlString = ui.string.DropdownDefaultLabel export let params: Record = {} export let items: DropdownIntlItem[] - export let selected: DropdownIntlItem['id'] | undefined = undefined + export let multiselect: boolean = false + export let selected: DropdownIntlItem['id'] | Array | undefined = multiselect ? [] : undefined export let disabled: boolean = false export let kind: ButtonKind = 'regular' export let size: ButtonSize = 'small' @@ -48,9 +49,11 @@ let container: HTMLElement let opened: boolean = false - $: selectedItem = items.find((x) => x.id === selected) - $: if (shouldUpdateUndefined && selected === undefined && items[0] !== undefined) { - selected = items[0].id + $: selectedItem = multiselect + ? (items ?? []).filter((p) => (selected as Array)?.includes(p.id)) + : (items ?? []).find((x) => x.id === selected) + $: if (shouldUpdateUndefined && selected === undefined && items?.[0] !== undefined) { + selected = multiselect ? [items[0].id] : items[0].id dispatch('selected', selected) } @@ -59,13 +62,24 @@ function openPopup () { if (!opened) { opened = true - showPopup(DropdownLabelsPopupIntl, { items, selected, params, withSearch }, container, (result) => { - if (result) { - selected = result - dispatch('selected', result) + showPopup( + DropdownLabelsPopupIntl, + { items, selected, params, withSearch, multiselect }, + container, + (result) => { + if (result) { + selected = result + dispatch('selected', result) + } + opened = false + }, + (result) => { + if (result != null) { + selected = result + dispatch('selected', result) + } } - opened = false - }) + ) } } @@ -98,10 +112,21 @@ on:click={openPopup} > -
+ + diff --git a/packages/ui/src/components/DropdownLabelsPopupIntl.svelte b/packages/ui/src/components/DropdownLabelsPopupIntl.svelte index b9ed3eea96..013de21455 100644 --- a/packages/ui/src/components/DropdownLabelsPopupIntl.svelte +++ b/packages/ui/src/components/DropdownLabelsPopupIntl.svelte @@ -23,12 +23,24 @@ import ui from '../plugin' export let items: DropdownIntlItem[] - export let selected: DropdownIntlItem['id'] | undefined = undefined + export let selected: DropdownIntlItem['id'] | Array | undefined = undefined + export let multiselect: boolean = false export let params: Record = {} export let withSearch: boolean = false export let searchPlaceholder: IntlString = ui.string.Search const dispatch = createEventDispatcher() + + function isSelected ( + selected: DropdownIntlItem['id'] | Array | undefined, + item: DropdownIntlItem + ): boolean { + if (Array.isArray(selected)) { + return selected.includes(item.id) + } else { + return item.id === selected + } + } let btns: HTMLButtonElement[] = [] const keyDown = (ev: KeyboardEvent, n?: number): void => { @@ -100,7 +112,18 @@ keyDown(ev, i) }} on:click={() => { - dispatch('close', item.id) + if (multiselect && Array.isArray(selected)) { + const index = selected.indexOf(item.id) + if (index !== -1) { + selected.splice(index, 1) + selected = selected + } else { + selected = selected === undefined ? [item.id] : [...selected, item.id] + } + dispatch('update', selected) + } else { + dispatch('close', item.id) + } }} >
@@ -110,7 +133,7 @@
- {#if item.id === selected}{/if} + {#if isSelected(selected, item)}{/if}
{/each} diff --git a/plugins/card-resources/src/components/CardVersionSelector.svelte b/plugins/card-resources/src/components/CardVersionSelector.svelte index 9c579f9eca..1e7d165c5c 100644 --- a/plugins/card-resources/src/components/CardVersionSelector.svelte +++ b/plugins/card-resources/src/components/CardVersionSelector.svelte @@ -20,7 +20,7 @@ import { createQuery, getClient } from '@hcengineering/presentation' import { Button, DropdownLabels, DropdownTextItem, getCurrentLocation, navigate, showPopup } from '@hcengineering/ui' import card from '../plugin' - import NewVersionPopup from './NewVersionPopup.svelte' + import { createNewVersion } from '../utils' export let value: Card @@ -70,10 +70,12 @@ } } - function newVersion (): void { - showPopup(NewVersionPopup, { - value - }) + async function newVersion (): Promise { + const _id = await createNewVersion(value) + const loc = getCurrentLocation() + loc.path[2] = cardId + loc.path[3] = _id + navigate(loc) } diff --git a/plugins/card-resources/src/components/NewVersionPopup.svelte b/plugins/card-resources/src/components/NewVersionPopup.svelte deleted file mode 100644 index 36963c8ed2..0000000000 --- a/plugins/card-resources/src/components/NewVersionPopup.svelte +++ /dev/null @@ -1,106 +0,0 @@ - - - - -
-
-
- - diff --git a/plugins/card-resources/src/components/settings/GeneralSection.svelte b/plugins/card-resources/src/components/settings/GeneralSection.svelte index 9f4a04a05f..38551c8102 100644 --- a/plugins/card-resources/src/components/settings/GeneralSection.svelte +++ b/plugins/card-resources/src/components/settings/GeneralSection.svelte @@ -26,10 +26,13 @@ getCurrentLocation, getPlatformColorDef, IconDelete, + IconSettings, + Label, ModernEditbox, navigate, showPopup, themeStore, + Toggle, ToggleWithLabel } from '@hcengineering/ui' import view from '@hcengineering/view' @@ -37,6 +40,7 @@ import { exportModule } from '../../exporter' import card from '../../plugin' import { deleteMasterTag } from '../../utils' + import VersioningSetting from './VersioningSetting.svelte' export let masterTag: MasterTag @@ -143,6 +147,12 @@ }) } + function versioningSetting (): void { + showPopup(VersioningSetting, { + masterTag: masterTag._id + }) + } + let versioningEnabled = h.classHierarchyMixin(masterTag._id, core.mixin.VersionableClass)?.enabled $: versioningEnabled = h.classHierarchyMixin(masterTag._id, core.mixin.VersionableClass)?.enabled @@ -191,15 +201,16 @@ {/if}
{#if !h.isMixin(masterTag._id)} -
- +
+
-
+
+ + + +
+
+
+
+
+ {#if relationsA.length > 0 || relationsB.length > 0} +
+
+
+
+ {#each relationsB as assoc} + {@const id = `${assoc._id}_b`} + {assoc.nameB} + { + if (e.detail === true) { + excludedRelations.delete(id) + } else { + excludedRelations.add(id) + } + excludedRelations = excludedRelations + }} + /> + {/each} + {#each relationsA as assoc} + {@const id = `${assoc._id}_a`} + {assoc.nameA} + { + if (e.detail === true) { + excludedRelations.delete(id) + } else { + excludedRelations.add(id) + } + excludedRelations = excludedRelations + }} + /> + {/each} +
+ {/if} +
+ + + diff --git a/plugins/card-resources/src/utils.ts b/plugins/card-resources/src/utils.ts index 66f6afc06a..3af45cbf1c 100644 --- a/plugins/card-resources/src/utils.ts +++ b/plugins/card-resources/src/utils.ts @@ -39,6 +39,7 @@ import core, { type Space, toRank, type TxOperations, + type VersionableClass, type WithLookup } from '@hcengineering/core' import login from '@hcengineering/login' @@ -74,6 +75,7 @@ import CreateSpace from './components/navigator/CreateSpace.svelte' import card from './plugin' import { type NavigatorConfig } from './types' import { writable } from 'svelte/store' +import { makeRank } from '@hcengineering/rank' export async function deleteMasterTag (tag: MasterTag | undefined, onDelete?: () => void): Promise { if (tag !== undefined) { @@ -280,7 +282,7 @@ export async function createTypePermissions (masterTag: MasterTag | Tag): Promis async function cloneCard ( origin: Card, overrideProps: Record, - relationToCopy: Set | 'all', + versionableClass?: VersionableClass, copyIds: boolean = false ): Promise> { const client = getClient() @@ -292,8 +294,12 @@ async function cloneCard ( const skipClasses = copyIds ? [core.class.TypeCollaborativeDoc] : [core.class.TypeCollaborativeDoc, core.class.TypeIdentifier] + const systemFields = ['_class', 'id', 'createdOn', 'modifiedOn', 'modifiedBy', 'createdBy', 'createdOn', 'rank'] for (const [key, attr] of attrs) { + if (versionableClass?.excludedProperties?.includes(key) === true || systemFields.includes(key)) { + continue + } if (attr.type._class === core.class.Collection) { ;(props as any)[key] = 0 } else if (!skipClasses.includes(attr.type._class)) { @@ -303,19 +309,25 @@ async function cloneCard ( for (const [k, v] of Object.entries(overrideProps)) { ;(props as any)[k] = v } + props.rank = makeRank(origin.rank, undefined) const targetId = generateId() const relationsA = await client.findAll(core.class.Relation, { docA: origin._id }) const relationsB = await client.findAll(core.class.Relation, { docB: origin._id }) - const markup = await getMarkup(makeDocCollabId(origin, 'content'), origin.content) - if (!isEmptyMarkup(markup)) { - const collabId = makeCollabId(base, targetId, 'content') - props.content = await createMarkup(collabId, markup) + if (versionableClass?.excludedProperties?.includes('content') !== true) { + const markup = await getMarkup(makeDocCollabId(origin, 'content'), origin.content) + if (!isEmptyMarkup(markup)) { + const collabId = makeCollabId(base, targetId, 'content') + props.content = await createMarkup(collabId, markup) + } } const ops = client.apply(`Duplicate_card_${origin._id}`) await ops.createDoc(base, origin.space, props, targetId) for (const mixin of mixins) { + if (versionableClass?.excludeMixins?.includes(mixin) === true) { + continue + } const mixinAttrs = h.getOwnAttributes(mixin) const as = h.as(origin, mixin) const attributes: Partial> = {} @@ -326,7 +338,7 @@ async function cloneCard ( } for (const rel of relationsA) { - if (relationToCopy === 'all' || relationToCopy.has(`${rel.association}_b`)) { + if (versionableClass?.excludedRelations?.includes(`${rel.association}_b`) !== true) { await ops.createDoc(core.class.Relation, core.space.Workspace, { docA: targetId, docB: rel.docB, @@ -335,7 +347,7 @@ async function cloneCard ( } } for (const rel of relationsB) { - if (relationToCopy === 'all' || relationToCopy.has(`${rel.association}_a`)) { + if (versionableClass?.excludedRelations?.includes(`${rel.association}_a`) !== true) { await ops.createDoc(core.class.Relation, core.space.Workspace, { docA: rel.docA, docB: targetId, @@ -345,25 +357,23 @@ async function cloneCard ( } await ops.commit() - const attachments = await client.findAll(attachment.class.Attachment, { attachedTo: origin._id }) - const attachmentOps = client.apply(`Duplicate_attachments_${origin._id}`) - for (const att of attachments) { - const { _id, modifiedBy, modifiedOn, attachedTo, attachedToClass, collection, space, ...props } = att - await attachmentOps.addCollection(attachment.class.Attachment, origin.space, targetId, base, 'attachments', props) + if (versionableClass?.excludedProperties?.includes('attachments') !== true) { + const attachments = await client.findAll(attachment.class.Attachment, { attachedTo: origin._id }) + const attachmentOps = client.apply(`Duplicate_attachments_${origin._id}`) + for (const att of attachments) { + const { _id, modifiedBy, modifiedOn, attachedTo, attachedToClass, collection, space, ...props } = att + await attachmentOps.addCollection(attachment.class.Attachment, origin.space, targetId, base, 'attachments', props) + } + await attachmentOps.commit() } - await attachmentOps.commit() return targetId } export async function duplicateCard (origin: Card): Promise { - const targetId = await cloneCard( - origin, - { - title: `${origin.title} (Copy)` - }, - 'all' - ) + const targetId = await cloneCard(origin, { + title: `${origin.title} (Copy)` + }) const loc = getCurrentLocation() loc.path[2] = cardId @@ -513,14 +523,17 @@ export async function cardFactory (props: Record = {}): Promise): Promise> { +export async function createNewVersion (card: Card): Promise> { + const client = getClient() + const mixin = client.getHierarchy().classHierarchyMixin(card._class, core.mixin.VersionableClass) + return await cloneCard( card, { baseId: card.baseId, docCreatedBy: card.docCreatedBy ?? card.createdBy ?? card.modifiedBy }, - relationsToCopy, + mixin, true ) } diff --git a/plugins/process-resources/src/plugin.ts b/plugins/process-resources/src/plugin.ts index 55d58db6b6..de5dcd33bc 100644 --- a/plugins/process-resources/src/plugin.ts +++ b/plugins/process-resources/src/plugin.ts @@ -267,6 +267,7 @@ export default mergeIds(processId, process, { UnlockField: '' as IntlString, Export: '' as IntlString, Import: '' as IntlString, + TextFromIdentifier: '' as IntlString, TextFromNumber: '' as IntlString, TextFromDate: '' as IntlString, TextFromCheckbox: '' as IntlString, diff --git a/plugins/process/src/index.ts b/plugins/process/src/index.ts index dab168fdab..c1147efca7 100644 --- a/plugins/process/src/index.ts +++ b/plugins/process/src/index.ts @@ -378,6 +378,7 @@ export default plugin(processId, { EmptyValue: '' as Ref, EmptyArray: '' as Ref, CurrentDate: '' as Ref, + StringFromIdentifier: '' as Ref, StringFromNumber: '' as Ref, StringFromDate: '' as Ref, StringFromMarkup: '' as Ref, diff --git a/server-plugins/process-resources/src/index.ts b/server-plugins/process-resources/src/index.ts index 238d71812d..ad53f2be09 100644 --- a/server-plugins/process-resources/src/index.ts +++ b/server-plugins/process-resources/src/index.ts @@ -107,6 +107,7 @@ import { LastValue, LowerCase, MarkupFromString, + StringFromIdentifier, Max, Min, Modulo, @@ -800,6 +801,7 @@ export default async () => ({ Max, StringFromMarkup, MarkupFromString, + StringFromIdentifier, StringFromEnum, EnumFromString }, diff --git a/server-plugins/process-resources/src/transform.ts b/server-plugins/process-resources/src/transform.ts index 31806a2af8..66e412efce 100644 --- a/server-plugins/process-resources/src/transform.ts +++ b/server-plugins/process-resources/src/transform.ts @@ -582,6 +582,10 @@ export function MarkupFromString (value: string): string { return value } +export function StringFromIdentifier (value: string): string { + return value +} + export function StringFromEnum (value: string): string { if (value == null) return '' return String(value) diff --git a/server-plugins/process/src/index.ts b/server-plugins/process/src/index.ts index 83318be440..895ae7b1d9 100644 --- a/server-plugins/process/src/index.ts +++ b/server-plugins/process/src/index.ts @@ -126,6 +126,7 @@ export default plugin(serverProcessId, { NumberFromString: '' as Resource, DateFromString: '' as Resource, MarkupFromString: '' as Resource, + StringFromIdentifier: '' as Resource, YearFromDate: '' as Resource, MonthFromDate: '' as Resource, DayFromDate: '' as Resource, From 309aefb9673fd964fa89f9cd09f7d53dd83b3ded Mon Sep 17 00:00:00 2001 From: Denis Bykhov Date: Sun, 17 May 2026 16:05:17 +0500 Subject: [PATCH 07/52] Fix_relation_versioning_setting (#10844) Signed-off-by: Denis Bykhov --- .../src/components/settings/VersioningSetting.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/card-resources/src/components/settings/VersioningSetting.svelte b/plugins/card-resources/src/components/settings/VersioningSetting.svelte index 89101a2da3..feb2bd3df1 100644 --- a/plugins/card-resources/src/components/settings/VersioningSetting.svelte +++ b/plugins/card-resources/src/components/settings/VersioningSetting.svelte @@ -175,7 +175,7 @@ {@const id = `${assoc._id}_a`} {assoc.nameA} { if (e.detail === true) { excludedRelations.delete(id) From 9910e8d502de9ae32d9d1e133948185faec1b457 Mon Sep 17 00:00:00 2001 From: Denis Bykhov Date: Sun, 17 May 2026 20:18:38 +0500 Subject: [PATCH 08/52] Add missing translation (#10845) Signed-off-by: Denis Bykhov --- plugins/process-assets/lang/cs.json | 3 ++- plugins/process-assets/lang/de.json | 3 ++- plugins/process-assets/lang/en.json | 3 ++- plugins/process-assets/lang/es.json | 3 ++- plugins/process-assets/lang/fr.json | 3 ++- plugins/process-assets/lang/it.json | 3 ++- plugins/process-assets/lang/ja.json | 3 ++- plugins/process-assets/lang/ko.json | 3 ++- plugins/process-assets/lang/pt-br.json | 3 ++- plugins/process-assets/lang/pt.json | 3 ++- plugins/process-assets/lang/ru.json | 3 ++- plugins/process-assets/lang/tr.json | 3 ++- plugins/process-assets/lang/zh.json | 3 ++- 13 files changed, 26 insertions(+), 13 deletions(-) diff --git a/plugins/process-assets/lang/cs.json b/plugins/process-assets/lang/cs.json index 3203b5b923..3eb87585db 100644 --- a/plugins/process-assets/lang/cs.json +++ b/plugins/process-assets/lang/cs.json @@ -171,7 +171,8 @@ "TextFromMarkup": "Text z MarkUp", "TextFromSelect": "Text z výběru", "SelectFromText": "Výběr z textu", - "AskSubclass": "Zeptat se na podtřídu" + "AskSubclass": "Zeptat se na podtřídu", + "TextFromIdentifier": "Text z identifikátoru" }, "error": { "MethodNotFound": "Metoda nenalezena: {methodId}", diff --git a/plugins/process-assets/lang/de.json b/plugins/process-assets/lang/de.json index 873547e928..98ceeff370 100644 --- a/plugins/process-assets/lang/de.json +++ b/plugins/process-assets/lang/de.json @@ -171,7 +171,8 @@ "MarkupFromText": "Markup aus Text", "TextFromSelect": "Text aus Auswahl", "SelectFromText": "Auswahl aus Text", - "AskSubclass": "Nach Unterklasse fragen" + "AskSubclass": "Nach Unterklasse fragen", + "TextFromIdentifier": "Text aus Identifikator" }, "error": { "MethodNotFound": "Methode nicht gefunden: {methodId}", diff --git a/plugins/process-assets/lang/en.json b/plugins/process-assets/lang/en.json index bea2c5b76a..0077c6acef 100644 --- a/plugins/process-assets/lang/en.json +++ b/plugins/process-assets/lang/en.json @@ -176,7 +176,8 @@ "MarkupFromText": "Markup from text", "TextFromSelect": "Text from select", "SelectFromText": "Select from text", - "AskSubclass": "Ask subclass" + "AskSubclass": "Ask subclass", + "TextFromIdentifier": "Text from identifier" }, "error": { "MethodNotFound": "Method not found: {methodId}", diff --git a/plugins/process-assets/lang/es.json b/plugins/process-assets/lang/es.json index 973a7ad5c9..92f84e0701 100644 --- a/plugins/process-assets/lang/es.json +++ b/plugins/process-assets/lang/es.json @@ -176,7 +176,8 @@ "MarkupFromText": "Markup desde texto", "TextFromSelect": "Texto desde selección", "SelectFromText": "Selección desde texto", - "AskSubclass": "Preguntar por subclase" + "AskSubclass": "Preguntar por subclase", + "TextFromIdentifier": "Texto desde identificador" }, "error": { "MethodNotFound": "Método no encontrado: {methodId}", diff --git a/plugins/process-assets/lang/fr.json b/plugins/process-assets/lang/fr.json index 860b3dfc57..d13f10ee75 100644 --- a/plugins/process-assets/lang/fr.json +++ b/plugins/process-assets/lang/fr.json @@ -176,7 +176,8 @@ "MarkupFromText": "Markup depuis texte", "TextFromSelect": "Texte depuis sélection", "SelectFromText": "Sélection depuis texte", - "AskSubclass": "Demander la sous-classe" + "AskSubclass": "Demander la sous-classe", + "TextFromIdentifier": "Texte depuis identifiant" }, "error": { "MethodNotFound": "Méthode introuvable : {methodId}", diff --git a/plugins/process-assets/lang/it.json b/plugins/process-assets/lang/it.json index 9d88fbc691..ed2f7e3401 100644 --- a/plugins/process-assets/lang/it.json +++ b/plugins/process-assets/lang/it.json @@ -176,7 +176,8 @@ "MarkupFromText": "Markup da testo", "TextFromSelect": "Testo da selezione", "SelectFromText": "Selezione da testo", - "AskSubclass": "Chiedi sottoclasse" + "AskSubclass": "Chiedi sottoclasse", + "TextFromIdentifier": "Testo da identificatore" }, "error": { "MethodNotFound": "Metodo non trovato: {methodId}", diff --git a/plugins/process-assets/lang/ja.json b/plugins/process-assets/lang/ja.json index d6253f91a2..deed7948c1 100644 --- a/plugins/process-assets/lang/ja.json +++ b/plugins/process-assets/lang/ja.json @@ -175,7 +175,8 @@ "MarkupFromText": "マークアップからテキスト", "TextFromSelect": "テキストから選択", "SelectFromText": "選択からテキスト", - "AskSubclass": "サブクラスを尋ねる" + "AskSubclass": "サブクラスを尋ねる", + "TextFromIdentifier": "テキストから識別子" }, "error": { "MethodNotFound": "メソッドが見つかりません: {methodId}", diff --git a/plugins/process-assets/lang/ko.json b/plugins/process-assets/lang/ko.json index 042d94f0a1..140acf1aeb 100644 --- a/plugins/process-assets/lang/ko.json +++ b/plugins/process-assets/lang/ko.json @@ -176,7 +176,8 @@ "MarkupFromText": "텍스트에서 마크업으로", "TextFromSelect": "선택에서 텍스트로", "SelectFromText": "텍스트에서 선택으로", - "AskSubclass": "하위 클래스 선택" + "AskSubclass": "하위 클래스 선택", + "TextFromIdentifier": "식별자에서 텍스트로" }, "error": { "MethodNotFound": "메서드를 찾을 수 없습니다: {methodId}", diff --git a/plugins/process-assets/lang/pt-br.json b/plugins/process-assets/lang/pt-br.json index 1d2665b96e..5ee9a3286a 100644 --- a/plugins/process-assets/lang/pt-br.json +++ b/plugins/process-assets/lang/pt-br.json @@ -164,7 +164,8 @@ "MarkupFromText": "Marcação de texto", "TextFromSelect": "Texto de seleção", "SelectFromText": "Seleção de texto", - "AskSubclass": "Perguntar subclasse" + "AskSubclass": "Perguntar subclasse", + "TextFromIdentifier": "Texto de identificador" }, "error": { "MethodNotFound": "Método não encontrado: {methodId}", diff --git a/plugins/process-assets/lang/pt.json b/plugins/process-assets/lang/pt.json index f94a9a46fb..152f18a4a9 100644 --- a/plugins/process-assets/lang/pt.json +++ b/plugins/process-assets/lang/pt.json @@ -176,7 +176,8 @@ "MarkupFromText": "Marcação de texto", "TextFromSelect": "Texto de seleção", "SelectFromText": "Seleção de texto", - "AskSubclass": "Perguntar subclasse" + "AskSubclass": "Perguntar subclasse", + "TextFromIdentifier": "Texto de identificador" }, "error": { "MethodNotFound": "Método não encontrado: {methodId}", diff --git a/plugins/process-assets/lang/ru.json b/plugins/process-assets/lang/ru.json index a4918da8fc..88972a2007 100644 --- a/plugins/process-assets/lang/ru.json +++ b/plugins/process-assets/lang/ru.json @@ -176,7 +176,8 @@ "TextFromMarkup": "Разметка из текста", "TextFromSelect": "Текст из выбора", "SelectFromText": "Выбор из текста", - "AskSubclass": "Спросить подкласс" + "AskSubclass": "Спросить подкласс", + "TextFromIdentifier": "Текст из идентификатора" }, "error": { "MethodNotFound": "Метод не найден: {methodId}", diff --git a/plugins/process-assets/lang/tr.json b/plugins/process-assets/lang/tr.json index 791429d87f..4dcc0c1f69 100644 --- a/plugins/process-assets/lang/tr.json +++ b/plugins/process-assets/lang/tr.json @@ -171,7 +171,8 @@ "MarkupFromText": "İşaretlemeden metin", "TextFromSelect": "Seçimden metin", "SelectFromText": "Metinden seçim", - "AskSubclass": "Alt sınıfı sor" + "AskSubclass": "Alt sınıfı sor", + "TextFromIdentifier": "Tanımlayıcıdan metne" }, "error": { "MethodNotFound": "Metod bulunamadı: {methodId}", diff --git a/plugins/process-assets/lang/zh.json b/plugins/process-assets/lang/zh.json index cd5c7f5f0e..8afa4d0d9d 100644 --- a/plugins/process-assets/lang/zh.json +++ b/plugins/process-assets/lang/zh.json @@ -176,7 +176,8 @@ "MarkupFromText": "文本到标记", "TextFromSelect": "选择到文本", "SelectFromText": "文本到选择", - "AskSubclass": "询问子类" + "AskSubclass": "询问子类", + "TextFromIdentifier": "文本从标识符" }, "error": { "MethodNotFound": "找不到方法:{methodId}", From f2a0c4a106386d456ee7f0f28ddbc5d7c646d26c Mon Sep 17 00:00:00 2001 From: Denis Bykhov Date: Sun, 17 May 2026 20:18:49 +0500 Subject: [PATCH 09/52] fix enum presenter readonly (#10846) Signed-off-by: Denis Bykhov --- .../src/components/DocTable.svelte | 70 ++++++++++++------- .../src/components/EnumPresenter.svelte | 3 +- .../src/components/Table.svelte | 2 +- 3 files changed, 47 insertions(+), 28 deletions(-) diff --git a/plugins/view-resources/src/components/DocTable.svelte b/plugins/view-resources/src/components/DocTable.svelte index aae5d72edf..910b14f654 100644 --- a/plugins/view-resources/src/components/DocTable.svelte +++ b/plugins/view-resources/src/components/DocTable.svelte @@ -26,6 +26,7 @@ import { getResource } from '@hcengineering/platform' import { Readable } from 'svelte/store' import { canChangeAttribute } from '../permissions' + import { canEditSpace } from '../visibilityTester' export let objects: Doc[] export let config: Array @@ -81,7 +82,7 @@ dispatch('row-focus', object) } - const joinProps = (attribute: AttributeModel, object: Doc, readonly: boolean) => { + const joinProps = (attribute: AttributeModel, object: Doc, readonly: boolean, editable: boolean) => { const readonlyParams = readonly || (attribute?.attribute?.readonly ?? false) ? { @@ -89,7 +90,12 @@ editable: false, disabled: true } - : {} + : { + readonly: !editable, + editable + } + + console.log('attr', attribute, readonlyParams) if (attribute.collectionAttr || attribute.attribute?.type?._class === core.class.TypeIdentifier) { return { object, ...attribute.props, ...readonlyParams } } @@ -98,6 +104,7 @@ } return { ...attribute.props, space: object.space, ...readonlyParams } } + function getValue (attribute: AttributeModel, object: Doc): any { if (attribute.castRequest) { return getObjectValue( @@ -117,6 +124,7 @@ if (attr === undefined) return if (attribute.collectionAttr) return if (attribute.isLookup) return + if (attribute.attribute?.readonly === true) return const key = attribute.castRequest ? attribute.key.substring(attribute.castRequest.length + 1) : attribute.key return (value: any) => { onChange(value, doc, key, attr) @@ -170,6 +178,13 @@ if (permissionsStore === undefined) return true return canChangeAttribute(attr, object.space as Ref, permissionsStore, object._class) } + + async function canEdit (object: Doc): Promise { + if (client.getHierarchy().isDerived(object._class, core.class.Space)) { + return await canEditSpace(object) + } + return true + } {#if !model || isBuildingModel} @@ -213,30 +228,33 @@ bind:this={refs[row]} on:contextmenu={contextHandler(object, row)} > - {#each model as attribute, cell} - -
- -
- - {/each} + {#await canEdit(object) then canEditObject} + {#each model as attribute, cell} + +
+ +
+ + {/each} + {/await} {/each} diff --git a/plugins/view-resources/src/components/EnumPresenter.svelte b/plugins/view-resources/src/components/EnumPresenter.svelte index 032cfcd3a0..3a0428f6c2 100644 --- a/plugins/view-resources/src/components/EnumPresenter.svelte +++ b/plugins/view-resources/src/components/EnumPresenter.svelte @@ -21,6 +21,7 @@ export let value: string export let type: EnumOf | undefined = undefined + export let readonly = false export let onChange: ((value: string) => void) | undefined = undefined let items: DropdownTextItem[] = [] @@ -42,7 +43,7 @@ ) -{#if onChange !== undefined && type !== undefined} +{#if onChange !== undefined && type !== undefined && !readonly} { onChange(value, doc, key, attr) From 372a4b0ca31ecda5aa826c14b58ac6838889e83a Mon Sep 17 00:00:00 2001 From: Denis Bykhov Date: Mon, 18 May 2026 01:31:17 +0500 Subject: [PATCH 10/52] Id override (#10847) Signed-off-by: Denis Bykhov --- models/card/src/actions.ts | 8 +- models/card/src/index.ts | 18 +- models/card/src/plugin.ts | 3 +- .../src/components/CardAttributeEditor.svelte | 16 +- .../src/components/DuplicateCard.svelte | 203 ++++++++++++++++ .../src/components/EditCardNew.svelte | 2 +- .../src/components/MasterTagAttributes.svelte | 6 +- .../src/components/TagAttributes.svelte | 5 +- .../settings/DuplicateSetting.svelte | 217 ++++++++++++++++++ .../components/settings/GeneralSection.svelte | 13 ++ .../settings/VersioningSetting.svelte | 63 ++--- plugins/card-resources/src/index.ts | 6 +- plugins/card-resources/src/plugin.ts | 1 + plugins/card-resources/src/utils.ts | 38 +-- plugins/card/src/index.ts | 9 +- plugins/setting-assets/lang/cs.json | 3 +- plugins/setting-assets/lang/de.json | 3 +- plugins/setting-assets/lang/en.json | 3 +- plugins/setting-assets/lang/es.json | 3 +- plugins/setting-assets/lang/fr.json | 3 +- plugins/setting-assets/lang/it.json | 3 +- plugins/setting-assets/lang/ja.json | 3 +- plugins/setting-assets/lang/ko.json | 3 +- plugins/setting-assets/lang/pt-br.json | 3 +- plugins/setting-assets/lang/pt.json | 3 +- plugins/setting-assets/lang/ru.json | 3 +- plugins/setting-assets/lang/tr.json | 3 +- plugins/setting-assets/lang/zh.json | 3 +- .../src/components/ClassAttributesList.svelte | 28 +++ plugins/setting-resources/src/plugin.ts | 3 +- 30 files changed, 594 insertions(+), 84 deletions(-) create mode 100644 plugins/card-resources/src/components/DuplicateCard.svelte create mode 100644 plugins/card-resources/src/components/settings/DuplicateSetting.svelte diff --git a/models/card/src/actions.ts b/models/card/src/actions.ts index c948a572a8..436b479eb0 100644 --- a/models/card/src/actions.ts +++ b/models/card/src/actions.ts @@ -264,7 +264,13 @@ export function createActions (builder: Builder): void { createAction( builder, { - action: card.actionImpl.DuplicateCard, + action: view.actionImpl.ShowPopup, + actionProps: { + component: card.component.DuplicateCard, + fillProps: { + _object: 'value' + } + }, label: card.string.Duplicate, icon: card.icon.Duplicate, input: 'focus', diff --git a/models/card/src/index.ts b/models/card/src/index.ts index 14c76397eb..fbb4d05c00 100644 --- a/models/card/src/index.ts +++ b/models/card/src/index.ts @@ -12,7 +12,6 @@ // limitations under the License. import activity from '@hcengineering/activity' -import communication from '@hcengineering/communication' import { type CanCreateCardResource, type Card, @@ -23,6 +22,7 @@ import { type CardViewDefaults, type CreateCardExtension, DOMAIN_CARD, + type DuplicateSetting, type ExportExtension, type ExportFunc, type FavoriteCard, @@ -34,6 +34,8 @@ import { type Tag } from '@hcengineering/card' import chunter from '@hcengineering/chunter' +import communication from '@hcengineering/communication' +import converter from '@hcengineering/converter' import core, { AccountRole, type Blobs, @@ -46,6 +48,7 @@ import core, { DOMAIN_SPACE, IndexKind, type MarkupBlobRef, + type Mixin as MixinType, type MixinData, type Rank, type Ref, @@ -77,7 +80,7 @@ import presentation from '@hcengineering/model-presentation' import setting from '@hcengineering/model-setting' import view, { type Viewlet } from '@hcengineering/model-view' import workbench, { WidgetType } from '@hcengineering/model-workbench' -import converter from '@hcengineering/converter' +import notification from '@hcengineering/notification' import { type Asset, getEmbeddedLabel, type IntlString, type Resource } from '@hcengineering/platform' import time, { type ToDo } from '@hcengineering/time' import { PaletteColorIndexes } from '@hcengineering/ui/src/colors' @@ -86,7 +89,6 @@ import { type BuildModelKey, type ViewOptionModel } from '@hcengineering/view' import { createActions } from './actions' import { defineActionPermissions, definePermissions } from './permissions' import card from './plugin' -import notification from '@hcengineering/notification' export { cardId } from '@hcengineering/card' @@ -223,6 +225,13 @@ export class TExportExtension extends TDoc implements ExportExtension { func!: Resource } +@Mixin(card.mixin.DuplicateSetting, card.class.MasterTag) +export class TDuplicateSetting extends TMasterTag implements DuplicateSetting { + excludedProperties?: string[] + excludedRelations?: string[] // ${associationId}_${a|b} + excludeMixins?: Ref>[] +} + export * from './migration' const showAllVersionsOption: ViewOptionModel = { @@ -441,7 +450,8 @@ export function createModel (builder: Builder): void { TFavoriteCard, TFavoriteType, TCreateCardExtension, - TExportExtension + TExportExtension, + TDuplicateSetting ) builder.createDoc( diff --git a/models/card/src/plugin.ts b/models/card/src/plugin.ts index 74a3e31fef..ee8e754b19 100644 --- a/models/card/src/plugin.ts +++ b/models/card/src/plugin.ts @@ -30,7 +30,6 @@ export default mergeIds(cardId, card, { }, actionImpl: { DeleteMasterTag: '' as ViewAction, - DuplicateCard: '' as ViewAction, EditSpace: '' as ViewAction, CreateChild: '' as ViewAction }, @@ -39,7 +38,7 @@ export default mergeIds(cardId, card, { SetParent: '' as Ref>, UnsetParent: '' as Ref>, PublicLink: '' as Ref>, - Duplicate: '' as Ref, + Duplicate: '' as Ref>, CreateChild: '' as Ref }, category: { diff --git a/plugins/card-resources/src/components/CardAttributeEditor.svelte b/plugins/card-resources/src/components/CardAttributeEditor.svelte index b5c7766d56..47158c15a7 100644 --- a/plugins/card-resources/src/components/CardAttributeEditor.svelte +++ b/plugins/card-resources/src/components/CardAttributeEditor.svelte @@ -71,26 +71,14 @@
{/if}
- value.readonlySections?.includes(p))} - {value} - {ignoreKeys} - fourRows={columns === 2} - /> +
{#if mixins.length > 0}
{#each mixins as tag, i (tag._id)}
- +
{/each}
diff --git a/plugins/card-resources/src/components/DuplicateCard.svelte b/plugins/card-resources/src/components/DuplicateCard.svelte new file mode 100644 index 0000000000..205e08cac5 --- /dev/null +++ b/plugins/card-resources/src/components/DuplicateCard.svelte @@ -0,0 +1,203 @@ + + + + +
+ {#if allProperties.length > 0} +
+
+ {/if} + {#if mixins.length > 0} +
+
+ {/if} + {#if relationsA.length > 0 || relationsB.length > 0} +
+
+
+
+ {#each relationsB as assoc} + {@const id = `${assoc._id}_b`} + {assoc.nameB} + { + if (e.detail === true) { + excludedRelations.delete(id) + } else { + excludedRelations.add(id) + } + excludedRelations = excludedRelations + }} + /> + {/each} + {#each relationsA as assoc} + {@const id = `${assoc._id}_a`} + {assoc.nameA} + { + if (e.detail === true) { + excludedRelations.delete(id) + } else { + excludedRelations.add(id) + } + excludedRelations = excludedRelations + }} + /> + {/each} +
+ {/if} +
+ + + diff --git a/plugins/card-resources/src/components/EditCardNew.svelte b/plugins/card-resources/src/components/EditCardNew.svelte index 19b6b8477b..31b6a25d17 100644 --- a/plugins/card-resources/src/components/EditCardNew.svelte +++ b/plugins/card-resources/src/components/EditCardNew.svelte @@ -277,7 +277,7 @@ }} /> - {#if !_readonly} + {#if !readonly}
- - + + diff --git a/plugins/card-resources/src/components/settings/GeneralSection.svelte b/plugins/card-resources/src/components/settings/GeneralSection.svelte index 38551c8102..7de3ad16e8 100644 --- a/plugins/card-resources/src/components/settings/GeneralSection.svelte +++ b/plugins/card-resources/src/components/settings/GeneralSection.svelte @@ -41,6 +41,7 @@ import card from '../../plugin' import { deleteMasterTag } from '../../utils' import VersioningSetting from './VersioningSetting.svelte' + import DuplicateSetting from './DuplicateSetting.svelte' export let masterTag: MasterTag @@ -153,6 +154,12 @@ }) } + function duplicateSetting (): void { + showPopup(DuplicateSetting, { + masterTag: masterTag._id + }) + } + let versioningEnabled = h.classHierarchyMixin(masterTag._id, core.mixin.VersionableClass)?.enabled $: versioningEnabled = h.classHierarchyMixin(masterTag._id, core.mixin.VersionableClass)?.enabled @@ -217,6 +224,12 @@ on:change={(e) => attributeUpdated('singleColumn', e.detail)} />
+
+
{/if}
diff --git a/plugins/card-resources/src/components/settings/VersioningSetting.svelte b/plugins/card-resources/src/components/settings/VersioningSetting.svelte index feb2bd3df1..a58231c944 100644 --- a/plugins/card-resources/src/components/settings/VersioningSetting.svelte +++ b/plugins/card-resources/src/components/settings/VersioningSetting.svelte @@ -17,11 +17,11 @@ - +
-
-