@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
JavaScript
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