UNPKG

@botonic/plugin-contentful

Version:

Botonic Plugin Contentful is one of the **[available](https://github.com/hubtype/botonic/tree/master/packages)** plugins for Botonic. **[Contentful](http://www.contentful.com)** is a CMS (Content Management System) which manages contents of a great variet

424 lines 13.3 kB
import { shallowClone } from '../util/objects'; import { ContentCallback, ContentId, TopContentId } from './callback'; import { ContentType, CustomContentType, } from './cms'; export var ButtonStyle; (function (ButtonStyle) { ButtonStyle[ButtonStyle["BUTTON"] = 0] = "BUTTON"; ButtonStyle[ButtonStyle["QUICK_REPLY"] = 1] = "QUICK_REPLY"; })(ButtonStyle || (ButtonStyle = {})); /** Not a Content because it cannot have custom fields */ export class Asset { /** * @param details depends on the type. eg the image size */ constructor(id, url, info, details) { this.id = id; this.url = url; this.info = info; this.details = details; } } /** * Any content deliverable from a CMS. * They are immutable, which allows sharing and caching them. */ export class Content { constructor(contentType) { this.contentType = contentType; } /** @return error message if any issue detected */ validate() { return undefined; } get contentId() { return new ContentId(this.contentType, this.id); } toString() { return `'${this.id}/${this.name}'`; } static validateContents(contents) { const validations = contents.map(c => { const validation = c.validate(); if (!validation) { return validation; } return `${c.contentType} ${c.toString()}: ${validation}`; }); return this.mergeValidations(validations); } static mergeValidations(validations) { validations = validations.filter(v => v); if (validations.length == 0) { return undefined; } return validations.join('. '); } } /** * A self-contained content with {@link CommonFields} */ export class TopContent extends Content { constructor(common, contentType) { super(contentType); this.common = common; this.contentType = contentType; } get name() { return this.common.name; } get id() { return this.common.id; } get contentId() { return new TopContentId(this.contentType, this.id); } isSearchable() { return this.common.keywords.length > 0 || Boolean(this.common.searchableBy); } } /** * Contents which have a correspondent Botonic React Message */ export class MessageContent extends TopContent { constructor(common, contentType) { super(common, contentType); this.common = common; this.contentType = contentType; } findRecursively(predicate) { if (predicate(this)) { return this; } if (!this.common.followUp) { return undefined; } return this.common.followUp.findRecursively(predicate); } cloneWithFollowUp(newFollowUp) { const clone = shallowClone(this); clone.common = shallowClone(clone.common); clone.common.followUp = newFollowUp; return clone; } cloneWithFollowUpLast(lastContent) { if (!this.common.followUp) { return this.cloneWithFollowUp(lastContent); } const followUp = this.common.followUp.cloneWithFollowUpLast(lastContent); return this.cloneWithFollowUp(followUp); } validate() { // shortText only validated when it's searchable, since // it's only required so far to show text on buttons which // refer to this content if (this.isSearchable() && !this.common.shortText) { return `is searchable but has no shortText`; } return undefined; } } /** * When any {@link keywords} is detected on a user input, we can use display the {@link shortText} for users * to confirm their interest on this content */ export class CommonFields { constructor(id, name, opt) { this.id = id; this.name = name; if (opt) { this.shortText = opt.shortText || ''; this.keywords = opt.keywords || []; this.searchableBy = opt.searchableBy; this.partitions = opt.partitions || []; this.dateRange = opt.dateRange; this.followUp = opt.followUp; } else { this.shortText = ''; this.keywords = []; this.partitions = []; } this.customFields = (opt === null || opt === void 0 ? void 0 : opt.customFields) || {}; } toString() { return `'${this.id}/${this.name}'`; } } /** * In CMS, there are 2 options to store the buttons: * 1) To have direct references to the target content opened by the button * 2) To have intermediate button contents which reference * the target and allow the button to have a text different than the target content's * shortText. * * Option 1 has these advantages: * - The contents also needs the shortText to display a button to disambiguate when * the bot does NLU based on the content keywords. * - It simplifies adding and removing a button (it's only a link to another content) * - It avoids duplication of texts (specially for targets with many sources and many languages) * - It simplifies the i18n (the text of the button is more related to the target content that to the source one) */ export class Button extends Content { /** * @param id If content having the button has a direct reference to the target * content instead of a Button content, the id will be the id of the target. * If content having the button has a reference to a Button content, id will * be the id of the Button content */ constructor(id, name, text, callback) { super(ContentType.BUTTON); this.id = id; this.name = name; this.callback = callback; this.customFields = {}; this.usingNameForText = !text; this.text = text || this.name; } validate() { if (this.usingNameForText) { return this.name ? `without short text. Using instead 'name' field. ` : `without short text nor name.`; } return undefined; } isDirectReferenceToTarget() { return (this.callback instanceof ContentCallback && this.callback.id == this.id); } toString() { if (this.isDirectReferenceToTarget()) { return `to ${this.callback.toString()}`; } return `${super.toString()} to content ${this.callback.toString()}`; } cloneWithText(newText) { const clone = shallowClone(this); clone.text = newText; return clone; } } export class Custom extends Content { constructor(id, name, fields = {}) { super(CustomContentType.CUSTOM); this.id = id; this.name = name; this.fields = fields; } } export class StartUp extends MessageContent { constructor(common, imgUrl, text, buttons) { super(common, ContentType.STARTUP); this.common = common; this.imgUrl = imgUrl; this.text = text; this.buttons = buttons; } validate() { const noText = !this.text ? `has no text` : undefined; const noButtonsNoFollowUp = !this.buttons.length && !this.common.followUp ? 'has no buttons nor follow up' : undefined; return Content.mergeValidations([ noText, noButtonsNoFollowUp, Content.validateContents(this.buttons), ]); } cloneWithText(newText) { const clone = shallowClone(this); clone.text = newText; return clone; } cloneWithButtons(buttons) { const clone = shallowClone(this); clone.buttons = buttons; return clone; } } export class Carousel extends MessageContent { constructor(common, elements = []) { super(common, ContentType.CAROUSEL); this.common = common; this.elements = elements; } validate() { if (this.elements.length == 0) { return 'has no elements'; } return Content.validateContents(this.elements); } cloneWithElements(elements) { const clone = shallowClone(this); clone.elements = elements; return clone; } } /** Part of a carousel */ export class Element extends Content { constructor(id, buttons, title, subtitle = '', imgUrl = '') { super(ContentType.ELEMENT); this.id = id; this.buttons = buttons; this.title = title; this.subtitle = subtitle; this.imgUrl = imgUrl; if (!title && !subtitle && !imgUrl) { // TODO throw an exception when CsvExport is fixed (@see IgnoreFallbackDecorator) console.error(`Element '${id}' should have title, subtitle or image URL`); } this.name = title || ''; } validate() { return Content.validateContents(this.buttons); } cloneWithButtons(buttons) { const clone = shallowClone(this); clone.buttons = buttons; return clone; } } export class Document extends MessageContent { constructor(common, docUrl) { super(common, ContentType.DOCUMENT); this.common = common; this.docUrl = docUrl; } } export class Image extends MessageContent { constructor(common, imgUrl) { super(common, ContentType.IMAGE); this.common = common; this.imgUrl = imgUrl; } } export class Video extends MessageContent { constructor(common, videoUrl) { super(common, ContentType.VIDEO); this.common = common; this.videoUrl = videoUrl; } } export class Text extends MessageContent { constructor(common, // Full text text, buttons, buttonsStyle) { super(common, ContentType.TEXT); this.common = common; this.text = text; this.buttons = buttons; this.buttonsStyle = buttonsStyle; } validate() { const noText = !this.text ? `has no text` : undefined; return Content.mergeValidations([ noText, super.validate(), Content.validateContents(this.buttons), ]); } cloneWithButtons(buttons) { const clone = shallowClone(this); clone.buttons = buttons; return clone; } cloneWithText(newText) { const clone = shallowClone(this); clone.text = newText; return clone; } } export const Chitchat = Text; export class Url extends TopContent { constructor(common, url) { super(common, ContentType.URL); this.common = common; this.url = url; } } export class Payload extends TopContent { constructor(common, payload) { super(common, ContentType.PAYLOAD); this.common = common; this.payload = payload; } } export class Queue extends TopContent { constructor(common, queue, schedule, handoffMessage) { super(common, ContentType.QUEUE); this.common = common; this.queue = queue; this.schedule = schedule; this.handoffMessage = handoffMessage; } } export class DateRangeContent extends TopContent { constructor(common, dateRange) { super(common, ContentType.DATE_RANGE); this.common = common; this.dateRange = dateRange; } } export class ScheduleContent extends TopContent { constructor(common, schedule) { super(common, ContentType.SCHEDULE); this.common = common; this.schedule = schedule; } } export class HandoffAgentEmail { constructor(agentEmail) { this.agentEmail = agentEmail; this.type = 'AGENT_EMAIL'; } } export class HandoffAgentId { constructor(agentId) { this.agentId = agentId; this.type = 'AGENT_ID'; } } /** * Most CommonFields make no sense for Handoff. * However, we decided to make it a TopContent since it does not depend on other content. * Also CommonFields might be potentially useful. */ export class Handoff extends TopContent { constructor(common, onFinish, message, failMessage, //agent and queue are optional because often they are set dynamically by the bot queue, agent, shadowing) { super(common, ContentType.HANDOFF); this.common = common; this.onFinish = onFinish; this.message = message; this.failMessage = failMessage; this.queue = queue; this.agent = agent; this.shadowing = shadowing; } cloneWithQueue(newQueue) { const clone = shallowClone(this); clone.queue = newQueue; return clone; } cloneWithAgent(newAgent) { const clone = shallowClone(this); clone.agent = newAgent; return clone; } } export var InputType; (function (InputType) { InputType["INTENTS"] = "intents"; InputType["KEYWORDS"] = "keywords"; })(InputType || (InputType = {})); export class Input extends TopContent { constructor(common, title, keywords, target, type = InputType.KEYWORDS) { super(common, ContentType.INPUT); this.common = common; this.title = title; this.keywords = keywords; this.target = target; this.type = type; } } //# sourceMappingURL=contents.js.map