From 8c0838d8b3e44ecfa38e5686cc676378d84090f6 Mon Sep 17 00:00:00 2001 From: Andrey Sobolev Date: Fri, 23 Jun 2023 15:39:35 +0700 Subject: [PATCH] UBER-520: Fix images drag & drop UBER-520: Fix images drag & drop * Fix images paste * Fix issues inside github * Enable link presenters for comments. * UBER-451: Disable paste of non images into description content Signed-off-by: Andrey Sobolev --- .../text-editor/src/components/imageExt.ts | 154 +++++++++++------- .../src/components/AttachmentPresenter.svelte | 4 + .../src/components/CommentPresenter.svelte | 25 +++ .../activity/TxCommentCreate.svelte | 25 +++ .../linkPresenters/GithubPresenter.svelte | 9 +- 5 files changed, 152 insertions(+), 65 deletions(-) diff --git a/packages/text-editor/src/components/imageExt.ts b/packages/text-editor/src/components/imageExt.ts index ba22a2080c..d65814fe2d 100644 --- a/packages/text-editor/src/components/imageExt.ts +++ b/packages/text-editor/src/components/imageExt.ts @@ -1,11 +1,12 @@ -import { getEmbeddedLabel } from '@hcengineering/platform' -import { getFileUrl } from '@hcengineering/presentation' +import { getEmbeddedLabel, getMetadata } from '@hcengineering/platform' +import presentation, { getFileUrl } from '@hcengineering/presentation' import { Action, IconSize, Menu, getEventPositionElement, getIconSize2x, showPopup } from '@hcengineering/ui' import { Node, createNodeFromContent, mergeAttributes, nodeInputRule } from '@tiptap/core' import { Plugin, PluginKey } from 'prosemirror-state' import plugin from '../plugin' -import { Node as ProseMirrorNode } from '@tiptap/pm/model' +import { Fragment, Node as ProseMirrorNode } from '@tiptap/pm/model' +import { EditorView } from 'prosemirror-view' /** * @public @@ -40,6 +41,14 @@ declare module '@tiptap/core' { */ export const inputRegex = /(?:^|\s)(!\[(.+|:?)]\((\S+)(?:(?:\s+)["'](\S+)["'])?\))$/ +function getType (type: string): 'image' | 'other' { + if (type.startsWith('image/')) { + return 'image' + } + + return 'other' +} + /** * @public */ @@ -160,74 +169,97 @@ export const ImageRef = Node.create({ }, addProseMirrorPlugins () { const opt = this.options - return [ - new Plugin({ - key: new PluginKey('handle-image-paste'), - props: { - handleDrop (view, event, slice) { - const uris = (event.dataTransfer?.getData('text/uri-list') ?? '') - .split('\r\n') - .filter((it) => !it.startsWith('#')) - let result = false - const pos = view.posAtCoords({ left: event.x, top: event.y }) - for (const uri of uris) { - if (uri !== '') { - const url = new URL(uri) - if (url.hostname !== location.hostname) { - return - } + function handleDrop ( + view: EditorView, + pos: { pos: number, inside: number } | null, + dataTransfer: DataTransfer + ): any { + const uris = (dataTransfer.getData('text/uri-list') ?? '').split('\r\n').filter((it) => !it.startsWith('#')) + let result = false + for (const uri of uris) { + if (uri !== '') { + const url = new URL(uri) + const uploadUrl = getMetadata(presentation.metadata.UploadURL) + if (uploadUrl === undefined || !url.href.includes(uploadUrl)) { + continue + } + + const _file = (url.searchParams.get('file') ?? '').split('/').join('') - const _file = (url.searchParams.get('file') ?? '').split('/').join('') + if (_file.trim().length === 0) { + continue + } - if (_file.trim().length === 0) { - return + 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' } - const content = createNodeFromContent( - ``, - view.state.schema, - { - parseOptions: { - preserveWhitespace: 'full' - } - } - ) - event.preventDefault() - event.stopPropagation() - view.dispatch(view.state.tr.insert(pos?.pos ?? 0, content)) - result = true } - } - if (result) { - return result - } + ) + } + if (content !== undefined) { + view.dispatch(view.state.tr.insert(pos?.pos ?? 0, content)) + result = true + } + } + } + if (result) { + return result + } - const files = event.dataTransfer?.files - if (files !== undefined && opt.attachFile !== undefined) { - event.preventDefault() - event.stopPropagation() - for (let i = 0; i < files.length; i++) { - const file = files.item(i) - if (file != null) { - 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 files = dataTransfer?.files + if (files !== undefined && opt.attachFile !== undefined) { + for (let i = 0; i < files.length; i++) { + const file = files.item(i) + if (file != null) { + 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)) } } + }) + } + } + } + return true + } + return [ + new Plugin({ + key: new PluginKey('handle-image-paste'), + props: { + handlePaste (view, event, slice) { + event.preventDefault() + event.stopPropagation() + const dataTransfer = event.clipboardData + if (dataTransfer !== null) { + return handleDrop(view, { pos: view.state.selection.$from.pos, inside: 0 }, dataTransfer) + } + }, + handleDrop (view, event, slice) { + event.preventDefault() + event.stopPropagation() + const dataTransfer = event.dataTransfer + if (dataTransfer !== null) { + return handleDrop(view, view.posAtCoords({ left: event.x, top: event.y }), dataTransfer) } - return result }, handleClick: (view, pos, event) => { if (event.button !== 0) { diff --git a/plugins/attachment-resources/src/components/AttachmentPresenter.svelte b/plugins/attachment-resources/src/components/AttachmentPresenter.svelte index 083fce194f..f5f7203a45 100644 --- a/plugins/attachment-resources/src/components/AttachmentPresenter.svelte +++ b/plugins/attachment-resources/src/components/AttachmentPresenter.svelte @@ -82,6 +82,9 @@ ${getFileUrl(value.file, getIconSize2x('large'))} 2x );` : '' + function dragStart (event: DragEvent): void { + event.dataTransfer?.setData('application/contentType', value.type) + }
@@ -92,6 +95,7 @@ download={value.name} on:click={clickHandler} on:mousedown={middleClickHandler} + on:dragstart={dragStart} > {#if showPreview}
): HTMLLinkElement[] { + const res: HTMLLinkElement[] = [] + nodes.forEach((p) => { + if (p.nodeType !== Node.TEXT_NODE) { + if (p.nodeName === 'A') { + res.push(p as HTMLLinkElement) + } + res.push(...parseLinks(p.childNodes)) + } + }) + return res + } {#if inline} @@ -68,6 +90,9 @@ + {#each links as link} + + {/each}
{/await} diff --git a/plugins/chunter-resources/src/components/activity/TxCommentCreate.svelte b/plugins/chunter-resources/src/components/activity/TxCommentCreate.svelte index 2acab60c6a..d42b7555a6 100644 --- a/plugins/chunter-resources/src/components/activity/TxCommentCreate.svelte +++ b/plugins/chunter-resources/src/components/activity/TxCommentCreate.svelte @@ -22,6 +22,7 @@ import { updateBacklinks } from '../../backlinks' import chunter from '../../plugin' import { AttachmentRefInput } from '@hcengineering/attachment-resources' + import { LinkPresenter } from '@hcengineering/view-resources' export let tx: TxCreateDoc export let value: Comment @@ -57,6 +58,27 @@ } let refInput: AttachmentRefInput let loading = false + + $: links = getLinks(value.message) + + function getLinks (content: string): HTMLLinkElement[] { + const parser = new DOMParser() + const parent = parser.parseFromString(content, 'text/html').firstChild?.childNodes[1] as HTMLElement + return parseLinks(parent.childNodes) + } + + function parseLinks (nodes: NodeListOf): HTMLLinkElement[] { + const res: HTMLLinkElement[] = [] + nodes.forEach((p) => { + if (p.nodeType !== Node.TEXT_NODE) { + if (p.nodeName === 'A') { + res.push(p as HTMLLinkElement) + } + res.push(...parseLinks(p.childNodes)) + } + }) + return res + }
@@ -83,6 +105,9 @@ {:else} + {#each links as link} + + {/each} {/if}
diff --git a/plugins/view-resources/src/components/linkPresenters/GithubPresenter.svelte b/plugins/view-resources/src/components/linkPresenters/GithubPresenter.svelte index e3db473616..866f6f29c1 100644 --- a/plugins/view-resources/src/components/linkPresenters/GithubPresenter.svelte +++ b/plugins/view-resources/src/components/linkPresenters/GithubPresenter.svelte @@ -37,13 +37,14 @@ } async function getData (href: string): Promise { - const params = href.replace(/(http.:\/\/)?(www.)?github.com\//, '') + let params = href.replace(/(http.:\/\/)?(www.)?github.com\//, '') + params = params.replace('/pull/', '/pulls/') const res = await (await fetch(`https://api.github.com/repos/${params}`)).json() return { number: res.number, body: format(res.body), title: res.title, - assignees: res.assignees.map((p: any) => { + assignees: res.assignees?.map((p: any) => { return { login: p.login, url: p.html_url @@ -73,7 +74,7 @@
{/if}
- {#if data.assignees.length} + {#if data.assignees?.length}
@@ -83,7 +84,7 @@
{/if} - {#if data.labels.length} + {#if data.labels?.length}