diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index 8126b9d196..f0e1d4c7bb 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -1865,6 +1865,9 @@ dependencies: telegram: specifier: 2.22.2 version: 2.22.2 + tippy.js: + specifier: ~6.3.7 + version: 6.3.7 toposort: specifier: ^2.0.2 version: 2.0.2 @@ -22919,7 +22922,7 @@ packages: dev: false file:projects/chunter-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-xn0DRxwtWoYCOZuUKT7v5zyeNjrU+lugmH/+rOqHdfVMoOmDg3l3cVWyAGf/4MOJMdT1dMt/0Nd3GT8R8Tiexg==, tarball: file:projects/chunter-resources.tgz} + resolution: {integrity: sha512-yjFks6novOHe2i9lKTi27FM5/AchzWFrTqCnp701jfGRYrSreAO+t2ebIlA/o0IGxKxHv4ZHG4fF6FIPitwwGw==, tarball: file:projects/chunter-resources.tgz} id: file:projects/chunter-resources.tgz name: '@rush-temp/chunter-resources' version: 0.0.0 @@ -32550,7 +32553,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-ZxHpsDpXsdc+KLgeibgCiwb/+isfY43XjNKXDtRfSSDy0X5kC4g5uBqAOri+Kk9v8Rko0jTKZtLLuo/AnqhJxw==, tarball: file:projects/text-editor-resources.tgz} + resolution: {integrity: sha512-hCYKIJCnXXMyGcVD33T1AMClfJ3pB7F0pDPTw8ti2g1ldib78IOMw334ZX4N2LhzVxtBe3AGbz0dpp6CHTQFXg==, tarball: file:projects/text-editor-resources.tgz} id: file:projects/text-editor-resources.tgz name: '@rush-temp/text-editor-resources' version: 0.0.0 @@ -32604,6 +32607,7 @@ packages: svelte-eslint-parser: 0.33.1(svelte@4.2.19) svelte-loader: 3.2.0(svelte@4.2.19) svelte-preprocess: 5.1.3(postcss-load-config@4.0.2)(postcss@8.4.35)(sass@1.71.1)(svelte@4.2.19)(typescript@5.3.3) + tippy.js: 6.3.7 ts-jest: 29.1.2(esbuild@0.20.1)(jest@29.7.0)(typescript@5.3.3) typescript: 5.3.3 y-indexeddb: 9.0.12(yjs@13.6.19) diff --git a/dev/prod/webpack.config.js b/dev/prod/webpack.config.js index c7314cf6a5..4216def643 100644 --- a/dev/prod/webpack.config.js +++ b/dev/prod/webpack.config.js @@ -331,7 +331,16 @@ module.exports = [ hot: true, client: { logging: 'info', - overlay: true, + overlay: { + errors: true, + warnings: false, + runtimeErrors: (error) => { + if (error.message.includes("ResizeObserver")) { + return false; + } + return true; + }, + }, progress: false }, proxy: diff --git a/models/text-editor/src/index.ts b/models/text-editor/src/index.ts index cbf19db02e..5b4675878a 100644 --- a/models/text-editor/src/index.ts +++ b/models/text-editor/src/index.ts @@ -361,4 +361,16 @@ export function createModel (builder: Builder): void { category: 110, index: 5 }) + + builder.createDoc(textEditor.class.TextEditorAction, core.space.Model, { + action: textEditor.function.CreateInlineComment, + icon: textEditor.icon.Comment, + visibilityTester: textEditor.function.ShouldShowCreateInlineCommentAction, + isActive: { + name: 'inlineComment' + }, + label: textEditor.string.Comment, + category: 110, + index: 10 + }) } diff --git a/models/text-editor/src/plugin.ts b/models/text-editor/src/plugin.ts index ad954bb19f..22190c7b71 100644 --- a/models/text-editor/src/plugin.ts +++ b/models/text-editor/src/plugin.ts @@ -34,6 +34,9 @@ export default mergeIds(textEditorId, textEditor, { IsEditableTableActive: '' as Resource, IsEditableNote: '' as Resource, IsEditable: '' as Resource, - IsHeadingVisible: '' as Resource + IsHeadingVisible: '' as Resource, + + CreateInlineComment: '' as Resource, + ShouldShowCreateInlineCommentAction: '' as Resource } }) diff --git a/packages/panel/src/components/Panel.svelte b/packages/panel/src/components/Panel.svelte index 3a4dfee969..9cbe0a16cf 100644 --- a/packages/panel/src/components/Panel.svelte +++ b/packages/panel/src/components/Panel.svelte @@ -46,6 +46,7 @@ export let allowClose: boolean = true export let embedded: boolean = false export let useMaxWidth: boolean | undefined = undefined + export let sideContentSpace: number = 0 export let isFullSize: boolean = false export let contentClasses: string | undefined = undefined export let content: HTMLElement | undefined | null = undefined @@ -246,6 +247,8 @@ bind:this={content} class={contentClasses ?? 'popupPanel-body__main-content py-8 clear-mins'} class:max={useMaxWidth} + class:side-content-space={sideContentSpace > 0} + style:--side-content-space={`${sideContentSpace}px`} > {#if !withoutActivity} @@ -275,6 +278,8 @@
0} + style:--side-content-space={`${sideContentSpace}px`} use:resizeObserver={(element) => { activityRef?.onContainerResized?.(element) }} diff --git a/packages/theme/styles/panel.scss b/packages/theme/styles/panel.scss index 0702583cd8..830eb56725 100644 --- a/packages/theme/styles/panel.scss +++ b/packages/theme/styles/panel.scss @@ -397,6 +397,12 @@ width: calc(100% - 7.5rem); max-width: 54rem; + &.side-content-space { + --side-content-space: 21rem; + max-width: calc(54rem + var(--side-content-space)); + padding-right: var(--side-content-space); + } + &.max { max-width: 100%; } diff --git a/packages/theme/styles/prose.scss b/packages/theme/styles/prose.scss index a358f51a0d..07723fdc7a 100644 --- a/packages/theme/styles/prose.scss +++ b/packages/theme/styles/prose.scss @@ -477,6 +477,19 @@ pre.proseCodeBlock>pre.proseCode { } } +.proseInlineCommentHighlight { + background: rgba(255, 203, 0, .12); + border-bottom: 2px solid rgba(255, 203, 0, .35); + padding-bottom: 2px; + transition: background 0.2s ease, border 0.2s ease; + + &.active { + transition-delay: 150ms; + background: rgba(255, 203, 0, .24); + border-bottom: 2px solid rgb(255, 203, 0); + } +} + .theme-dark { @import './github-dark.scss'; } diff --git a/packages/ui/src/components/TooltipInstance.svelte b/packages/ui/src/components/TooltipInstance.svelte index ce5093d8d4..69409d69db 100644 --- a/packages/ui/src/components/TooltipInstance.svelte +++ b/packages/ui/src/components/TooltipInstance.svelte @@ -97,12 +97,19 @@ if (clWidth === undefined) { clWidth = tooltipHTML.clientWidth } + + let isElementInvalidTarget = false + if ($tooltip.element) { rect = $tooltip.element.getBoundingClientRect() rectAnchor = $tooltip.anchor ? $tooltip.anchor.getBoundingClientRect() : $tooltip.element.getBoundingClientRect() + if (rect.x === 0 && rect.y === 0 && rect.width === 0 && rect.height === 0) { + isElementInvalidTarget = true + } + if ($tooltip.component) { clearStyles() if (rect.bottom + tooltipHTMLToCheck.clientHeight + 28 < docHeight) { @@ -175,8 +182,13 @@ options.transform = 'translate(-50%, -50%)' options.classList = 'no-arrow' } - options.visibility = 'visible' - shown = true + if (isElementInvalidTarget) { + options.visibility = 'hidden' + shown = false + } else { + options.visibility = 'visible' + shown = true + } } else if (tooltipHTML) { shown = false options.visibility = 'hidden' @@ -368,6 +380,7 @@ style:height={options.height} style:max-width={options.maxWidth} style:transform={options.transform} + style:visibility={options.visibility} style:z-index={($modals.findIndex((t) => t.type === 'tooltip') ?? 1) + 10000} > diff --git a/plugins/activity-resources/src/components/ActivityMessageActions.svelte b/plugins/activity-resources/src/components/ActivityMessageActions.svelte index 804f944751..b737971cb6 100644 --- a/plugins/activity-resources/src/components/ActivityMessageActions.svelte +++ b/plugins/activity-resources/src/components/ActivityMessageActions.svelte @@ -34,9 +34,15 @@ const client = getClient() + let providedMenuActions: Action[] = [] + let providedInlineActions: Action[] = [] + let inlineActions: ViewAction[] = [] let isActionMenuOpened = false + $: providedMenuActions = actions.filter((a) => !a.inline) + $: providedInlineActions = actions.filter((a) => a.inline) + $: void updateInlineActions(message, excludedActions) savedMessagesStore.subscribe(() => { @@ -58,7 +64,7 @@ Menu, { object: message, - actions, + actions: providedMenuActions, baseMenuClass: activity.class.ActivityMessage, excludedActions: inlineActions.map(({ _id }) => _id).concat(excludedActions) }, @@ -94,6 +100,17 @@ {#if message}
+ {#each providedInlineActions as inline} + {#if inline.icon} + inline.action({}, ev)} + /> + {/if} + {/each} + {#each inlineActions as inline} {#if inline.icon} void) | undefined = undefined export let onReply: ((message: ActivityMessage) => void) | undefined = undefined + export let embeddedActions: boolean = false export let socialIcon: Asset | undefined = undefined const client = getClient() - let menuActionIds: string[] = [] + let menuActions: ViewAction[] = [] let element: HTMLDivElement | undefined = undefined let isActionsOpened = false @@ -83,7 +84,7 @@ $: withActions && getActions(client, message, activity.class.ActivityMessage).then((res) => { - menuActionIds = res.map(({ _id }) => _id) + menuActions = res }) function scrollToMessage (): void { @@ -108,7 +109,7 @@ $: key = parentMessage != null ? `${message._id}_${parentMessage._id}` : message._id $: isHidden = !!viewlet?.onlyWithParent && parentMessage === undefined - $: withActionMenu = withActions && !embedded && (actions.length > 0 || menuActionIds.length > 0) + $: withActionMenu = withActions && !embedded && (actions.findIndex((a) => !a.inline) >= 0 || menuActions.length > 0) $: readonly = readonly || $restrictionStore.disableComments @@ -264,7 +265,13 @@
{#if withActions && !readonly} -
+
+ + +{#if value} + + + {#if !isEditing} + {#if withShowMore} + +
+ +
+
+ {:else} +
+ +
+ {/if} + {:else} + { + isEditing = false + }} + on:message={handleSubmitEvent} + /> + {/if} +
+
+{/if} diff --git a/plugins/chunter-resources/src/components/inline-comment/InlineCommentThread.svelte b/plugins/chunter-resources/src/components/inline-comment/InlineCommentThread.svelte new file mode 100644 index 0000000000..504fa47d87 --- /dev/null +++ b/plugins/chunter-resources/src/components/inline-comment/InlineCommentThread.svelte @@ -0,0 +1,114 @@ + + + +
+ + {#each thread.messages as message, index (message._id)} + + {/each} + +
+ +
+
+ + diff --git a/plugins/chunter-resources/src/index.ts b/plugins/chunter-resources/src/index.ts index 710e86521c..f298a9a76c 100644 --- a/plugins/chunter-resources/src/index.ts +++ b/plugins/chunter-resources/src/index.ts @@ -55,6 +55,7 @@ import ChatWidgetTab from './components/ChatWidgetTab.svelte' import WorkbenchTabExtension from './components/WorkbenchTabExtension.svelte' import DirectMessageButton from './components/DirectMessageButton.svelte' import EmployeePresenter from './components/ChunterEmployeePresenter.svelte' +import InlineCommentThread from './components/inline-comment/InlineCommentThread.svelte' import { chunterSpaceLinkFragmentProvider, @@ -186,7 +187,8 @@ export default async (): Promise => ({ ChatWidgetTab, WorkbenchTabExtension, DirectMessageButton, - EmployeePresenter + EmployeePresenter, + InlineCommentThread }, activity: { ChannelCreatedMessage, diff --git a/plugins/chunter-resources/src/plugin.ts b/plugins/chunter-resources/src/plugin.ts index b7e8e3e559..5e70cb1c77 100644 --- a/plugins/chunter-resources/src/plugin.ts +++ b/plugins/chunter-resources/src/plugin.ts @@ -108,6 +108,7 @@ export default mergeIds(chunterId, chunter, { ArchiveActivityConfirmationMessage: '' as IntlString, JoinChannelHeader: '' as IntlString, JoinChannelText: '' as IntlString, - LatestMessages: '' as IntlString + LatestMessages: '' as IntlString, + ResolveThread: '' as IntlString } }) diff --git a/plugins/chunter/src/index.ts b/plugins/chunter/src/index.ts index 8adaaf5321..a1d3e78c9c 100644 --- a/plugins/chunter/src/index.ts +++ b/plugins/chunter/src/index.ts @@ -149,7 +149,8 @@ export default plugin(chunterId, { ThreadMessagePresenter: '' as AnyComponent, ChatMessagePreview: '' as AnyComponent, ThreadMessagePreview: '' as AnyComponent, - DirectIcon: '' as AnyComponent + DirectIcon: '' as AnyComponent, + InlineCommentThread: '' as AnyComponent }, activity: { MembersChangedMessage: '' as AnyComponent diff --git a/plugins/controlled-documents-resources/src/components/document/EditDocContent.svelte b/plugins/controlled-documents-resources/src/components/document/EditDocContent.svelte index a54be64d67..f25e5bf478 100644 --- a/plugins/controlled-documents-resources/src/components/document/EditDocContent.svelte +++ b/plugins/controlled-documents-resources/src/components/document/EditDocContent.svelte @@ -271,6 +271,7 @@ editorAttributes={{ style: 'padding: 0 2em; margin: 0 -2em;' }} overflow="none" canShowPopups={!$areDocumentCommentPopupsOpened} + enableInlineComments={false} onExtensions={handleExtensions} kitOptions={{ note: { diff --git a/plugins/document-resources/src/components/DocumentEditor.svelte b/plugins/document-resources/src/components/DocumentEditor.svelte index 8c5766c6e7..6e6d29f923 100644 --- a/plugins/document-resources/src/components/DocumentEditor.svelte +++ b/plugins/document-resources/src/components/DocumentEditor.svelte @@ -32,6 +32,7 @@ export let focusIndex = -1 export let overflow: 'auto' | 'none' = 'none' export let editorAttributes: Record = {} + export let requestSideSpace: ((width: number) => void) | undefined = undefined const client = getClient() @@ -74,6 +75,7 @@ {boundary} {overflow} {editorAttributes} + {requestSideSpace} onExtensions={handleExtensions} on:update on:open-document diff --git a/plugins/document-resources/src/components/EditDoc.svelte b/plugins/document-resources/src/components/EditDoc.svelte index 056b83d3ca..166cd8fe59 100644 --- a/plugins/document-resources/src/components/EditDoc.svelte +++ b/plugins/document-resources/src/components/EditDoc.svelte @@ -197,6 +197,12 @@ localStorage.setItem('document.useMaxWidth', useMaxWidth.toString()) } + let sideContentSpace = 0 + + function updateSizeContentSpace (width: number): void { + sideContentSpace = width + } + onMount(() => { dispatch('open', { ignoreKeys: ['comments', 'name'] }) }) @@ -259,6 +265,7 @@ isCustomAttr={false} isSub={false} bind:useMaxWidth + {sideContentSpace} printHeader={false} {embedded} adaptive={'default'} @@ -369,6 +376,7 @@ boundary={content} overflow={'none'} editorAttributes={{ style: 'padding: 0 2em 2em; margin: 0 -2em; min-height: 30vh' }} + requestSideSpace={updateSizeContentSpace} attachFile={async (file) => { return await createEmbedding(file) }} diff --git a/plugins/text-editor-assets/assets/icons.svg b/plugins/text-editor-assets/assets/icons.svg index a5c3211553..239fe4387f 100644 --- a/plugins/text-editor-assets/assets/icons.svg +++ b/plugins/text-editor-assets/assets/icons.svg @@ -197,4 +197,8 @@ + + + + \ No newline at end of file diff --git a/plugins/text-editor-assets/lang/en.json b/plugins/text-editor-assets/lang/en.json index 0c5390b770..e9e19ce94f 100644 --- a/plugins/text-editor-assets/lang/en.json +++ b/plugins/text-editor-assets/lang/en.json @@ -59,6 +59,9 @@ "SeparatorLine": "Separator line", "TodoList": "Action item", "DrawingBoard": "Drawing board", - "MermaidDiargram": "Diagram" + "MermaidDiargram": "Diagram", + "Comment": "Comment", + "AddComment": "Add a comment", + "AddCommentPlaceholder": "Add a comment..." } } \ No newline at end of file diff --git a/plugins/text-editor-assets/lang/ru.json b/plugins/text-editor-assets/lang/ru.json index 5d42871c13..bf64f35c1d 100644 --- a/plugins/text-editor-assets/lang/ru.json +++ b/plugins/text-editor-assets/lang/ru.json @@ -59,6 +59,9 @@ "SeparatorLine": "Разделительная линия", "TodoList": "Действие", "DrawingBoard": "Доска", - "MermaidDiargram": "Диаграмма" + "MermaidDiargram": "Диаграмма", + "Comment": "Комментарий", + "AddComment": "Добавить комментарий", + "AddCommentPlaceholder": "Добавьте комментарий..." } } \ No newline at end of file diff --git a/plugins/text-editor-assets/src/index.ts b/plugins/text-editor-assets/src/index.ts index b34b337333..0b5a98d852 100644 --- a/plugins/text-editor-assets/src/index.ts +++ b/plugins/text-editor-assets/src/index.ts @@ -39,5 +39,6 @@ loadMetadata(textEditor.icon, { Expand: `${icons}#expand`, ScaleOut: `${icons}#scaleOut`, Download: `${icons}#download`, - Note: `${icons}#note` + Note: `${icons}#note`, + Comment: `${icons}#comment` }) diff --git a/plugins/text-editor-resources/package.json b/plugins/text-editor-resources/package.json index df3012c3e2..f4c7251291 100644 --- a/plugins/text-editor-resources/package.json +++ b/plugins/text-editor-resources/package.json @@ -85,6 +85,8 @@ "y-indexeddb": "^9.0.12", "lowlight": "^3.1.0", "mermaid": "~11.4.1", - "@hcengineering/theme": "^0.6.5" + "@hcengineering/theme": "^0.6.5", + "tippy.js": "~6.3.7", + "@hcengineering/chunter": "^0.6.20" } } diff --git a/plugins/text-editor-resources/src/components/CollaborativeTextEditor.svelte b/plugins/text-editor-resources/src/components/CollaborativeTextEditor.svelte index b8650476f0..6cfc49bd41 100644 --- a/plugins/text-editor-resources/src/components/CollaborativeTextEditor.svelte +++ b/plugins/text-editor-resources/src/components/CollaborativeTextEditor.svelte @@ -84,6 +84,7 @@ import { type FileAttachFunction } from './extension/types' import { completionConfig, inlineCommandsConfig } from './extensions' import { MermaidExtension, mermaidOptions } from './extension/mermaid' + import { InlineCommentExtension } from './extension/inlineComment' export let object: Doc export let attribute: KeyedAttribute @@ -112,6 +113,8 @@ export let withSideMenu = true export let withInlineCommands = true export let kitOptions: Partial = {} + export let requestSideSpace: ((width: number) => void) | undefined = undefined + export let enableInlineComments: boolean = true const client = getClient() const dispatch = createEventDispatcher() @@ -148,6 +151,7 @@ let element: HTMLElement let textToolbarElement: HTMLElement let imageToolbarElement: HTMLElement + let editorPopupContainer: HTMLElement let placeHolderStr: string = '' @@ -430,6 +434,12 @@ onMount(async () => { await ph + if (enableInlineComments) { + optionalExtensions.push( + InlineCommentExtension.configure({ ydoc, boundary, popupContainer: editorPopupContainer, requestSideSpace }) + ) + } + editor = new Editor({ enableContentCheck: true, element, @@ -534,6 +544,7 @@ style="display: none" on:change={fileSelected} /> +
= {} + export let requestSideSpace: ((width: number) => void) | undefined = undefined + export let enableInlineComments: boolean = true let element: HTMLElement @@ -99,6 +101,8 @@ {canShowPopups} {editorAttributes} {kitOptions} + {requestSideSpace} + {enableInlineComments} on:editor on:update on:open-document diff --git a/plugins/text-editor-resources/src/components/ReferenceInput.svelte b/plugins/text-editor-resources/src/components/ReferenceInput.svelte index 2851084e47..3514d4b329 100644 --- a/plugins/text-editor-resources/src/components/ReferenceInput.svelte +++ b/plugins/text-editor-resources/src/components/ReferenceInput.svelte @@ -23,7 +23,8 @@ handler, registerFocus, deviceOptionsStore as deviceInfo, - checkAdaptiveMatching + checkAdaptiveMatching, + IconClose } from '@hcengineering/ui' import { createEventDispatcher } from 'svelte' import { FocusPosition } from '@tiptap/core' @@ -36,25 +37,31 @@ import { completionConfig } from './extensions' import { EmojiExtension } from './extension/emoji' import { IsEmptyContentExtension } from './extension/isEmptyContent' + import view from '@hcengineering/view' import Send from './icons/Send.svelte' export let content: Markup = EmptyMarkup export let showHeader = false export let showActions = true export let showSend = true + export let showCancel = false export let iconSend: Asset | AnySvelteComponent | undefined = undefined export let labelSend: IntlString | undefined = undefined + export let labelCancel: IntlString | undefined = undefined export let kindSend: ButtonKind = 'ghost' + export let kindCancel: ButtonKind = 'ghost' export let haveAttachment = false export let placeholder: IntlString | undefined = undefined export let extraActions: RefAction[] = [] export let loading: boolean = false export let focusable: boolean = false + export let noborder: boolean = false export let boundary: HTMLElement | undefined = undefined export let autofocus: FocusPosition = false export let canEmbedFiles = true export let canEmbedImages = true export let onPaste: ((view: EditorView, event: ClipboardEvent) => boolean) | undefined = undefined + export let onCancel: (() => void) | undefined = undefined const dispatch = createEventDispatcher() const buttonSize = 'medium' @@ -142,7 +149,7 @@ }) -
+
{#if showHeader && $$slots.header}
@@ -160,7 +167,7 @@ if (canSubmit) { dispatch('message', ev.detail) content = EmptyMarkup - editor?.clear() + editor?.clear?.() } }} on:blur={() => { @@ -207,7 +214,34 @@ {/if}
- {#if showSend} + {#if showCancel && showSend} +
+
+ {/if} + {#if showSend && !showCancel}