diff --git a/ghost/admin/lib/koenig-editor/addon/components/koenig-card-product.hbs b/ghost/admin/lib/koenig-editor/addon/components/koenig-card-product.hbs index d7e9325d81..978f5c996d 100644 --- a/ghost/admin/lib/koenig-editor/addon/components/koenig-card-product.hbs +++ b/ghost/admin/lib/koenig-editor/addon/components/koenig-card-product.hbs @@ -21,44 +21,99 @@ as |card| > {{#if @isEditing}} - -

{{@payload.productTitle}}

-

{{@payload.productDescription}}

-
+
+ +
+ {{#if (or this.previewSrc @payload.productImageSrc)}} + {{@payload.alt}} + {{#if this.isDraggedOver}} +
+ + Drop to replace image + +
+ {{/if}} + {{/if}} + + {{#if (or uploader.errors uploader.isUploading (not @payload.productImageSrc))}} +
+ {{#if uploader.errors}} + + {{uploader.errors.firstObject.message}} + + {{/if}} + + {{#if this.isDraggedOver}} + + Drop it like it's hot 🔥 + + {{else if uploader.isUploading}} + {{uploader.progressBar}} + {{else if (not this.previewSrc @payload.productImageSrc)}} + + {{/if}} +
+ {{/if}} +
+ +
+ +
+
+ + + {{#if (and @payload.productButton @payload.productUrl)}} +
+ {{@payload.productButton}} +
+ {{/if}} +
- +
- -
- -
-
-
- +
-

{{@payload.productTitle}}

-

{{@payload.productDescription}}

- - +
+ {{#if @payload.productImageSrc}} + + {{/if}} +

{{@payload.productTitle}}

+

{{@payload.productDescription}}

+ {{#if (and @payload.productButton @payload.productUrl)}} +
+ {{@payload.productButton}} +
+ {{/if}} +
{{/if}} \ No newline at end of file diff --git a/ghost/admin/lib/koenig-editor/addon/components/koenig-card-product.js b/ghost/admin/lib/koenig-editor/addon/components/koenig-card-product.js index 95365b8317..f31b388491 100644 --- a/ghost/admin/lib/koenig-editor/addon/components/koenig-card-product.js +++ b/ghost/admin/lib/koenig-editor/addon/components/koenig-card-product.js @@ -1,4 +1,10 @@ +import $ from 'jquery'; +import Browser from 'mobiledoc-kit/utils/browser'; import Component from '@glimmer/component'; +import { + IMAGE_EXTENSIONS, + IMAGE_MIME_TYPES +} from 'ghost-admin/components/gh-image-uploader'; import {action} from '@ember/object'; import {isBlank} from '@ember/utils'; import {run} from '@ember/runloop'; @@ -12,16 +18,20 @@ export default class KoenigCardProductComponent extends Component { @service membersUtils; @service ui; - get isEmpty() { - const {productTitle, productDescription, productUrl} = this.args.payload; + files= null; + imageExtensions = IMAGE_EXTENSIONS; + imageMimeTypes = IMAGE_MIME_TYPES; - return isBlank(productTitle) && isBlank(productDescription) && isBlank(productUrl); + get isEmpty() { + const {productTitle, productDescription, productUrl, productButton, productImageSrc} = this.args.payload; + + return isBlank(productTitle) && isBlank(productDescription) && isBlank(productUrl) && isBlank(productButton) && isBlank(productImageSrc); } get isIncomplete() { - const {productTitle, productDescription, productUrl} = this.args.payload; + const {productTitle, productDescription, productUrl, productButton, productImageSrc} = this.args.payload; - return isBlank(productTitle) || isBlank(productDescription) || isBlank(productUrl); + return isBlank(productTitle) || isBlank(productDescription) || isBlank(productUrl) || isBlank(productButton) || isBlank(productImageSrc); } get toolbar() { @@ -62,13 +72,40 @@ export default class KoenigCardProductComponent extends Component { } @action - setProductTitle(event) { - this._updatePayloadAttr('productTitle', event.target.value); + registerEditor(textReplacementEditor) { + let commands = { + 'META+ENTER': run.bind(this, this._enter, 'meta'), + 'CTRL+ENTER': run.bind(this, this._enter, 'ctrl') + }; + + Object.keys(commands).forEach((str) => { + textReplacementEditor.registerKeyCommand({ + str, + run() { + return commands[str](textReplacementEditor, str); + } + }); + }); + + this._textReplacementEditor = textReplacementEditor; + + run.scheduleOnce('afterRender', this, this._afterRender); + } + + _enter(modifier) { + if (this.args.isEditing && (modifier === 'meta' || (modifier === 'crtl' && Browser.isWin()))) { + this.args.editCard(); + } } @action - setProductDescription(event) { - this._updatePayloadAttr('productDescription', event.target.value); + setProductTitle(content) { + this._updatePayloadAttr('productTitle', content); + } + + @action + setProductDescription(content) { + this._updatePayloadAttr('productDescription', content); } @action @@ -76,6 +113,11 @@ export default class KoenigCardProductComponent extends Component { this._updatePayloadAttr('productUrl', event.target.value); } + @action + setProductButton(event) { + this._updatePayloadAttr('productButton', event.target.value); + } + @action leaveEditMode() { if (this.isEmpty) { @@ -99,4 +141,97 @@ export default class KoenigCardProductComponent extends Component { // update the mobiledoc and stay in edit mode this.args.saveCard?.(payload, false); } + + _afterRender() { + this._placeCursorAtEnd(); + this._focusInput(); + } + + _placeCursorAtEnd() { + if (!this._textReplacementEditor) { + return; + } + + let tailPosition = this._textReplacementEditor.post.tailPosition(); + let rangeToSelect = tailPosition.toRange(); + this._textReplacementEditor.selectRange(rangeToSelect); + } + + _focusInput() { + let headingInput = this.element.querySelector('.kg-product-card-title .koenig-basic-html-input__editor'); + + if (headingInput) { + headingInput.focus(); + } + } + + @action + setPreviewSrc(files) { + let file = files[0]; + if (file) { + let url = URL.createObjectURL(file); + this.previewSrc = url; + + let imageElem = new Image(); + imageElem.onload = () => { + // store width/height for use later to avoid saving an image card with no `src` + this._imageWidth = imageElem.naturalWidth; + this._imageHeight = imageElem.naturalHeight; + }; + imageElem.src = url; + } + } + + @action + resetSrcs() { + // this.set('previewSrc', null); + this.previewSrc = null; + // this._imageWidth = null; + // this._imageHeight = null; + + // create undo snapshot when clearing + this.args.editor.run(() => { + this._updatePayloadAttr('productImageSrc', null); + // this._updatePayloadAttr('width', null); + // this._updatePayloadAttr('height', null); + }); + } + + @action + updateSrc(images) { + let [image] = images; + + // create undo snapshot when image finishes uploading + this.args.editor.run(() => { + this._updatePayloadAttr('productImageSrc', image.url); + if (this._imageWidth && this._imageHeight) { + // this._updatePayloadAttr('width', this._imageWidth); + // this._updatePayloadAttr('height', this._imageHeight); + } + this._imageWidth = null; + this._imageHeight = null; + }); + } + + /** + * Opens a file selection dialog - Triggered by "Upload Image" buttons, + * searches for the hidden file input within the .gh-setting element + * containing the clicked button then simulates a click + * @param {MouseEvent} event - MouseEvent fired by the button click + */ + @action + triggerFileDialog(event) { + this._triggerFileDialog(event); + } + + _triggerFileDialog(event) { + let target = event && event.target || this.element; + + // simulate click to open file dialog + // using jQuery because IE11 doesn't support MouseEvent + $(target) + .closest('.__mobiledoc-card') + .find('input[type="file"]') + .click(); + } }