0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-03-11 02:12:21 -05:00

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

View file

@ -10,6 +10,7 @@ export default class GhKoenigEditorReactComponent extends Component {
containerElement = null; containerElement = null;
titleElement = null; titleElement = null;
subtitleElement = null;
mousedownY = 0; mousedownY = 0;
uploadUrl = `${ghostPaths().apiRoot}/images/upload/`; uploadUrl = `${ghostPaths().apiRoot}/images/upload/`;
@ -111,21 +112,34 @@ export default class GhKoenigEditorReactComponent extends Component {
this.titleElement.focus(); 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 @action
onTitleKeydown(event) { onTitleKeydown(event) {
if (this.feature.get('editorSubtitle')) { 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(); event.preventDefault();
const subheadElement = document.querySelector('.gh-editor-subtitle'); this.subtitleElement?.focus();
if (subheadElement) { }
subheadElement.focus();
if ((key === 'ArrowDown' || key === 'ArrowRight') && !event.shiftKey) {
const couldLeaveTitle = !value || selectionStart === value.length;
if (couldLeaveTitle) {
event.preventDefault();
this.subtitleElement?.focus();
} }
} }
} else { } 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; const {editorAPI} = this;
if (!editorAPI || event.originalEvent.isComposing) { if (!editorAPI || event.originalEvent.isComposing) {
@ -152,6 +166,21 @@ export default class GhKoenigEditorReactComponent extends Component {
// Subhead ("excerpt") Actions ------------------------------------------- // 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 @action
onExcerptInput(event) { onExcerptInput(event) {
this.args.setExcerpt?.(event.target.value); this.args.setExcerpt?.(event.target.value);
@ -159,9 +188,37 @@ export default class GhKoenigEditorReactComponent extends Component {
@action @action
onExcerptKeydown(event) { 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(); event.preventDefault();
this.editorAPI.focusEditor({position: 'top'});
if (key === 'Enter' && !editorAPI.editorIsEmpty()) {
editorAPI.insertParagraphAtTop({focus: true});
} else {
editorAPI.focusEditor({position: 'top'});
}
} }
} }