From f64901cfd52034c12400913b0082ab3ea1e794bf Mon Sep 17 00:00:00 2001 From: Alexander Onnikov Date: Wed, 25 Oct 2023 23:13:43 +0700 Subject: [PATCH] UBER-1118 Wiki images improvements (#3887) Signed-off-by: Alexander Onnikov --- .../src/components/PDFViewer.svelte | 11 ++- .../src/components/CollaboratorEditor.svelte | 7 +- .../src/components/ImageStyleToolbar.svelte | 45 ++++++----- .../src/components/TextEditor.svelte | 7 +- .../src/components/extension/imageExt.ts | 80 ++++++++++++------- .../extension/inlineStyleToolbar.ts | 53 ++++-------- 6 files changed, 107 insertions(+), 96 deletions(-) diff --git a/packages/presentation/src/components/PDFViewer.svelte b/packages/presentation/src/components/PDFViewer.svelte index c86aa1653d..17bb0ae1f6 100644 --- a/packages/presentation/src/components/PDFViewer.svelte +++ b/packages/presentation/src/components/PDFViewer.svelte @@ -15,7 +15,7 @@ diff --git a/packages/text-editor/src/components/CollaboratorEditor.svelte b/packages/text-editor/src/components/CollaboratorEditor.svelte index 2cdff2c3c4..0a6b580956 100644 --- a/packages/text-editor/src/components/CollaboratorEditor.svelte +++ b/packages/text-editor/src/components/CollaboratorEditor.svelte @@ -240,7 +240,7 @@ $: updateEditor(editor, field, comparedVersion) $: if (editor) dispatch('editor', editor) - const tippyOptions = { + $: tippyOptions = { zIndex: 100000, popperOptions: { modifiers: [ @@ -287,7 +287,10 @@ InlinePopupExtension.configure({ pluginKey: 'show-image-actions-popup', element: imageToolbarElement, - tippyOptions, + tippyOptions: { + ...tippyOptions, + appendTo: () => boundary ?? element + }, shouldShow: () => { if (!visible && !readonly) { return false diff --git a/packages/text-editor/src/components/ImageStyleToolbar.svelte b/packages/text-editor/src/components/ImageStyleToolbar.svelte index 44e071e84d..9ac8d3610c 100644 --- a/packages/text-editor/src/components/ImageStyleToolbar.svelte +++ b/packages/text-editor/src/components/ImageStyleToolbar.svelte @@ -39,16 +39,22 @@ function openImage () { const attributes = textEditor.getAttributes('image') - const fileId = attributes['file-id'] + const fileId = attributes['file-id'] ?? attributes.src const fileName = attributes.alt ?? '' - showPopup(PDFViewer, { file: fileId, name: fileName, contentType: 'image/*', showIcon: false }, 'centered', () => { - dispatch('focus') - }) + showPopup( + PDFViewer, + { file: fileId, name: fileName, contentType: 'image/*', fullSize: true, showIcon: false }, + 'centered', + () => { + dispatch('focus') + } + ) } function openOriginalImage () { const attributes = textEditor.getAttributes('image') - const url = getFileUrl(attributes['file-id'], 'full') + const fileId = attributes['file-id'] ?? attributes.src + const url = getFileUrl(fileId, 'full') window.open(url, '_blank') } @@ -64,21 +70,7 @@ } }) - const actions = [ - { - id: '#imageOpen', - icon: IconScaleOut, - label: plugin.string.ViewImage, - action: openImage - }, - { - id: '#imageOriginal', - icon: IconExpand, - label: plugin.string.ViewOriginal, - action: openOriginalImage - }, - ...widthActions - ] + const actions = [...widthActions] showPopup( SelectPopup, @@ -123,6 +115,19 @@ on:click={getImageAlignmentToggler('right')} />
+ + +
boundary ?? element + }, shouldShow: () => editor?.isActive('image') }) ], diff --git a/packages/text-editor/src/components/extension/imageExt.ts b/packages/text-editor/src/components/extension/imageExt.ts index aa0f129120..08e0f49a40 100644 --- a/packages/text-editor/src/components/extension/imageExt.ts +++ b/packages/text-editor/src/components/extension/imageExt.ts @@ -1,8 +1,22 @@ +// +// Copyright © 2023 Hardcore Engineering Inc. +// +// Licensed under the Eclipse Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. You may +// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// import { getMetadata } from '@hcengineering/platform' -import presentation, { getFileUrl } from '@hcengineering/presentation' -import { IconSize, getIconSize2x } from '@hcengineering/ui' -import { Node, createNodeFromContent, mergeAttributes, nodeInputRule } from '@tiptap/core' -import { Fragment, Node as ProseMirrorNode } from '@tiptap/pm/model' +import presentation, { PDFViewer, getFileUrl } from '@hcengineering/presentation' +import { IconSize, getIconSize2x, showPopup } from '@hcengineering/ui' +import { Node, mergeAttributes, nodeInputRule } from '@tiptap/core' +import { Node as ProseMirrorNode } from '@tiptap/pm/model' import { Plugin, PluginKey } from '@tiptap/pm/state' import { EditorView } from '@tiptap/pm/view' import { getDataAttribute } from '../../utils' @@ -12,6 +26,11 @@ import { getDataAttribute } from '../../utils' */ export type FileAttachFunction = (file: File) => Promise<{ file: string, type: string } | undefined> +/** + * @public + */ +export type ImageAlignment = 'center' | 'left' | 'right' + /** * @public */ @@ -25,7 +44,7 @@ export interface ImageOptions { } export interface ImageAlignmentOptions { - align?: 'center' | 'left' | 'right' + align?: ImageAlignment } export interface ImageSizeOptions { @@ -129,7 +148,7 @@ export const ImageExtension = Node.create({ const divAttributes = { class: 'text-editor-image-container', 'data-type': this.name, - 'data-align': node.attrs.align ?? 'center' + 'data-align': node.attrs.align } const imgAttributes = mergeAttributes( @@ -244,20 +263,10 @@ export const ImageExtension = Node.create({ const ctype = dataTransfer.getData('application/contentType') const type = getType(ctype ?? 'other') - let content: ProseMirrorNode | Fragment | undefined if (type === 'image') { - content = createNodeFromContent( - ``, - view.state.schema, - { - parseOptions: { - preserveWhitespace: 'full' - } - } - ) - } - if (content !== undefined) { - view.dispatch(view.state.tr.insert(pos?.pos ?? 0, content)) + const node = view.state.schema.nodes.image.create({ 'file-id': _file }) + const transaction = view.state.tr.insert(pos?.pos ?? 0, node) + view.dispatch(transaction) result = true } } @@ -275,16 +284,9 @@ export const ImageExtension = Node.create({ void opt.attachFile(file).then((id) => { if (id !== undefined) { if (id.type.includes('image')) { - const content = createNodeFromContent( - ``, - view.state.schema, - { - parseOptions: { - preserveWhitespace: 'full' - } - } - ) - view.dispatch(view.state.tr.insert(pos?.pos ?? 0, content)) + const node = view.state.schema.nodes.image.create({ 'file-id': id.file }) + const transaction = view.state.tr.insert(pos?.pos ?? 0, node) + view.dispatch(transaction) } } }) @@ -315,6 +317,26 @@ export const ImageExtension = Node.create({ if (dataTransfer !== null) { return handleDrop(view, view.posAtCoords({ left: event.x, top: event.y }), dataTransfer) } + }, + handleDoubleClickOn (view, pos, node, nodePos, event) { + if (node.type.name !== 'image') { + return + } + + const fileId = node.attrs['file-id'] ?? node.attrs.src + const fileName = node.attrs.alt ?? '' + + showPopup( + PDFViewer, + { + file: fileId, + name: fileName, + contentType: 'image/*', + fullSize: true, + showIcon: false + }, + 'centered' + ) } } }) diff --git a/packages/text-editor/src/components/extension/inlineStyleToolbar.ts b/packages/text-editor/src/components/extension/inlineStyleToolbar.ts index fcd5c99c6d..9ede31c7a3 100644 --- a/packages/text-editor/src/components/extension/inlineStyleToolbar.ts +++ b/packages/text-editor/src/components/extension/inlineStyleToolbar.ts @@ -1,4 +1,4 @@ -import { Editor, Extension, isTextSelection } from '@tiptap/core' +import { Extension, isTextSelection } from '@tiptap/core' import { BubbleMenuOptions } from '@tiptap/extension-bubble-menu' import { Plugin, PluginKey } from 'prosemirror-state' import { InlinePopupExtension } from './inlinePopup' @@ -9,39 +9,23 @@ export type InlineStyleToolbarOptions = BubbleMenuOptions & { } export interface InlineStyleToolbarStorage { - isShown: boolean -} - -const handleFocus = (editor: Editor, options: InlineStyleToolbarOptions, storage: InlineStyleToolbarStorage): void => { - if (!options.isSupported()) { - return - } - - if (editor.isEmpty) { - return - } - - if (options.isSelectionOnly?.() === true && editor.view.state.selection.empty) { - return - } - - storage.isShown = true + canShowWithoutSelection: boolean } export const InlineStyleToolbarExtension = Extension.create({ pluginKey: new PluginKey('inline-style-toolbar'), addProseMirrorPlugins () { - const options = this.options const storage = this.storage - const editor = this.editor const plugins = [ ...(this.parent?.() ?? []), new Plugin({ key: new PluginKey('inline-style-toolbar-click-plugin'), props: { - handleClick () { - handleFocus(editor, options, storage) + handleClickOn (view, pos, node, nodePos, event, direct) { + if (direct) { + storage.canShowWithoutSelection = node.type.name !== 'image' + } } } }) @@ -51,7 +35,7 @@ export const InlineStyleToolbarExtension = Extension.create