Skip to content

Commit

Permalink
Make images block elements
Browse files Browse the repository at this point in the history
Use markdown-it-image-figures to render standalone images inside
`<figure>` elements instead of `<p>` 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 <jonas@freesources.org>
  • Loading branch information
mejo- authored and max-nextcloud committed Nov 15, 2022
1 parent 0c496fa commit 4b63cb6
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 3 deletions.
6 changes: 3 additions & 3 deletions src/extensions/RichText.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import HardBreak from './HardBreak.js'
import Heading from '../nodes/Heading/index.js'
import HorizontalRule from '@tiptap/extension-horizontal-rule'
import Image from './../nodes/Image.js'
import ImageInline from './../nodes/ImageInline.js'
import KeepSyntax from './KeepSyntax.js'
import ListItem from '@tiptap/extension-list-item'
import Mention from './../extensions/Mention.js'
Expand Down Expand Up @@ -82,9 +83,8 @@ export default Extension.create({
TaskItem,
Callout,
Underline,
Image.configure({
inline: true,
}),
Image,
ImageInline,
Dropcursor,
KeepSyntax,
FrontMatter,
Expand Down
2 changes: 2 additions & 0 deletions src/markdownit/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand All @@ -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 <br> element
markdownit.renderer.rules.hardbreak = (tokens, idx, options) => (options.xhtmlOut ? '<br />' : '<br>')
Expand Down
10 changes: 10 additions & 0 deletions src/nodes/Image.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
74 changes: 74 additions & 0 deletions src/nodes/ImageInline.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* @copyright Copyright (c) 2022 Jonas <jonas@freesources.org>
*
* @author Jonas <jonas@freesources.org>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/

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

0 comments on commit 4b63cb6

Please sign in to comment.