Fixed cursor movement across title/subtitle/editor

closes https://linear.app/tryghost/issue/MOM-175

- matches cursor behaviour on Up/Down/Left/Right/Tab/Enter to our previous behaviour when we only had the title and editor
This commit is contained in:
Kevin Ansfield 2024-06-03 12:51:48 +01:00
parent 833ac83921
commit 76a41d4e92
2 changed files with 69 additions and 11 deletions

View File

@ -67,6 +67,7 @@
@value={{readonly this.excerpt}}
@input={{this.onExcerptInput}}
@keyDown={{this.onExcerptKeydown}}
@didCreateTextarea={{this.registerSubtitleElement}}
data-test-textarea="subtitle"
/>
{{#if @excerptErrorMessage}}
@ -84,7 +85,7 @@
@cardConfig={{@cardOptions}}
@onChange={{@onBodyChange}}
@registerAPI={{this.registerEditorAPI}}
@cursorDidExitAtTop={{this.focusTitle}}
@cursorDidExitAtTop={{if this.feature.editorSubtitle this.focusSubtitle this.focusTitle}}
@updateWordCount={{@updateWordCount}}
@updatePostTkCount={{@updatePostTkCount}}
/>

View File

@ -10,6 +10,7 @@ export default class GhKoenigEditorReactComponent extends Component {
containerElement = null;
titleElement = null;
subtitleElement = null;
mousedownY = 0;
uploadUrl = `${ghostPaths().apiRoot}/images/upload/`;
@ -111,21 +112,34 @@ export default class GhKoenigEditorReactComponent extends Component {
this.titleElement.focus();
}
// move cursor to the editor on
// - Tab
// - Arrow Down/Right when input is empty or caret at end of input
// - Enter, creating an empty paragraph when editor is not empty
@action
onTitleKeydown(event) {
if (this.feature.get('editorSubtitle')) {
if (event.key === 'Enter') {
// move cursor to the subtitle on
// - Tab (handled by browser)
// - Arrow Down/Right when input is empty or caret at end of input
// - Enter
const {key} = event;
const {value, selectionStart} = event.target;
if (key === 'Enter') {
event.preventDefault();
const subheadElement = document.querySelector('.gh-editor-subtitle');
if (subheadElement) {
subheadElement.focus();
this.subtitleElement?.focus();
}
if ((key === 'ArrowDown' || key === 'ArrowRight') && !event.shiftKey) {
const couldLeaveTitle = !value || selectionStart === value.length;
if (couldLeaveTitle) {
event.preventDefault();
this.subtitleElement?.focus();
}
}
} else {
// move cursor to the editor on
// - Tab
// - Arrow Down/Right when input is empty or caret at end of input
// - Enter, creating an empty paragraph when editor is not empty
const {editorAPI} = this;
if (!editorAPI || event.originalEvent.isComposing) {
@ -152,6 +166,21 @@ export default class GhKoenigEditorReactComponent extends Component {
// Subhead ("excerpt") Actions -------------------------------------------
@action
registerSubtitleElement(element) {
this.subtitleElement = element;
}
@action
focusSubtitle() {
this.subtitleElement?.focus();
// timeout ensures this occurs after the keyboard events
setTimeout(() => {
this.subtitleElement?.setSelectionRange(-1, -1);
}, 0);
}
@action
onExcerptInput(event) {
this.args.setExcerpt?.(event.target.value);
@ -159,9 +188,37 @@ export default class GhKoenigEditorReactComponent extends Component {
@action
onExcerptKeydown(event) {
if (event.key === 'Enter') {
// move cursor to the title on
// - Shift+Tab (handled by the browser)
// - Arrow Up/Left when input is empty or caret at start of input
// move cursor to the editor on
// - Tab
// - Arrow Down/Right when input is empty or caret at end of input
// - Enter, creating an empty paragraph when editor is not empty
const {key} = event;
const {value, selectionStart} = event.target;
if ((key === 'ArrowUp' || key === 'ArrowLeft') && !event.shiftKey) {
const couldLeaveTitle = !value || selectionStart === 0;
if (couldLeaveTitle) {
event.preventDefault();
this.focusTitle();
}
}
const {editorAPI} = this;
const couldLeaveTitle = !value || selectionStart === value.length;
const arrowLeavingTitle = (key === 'ArrowRight' || key === 'ArrowDown') && couldLeaveTitle;
if (key === 'Enter' || (key === 'Tab' && !event.shiftKey) || arrowLeavingTitle) {
event.preventDefault();
this.editorAPI.focusEditor({position: 'top'});
if (key === 'Enter' && !editorAPI.editorIsEmpty()) {
editorAPI.insertParagraphAtTop({focus: true});
} else {
editorAPI.focusEditor({position: 'top'});
}
}
}