From 81f3718867ec4932357f6ef4460b1f0eeaf3bdbf Mon Sep 17 00:00:00 2001 From: Kevin Ansfield Date: Mon, 18 Jul 2022 11:11:19 +0100 Subject: [PATCH] Duplicated `` to `` no issue - the component wraps the title and editor canvas components, we only want to replace the canvas component with a react experiment so we need a duplicate for use in the `react-editor` experiment templates --- ghost/admin/.lint-todo | 5 + .../app/components/gh-koenig-editor-react.hbs | 59 +++++++ .../app/components/gh-koenig-editor-react.js | 154 ++++++++++++++++++ .../admin/app/components/gh-koenig-editor.hbs | 1 + ghost/admin/app/templates/react-editor.hbs | 2 +- 5 files changed, 220 insertions(+), 1 deletion(-) create mode 100644 ghost/admin/app/components/gh-koenig-editor-react.hbs create mode 100644 ghost/admin/app/components/gh-koenig-editor-react.js diff --git a/ghost/admin/.lint-todo b/ghost/admin/.lint-todo index c1ef22cf7c..7c14de607b 100644 --- a/ghost/admin/.lint-todo +++ b/ghost/admin/.lint-todo @@ -939,3 +939,8 @@ add|ember-template-lint|no-action|4|11|4|11|831dd12209c22868d0614c65417281fda799 add|ember-template-lint|require-valid-alt-text|3|44|3|44|a7f0566c430150bae4153e0dfb489a218bdeb8a4|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-embed/nft.hbs add|ember-template-lint|require-valid-alt-text|8|20|8|20|9d0c591086dc9139ff38a7b385c3367a83438786|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-embed/nft.hbs add|ember-template-lint|require-input-label|10|12|10|12|8c3c0ea315ff4da828363989a45fa11256a78796|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-image/selector-tenor.hbs +remove|ember-template-lint|no-down-event-binding|5|13|5|13|a158be60fde14211f6abfbac329c94fa5a3b4a46|1658102400000|1668474000000|1673658000000|app/components/gh-koenig-editor.hbs +remove|ember-template-lint|no-invalid-interactive|5|8|5|8|94046126dd697b080aa16222b00a3d5a545ee001|1658102400000|1668474000000|1673658000000|app/components/gh-koenig-editor.hbs +remove|ember-template-lint|no-invalid-interactive|6|8|6|8|94046126dd697b080aa16222b00a3d5a545ee001|1658102400000|1668474000000|1673658000000|app/components/gh-koenig-editor.hbs +remove|ember-template-lint|no-passed-in-event-handlers|26|12|26|12|4ad1176b9a4bb23d79114735b478240166113502|1658102400000|1668474000000|1673658000000|app/components/gh-koenig-editor.hbs +remove|ember-template-lint|no-passed-in-event-handlers|28|12|28|12|7ffe0a55c81efcfcd46880a930fa69bee73835b3|1658102400000|1668474000000|1673658000000|app/components/gh-koenig-editor.hbs diff --git a/ghost/admin/app/components/gh-koenig-editor-react.hbs b/ghost/admin/app/components/gh-koenig-editor-react.hbs new file mode 100644 index 0000000000..32c4bf0a0d --- /dev/null +++ b/ghost/admin/app/components/gh-koenig-editor-react.hbs @@ -0,0 +1,59 @@ +
+ {{!-- full height content pane --}} + {{!-- template-lint-disable no-down-event-binding no-invalid-interactive no-passed-in-event-handlers --}} +
+ + + + + +
+
\ No newline at end of file diff --git a/ghost/admin/app/components/gh-koenig-editor-react.js b/ghost/admin/app/components/gh-koenig-editor-react.js new file mode 100644 index 0000000000..30cf3684f6 --- /dev/null +++ b/ghost/admin/app/components/gh-koenig-editor-react.js @@ -0,0 +1,154 @@ +import Component from '@glimmer/component'; +import {action} from '@ember/object'; +import {tracked} from '@glimmer/tracking'; + +export default class GhKoenigEditorReactComponent extends Component { + containerElement = null; + titleElement = null; + koenigEditor = null; + mousedownY = 0; + + @tracked titleIsHovered = false; + @tracked titleIsFocused = false; + + get title() { + return this.args.title === '(Untitled)' ? '' : this.args.title; + } + + @action + registerElement(element) { + this.containerElement = element; + } + + @action + trackMousedown(event) { + // triggered when a mousedown is registered on .gh-koenig-editor-pane + this.mousedownY = event.clientY; + } + + // Title actions ----------------------------------------------------------- + + @action + registerTitleElement(element) { + this.titleElement = element; + + // this is needed because focus event handler won't be fired if input has focus when rendering + if (this.titleElement === document.activeElement) { + this.titleIsFocused = true; + } + } + + @action + updateTitle(event) { + this.args.onTitleChange?.(event.target.value); + } + + @action + focusTitle() { + this.titleElement.focus(); + } + + @action + onTitleKeydown(event) { + let value = event.target.value; + let selectionStart = event.target.selectionStart; + + // enter will always focus the editor + // down arrow will only focus the editor when the cursor is at the + // end of the input to preserve the default OS behaviour + if ( + event.key === 'Enter' || + event.key === 'Tab' || + ((event.key === 'ArrowDown' || event.key === 'ArrowRight') && (!value || selectionStart === value.length)) + ) { + event.preventDefault(); + + // on Enter we also want to create a blank para if necessary + if (event.key === 'Enter') { + this._addParaAtTop(); + } + + this.koenigEditor.focus(); + } + } + + // Body actions ------------------------------------------------------------ + + @action + onEditorCreated(koenig) { + this._setupEditor(koenig); + this.args.onEditorCreated?.(koenig); + } + + @action + focusEditor(event) { + if (event.target.classList.contains('gh-koenig-editor-pane')) { + let editorCanvas = this.koenigEditor.element; + let {bottom} = editorCanvas.getBoundingClientRect(); + + // if a mousedown and subsequent mouseup occurs below the editor + // canvas, focus the editor and put the cursor at the end of the + // document + if (this.mousedownY > bottom && event.clientY > bottom) { + let {post} = this.koenigEditor; + let range = post.toRange(); + let {tailSection} = range; + + event.preventDefault(); + this.koenigEditor.focus(); + + // we should always have a visible cursor when focusing + // at the bottom so create an empty paragraph if last + // section is a card + if (tailSection.isCardSection) { + this.koenigEditor.run((postEditor) => { + let newSection = postEditor.builder.createMarkupSection('p'); + postEditor.insertSectionAtEnd(newSection); + tailSection = newSection; + }); + } + + this.koenigEditor.selectRange(tailSection.tailPosition()); + + // ensure we're scrolled to the bottom + this.containerElement.scrollTop = this.containerElement.scrollHeight; + } + } + } + + _setupEditor(koenig) { + let component = this; + + this.koenigEditor = koenig.editor; + + // focus the title when pressing SHIFT+TAB + this.koenigEditor.registerKeyCommand({ + str: 'SHIFT+TAB', + run() { + component.focusTitle(); + return true; + } + }); + } + + _addParaAtTop() { + if (!this.koenigEditor) { + return; + } + + let editor = this.koenigEditor; + let section = editor.post.toRange().head.section; + + // create a blank paragraph at the top of the editor unless it's already + // a blank paragraph + if (section.isListItem || !section.isBlank || section.text !== '') { + editor.run((postEditor) => { + let {builder} = postEditor; + let newPara = builder.createMarkupSection('p'); + let sections = section.isListItem ? section.parent.parent.sections : section.parent.sections; + + postEditor.insertSectionBefore(sections, newPara, section); + }); + } + } +} diff --git a/ghost/admin/app/components/gh-koenig-editor.hbs b/ghost/admin/app/components/gh-koenig-editor.hbs index f2df4652ee..32c4bf0a0d 100644 --- a/ghost/admin/app/components/gh-koenig-editor.hbs +++ b/ghost/admin/app/components/gh-koenig-editor.hbs @@ -1,5 +1,6 @@
{{!-- full height content pane --}} + {{!-- template-lint-disable no-down-event-binding no-invalid-interactive no-passed-in-event-handlers --}}