mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-26 12:21:36 +03:00
✨ Koenig - Unsplash integration
refs https://github.com/TryGhost/Ghost/issues/9724 - standardised `{{gh-unsplash}}` actions and action arguments to better represent a generic "image source" - added `{{gh-unsplash searchTerm="ghosts"}}` parameter - added `payload` param to `card` definitions used for plus/slash menus so that default payload params can be passed to cards - added a concept of "image selectors" to image card - if a `payload.imageSelector` param is received by the card it will look it up in it's list of known selectors and display the appropriate image selection component - if the card was created with an image selector param and the image selector is closed without selecting an image then the card will be removed - delete image cards during cleanup if they were created via selector but have no src
This commit is contained in:
parent
9a81a80706
commit
4463f975e3
@ -128,8 +128,8 @@ export default Component.extend({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
addUnsplashPhoto(photo) {
|
addUnsplashPhoto({src}) {
|
||||||
this.set('url', photo.urls.regular);
|
this.set('url', src);
|
||||||
this.send('saveUrl');
|
this.send('saveUrl');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -363,11 +363,11 @@ export default Component.extend(ShortcutsMixin, {
|
|||||||
this.toggleProperty('_showUnsplash');
|
this.toggleProperty('_showUnsplash');
|
||||||
},
|
},
|
||||||
|
|
||||||
insertUnsplashPhoto(photo) {
|
insertUnsplashPhoto({src, alt, caption}) {
|
||||||
let image = {
|
let image = {
|
||||||
alt: photo.description || '',
|
alt,
|
||||||
url: photo.urls.regular,
|
url: src,
|
||||||
credit: `<small>Photo by [${photo.user.name}](${photo.user.links.html}?utm_source=ghost&utm_medium=referral&utm_campaign=api-credit) / [Unsplash](https://unsplash.com/?utm_source=ghost&utm_medium=referral&utm_campaign=api-credit)</small>`
|
credit: `<small>${caption}</small>`
|
||||||
};
|
};
|
||||||
|
|
||||||
this._insertImages([image]);
|
this._insertImages([image]);
|
||||||
|
@ -13,7 +13,7 @@ export default Component.extend({
|
|||||||
zoomed: false,
|
zoomed: false,
|
||||||
|
|
||||||
// closure actions
|
// closure actions
|
||||||
insert() {},
|
select() {},
|
||||||
zoom() {},
|
zoom() {},
|
||||||
|
|
||||||
style: computed('zoomed', function () {
|
style: computed('zoomed', function () {
|
||||||
@ -78,10 +78,10 @@ export default Component.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
insert(event) {
|
select(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
this.insert(this.get('photo'));
|
this.select(this.get('photo'));
|
||||||
},
|
},
|
||||||
|
|
||||||
zoom(event) {
|
zoom(event) {
|
||||||
|
@ -16,10 +16,11 @@ export default Component.extend(ShortcutsMixin, {
|
|||||||
shortcuts: null,
|
shortcuts: null,
|
||||||
tagName: '',
|
tagName: '',
|
||||||
zoomedPhoto: null,
|
zoomedPhoto: null,
|
||||||
|
searchTerm: null,
|
||||||
|
|
||||||
// closure actions
|
// closure actions
|
||||||
close() {},
|
close() {},
|
||||||
insert() {},
|
select() {},
|
||||||
|
|
||||||
sideNavHidden: or('ui.{autoNav,isFullScreen,showMobileMenu}'),
|
sideNavHidden: or('ui.{autoNav,isFullScreen,showMobileMenu}'),
|
||||||
|
|
||||||
@ -31,6 +32,16 @@ export default Component.extend(ShortcutsMixin, {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
didReceiveAttrs() {
|
||||||
|
this._super(...arguments);
|
||||||
|
|
||||||
|
if (this.searchTerm !== this._searchTerm) {
|
||||||
|
this.unsplash.updateSearch(this.searchTerm);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._searchTerm = this.searchTerm;
|
||||||
|
},
|
||||||
|
|
||||||
didInsertElement() {
|
didInsertElement() {
|
||||||
this._super(...arguments);
|
this._super(...arguments);
|
||||||
this._resizeCallback = bind(this, this._handleResize);
|
this._resizeCallback = bind(this, this._handleResize);
|
||||||
@ -63,9 +74,16 @@ export default Component.extend(ShortcutsMixin, {
|
|||||||
this.set('zoomedPhoto', null);
|
this.set('zoomedPhoto', null);
|
||||||
},
|
},
|
||||||
|
|
||||||
insert(photo) {
|
select(photo) {
|
||||||
this.get('unsplash').triggerDownload(photo);
|
this.get('unsplash').triggerDownload(photo);
|
||||||
this.insert(photo);
|
|
||||||
|
let selectParams = {
|
||||||
|
src: photo.urls.regular,
|
||||||
|
alt: photo.description || '',
|
||||||
|
caption: `Photo by <a href="${photo.user.links.html}?utm_source=ghost&utm_medium=referral&utm_campaign=api-credit">${photo.user.name}</a> / <a href="https://unsplash.com/?utm_source=ghost&utm_medium=referral&utm_campaign=api-credit">Unsplash</a>`
|
||||||
|
};
|
||||||
|
this.select(selectParams);
|
||||||
|
|
||||||
this.close();
|
this.close();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -54,6 +54,21 @@ export default Service.extend({
|
|||||||
return reject();
|
return reject();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
updateSearch(term) {
|
||||||
|
if (term === this.get('searchTerm')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.set('searchTerm', term);
|
||||||
|
this._reset();
|
||||||
|
|
||||||
|
if (term) {
|
||||||
|
return this.get('_search').perform(term);
|
||||||
|
} else {
|
||||||
|
return this.get('_loadNew').perform();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
retryLastRequest() {
|
retryLastRequest() {
|
||||||
return this.get('_retryLastRequest').perform();
|
return this.get('_retryLastRequest').perform();
|
||||||
},
|
},
|
||||||
@ -75,18 +90,7 @@ export default Service.extend({
|
|||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
updateSearch(term) {
|
updateSearch(term) {
|
||||||
if (term === this.get('searchTerm')) {
|
return this.updateSearch(term);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.set('searchTerm', term);
|
|
||||||
this._reset();
|
|
||||||
|
|
||||||
if (term) {
|
|
||||||
return this.get('_search').perform(term);
|
|
||||||
} else {
|
|
||||||
return this.get('_loadNew').perform();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@
|
|||||||
|
|
||||||
{{#if _showUnsplash}}
|
{{#if _showUnsplash}}
|
||||||
{{gh-unsplash
|
{{gh-unsplash
|
||||||
insert=(action "addUnsplashPhoto")
|
select=(action "addUnsplashPhoto")
|
||||||
close=(action (toggle "_showUnsplash" this))
|
close=(action (toggle "_showUnsplash" this))
|
||||||
}}
|
}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
|
|
||||||
{{#if _showUnsplash}}
|
{{#if _showUnsplash}}
|
||||||
{{gh-unsplash
|
{{gh-unsplash
|
||||||
insert=(action "insertUnsplashPhoto")
|
select=(action "insertUnsplashPhoto")
|
||||||
close=(action "toggleUnsplash")}}
|
close=(action "toggleUnsplash")}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
{{photo.user.name}}
|
{{photo.user.name}}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<a class="gh-unsplash-button" href="#" onclick={{action "insert"}}>Insert image</a>
|
<a class="gh-unsplash-button" href="#" onclick={{action "select"}}>Insert image</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -40,7 +40,7 @@
|
|||||||
{{#each unsplash.columns as |photos|}}
|
{{#each unsplash.columns as |photos|}}
|
||||||
<div class="gh-unsplash-grid-column">
|
<div class="gh-unsplash-grid-column">
|
||||||
{{#each photos as |photo|}}
|
{{#each photos as |photo|}}
|
||||||
{{gh-unsplash-photo photo=photo zoom=(action "zoomPhoto") insert=(action "insert")}}
|
{{gh-unsplash-photo photo=photo zoom=(action "zoomPhoto") select=(action "select")}}
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</div>
|
</div>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
@ -73,17 +73,12 @@
|
|||||||
|
|
||||||
{{!-- zoomed image overlay --}}
|
{{!-- zoomed image overlay --}}
|
||||||
{{#if zoomedPhoto}}
|
{{#if zoomedPhoto}}
|
||||||
<<<<<<< HEAD
|
|
||||||
<div class="absolute top-0 right-0 bottom-0 left-0 pr20 pb10 pl20 bg-white" {{action "closeZoom"}}>
|
|
||||||
{{gh-unsplash-photo photo=zoomedPhoto zoomed=true zoom=(action "closeZoom") insert=(action "insert")}}
|
|
||||||
=======
|
|
||||||
<div class="absolute flex justify-center top-0 right-0 bottom-0 left-0 pr20 pb10 pl20 bg-white overflow-hidden" {{action "closeZoom"}}>
|
<div class="absolute flex justify-center top-0 right-0 bottom-0 left-0 pr20 pb10 pl20 bg-white overflow-hidden" {{action "closeZoom"}}>
|
||||||
{{gh-unsplash-photo
|
{{gh-unsplash-photo
|
||||||
photo=zoomedPhoto
|
photo=zoomedPhoto
|
||||||
zoomed=true
|
zoomed=true
|
||||||
zoom=(action "closeZoom")
|
zoom=(action "closeZoom")
|
||||||
select=(action "select")}}
|
select=(action "select")}}
|
||||||
>>>>>>> 419884354... fixed zoom layout
|
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
|
@ -6,7 +6,7 @@ import {
|
|||||||
IMAGE_EXTENSIONS,
|
IMAGE_EXTENSIONS,
|
||||||
IMAGE_MIME_TYPES
|
IMAGE_MIME_TYPES
|
||||||
} from 'ghost-admin/components/gh-image-uploader';
|
} from 'ghost-admin/components/gh-image-uploader';
|
||||||
import {computed, set} from '@ember/object';
|
import {computed, set, setProperties} from '@ember/object';
|
||||||
import {htmlSafe} from '@ember/string';
|
import {htmlSafe} from '@ember/string';
|
||||||
import {isEmpty} from '@ember/utils';
|
import {isEmpty} from '@ember/utils';
|
||||||
import {run} from '@ember/runloop';
|
import {run} from '@ember/runloop';
|
||||||
@ -32,11 +32,21 @@ export default Component.extend({
|
|||||||
deselectCard() {},
|
deselectCard() {},
|
||||||
editCard() {},
|
editCard() {},
|
||||||
saveCard() {},
|
saveCard() {},
|
||||||
|
deleteCard() {},
|
||||||
moveCursorToNextSection() {},
|
moveCursorToNextSection() {},
|
||||||
moveCursorToPrevSection() {},
|
moveCursorToPrevSection() {},
|
||||||
addParagraphAfterCard() {},
|
addParagraphAfterCard() {},
|
||||||
registerComponent() {},
|
registerComponent() {},
|
||||||
|
|
||||||
|
imageSelector: computed('payload.imageSelector', function () {
|
||||||
|
let selector = this.payload.imageSelector;
|
||||||
|
let imageSelectors = {
|
||||||
|
unsplash: 'gh-unsplash'
|
||||||
|
};
|
||||||
|
|
||||||
|
return imageSelectors[selector];
|
||||||
|
}),
|
||||||
|
|
||||||
counts: computed('payload.{src,caption}', function () {
|
counts: computed('payload.{src,caption}', function () {
|
||||||
let wordCount = 0;
|
let wordCount = 0;
|
||||||
let imageCount = 0;
|
let imageCount = 0;
|
||||||
@ -168,6 +178,23 @@ export default Component.extend({
|
|||||||
resetSrcs() {
|
resetSrcs() {
|
||||||
this.set('previewSrc', null);
|
this.set('previewSrc', null);
|
||||||
this._updatePayloadAttr('src', null);
|
this._updatePayloadAttr('src', null);
|
||||||
|
},
|
||||||
|
|
||||||
|
selectFromImageSelector({src, caption, alt}) {
|
||||||
|
let {payload, saveCard} = this;
|
||||||
|
let imageSelector, searchTerm;
|
||||||
|
|
||||||
|
setProperties(payload, {src, caption, alt, imageSelector, searchTerm});
|
||||||
|
|
||||||
|
saveCard(payload, false);
|
||||||
|
},
|
||||||
|
|
||||||
|
closeImageSelector() {
|
||||||
|
if (!this.payload.src) {
|
||||||
|
return this.deleteCard();
|
||||||
|
}
|
||||||
|
|
||||||
|
set(this.payload, 'imageSelector', undefined);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -96,7 +96,7 @@ export default Component.extend({
|
|||||||
let range = this._editorRange;
|
let range = this._editorRange;
|
||||||
|
|
||||||
if (item.type === 'card') {
|
if (item.type === 'card') {
|
||||||
this.replaceWithCardSection(item.replaceArg, range);
|
this.replaceWithCardSection(item.replaceArg, range, item.payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._hideButton();
|
this._hideButton();
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import Component from '@ember/component';
|
import Component from '@ember/component';
|
||||||
import layout from '../templates/components/koenig-slash-menu';
|
import layout from '../templates/components/koenig-slash-menu';
|
||||||
import {CARD_MENU} from '../options/cards';
|
import {CARD_MENU} from '../options/cards';
|
||||||
|
import {assign} from '@ember/polyfills';
|
||||||
import {computed, set} from '@ember/object';
|
import {computed, set} from '@ember/object';
|
||||||
import {copy} from '@ember/object/internals';
|
import {copy} from '@ember/object/internals';
|
||||||
import {htmlSafe} from '@ember/string';
|
import {htmlSafe} from '@ember/string';
|
||||||
@ -91,7 +92,7 @@ export default Component.extend({
|
|||||||
itemClicked(item, event) {
|
itemClicked(item, event) {
|
||||||
let range = this._openRange.head.section.toRange();
|
let range = this._openRange.head.section.toRange();
|
||||||
let [, ...params] = this._query.split(/\s/);
|
let [, ...params] = this._query.split(/\s/);
|
||||||
let payload;
|
let payload = assign({}, item.payload);
|
||||||
|
|
||||||
// make sure the click doesn't propagate and get picked up by the
|
// make sure the click doesn't propagate and get picked up by the
|
||||||
// newly inserted card which can then remove itself because it
|
// newly inserted card which can then remove itself because it
|
||||||
@ -103,7 +104,6 @@ export default Component.extend({
|
|||||||
|
|
||||||
// params are order-dependent and listed in CARD_MENU for each card
|
// params are order-dependent and listed in CARD_MENU for each card
|
||||||
if (!isEmpty(item.params) && !isEmpty(params)) {
|
if (!isEmpty(item.params) && !isEmpty(params)) {
|
||||||
payload = {};
|
|
||||||
item.params.forEach((param, i) => {
|
item.params.forEach((param, i) => {
|
||||||
payload[param] = params[i];
|
payload[param] = params[i];
|
||||||
});
|
});
|
||||||
|
@ -8,7 +8,9 @@ export default [
|
|||||||
createComponentCard('embed', {hasEditMode: false, deleteIfEmpty: 'payload.html'}),
|
createComponentCard('embed', {hasEditMode: false, deleteIfEmpty: 'payload.html'}),
|
||||||
createComponentCard('hr', {hasEditMode: false, selectAfterInsert: false}),
|
createComponentCard('hr', {hasEditMode: false, selectAfterInsert: false}),
|
||||||
createComponentCard('html', {deleteIfEmpty: 'payload.html'}),
|
createComponentCard('html', {deleteIfEmpty: 'payload.html'}),
|
||||||
createComponentCard('image', {hasEditMode: false}),
|
createComponentCard('image', {hasEditMode: false, deleteIfEmpty(card) {
|
||||||
|
return card.payload.imageSelector && !card.payload.src;
|
||||||
|
}}),
|
||||||
createComponentCard('markdown', {deleteIfEmpty: 'payload.markdown'})
|
createComponentCard('markdown', {deleteIfEmpty: 'payload.markdown'})
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -89,8 +91,11 @@ export const CARD_MENU = [
|
|||||||
iconClass: 'kg-card-type-unsplash',
|
iconClass: 'kg-card-type-unsplash',
|
||||||
matches: ['unsplash'],
|
matches: ['unsplash'],
|
||||||
type: 'card',
|
type: 'card',
|
||||||
replaceArg: 'embed',
|
replaceArg: 'image',
|
||||||
params: ['url']
|
params: ['searchTerm'],
|
||||||
|
payload: {
|
||||||
|
imageSelector: 'unsplash'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Vimeo',
|
label: 'Vimeo',
|
||||||
|
@ -71,4 +71,11 @@
|
|||||||
placeholder="Type caption for image (optional)"
|
placeholder="Type caption for image (optional)"
|
||||||
}}
|
}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if imageSelector}}
|
||||||
|
{{component imageSelector
|
||||||
|
searchTerm=payload.searchTerm
|
||||||
|
select=(action "selectFromImageSelector")
|
||||||
|
close=(action "closeImageSelector")}}
|
||||||
|
{{/if}}
|
||||||
{{/koenig-card}}
|
{{/koenig-card}}
|
||||||
|
Loading…
Reference in New Issue
Block a user