Ghost/ghost/admin/lib/koenig-editor/addon/components/koenig-card-bookmark.js
Kevin Ansfield c8839bb0a2 Added display of server-side error message when bookmark request fails
closes https://github.com/TryGhost/Ghost/issues/11212

- store error message that is received from the server and remove the url from the context string for cleaner display
2020-06-08 17:40:33 +01:00

182 lines
5.5 KiB
JavaScript

import Component from '@ember/component';
import {NO_CURSOR_MOVEMENT} from './koenig-editor';
import {computed} from '@ember/object';
import {utils as ghostHelperUtils} from '@tryghost/helpers';
import {isBlank} from '@ember/utils';
import {inject as service} from '@ember/service';
import {set} from '@ember/object';
import {task} from 'ember-concurrency';
const {countWords} = ghostHelperUtils;
export default Component.extend({
ajax: service(),
ghostPaths: service(),
// attrs
payload: null,
isSelected: false,
isEditing: false,
// internal properties
hasError: false,
// closure actions
selectCard() {},
deselectCard() {},
editCard() {},
saveCard() {},
deleteCard() {},
moveCursorToNextSection() {},
moveCursorToPrevSection() {},
addParagraphAfterCard() {},
registerComponent() {},
counts: computed('payload.{metadata,caption}', function () {
let imgCount = 0;
let wordCount = 0;
let metadata = this.payload.metadata;
let caption = this.payload.caption;
imgCount = (metadata && metadata.icon) ? (imgCount + 1) : imgCount;
imgCount = (metadata && metadata.thumbnail) ? (imgCount + 1) : imgCount;
let metadataWordCount = metadata ? (countWords(this.payload.metadata.title) + countWords(this.payload.metadata.description)) : 0;
wordCount = countWords(caption) + metadataWordCount;
return {
imageCount: imgCount,
wordCount: wordCount
};
}),
init() {
this._super(...arguments);
if (this.payload.url && !this.payload.metadata) {
this.convertUrl.perform(this.payload.url);
}
this.registerComponent(this);
},
didInsertElement() {
this._super(...arguments);
this._focusInput();
},
actions: {
onDeselect() {
if (this.payload.url && !this.payload.metadata && !this.hasError) {
this.convertUrl.perform(this.payload.url);
} else {
this._deleteIfEmpty();
}
},
updateUrl(event) {
let url = event.target.value;
set(this.payload, 'url', url);
},
urlKeydown(event) {
if (event.key === 'Enter') {
event.preventDefault();
this.convertUrl.perform(this.payload.url);
}
if (event.key === 'Escape') {
event.target.blur();
this.deleteCard();
}
},
updateCaption(caption) {
set(this.payload, 'caption', caption);
this.saveCard(this.payload, false);
},
retry() {
this.set('errorMessage', null);
this.set('hasError', false);
},
insertAsLink(options = {linkOnError: false}) {
let {range} = this.editor;
this.editor.run((postEditor) => {
let {builder} = postEditor;
let cardSection = this.env.postModel;
let p = builder.createMarkupSection('p');
let link = builder.createMarkup('a', {href: this.payload.url});
postEditor.replaceSection(cardSection, p);
postEditor.insertTextWithMarkup(p.toRange().head, this.payload.url, [link]);
// if a user is typing further on in the doc (possible if embed
// was created automatically via paste of URL) then return the
// cursor so the card->link change doesn't cause a cursor jump
if (range.headSection !== cardSection) {
postEditor.setRange(range);
}
// avoid adding an extra undo step when automatically creating
// link after an error so that an Undo after pasting a URL
// doesn't get stuck in a loop going through link->embed->link
if (options.linkOnError) {
postEditor.cancelSnapshot();
}
});
}
},
convertUrl: task(function* (url) {
if (isBlank(url)) {
this.deleteCard();
return;
}
try {
let oembedEndpoint = this.ghostPaths.url.api('oembed');
let response = yield this.ajax.request(oembedEndpoint, {
data: {
url,
type: 'bookmark'
}
});
if (!response.metadata) {
throw 'No metadata returned';
}
set(this.payload, 'linkOnError', undefined);
set(this.payload, 'metadata', response.metadata);
this.saveCard(this.payload, false);
} catch (err) {
if (this.payload.linkOnError) {
this.send('insertAsLink', {linkOnError: true});
return;
}
if (err.payload.errors && err.payload.errors[0]) {
let [firstError] = err.payload.errors;
let errorMessage = firstError.context || firstError.message;
errorMessage = errorMessage.replace(url, '').trim();
this.set('errorMessage', errorMessage);
}
this.set('hasError', true);
}
}),
_focusInput() {
let urlInput = this.element.querySelector('[name="url"]');
if (urlInput) {
urlInput.focus();
}
},
_deleteIfEmpty() {
if (isBlank(this.payload.metadata) && !this.convertUrl.isRunning && !this.hasError) {
this.deleteCard(NO_CURSOR_MOVEMENT);
}
}
});