From 90a6b13ef03fc11c6ab6d0a62a86dc5699d9b478 Mon Sep 17 00:00:00 2001 From: Max Date: Tue, 17 Sep 2024 14:19:23 +0200 Subject: [PATCH 01/13] enh: add extractLinkParagraphs Signed-off-by: Max --- src/plugins/extractLinkParagraphs.js | 70 ++++++++++++++++ .../plugins/extractLinkParagraphs.spec.js | 84 +++++++++++++++++++ 2 files changed, 154 insertions(+) create mode 100644 src/plugins/extractLinkParagraphs.js create mode 100644 src/tests/plugins/extractLinkParagraphs.spec.js diff --git a/src/plugins/extractLinkParagraphs.js b/src/plugins/extractLinkParagraphs.js new file mode 100644 index 00000000000..34f8f22041e --- /dev/null +++ b/src/plugins/extractLinkParagraphs.js @@ -0,0 +1,70 @@ +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { isLinkToSelfWithHash } from './../helpers/links.js' + +/** + * Get a list of all paragraphs that can be converted into a preview. + * + * @param {Document} doc - the prosemirror doc + * @return {Array} paragraphs with one link only found in the doc + */ +export default function extractLinkParagraphs(doc) { + const counter = new Map() + const paragraphs = [] + + doc.descendants((node, offset) => { + if (node.type.name !== 'paragraph') { + return + } + // ignore paragraphs that cannot be converted + if (!previewPossible(node)) return + paragraphs.push(Object.freeze({ + offset, + })) + }) + + return paragraphs +} + +/** + * Is it possible to convert the currently selected node into a preview? + * @param {object} state current editor state + * @param {object} state.selection current selection + * @return {boolean} + */ +function previewPossible(node) { + if (hasOtherContent(node)) { + return false + } + const href = extractHref(node.firstChild) + if (!href || isLinkToSelfWithHash(href)) { + return false + } + return true +} + +/** + * Does the node contain more content than the first child + * @param {object} node node to inspect + * @return {boolean} + */ +function hasOtherContent(node) { + return node.childCount > 2 + || (node.childCount === 2 && node.lastChild.textContent.trim()) +} + +/** + * Get the link href of the given node + * @param {object} node to inspect + * @return {string} The href of the link mark of the node + */ +function extractHref(node) { + if (!node) { + return undefined + } + const link = node.marks.find(mark => mark.type.name === 'link') + return link?.attrs.href +} diff --git a/src/tests/plugins/extractLinkParagraphs.spec.js b/src/tests/plugins/extractLinkParagraphs.spec.js new file mode 100644 index 00000000000..23a94c3a68b --- /dev/null +++ b/src/tests/plugins/extractLinkParagraphs.spec.js @@ -0,0 +1,84 @@ +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import extractLinkParagraphs from '../../plugins/extractLinkParagraphs.js' +import Link from '../../marks/Link.js' +import { createCustomEditor } from '../helpers.js' + +describe('extractLinkParagraphs', () => { + + it('returns an empty array for an empty doc', () => { + const doc = prepareDoc('') + const paragraphs = extractLinkParagraphs(doc) + expect(paragraphs).toEqual([]) + }) + + it('returns paragraphs with a single link', () => { + const content = '

Link

' + const doc = prepareDoc(content) + const paragraphs = extractLinkParagraphs(doc) + expect(paragraphs).toEqual([ + { offset: 0 } + ]) + }) + + it('returns paragraphs with a single link and whitespace', () => { + const content = '

Link

' + const doc = prepareDoc(content) + const paragraphs = extractLinkParagraphs(doc) + expect(paragraphs).toEqual([ + { offset: 0 } + ]) + }) + + it('returns multiple paragraphs with a single link', () => { + const paragraph = '

Link

' + const content = paragraph + paragraph + const doc = prepareDoc(content) + const paragraphs = extractLinkParagraphs(doc) + expect(paragraphs).toEqual([ + { offset: 0 }, + { offset: 6 } + ]) + }) + + it('ignores an empty paragraph', () => { + const content = '

' + const doc = prepareDoc(content) + const paragraphs = extractLinkParagraphs(doc) + expect(paragraphs).toEqual([]) + }) + + it('ignores paragraphs with text after the link', () => { + const content = '

Link Hello

' + const doc = prepareDoc(content) + const paragraphs = extractLinkParagraphs(doc) + expect(paragraphs).toEqual([]) + }) + + it('ignores paragraphs with text before the link', () => { + const content = '

bla Link

' + const doc = prepareDoc(content) + const paragraphs = extractLinkParagraphs(doc) + expect(paragraphs).toEqual([]) + }) + + it('ignores paragraphs with multiple links', () => { + const link = 'Link' + const content = `

${link} ${link}

` + const doc = prepareDoc(content) + const paragraphs = extractLinkParagraphs(doc) + expect(paragraphs).toEqual([]) + }) + +}) + +function prepareDoc(content) { + const editor = createCustomEditor({ + content, + extensions: [ Link ] + }) + return editor.state.doc +} From 5bb8981617ce77fb9a99e5195e3431f026bcaa4c Mon Sep 17 00:00:00 2001 From: Max Date: Tue, 17 Sep 2024 14:29:32 +0200 Subject: [PATCH 02/13] refactor(test): reuse link string Signed-off-by: Max --- src/tests/plugins/extractLinkParagraphs.spec.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/tests/plugins/extractLinkParagraphs.spec.js b/src/tests/plugins/extractLinkParagraphs.spec.js index 23a94c3a68b..1c51d1eadeb 100644 --- a/src/tests/plugins/extractLinkParagraphs.spec.js +++ b/src/tests/plugins/extractLinkParagraphs.spec.js @@ -8,6 +8,7 @@ import Link from '../../marks/Link.js' import { createCustomEditor } from '../helpers.js' describe('extractLinkParagraphs', () => { + const link = 'Link' it('returns an empty array for an empty doc', () => { const doc = prepareDoc('') @@ -16,7 +17,7 @@ describe('extractLinkParagraphs', () => { }) it('returns paragraphs with a single link', () => { - const content = '

Link

' + const content = `

${link}

` const doc = prepareDoc(content) const paragraphs = extractLinkParagraphs(doc) expect(paragraphs).toEqual([ @@ -25,7 +26,7 @@ describe('extractLinkParagraphs', () => { }) it('returns paragraphs with a single link and whitespace', () => { - const content = '

Link

' + const content = `

${link}

` const doc = prepareDoc(content) const paragraphs = extractLinkParagraphs(doc) expect(paragraphs).toEqual([ @@ -34,7 +35,7 @@ describe('extractLinkParagraphs', () => { }) it('returns multiple paragraphs with a single link', () => { - const paragraph = '

Link

' + const paragraph = `

${link}

` const content = paragraph + paragraph const doc = prepareDoc(content) const paragraphs = extractLinkParagraphs(doc) @@ -52,21 +53,20 @@ describe('extractLinkParagraphs', () => { }) it('ignores paragraphs with text after the link', () => { - const content = '

Link Hello

' + const content = `

${link} Hello

` const doc = prepareDoc(content) const paragraphs = extractLinkParagraphs(doc) expect(paragraphs).toEqual([]) }) it('ignores paragraphs with text before the link', () => { - const content = '

bla Link

' + const content = `

Hello ${link}

` const doc = prepareDoc(content) const paragraphs = extractLinkParagraphs(doc) expect(paragraphs).toEqual([]) }) it('ignores paragraphs with multiple links', () => { - const link = 'Link' const content = `

${link} ${link}

` const doc = prepareDoc(content) const paragraphs = extractLinkParagraphs(doc) From 88716d9d51862ede81427f3696bfe25a5c1e876e Mon Sep 17 00:00:00 2001 From: Max Date: Tue, 17 Sep 2024 15:06:52 +0200 Subject: [PATCH 03/13] enh(preview): include previews in list of link paragraphs Render the preview options as a decoration for previews too. Signed-off-by: Max --- src/plugins/extractLinkParagraphs.js | 25 +++++++------ .../plugins/extractLinkParagraphs.spec.js | 37 ++++++++++++++++--- 2 files changed, 45 insertions(+), 17 deletions(-) diff --git a/src/plugins/extractLinkParagraphs.js b/src/plugins/extractLinkParagraphs.js index 34f8f22041e..fc2cdd3f9b3 100644 --- a/src/plugins/extractLinkParagraphs.js +++ b/src/plugins/extractLinkParagraphs.js @@ -12,31 +12,32 @@ import { isLinkToSelfWithHash } from './../helpers/links.js' * @return {Array} paragraphs with one link only found in the doc */ export default function extractLinkParagraphs(doc) { - const counter = new Map() const paragraphs = [] doc.descendants((node, offset) => { - if (node.type.name !== 'paragraph') { - return + if (previewPossible(node)) { + paragraphs.push(Object.freeze({ + offset, + type: 'text-only', + })) + } else if (node.type.name === 'preview') { + paragraphs.push(Object.freeze({ + offset, + type: 'link-preview', + })) } - // ignore paragraphs that cannot be converted - if (!previewPossible(node)) return - paragraphs.push(Object.freeze({ - offset, - })) }) return paragraphs } /** - * Is it possible to convert the currently selected node into a preview? - * @param {object} state current editor state - * @param {object} state.selection current selection + * Is it possible to convert the node into a preview? + * @param {object} node the node in question * @return {boolean} */ function previewPossible(node) { - if (hasOtherContent(node)) { + if (node.type.name !== 'paragraph' || hasOtherContent(node)) { return false } const href = extractHref(node.firstChild) diff --git a/src/tests/plugins/extractLinkParagraphs.spec.js b/src/tests/plugins/extractLinkParagraphs.spec.js index 1c51d1eadeb..60f49e7f570 100644 --- a/src/tests/plugins/extractLinkParagraphs.spec.js +++ b/src/tests/plugins/extractLinkParagraphs.spec.js @@ -5,10 +5,12 @@ import extractLinkParagraphs from '../../plugins/extractLinkParagraphs.js' import Link from '../../marks/Link.js' +import Preview from '../../nodes/Preview.js' import { createCustomEditor } from '../helpers.js' describe('extractLinkParagraphs', () => { const link = 'Link' + const preview = 'Link' it('returns an empty array for an empty doc', () => { const doc = prepareDoc('') @@ -21,7 +23,15 @@ describe('extractLinkParagraphs', () => { const doc = prepareDoc(content) const paragraphs = extractLinkParagraphs(doc) expect(paragraphs).toEqual([ - { offset: 0 } + { offset: 0 , type: 'text-only' } + ]) + }) + + it('returns paragraphs with a single preview', () => { + const doc = prepareDoc(preview) + const paragraphs = extractLinkParagraphs(doc) + expect(paragraphs).toEqual([ + { offset: 0 , type: 'link-preview' } ]) }) @@ -30,7 +40,7 @@ describe('extractLinkParagraphs', () => { const doc = prepareDoc(content) const paragraphs = extractLinkParagraphs(doc) expect(paragraphs).toEqual([ - { offset: 0 } + { offset: 0 , type: 'text-only' } ]) }) @@ -40,8 +50,18 @@ describe('extractLinkParagraphs', () => { const doc = prepareDoc(content) const paragraphs = extractLinkParagraphs(doc) expect(paragraphs).toEqual([ - { offset: 0 }, - { offset: 6 } + { offset: 0 , type: 'text-only' }, + { offset: 6 , type: 'text-only' } + ]) + }) + + it('returns previews mixed with paragraphs with a single link', () => { + const content = `

${link}

${preview}` + const doc = prepareDoc(content) + const paragraphs = extractLinkParagraphs(doc) + expect(paragraphs).toEqual([ + { offset: 0 , type: 'text-only' }, + { offset: 6 , type: 'link-preview' } ]) }) @@ -59,6 +79,13 @@ describe('extractLinkParagraphs', () => { expect(paragraphs).toEqual([]) }) + it('ignores paragraphs with a link to self', () => { + const content = '

test

' + const doc = prepareDoc(content) + const paragraphs = extractLinkParagraphs(doc) + expect(paragraphs).toEqual([]) + }) + it('ignores paragraphs with text before the link', () => { const content = `

Hello ${link}

` const doc = prepareDoc(content) @@ -78,7 +105,7 @@ describe('extractLinkParagraphs', () => { function prepareDoc(content) { const editor = createCustomEditor({ content, - extensions: [ Link ] + extensions: [ Link, Preview ] }) return editor.state.doc } From 9eb06ccc6157701a0ee158fff8fa6294379e3993 Mon Sep 17 00:00:00 2001 From: Jonas Date: Tue, 17 Sep 2024 18:29:11 +0200 Subject: [PATCH 04/13] feat(previewOptions): Migrate preview options to prosemirror decorations Fixes: #6185 Signed-off-by: Jonas --- src/components/Editor/PreviewOptions.vue | 64 ++++++++-- src/nodes/Paragraph.js | 9 ++ src/plugins/previewOptions.js | 150 +++++++++++++++++++++++ 3 files changed, 212 insertions(+), 11 deletions(-) create mode 100644 src/plugins/previewOptions.js diff --git a/src/components/Editor/PreviewOptions.vue b/src/components/Editor/PreviewOptions.vue index 04a859e4b55..1d0de226695 100644 --- a/src/components/Editor/PreviewOptions.vue +++ b/src/components/Editor/PreviewOptions.vue @@ -3,32 +3,31 @@ - SPDX-License-Identifier: AGPL-3.0-or-later -->