0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-02-24 23:48:13 -05:00

Added first pass of product card

refs https://github.com/TryGhost/Team/issues/1233

- adds card that contains a a title, description wrapped in a link
- this hasn't been designed yet so I didn't add any css
This commit is contained in:
Thibaut Patel 2021-11-22 12:38:46 +01:00
parent 98da36ce2a
commit 0285f02fb6
4 changed files with 202 additions and 2 deletions

View file

@ -0,0 +1,85 @@
<KoenigCard
@env={{@env}}
@class={{concat (kg-style "container-card") " kg-button-card mih10 miw-100 relative"}}
@headerOffset={{@headerOffset}}
@toolbar={{this.toolbar}}
@payload={{@payload}}
@isSelected={{@isSelected}}
@isEditing={{@isEditing}}
@selectCard={{@selectCard}}
@deselectCard={{@deselectCard}}
@editCard={{@editCard}}
@hasEditMode={{true}}
@saveCard={{@saveCard}}
@saveAsSnippet={{@saveAsSnippet}}
@onLeaveEdit={{this.leaveEditMode}}
@addParagraphAfterCard={{@addParagraphAfterCard}}
@moveCursorToPrevSection={{@moveCursorToPrevSection}}
@moveCursorToNextSection={{@moveCursorToNextSection}}
@editor={{@editor}}
{{did-insert this.registerElement}}
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>
<KoenigSettingsPanel>
<div class="kg-settings-panel-control">
<label class="kg-settings-panel-control-label" for="product-title-input">Product 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")}}
>
</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>
<div class="kg-settings-panel-control-input">
<input
type="text"
class="gh-input"
id="product-url-input"
name="product-url"
value={{@payload.productUrl}}
placeholder="Add product url"
{{on "input" this.setProductUrl}}
{{on-key "Enter" (fn this.focusElement "#product-url-input")}}
>
</div>
</div>
</KoenigSettingsPanel>
{{else}}
<a href={{@payload.productUrl}} target="_blank" rel="noopener noreferrer">
<div><p>{{@payload.productTitle}}</p></div>
<div><p>{{@payload.productDescription}}</p></div>
</a>
{{/if}}
</KoenigCard>

View file

@ -0,0 +1,102 @@
import Component from '@glimmer/component';
import {action} from '@ember/object';
import {isBlank} from '@ember/utils';
import {run} from '@ember/runloop';
import {inject as service} from '@ember/service';
import {set} from '@ember/object';
export default class KoenigCardProductComponent extends Component {
@service config;
@service feature;
@service store;
@service membersUtils;
@service ui;
get isEmpty() {
const {productTitle, productDescription, productUrl} = this.args.payload;
return isBlank(productTitle) && isBlank(productDescription) && isBlank(productUrl);
}
get isIncomplete() {
const {productTitle, productDescription, productUrl} = this.args.payload;
return isBlank(productTitle) || isBlank(productDescription) || isBlank(productUrl);
}
get toolbar() {
if (this.args.isEditing) {
return false;
}
return {
items: [{
buttonClass: 'fw4 flex items-center white',
icon: 'koenig/kg-edit',
iconClass: 'fill-white',
title: 'Edit',
text: '',
action: run.bind(this, this.args.editCard)
}]
};
}
constructor() {
super(...arguments);
this.args.registerComponent(this);
const payloadDefaults = {};
Object.entries(payloadDefaults).forEach(([key, value]) => {
if (this.args.payload[key] === undefined) {
this._updatePayloadAttr(key, value);
}
});
}
// required for snippet rects to be calculated - editor reaches in to component,
// expecting a non-Glimmer component with a .element property
@action
registerElement(element) {
this.element = element;
}
@action
setProductTitle(event) {
this._updatePayloadAttr('productTitle', event.target.value);
}
@action
setProductDescription(event) {
this._updatePayloadAttr('productDescription', event.target.value);
}
@action
setProductUrl(event) {
this._updatePayloadAttr('productUrl', event.target.value);
}
@action
leaveEditMode() {
if (this.isEmpty) {
// afterRender is required to avoid double modification of `isSelected`
// TODO: see if there's a way to avoid afterRender
run.scheduleOnce('afterRender', this, this.args.deleteCard);
}
}
@action
focusElement(selector, event) {
event.preventDefault();
document.querySelector(selector)?.focus();
}
_updatePayloadAttr(attr, value) {
let payload = this.args.payload;
set(payload, attr, value);
// update the mobiledoc and stay in edit mode
this.args.saveCard?.(payload, false);
}
}

View file

@ -20,7 +20,8 @@ export const CARD_COMPONENT_MAP = {
paywall: 'koenig-card-paywall',
file: 'koenig-card-file',
audio: 'koenig-card-audio',
video: 'koenig-card-video'
video: 'koenig-card-video',
product: 'koenig-card-product'
};
// map card names to generic icons (used for ghost elements when dragging)
@ -43,7 +44,8 @@ export const CARD_ICON_MAP = {
paywall: 'koenig/kg-card-type-divider',
file: 'koenig/kg-card-type-divider',
audio: 'koenig/kg-card-type-divider',
video: 'koenig/kg-card-type-video'
video: 'koenig/kg-card-type-video',
product: 'koenig/kg-card-type-product'
};
// TODO: move koenigOptions directly into cards now that card components register
@ -67,6 +69,7 @@ export default [
createComponentCard('file'),
createComponentCard('audio'),
createComponentCard('video'),
createComponentCard('product'),
createComponentCard('paywall', {hasEditMode: false, selectAfterInsert: false})
];
@ -225,6 +228,15 @@ export const CARD_MENU = [
type: 'card',
replaceArg: 'video',
isAvailable: 'feature.videoCard'
},
{
label: 'Product',
icon: 'koenig/kg-card-type-other',
desc: 'Add a product recommendation',
matches: ['product'],
type: 'card',
replaceArg: 'product',
isAvailable: 'feature.productCard'
}]
},
{

View file

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