Koenig - Backspace undoes code/strike text expansions

refs https://github.com/TryGhost/Ghost/issues/9505
- prevent typing at the end of a `code` or ~strike~ markup continuing the format
- if <kbd>Backspace</kbd> is pressed at the end of a `code` or ~strike~ markup then undo the text expansion and remove the last char
This commit is contained in:
Kevin Ansfield 2018-04-12 17:20:27 +01:00
parent d54172172d
commit 30a61226f8

View File

@ -53,6 +53,13 @@ const CURSOR_BEFORE = -1;
const CURSOR_AFTER = 1; const CURSOR_AFTER = 1;
const NO_CURSOR_MOVEMENT = 0; const NO_CURSOR_MOVEMENT = 0;
// markups that should not be continued when typing and reverted to their
// text expansion style when backspacing over findal char of markup
const SPECIAL_MARKUPS = {
S: '~~',
CODE: '`'
};
function arrayToMap(array) { function arrayToMap(array) {
let map = Object.create(null); let map = Object.create(null);
array.forEach((key) => { array.forEach((key) => {
@ -490,6 +497,24 @@ export default Component.extend({
this.deselectCard(this._selectedCard); this.deselectCard(this._selectedCard);
} }
// if we have `code` or ~strike~ formatting to the left but not the right
// then toggle the formatting - these formats should only be creatable
// through the text expansions
// HACK: this is largely duplicated in `inputModeDidChange` to work
// around an event ordering bug - see comments there
if (isCollapsed && head.marker) {
Object.keys(SPECIAL_MARKUPS).forEach((tagName) => {
if (head.marker.hasMarkup(tagName)) {
let nextMarker = head.markerIn(1);
if (!nextMarker || !nextMarker.hasMarkup(tagName)) {
run.next(this, function () {
editor.toggleMarkup(tagName);
});
}
}
});
}
// pass the selected range through to the toolbar + menu components // pass the selected range through to the toolbar + menu components
this.set('selectedRange', editor.range); this.set('selectedRange', editor.range);
}, },
@ -507,6 +532,23 @@ export default Component.extend({
let sectionParentTagNames = editor.activeSections.map(s => s.isNested ? s.parent.tagName : s.tagName); let sectionParentTagNames = editor.activeSections.map(s => s.isNested ? s.parent.tagName : s.tagName);
let sectionTags = arrayToMap(sectionParentTagNames); let sectionTags = arrayToMap(sectionParentTagNames);
// HACK: this is largly duplicated with our `cursorDidChange` handling.
// On keyboard cursor movement our `cursorDidChange` toggle for special
// formats happens before mobiledoc's readstate updates activeMarkups
// so we have to re-do it here
let {head, isCollapsed} = editor.range;
if (isCollapsed) {
let activeMarkupTagNames = editor.activeMarkups.mapBy('tagName');
Object.keys(SPECIAL_MARKUPS).forEach((tagName) => {
if (activeMarkupTagNames.includes(tagName.toLowerCase())) {
let nextMarker = head.markerIn(1);
if (!nextMarker || !nextMarker.hasMarkup(tagName)) {
return editor.toggleMarkup(tagName);
}
}
});
}
// Avoid updating this component's properties synchronously while // Avoid updating this component's properties synchronously while
// rendering the editor (after rendering the component) because it // rendering the editor (after rendering the component) because it
// causes Ember to display deprecation warnings // causes Ember to display deprecation warnings
@ -522,7 +564,8 @@ export default Component.extend({
}, },
handleBackspaceKey() { handleBackspaceKey() {
let {isCollapsed, head: {offset, section}} = this.editor.range; let editor = this.get('editor');
let {head, isCollapsed, head: {marker, offset, section}} = editor.range;
// if a card is selected we should delete the card then place the cursor // if a card is selected we should delete the card then place the cursor
// at the end of the previous section // at the end of the previous section
@ -557,6 +600,39 @@ export default Component.extend({
return; return;
} }
// if the markup about to be deleted is a special format (code, strike)
// then undo the text expansion to allow it to be extended
if (isCollapsed && marker) {
let specialMarkupTagNames = Object.keys(SPECIAL_MARKUPS);
let hasReversed = false;
specialMarkupTagNames.forEach((tagName) => {
// only continue if we're about to delete a special markup
let markup = marker.markups.find(markup => markup.tagName.toUpperCase() === tagName);
if (markup) {
let nextMarker = head.markerIn(1);
// ensure we're at the end of the markup not inside it
if (!nextMarker || !nextMarker.hasMarkup(tagName)) {
// wrap with the text expansion, remove formatting, then delete the last char
editor.run((postEditor) => {
let markdown = SPECIAL_MARKUPS[tagName];
let range = editor.range.expandByMarker(marker => !!marker.markups.includes(markup));
postEditor.insertText(range.head, markdown);
range = range.extend(markdown.length);
let endPos = postEditor.insertText(range.tail, markdown);
range = range.extend(markdown.length);
postEditor.toggleMarkup(tagName, range);
endPos = postEditor.deleteAtPosition(endPos, -1);
postEditor.setRange(endPos);
});
hasReversed = true;
}
}
});
if (hasReversed) {
return;
}
}
return false; return false;
}, },