From 16e509f21115587605c5ec7c63e74431d080b81d Mon Sep 17 00:00:00 2001 From: Jonas Date: Wed, 19 Oct 2022 12:35:34 +0200 Subject: [PATCH 1/9] Add markdown-it-image-figures for treating images as block elements Signed-off-by: Jonas --- package-lock.json | 18 ++++++++++++++++++ package.json | 1 + 2 files changed, 19 insertions(+) diff --git a/package-lock.json b/package-lock.json index d0cb3a91d0d..30d5f551816 100644 --- a/package-lock.json +++ b/package-lock.json @@ -63,6 +63,7 @@ "markdown-it": "^13.0.1", "markdown-it-container": "^3.0.0", "markdown-it-front-matter": "^0.2.3", + "markdown-it-image-figures": "^2.1.0", "mitt": "^3.0.0", "path-normalize": "^6.0.7", "prosemirror-collab": "^1.3.0", @@ -14144,6 +14145,17 @@ "resolved": "https://registry.npmjs.org/markdown-it-front-matter/-/markdown-it-front-matter-0.2.3.tgz", "integrity": "sha512-s9+rcClLmZsZc3YL8Awjg/YO/VdphlE20LJ9Bx5a8RAFLI5a1vq6Mll8kOzG6w/wy8yhFLBupaa6Mfd60GATkA==" }, + "node_modules/markdown-it-image-figures": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/markdown-it-image-figures/-/markdown-it-image-figures-2.1.0.tgz", + "integrity": "sha512-a5FIzzUlK65iGfQlNWH94cyxKpV+Qj8a+4Q3+wVseryHMCOfGaX0aCNtdEkCeb19uGDzVw3Az5tst2hXWAdJ1Q==", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "markdown-it": "*" + } + }, "node_modules/markdown-it/node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -30535,6 +30547,12 @@ "resolved": "https://registry.npmjs.org/markdown-it-front-matter/-/markdown-it-front-matter-0.2.3.tgz", "integrity": "sha512-s9+rcClLmZsZc3YL8Awjg/YO/VdphlE20LJ9Bx5a8RAFLI5a1vq6Mll8kOzG6w/wy8yhFLBupaa6Mfd60GATkA==" }, + "markdown-it-image-figures": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/markdown-it-image-figures/-/markdown-it-image-figures-2.1.0.tgz", + "integrity": "sha512-a5FIzzUlK65iGfQlNWH94cyxKpV+Qj8a+4Q3+wVseryHMCOfGaX0aCNtdEkCeb19uGDzVw3Az5tst2hXWAdJ1Q==", + "requires": {} + }, "material-colors": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/material-colors/-/material-colors-1.2.6.tgz", diff --git a/package.json b/package.json index ca07224e991..cd434290e29 100644 --- a/package.json +++ b/package.json @@ -88,6 +88,7 @@ "markdown-it": "^13.0.1", "markdown-it-container": "^3.0.0", "markdown-it-front-matter": "^0.2.3", + "markdown-it-image-figures": "^2.1.0", "mitt": "^3.0.0", "path-normalize": "^6.0.7", "prosemirror-collab": "^1.3.0", From 26b958b039d8b3c6f2a3869bcc2fabc835f871bb Mon Sep 17 00:00:00 2001 From: Jonas Date: Wed, 19 Oct 2022 12:57:31 +0200 Subject: [PATCH 2/9] Make images block elements Use markdown-it-image-figures to render standalone images inside `
` elements instead of `

` when markdown source gets parsed into HTML. This allows to treat images as block elements by TipTap. Also keep support for inline images to not break markdown files that have inline images. Fixes: #2873 Signed-off-by: Jonas --- src/extensions/RichText.js | 6 ++-- src/markdownit/index.js | 2 ++ src/nodes/Image.js | 10 ++++++ src/nodes/ImageInline.js | 74 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 89 insertions(+), 3 deletions(-) create mode 100644 src/nodes/ImageInline.js diff --git a/src/extensions/RichText.js b/src/extensions/RichText.js index df329d6799e..5ec05091ce8 100644 --- a/src/extensions/RichText.js +++ b/src/extensions/RichText.js @@ -38,6 +38,7 @@ import HardBreak from './HardBreak.js' import KeepSyntax from './KeepSyntax.js' import Table from './../nodes/Table.js' import Image from './../nodes/Image.js' +import ImageInline from './../nodes/ImageInline.js' import Heading from '../nodes/Heading/index.js' import BulletList from './../nodes/BulletList.js' import TaskList from './../nodes/TaskList.js' @@ -80,9 +81,8 @@ export default Extension.create({ TaskItem, Callout, Underline, - Image.configure({ - inline: true, - }), + Image, + ImageInline, Dropcursor, KeepSyntax, FrontMatter, diff --git a/src/markdownit/index.js b/src/markdownit/index.js index 1ad52249670..7749e0f3468 100644 --- a/src/markdownit/index.js +++ b/src/markdownit/index.js @@ -5,6 +5,7 @@ import underline from './underline.js' import splitMixedLists from './splitMixedLists.js' import callouts from './callouts.js' import keepSyntax from './keepSyntax.js' +import implicitFigures from 'markdown-it-image-figures' const markdownit = MarkdownIt('commonmark', { html: false, breaks: false }) .enable('strikethrough') @@ -15,6 +16,7 @@ const markdownit = MarkdownIt('commonmark', { html: false, breaks: false }) .use(callouts) .use(keepSyntax) .use(markdownitMentions) + .use(implicitFigures) // Issue #3370: To preserve softbreaks within md files we preserve all whitespaces, so we must not introduce additional new lines after a
element markdownit.renderer.rules.hardbreak = (tokens, idx, options) => (options.xhtmlOut ? '
' : '
') diff --git a/src/nodes/Image.js b/src/nodes/Image.js index 3660c04e72e..137bd7715f3 100644 --- a/src/nodes/Image.js +++ b/src/nodes/Image.js @@ -29,6 +29,16 @@ const Image = TiptapImage.extend({ selectable: false, + parseHTML() { + return [ + { + tag: this.options.allowBase64 + ? 'figure img[src]' + : 'figure img[src]:not([src^="data:"])', + }, + ] + }, + renderHTML() { // Avoid the prosemirror node creation to trigger image loading as we use a custom node view anyways // Otherwise it would attempt to load the image from the current location before the node view is even initialized diff --git a/src/nodes/ImageInline.js b/src/nodes/ImageInline.js new file mode 100644 index 00000000000..fc82eb71385 --- /dev/null +++ b/src/nodes/ImageInline.js @@ -0,0 +1,74 @@ +/* + * @copyright Copyright (c) 2022 Jonas + * + * @author Jonas + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +import TiptapImage from '@tiptap/extension-image' +import ImageView from './ImageView.vue' +import { VueNodeViewRenderer } from '@tiptap/vue-2' + +// Inline image extension. Needed if markdown contains inline images. +// Not supported to be created from our UI (we default to block images). +const ImageInline = TiptapImage.extend({ + name: 'image-inline', + + // Lower priority than (block) Image extension + priority: 99, + + selectable: false, + + parseHTML() { + return [ + { + tag: this.options.allowBase64 + ? 'img[src]' + : 'img[src]:not([src^="data:"])', + }, + ] + }, + + addOptions() { + return { + ...this.parent?.(), + inline: true, + } + }, + + // Empty commands, we want only those from (block) Image extension + addCommands() { + return {} + }, + + // Empty input rules, we want only those from (block) Image extension + addInputRules() { + return [] + }, + + addNodeView() { + return VueNodeViewRenderer(ImageView) + }, + + toMarkdown(state, node) { + state.write('![' + state.esc(node.attrs.alt || '') + '](' + node.attrs.src.replace(/[()]/g, '\\$&') + + (node.attrs.title ? ' "' + node.attrs.title.replace(/"/g, '\\"') + '"' : '') + ')') + }, +}) + +export default ImageInline From 4973f3530ee32fe3c993ddbeb531372986dc729d Mon Sep 17 00:00:00 2001 From: Jonas Date: Wed, 19 Oct 2022 13:07:04 +0200 Subject: [PATCH 3/9] Special-treat commonmark tests with inline images Replace `

` with `

` now that we use markdown-it-image-figures. Signed-off-by: Jonas --- src/tests/markdown.spec.js | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/tests/markdown.spec.js b/src/tests/markdown.spec.js index 35f1f940af3..a9e32a98107 100644 --- a/src/tests/markdown.spec.js +++ b/src/tests/markdown.spec.js @@ -42,21 +42,46 @@ describe('Commonmark', () => { .replace(/
/, '
\n') } + // special treatment because we use markdown-it-image-figures + const figureImageMarkdownTests = [ + 513, 516, 527, 568, 569, 570, 571, 572, 573, 574, 576, 577, 578, 579, 580, 581, 582, 584, 585, 587 + ] + spec.forEach((entry) => { if (skippedMarkdownTests.indexOf(entry.example) !== -1) { return } test('commonmark ' + entry.example, () => { - const expected = entry.markdown.includes('__') + let expected = entry.markdown.includes('__') ? entry.html.replace(//g, '').replace(/<\/strong>/g, '') : entry.html + if (figureImageMarkdownTests.indexOf(entry.example) !== -1) { + expected = expected.replace(/

/g, '

').replace(/<\/p>/g, '
') + } + const rendered = markdownit.render(entry.markdown) + // Ignore special markup for untouched markdown expect(normalize(rendered)).toBe(expected) }) }) }) +describe('Commonmark images', () => { + beforeAll(() => { + // Make sure html tests pass + // entry.section === 'HTML blocks' || entry.section === 'Raw HTML' + markdownit.set({ html: true}) + }) + afterAll(() => { + markdownit.set({ html: false}) + }) + + test('commonmark 513', () => { + expect(markdownit.render('[![moon](moon.jpg)](/uri)\n')).toBe('
\"moon\"
\n') + }) +}) + describe('Markdown though editor', () => { test('headlines', () => { expect(markdownThroughEditor('# Test')).toBe('# Test') @@ -97,6 +122,7 @@ describe('Markdown though editor', () => { }) test('images', () => { expect(markdownThroughEditor('![test](foo)')).toBe('![test](foo)') + expect(markdownThroughEditor('text ![test](foo) moretext')).toBe('text ![test](foo) moretext') }) test('special characters', () => { expect(markdownThroughEditor('"\';&.-#><')).toBe('"\';&.-#><') @@ -179,6 +205,7 @@ describe('Markdown serializer from html', () => { test('images', () => { expect(markdownThroughEditorHtml('description')).toBe('![description](image)') expect(markdownThroughEditorHtml('

description

')).toBe('![description](image)') + expect(markdownThroughEditorHtml('

textdescriptionmoretext

')).toBe('text![description](image)moretext') }) test('checkboxes', () => { expect(markdownThroughEditorHtml('
')).toBe('* [x] foo') From 9e2e31eb8f08036014071a7648f612acbb79dca0 Mon Sep 17 00:00:00 2001 From: Jonas Date: Mon, 24 Oct 2022 11:54:11 +0200 Subject: [PATCH 4/9] Fix typo: 's/editor-midia-handler/editor-media-handler' Signed-off-by: Jonas --- src/components/Editor/MediaHandler.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Editor/MediaHandler.vue b/src/components/Editor/MediaHandler.vue index 55d8cd3b79b..1f04e62b465 100644 --- a/src/components/Editor/MediaHandler.vue +++ b/src/components/Editor/MediaHandler.vue @@ -21,8 +21,8 @@ -->