Koenig - Convert pasted <img> and <hr> elements to cards

refs https://github.com/TryGhost/Ghost/issues/9623
- use our custom paste event to intercept and modify pasted HTML before passing to mobiledoc
  - wrap the html in a `<div>` so that all elements get parsed by mobiledoc-kit
- when pasting results in the last pasted section being a card, insert a blank paragraph
  - fixes immediate breaking of the undo stack
This commit is contained in:
Kevin Ansfield 2018-05-18 13:55:46 +01:00
parent 0fa0068f6a
commit fb226ac956
2 changed files with 61 additions and 1 deletions

View File

@ -523,6 +523,8 @@ export default Component.extend({
cursorDidChange(editor) {
let {head, tail, direction, isCollapsed, head: {section}} = editor.range;
let isPasting = this._isPasting;
this._isPasting = false;
// sometimes we perform a programatic edit that causes a cursor change
// but we actually want to skip the default behaviour because we've
@ -543,6 +545,29 @@ export default Component.extend({
// select the card if the cursor is on the before/after &zwnj; char
if (section && isCollapsed && section.type === 'card-section') {
if (isPasting) {
// when pasting, if the last section added is a card we don't want to
// select it (it breaks the undo stack) but rather create an empty
// paragraph underneath
// TODO: why does the undo stack break?
let collection = section.parent.sections;
let nextSection = section.next;
editor.run((postEditor) => {
let newSection = postEditor.builder.createMarkupSection('p');
if (nextSection) {
postEditor.insertSectionBefore(collection, newSection, nextSection);
} else {
postEditor.insertSectionAtEnd(newSection);
}
postEditor.setRange(newSection.tailPosition());
this.set('selectedRange', newSection.tailPosition());
this._skipCursorChange = true;
});
return;
}
if (head.offset === 0 || head.offset === 1) {
// select card after render to ensure that our componentCards
// attr is populated
@ -620,6 +645,11 @@ export default Component.extend({
let editor = this.editor;
let range = editor.range;
// when pasting, if the last section added is a card we don't want to
// select it (it breaks the undo stack) but rather create an empty
// paragraph underneath
this._isPasting = true;
// if a URL is pasted and we have a selection, make that selection a link
if (range && !range.isCollapsed && range.headSection === range.tailSection && range.headSection.isMarkerable) {
let {text} = getContentFromPasteEvent(event);
@ -689,7 +719,14 @@ export default Component.extend({
return text;
}
if (type === 'text/html') {
return normalizedHtml;
// HACK: mobiledoc-kit won't parse top-level <img> or
// other "unknown" elements so we wrap everything here
// so that we don't get blank posts and elements are
// passed through to our parser plugins correctly
// TODO: fix parsing in mobiledoc, related issues:
// https://github.com/bustle/mobiledoc-kit/issues/619
// https://github.com/bustle/mobiledoc-kit/issues/494
return `<div>${normalizedHtml}</div>`;
}
}
}

View File

@ -21,7 +21,30 @@ export function removeLeadingNewline(node) {
node.nodeValue = node.nodeValue.replace(/^\n/, '');
}
export function imgToCard(node, builder, {addSection, nodeFinished}) {
if (node.nodeType !== 1 || node.tagName !== 'IMG') {
return;
}
let payload = {src: node.src};
let cardSection = builder.createCardSection('image', payload);
addSection(cardSection);
nodeFinished();
}
export function hrToCard(node, builder, {addSection, nodeFinished}) {
if (node.nodeType !== 1 || node.tagName !== 'HR') {
return;
}
let cardSection = builder.createCardSection('hr');
addSection(cardSection);
nodeFinished();
}
export default [
brToSoftBreakAtom,
removeLeadingNewline,
imgToCard,
hrToCard
];