From 75c207a82215d9ada5ed6376a2a940c7a9bda1fb Mon Sep 17 00:00:00 2001 From: Victor Ilyushchenko Date: Wed, 19 Feb 2025 10:28:58 +0300 Subject: [PATCH 1/2] UBERF-9144: Stay in same view after delete sub-issue (#8051) * UBERF-9144: Stay in same view after delete sub-issue Signed-off-by: Victor Ilyushchenko * fmt Signed-off-by: Victor Ilyushchenko * move link resolver back to text-editor-resources Signed-off-by: Victor Ilyushchenko * ff Signed-off-by: Victor Ilyushchenko --------- Signed-off-by: Victor Ilyushchenko --- .../src/components/extension/reference.ts | 59 +++++++++++++++---- plugins/text-editor-resources/src/index.ts | 1 + plugins/tracker-resources/src/index.ts | 14 ++++- 3 files changed, 58 insertions(+), 16 deletions(-) diff --git a/plugins/text-editor-resources/src/components/extension/reference.ts b/plugins/text-editor-resources/src/components/extension/reference.ts index 65f9004b17..8c1b69165a 100644 --- a/plugins/text-editor-resources/src/components/extension/reference.ts +++ b/plugins/text-editor-resources/src/components/extension/reference.ts @@ -1,3 +1,18 @@ +// +// Copyright © 2025 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 { mergeAttributes, type Editor } from '@tiptap/core' import { Plugin, PluginKey } from '@tiptap/pm/state' import MentionList from '../MentionList.svelte' @@ -9,8 +24,9 @@ import Suggestion, { type SuggestionKeyDownProps, type SuggestionOptions, type S import { type Class, type Doc, type Ref } from '@hcengineering/core' import { getMetadata, getResource } from '@hcengineering/platform' import presentation, { createQuery, getClient } from '@hcengineering/presentation' -import { parseLocation } from '@hcengineering/ui' import view from '@hcengineering/view' + +import { parseLocation, type Location } from '@hcengineering/ui' import workbench, { type Application } from '@hcengineering/workbench' export interface ReferenceExtensionOptions extends ReferenceOptions { @@ -104,7 +120,7 @@ export const ReferenceExtension = ReferenceNode.extend { span.setAttribute('data-label', props.label) - span.innerText = options.renderLabel({ options, props: props ?? node.attrs }) + span.innerText = options.renderLabel({ options, props: props ?? (node.attrs as ReferenceNodeProps) }) } const id = node.attrs.id @@ -328,16 +344,37 @@ export async function getReferenceLabel ( return label } -export async function getReferenceFromUrl (text: string): Promise { +export async function getReferenceFromUrl (urlString: string): Promise { + const target = await getTargetObjectFromUrl(urlString) + if (target === undefined) return + + const label = await getReferenceLabel(target._class, target._id) + if (label === '') return + + return { + id: target._id, + objectclass: target._class, + label + } +} + +export async function getTargetObjectFromUrl ( + urlOrLocation: string | Location +): Promise<{ _id: Ref, _class: Ref> } | undefined> { const client = getClient() const hierarchy = client.getHierarchy() - const url = new URL(text) + let location: Location + if (typeof urlOrLocation === 'string') { + const url = new URL(urlOrLocation) - const frontUrl = getMetadata(presentation.metadata.FrontUrl) ?? window.location.origin - if (url.origin !== frontUrl) return + const frontUrl = getMetadata(presentation.metadata.FrontUrl) ?? window.location.origin + if (url.origin !== frontUrl) return - const location = parseLocation(url) + location = parseLocation(url) + } else { + location = urlOrLocation + } const appAlias = (location.path[2] ?? '').trim() if (!(appAlias.length > 0)) return @@ -363,12 +400,8 @@ export async function getReferenceFromUrl (text: string): Promise | undefined = linkProvider !== undefined ? (await (await getResource(linkProvider.decode))(id)) ?? id : id - const label = await getReferenceLabel(objectclass, _id) - if (label === '') return - return { - id: _id, - objectclass, - label + _id, + _class: objectclass } } diff --git a/plugins/text-editor-resources/src/index.ts b/plugins/text-editor-resources/src/index.ts index 0e2d6fe394..b5747179f9 100644 --- a/plugins/text-editor-resources/src/index.ts +++ b/plugins/text-editor-resources/src/index.ts @@ -27,6 +27,7 @@ import { openImage, downloadImage, expandImage, moreImageActions } from './compo import { configureNote, isEditableNote } from './components/extension/note' import { createInlineComment, shouldShowCreateInlineCommentAction } from './components/extension/inlineComment' import { isTextStylingEnabled, openBackgroundColorOptions, openTextColorOptions } from './components/extension/colors' +export { getTargetObjectFromUrl, getReferenceFromUrl, getReferenceLabel } from './components/extension/reference' export * from '@hcengineering/presentation/src/types' export type { EditorKitOptions } from './kits/editor-kit' diff --git a/plugins/tracker-resources/src/index.ts b/plugins/tracker-resources/src/index.ts index 4397654845..62f2e12ce1 100644 --- a/plugins/tracker-resources/src/index.ts +++ b/plugins/tracker-resources/src/index.ts @@ -170,11 +170,12 @@ import ProjectSpacePresenter from './components/projects/ProjectSpacePresenter.s import { get } from 'svelte/store' import { settingId } from '@hcengineering/setting' +import type { TaskType } from '@hcengineering/task' import { getAllStates } from '@hcengineering/task-resources' +import view, { type Filter } from '@hcengineering/view' import EstimationValueEditor from './components/issues/timereport/EstimationValueEditor.svelte' import TimePresenter from './components/issues/timereport/TimePresenter.svelte' -import type { TaskType } from '@hcengineering/task' -import view, { type Filter } from '@hcengineering/view' +import { getTargetObjectFromUrl } from '@hcengineering/text-editor-resources' export { default as AssigneeEditor } from './components/issues/AssigneeEditor.svelte' export { default as SubIssueList } from './components/issues/edit/SubIssueList.svelte' @@ -265,12 +266,19 @@ async function deleteIssue (issue: Issue | Issue[]): Promise { }, action: async () => { const objs = Array.isArray(issue) ? issue : [issue] + + const target = await getTargetObjectFromUrl(getCurrentLocation()) + const deletingFromTargetIssuePage = objs.some((obj) => obj._id === target?._id) + try { await deleteObjects(getClient(), objs as unknown as Doc[]) } catch (err: any) { Analytics.handleError(err) } - closePanel() + + if (deletingFromTargetIssuePage) { + closePanel() + } } }) } From 7e18269f06a4e73964af8dcfa90d4496387dc4b3 Mon Sep 17 00:00:00 2001 From: Alexander Onnikov Date: Wed, 19 Feb 2025 14:29:18 +0700 Subject: [PATCH 2/2] UBERF-9491 Filter out default undefined values from component props (#8052) Signed-off-by: Alexander Onnikov --- packages/ui/src/components/Component.svelte | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/components/Component.svelte b/packages/ui/src/components/Component.svelte index 26f10aec6b..e83560e83e 100644 --- a/packages/ui/src/components/Component.svelte +++ b/packages/ui/src/components/Component.svelte @@ -33,11 +33,23 @@ let _is: AnyComponent | AnySvelteComponent = is let _props: any = props + // See https://github.com/sveltejs/svelte/issues/4068 + // When passing undefined prop value, then Svelte uses default value only first time when + // component is instantiated. On the next update the value will be set to undefined. + // Here we filter out undefined values from props on updates to ensure we don't overwrite them. + const filterDefaultUndefined = (pnew: any, pold: any): any => + pnew != null + ? Object.fromEntries(Object.entries(pnew).filter(([k, v]) => v !== undefined || pold?.[k] !== undefined)) + : pnew + $: if (!deepEqual(_is, is)) { _is = is } - $: if (!deepEqual(_props, props)) { - _props = props + $: { + const p = filterDefaultUndefined(props, _props) + if (!deepEqual(_props, p)) { + _props = p + } } let Ctor: any @@ -61,6 +73,7 @@ .then((res) => { if (current === counter) { Ctor = res + _props = props loading = false } }) @@ -72,9 +85,11 @@ } else { loading = false Ctor = component + _props = props } } else { Ctor = _is + _props = props } }