Koenig - Extract card caption input into component

refs https://github.com/TryGhost/Ghost/issues/9623
- add new `{{koenig-caption-input}}` component
- yield koenig-caption-input instance from the `{{koenig-card}}` component
- remove duplicated caption logic from image and embed cards
- remove placeholder when caption input has focus
This commit is contained in:
Kevin Ansfield 2018-06-12 18:08:27 +01:00
parent 231315e2f6
commit 2dc916299b
9 changed files with 174 additions and 188 deletions

View File

@ -0,0 +1,110 @@
import Component from '@ember/component';
import layout from '../templates/components/koenig-caption-input';
import {computed} from '@ember/object';
import {kgStyle} from 'ember-cli-ghost-spirit/helpers/kg-style';
import {run} from '@ember/runloop';
export default Component.extend({
tagName: 'figcaption',
classNameBindings: ['figCaptionClass'],
layout,
caption: '',
placeholder: '',
_keypressHandler: null,
_keydownHandler: null,
update() {},
onDidInsertElement() {},
figCaptionClass: computed(function () {
return `${kgStyle(['figcaption'])} w-100`;
}),
didReceiveAttrs() {
this._super(...arguments);
if (this.captureInput && !this._keypressHandler) {
this._attachHandlers();
}
if (!this.captureInput && this._keypressHandler) {
this._detachHandlers();
}
},
willDestroyElement() {
this._super(...arguments);
this._detachHandlers();
},
_attachHandlers() {
if (!this._keypressHandler) {
this._keypressHandler = run.bind(this, this._handleKeypress);
window.addEventListener('keypress', this._keypressHandler);
}
if (!this._keydownHandler) {
this._keydownHandler = run.bind(this, this._handleKeydown);
window.addEventListener('keydown', this._keydownHandler);
}
},
_detachHandlers() {
window.removeEventListener('keypress', this._keypressHandler);
window.removeEventListener('keydown', this._keydownHandler);
this._keypressHandler = null;
this._keydownHandler = null;
},
// only fires if the card is selected, moves focus to the caption input so
// that it's possible to start typing without explicitly focusing the input
_handleKeypress(event) {
let captionInput = this.element.querySelector('[name="caption"]');
if (captionInput && captionInput !== document.activeElement) {
captionInput.value = `${captionInput.value}${event.key}`;
captionInput.focus();
event.preventDefault();
}
},
// this will be fired for keydown events when the caption input is focused,
// we look for cursor movements or the enter key to defocus and trigger the
// corresponding editor behaviour
_handleKeydown(event) {
let captionInput = this.element.querySelector('[name="caption"]');
if (event.target === captionInput) {
if (event.key === 'Escape') {
captionInput.blur();
return;
}
if (event.key === 'Enter') {
captionInput.blur();
this.addParagraphAfterCard();
event.preventDefault();
return;
}
let selectionStart = captionInput.selectionStart;
let length = captionInput.value.length;
if ((event.key === 'ArrowUp' || event.key === 'ArrowLeft') && selectionStart === 0) {
captionInput.blur();
this.moveCursorToPrevSection();
event.preventDefault();
return;
}
if ((event.key === 'ArrowDown' || event.key === 'ArrowRight') && selectionStart === length) {
captionInput.blur();
this.moveCursorToNextSection();
event.preventDefault();
return;
}
}
}
});

View File

