mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-02-24 23:48:13 -05:00
Updated the first version of the product card
refs https://github.com/TryGhost/Team/issues/1233 - Moved to in-line editing for the title/description - Only the button is clickable. The button dissapears if the text/href isn't defined - Added an image (the image can't be deleted for now)
This commit is contained in:
parent
09cae883a8
commit
93dba3cb9a
2 changed files with 238 additions and 41 deletions
|
@ -21,44 +21,99 @@
|
|||
as |card|
|
||||
>
|
||||
{{#if @isEditing}}
|
||||
<a href={{@payload.productUrl}} target="_blank" rel="noopener noreferrer">
|
||||
<div><p>{{@payload.productTitle}}</p></div>
|
||||
<div><p>{{@payload.productDescription}}</p></div>
|
||||
</a>
|
||||
<div>
|
||||
<GhUploader
|
||||
@files={{this.files}}
|
||||
@accept={{this.imageMimeTypes}}
|
||||
@extensions={{this.imageExtensions}}
|
||||
@onStart={{action "setPreviewSrc"}}
|
||||
@onComplete={{action "updateSrc"}}
|
||||
@onFailed={{action "resetSrcs"}}
|
||||
as |uploader|
|
||||
>
|
||||
<div class="relative{{unless (or this.previewSrc @payload.productImageSrc) " bg-whitegrey-l2"}}">
|
||||
{{#if (or this.previewSrc @payload.productImageSrc)}}
|
||||
<img src={{or this.previewSrc @payload.productImageSrc}} class="{{kg-style this.kgImgStyle sidebar=this.ui.hasSideNav}}" alt={{@payload.alt}}>
|
||||
{{#if this.isDraggedOver}}
|
||||
<div class="absolute absolute--fill flex items-center bg-black-60 pe-none">
|
||||
<span class="db center sans-serif fw7 f7 white">
|
||||
Drop to replace image
|
||||
</span>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
{{#if (or uploader.errors uploader.isUploading (not @payload.productImageSrc))}}
|
||||
<div class="relative miw-100 flex items-center {{if (not this.previewSrc @payload.productImageSrc) "kg-media-placeholder ba b--whitegrey" "absolute absolute--fill bg-white-50"}}">
|
||||
{{#if uploader.errors}}
|
||||
<span class="db absolute top-0 right-0 left-0 pl2 pr2 bg-red white sans-serif f7">
|
||||
{{uploader.errors.firstObject.message}}
|
||||
</span>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.isDraggedOver}}
|
||||
<span class="db center sans-serif fw7 f7 middarkgrey">
|
||||
Drop it like it's hot 🔥
|
||||
</span>
|
||||
{{else if uploader.isUploading}}
|
||||
{{uploader.progressBar}}
|
||||
{{else if (not this.previewSrc @payload.productImageSrc)}}
|
||||
<button class="flex flex-column items-center center sans-serif fw4 f7 middarkgrey pa16 pt14 pb14 kg-image-button" onclick={{action "triggerFileDialog"}}>
|
||||
{{svg-jar this.placeholder class="kg-placeholder-image"}}
|
||||
<span class="mt2 midgrey">Click to select an image</span>
|
||||
</button>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<div style="display:none">
|
||||
<GhFileInput @multiple={{false}} @action={{uploader.setFiles}} @accept={{this.imageMimeTypes}} />
|
||||
</div>
|
||||
</GhUploader>
|
||||
<KoenigBasicHtmlInput
|
||||
@html={{@payload.productTitle}}
|
||||
@placeholder="Add a product title..."
|
||||
@class="w-100 fw4 bn bg-transparent kg-product-card-title"
|
||||
@name="title"
|
||||
@defaultTag="h4"
|
||||
@onChange={{action "setProductTitle"}}
|
||||
@didCreateEditor={{action "registerEditor"}}
|
||||
/>
|
||||
<KoenigBasicHtmlInput
|
||||
@html={{@payload.productDescription}}
|
||||
@placeholder="Add a product description..."
|
||||
@class="w-100 fw4 bn bg-transparent kg-product-card-description"
|
||||
@name="description"
|
||||
@defaultTag="p"
|
||||
@onChange={{action "setProductDescription"}}
|
||||
@didCreateEditor={{action "registerEditor"}}
|
||||
/>
|
||||
{{#if (and @payload.productButton @payload.productUrl)}}
|
||||
<div>
|
||||
<a href={{@payload.productUrl}} class="gh-btn gh-btn-accent" style="width: 100%" target="_blank" rel="noopener noreferrer"><span>{{@payload.productButton}}</span></a>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<KoenigSettingsPanel>
|
||||
<div class="kg-settings-panel-control">
|
||||
<label class="kg-settings-panel-control-label" for="product-title-input">Product title</label>
|
||||
<label class="kg-settings-panel-control-label" for="product-button-input">Button title</label>
|
||||
<div class="kg-settings-panel-control-input">
|
||||
<input
|
||||
type="text"
|
||||
class="gh-input"
|
||||
id="product-title-input"
|
||||
name="product-title"
|
||||
value={{@payload.productTitle}}
|
||||
placeholder="Add product title"
|
||||
{{on "input" this.setProductTitle}}
|
||||
{{on-key "Enter" (fn this.focusElement "#product-title-input")}}
|
||||
id="product-button-input"
|
||||
name="product-button"
|
||||
value={{@payload.productButton}}
|
||||
placeholder="Add product button"
|
||||
{{on "input" this.setProductButton}}
|
||||
{{on-key "Enter" (fn this.focusElement "#product-button-input")}}
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="kg-settings-panel-control">
|
||||
<label class="kg-settings-panel-control-label" for="product-description-input">Product description</label>
|
||||
<div class="kg-settings-panel-control-input">
|
||||
<input
|
||||
type="text"
|
||||
class="gh-input"
|
||||
id="product-description-input"
|
||||
name="product-description"
|
||||
value={{@payload.productDescription}}
|
||||
placeholder="Add product description"
|
||||
{{on "input" this.setProductDescription}}
|
||||
{{on-key "Enter" (fn this.focusElement "#product-description-input")}}
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="kg-settings-panel-control">
|
||||
<label class="kg-settings-panel-control-label" for="product-url-input">Product url</label>
|
||||
<label class="kg-settings-panel-control-label" for="product-url-input">Button url</label>
|
||||
<div class="kg-settings-panel-control-input">
|
||||
<input
|
||||
type="text"
|
||||
|
@ -76,10 +131,17 @@
|
|||
|
||||
{{else}}
|
||||
|
||||
<a href={{@payload.productUrl}} target="_blank" rel="noopener noreferrer">
|
||||
<div><p>{{@payload.productTitle}}</p></div>
|
||||
<div><p>{{@payload.productDescription}}</p></div>
|
||||
</a>
|
||||
|
||||
<div>
|
||||
{{#if @payload.productImageSrc}}
|
||||
<img src={{@payload.productImageSrc}} />
|
||||
{{/if}}
|
||||
<h4>{{@payload.productTitle}}</h4>
|
||||
<p>{{@payload.productDescription}}</p>
|
||||
{{#if (and @payload.productButton @payload.productUrl)}}
|
||||
<div>
|
||||
<a href={{@payload.productUrl}} class="gh-btn gh-btn-accent" style="width: 100%" target="_blank" rel="noopener noreferrer"><span>{{@payload.productButton}}</span></a>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</KoenigCard>
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue