From 245f4ea80eb4cc3166508d8fcddf6095b7419ebf Mon Sep 17 00:00:00 2001 From: Kevin Ansfield Date: Thu, 18 May 2017 09:01:30 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20auto-expanding=20editor=20title=20i?= =?UTF-8?q?nput=20(#699)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit closes https://github.com/TryGhost/Ghost/issues/8463 - move generic text input handling into `text-input` mixin so it applies to text inputs and textareas - adds `autoExpand` property to `gh-textarea` that accepts a selector to watch for resize changes, if the property is set then auto-expanding behaviour is triggered any time the textarea value is changed or when the selector element is resized (this prevents change in textarea width from toggling nav or split screen mode resulting in textarea content being hidden or the textarea being taller than it's contents) - adds `ember-element-resize-detector` addon to allow watching of element resizes rather than window resizes (this was already included as a sub-dependency via `ember-light-table`->`ember-scrollable`->`ember-element-resize-detector`) --- ghost/admin/app/components/gh-input.js | 11 +--- ghost/admin/app/components/gh-textarea.js | 64 ++++++++++++++++++- .../app/components/gh-trim-focus-input.js | 29 --------- ghost/admin/app/mixins/text-input.js | 46 ++++++++++++- ghost/admin/app/templates/editor/edit.hbs | 4 +- ghost/admin/package.json | 1 + 6 files changed, 111 insertions(+), 44 deletions(-) diff --git a/ghost/admin/app/components/gh-input.js b/ghost/admin/app/components/gh-input.js index cf54344a17..9ccb972cf2 100644 --- a/ghost/admin/app/components/gh-input.js +++ b/ghost/admin/app/components/gh-input.js @@ -2,14 +2,5 @@ import OneWayInput from 'ember-one-way-controls/components/one-way-input'; import TextInputMixin from 'ghost-admin/mixins/text-input'; export default OneWayInput.extend(TextInputMixin, { - classNames: 'gh-input', - - // prevent default TAB behaviour if we have a keyEvent for it - keyDown(event) { - if (event.keyCode === 9 && this.get('keyEvents.9')) { - event.preventDefault(); - } - - this._super(...arguments); - } + classNames: 'gh-input' }); diff --git a/ghost/admin/app/components/gh-textarea.js b/ghost/admin/app/components/gh-textarea.js index ed9437c8ce..4f69ce17b0 100644 --- a/ghost/admin/app/components/gh-textarea.js +++ b/ghost/admin/app/components/gh-textarea.js @@ -1,6 +1,68 @@ import OneWayTextarea from 'ember-one-way-controls/components/one-way-textarea'; import TextInputMixin from 'ghost-admin/mixins/text-input'; +import run from 'ember-runloop'; +import injectService from 'ember-service/inject'; export default OneWayTextarea.extend(TextInputMixin, { - classNames: 'gh-input' + resizeDetector: injectService(), + + classNames: 'gh-input', + + autoExpand: false, + + willInsertElement() { + this._super(...arguments); + + // disable the draggable resize element that browsers add to textareas + if (this.get('autoExpand')) { + this.element.style.resize = 'none'; + } + }, + + didInsertElement() { + this._super(...arguments); + + // set up resize handler on element insert so that we can autoexpand + // when the element container changes size + if (this.get('autoExpand')) { + run.scheduleOnce('afterRender', this, this._setupAutoExpand); + } + }, + + didReceiveAttrs() { + this._super(...arguments); + + // trigger auto-expand any time the value changes + if (this.get('autoExpand')) { + run.scheduleOnce('afterRender', this, this._autoExpand); + } + }, + + willDestroyElement() { + this._teardownAutoExpand(); + this._super(...arguments); + }, + + _autoExpand() { + let el = this.element; + + // collapse the element first so that we can shrink as well as expand + // then set the height to match the text height + el.style.height = 0; + el.style.height = `${el.scrollHeight}px`; + }, + + _setupAutoExpand() { + this._resizeCallback = run.bind(this, this._onResize); + this.get('resizeDetector').setup(this.get('autoExpand'), this._resizeCallback); + this._autoExpand(); + }, + + _onResize() { + this._autoExpand(); + }, + + _teardownAutoExpand() { + this.get('resizeDetector').teardown(this.get('autoExpand'), this._resizeCallback); + } }); diff --git a/ghost/admin/app/components/gh-trim-focus-input.js b/ghost/admin/app/components/gh-trim-focus-input.js index 5569333f90..59070f8719 100644 --- a/ghost/admin/app/components/gh-trim-focus-input.js +++ b/ghost/admin/app/components/gh-trim-focus-input.js @@ -1,5 +1,3 @@ -/* global device */ -import computed from 'ember-computed'; import GhostInput from 'ghost-admin/components/gh-input'; /** @@ -12,25 +10,6 @@ const TrimFocusInputComponent = GhostInput.extend({ shouldFocus: true, - attributeBindings: ['autofocus'], - - autofocus: computed(function () { - if (this.get('shouldFocus')) { - return (device.ios()) ? false : 'autofocus'; - } - - return false; - }), - - init() { - this._super(...arguments); - }, - - didInsertElement() { - this._super(...arguments); - this._focus(); - }, - focusOut(event) { this._trimInput(event.target.value); }, @@ -41,14 +20,6 @@ const TrimFocusInputComponent = GhostInput.extend({ } this._processNewValue(value); - }, - - _focus() { - // Until mobile safari has better support - // for focusing, we just ignore it - if (this.get('shouldFocus') && !device.ios()) { - this.element.focus(); - } } }); diff --git a/ghost/admin/app/mixins/text-input.js b/ghost/admin/app/mixins/text-input.js index 144b90ba34..2aceca01dd 100644 --- a/ghost/admin/app/mixins/text-input.js +++ b/ghost/admin/app/mixins/text-input.js @@ -1,9 +1,27 @@ +/* global device */ import Mixin from 'ember-metal/mixin'; +import computed from 'ember-computed'; export default Mixin.create({ + attributeBindings: ['autofocus'], + selectOnClick: false, + shouldFocus: false, stopEnterKeyDownPropagation: false, + autofocus: computed(function () { + if (this.get('shouldFocus')) { + return (device.ios()) ? false : 'autofocus'; + } + + return false; + }), + + didInsertElement() { + this._super(...arguments); + this._focus(); + }, + click(event) { if (this.get('selectOnClick')) { event.currentTarget.select(); @@ -12,12 +30,36 @@ export default Mixin.create({ keyDown(event) { // stop event propagation when pressing "enter" - // most useful in the case when undesired (global) keyboard shortcuts are getting triggered while interacting - // with this particular input element. + // most useful in the case when undesired (global) keyboard shortcuts + // are getting triggered while interacting with this particular input element. if (this.get('stopEnterKeyDownPropagation') && event.keyCode === 13) { event.stopPropagation(); return true; } + + // prevent default TAB behaviour if we have a keyEvent for it + if (event.keyCode === 9 && this.get('keyEvents.9')) { + event.preventDefault(); + } + + this._super(...arguments); + }, + + keyPress(event) { + // prevent default ENTER behaviour if we have a keyEvent for it + if (event.keyCode === 13 && this.get('keyEvents.13')) { + event.preventDefault(); + } + + this._super(...arguments); + }, + + _focus() { + // Until mobile safari has better support + // for focusing, we just ignore it + if (this.get('shouldFocus') && !device.ios()) { + this.element.focus(); + } } }); diff --git a/ghost/admin/app/templates/editor/edit.hbs b/ghost/admin/app/templates/editor/edit.hbs index 3be68d342a..2e096c238d 100644 --- a/ghost/admin/app/templates/editor/edit.hbs +++ b/ghost/admin/app/templates/editor/edit.hbs @@ -50,12 +50,12 @@ as |markdown| }}
- {{gh-trim-focus-input model.titleScratch - type="text" + {{gh-textarea model.titleScratch class="gh-editor-title" placeholder="Your Post Title" tabindex="1" shouldFocus=shouldFocusTitle + autoExpand=".gh-markdown-editor-pane" focus-out="updateTitle" update=(action (perform updateTitle)) keyEvents=(hash diff --git a/ghost/admin/package.json b/ghost/admin/package.json index 1330a393ef..c0dea0c7f2 100644 --- a/ghost/admin/package.json +++ b/ghost/admin/package.json @@ -67,6 +67,7 @@ "ember-concurrency": "0.8.3", "ember-data": "2.13.1", "ember-data-filter": "1.13.0", + "ember-element-resize-detector": "0.1.5", "ember-export-application-global": "2.0.0", "ember-infinity": "0.2.8", "ember-inline-svg": "0.1.11",