From 8e2a6221693dfc92b9a53ca8a2a54ee0f965a54c Mon Sep 17 00:00:00 2001 From: Kevin Ansfield Date: Mon, 14 May 2018 18:29:54 +0100 Subject: [PATCH] Koenig - Allow heading levels to be changed via markdown expansion refs https://github.com/TryGhost/Ghost/issues/9623 - disable the default mobiledoc-kit heading text expansion and replace with our own that matches heading markdown at the beginning of existing text and will change the heading level of the existing text --- .../addon/options/text-expansions.js | 89 +++++++++++++------ 1 file changed, 61 insertions(+), 28 deletions(-) diff --git a/ghost/admin/lib/koenig-editor/addon/options/text-expansions.js b/ghost/admin/lib/koenig-editor/addon/options/text-expansions.js index 6071cd079c..2cb4fa1d5c 100644 --- a/ghost/admin/lib/koenig-editor/addon/options/text-expansions.js +++ b/ghost/admin/lib/koenig-editor/addon/options/text-expansions.js @@ -9,43 +9,41 @@ import {run} from '@ember/runloop'; // https://github.com/bustlelabs/mobiledoc-kit#responding-to-text-input export default function (editor, koenig) { - // We don't want to run all our content rules on every text entry event, - // instead we check to see if this text entry event could match a content - // rule, and only then run the rules. Right now we only want to match - // content ending with *, _, ), ~, and `. This could increase as we support - // more markdown. + /* block level markdown ------------------------------------------------- */ + editor.unregisterTextInputHandler('heading'); editor.onTextInput({ - name: 'inline_markdown', - match: /[*_)~`]$/, + name: 'md_heading', + match: /^(#{1,6}) /, run(editor, matches) { + let hashes = matches[1]; + let headingTag = `h${hashes.length}`; + let {range} = editor; let text = editor.range.head.section.textUntil(editor.range.head); - switch (matches[0]) { - case '*': - matchStrongStar(editor, text); - matchEmStar(editor, text); - break; - case '_': - matchStrongUnderscore(editor, text); - matchEmUnderscore(editor, text); - break; - case ')': - matchLink(editor, text); - matchImage(editor, text); - break; - case '~': - matchStrikethrough(editor, text); - break; - case '`': - matchCode(editor, text); - break; + // we don't want to convert to a heading if the user has not just + // finished typing the markdown (eg, they've made a previous + // heading expansion then Cmd-Z'ed it to get the text back then + // starts typing at the end of the heading) + if (text !== matches[0]) { + return; } + + editor.run((postEditor) => { + range = range.extend(-(matches[0].length)); + let position = postEditor.deleteRange(range); + postEditor.setRange(position); + + // skip toggle if we already have the same heading level + if (editor.activeSection.tagName === headingTag) { + return; + } + + postEditor.toggleSection(headingTag); + }); } }); - /* block level markdown ------------------------------------------------- */ - // mobiledoc-kit has `* ` already built-in so we only need to add `- ` editor.onTextInput({ name: 'md_ul', @@ -85,6 +83,41 @@ export default function (editor, koenig) { /* inline markdown ------------------------------------------------------ */ + // We don't want to run all our content rules on every text entry event, + // instead we check to see if this text entry event could match a content + // rule, and only then run the rules. Right now we only want to match + // content ending with *, _, ), ~, and `. This could increase as we support + // more markdown. + + editor.onTextInput({ + name: 'inline_markdown', + match: /[*_)~`]$/, + run(editor, matches) { + let text = editor.range.head.section.textUntil(editor.range.head); + + switch (matches[0]) { + case '*': + matchStrongStar(editor, text); + matchEmStar(editor, text); + break; + case '_': + matchStrongUnderscore(editor, text); + matchEmUnderscore(editor, text); + break; + case ')': + matchLink(editor, text); + matchImage(editor, text); + break; + case '~': + matchStrikethrough(editor, text); + break; + case '`': + matchCode(editor, text); + break; + } + } + }); + // --\s = en dash – // ---. = em dash — // separate to the grouped replacement functions because we're matching on