UNPKG

@dialpad/dialtone

Version:

Dialpad's Dialtone design system monorepo

621 lines (620 loc) 23.2 kB
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