UNPKG

@dialpad/dialtone

Version:

Dialpad's Dialtone design system monorepo

789 lines (788 loc) 28.4 kB
"use strict"; Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } }); const editor_constants = require("./editor_constants.cjs"); const common_utils = require("../../../common/utils.cjs"); const vue3 = require("@dialpad/dialtone-icons/vue3"); const vue = require("vue"); const _pluginVue_exportHelper = require("../../../_virtual/_plugin-vue_export-helper.cjs"); const rich_text_editor = require("../../../components/rich_text_editor/rich_text_editor.vue.cjs"); const button = require("../../../components/button/button.vue.cjs"); const popover = require("../../../components/popover/popover.vue.cjs"); const stack = require("../../../components/stack/stack.vue.cjs"); const input = require("../../../components/input/input.vue.cjs"); const tooltip = require("../../../components/tooltip/tooltip.vue.cjs"); const rich_text_editor_constants = require("../../../components/rich_text_editor/rich_text_editor_constants.cjs"); const _sfc_main = { compatConfig: { MODE: 3 }, name: "DtRecipeEditor", components: { DtRichTextEditor: rich_text_editor.default, DtButton: button.default, DtPopover: popover.default, DtStack: stack.default, DtInput: input.default, DtTooltip: tooltip.default, DtIconLightningBolt: vue3.DtIconLightningBolt, DtIconBold: vue3.DtIconBold, DtIconItalic: vue3.DtIconItalic, DtIconUnderline: vue3.DtIconUnderline, DtIconStrikethrough: vue3.DtIconStrikethrough, DtIconListBullet: vue3.DtIconListBullet, DtIconListOrdered: vue3.DtIconListOrdered, DtIconAlignLeft: vue3.DtIconAlignLeft, DtIconAlignCenter: vue3.DtIconAlignCenter, DtIconAlignRight: vue3.DtIconAlignRight, DtIconAlignJustify: vue3.DtIconAlignJustify, DtIconQuote: vue3.DtIconQuote, DtIconCodeBlock: vue3.DtIconCodeBlock, DtIconLink2: vue3.DtIconLink2 }, mixins: [], 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_constants.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_constants.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: vue3.DtIconLightningBolt, dataQA: "dt-recipe-editor-quick-replies-btn", tooltipMessage: "Quick Reply", onClick: this.onQuickRepliesClick } ].filter((button2) => button2.showBtn); }, textFormatButtons() { return [ { showBtn: this.showBoldButton, selector: "bold", icon: vue3.DtIconBold, dataQA: "dt-recipe-editor-bold-btn", tooltipMessage: "Bold", onClick: this.onBoldTextToggle }, { showBtn: this.showItalicsButton, selector: "italic", icon: vue3.DtIconItalic, dataQA: "dt-recipe-editor-italics-btn", tooltipMessage: "Italics", onClick: this.onItalicTextToggle }, { showBtn: this.showUnderlineButton, selector: "underline", icon: vue3.DtIconUnderline, dataQA: "dt-recipe-editor-underline-btn", tooltipMessage: "Underline", onClick: this.onUnderlineTextToggle }, { showBtn: this.showStrikeButton, selector: "strike", icon: vue3.DtIconStrikethrough, dataQA: "dt-recipe-editor-strike-btn", tooltipMessage: "Strike", onClick: this.onStrikethroughTextToggle } ].filter((button2) => button2.showBtn); }, alignmentButtons() { return [ { showBtn: this.showAlignLeftButton, selector: { textAlign: "left" }, icon: vue3.DtIconAlignLeft, dataQA: "dt-recipe-editor-align-left-btn", tooltipMessage: "Align Left", onClick: () => this.onTextAlign("left") }, { showBtn: this.showAlignCenterButton, selector: { textAlign: "center" }, icon: vue3.DtIconAlignCenter, dataQA: "dt-recipe-editor-align-center-btn", tooltipMessage: "Align Center", onClick: () => this.onTextAlign("center") }, { showBtn: this.showAlignRightButton, selector: { textAlign: "right" }, icon: vue3.DtIconAlignRight, dataQA: "dt-recipe-editor-align-right-btn", tooltipMessage: "Align Right", onClick: () => this.onTextAlign("right") }, { showBtn: this.showAlignJustifyButton, selector: { textAlign: "justify" }, icon: vue3.DtIconAlignJustify, dataQA: "dt-recipe-editor-align-justify-btn", tooltipMessage: "Align Justify", onClick: () => this.onTextAlign("justify") } ].filter((button2) => button2.showBtn); }, listButtons() { return [ { showBtn: this.showListItemsButton, selector: "bulletList", icon: vue3.DtIconListBullet, dataQA: "dt-recipe-editor-list-items-btn", tooltipMessage: "Bullet List", onClick: this.onBulletListToggle }, { showBtn: this.showOrderedListButton, selector: "orderedList", icon: vue3.DtIconListOrdered, dataQA: "dt-recipe-editor-ordered-list-items-btn", tooltipMessage: "Ordered List", onClick: this.onOrderedListToggle } ].filter((button2) => button2.showBtn); }, individualButtons() { return [ { showBtn: this.showQuoteButton, selector: "blockquote", icon: vue3.DtIconQuote, dataQA: "dt-recipe-editor-blockquote-btn", tooltipMessage: "Quote", onClick: this.onBlockquoteToggle }, { showBtn: this.showCodeBlockButton, selector: "codeBlock", icon: vue3.DtIconCodeBlock, dataQA: "dt-recipe-editor-code-block-btn", tooltipMessage: "Code", onClick: this.onCodeBlockToggle } ].filter((button2) => button2.showBtn); }, linkButton() { return { showBtn: this.showAddLink.showAddLinkButton, selector: "link", icon: vue3.DtIconLink2, dataQA: "dt-recipe-editor-add-link-btn", tooltipMessage: "Link", onClick: this.openLinkInput }; } }, watch: { value(newValue) { this.internalInputValue = newValue; } }, methods: { removeClassStyleAttrs: common_utils.removeClassStyleAttrs, addClassStyleAttrs: common_utils.addClassStyleAttrs, 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_constants.EDITOR_SUPPORTED_LINK_PROTOCOLS.find((prefixRegex) => prefixRegex.test(this.linkInput)); if (!prefix) { this.linkInput = `${editor_constants.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"); }, 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(); }, onBlockquoteToggle() { var _a; (_a = this.$refs.richTextEditor) == null ? void 0 : _a.editor.chain().focus().toggleBlockquote().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); } } }; const _hoisted_1 = /* @__PURE__ */ vue.createElementVNode("div", { class: "d-recipe-editor__button-group-divider" }, null, -1); const _hoisted_2 = { class: "d-recipe-editor__popover-content" }; const _hoisted_3 = { key: 0 }; function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) { const _component_dt_button = vue.resolveComponent("dt-button"); const _component_dt_tooltip = vue.resolveComponent("dt-tooltip"); const _component_dt_stack = vue.resolveComponent("dt-stack"); const _component_dt_input = vue.resolveComponent("dt-input"); const _component_dt_popover = vue.resolveComponent("dt-popover"); const _component_dt_rich_text_editor = vue.resolveComponent("dt-rich-text-editor"); return vue.openBlock(), vue.createElementBlock("div", vue.mergeProps({ class: "d-recipe-editor" }, $options.addClassStyleAttrs(_ctx.$attrs), { "data-qa": "dt-recipe-editor", role: "presentation", onClick: _cache[4] || (_cache[4] = ($event) => _ctx.$refs.richTextEditor.focusEditor()) }), [ vue.createVNode(_component_dt_stack, { class: "d-recipe-editor__top-bar", direction: "row", gap: "450" }, { default: vue.withCtx(() => [ (vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList($options.buttonGroups, (buttonGroup) => { return vue.openBlock(), vue.createBlock(_component_dt_stack, { key: buttonGroup.key, direction: "row", gap: "300" }, { default: vue.withCtx(() => [ (vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList(buttonGroup.buttonGroup, (button2) => { return vue.openBlock(), vue.createBlock(_component_dt_tooltip, { key: `${buttonGroup.key}-${JSON.stringify(button2.selector)}`, message: button2.tooltipMessage, placement: "top" }, { anchor: vue.withCtx(() => { var _a, _b; return [ vue.createVNode(_component_dt_button, { active: (_b = (_a = _ctx.$refs.richTextEditor) == null ? void 0 : _a.editor) == null ? void 0 : _b.isActive(button2.selector), "aria-label": button2.tooltipMessage, "data-qa": button2.dataQA, importance: "clear", kind: "muted", size: "xs", onClick: ($event) => button2.onClick() }, { icon: vue.withCtx(() => [ (vue.openBlock(), vue.createBlock(vue.resolveDynamicComponent(button2.icon), { size: "200" })) ]), default: vue.withCtx(() => [ vue.createTextVNode(" " + vue.toDisplayString(button2 == null ? void 0 : button2.label), 1) ]), _: 2 }, 1032, ["active", "aria-label", "data-qa", "onClick"]) ]; }), _: 2 }, 1032, ["message"]); }), 128)), _hoisted_1 ]), _: 2 }, 1024); }), 128)), $options.linkButton.showBtn ? (vue.openBlock(), vue.createBlock(_component_dt_stack, { key: 0, direction: "row", gap: "300" }, { default: vue.withCtx(() => [ vue.createVNode(_component_dt_popover, { open: $data.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", onClick: [ $options.onInputFocus, vue.withModifiers($options.onInputFocus, ["stop"]) ], onOpened: $options.updateInput }, { anchor: vue.withCtx(() => [ (vue.openBlock(), vue.createBlock(_component_dt_tooltip, { key: $options.linkButton.key, message: $options.linkButton.tooltipMessage, placement: "top" }, { anchor: vue.withCtx(() => { var _a, _b; return [ vue.createVNode(_component_dt_button, { active: (_b = (_a = _ctx.$refs.richTextEditor) == null ? void 0 : _a.editor) == null ? void 0 : _b.isActive($options.linkButton.selector), "aria-label": $options.linkButton.tooltipMessage, "data-qa": $options.linkButton.dataQA, importance: "clear", kind: "muted", size: "xs", onClick: _cache[0] || (_cache[0] = ($event) => $options.linkButton.onClick()) }, { icon: vue.withCtx(() => [ (vue.openBlock(), vue.createBlock(vue.resolveDynamicComponent($options.linkButton.icon), { size: "200" })) ]), _: 1 }, 8, ["active", "aria-label", "data-qa"]) ]; }), _: 1 }, 8, ["message"])) ]), content: vue.withCtx(() => [ vue.createElementVNode("div", _hoisted_2, [ $props.showAddLink.setLinkTitle.length > 0 ? (vue.openBlock(), vue.createElementBlock("span", _hoisted_3, vue.toDisplayString($props.showAddLink.setLinkTitle), 1)) : vue.createCommentVNode("", true), vue.createVNode(_component_dt_input, { modelValue: $data.linkInput, "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => $data.linkInput = $event), "input-aria-label": $props.showAddLink.setLinkInputAriaLabel, placeholder: $props.setLinkPlaceholder, "data-qa": "dt-recipe-editor-link-input", "input-wrapper-class": "d-recipe-editor-link__input-wrapper", onClick: [ $options.onInputFocus, vue.withModifiers($options.onInputFocus, ["stop"]) ], onFocus: $options.onInputFocus, onKeydown: vue.withKeys($options.setLink, ["enter"]) }, null, 8, ["modelValue", "input-aria-label", "placeholder", "onClick", "onFocus", "onKeydown"]) ]) ]), footerContent: vue.withCtx(() => [ vue.createVNode(_component_dt_stack, { direction: "row", gap: "300", class: "d-recipe-editor__popover-footer" }, { default: vue.withCtx(() => [ vue.createVNode(_component_dt_button, { "aria-label": $props.removeLinkButton.ariaLabel, "data-qa": "dt-recipe-editor-remove-link-btn", importance: "clear", kind: "muted", size: "sm", onClick: $options.removeLink }, { default: vue.withCtx(() => [ vue.createTextVNode(vue.toDisplayString($props.removeLinkButton.label), 1) ]), _: 1 }, 8, ["aria-label", "onClick"]), vue.createVNode(_component_dt_button, { "aria-label": $props.cancelSetLinkButton.ariaLabel, "data-qa": "dt-recipe-editor-set-link-cancel-btn", importance: "clear", kind: "muted", size: "sm", onClick: $options.closeLinkInput }, { default: vue.withCtx(() => [ vue.createTextVNode(vue.toDisplayString($props.cancelSetLinkButton.label), 1) ]), _: 1 }, 8, ["aria-label", "onClick"]), vue.createVNode(_component_dt_button, { "aria-label": $props.confirmSetLinkButton.ariaLabel, "data-qa": "dt-recipe-editor-set-link-confirm-btn", size: "sm", onClick: $options.setLink }, { default: vue.withCtx(() => [ vue.createTextVNode(vue.toDisplayString($props.confirmSetLinkButton.label), 1) ]), _: 1 }, 8, ["aria-label", "onClick"]) ]), _: 1 }) ]), _: 1 }, 8, ["open", "onClick", "onOpened"]) ]), _: 1 })) : vue.createCommentVNode("", true) ]), _: 1 }), vue.createElementVNode("div", { style: vue.normalizeStyle({ "max-height": $props.maxHeight }), class: "d-recipe-editor__content" }, [ vue.createVNode(_component_dt_rich_text_editor, vue.mergeProps({ ref: "richTextEditor", modelValue: $data.internalInputValue, "onUpdate:modelValue": _cache[2] || (_cache[2] = ($event) => $data.internalInputValue = $event), "allow-inline-images": true, "allow-line-breaks": true, "hide-link-bubble-menu": true, "auto-focus": $props.autoFocus, editable: $props.editable, "input-aria-label": $props.inputAriaLabel, "input-class": `d-recipe-editor__content-input ${$props.inputClass}`, link: true, "output-format": $options.htmlOutputFormat, placeholder: $props.placeholder, "use-div-tags": $props.useDivTags, "data-qa": "dt-rich-text-editor" }, $options.removeClassStyleAttrs(_ctx.$attrs), { onBlur: $options.onBlur, onFocus: $options.onFocus, onInput: _cache[3] || (_cache[3] = ($event) => $options.onInput($event)) }), null, 16, ["modelValue", "auto-focus", "editable", "input-aria-label", "input-class", "output-format", "placeholder", "use-div-tags", "onBlur", "onFocus"]) ], 4) ], 16); } const editor = /* @__PURE__ */ _pluginVue_exportHelper.default(_sfc_main, [["render", _sfc_render]]); exports.default = editor; //# sourceMappingURL=editor.vue.cjs.map