Koenig - Improve MD text expansion behaviour

refs https://github.com/TryGhost/Ghost/issues/9505
- fix overzealous matching of inline formatting
  - ensure the match starts with a space or the start of the paragraph
  - ensure the start matched chars are not followed by a space
  - ensure the end matched chars are not preceded by a space
  - avoid matching more MD chars than expected
- allow text expansions to work when editing text in the middle of a paragraph
  - do not add a trailing space after reformatting in this case
This commit is contained in:
Kevin Ansfield 2018-03-15 17:13:50 +00:00
parent 775e59f55b
commit cc083ef93f

View File

@ -20,27 +20,28 @@ export default function (editor) {
editor.onTextInput({ editor.onTextInput({
name: 'inline_markdown', name: 'inline_markdown',
match: /[*_)~`]$/, match: /[*_)~`]$/,
run(postEditor, matches) { run(editor, matches) {
let text = postEditor.range.head.section.textUntil(postEditor.range.head); let text = editor.range.head.section.textUntil(editor.range.head);
let hasTextAfter = editor.range.head.section.text.length > text.length;
switch (matches[0]) { switch (matches[0]) {
case '*': case '*':
matchStrongStar(postEditor, text); matchStrongStar(editor, text, hasTextAfter);
matchEmStar(postEditor, text); matchEmStar(editor, text, hasTextAfter);
break; break;
case '_': case '_':
matchStrongUnderscore(postEditor, text); matchStrongUnderscore(editor, text, hasTextAfter);
matchEmUnderscore(postEditor, text); matchEmUnderscore(editor, text, hasTextAfter);
break; break;
case ')': case ')':
matchLink(postEditor, text); matchLink(editor, text, hasTextAfter);
matchImage(postEditor, text); matchImage(editor, text, hasTextAfter);
break; break;
case '~': case '~':
matchStrikethrough(postEditor, text); matchStrikethrough(editor, text, hasTextAfter);
break; break;
case '`': case '`':
matchCode(postEditor, text); matchCode(editor, text, hasTextAfter);
break; break;
} }
} }
@ -99,37 +100,43 @@ export default function (editor) {
/* inline markdown ------------------------------------------------------ */ /* inline markdown ------------------------------------------------------ */
function matchStrongStar(editor, text) { function matchStrongStar(editor, text, hasTextAfter = false) {
let {range} = editor; let {range} = editor;
let matches = text.match(/\*\*(.+?)\*\*$/); let matches = text.match(/(^|\s)\*\*([^\s*].+?[^\s*])\*\*/);
if (matches) { if (matches) {
range = range.extend(-(matches[0].length)); let match = matches[0][0] === '*' ? matches[0] : matches[0].substr(1);
range = range.extend(-(match.length));
editor.run((postEditor) => { editor.run((postEditor) => {
let position = postEditor.deleteRange(range); let position = postEditor.deleteRange(range);
let bold = postEditor.builder.createMarkup('strong'); let bold = postEditor.builder.createMarkup('strong');
let nextPosition = postEditor.insertTextWithMarkup(position, matches[1], [bold]); let nextPosition = postEditor.insertTextWithMarkup(position, matches[2], [bold]);
postEditor.insertTextWithMarkup(nextPosition, ' ', []); if (!hasTextAfter) {
postEditor.insertTextWithMarkup(nextPosition, ' ', []);
}
}); });
} }
} }
function matchStrongUnderscore(editor, text) { function matchStrongUnderscore(editor, text, hasTextAfter = false) {
let {range} = editor; let {range} = editor;
let matches = text.match(/__(.+?)__$/); let matches = text.match(/(^|\s)__([^\s_].+?[^\s_])__/);
if (matches) { if (matches) {
range = range.extend(-(matches[0].length)); let match = matches[0][0] === '_' ? matches[0] : matches[0].substr(1);
range = range.extend(-(match.length));
editor.run((postEditor) => { editor.run((postEditor) => {
let position = postEditor.deleteRange(range); let position = postEditor.deleteRange(range);
let bold = postEditor.builder.createMarkup('strong'); let bold = postEditor.builder.createMarkup('strong');
let nextPosition = postEditor.insertTextWithMarkup(position, matches[1], [bold]); let nextPosition = postEditor.insertTextWithMarkup(position, matches[2], [bold]);
postEditor.insertTextWithMarkup(nextPosition, ' ', []); if (!hasTextAfter) {
postEditor.insertTextWithMarkup(nextPosition, ' ', []);
}
}); });
} }
} }
function matchEmStar(editor, text) { function matchEmStar(editor, text, hasTextAfter = false) {
let {range} = editor; let {range} = editor;
let matches = text.match(/(^|[^*])\*([^*].*?)\*$/); let matches = text.match(/(^|\s)\*([^\s*][^*]+?[^\s])\*/);
if (matches) { if (matches) {
let match = matches[0][0] === '*' ? matches[0] : matches[0].substr(1); let match = matches[0][0] === '*' ? matches[0] : matches[0].substr(1);
range = range.extend(-(match.length)); range = range.extend(-(match.length));
@ -137,14 +144,16 @@ export default function (editor) {
let position = postEditor.deleteRange(range); let position = postEditor.deleteRange(range);
let em = postEditor.builder.createMarkup('em'); let em = postEditor.builder.createMarkup('em');
let nextPosition = postEditor.insertTextWithMarkup(position, matches[2], [em]); let nextPosition = postEditor.insertTextWithMarkup(position, matches[2], [em]);
postEditor.insertTextWithMarkup(nextPosition, ' ', []); if (!hasTextAfter) {
postEditor.insertTextWithMarkup(nextPosition, ' ', []);
}
}); });
} }
} }
function matchEmUnderscore(editor, text) { function matchEmUnderscore(editor, text, hasTextAfter = false) {
let {range} = editor; let {range} = editor;
let matches = text.match(/(^|[^_])_([^_].+?)_$/); let matches = text.match(/(^|\s)_([^\s_][^_]+?[^\s])_/);
if (matches) { if (matches) {
let match = matches[0][0] === '_' ? matches[0] : matches[0].substr(1); let match = matches[0][0] === '_' ? matches[0] : matches[0].substr(1);
range = range.extend(-(match.length)); range = range.extend(-(match.length));
@ -152,12 +161,48 @@ export default function (editor) {
let position = postEditor.deleteRange(range); let position = postEditor.deleteRange(range);
let em = postEditor.builder.createMarkup('em'); let em = postEditor.builder.createMarkup('em');
let nextPosition = postEditor.insertTextWithMarkup(position, matches[2], [em]); let nextPosition = postEditor.insertTextWithMarkup(position, matches[2], [em]);
postEditor.insertTextWithMarkup(nextPosition, ' ', []); if (!hasTextAfter) {
postEditor.insertTextWithMarkup(nextPosition, ' ', []);
}
}); });
} }
} }
function matchLink(editor, text) { function matchStrikethrough(editor, text, hasTextAfter = false) {
let {range} = editor;
let matches = text.match(/(^|\s)~([^\s~][^~]+?[^\s])~/);
if (matches) {
let match = matches[0][0] === '~' ? matches[0] : matches[0].substr(1);
range = range.extend(-(match.length));
editor.run((postEditor) => {
let position = postEditor.deleteRange(range);
let s = postEditor.builder.createMarkup('s');
let nextPosition = postEditor.insertTextWithMarkup(position, matches[2], [s]);
if (!hasTextAfter) {
postEditor.insertTextWithMarkup(nextPosition, ' ', []); // insert the un-marked-up space
}
});
}
}
function matchCode(editor, text, hasTextAfter = false) {
let {range} = editor;
let matches = text.match(/(^|\s)`([^\s`][^`]+?[^\s])`/);
if (matches) {
let match = matches[0][0] === '`' ? matches[0] : matches[0].substr(1);
range = range.extend(-(match.length));
editor.run((postEditor) => {
let position = postEditor.deleteRange(range);
let code = postEditor.builder.createMarkup('code');
let nextPosition = postEditor.insertTextWithMarkup(position, matches[2], [code]);
if (!hasTextAfter) {
postEditor.insertTextWithMarkup(nextPosition, ' ', []); // insert the un-marked-up space
}
});
}
}
function matchLink(editor, text, hasTextAfter = false) {
let {range} = editor; let {range} = editor;
let matches = text.match(/(^|[^!])\[(.*?)\]\((.*?)\)$/); let matches = text.match(/(^|[^!])\[(.*?)\]\((.*?)\)$/);
if (matches) { if (matches) {
@ -169,12 +214,14 @@ export default function (editor) {
let position = postEditor.deleteRange(range); let position = postEditor.deleteRange(range);
let a = postEditor.builder.createMarkup('a', {href: url}); let a = postEditor.builder.createMarkup('a', {href: url});
let nextPosition = postEditor.insertTextWithMarkup(position, text, [a]); let nextPosition = postEditor.insertTextWithMarkup(position, text, [a]);
postEditor.insertTextWithMarkup(nextPosition, ' ', []); // insert the un-marked-up space if (!hasTextAfter) {
postEditor.insertTextWithMarkup(nextPosition, ' ', []); // insert the un-marked-up space
}
}); });
} }
} }
function matchImage(editor, text) { function matchImage(editor, text, hasTextAfter = false) {
let matches = text.match(/^!\[(.*?)\]\((.*?)\)$/); let matches = text.match(/^!\[(.*?)\]\((.*?)\)$/);
if (matches) { if (matches) {
let {range: {head, head: {section}}} = editor; let {range: {head, head: {section}}} = editor;
@ -208,32 +255,4 @@ export default function (editor) {
}); });
} }
} }
function matchStrikethrough(editor, text) {
let {range} = editor;
let matches = text.match(/~(.+?)~$/);
if (matches) {
range = range.extend(-(matches[0].length));
editor.run((postEditor) => {
let position = postEditor.deleteRange(range);
let s = postEditor.builder.createMarkup('s');
let nextPosition = postEditor.insertTextWithMarkup(position, matches[1], [s]);
postEditor.insertTextWithMarkup(nextPosition, ' ', []); // insert the un-marked-up space
});
}
}
function matchCode(editor, text) {
let {range} = editor;
let matches = text.match(/`(.+?)`/);
if (matches) {
range = range.extend(-(matches[0].length));
editor.run((postEditor) => {
let position = postEditor.deleteRange(range);
let code = postEditor.builder.createMarkup('code');
let nextPosition = postEditor.insertTextWithMarkup(position, matches[1], [code]);
postEditor.insertTextWithMarkup(nextPosition, ' ', []); // insert the un-marked-up space
});
}
}
} }