@ -26,10 +26,10 @@ export default Component.extend({
deselectCard() {}, deselectCard() {},
editCard() {}, editCard() {},
saveCard() {}, saveCard() {},
deleteCard() { }, deleteCard() {},
moveCursorToNextSection() { }, moveCursorToNextSection() {},
moveCursorToPrevSection() { }, moveCursorToPrevSection() {},
addParagraphAfterCard() { }, addParagraphAfterCard() {},
init() { init() {
this._super(...arguments); this._super(...arguments);
@ -43,19 +43,8 @@ export default Component.extend({
this._loadPayloadScript(); this._loadPayloadScript();
}, },
willDestroyElement() {
this._super(...arguments);
this._detachHandlers();
},
actions: { actions: {
onSelect() {
this._attachHandlers();
},
onDeselect() { onDeselect() {
this._detachHandlers();
if (this.payload.url && !this.payload.html && !this.hasError) { if (this.payload.url && !this.payload.html && !this.hasError) {
this.convertUrl.perform(this.payload.url); this.convertUrl.perform(this.payload.url);
} else { } else {
@ -101,74 +90,6 @@ export default Component.extend({
} }
}, },
_attachHandlers() {
if (!this._keypressHandler) {
this._keypressHandler = run.bind(this, this._handleKeypress);
window.addEventListener('keypress', this._keypressHandler);
}
if (!this._keydownHandler) {
this._keydownHandler = run.bind(this, this._handleKeydown);
window.addEventListener('keydown', this._keydownHandler);
}
},
_detachHandlers() {
window.removeEventListener('keypress', this._keypressHandler);
window.removeEventListener('keydown', this._keydownHandler);
this._keypressHandler = null;
this._keydownHandler = null;
},
// only fires if the card is selected, moves focus to the caption input so
// that it's possible to start typing without explicitly focusing the input
_handleKeypress(event) {
let captionInput = this.element.querySelector('[name="caption"]');
if (captionInput && captionInput !== document.activeElement) {
captionInput.value = `${captionInput.value}${event.key}`;
captionInput.focus();
}
},
// this will be fired for keydown events when the caption input is focused,
// we look for cursor movements or the enter key to defocus and trigger the
// corresponding editor behaviour
_handleKeydown(event) {
let captionInput = this.element.querySelector('[name="caption"]');
if (event.target === captionInput) {
if (event.key === 'Escape') {
captionInput.blur();
return;
}
if (event.key === 'Enter') {
captionInput.blur();
this.addParagraphAfterCard();
event.preventDefault();
return;
}
let selectionStart = captionInput.selectionStart;
let length = captionInput.value.length;
if ((event.key === 'ArrowUp' || event.key === 'ArrowLeft') && selectionStart === 0) {
captionInput.blur();
this.moveCursorToPrevSection();
event.preventDefault();
return;
}
if ((event.key === 'ArrowDown' || event.key === 'ArrowRight') && selectionStart === length) {
captionInput.blur();
this.moveCursorToNextSection();
event.preventDefault();
return;
}
}
},
convertUrl: task(function* (url) { convertUrl: task(function* (url) {
if (isBlank(url)) { if (isBlank(url)) {
this.deleteCard(); this.deleteCard();

View File

@ -110,11 +110,6 @@ export default Component.extend({
} }
}, },
willDestroyElement() {
this._super(...arguments);
this._detachHandlers();
},
actions: { actions: {
updateSrc(images) { updateSrc(images) {
let [image] = images; let [image] = images;
@ -125,14 +120,6 @@ export default Component.extend({
this._updatePayloadAttr('caption', caption); this._updatePayloadAttr('caption', caption);
}, },
onSelect() {
this._attachHandlers();
},
onDeselect() {
this._detachHandlers();
},
/** /**
* Opens a file selection dialog - Triggered by "Upload Image" buttons, * Opens a file selection dialog - Triggered by "Upload Image" buttons,
* searches for the hidden file input within the .gh-setting element * searches for the hidden file input within the .gh-setting element
@ -176,74 +163,6 @@ export default Component.extend({
save(payload, false); save(payload, false);
}, },
_attachHandlers() {
if (!this._keypressHandler) {
this._keypressHandler = run.bind(this, this._handleKeypress);
window.addEventListener('keypress', this._keypressHandler);
}
if (!this._keydownHandler) {
this._keydownHandler = run.bind(this, this._handleKeydown);
window.addEventListener('keydown', this._keydownHandler);
}
},
_detachHandlers() {
window.removeEventListener('keypress', this._keypressHandler);
window.removeEventListener('keydown', this._keydownHandler);
this._keypressHandler = null;
this._keydownHandler = null;
},
// only fires if the card is selected, moves focus to the caption input so
// that it's possible to start typing without explicitly focusing the input
_handleKeypress(event) {
let captionInput = this.element.querySelector('[name="caption"]');
if (captionInput && captionInput !== document.activeElement) {
captionInput.value = `${captionInput.value}${event.key}`;
captionInput.focus();
}
},
// this will be fired for keydown events when the caption input is focused,
// we look for cursor movements or the enter key to defocus and trigger the
// corresponding editor behaviour
_handleKeydown(event) {
let captionInput = this.element.querySelector('[name="caption"]');
if (event.target === captionInput) {
if (event.key === 'Escape') {
captionInput.blur();
return;
}
if (event.key === 'Enter') {
captionInput.blur();
this.addParagraphAfterCard();
event.preventDefault();
return;
}
let selectionStart = captionInput.selectionStart;
let length = captionInput.value.length;
if ((event.key === 'ArrowUp' || event.key === 'ArrowLeft') && selectionStart === 0) {
captionInput.blur();
this.moveCursorToPrevSection();
event.preventDefault();
return;
}
if ((event.key === 'ArrowDown' || event.key === 'ArrowRight') && selectionStart === length) {
captionInput.blur();
this.moveCursorToNextSection();
event.preventDefault();
return;
}
}
},
_triggerFileDialog(event) { _triggerFileDialog(event) {
let target = event && event.target || this.element; let target = event && event.target || this.element;

View File

@ -0,0 +1,10 @@
<input
placeholder={{if isFocused "" placeholder}}
value={{caption}}
type="text"
class="miw-100 tc bn form-text bg-transparent"
name="caption"
oninput={{action update value="target.value"}}
onfocus={{action (mut isFocused) true}}
onblur={{action (mut isFocused) false}}
>

View File

@ -3,13 +3,16 @@
isSelected=isSelected isSelected=isSelected
isEditing=isEditing isEditing=isEditing
selectCard=(action selectCard) selectCard=(action selectCard)
onSelect=(action "onSelect")
deselectCard=(action deselectCard) deselectCard=(action deselectCard)
onDeselect=(action "onDeselect") onDeselect=(action "onDeselect")
editCard=(action editCard) editCard=(action editCard)
toolbar=toolbar toolbar=toolbar
hasEditMode=false hasEditMode=false
showSelectedOutline=payload.html showSelectedOutline=payload.html
addParagraphAfterCard=addParagraphAfterCard
moveCursorToPrevSection=moveCursorToPrevSection
moveCursorToNextSection=moveCursorToNextSection
as |card|
}} }}
{{#if payload.html}} {{#if payload.html}}
<div class="kg-card-hover"> <div class="kg-card-hover">
@ -19,16 +22,11 @@
</div> </div>
{{#if (or isSelected payload.caption)}} {{#if (or isSelected payload.caption)}}
<figcaption class="{{kg-style "figcaption"}} w-100"> {{card.captionInput
<input caption=payload.caption
type="text" update=(action "updateCaption")
placeholder="Type caption for embed (optional)" placeholder="Type caption for embed (optional)"
class="miw-100 tc bn form-text bg-transparent" }}
name="caption"
value={{payload.caption}}
oninput={{action "updateCaption" value="target.value"}}
>
</figcaption>
{{/if}} {{/if}}
</div> </div>
{{else}} {{else}}

View File

@ -5,11 +5,13 @@
isEditing=isEditing isEditing=isEditing
selectCard=(action selectCard) selectCard=(action selectCard)
deselectCard=(action deselectCard) deselectCard=(action deselectCard)
onSelect=(action "onSelect")
onDeselect=(action "onDeselect")
editCard=(action editCard) editCard=(action editCard)
toolbar=toolbar toolbar=toolbar
hasEditMode=false hasEditMode=false
addParagraphAfterCard=addParagraphAfterCard
moveCursorToPrevSection=moveCursorToPrevSection
moveCursorToNextSection=moveCursorToNextSection
as |card|
}} }}
{{#gh-uploader {{#gh-uploader
files=files files=files
@ -51,14 +53,10 @@
{{/gh-uploader}} {{/gh-uploader}}
{{#if (or isSelected payload.caption)}} {{#if (or isSelected payload.caption)}}
<figcaption class="{{kg-style "figcaption"}} w-100"> {{card.captionInput
<input caption=payload.caption
value={{payload.caption}} update=(action "updateCaption")
type="text" placeholder="Type caption for image (optional)"
class="miw-100 tc bn form-text bg-transparent" }}
name="caption"
oninput={{action "updateCaption" value="target.value"}}
placeholder="Type caption for image (optional)">
</figcaption>
{{/if}} {{/if}}
{{/koenig-card}} {{/koenig-card}}

View File

@ -4,7 +4,12 @@
{{/sticky-element}} {{/sticky-element}}
{{/if}} {{/if}}
{{yield}} {{yield (hash captionInput=(component "koenig-caption-input"
captureInput=isSelected
addParagraphAfterCard=addParagraphAfterCard
moveCursorToPrevSection=moveCursorToPrevSection
moveCursorToNextSection=moveCursorToNextSection
))}}
{{#if toolbar}} {{#if toolbar}}
<ul data-toolbar="true" class="kg-action-bar bg-darkgrey-d1 inline-flex pa0 ma0 pl1 pr1 nl1 list br3 shadow-2 items-center absolute white sans-serif f8 fw6 tracked-2 anim-fast-bezier z-999 {{if showToolbar "" "o-0 pop-down"}}" style={{toolbarStyle}}> <ul data-toolbar="true" class="kg-action-bar bg-darkgrey-d1 inline-flex pa0 ma0 pl1 pr1 nl1 list br3 shadow-2 items-center absolute white sans-serif f8 fw6 tracked-2 anim-fast-bezier z-999 {{if showToolbar "" "o-0 pop-down"}}" style={{toolbarStyle}}>
@ -25,4 +30,4 @@
{{/if}} {{/if}}
{{/each}} {{/each}}
</ul> </ul>
{{/if}} {{/if}}

View File

@ -0,0 +1 @@
export {default} from 'koenig-editor/components/koenig-caption-input';

View File

@ -0,0 +1,24 @@
import hbs from 'htmlbars-inline-precompile';
import {describe, it} from 'mocha';
import {expect} from 'chai';
import {setupComponentTest} from 'ember-mocha';
describe('Integration: Component: koenig-caption-input', function () {
setupComponentTest('koenig-caption-input', {
integration: true
});
it.skip('renders', function () {
// Set any properties with this.set('myProperty', 'value');
// Handle any actions with this.on('myAction', function(val) { ... });
// Template block usage:
// this.render(hbs`
// {{#koenig-caption-input}}
// template content
// {{/koenig-caption-input}}
// `);
this.render(hbs`{{koenig-caption-input}}`);
expect(this.$()).to.have.length(1);
});
});