UNPKG

@nextcloud/vue

Version:
952 lines (951 loc) 32.9 kB
import '../assets/NcRichContenteditable-BuaWt3Xn.css'; import debounce from "debounce"; import Tribute from "tributejs/dist/tribute.esm.js"; import { useIsDarkTheme } from "../composables/useIsDarkTheme/index.mjs"; import { g as getAvatarUrl } from "./NcMentionBubble.vue_vue_type_style_index_0_scoped_45238efd_lang-D6LzDiYf.mjs"; import { N as NcUserStatusIcon } from "./NcUserStatusIcon-CGEf7fej.mjs"; import { createElementBlock, openBlock, normalizeClass, createElementVNode, normalizeStyle, toDisplayString, createApp, resolveComponent, createBlock, createCommentVNode, mergeProps, withModifiers, withKeys } from "vue"; import { _ as _export_sfc } from "./_plugin-vue_export-helper-1tPrXgE0.mjs"; import { e as emojiSearch, a as emojiAddRecent } from "./emoji-BY_D0V5K.mjs"; import { r as register, k as t37, p as t34, a as t, q as n } from "./_l10n-DrTiip5c.mjs"; import escapeHTML from "escape-html"; import stripTags from "striptags"; import { c as createElementId } from "./createElementId-DhjFt1I9.mjs"; import { l as logger } from "./logger-D3RVzcfQ.mjs"; import "@nextcloud/auth"; import "@nextcloud/axios"; import "@nextcloud/router"; import "@nextcloud/sharing/public"; import "@vueuse/core"; import "vue-router"; import "./legacy-DcjXBL_t.mjs"; import "./NcButton-Dc8V4Urj.mjs"; import { g as getLinkWithPicker, s as searchProvider } from "./referencePickerModal-DmD3-xYB.mjs"; import "./customPickerElements-4pQTZUnk.mjs"; import "./autolink-U5pBzLgI.mjs"; import "./NcRichText-CBMtJzE_.mjs"; import "./NcEmptyContent-B8-90BSI.mjs"; import "./NcHighlight.vue_vue_type_script_lang-DnWQDM_2.mjs"; import "./NcSelect-Czzsi3P_.mjs"; import "./NcLoadingIcon-b_ajZ_nQ.mjs"; import "./NcTextField.vue_vue_type_script_setup_true_lang-D1y_LfGJ.mjs"; import "dompurify"; import "./NcIconSvgWrapper-BvLanNaW.mjs"; import "./NcInputField-Bwsh2aHY.mjs"; import "@nextcloud/event-bus"; import "focus-trap"; import "./NcActions-DWmvh7-Y.mjs"; import "../composables/useFormatDateTime/index.mjs"; import "../composables/useHotKey/index.mjs"; import "../composables/useIsFullscreen/index.mjs"; import "../composables/useIsMobile/index.mjs"; import "./NcModal-MC_HktJd.mjs"; import "./rtl-v0UOPAM7.mjs"; const _sfc_main$2 = { name: "NcMentionBubble", /* eslint vue/require-prop-comment: warn -- TODO: Add a proper doc block about what this props do */ props: { /** * Id of the bubble */ id: { type: String, required: true }, /** * The main text */ label: { type: String, required: false, default: null }, /** * Icon to be applied */ icon: { type: String, required: true }, /** * URL of the icon */ iconUrl: { type: [String, null], default: null }, source: { type: String, required: true }, /** * Is the bubble shown as primary */ primary: { type: Boolean, default: false } }, setup() { const isDarkTheme = useIsDarkTheme(); return { isDarkTheme }; }, computed: { avatarUrl() { if (this.iconUrl) { return this.iconUrl; } return this.id && this.source === "users" ? getAvatarUrl(this.id, { isDarkTheme: this.isDarkTheme }) : null; }, mentionText() { return !this.id.includes(" ") && !this.id.includes("/") ? `@${this.id}` : `@"${this.id}"`; } } }; const _hoisted_1$2 = { class: "mention-bubble__wrapper" }; const _hoisted_2$2 = { class: "mention-bubble__content" }; const _hoisted_3$1 = ["title"]; const _hoisted_4$1 = { role: "none", class: "mention-bubble__select" }; function _sfc_render$2(_ctx, _cache, $props, $setup, $data, $options) { return openBlock(), createElementBlock("span", { class: normalizeClass(["mention-bubble", { "mention-bubble--primary": $props.primary }]), contenteditable: "false" }, [ createElementVNode("span", _hoisted_1$2, [ createElementVNode("span", _hoisted_2$2, [ createElementVNode("span", { class: normalizeClass([[$props.icon, `mention-bubble__icon--${$options.avatarUrl ? "with-avatar" : ""}`], "mention-bubble__icon"]), style: normalizeStyle($options.avatarUrl ? { backgroundImage: `url(${$options.avatarUrl})` } : null) }, null, 6), createElementVNode("span", { role: "heading", class: "mention-bubble__title", title: $props.label }, null, 8, _hoisted_3$1) ]), createElementVNode("span", _hoisted_4$1, toDisplayString($options.mentionText), 1) ]) ], 2); } const NcMentionBubble = /* @__PURE__ */ _export_sfc(_sfc_main$2, [["render", _sfc_render$2], ["__scopeId", "data-v-45238efd"]]); const MENTION_START = /(?=[a-z0-9_\-@.'])\B/.source; const MENTION_SIMPLE = /(@[a-z0-9_\-@.']+)/.source; const MENTION_GUEST = /@&quot;(?:guest|email){1}\/[a-f0-9]+&quot;/.source; const MENTION_PREFIXED = /@&quot;(?:federated_)?(?:group|team|user){1}\/[a-z0-9_\-@.' /:]+&quot;/.source; const MENTION_WITH_SPACE = /@&quot;[a-z0-9_\-@.' ]+&quot;/.source; const MENTION_COMPLEX = `(${MENTION_GUEST}|${MENTION_PREFIXED}|${MENTION_WITH_SPACE})`; const USERID_REGEX = new RegExp(`${MENTION_START}${MENTION_SIMPLE}`, "gi"); const USERID_REGEX_WITH_SPACE = new RegExp(`${MENTION_START}${MENTION_COMPLEX}`, "gi"); const richEditor = { props: { userData: { type: Object, default: () => ({}) } }, methods: { /** * Convert the value string to html for the inner content * * @param {string} value the content without html * @return {string} rendered html */ renderContent(value) { const sanitizedValue = escapeHTML(value); const splitValue = sanitizedValue.split(USERID_REGEX).map((part) => part.split(USERID_REGEX_WITH_SPACE)).flat(); return splitValue.map((part) => { if (!part.startsWith("@")) { return part; } const id = part.slice(1).replace(/&quot;/gi, ""); return this.genSelectTemplate(id); }).join("").replace(/\n/gmi, "<br>").replace(/&amp;/gmi, "&"); }, /** * Convert the innerHtml content to a string with mentions as text * * @param {string} content the content without html * @return {string} */ parseContent(content) { let text = content; text = text.replace(/<br>/gmi, "\n"); text = text.replace(/&nbsp;/gmi, " "); text = text.replace(/&amp;/gmi, "&"); text = text.replace(/<\/div>/gmi, "\n"); text = stripTags(text, "<div>"); text = stripTags(text); return text; }, /** * Generate an autocompletion popup entry template * * @param {string} value the value to match against the userData * @return {string} */ genSelectTemplate(value) { if (typeof value === "undefined") { return `${this.autocompleteTribute.current.collection.trigger}${this.autocompleteTribute.current.mentionText}`; } const data = this.userData[value]; if (!data) { return [" ", "/", ":"].every((char) => !value.includes(char)) ? `@${value}` : `@"${value}"`; } return this.renderComponentHtml(data, NcMentionBubble).replace(/[\n\t]/gmi, "").replace(/>\s+</g, "><"); }, /** * Render a component and return its html content * * @param {object} props the props to pass to the component * @param {object} component the component to render * @return {string} the rendered html */ renderComponentHtml(props, component) { const Item = createApp(component, { ...props }); const mount = document.createElement("div"); mount.style.display = "none"; document.body.appendChild(mount); Item.mount(mount); const renderedHtml = mount.innerHTML; Item.unmount(); mount.remove(); return renderedHtml; } } }; const _sfc_main$1 = { name: "NcAutoCompleteResult", components: { NcUserStatusIcon }, /* eslint vue/require-prop-comment: warn -- TODO: Add a proper doc block about what this props do */ props: { /** * The label text */ label: { type: String, required: false, default: null }, /** * The secondary line of text if any */ subline: { type: String, default: null }, /** * Unique id */ id: { type: String, default: null }, /** * The icon class */ icon: { type: String, required: true }, /** * Icon as external URL */ iconUrl: { type: String, default: null }, source: { type: String, required: true }, status: { type: [Object, Array], default: () => ({}) } }, setup() { const isDarkTheme = useIsDarkTheme(); return { isDarkTheme }; }, computed: { avatarUrl() { if (this.iconUrl) { return this.iconUrl; } return this.id && this.source === "users" ? getAvatarUrl(this.id, { isDarkTheme: this.isDarkTheme }) : null; } } }; const _hoisted_1$1 = { class: "autocomplete-result" }; const _hoisted_2$1 = { key: 0, class: "autocomplete-result__status autocomplete-result__status--icon" }; const _hoisted_3 = { class: "autocomplete-result__content" }; const _hoisted_4 = ["title"]; const _hoisted_5 = { key: 0, class: "autocomplete-result__subline" }; function _sfc_render$1(_ctx, _cache, $props, $setup, $data, $options) { const _component_NcUserStatusIcon = resolveComponent("NcUserStatusIcon"); return openBlock(), createElementBlock("div", _hoisted_1$1, [ createElementVNode("div", { class: normalizeClass([[$props.icon, `autocomplete-result__icon--${$options.avatarUrl ? "with-avatar" : ""}`], "autocomplete-result__icon"]), style: normalizeStyle($options.avatarUrl ? { backgroundImage: `url(${$options.avatarUrl})` } : null) }, [ $props.status.icon ? (openBlock(), createElementBlock("span", _hoisted_2$1, toDisplayString($props.status && $props.status.icon || ""), 1)) : $props.status.status && $props.status.status !== "offline" ? (openBlock(), createBlock(_component_NcUserStatusIcon, { key: 1, class: "autocomplete-result__status", status: $props.status.status }, null, 8, ["status"])) : createCommentVNode("", true) ], 6), createElementVNode("span", _hoisted_3, [ createElementVNode("span", { class: "autocomplete-result__title", title: $props.label }, toDisplayString($props.label), 9, _hoisted_4), $props.subline ? (openBlock(), createElementBlock("span", _hoisted_5, toDisplayString($props.subline), 1)) : createCommentVNode("", true) ]) ]); } const NcAutoCompleteResult = /* @__PURE__ */ _export_sfc(_sfc_main$1, [["render", _sfc_render$1], ["__scopeId", "data-v-ca83b679"]]); register(t34, t37); const style1 = { "material-design-icon": "_material-design-icon_1xkrb_12", "tribute-container": "_tribute-container_1xkrb_20", "tribute-container__item": "_tribute-container__item_1xkrb_41", "tribute-container--focus-visible": "_tribute-container--focus-visible_1xkrb_55", "tribute-container-autocomplete": "_tribute-container-autocomplete_1xkrb_59", "tribute-container-emoji": "_tribute-container-emoji_1xkrb_65", "tribute-container-link": "_tribute-container-link_1xkrb_66", "tribute-item": "_tribute-item_1xkrb_71", "tribute-item__title": "_tribute-item__title_1xkrb_86", "tribute-item__icon": "_tribute-item__icon_1xkrb_91" }; const smilesCharacters = ["d", "D", "p", "P", "s", "S", "x", "X", ")", "(", "|", "/"]; const textSmiles = []; smilesCharacters.forEach((char) => { textSmiles.push(":" + char); textSmiles.push(":-" + char); }); const _sfc_main = { name: "NcRichContenteditable", mixins: [richEditor], inheritAttrs: false, props: { /** * The ID attribute of the content editable */ id: { type: String, default: () => createElementId() }, /** * Visual label of the contenteditable */ label: { type: String, default: "" }, /** * The text content */ modelValue: { type: String, required: true }, /** * Placeholder to be shown if empty */ placeholder: { type: String, default: t("Write a message …") }, /** * Auto complete function */ autoComplete: { type: Function, default: () => [] }, /** * The containing element for the menu popover */ menuContainer: { type: Element, default: () => document.body }, /** * Make the contenteditable looks like a textarea or not. * Default looks like a single-line input. * This also handle the default enter/shift+enter behaviour. * if multiline, enter = newline; otherwise enter = submit * shift+enter always add a new line. ctrl+enter always submits */ multiline: { type: Boolean, default: false }, /** * Is the content editable ? */ contenteditable: { type: Boolean, default: true }, /** * Disable the editing and show specific disabled design */ disabled: { type: Boolean, default: false }, /** * Max allowed length */ maxlength: { type: Number, default: null }, /** * Enable or disable emoji autocompletion */ emojiAutocomplete: { type: Boolean, default: true }, /** * Enable or disable link autocompletion */ linkAutocomplete: { type: Boolean, default: true }, /** * CSS class to apply to the root element. */ class: { type: [String, Array, Object], default: "" } }, emits: [ "paste", "update:modelValue", "smartPickerSubmit", "submit" ], setup() { const segmenter = new Intl.Segmenter(); return { // Constants labelId: createElementId(), tributeId: createElementId(), segmenter, /** * Non-reactive property to store Tribute instance * * @type {import('tributejs').default | null} */ tribute: null, tributeStyleMutationObserver: null }; }, data() { return { // Represent the raw untrimmed text of the contenteditable // serves no other purpose than to check whether the // content is empty or not localValue: this.modelValue, // Is in text composition session in IME isComposing: false, // Tribute autocomplete isAutocompleteOpen: false, autocompleteActiveId: void 0, isTributeIntegrationDone: false }; }, computed: { /** * Is the current trimmed value empty? * * @return {boolean} */ isEmptyValue() { return !this.localValue || this.localValue.trim() === ""; }, /** * Is the current value over maxlength? * * @return {boolean} */ isOverMaxlength() { if (this.isEmptyValue || !this.maxlength) { return false; } const length = [...this.segmenter.segment(this.localValue)].length; return length > this.maxlength; }, /** * Tooltip to show if characters count is over limit * * @return {string} */ tooltipString() { if (!this.isOverMaxlength) { return null; } return n("Message limit of %n character reached", "Message limit of %n characters reached", this.maxlength); }, /** * Edit is only allowed when contenteditableis true and disabled is false * * @return {boolean} */ canEdit() { return this.contenteditable && !this.disabled; }, /** * Compute debounce function for the autocomplete function */ debouncedAutoComplete() { return debounce(async (search, callback) => { this.autoComplete(search, callback); }, 100); } }, watch: { /** * If the parent value change, we compare the plain text rendering * If it's different, we render everything and update the main content */ modelValue() { const html = this.$refs.contenteditable.innerHTML; if (this.modelValue.trim() !== this.parseContent(html).trim()) { this.updateContent(this.modelValue); } } }, mounted() { this.initializeTribute(); this.updateContent(this.modelValue); this.$refs.contenteditable.contentEditable = this.canEdit; }, beforeUnmount() { if (this.tribute) { this.tribute.detach(this.$refs.contenteditable); } if (this.tributeStyleMutationObserver) { this.tributeStyleMutationObserver.disconnect(); } }, methods: { /** * Focus the richContenteditable * * @public */ focus() { this.$refs.contenteditable.focus(); }, initializeTribute() { const renderMenuItem = (content) => `<div id="${createElementId()}" class="${this.$style["tribute-item"]}" role="option">${content}</div>`; const tributesCollection = []; tributesCollection.push({ fillAttr: "id", // Search against id and label (display name) (fallback to title for v8.0.0..8.6.1 compatibility) lookup: (result) => `${result.id} ${result.label ?? result.title}`, requireLeadingSpace: true, // Popup mention autocompletion templates menuItemTemplate: (item) => renderMenuItem(this.renderComponentHtml(item.original, NcAutoCompleteResult)), // Hide if no results noMatchTemplate: () => '<span class="hidden"></span>', // Inner display of mentions selectTemplate: (item) => this.genSelectTemplate(item?.original?.id), // Autocompletion results values: this.debouncedAutoComplete, // Class added to the menu container containerClass: `${this.$style["tribute-container"]} ${this.$style["tribute-container-autocomplete"]}`, // Class added to each list item itemClass: this.$style["tribute-container__item"] }); if (this.emojiAutocomplete) { tributesCollection.push({ trigger: ":", // Don't use the tribute search function at all // We pass search results as values (see below) lookup: (result, query) => query, requireLeadingSpace: true, // Popup mention autocompletion templates menuItemTemplate: (item) => { if (textSmiles.includes(item.original)) { return item.original; } return renderMenuItem(`<span class="${this.$style["tribute-item__emoji"]}">${item.original.native}</span> :${item.original.short_name}`); }, // Hide if no results noMatchTemplate: () => t("No emoji found"), // Display raw emoji along with its name selectTemplate: (item) => { if (textSmiles.includes(item.original)) { return item.original; } emojiAddRecent(item.original); return item.original.native; }, // Pass the search results as values values: (text, cb) => { const emojiResults = emojiSearch(text); if (textSmiles.includes(":" + text)) { emojiResults.unshift(":" + text); } cb(emojiResults); }, // Class added to the menu container containerClass: `${this.$style["tribute-container"]} ${this.$style["tribute-container-emoji"]}`, // Class added to each list item itemClass: this.$style["tribute-container__item"] }); } if (this.linkAutocomplete) { tributesCollection.push({ trigger: "/", // Don't use the tribute search function at all // We pass search results as values (see below) lookup: (result, query) => query, requireLeadingSpace: true, // Popup mention autocompletion templates menuItemTemplate: (item) => renderMenuItem(`<img class="${this.$style["tribute-item__icon"]}" src="${item.original.icon_url}"> <span class="${this.$style["tribute-item__title"]}">${item.original.title}</span>`), // Hide if no results noMatchTemplate: () => t("No link provider found"), selectTemplate: this.getLink, // Pass the search results as values values: (text, cb) => cb(searchProvider(text)), // Class added to the menu container containerClass: `${this.$style["tribute-container"]} ${this.$style["tribute-container-link"]}`, // Class added to each list item itemClass: this.$style["tribute-container__item"] }); } this.tribute = new Tribute({ collection: tributesCollection, // FIXME: tributejs doesn't support allowSpaces as a collection option, only as a global one // Requires to fork a library to allow spaces only in the middle of mentions ('@' trigger) allowSpaces: false, // Where to inject the menu popup menuContainer: this.menuContainer }); this.tribute.attach(this.$refs.contenteditable); }, getLink(item) { getLinkWithPicker(item.original.id).then((result) => { const tmpElem = document.getElementById("tmp-smart-picker-result-node"); const eventData = { result, insertText: true }; this.$emit("smartPickerSubmit", eventData); if (eventData.insertText) { const newElem = document.createTextNode(result); tmpElem.replaceWith(newElem); this.setCursorAfter(newElem); this.updateValue(this.$refs.contenteditable.innerHTML); } else { tmpElem.remove(); } }).catch((error) => { logger.debug("[NcRichContenteditable] Smart picker promise rejected:", { error }); const tmpElem = document.getElementById("tmp-smart-picker-result-node"); this.setCursorAfter(tmpElem); tmpElem.remove(); }); return '<span id="tmp-smart-picker-result-node"></span>'; }, setCursorAfter(element) { const range = document.createRange(); range.setEndAfter(element); range.collapse(); const selection = window.getSelection(); selection.removeAllRanges(); selection.addRange(range); }, moveCursorToEnd() { if (!document.createRange) { return; } if (window.getSelection().rangeCount > 0 && this.$refs.contenteditable.contains(window.getSelection().getRangeAt(0).commonAncestorContainer)) { return; } const range = document.createRange(); range.selectNodeContents(this.$refs.contenteditable); range.collapse(false); const selection = window.getSelection(); selection.removeAllRanges(); selection.addRange(range); }, /** * Re-emit the input event to the parent * * @param {Event} event the input event */ onInput(event) { this.updateValue(event.target.innerHTML); }, /** * When pasting, sanitize the content, extract text * and render it again * * @param {Event} event the paste event * @fires Event paste the original paste event */ onPaste(event) { if (!this.canEdit) { return; } event.preventDefault(); const clipboardData = event.clipboardData; this.$emit("paste", event); if (clipboardData.files.length !== 0 || !Object.values(clipboardData.items).find((item) => item?.type.startsWith("text"))) { return; } const text = clipboardData.getData("text"); const selection = window.getSelection(); const range = selection.getRangeAt(0); range.deleteContents(); range.insertNode(document.createTextNode(text)); range.collapse(false); this.updateValue(this.$refs.contenteditable.innerHTML); }, /** * Update the value text from the provided html * * @param {string} htmlOrText the html content (or raw text with @mentions) */ updateValue(htmlOrText) { const text = this.parseContent(htmlOrText).replace(/^\n$/, ""); this.localValue = text; this.$emit("update:modelValue", text); }, /** * Update content and local value * * @param {string} value the message value */ updateContent(value) { const renderedContent = this.renderContent(value); this.$refs.contenteditable.innerHTML = renderedContent; this.localValue = value; }, /** * Enter key pressed. Submits if not multiline * * @param {Event} event the keydown event */ onEnter(event) { if (this.multiline || this.isOverMaxlength || this.tribute.isActive || this.isComposing) { return; } event.preventDefault(); event.stopPropagation(); this.$emit("submit", event); }, /** * Ctrl + Enter key pressed is used to submit * * @param {Event} event the keydown event */ onCtrlEnter(event) { if (this.isOverMaxlength) { return; } this.$emit("submit", event); }, onKeyUp(event) { event.stopImmediatePropagation(); }, onKeyEsc(event) { if (this.tribute && this.isAutocompleteOpen) { event.stopImmediatePropagation(); this.tribute.hideMenu(); } }, /** * Get HTML element with Tribute.js container * * @return {HTMLElement} */ getTributeContainer() { return this.tribute.menu; }, /** * Get the currently selected item element id in Tribute.js container * * @return {HTMLElement} */ getTributeSelectedItem() { return this.getTributeContainer().querySelector('.highlight [id^="nc-rich-contenteditable-tribute-item-"]'); }, /** * Handle Tribute activation * * @param {boolean} isActive - is active */ onTributeActive(isActive) { this.isAutocompleteOpen = isActive; if (isActive) { this.getTributeContainer().setAttribute("class", this.tribute.current.collection.containerClass || this.$style["tribute-container"]); this.setupTributeIntegration(); document.removeEventListener("click", this.hideTribute, true); } else { this.debouncedAutoComplete.clear(); this.autocompleteActiveId = void 0; this.setTributeFocusVisible(false); } }, onTributeArrowKeyDown() { if (!this.isAutocompleteOpen) { return; } this.setTributeFocusVisible(true); this.onTributeSelectedItemWillChange(); }, onTributeSelectedItemWillChange() { requestAnimationFrame(() => { this.autocompleteActiveId = this.getTributeSelectedItem()?.id; }); }, setupTributeIntegration() { if (this.isTributeIntegrationDone) { return; } this.isTributeIntegrationDone = true; const tributeContainer = this.getTributeContainer(); tributeContainer.id = this.tributeId; tributeContainer.setAttribute("role", "listbox"); const ul = tributeContainer.children[0]; ul.setAttribute("role", "presentation"); this.tributeStyleMutationObserver = new MutationObserver(([{ target }]) => { if (target.style.display !== "none") { this.onTributeSelectedItemWillChange(); } }).observe(tributeContainer, { attributes: true, attributeFilter: ["style"] }); tributeContainer.addEventListener("mousemove", () => { this.setTributeFocusVisible(false); this.onTributeSelectedItemWillChange(); }, { passive: true }); }, /** * Set tribute-container--focus-visible class on the Tribute container when the user navigates the listbox via keyboard. * * Because the real focus is kept on the textbox, we cannot use the :focus-visible pseudo-class * to style selected options in the autocomplete listbox. * * @param {boolean} withFocusVisible - should the focus-visible class be added */ setTributeFocusVisible(withFocusVisible) { if (withFocusVisible) { this.getTributeContainer().classList.add(this.$style["tribute-container--focus-visible"]); } else { this.getTributeContainer().classList.remove(this.$style["tribute-container--focus-visible"]); } }, /** * Show tribute menu programmatically. * * @param {string} trigger - trigger character, can be '/', '@', or ':' * * @public */ showTribute(trigger) { this.focus(); const index = this.tribute.collection.findIndex((collection) => collection.trigger === trigger); this.tribute.showMenuForCollection(this.$refs.contenteditable, index); this.updateValue(this.$refs.contenteditable.innerHTML); document.addEventListener("click", this.hideTribute, true); }, /** * Hide tribute menu programmatically * */ hideTribute() { this.tribute.hideMenu(); document.removeEventListener("click", this.hideTribute, true); } } }; const _hoisted_1 = ["id", "contenteditable", "aria-labelledby", "aria-placeholder", "aria-controls", "aria-expanded", "aria-activedescendant", "title"]; const _hoisted_2 = ["id"]; function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) { return openBlock(), createElementBlock("div", { class: normalizeClass(["rich-contenteditable", _ctx.$props.class]) }, [ createElementVNode("div", mergeProps({ id: $props.id, ref: "contenteditable", class: [{ "rich-contenteditable__input--empty": $options.isEmptyValue, "rich-contenteditable__input--multiline": $props.multiline, "rich-contenteditable__input--has-label": $props.label, "rich-contenteditable__input--overflow": $options.isOverMaxlength, "rich-contenteditable__input--disabled": $props.disabled }, "rich-contenteditable__input"], contenteditable: $options.canEdit, "aria-labelledby": $props.label ? $setup.labelId : void 0, "aria-placeholder": $props.placeholder, "aria-multiline": "true", role: "textbox", "aria-haspopup": "listbox", "aria-autocomplete": "inline", "aria-controls": $setup.tributeId, "aria-expanded": $data.isAutocompleteOpen ? "true" : "false", "aria-activedescendant": $data.autocompleteActiveId, title: $options.tooltipString }, _ctx.$attrs, { onFocus: _cache[0] || (_cache[0] = (...args) => $options.moveCursorToEnd && $options.moveCursorToEnd(...args)), onInput: _cache[1] || (_cache[1] = (...args) => $options.onInput && $options.onInput(...args)), onCompositionstart: _cache[2] || (_cache[2] = ($event) => $data.isComposing = true), onCompositionend: _cache[3] || (_cache[3] = ($event) => $data.isComposing = false), onKeydownCapture: _cache[4] || (_cache[4] = withKeys((...args) => $options.onKeyEsc && $options.onKeyEsc(...args), ["esc"])), onKeydown: [ _cache[5] || (_cache[5] = withKeys(withModifiers((...args) => $options.onEnter && $options.onEnter(...args), ["exact"]), ["enter"])), _cache[6] || (_cache[6] = withKeys(withModifiers((...args) => $options.onCtrlEnter && $options.onCtrlEnter(...args), ["ctrl", "exact", "stop", "prevent"]), ["enter"])), _cache[9] || (_cache[9] = withKeys(withModifiers((...args) => $options.onTributeArrowKeyDown && $options.onTributeArrowKeyDown(...args), ["exact", "stop"]), ["up"])), _cache[10] || (_cache[10] = withKeys(withModifiers((...args) => $options.onTributeArrowKeyDown && $options.onTributeArrowKeyDown(...args), ["exact", "stop"]), ["down"])) ], onPaste: _cache[7] || (_cache[7] = (...args) => $options.onPaste && $options.onPaste(...args)), onKeyupCapture: _cache[8] || (_cache[8] = withModifiers((...args) => $options.onKeyUp && $options.onKeyUp(...args), ["stop", "prevent"])), onTributeActiveTrue: _cache[11] || (_cache[11] = ($event) => $options.onTributeActive(true)), onTributeActiveFalse: _cache[12] || (_cache[12] = ($event) => $options.onTributeActive(false)) }), null, 16, _hoisted_1), $props.label ? (openBlock(), createElementBlock("div", { key: 0, id: $setup.labelId, class: "rich-contenteditable__label" }, toDisplayString($props.label), 9, _hoisted_2)) : createCommentVNode("", true) ], 2); } const cssModules = { "$style": style1 }; const NcRichContenteditable = /* @__PURE__ */ _export_sfc(_sfc_main, [["render", _sfc_render], ["__cssModules", cssModules], ["__scopeId", "data-v-faef642b"]]); export { NcMentionBubble as N, NcAutoCompleteResult as a, NcRichContenteditable as b }; //# sourceMappingURL=NcRichContenteditable-0w6dbJeG.mjs.map