diff --git a/ghost/admin/app/services/feature.js b/ghost/admin/app/services/feature.js index 8ed5218214..3326185fc9 100644 --- a/ghost/admin/app/services/feature.js +++ b/ghost/admin/app/services/feature.js @@ -59,7 +59,6 @@ export default Service.extend({ oauthLogin: feature('oauthLogin', {developer: true}), emailOnlyPosts: feature('emailOnlyPosts', {developer: true}), dashboardTwo: feature('dashboardTwo', {developer: true}), - snippetReplacements: feature('snippetReplacements', {developer: true}), _user: null, diff --git a/ghost/admin/app/templates/settings/labs.hbs b/ghost/admin/app/templates/settings/labs.hbs index 00927b121a..f1d7aa74ea 100644 --- a/ghost/admin/app/templates/settings/labs.hbs +++ b/ghost/admin/app/templates/settings/labs.hbs @@ -309,19 +309,6 @@ -
-
-
-

Snippet replacements

-

- When creating a snippet, allow for replacing an existing snippet. -

-
-
- -
-
-
{{/if}} diff --git a/ghost/admin/lib/koenig-editor/addon/components/koenig-editor.hbs b/ghost/admin/lib/koenig-editor/addon/components/koenig-editor.hbs index 230848ce3a..8bae5cdbd0 100644 --- a/ghost/admin/lib/koenig-editor/addon/components/koenig-editor.hbs +++ b/ghost/admin/lib/koenig-editor/addon/components/koenig-editor.hbs @@ -37,25 +37,15 @@ {{!-- pop-up snippet editing toolbar --}} {{#if this.snippetRange}} - {{#if (feature "snippetReplacements")}} - - {{else}} - - {{/if}} + {{/if}} {{!-- (+) icon and pop-up menu --}} diff --git a/ghost/admin/lib/koenig-editor/addon/components/koenig-snippet-input-labs.hbs b/ghost/admin/lib/koenig-editor/addon/components/koenig-snippet-input-labs.hbs deleted file mode 100644 index 12b52fe4a7..0000000000 --- a/ghost/admin/lib/koenig-editor/addon/components/koenig-snippet-input-labs.hbs +++ /dev/null @@ -1,25 +0,0 @@ -
- - {{#if snippet.__isSuggestion__}} - {{snippet}} - {{else}} -
- {{snippet.name}} - {{svg-jar "sync"}} -
- {{/if}} -
-
\ No newline at end of file diff --git a/ghost/admin/lib/koenig-editor/addon/components/koenig-snippet-input-labs.js b/ghost/admin/lib/koenig-editor/addon/components/koenig-snippet-input-labs.js deleted file mode 100644 index 22b3c4fd51..0000000000 --- a/ghost/admin/lib/koenig-editor/addon/components/koenig-snippet-input-labs.js +++ /dev/null @@ -1,222 +0,0 @@ -import Component from '@glimmer/component'; -import getScrollParent from '../utils/get-scroll-parent'; -import {TOOLBAR_MARGIN} from './koenig-toolbar'; -import {action} from '@ember/object'; -import {guidFor} from '@ember/object/internals'; -import {htmlSafe} from '@ember/template'; -import {run} from '@ember/runloop'; -import {inject as service} from '@ember/service'; -import {tracked} from '@glimmer/tracking'; - -// pixels that should be added to the `left` property of the tick adjustment styles -// TODO: handle via CSS? -const TICK_ADJUSTMENT = 8; - -export default class KoenigSnippetInputLabsComponent extends Component { - @service koenigUi; - - @tracked name = ''; - @tracked style = htmlSafe(''); - - constructor() { - super(...arguments); - - // hide any other toolbars - this.koenigUi.inputHasFocus = true; - - // record the range now because the property is bound and will update - // when the selection changes - this._snippetRange = this.args.snippetRange; - - // grab a window range so that we can use getBoundingClientRect. Using - // document.createRange is more efficient than doing editor.setRange - // because it doesn't trigger all of the selection changing side-effects - // TODO: extract MobiledocRange->NativeRange into a util - let editor = this.args.editor; - let cursor = editor.cursor; - let {head, tail} = this.args.snippetRange; - let {node: headNode, offset: headOffset} = cursor._findNodeForPosition(head); - let {node: tailNode, offset: tailOffset} = cursor._findNodeForPosition(tail); - let range = document.createRange(); - range.setStart(headNode, headOffset); - range.setEnd(tailNode, tailOffset); - this._windowRange = range; - - // watch the window for mousedown events so that we can close the menu - // when we detect a click outside - this._onMousedownHandler = run.bind(this, this._handleMousedown); - window.addEventListener('mousedown', this._onMousedownHandler); - - // watch for keydown events so that we can close the menu on Escape - this._onKeydownHandler = run.bind(this, this._handleKeydown); - window.addEventListener('keydown', this._onKeydownHandler); - - this.scrollParent = getScrollParent(editor.element); - this.scrollTop = this.scrollParent.scrollTop; - } - - get snippetMobiledoc() { - let {snippetRange, editor} = this.args; - return editor.serializePost(editor.post.trimTo(snippetRange), 'mobiledoc'); - } - - willDestroy() { - super.willDestroy?.(...arguments); - this.koenigUi.inputHasFocus = false; - window.removeEventListener('mousedown', this._onMousedownHandler); - window.removeEventListener('keydown', this._onKeydownHandler); - this._removeStyleElement(); - } - - @action - selectSnippet(snippetName) { - const snippetNameLC = snippetName.trim().toLowerCase(); - const existingSnippet = this.args.snippets.find(snippet => snippet.name.toLowerCase() === snippetNameLC); - - if (existingSnippet) { - this.replaceSnippet(existingSnippet); - } else { - this.createSnippet(snippetName); - } - } - - createSnippet(name) { - this.args.save({ - name, - mobiledoc: this.snippetMobiledoc - }).then(() => { - this.args.cancel(); - }); - } - - replaceSnippet(snippet) { - this.args.update( - snippet, - {mobiledoc: this.snippetMobiledoc} - ); - - // close the snippet input - this.args.cancel(); - } - - @action - nameKeydown(event) { - if (event.key === 'Enter') { - // prevent Enter from triggering in the editor and removing text - event.preventDefault(); - - // convert selection into a mobiledoc document - let {snippetRange, editor} = this.args; - let mobiledoc = editor.serializePost(editor.post.trimTo(snippetRange), 'mobiledoc'); - - this.args.save({ - name: event.target.value, - mobiledoc - }).then(() => { - this.args.cancel(); - }); - } - } - - @action - nameInput(name) { - this.name = name; - } - - // TODO: largely shared with {{koenig-toolbar}} and {{koenig-link-input}} - extract to a shared util? - @action - registerAndPositionElement(element) { - this.scrollParent.scrollTop = this.scrollTop; - - element.id = guidFor(element); - this.element = element; - - let containerRect = this.element.offsetParent.getBoundingClientRect(); - let rangeRect = this.args.snippetRect || this._windowRange.getBoundingClientRect(); - let {width, height} = this.element.getBoundingClientRect(); - let newPosition = {}; - - // rangeRect is relative to the viewport so we need to subtract the - // container measurements to get a position relative to the container - newPosition = { - top: rangeRect.top - containerRect.top - height - TOOLBAR_MARGIN, - left: rangeRect.left - containerRect.left + rangeRect.width / 2 - width / 2, - right: null - }; - - let tickPosition = 50; - // don't overflow left boundary - if (newPosition.left < 0) { - newPosition.left = 0; - - // calculate the tick percentage position - let absTickPosition = rangeRect.left - containerRect.left + rangeRect.width / 2; - tickPosition = absTickPosition / width * 100; - if (tickPosition < 5) { - tickPosition = 5; - } - } - // same for right boundary - if (newPosition.left + width > containerRect.width) { - newPosition.left = null; - newPosition.right = 0; - - // calculate the tick percentage position - let absTickPosition = rangeRect.right - containerRect.right - rangeRect.width / 2; - tickPosition = 100 + absTickPosition / width * 100; - if (tickPosition > 95) { - tickPosition = 95; - } - } - - // the tick is a pseudo-element so we the only way we can affect it's - // style is by adding a style element to the head - this._removeStyleElement(); // reset to base styles - if (tickPosition !== 50) { - this._addStyleElement(`left: calc(${tickPosition}% - ${TICK_ADJUSTMENT}px)`); - } - - // update the toolbar position - this.style = htmlSafe(Object.keys(newPosition).map((style) => { - if (newPosition[style] !== null) { - return `${style}: ${newPosition[style]}px`; - } - }).compact().join('; ')); - } - - _handleMousedown(event) { - const isOutsideElement = this.element && !event.target.closest(this.element.id); - const isOutsideDropdown = !event.target.closest('.ember-basic-dropdown-content'); - - if (isOutsideElement && isOutsideDropdown) { - this.args.cancel(); - } - } - - _handleKeydown(event) { - if (event.key === 'Escape') { - this._cancelAndReselect(); - } - } - - _cancelAndReselect() { - this.args.cancel(); - if (this._snippetRange) { - this.args.editor.selectRange(this._snippetRange); - } - } - - _addStyleElement(styles) { - let styleElement = document.createElement('style'); - styleElement.id = `${this.element.id}-style`; - styleElement.innerHTML = `#${this.element.id}:before, #${this.element.id}:after { ${styles} }`; - document.head.appendChild(styleElement); - } - - _removeStyleElement() { - let styleElement = document.querySelector(`#${this.element.id}-style`); - if (styleElement) { - styleElement.remove(); - } - } -} diff --git a/ghost/admin/lib/koenig-editor/addon/components/koenig-snippet-input.hbs b/ghost/admin/lib/koenig-editor/addon/components/koenig-snippet-input.hbs index c3b5de310f..12b52fe4a7 100644 --- a/ghost/admin/lib/koenig-editor/addon/components/koenig-snippet-input.hbs +++ b/ghost/admin/lib/koenig-editor/addon/components/koenig-snippet-input.hbs @@ -1,10 +1,25 @@
- + + {{#if snippet.__isSuggestion__}} + {{snippet}} + {{else}} +
+ {{snippet.name}} + {{svg-jar "sync"}} +
+ {{/if}} +
\ No newline at end of file diff --git a/ghost/admin/lib/koenig-editor/addon/components/koenig-snippet-input.js b/ghost/admin/lib/koenig-editor/addon/components/koenig-snippet-input.js index c84daf31e6..d8ec708fe4 100644 --- a/ghost/admin/lib/koenig-editor/addon/components/koenig-snippet-input.js +++ b/ghost/admin/lib/koenig-editor/addon/components/koenig-snippet-input.js @@ -50,6 +50,14 @@ export default class KoenigSnippetInputComponent extends Component { // watch for keydown events so that we can close the menu on Escape this._onKeydownHandler = run.bind(this, this._handleKeydown); window.addEventListener('keydown', this._onKeydownHandler); + + this.scrollParent = getScrollParent(editor.element); + this.scrollTop = this.scrollParent.scrollTop; + } + + get snippetMobiledoc() { + let {snippetRange, editor} = this.args; + return editor.serializePost(editor.post.trimTo(snippetRange), 'mobiledoc'); } willDestroy() { @@ -61,15 +69,34 @@ export default class KoenigSnippetInputComponent extends Component { } @action - focusInput(element) { - let scrollParent = getScrollParent(element); - let scrollTop = scrollParent.scrollTop; + selectSnippet(snippetName) { + const snippetNameLC = snippetName.trim().toLowerCase(); + const existingSnippet = this.args.snippets.find(snippet => snippet.name.toLowerCase() === snippetNameLC); - element.focus(); + if (existingSnippet) { + this.replaceSnippet(existingSnippet); + } else { + this.createSnippet(snippetName); + } + } - // reset the scroll position to avoid jumps - // TODO: why does the input focus cause a scroll to the bottom of the doc? - scrollParent.scrollTop = scrollTop; + createSnippet(name) { + this.args.save({ + name, + mobiledoc: this.snippetMobiledoc + }).then(() => { + this.args.cancel(); + }); + } + + replaceSnippet(snippet) { + this.args.update( + snippet, + {mobiledoc: this.snippetMobiledoc} + ); + + // close the snippet input + this.args.cancel(); } @action @@ -92,13 +119,15 @@ export default class KoenigSnippetInputComponent extends Component { } @action - nameInput(event) { - this.name = event.target.value; + nameInput(name) { + this.name = name; } // TODO: largely shared with {{koenig-toolbar}} and {{koenig-link-input}} - extract to a shared util? @action registerAndPositionElement(element) { + this.scrollParent.scrollTop = this.scrollTop; + element.id = guidFor(element); this.element = element; @@ -156,7 +185,10 @@ export default class KoenigSnippetInputComponent extends Component { } _handleMousedown(event) { - if (this.element && !event.target.closest(this.element.id)) { + const isOutsideElement = this.element && !event.target.closest(this.element.id); + const isOutsideDropdown = !event.target.closest('.ember-basic-dropdown-content'); + + if (isOutsideElement && isOutsideDropdown) { this.args.cancel(); } } diff --git a/ghost/admin/lib/koenig-editor/app/components/koenig-snippet-input-labs.js b/ghost/admin/lib/koenig-editor/app/components/koenig-snippet-input-labs.js deleted file mode 100644 index 28266529d3..0000000000 --- a/ghost/admin/lib/koenig-editor/app/components/koenig-snippet-input-labs.js +++ /dev/null @@ -1 +0,0 @@ -export {default} from 'koenig-editor/components/koenig-snippet-input-labs';