@dialpad/dialtone
Version:
Dialpad's Dialtone design system monorepo
621 lines (620 loc) • 23.2 kB
JavaScript
import { EDITOR_SUPPORTED_LINK_PROTOCOLS, EDITOR_DEFAULT_LINK_PREFIX } from "./editor_constants.js";
import { DtIconLightningBolt, DtIconBold, DtIconItalic, DtIconUnderline, DtIconStrikethrough, DtIconListBullet, DtIconListOrdered, DtIconAlignLeft, DtIconAlignCenter, DtIconAlignRight, DtIconAlignJustify, DtIconQuote, DtIconCodeBlock, DtIconLink2 } from "@dialpad/dialtone-icons/vue2";
import normalizeComponent from "../../../_virtual/_plugin-vue2_normalizer.js";
import DtRichTextEditor from "../../../components/rich_text_editor/rich_text_editor.vue.js";
import DtButton from "../../../components/button/button.vue.js";
import DtPopover from "../../../components/popover/popover.vue.js";
import DtStack from "../../../components/stack/stack.vue.js";
import DtInput from "../../../components/input/input.vue.js";
import DtTooltip from "../../../components/tooltip/tooltip.vue.js";
import { RICH_TEXT_EDITOR_AUTOFOCUS_TYPES, RICH_TEXT_EDITOR_OUTPUT_FORMATS } from "../../../components/rich_text_editor/rich_text_editor_constants.js";
const _sfc_main = {
name: "DtRecipeEditor",
components: {
DtRichTextEditor,
DtButton,
DtPopover,
DtStack,
DtInput,
DtTooltip,
DtIconLightningBolt,
DtIconBold,
DtIconItalic,
DtIconUnderline,
DtIconStrikethrough,
DtIconListBullet,
DtIconListOrdered,
DtIconAlignLeft,
DtIconAlignCenter,
DtIconAlignRight,
DtIconAlignJustify,
DtIconQuote,
DtIconCodeBlock,
DtIconLink2
},
inheritAttrs: false,
props: {
/**
* Value of the input. The object format should match TipTap's JSON
* document structure: https://tiptap.dev/guide/output#option-1-json
*/
value: {
type: [Object, String],
default: ""
},
/**
* Whether the input is editable
*/
editable: {
type: Boolean,
default: true
},
/**
* Descriptive label for the input element
*/
inputAriaLabel: {
type: String,
required: true,
default: ""
},
/**
* Additional class name for the input element. Only accepts a String value
* because this is passed to the editor via options. For multiple classes,
* join them into one string, e.g. "d-p8 d-hmx96"
*/
inputClass: {
type: String,
default: ""
},
/**
* Whether the input should receive focus after the component has been
* mounted. Either one of `start`, `end`, `all` or a Boolean or a Number.
* - `start` Sets the focus to the beginning of the input
* - `end` Sets the focus to the end of the input
* - `all` Selects the whole contents of the input
* - `Number` Sets the focus to a specific position in the input
* - `true` Defaults to `start`
* - `false` Disables autofocus
* @values true, false, start, end, all, number
*/
autoFocus: {
type: [Boolean, String, Number],
default: false,
validator(autoFocus) {
if (typeof autoFocus === "string") {
return RICH_TEXT_EDITOR_AUTOFOCUS_TYPES.includes(autoFocus);
}
return true;
}
},
/**
* Placeholder text
*/
placeholder: {
type: String,
default: ""
},
/**
* Content area needs to dynamically adjust height based on the conversation area height.
* can be vh|px|rem|em|%
*/
maxHeight: {
type: String,
default: "unset"
},
/**
* Confirm set link button defaults.
*/
confirmSetLinkButton: {
type: Object,
default: () => ({ label: "Confirm", ariaLabel: "Confirm set link" })
},
/**
* Remove link button defaults.
*/
removeLinkButton: {
type: Object,
default: () => ({ label: "Remove", ariaLabel: "Remove link" })
},
/**
* Cancel set link button defaults.
*/
cancelSetLinkButton: {
type: Object,
default: () => ({ label: "Cancel", ariaLabel: "Cancel set link" })
},
/**
* Placeholder text for the set link input field
*/
setLinkPlaceholder: {
type: String,
default: ""
},
/**
* Show button to render text as bold
*/
showBoldButton: {
type: Boolean,
default: true
},
/**
* Show button to render text in italics
*/
showItalicsButton: {
type: Boolean,
default: true
},
/**
* Show button to underline text
*/
showUnderlineButton: {
type: Boolean,
default: true
},
/**
* Show button to strike text
*/
showStrikeButton: {
type: Boolean,
default: true
},
/**
* Show button to render list items
*/
showListItemsButton: {
type: Boolean,
default: true
},
/**
* Show button to render ordered list items
*/
showOrderedListButton: {
type: Boolean,
default: true
},
/**
* Show button to align text to the left
*/
showAlignLeftButton: {
type: Boolean,
default: true
},
/**
* Show button to align text to the center
*/
showAlignCenterButton: {
type: Boolean,
default: true
},
/**
* Show button to align text to the right
*/
showAlignRightButton: {
type: Boolean,
default: true
},
/**
* Show button to justify text
*/
showAlignJustifyButton: {
type: Boolean,
default: true
},
/**
* Show button to add quote format to text
*/
showQuoteButton: {
type: Boolean,
default: true
},
/**
* Show button to add code block
*/
showCodeBlockButton: {
type: Boolean,
default: true
},
/**
* Show button to handle quick replies
*/
showQuickRepliesButton: {
type: Boolean,
default: true
},
/**
* Show add link default config.
*/
showAddLink: {
type: Object,
default: () => ({
showAddLinkButton: true,
setLinkTitle: "Add a link",
setLinkInputAriaLabel: "Input field to add link"
})
},
/**
* Use div tags instead of paragraph tags to show text
*/
useDivTags: {
type: Boolean,
default: false
}
},
emits: [
/**
* Native focus event
* @event input
* @type {String|JSON}
*/
"focus",
/**
* Native blur event
* @event input
* @type {String|JSON}
*/
"blur",
/**
* Native input event
* @event input
* @type {String|JSON}
*/
"input",
/**
* Quick replies button
* pressed event
* @event quick-replies-click
*/
"quick-replies-click"
],
data() {
return {
internalInputValue: this.value,
// internal input content
hasFocus: false,
linkOptions: {
class: "d-recipe-editor__link"
},
showLinkInput: false,
linkInput: ""
};
},
computed: {
inputLength() {
return this.internalInputValue.length;
},
htmlOutputFormat() {
return RICH_TEXT_EDITOR_OUTPUT_FORMATS[2];
},
showingTextFormatButtons() {
return this.showBoldButton || this.showItalicsButton || this.showStrikeButton || this.showUnderlineButton;
},
showingAlignmentButtons() {
return this.showAlignLeftButton || this.showAlignCenterButton || this.showAlignRightButton || this.showAlignJustifyButton;
},
showingListButtons() {
return this.showListItemsButton || this.showOrderedListButton;
},
buttonGroups() {
const individualButtonStacks = this.individualButtons.map((buttonData) => ({
key: buttonData.selector,
buttonGroup: [buttonData]
}));
return [
{ key: "new", buttonGroup: this.newButtons },
{ key: "format", buttonGroup: this.textFormatButtons },
{ key: "alignment", buttonGroup: this.alignmentButtons },
{ key: "list", buttonGroup: this.listButtons },
...individualButtonStacks
].filter((buttonGroupData) => buttonGroupData.buttonGroup.length > 0);
},
newButtons() {
return [
{
showBtn: this.showQuickRepliesButton,
label: "Quick reply",
selector: "quickReplies",
icon: DtIconLightningBolt,
dataQA: "dt-recipe-editor-quick-replies-btn",
tooltipMessage: "Quick Reply",
onClick: this.onQuickRepliesClick
}
].filter((button) => button.showBtn);
},
textFormatButtons() {
return [
{
showBtn: this.showBoldButton,
selector: "bold",
icon: DtIconBold,
dataQA: "dt-recipe-editor-bold-btn",
tooltipMessage: "Bold",
onClick: this.onBoldTextToggle
},
{
showBtn: this.showItalicsButton,
selector: "italic",
icon: DtIconItalic,
dataQA: "dt-recipe-editor-italics-btn",
tooltipMessage: "Italics",
onClick: this.onItalicTextToggle
},
{
showBtn: this.showUnderlineButton,
selector: "underline",
icon: DtIconUnderline,
dataQA: "dt-recipe-editor-underline-btn",
tooltipMessage: "Underline",
onClick: this.onUnderlineTextToggle
},
{
showBtn: this.showStrikeButton,
selector: "strike",
icon: DtIconStrikethrough,
dataQA: "dt-recipe-editor-strike-btn",
tooltipMessage: "Strike",
onClick: this.onStrikethroughTextToggle
}
].filter((button) => button.showBtn);
},
alignmentButtons() {
return [
{
showBtn: this.showAlignLeftButton,
selector: { textAlign: "left" },
icon: DtIconAlignLeft,
dataQA: "dt-recipe-editor-align-left-btn",
tooltipMessage: "Align Left",
onClick: () => this.onTextAlign("left")
},
{
showBtn: this.showAlignCenterButton,
selector: { textAlign: "center" },
icon: DtIconAlignCenter,
dataQA: "dt-recipe-editor-align-center-btn",
tooltipMessage: "Align Center",
onClick: () => this.onTextAlign("center")
},
{
showBtn: this.showAlignRightButton,
selector: { textAlign: "right" },
icon: DtIconAlignRight,
dataQA: "dt-recipe-editor-align-right-btn",
tooltipMessage: "Align Right",
onClick: () => this.onTextAlign("right")
},
{
showBtn: this.showAlignJustifyButton,
selector: { textAlign: "justify" },
icon: DtIconAlignJustify,
dataQA: "dt-recipe-editor-align-justify-btn",
tooltipMessage: "Align Justify",
onClick: () => this.onTextAlign("justify")
}
].filter((button) => button.showBtn);
},
listButtons() {
return [
{
showBtn: this.showListItemsButton,
selector: "bulletList",
icon: DtIconListBullet,
dataQA: "dt-recipe-editor-list-items-btn",
tooltipMessage: "Bullet List",
onClick: this.onBulletListToggle
},
{
showBtn: this.showOrderedListButton,
selector: "orderedList",
icon: DtIconListOrdered,
dataQA: "dt-recipe-editor-ordered-list-items-btn",
tooltipMessage: "Ordered List",
onClick: this.onOrderedListToggle
}
].filter((button) => button.showBtn);
},
individualButtons() {
return [
{
showBtn: this.showQuoteButton,
selector: "blockquote",
icon: DtIconQuote,
dataQA: "dt-recipe-editor-blockquote-btn",
tooltipMessage: "Quote",
onClick: this.onBlockquoteToggle
},
{
showBtn: this.showCodeBlockButton,
selector: "codeBlock",
icon: DtIconCodeBlock,
dataQA: "dt-recipe-editor-code-block-btn",
tooltipMessage: "Code",
onClick: this.onCodeBlockToggle
}
].filter((button) => button.showBtn);
},
linkButton() {
return {
showBtn: this.showAddLink.showAddLinkButton,
selector: "link",
icon: DtIconLink2,
dataQA: "dt-recipe-editor-add-link-btn",
tooltipMessage: "Link",
onClick: this.openLinkInput
};
}
},
watch: {
value(newValue) {
this.internalInputValue = newValue;
}
},
methods: {
onInputFocus(event) {
event == null ? void 0 : event.stopPropagation();
},
removeLink() {
var _a, _b, _c, _d, _e;
(_e = (_d = (_c = (_b = (_a = this.$refs.richTextEditor) == null ? void 0 : _a.editor) == null ? void 0 : _b.chain()) == null ? void 0 : _c.focus()) == null ? void 0 : _d.unsetLink()) == null ? void 0 : _e.run();
this.closeLinkInput();
},
setLink(event) {
var _a, _b, _c;
const editor2 = (_a = this.$refs.richTextEditor) == null ? void 0 : _a.editor;
event == null ? void 0 : event.preventDefault();
event == null ? void 0 : event.stopPropagation();
if (!this.linkInput) {
this.removeLink();
return;
}
const prefix = EDITOR_SUPPORTED_LINK_PROTOCOLS.find((prefixRegex) => prefixRegex.test(this.linkInput));
if (!prefix) {
this.linkInput = `${EDITOR_DEFAULT_LINK_PREFIX}${this.linkInput}`;
}
const selection = (_c = (_b = editor2 == null ? void 0 : editor2.view) == null ? void 0 : _b.state) == null ? void 0 : _c.selection;
if (selection.anchor === selection.head) {
editor2.chain().focus().insertContentAt(
selection.anchor,
`<a class="${this.linkOptions.class}" href=${this.linkInput}>${this.linkInput}</a>`
).run();
} else {
editor2.chain().focus().extendMarkRange("link").setLink({ href: this.linkInput, class: this.linkOptions.class }).run();
}
this.closeLinkInput();
},
openLinkInput() {
this.showLinkInput = true;
},
updateInput(openedInput) {
var _a, _b, _c;
if (!openedInput) {
return this.closeLinkInput();
}
this.linkInput = (_c = (_b = (_a = this.$refs.richTextEditor) == null ? void 0 : _a.editor) == null ? void 0 : _b.getAttributes("link")) == null ? void 0 : _c.href;
},
closeLinkInput() {
var _a;
this.showLinkInput = false;
this.linkInput = "";
(_a = this.$refs.richTextEditor.editor) == null ? void 0 : _a.chain().focus();
},
onBoldTextToggle() {
var _a, _b;
(_b = (_a = this.$refs.richTextEditor) == null ? void 0 : _a.editor) == null ? void 0 : _b.chain().focus().toggleBold().run();
},
onItalicTextToggle() {
var _a;
(_a = this.$refs.richTextEditor) == null ? void 0 : _a.editor.chain().focus().toggleItalic().run();
},
onUnderlineTextToggle() {
var _a;
(_a = this.$refs.richTextEditor) == null ? void 0 : _a.editor.chain().focus().toggleUnderline().run();
},
onStrikethroughTextToggle() {
var _a;
(_a = this.$refs.richTextEditor) == null ? void 0 : _a.editor.chain().focus().toggleStrike().run();
},
onTextAlign(alignment) {
var _a, _b, _c, _d;
if ((_b = (_a = this.$refs.richTextEditor) == null ? void 0 : _a.editor) == null ? void 0 : _b.isActive({ textAlign: alignment })) {
return (_c = this.$refs.richTextEditor) == null ? void 0 : _c.editor.chain().focus().unsetTextAlign().run();
}
(_d = this.$refs.richTextEditor) == null ? void 0 : _d.editor.chain().focus().setTextAlign(alignment).run();
},
onBulletListToggle() {
var _a;
(_a = this.$refs.richTextEditor) == null ? void 0 : _a.editor.chain().focus().toggleBulletList().run();
},
onOrderedListToggle() {
var _a;
(_a = this.$refs.richTextEditor) == null ? void 0 : _a.editor.chain().focus().toggleOrderedList().run();
},
onCodeBlockToggle() {
var _a;
(_a = this.$refs.richTextEditor) == null ? void 0 : _a.editor.chain().focus().toggleCodeBlock().run();
},
onQuickRepliesClick() {
this.$emit("quick-replies-click");
},
onBlockquoteToggle() {
var _a;
(_a = this.$refs.richTextEditor) == null ? void 0 : _a.editor.chain().focus().toggleBlockquote().run();
},
insertInMessageBody(messageContent) {
var _a;
(_a = this.$refs.richTextEditor) == null ? void 0 : _a.editor.chain().focus().insertContent(messageContent).run();
},
setCursorPosition(position = null) {
var _a;
(_a = this.$refs.richTextEditor) == null ? void 0 : _a.editor.chain().focus(position).run();
},
onFocus(event) {
this.hasFocus = true;
this.$emit("focus", event);
},
onBlur(event) {
this.hasFocus = false;
this.$emit("blur", event);
},
onInput(event) {
this.$emit("input", event);
}
}
};
var _sfc_render = function render() {
var _vm = this, _c = _vm._self._c;
return _c("div", { staticClass: "d-recipe-editor", attrs: { "data-qa": "dt-recipe-editor", "role": "presentation" }, on: { "click": function($event) {
return _vm.$refs.richTextEditor.focusEditor();
} } }, [_c("dt-stack", { staticClass: "d-recipe-editor__top-bar", attrs: { "direction": "row", "gap": "450" } }, [_vm._l(_vm.buttonGroups, function(buttonGroup) {
return _c("dt-stack", { key: buttonGroup.key, attrs: { "direction": "row", "gap": "300" } }, [_vm._l(buttonGroup.buttonGroup, function(button) {
return _c("dt-tooltip", { key: `${buttonGroup.key}-${JSON.stringify(button.selector)}`, attrs: { "message": button.tooltipMessage, "placement": "top" }, scopedSlots: _vm._u([{ key: "anchor", fn: function() {
var _a, _b;
return [_c("dt-button", { attrs: { "active": (_b = (_a = _vm.$refs.richTextEditor) == null ? void 0 : _a.editor) == null ? void 0 : _b.isActive(button.selector), "aria-label": button.tooltipMessage, "data-qa": button.dataQA, "importance": "clear", "kind": "muted", "size": "xs" }, on: { "click": function($event) {
return button.onClick();
} }, scopedSlots: _vm._u([{ key: "icon", fn: function() {
return [_c(button.icon, { tag: "component", attrs: { "size": "200" } })];
}, proxy: true }], null, true) }, [button.label ? _c("span", [_vm._v(_vm._s(button.label))]) : _vm._e()])];
}, proxy: true }], null, true) });
}), _c("div", { staticClass: "d-recipe-editor__button-group-divider" })], 2);
}), _vm.linkButton.showBtn ? _c("dt-stack", { attrs: { "direction": "row", "gap": "300" } }, [_c("dt-popover", { attrs: { "open": _vm.showLinkInput, "show-close-button": false, "visually-hidden-close": true, "visually-hidden-close-label": "Close link input popover", "data-qa": "dt-recipe-editor-link-input-popover", "padding": "none", "placement": "bottom-start" }, on: { "update:open": function($event) {
_vm.showLinkInput = $event;
}, "click": _vm.onInputFocus, "opened": _vm.updateInput }, nativeOn: { "click": function($event) {
$event.stopPropagation();
return _vm.onInputFocus.apply(null, arguments);
} }, scopedSlots: _vm._u([{ key: "anchor", fn: function() {
return [_c("dt-tooltip", { key: _vm.linkButton.key, attrs: { "message": _vm.linkButton.tooltipMessage, "placement": "top" }, scopedSlots: _vm._u([{ key: "anchor", fn: function() {
var _a, _b;
return [_c("dt-button", { attrs: { "active": (_b = (_a = _vm.$refs.richTextEditor) == null ? void 0 : _a.editor) == null ? void 0 : _b.isActive(_vm.linkButton.selector), "aria-label": _vm.linkButton.tooltipMessage, "data-qa": _vm.linkButton.dataQA, "importance": "clear", "kind": "muted", "size": "xs" }, on: { "click": function($event) {
return _vm.linkButton.onClick();
} }, scopedSlots: _vm._u([{ key: "icon", fn: function() {
return [_c(_vm.linkButton.icon, { tag: "component", attrs: { "size": "200" } })];
}, proxy: true }], null, false, 3601441856) })];
}, proxy: true }], null, false, 3049978849) })];
}, proxy: true }, { key: "content", fn: function() {
return [_c("div", { staticClass: "d-recipe-editor__popover-content" }, [_vm.showAddLink.setLinkTitle.length > 0 ? _c("span", [_vm._v(" " + _vm._s(_vm.showAddLink.setLinkTitle) + " ")]) : _vm._e(), _c("dt-input", { attrs: { "input-aria-label": _vm.showAddLink.setLinkInputAriaLabel, "placeholder": _vm.setLinkPlaceholder, "data-qa": "dt-recipe-editor-link-input", "input-wrapper-class": "d-recipe-editor-link__input-wrapper" }, on: { "click": _vm.onInputFocus, "focus": _vm.onInputFocus, "keydown": function($event) {
if (!$event.type.indexOf("key") && _vm._k($event.keyCode, "enter", 13, $event.key, "Enter")) return null;
return _vm.setLink.apply(null, arguments);
} }, nativeOn: { "click": function($event) {
$event.stopPropagation();
return _vm.onInputFocus.apply(null, arguments);
} }, model: { value: _vm.linkInput, callback: function($$v) {
_vm.linkInput = $$v;
}, expression: "linkInput" } })], 1)];
}, proxy: true }, { key: "footerContent", fn: function() {
return [_c("dt-stack", { staticClass: "d-recipe-editor__popover-footer", attrs: { "direction": "row", "gap": "300" } }, [_c("dt-button", { attrs: { "aria-label": _vm.removeLinkButton.ariaLabel, "data-qa": "dt-recipe-editor-remove-link-btn", "importance": "clear", "kind": "muted", "size": "sm" }, on: { "click": _vm.removeLink } }, [_vm._v(" " + _vm._s(_vm.removeLinkButton.label) + " ")]), _c("dt-button", { attrs: { "aria-label": _vm.cancelSetLinkButton.ariaLabel, "data-qa": "dt-recipe-editor-set-link-cancel-btn", "importance": "clear", "kind": "muted", "size": "sm" }, on: { "click": _vm.closeLinkInput } }, [_vm._v(" " + _vm._s(_vm.cancelSetLinkButton.label) + " ")]), _c("dt-button", { attrs: { "aria-label": _vm.confirmSetLinkButton.ariaLabel, "data-qa": "dt-recipe-editor-set-link-confirm-btn", "size": "sm" }, on: { "click": _vm.setLink } }, [_vm._v(" " + _vm._s(_vm.confirmSetLinkButton.label) + " ")])], 1)];
}, proxy: true }], null, false, 791538356) })], 1) : _vm._e()], 2), _c("div", { staticClass: "d-recipe-editor__content", style: { "max-height": _vm.maxHeight } }, [_c("dt-rich-text-editor", _vm._b({ ref: "richTextEditor", attrs: { "allow-inline-images": true, "allow-line-breaks": true, "hide-link-bubble-menu": true, "auto-focus": _vm.autoFocus, "editable": _vm.editable, "input-aria-label": _vm.inputAriaLabel, "input-class": `d-recipe-editor__content-input ${_vm.inputClass}`, "link": true, "output-format": _vm.htmlOutputFormat, "placeholder": _vm.placeholder, "use-div-tags": _vm.useDivTags, "data-qa": "dt-rich-text-editor" }, on: { "blur": _vm.onBlur, "focus": _vm.onFocus, "input": function($event) {
return _vm.onInput($event);
} }, model: { value: _vm.internalInputValue, callback: function($$v) {
_vm.internalInputValue = $$v;
}, expression: "internalInputValue" } }, "dt-rich-text-editor", _vm.$attrs, false))], 1)], 1);
};
var _sfc_staticRenderFns = [];
var __component__ = /* @__PURE__ */ normalizeComponent(
_sfc_main,
_sfc_render,
_sfc_staticRenderFns
);
const editor = __component__.exports;
export {
editor as default
};
//# sourceMappingURL=editor.vue.js.map