@rd-element/editor 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1,2 +1,2 @@
1
- Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:`Module`}});require('./style.css');const e=require("./resolver.cjs");let t=require("vue"),n=require("element-plus");function r(e){return e?Object.fromEntries(Object.entries(e).filter(([,e])=>e!==``&&e!=null&&!(Array.isArray(e)&&e.length===0)&&!(typeof e==`object`&&e&&Object.keys(e).length===0))):{}}function i(e,t){let n=0,r=[];function i(e,t){return e.filter(e=>e.input_code.includes(t)||e.name.includes(t)).map(e=>({type:`cardmenuitem`,value:e,label:e.name,items:[{type:`cardcontainer`,direction:`vertical`,items:[{type:`cardtext`,text:e.name,name:e.input_code}]}]}))}e.ui.registry.addAutocompleter(`quick_text_phrase`,{ch:`#`,minChars:0,columns:1,matches:(e,t,n)=>t&&t.endsWith(`#`)||!!n&&n.length<6,fetch:async e=>{if(n===1)return[];if(!n){n=1;try{r=await t()||[],n=2}catch(e){throw n=0,e}}return i(r,e)},onAction:(t,n,r)=>{n.deleteContents(),e.insertContent(r.content),t.hide()}})}function a(){if(!(0,t.inject)(n.dialogInjectionKey,null))return;let e=(0,n.useZIndex)().currentZIndex;(0,t.watchEffect)(()=>{e.value>1300&&document.documentElement.style.setProperty(`--el-dialog-z-index`,e.value.toString())}),(0,t.onBeforeUnmount)(()=>{document.documentElement.style.removeProperty(`--el-dialog-z-index`)})}var o={class:`rd-rich-editor`},s={key:0,class:`rd-rich-editor-footer-bar`},c=(0,t.defineComponent)({name:`RdRichEditor`,__name:`RdRichEditor`,props:(0,t.mergeModels)({plugins:{},language:{default:`zh_CN`},menubar:{},toolbar:{},toolbar_mode:{},min_height:{},height:{},textPhrase:{type:Boolean,default:!1},includes:{},excludes:{},imagesUploadHandler:{},fetchTextPhrases:{},tinymceBaseUrl:{},languageUrl:{}},{modelValue:{default:``},modelModifiers:{}}),emits:(0,t.mergeModels)([`input`,`change`],[`update:modelValue`]),setup(e,{expose:n,emit:c}){let l=e,u=c,d=(0,t.useModel)(e,`modelValue`),f=`rd-editor_${Date.now()}_${Math.random().toString(36).slice(2,8)}`,p=(0,t.ref)(!1),m=null,h=[];a();function g(){return m?.getContent()??``}function _(e){let t=m?.setContent(e??``);return d.value=g(),t}function v(e){m?.insertContent(e),d.value=g()}function y(e,t){let n=Array.isArray(t)?t:[t],r=e;return n.forEach(e=>{let t=e.trim();t&&(RegExp(`(?:^|\\s)${x(t)}(?:\\s|$)`).test(r)||(r=`${r} ${t}`.trim()))}),r}function b(e,t){let n=Array.isArray(t)?t:[t],r=e;return n.forEach(e=>{let t=e.trim();if(!t)return;let n=RegExp(`(?:^|\\s)${x(t)}(?=\\s|$)`,`g`);r=r.replace(n,` `).replace(/\s+/g,` `).trim()}),r}function x(e){return e.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`)}function S(){let{includes:e,excludes:t,textPhrase:n,imagesUploadHandler:a,fetchTextPhrases:o,tinymceBaseUrl:s,languageUrl:c,...v}=l,x=l.language===`zh-cn`?`zh_CN`:l.language,S={selector:`textarea#${f}`,language:x,plugins:`advlist autolink link image lists charmap emoticons preview hr anchor pagebreak nonbreaking code table paste insertdatetime wordcount`,menubar:`file edit view insert format tools`,menu:{insert:{title:`Insert`,items:`image link inserttable | charmap emoticons hr | pagebreak nonbreaking anchor | insertdatetime`}},toolbar:`undo redo | styleselect | fontsizeselect fontselect | bold italic forecolor backcolor | alignleft aligncenter alignright alignjustify | outdent indent | bullist numlist table | link image | code`,toolbar_mode:`wrap`,fontsize_formats:`5px 6px 7px 8px 9px 10px 12px 14px 16px 18px 20px 22px 24px 26px 28px 30px 32px 34px 36px 48px 72px`,font_formats:`Andale Mono=andale mono,times;Arial=arial,helvetica,sans-serif;Arial Black=arial black,avant garde;Book Antiqua=book antiqua,palatino;Calibri;Calibri Light;Comic Sans MS=comic sans ms,sans-serif;Courier New=courier new,courier;Georgia=georgia,palatino;Helvetica=helvetica;Impact=impact,chicago;Symbol=symbol;Tahoma=tahoma,arial,;Terminal=terminal,monaco;Times New Roman=times new roman,times;Trebuchet MS=trebuchet ms,geneva;Verdana=verdana,geneva;Webdings=webdings;Wingdings=wingdings,;`,min_height:480,table_default_styles:{width:`100%`,borderCollapse:`collapse`},image_advtab:!0,image_title:!1,paste_data_images:!0,images_upload_handler:(e,t,n)=>{if(!a){n(`imagesUploadHandler is not provided`);return}a(e.blob()).then(e=>{e?t(e):n(`Upload failed`)}).catch(()=>n(`Upload failed`))},setup(e){m=e,e.on(`init`,()=>{p.value=!0;let e=m?.getBody();for(e&&(e.style.fontSize=`16px`,e.style.fontFamily=`Calibri`),_(d.value);h.length;)h.shift()?.()}),e.on(`change`,e=>{d.value=g(),u(`change`,d.value,e)}),e.on(`input`,e=>{u(`input`,e)}),n&&(o?i(e,o):console.warn(`[RdRichEditor] textPhrase is true but fetchTextPhrases is not provided`))}};s&&(S.base_url=s),c&&(S.language_url=c);let C={...S,...r(v)};return e&&Object.keys(e).forEach(t=>{let n=C[t],r=e[t];typeof n==`string`&&r!=null&&(C[t]=y(n,r))}),t&&Object.keys(t).forEach(e=>{let n=C[e],r=t[e];typeof n==`string`&&r!=null&&(C[e]=b(n,r))}),C}function C(e){return function(...t){return p.value?e(...t):new Promise(n=>{h.push(()=>n(e(...t)))})}}function w(){let e=document.createElement(`div`);return e.innerHTML=d.value,!!(e.textContent?.trim()||e.querySelector(`a.mce-item-anchor, img, table`))}return(0,t.watch)(()=>d.value,e=>{!p.value||e===g()||_(e)}),(0,t.onMounted)(()=>{let e=window.tinymce;if(!e){console.error(`[RdRichEditor] window.tinymce is missing. Load TinyMCE 5.10.x via <script> before using RdRichEditor.`);return}l.tinymceBaseUrl&&(e.baseURL=l.tinymceBaseUrl),e.init(S())}),(0,t.onBeforeUnmount)(()=>{m?.remove(),m=null}),n({getEditor:C(()=>m),getContent:C(g),setContent:C(_),insertContent:C(v),validate:w}),(e,n)=>((0,t.openBlock)(),(0,t.createElementBlock)(`div`,o,[(0,t.createElementVNode)(`textarea`,{id:f,style:{visibility:`hidden`}}),p.value?((0,t.openBlock)(),(0,t.createElementBlock)(`div`,s,[(0,t.renderSlot)(e.$slots,`footer-bar`)])):(0,t.createCommentVNode)(``,!0)]))}}),l=[c];function u(e){l.forEach(t=>{let n=t.name||t.__name;n&&e.component(n,t)})}exports.RdRichEditor=c,exports.RodelEditorResolver=e.RodelEditorResolver,exports.default=u,exports.install=u;
1
+ Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:`Module`}});require('./style.css');const e=require("./resolver.cjs");let t=require("vue"),n=require("element-plus");function r(e){return e?Object.fromEntries(Object.entries(e).filter(([,e])=>e!==``&&e!=null&&!(Array.isArray(e)&&e.length===0)&&!(typeof e==`object`&&e&&Object.keys(e).length===0))):{}}function i(e,t){let n=null,r=null;function i(e,t){return e.filter(e=>e.input_code.includes(t)||e.name.includes(t)).map(e=>({type:`cardmenuitem`,value:e,label:e.name,items:[{type:`cardcontainer`,direction:`vertical`,items:[{type:`cardtext`,text:e.name,name:e.input_code}]}]}))}e.ui.registry.addAutocompleter(`quick_text_phrase`,{ch:`#`,minChars:0,columns:1,matches:(e,t,n)=>t&&t.endsWith(`#`)||!!n&&n.length<6,fetch:async e=>(n===null&&(r||=t().then(e=>(n=e||[],n)).finally(()=>{r=null}),await r),i(n,e)),onAction:(t,n,r)=>{n.deleteContents(),e.insertContent(r.content),t.hide()}})}function a(){if(!(0,t.inject)(n.dialogInjectionKey,null))return;let e=(0,n.useZIndex)().currentZIndex;(0,t.watchEffect)(()=>{e.value>1300&&document.documentElement.style.setProperty(`--el-dialog-z-index`,e.value.toString())}),(0,t.onBeforeUnmount)(()=>{document.documentElement.style.removeProperty(`--el-dialog-z-index`)})}var o={class:`rd-rich-editor`},s={key:0,class:`rd-rich-editor-footer-bar`},c=(0,t.defineComponent)({name:`RdRichEditor`,__name:`RdRichEditor`,props:(0,t.mergeModels)({plugins:{},language:{default:`zh_CN`},menubar:{},toolbar:{},toolbar_mode:{},min_height:{},height:{},textPhrase:{type:Boolean,default:!1},includes:{},excludes:{},imagesUploadHandler:{},fetchTextPhrases:{},tinymceBaseUrl:{},languageUrl:{}},{modelValue:{default:``},modelModifiers:{}}),emits:(0,t.mergeModels)([`input`,`change`],[`update:modelValue`]),setup(e,{expose:n,emit:c}){let l=e,u=c,d=(0,t.useModel)(e,`modelValue`),f=`rd-editor_${Date.now()}_${Math.random().toString(36).slice(2,8)}`,p=(0,t.ref)(!1),m=null,h=null,g=null,_=[];a();function v(){let e=m?.getContent()??``;return h&&(h.takeRecords(),g=e),e}function y(e){let t=m?.setContent(e??``);return d.value=v(),t}function b(e){m?.insertContent(e),d.value=v()}function x(e,t){let n=Array.isArray(t)?t:[t],r=e;return n.forEach(e=>{let t=e.trim();t&&(RegExp(`(?:^|\\s)${C(t)}(?:\\s|$)`).test(r)||(r=`${r} ${t}`.trim()))}),r}function S(e,t){let n=Array.isArray(t)?t:[t],r=e;return n.forEach(e=>{let t=e.trim();if(!t)return;let n=RegExp(`(?:^|\\s)${C(t)}(?=\\s|$)`,`g`);r=r.replace(n,` `).replace(/\s+/g,` `).trim()}),r}function C(e){return e.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`)}function w(){let{includes:e,excludes:t,textPhrase:n,imagesUploadHandler:a,fetchTextPhrases:o,tinymceBaseUrl:s,languageUrl:c,...b}=l,C=l.language===`zh-cn`?`zh_CN`:l.language,w={selector:`textarea#${f}`,language:C,plugins:`advlist autolink link image lists charmap emoticons preview hr anchor pagebreak nonbreaking code table paste insertdatetime wordcount`,menubar:`file edit view insert format tools`,menu:{insert:{title:`Insert`,items:`image link inserttable | charmap emoticons hr | pagebreak nonbreaking anchor | insertdatetime`}},toolbar:`undo redo | styleselect | fontsizeselect fontselect | bold italic forecolor backcolor | alignleft aligncenter alignright alignjustify | outdent indent | bullist numlist table | link image | code`,toolbar_mode:`wrap`,fontsize_formats:`5px 6px 7px 8px 9px 10px 12px 14px 16px 18px 20px 22px 24px 26px 28px 30px 32px 34px 36px 48px 72px`,font_formats:`Andale Mono=andale mono,times;Arial=arial,helvetica,sans-serif;Arial Black=arial black,avant garde;Book Antiqua=book antiqua,palatino;Calibri;Calibri Light;Comic Sans MS=comic sans ms,sans-serif;Courier New=courier new,courier;Georgia=georgia,palatino;Helvetica=helvetica;Impact=impact,chicago;Symbol=symbol;Tahoma=tahoma,arial,;Terminal=terminal,monaco;Times New Roman=times new roman,times;Trebuchet MS=trebuchet ms,geneva;Verdana=verdana,geneva;Webdings=webdings;Wingdings=wingdings,;`,min_height:480,table_default_styles:{width:`100%`,borderCollapse:`collapse`},image_advtab:!0,image_title:!1,paste_data_images:!0,images_upload_handler:(e,t,n)=>{if(!a){n(`imagesUploadHandler is not provided`);return}a(e.blob()).then(e=>{e?t(e):n(`Upload failed`)}).catch(()=>n(`Upload failed`))},setup(e){m=e,e.on(`init`,()=>{p.value=!0;let e=m?.getBody();for(e&&(e.style.fontSize=`16px`,e.style.fontFamily=`Calibri`,typeof MutationObserver<`u`&&(h=new MutationObserver(()=>{g=null}),h.observe(e,{subtree:!0,childList:!0,characterData:!0,attributes:!0}))),y(d.value);_.length;)_.shift()?.()}),e.on(`change`,e=>{let t=v();d.value=t,u(`change`,t,e)}),e.on(`input`,e=>{g=null,u(`input`,e)}),e.on(`BeforeSetContent SetContent Undo Redo`,()=>{g=null}),n&&(o?i(e,o):console.warn(`[RdRichEditor] textPhrase is true but fetchTextPhrases is not provided`))}};s&&(w.base_url=s),c&&(w.language_url=c);let T={...w,...r(b)};return e&&Object.keys(e).forEach(t=>{let n=T[t],r=e[t];typeof n==`string`&&r!=null&&(T[t]=x(n,r))}),t&&Object.keys(t).forEach(e=>{let n=T[e],r=t[e];typeof n==`string`&&r!=null&&(T[e]=S(n,r))}),T}function T(e){return function(...t){return p.value?e(...t):new Promise(n=>{_.push(()=>n(e(...t)))})}}function E(){let e=document.createElement(`div`);return e.innerHTML=d.value,!!(e.textContent?.trim()||e.querySelector(`a.mce-item-anchor, img, table`))}return(0,t.watch)(()=>d.value,e=>{p.value&&(h?.takeRecords().length&&(g=null),e!==(g??v())&&y(e))}),(0,t.onMounted)(()=>{let e=window.tinymce;if(!e){console.error(`[RdRichEditor] window.tinymce is missing. Load TinyMCE 5.10.x via <script> before using RdRichEditor.`);return}l.tinymceBaseUrl&&(e.baseURL=l.tinymceBaseUrl),e.init(w())}),(0,t.onBeforeUnmount)(()=>{h?.disconnect(),h=null,g=null,m?.remove(),m=null}),n({getEditor:T(()=>m),getContent:T(v),setContent:T(y),insertContent:T(b),validate:E}),(e,n)=>((0,t.openBlock)(),(0,t.createElementBlock)(`div`,o,[(0,t.createElementVNode)(`textarea`,{id:f,style:{visibility:`hidden`}}),p.value?((0,t.openBlock)(),(0,t.createElementBlock)(`div`,s,[(0,t.renderSlot)(e.$slots,`footer-bar`)])):(0,t.createCommentVNode)(``,!0)]))}}),l=[c];function u(e){l.forEach(t=>{let n=t.name||t.__name;n&&e.component(n,t)})}exports.RdRichEditor=c,exports.RodelEditorResolver=e.RodelEditorResolver,exports.default=u,exports.install=u;
2
2
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":[],"sources":["../src/utils/cleanObject.ts","../src/components/rdRichEditor/useQuickTextPhrase.ts","../src/components/rdRichEditor/dialogZIndexBridge.ts","../src/components/rdRichEditor/RdRichEditor.vue","../src/components/rdRichEditor/RdRichEditor.vue","../src/index.ts"],"sourcesContent":["/**\r\n * Remove empty values from a shallow object (null / undefined / '' / [] / {}).\r\n */\r\nexport function cleanObject<T>(obj: T | null | undefined): Partial<T> {\r\n if (!obj) return {}\r\n\r\n return Object.fromEntries(\r\n Object.entries(obj as Record<string, unknown>).filter(\r\n ([, v]) =>\r\n v !== '' &&\r\n v !== null &&\r\n v !== undefined &&\r\n !(Array.isArray(v) && v.length === 0) &&\r\n !(typeof v === 'object' && v && Object.keys(v as object).length === 0),\r\n ),\r\n ) as Partial<T>\r\n}\r\n","import type { TinyMCEEditor } from '../../types/tinymce'\r\n\r\nexport interface TextPhraseItem {\r\n name: string\r\n input_code: string\r\n content: string\r\n}\r\n\r\n/**\r\n * Register TinyMCE autocompleter for `#` quick text phrases.\r\n * Data is supplied by the host via `fetchTextPhrases`.\r\n */\r\nexport default function useQuickTextPhrase(\r\n editor: TinyMCEEditor,\r\n fetchTextPhrases: () => Promise<TextPhraseItem[]>,\r\n) {\r\n let status = 0 // 0 idle | 1 loading | 2 ready\r\n let quickTextPhrase: TextPhraseItem[] = []\r\n\r\n function filterListByPattern(list: TextPhraseItem[], pattern: string) {\r\n return list\r\n .filter((v) => v.input_code.includes(pattern) || v.name.includes(pattern))\r\n .map((v) => ({\r\n type: 'cardmenuitem',\r\n value: v,\r\n label: v.name,\r\n items: [\r\n {\r\n type: 'cardcontainer',\r\n direction: 'vertical',\r\n items: [\r\n {\r\n type: 'cardtext',\r\n text: v.name,\r\n name: v.input_code,\r\n },\r\n ],\r\n },\r\n ],\r\n }))\r\n }\r\n\r\n editor.ui.registry.addAutocompleter('quick_text_phrase', {\r\n ch: '#',\r\n minChars: 0,\r\n columns: 1,\r\n matches: (_rng: unknown, text: string, pattern: string) =>\r\n (text && text.endsWith('#')) || (!!pattern && pattern.length < 6),\r\n fetch: async (pattern: string) => {\r\n if (status === 1) return []\r\n if (!status) {\r\n status = 1\r\n try {\r\n quickTextPhrase = (await fetchTextPhrases()) || []\r\n status = 2\r\n } catch (err) {\r\n status = 0\r\n throw err\r\n }\r\n }\r\n return filterListByPattern(quickTextPhrase, pattern)\r\n },\r\n onAction: (\r\n autocompleterApi: { hide: () => void },\r\n rng: { deleteContents: () => void },\r\n value: TextPhraseItem,\r\n ) => {\r\n rng.deleteContents()\r\n editor.insertContent(value.content)\r\n autocompleterApi.hide()\r\n },\r\n })\r\n}\r\n","import { inject, onBeforeUnmount, watchEffect } from 'vue'\r\nimport { useZIndex, dialogInjectionKey } from 'element-plus'\r\n\r\n/**\r\n * When RdRichEditor is used inside ElDialog / RdDialog, raise TinyMCE\r\n * popover z-index above the dialog via --el-dialog-z-index.\r\n */\r\nexport function setupDialogZIndexBridge(): void {\r\n const dialogProvide = inject(dialogInjectionKey, null)\r\n if (!dialogProvide) return\r\n\r\n const dialogZIndex = useZIndex().currentZIndex\r\n watchEffect(() => {\r\n if (dialogZIndex.value > 1300) {\r\n document.documentElement.style.setProperty(\r\n '--el-dialog-z-index',\r\n dialogZIndex.value.toString(),\r\n )\r\n }\r\n })\r\n onBeforeUnmount(() => {\r\n document.documentElement.style.removeProperty('--el-dialog-z-index')\r\n })\r\n}\r\n","<script setup lang=\"ts\">\r\ndefineOptions({\r\n name: 'RdRichEditor',\r\n})\r\n\r\nimport { onBeforeUnmount, onMounted, ref, watch } from 'vue'\r\nimport type { TinyMCEEditor } from '../../types/tinymce'\r\nimport { cleanObject } from '../../utils/cleanObject'\r\nimport useQuickTextPhrase, { type TextPhraseItem } from './useQuickTextPhrase'\r\nimport { setupDialogZIndexBridge } from './dialogZIndexBridge'\r\nimport '../../styles/rd-rich-editor.css'\r\n\r\ninterface EditorConfigProps {\r\n plugins?: string | string[]\r\n language?: string\r\n menubar?: string | string[]\r\n toolbar?: string | string[]\r\n toolbar_mode?: string\r\n min_height?: number\r\n height?: number\r\n /** Enable `#` quick text phrase autocompleter (requires fetchTextPhrases) */\r\n textPhrase?: boolean\r\n}\r\n\r\ntype Props = EditorConfigProps & {\r\n includes?: Pick<EditorConfigProps, 'plugins' | 'menubar' | 'toolbar'>\r\n excludes?: Pick<EditorConfigProps, 'plugins' | 'menubar' | 'toolbar'>\r\n /**\r\n * Image upload hook. Return a publicly accessible URL.\r\n * When omitted, paste/insert image upload fails with a clear message.\r\n */\r\n imagesUploadHandler?: (blob: Blob) => Promise<string>\r\n /** Fetch quick text phrases when textPhrase is true */\r\n fetchTextPhrases?: () => Promise<TextPhraseItem[]>\r\n /** Override TinyMCE baseURL when static assets are not at the default path */\r\n tinymceBaseUrl?: string\r\n /**\r\n * TinyMCE language pack URL (language_url).\r\n * Needed when langs/*.js are not under tinymceBaseUrl (e.g. CDN demos).\r\n */\r\n languageUrl?: string\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n language: 'zh_CN',\r\n textPhrase: false,\r\n})\r\n\r\nconst emits = defineEmits<{\r\n input: [e: Event]\r\n change: [value: string, e: unknown]\r\n}>()\r\n\r\nconst inputValue = defineModel<string>({ default: '' })\r\nconst editorId = `rd-editor_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`\r\nconst isInit = ref(false)\r\nlet editorCtx: TinyMCEEditor | null = null\r\n\r\nconst task: Array<() => void> = []\r\n\r\nsetupDialogZIndexBridge()\r\n\r\nfunction getContentSync() {\r\n return editorCtx?.getContent() ?? ''\r\n}\r\n\r\nfunction setContentSync(value: string) {\r\n const result = editorCtx?.setContent(value ?? '')\r\n inputValue.value = getContentSync()\r\n return result\r\n}\r\n\r\nfunction insertContentSync(value: string) {\r\n editorCtx?.insertContent(value)\r\n inputValue.value = getContentSync()\r\n}\r\n\r\n/** Append tokens to a space-separated TinyMCE config string (word match). */\r\nfunction addStr(source: string, target: string | string[]) {\r\n const list = Array.isArray(target) ? target : [target]\r\n let result = source\r\n list.forEach((token) => {\r\n const trimmed = token.trim()\r\n if (!trimmed) return\r\n const re = new RegExp(`(?:^|\\\\s)${escapeRegExp(trimmed)}(?:\\\\s|$)`)\r\n if (!re.test(result)) {\r\n result = `${result} ${trimmed}`.trim()\r\n }\r\n })\r\n return result\r\n}\r\n\r\n/** Remove tokens from a space-separated TinyMCE config string. */\r\nfunction removeStr(source: string, target: string | string[]) {\r\n const list = Array.isArray(target) ? target : [target]\r\n let result = source\r\n list.forEach((token) => {\r\n const trimmed = token.trim()\r\n if (!trimmed) return\r\n const re = new RegExp(`(?:^|\\\\s)${escapeRegExp(trimmed)}(?=\\\\s|$)`, 'g')\r\n result = result.replace(re, ' ').replace(/\\s+/g, ' ').trim()\r\n })\r\n return result\r\n}\r\n\r\nfunction escapeRegExp(s: string) {\r\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\r\n}\r\n\r\nfunction initOpts(): Record<string, unknown> {\r\n const {\r\n includes,\r\n excludes,\r\n textPhrase,\r\n imagesUploadHandler,\r\n fetchTextPhrases,\r\n tinymceBaseUrl,\r\n languageUrl,\r\n ...restProps\r\n } = props\r\n\r\n const language = props.language === 'zh-cn' ? 'zh_CN' : props.language\r\n\r\n const baseOpts: Record<string, unknown> = {\r\n selector: `textarea#${editorId}`,\r\n language,\r\n plugins:\r\n 'advlist autolink link image lists charmap emoticons preview hr anchor pagebreak nonbreaking code table paste insertdatetime wordcount',\r\n menubar: 'file edit view insert format tools',\r\n menu: {\r\n insert: {\r\n title: 'Insert',\r\n items:\r\n 'image link inserttable | charmap emoticons hr | pagebreak nonbreaking anchor | insertdatetime',\r\n },\r\n },\r\n toolbar:\r\n 'undo redo | styleselect | fontsizeselect fontselect | bold italic forecolor backcolor | alignleft aligncenter alignright alignjustify | outdent indent | bullist numlist table | link image | code',\r\n toolbar_mode: 'wrap',\r\n fontsize_formats:\r\n '5px 6px 7px 8px 9px 10px 12px 14px 16px 18px 20px 22px 24px 26px 28px 30px 32px 34px 36px 48px 72px',\r\n font_formats:\r\n 'Andale Mono=andale mono,times;Arial=arial,helvetica,sans-serif;Arial Black=arial black,avant garde;Book Antiqua=book antiqua,palatino;Calibri;Calibri Light;Comic Sans MS=comic sans ms,sans-serif;Courier New=courier new,courier;Georgia=georgia,palatino;Helvetica=helvetica;Impact=impact,chicago;Symbol=symbol;Tahoma=tahoma,arial,;Terminal=terminal,monaco;Times New Roman=times new roman,times;Trebuchet MS=trebuchet ms,geneva;Verdana=verdana,geneva;Webdings=webdings;Wingdings=wingdings,;',\r\n min_height: 480,\r\n table_default_styles: { width: '100%', borderCollapse: 'collapse' },\r\n image_advtab: true,\r\n image_title: false,\r\n paste_data_images: true,\r\n images_upload_handler: (\r\n blobInfo: { blob: () => Blob },\r\n success: (url: string) => void,\r\n fail: (msg: string) => void,\r\n ) => {\r\n if (!imagesUploadHandler) {\r\n fail('imagesUploadHandler is not provided')\r\n return\r\n }\r\n imagesUploadHandler(blobInfo.blob())\r\n .then((url) => {\r\n if (url) success(url)\r\n else fail('Upload failed')\r\n })\r\n .catch(() => fail('Upload failed'))\r\n },\r\n setup(editor: TinyMCEEditor) {\r\n editorCtx = editor\r\n editor.on('init', () => {\r\n isInit.value = true\r\n const body = editorCtx?.getBody()\r\n if (body) {\r\n body.style.fontSize = '16px'\r\n body.style.fontFamily = 'Calibri'\r\n }\r\n setContentSync(inputValue.value)\r\n while (task.length) {\r\n task.shift()?.()\r\n }\r\n })\r\n editor.on('change', (e) => {\r\n inputValue.value = getContentSync()\r\n emits('change', inputValue.value, e)\r\n })\r\n editor.on('input', (e) => {\r\n emits('input', e as Event)\r\n })\r\n if (textPhrase) {\r\n if (!fetchTextPhrases) {\r\n console.warn(\r\n '[RdRichEditor] textPhrase is true but fetchTextPhrases is not provided',\r\n )\r\n } else {\r\n useQuickTextPhrase(editor, fetchTextPhrases)\r\n }\r\n }\r\n },\r\n }\r\n\r\n if (tinymceBaseUrl) {\r\n baseOpts.base_url = tinymceBaseUrl\r\n }\r\n if (languageUrl) {\r\n baseOpts.language_url = languageUrl\r\n }\r\n\r\n const result: Record<string, unknown> = {\r\n ...baseOpts,\r\n ...cleanObject(restProps),\r\n }\r\n\r\n if (includes) {\r\n ;(Object.keys(includes) as Array<keyof typeof includes>).forEach((name) => {\r\n const current = result[name]\r\n const extra = includes[name]\r\n if (typeof current === 'string' && extra != null) {\r\n result[name] = addStr(current, extra)\r\n }\r\n })\r\n }\r\n if (excludes) {\r\n ;(Object.keys(excludes) as Array<keyof typeof excludes>).forEach((name) => {\r\n const current = result[name]\r\n const remove = excludes[name]\r\n if (typeof current === 'string' && remove != null) {\r\n result[name] = removeStr(current, remove)\r\n }\r\n })\r\n }\r\n\r\n return result\r\n}\r\n\r\nfunction transferAsync<T extends (...args: any[]) => any>(fun: T) {\r\n return function (\r\n ...rest: Parameters<T>\r\n ): ReturnType<T> | Promise<ReturnType<T>> {\r\n if (!isInit.value) {\r\n return new Promise<ReturnType<T>>((resolve) => {\r\n task.push(() => resolve(fun(...rest)))\r\n })\r\n }\r\n return fun(...rest)\r\n }\r\n}\r\n\r\nfunction validate() {\r\n const tempDiv = document.createElement('div')\r\n tempDiv.innerHTML = inputValue.value\r\n if (tempDiv.textContent?.trim()) return true\r\n if (tempDiv.querySelector('a.mce-item-anchor, img, table')) return true\r\n return false\r\n}\r\n\r\nwatch(\r\n () => inputValue.value,\r\n (newVal) => {\r\n if (!isInit.value || newVal === getContentSync()) return\r\n setContentSync(newVal)\r\n },\r\n)\r\n\r\nonMounted(() => {\r\n const tinymce = window.tinymce\r\n if (!tinymce) {\r\n console.error(\r\n '[RdRichEditor] window.tinymce is missing. Load TinyMCE 5.10.x via <script> before using RdRichEditor.',\r\n )\r\n return\r\n }\r\n if (props.tinymceBaseUrl) {\r\n tinymce.baseURL = props.tinymceBaseUrl\r\n }\r\n void tinymce.init(initOpts())\r\n})\r\n\r\nonBeforeUnmount(() => {\r\n editorCtx?.remove()\r\n editorCtx = null\r\n})\r\n\r\ndefineExpose({\r\n getEditor: transferAsync(() => editorCtx),\r\n getContent: transferAsync(getContentSync),\r\n setContent: transferAsync(setContentSync),\r\n insertContent: transferAsync(insertContentSync),\r\n validate,\r\n})\r\n</script>\r\n\r\n<template>\r\n <div class=\"rd-rich-editor\">\r\n <textarea :id=\"editorId\" style=\"visibility: hidden\" />\r\n <div v-if=\"isInit\" class=\"rd-rich-editor-footer-bar\">\r\n <slot name=\"footer-bar\" />\r\n </div>\r\n </div>\r\n</template>\r\n","<script setup lang=\"ts\">\r\ndefineOptions({\r\n name: 'RdRichEditor',\r\n})\r\n\r\nimport { onBeforeUnmount, onMounted, ref, watch } from 'vue'\r\nimport type { TinyMCEEditor } from '../../types/tinymce'\r\nimport { cleanObject } from '../../utils/cleanObject'\r\nimport useQuickTextPhrase, { type TextPhraseItem } from './useQuickTextPhrase'\r\nimport { setupDialogZIndexBridge } from './dialogZIndexBridge'\r\nimport '../../styles/rd-rich-editor.css'\r\n\r\ninterface EditorConfigProps {\r\n plugins?: string | string[]\r\n language?: string\r\n menubar?: string | string[]\r\n toolbar?: string | string[]\r\n toolbar_mode?: string\r\n min_height?: number\r\n height?: number\r\n /** Enable `#` quick text phrase autocompleter (requires fetchTextPhrases) */\r\n textPhrase?: boolean\r\n}\r\n\r\ntype Props = EditorConfigProps & {\r\n includes?: Pick<EditorConfigProps, 'plugins' | 'menubar' | 'toolbar'>\r\n excludes?: Pick<EditorConfigProps, 'plugins' | 'menubar' | 'toolbar'>\r\n /**\r\n * Image upload hook. Return a publicly accessible URL.\r\n * When omitted, paste/insert image upload fails with a clear message.\r\n */\r\n imagesUploadHandler?: (blob: Blob) => Promise<string>\r\n /** Fetch quick text phrases when textPhrase is true */\r\n fetchTextPhrases?: () => Promise<TextPhraseItem[]>\r\n /** Override TinyMCE baseURL when static assets are not at the default path */\r\n tinymceBaseUrl?: string\r\n /**\r\n * TinyMCE language pack URL (language_url).\r\n * Needed when langs/*.js are not under tinymceBaseUrl (e.g. CDN demos).\r\n */\r\n languageUrl?: string\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n language: 'zh_CN',\r\n textPhrase: false,\r\n})\r\n\r\nconst emits = defineEmits<{\r\n input: [e: Event]\r\n change: [value: string, e: unknown]\r\n}>()\r\n\r\nconst inputValue = defineModel<string>({ default: '' })\r\nconst editorId = `rd-editor_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`\r\nconst isInit = ref(false)\r\nlet editorCtx: TinyMCEEditor | null = null\r\n\r\nconst task: Array<() => void> = []\r\n\r\nsetupDialogZIndexBridge()\r\n\r\nfunction getContentSync() {\r\n return editorCtx?.getContent() ?? ''\r\n}\r\n\r\nfunction setContentSync(value: string) {\r\n const result = editorCtx?.setContent(value ?? '')\r\n inputValue.value = getContentSync()\r\n return result\r\n}\r\n\r\nfunction insertContentSync(value: string) {\r\n editorCtx?.insertContent(value)\r\n inputValue.value = getContentSync()\r\n}\r\n\r\n/** Append tokens to a space-separated TinyMCE config string (word match). */\r\nfunction addStr(source: string, target: string | string[]) {\r\n const list = Array.isArray(target) ? target : [target]\r\n let result = source\r\n list.forEach((token) => {\r\n const trimmed = token.trim()\r\n if (!trimmed) return\r\n const re = new RegExp(`(?:^|\\\\s)${escapeRegExp(trimmed)}(?:\\\\s|$)`)\r\n if (!re.test(result)) {\r\n result = `${result} ${trimmed}`.trim()\r\n }\r\n })\r\n return result\r\n}\r\n\r\n/** Remove tokens from a space-separated TinyMCE config string. */\r\nfunction removeStr(source: string, target: string | string[]) {\r\n const list = Array.isArray(target) ? target : [target]\r\n let result = source\r\n list.forEach((token) => {\r\n const trimmed = token.trim()\r\n if (!trimmed) return\r\n const re = new RegExp(`(?:^|\\\\s)${escapeRegExp(trimmed)}(?=\\\\s|$)`, 'g')\r\n result = result.replace(re, ' ').replace(/\\s+/g, ' ').trim()\r\n })\r\n return result\r\n}\r\n\r\nfunction escapeRegExp(s: string) {\r\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\r\n}\r\n\r\nfunction initOpts(): Record<string, unknown> {\r\n const {\r\n includes,\r\n excludes,\r\n textPhrase,\r\n imagesUploadHandler,\r\n fetchTextPhrases,\r\n tinymceBaseUrl,\r\n languageUrl,\r\n ...restProps\r\n } = props\r\n\r\n const language = props.language === 'zh-cn' ? 'zh_CN' : props.language\r\n\r\n const baseOpts: Record<string, unknown> = {\r\n selector: `textarea#${editorId}`,\r\n language,\r\n plugins:\r\n 'advlist autolink link image lists charmap emoticons preview hr anchor pagebreak nonbreaking code table paste insertdatetime wordcount',\r\n menubar: 'file edit view insert format tools',\r\n menu: {\r\n insert: {\r\n title: 'Insert',\r\n items:\r\n 'image link inserttable | charmap emoticons hr | pagebreak nonbreaking anchor | insertdatetime',\r\n },\r\n },\r\n toolbar:\r\n 'undo redo | styleselect | fontsizeselect fontselect | bold italic forecolor backcolor | alignleft aligncenter alignright alignjustify | outdent indent | bullist numlist table | link image | code',\r\n toolbar_mode: 'wrap',\r\n fontsize_formats:\r\n '5px 6px 7px 8px 9px 10px 12px 14px 16px 18px 20px 22px 24px 26px 28px 30px 32px 34px 36px 48px 72px',\r\n font_formats:\r\n 'Andale Mono=andale mono,times;Arial=arial,helvetica,sans-serif;Arial Black=arial black,avant garde;Book Antiqua=book antiqua,palatino;Calibri;Calibri Light;Comic Sans MS=comic sans ms,sans-serif;Courier New=courier new,courier;Georgia=georgia,palatino;Helvetica=helvetica;Impact=impact,chicago;Symbol=symbol;Tahoma=tahoma,arial,;Terminal=terminal,monaco;Times New Roman=times new roman,times;Trebuchet MS=trebuchet ms,geneva;Verdana=verdana,geneva;Webdings=webdings;Wingdings=wingdings,;',\r\n min_height: 480,\r\n table_default_styles: { width: '100%', borderCollapse: 'collapse' },\r\n image_advtab: true,\r\n image_title: false,\r\n paste_data_images: true,\r\n images_upload_handler: (\r\n blobInfo: { blob: () => Blob },\r\n success: (url: string) => void,\r\n fail: (msg: string) => void,\r\n ) => {\r\n if (!imagesUploadHandler) {\r\n fail('imagesUploadHandler is not provided')\r\n return\r\n }\r\n imagesUploadHandler(blobInfo.blob())\r\n .then((url) => {\r\n if (url) success(url)\r\n else fail('Upload failed')\r\n })\r\n .catch(() => fail('Upload failed'))\r\n },\r\n setup(editor: TinyMCEEditor) {\r\n editorCtx = editor\r\n editor.on('init', () => {\r\n isInit.value = true\r\n const body = editorCtx?.getBody()\r\n if (body) {\r\n body.style.fontSize = '16px'\r\n body.style.fontFamily = 'Calibri'\r\n }\r\n setContentSync(inputValue.value)\r\n while (task.length) {\r\n task.shift()?.()\r\n }\r\n })\r\n editor.on('change', (e) => {\r\n inputValue.value = getContentSync()\r\n emits('change', inputValue.value, e)\r\n })\r\n editor.on('input', (e) => {\r\n emits('input', e as Event)\r\n })\r\n if (textPhrase) {\r\n if (!fetchTextPhrases) {\r\n console.warn(\r\n '[RdRichEditor] textPhrase is true but fetchTextPhrases is not provided',\r\n )\r\n } else {\r\n useQuickTextPhrase(editor, fetchTextPhrases)\r\n }\r\n }\r\n },\r\n }\r\n\r\n if (tinymceBaseUrl) {\r\n baseOpts.base_url = tinymceBaseUrl\r\n }\r\n if (languageUrl) {\r\n baseOpts.language_url = languageUrl\r\n }\r\n\r\n const result: Record<string, unknown> = {\r\n ...baseOpts,\r\n ...cleanObject(restProps),\r\n }\r\n\r\n if (includes) {\r\n ;(Object.keys(includes) as Array<keyof typeof includes>).forEach((name) => {\r\n const current = result[name]\r\n const extra = includes[name]\r\n if (typeof current === 'string' && extra != null) {\r\n result[name] = addStr(current, extra)\r\n }\r\n })\r\n }\r\n if (excludes) {\r\n ;(Object.keys(excludes) as Array<keyof typeof excludes>).forEach((name) => {\r\n const current = result[name]\r\n const remove = excludes[name]\r\n if (typeof current === 'string' && remove != null) {\r\n result[name] = removeStr(current, remove)\r\n }\r\n })\r\n }\r\n\r\n return result\r\n}\r\n\r\nfunction transferAsync<T extends (...args: any[]) => any>(fun: T) {\r\n return function (\r\n ...rest: Parameters<T>\r\n ): ReturnType<T> | Promise<ReturnType<T>> {\r\n if (!isInit.value) {\r\n return new Promise<ReturnType<T>>((resolve) => {\r\n task.push(() => resolve(fun(...rest)))\r\n })\r\n }\r\n return fun(...rest)\r\n }\r\n}\r\n\r\nfunction validate() {\r\n const tempDiv = document.createElement('div')\r\n tempDiv.innerHTML = inputValue.value\r\n if (tempDiv.textContent?.trim()) return true\r\n if (tempDiv.querySelector('a.mce-item-anchor, img, table')) return true\r\n return false\r\n}\r\n\r\nwatch(\r\n () => inputValue.value,\r\n (newVal) => {\r\n if (!isInit.value || newVal === getContentSync()) return\r\n setContentSync(newVal)\r\n },\r\n)\r\n\r\nonMounted(() => {\r\n const tinymce = window.tinymce\r\n if (!tinymce) {\r\n console.error(\r\n '[RdRichEditor] window.tinymce is missing. Load TinyMCE 5.10.x via <script> before using RdRichEditor.',\r\n )\r\n return\r\n }\r\n if (props.tinymceBaseUrl) {\r\n tinymce.baseURL = props.tinymceBaseUrl\r\n }\r\n void tinymce.init(initOpts())\r\n})\r\n\r\nonBeforeUnmount(() => {\r\n editorCtx?.remove()\r\n editorCtx = null\r\n})\r\n\r\ndefineExpose({\r\n getEditor: transferAsync(() => editorCtx),\r\n getContent: transferAsync(getContentSync),\r\n setContent: transferAsync(setContentSync),\r\n insertContent: transferAsync(insertContentSync),\r\n validate,\r\n})\r\n</script>\r\n\r\n<template>\r\n <div class=\"rd-rich-editor\">\r\n <textarea :id=\"editorId\" style=\"visibility: hidden\" />\r\n <div v-if=\"isInit\" class=\"rd-rich-editor-footer-bar\">\r\n <slot name=\"footer-bar\" />\r\n </div>\r\n </div>\r\n</template>\r\n","import type { App } from 'vue'\r\nimport { RdRichEditor } from './components/rdRichEditor'\r\nexport type { TextPhraseItem } from './components/rdRichEditor'\r\n\r\nexport { RdRichEditor }\r\nexport { RodelEditorResolver } from './resolver'\r\n\r\nimport './styles/rd-rich-editor.css'\r\n\r\nconst components = [RdRichEditor]\r\n\r\n/**\r\n * Register RdRichEditor. Host must load TinyMCE 5.10.x on window.tinymce.\r\n */\r\nexport function install(app: App): void {\r\n components.forEach((component) => {\r\n const name = component.name || (component as { __name?: string }).__name\r\n if (name) {\r\n app.component(name, component)\r\n }\r\n })\r\n}\r\n\r\nexport default install\r\n"],"mappings":"gLAGA,SAAgB,EAAe,EAAuC,CAGpE,OAFK,EAEE,OAAO,YACZ,OAAO,QAAQ,CAA8B,CAAC,CAAC,QAC5C,EAAG,KACF,IAAM,IACN,GAAM,MAEN,EAAE,MAAM,QAAQ,CAAC,GAAK,EAAE,SAAW,IACnC,EAAE,OAAO,GAAM,UAAY,GAAK,OAAO,KAAK,CAAW,CAAC,CAAC,SAAW,EACxE,CACF,EAXiB,CAAC,CAYpB,CCJA,SAAwB,EACtB,EACA,EACA,CACA,IAAI,EAAS,EACT,EAAoC,CAAC,EAEzC,SAAS,EAAoB,EAAwB,EAAiB,CACpE,OAAO,EACJ,OAAQ,GAAM,EAAE,WAAW,SAAS,CAAO,GAAK,EAAE,KAAK,SAAS,CAAO,CAAC,CAAC,CACzE,IAAK,IAAO,CACX,KAAM,eACN,MAAO,EACP,MAAO,EAAE,KACT,MAAO,CACL,CACE,KAAM,gBACN,UAAW,WACX,MAAO,CACL,CACE,KAAM,WACN,KAAM,EAAE,KACR,KAAM,EAAE,UACV,CACF,CACF,CACF,CACF,EAAE,CACN,CAEA,EAAO,GAAG,SAAS,iBAAiB,oBAAqB,CACvD,GAAI,IACJ,SAAU,EACV,QAAS,EACT,SAAU,EAAe,EAAc,IACpC,GAAQ,EAAK,SAAS,GAAG,GAAO,CAAC,CAAC,GAAW,EAAQ,OAAS,EACjE,MAAO,KAAO,IAAoB,CAChC,GAAI,IAAW,EAAG,MAAO,CAAC,EAC1B,GAAI,CAAC,EAAQ,CACX,EAAS,EACT,GAAI,CACF,EAAmB,MAAM,EAAiB,GAAM,CAAC,EACjD,EAAS,CACX,OAAS,EAAK,CAEZ,KADA,GAAS,EACH,CACR,CACF,CACA,OAAO,EAAoB,EAAiB,CAAO,CACrD,EACA,UACE,EACA,EACA,IACG,CACH,EAAI,eAAe,EACnB,EAAO,cAAc,EAAM,OAAO,EAClC,EAAiB,KAAK,CACxB,CACF,CAAC,CACH,CCjEA,SAAgB,GAAgC,CAE9C,GAAI,EAAA,EAAA,EAAA,OAAA,CADyB,EAAA,mBAAoB,IAC5C,EAAe,OAEpB,IAAM,GAAA,EAAA,EAAA,UAAA,CAAyB,CAAC,CAAC,eACjC,EAAA,EAAA,YAAA,KAAkB,CACZ,EAAa,MAAQ,MACvB,SAAS,gBAAgB,MAAM,YAC7B,sBACA,EAAa,MAAM,SAAS,CAC9B,CAEJ,CAAC,GACD,EAAA,EAAA,gBAAA,KAAsB,CACpB,SAAS,gBAAgB,MAAM,eAAe,qBAAqB,CACrE,CAAC,CACH,giBCoBA,IAAM,EAAQ,EAKR,EAAQ,EAKR,GAAA,EAAA,EAAA,SAAA,CAAgC,EAAA,YAAgB,EAChD,EAAW,aAAa,KAAK,IAAI,EAAE,GAAG,KAAK,OAAO,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,MAAM,EAAG,CAAC,IAC3E,GAAA,EAAA,EAAA,IAAA,CAAa,EAAK,EACpB,EAAkC,KAEhC,EAA0B,CAAC,EAEjC,EAAwB,EAExB,SAAS,GAAiB,CACxB,OAAO,GAAW,WAAW,GAAK,EACpC,CAEA,SAAS,EAAe,EAAe,CACrC,IAAM,EAAS,GAAW,WAAW,GAAS,EAAE,EAEhD,MADA,GAAW,MAAQ,EAAe,EAC3B,CACT,CAEA,SAAS,EAAkB,EAAe,CACxC,GAAW,cAAc,CAAK,EAC9B,EAAW,MAAQ,EAAe,CACpC,CAGA,SAAS,EAAO,EAAgB,EAA2B,CACzD,IAAM,EAAO,MAAM,QAAQ,CAAM,EAAI,EAAS,CAAC,CAAM,EACjD,EAAS,EASb,OARA,EAAK,QAAS,GAAU,CACtB,IAAM,EAAU,EAAM,KAAK,EACtB,IACU,OAAO,YAAY,EAAa,CAAO,EAAE,UACnD,CAAA,CAAG,KAAK,CAAM,IACjB,EAAS,GAAG,EAAO,GAAG,IAAU,KAAK,GAEzC,CAAC,EACM,CACT,CAGA,SAAS,EAAU,EAAgB,EAA2B,CAC5D,IAAM,EAAO,MAAM,QAAQ,CAAM,EAAI,EAAS,CAAC,CAAM,EACjD,EAAS,EAOb,OANA,EAAK,QAAS,GAAU,CACtB,IAAM,EAAU,EAAM,KAAK,EAC3B,GAAI,CAAC,EAAS,OACd,IAAM,EAAS,OAAO,YAAY,EAAa,CAAO,EAAE,WAAY,GAAG,EACvE,EAAS,EAAO,QAAQ,EAAI,GAAG,CAAC,CAAC,QAAQ,OAAQ,GAAG,CAAC,CAAC,KAAK,CAC7D,CAAC,EACM,CACT,CAEA,SAAS,EAAa,EAAW,CAC/B,OAAO,EAAE,QAAQ,sBAAuB,MAAM,CAChD,CAEA,SAAS,GAAoC,CAC3C,GAAM,CACJ,WACA,WACA,aACA,sBACA,mBACA,iBACA,cACA,GAAG,GACD,EAEE,EAAW,EAAM,WAAa,QAAU,QAAU,EAAM,SAExD,EAAoC,CACxC,SAAU,YAAY,IACtB,WACA,QACE,wIACF,QAAS,qCACT,KAAM,CACJ,OAAQ,CACN,MAAO,SACP,MACE,+FACJ,CACF,EACA,QACE,qMACF,aAAc,OACd,iBACE,sGACF,aACE,0eACF,WAAY,IACZ,qBAAsB,CAAE,MAAO,OAAQ,eAAgB,UAAW,EAClE,aAAc,GACd,YAAa,GACb,kBAAmB,GACnB,uBACE,EACA,EACA,IACG,CACH,GAAI,CAAC,EAAqB,CACxB,EAAK,qCAAqC,EAC1C,MACF,CACA,EAAoB,EAAS,KAAK,CAAC,CAAC,CACjC,KAAM,GAAQ,CACT,EAAK,EAAQ,CAAG,EACf,EAAK,eAAe,CAC3B,CAAC,CAAC,CACD,UAAY,EAAK,eAAe,CAAC,CACtC,EACA,MAAM,EAAuB,CAC3B,EAAY,EACZ,EAAO,GAAG,WAAc,CACtB,EAAO,MAAQ,GACf,IAAM,EAAO,GAAW,QAAQ,EAMhC,IALI,IACF,EAAK,MAAM,SAAW,OACtB,EAAK,MAAM,WAAa,WAE1B,EAAe,EAAW,KAAK,EACxB,EAAK,QACV,EAAK,MAAM,CAAC,GAAG,CAEnB,CAAC,EACD,EAAO,GAAG,SAAW,GAAM,CACzB,EAAW,MAAQ,EAAe,EAClC,EAAM,SAAU,EAAW,MAAO,CAAC,CACrC,CAAC,EACD,EAAO,GAAG,QAAU,GAAM,CACxB,EAAM,QAAS,CAAU,CAC3B,CAAC,EACG,IACG,EAKH,EAAmB,EAAQ,CAAgB,EAJ3C,QAAQ,KACN,wEACF,EAKN,CACF,EAEI,IACF,EAAS,SAAW,GAElB,IACF,EAAS,aAAe,GAG1B,IAAM,EAAkC,CACtC,GAAG,EACH,GAAG,EAAY,CAAS,CAC1B,EAqBA,OAnBI,GACD,OAAQ,KAAK,CAAQ,CAAC,CAAkC,QAAS,GAAS,CACzE,IAAM,EAAU,EAAO,GACjB,EAAQ,EAAS,GACnB,OAAO,GAAY,UAAY,GAAS,OAC1C,EAAO,GAAQ,EAAO,EAAS,CAAK,EAExC,CAAC,EAEC,GACD,OAAQ,KAAK,CAAQ,CAAC,CAAkC,QAAS,GAAS,CACzE,IAAM,EAAU,EAAO,GACjB,EAAS,EAAS,GACpB,OAAO,GAAY,UAAY,GAAU,OAC3C,EAAO,GAAQ,EAAU,EAAS,CAAM,EAE5C,CAAC,EAGI,CACT,CAEA,SAAS,EAAiD,EAAQ,CAChE,OAAO,SACL,GAAG,EACqC,CAMxC,OALK,EAAO,MAKL,EAAI,GAAG,CAAI,EAJT,IAAI,QAAwB,GAAY,CAC7C,EAAK,SAAW,EAAQ,EAAI,GAAG,CAAI,CAAC,CAAC,CACvC,CAAC,CAGL,CACF,CAEA,SAAS,GAAW,CAClB,IAAM,EAAU,SAAS,cAAc,KAAK,EAI5C,MAHA,GAAQ,UAAY,EAAW,MAE/B,GADI,EAAQ,aAAa,KAAK,GAC1B,EAAQ,cAAc,+BAA+B,EAE3D,QAEA,EAAA,EAAA,MAAA,KACQ,EAAW,MAChB,GAAW,CACN,CAAC,EAAO,OAAS,IAAW,EAAe,GAC/C,EAAe,CAAM,CACvB,CACF,GAEA,EAAA,EAAA,UAAA,KAAgB,CACd,IAAM,EAAU,OAAO,QACvB,GAAI,CAAC,EAAS,CACZ,QAAQ,MACN,uGACF,EACA,MACF,CACI,EAAM,iBACR,EAAQ,QAAU,EAAM,gBAE1B,EAAa,KAAK,EAAS,CAAC,CAC9B,CAAC,GAED,EAAA,EAAA,gBAAA,KAAsB,CACpB,GAAW,OAAO,EAClB,EAAY,IACd,CAAC,EAED,EAAa,CACX,UAAW,MAAoB,CAAS,EACxC,WAAY,EAAc,CAAc,EACxC,WAAY,EAAc,CAAc,EACxC,cAAe,EAAc,CAAiB,EAC9C,UACF,CAAC,qDASO,MALN,EAKM,EAAA,EAAA,EAAA,mBAAA,CAJkD,WAAA,CAA3C,GAAI,EAAU,MAAA,CAAA,WAAA,QAAA,IACd,EAAA,QAAA,EAAA,EAAA,UAAA,CAAA,GAAA,EAAA,EAAA,mBAAA,CAEL,MAFN,EAEM,EAAA,EAAA,EAAA,WAAA,CADsB,EAAA,OAAA,YAAA,CAAA,CAAA,IAAA,EAAA,EAAA,mBAAA,CAAA,GAAA,EAAA,CAAA,CAAA,ME3R1B,EAAa,CAAC,CAAY,EAKhC,SAAgB,EAAQ,EAAgB,CACtC,EAAW,QAAS,GAAc,CAChC,IAAM,EAAO,EAAU,MAAS,EAAkC,OAC9D,GACF,EAAI,UAAU,EAAM,CAAS,CAEjC,CAAC,CACH"}
1
+ {"version":3,"file":"index.cjs","names":[],"sources":["../src/utils/cleanObject.ts","../src/components/rdRichEditor/useQuickTextPhrase.ts","../src/components/rdRichEditor/dialogZIndexBridge.ts","../src/components/rdRichEditor/RdRichEditor.vue","../src/components/rdRichEditor/RdRichEditor.vue","../src/index.ts"],"sourcesContent":["/**\r\n * Remove empty values from a shallow object (null / undefined / '' / [] / {}).\r\n */\r\nexport function cleanObject<T>(obj: T | null | undefined): Partial<T> {\r\n if (!obj) return {}\r\n\r\n return Object.fromEntries(\r\n Object.entries(obj as Record<string, unknown>).filter(\r\n ([, v]) =>\r\n v !== '' &&\r\n v !== null &&\r\n v !== undefined &&\r\n !(Array.isArray(v) && v.length === 0) &&\r\n !(typeof v === 'object' && v && Object.keys(v as object).length === 0),\r\n ),\r\n ) as Partial<T>\r\n}\r\n","import type { TinyMCEEditor } from '../../types/tinymce'\r\n\r\nexport interface TextPhraseItem {\r\n name: string\r\n input_code: string\r\n content: string\r\n}\r\n\r\n/**\r\n * Register TinyMCE autocompleter for `#` quick text phrases.\r\n * Data is supplied by the host via `fetchTextPhrases`.\r\n */\r\nexport default function useQuickTextPhrase(\r\n editor: TinyMCEEditor,\r\n fetchTextPhrases: () => Promise<TextPhraseItem[]>,\r\n) {\r\n let quickTextPhrase: TextPhraseItem[] | null = null\r\n let pending: Promise<TextPhraseItem[]> | null = null\r\n\r\n function filterListByPattern(list: TextPhraseItem[], pattern: string) {\r\n return list\r\n .filter((v) => v.input_code.includes(pattern) || v.name.includes(pattern))\r\n .map((v) => ({\r\n type: 'cardmenuitem',\r\n value: v,\r\n label: v.name,\r\n items: [\r\n {\r\n type: 'cardcontainer',\r\n direction: 'vertical',\r\n items: [\r\n {\r\n type: 'cardtext',\r\n text: v.name,\r\n name: v.input_code,\r\n },\r\n ],\r\n },\r\n ],\r\n }))\r\n }\r\n\r\n editor.ui.registry.addAutocompleter('quick_text_phrase', {\r\n ch: '#',\r\n minChars: 0,\r\n columns: 1,\r\n matches: (_rng: unknown, text: string, pattern: string) =>\r\n (text && text.endsWith('#')) || (!!pattern && pattern.length < 6),\r\n fetch: async (pattern: string) => {\r\n if (quickTextPhrase === null) {\r\n if (!pending) {\r\n pending = fetchTextPhrases().then((items) => {\r\n quickTextPhrase = items || []\r\n return quickTextPhrase\r\n }).finally(() => {\r\n pending = null\r\n })\r\n }\r\n await pending\r\n }\r\n return filterListByPattern(quickTextPhrase!, pattern)\r\n },\r\n onAction: (\r\n autocompleterApi: { hide: () => void },\r\n rng: { deleteContents: () => void },\r\n value: TextPhraseItem,\r\n ) => {\r\n rng.deleteContents()\r\n editor.insertContent(value.content)\r\n autocompleterApi.hide()\r\n },\r\n })\r\n}\r\n","import { inject, onBeforeUnmount, watchEffect } from 'vue'\r\nimport { useZIndex, dialogInjectionKey } from 'element-plus'\r\n\r\n/**\r\n * When RdRichEditor is used inside ElDialog / RdDialog, raise TinyMCE\r\n * popover z-index above the dialog via --el-dialog-z-index.\r\n */\r\nexport function setupDialogZIndexBridge(): void {\r\n const dialogProvide = inject(dialogInjectionKey, null)\r\n if (!dialogProvide) return\r\n\r\n const dialogZIndex = useZIndex().currentZIndex\r\n watchEffect(() => {\r\n if (dialogZIndex.value > 1300) {\r\n document.documentElement.style.setProperty(\r\n '--el-dialog-z-index',\r\n dialogZIndex.value.toString(),\r\n )\r\n }\r\n })\r\n onBeforeUnmount(() => {\r\n document.documentElement.style.removeProperty('--el-dialog-z-index')\r\n })\r\n}\r\n","<script setup lang=\"ts\">\r\ndefineOptions({\r\n name: 'RdRichEditor',\r\n})\r\n\r\nimport { onBeforeUnmount, onMounted, ref, watch } from 'vue'\r\nimport type { TinyMCEEditor } from '../../types/tinymce'\r\nimport { cleanObject } from '../../utils/cleanObject'\r\nimport useQuickTextPhrase, { type TextPhraseItem } from './useQuickTextPhrase'\r\nimport { setupDialogZIndexBridge } from './dialogZIndexBridge'\r\nimport '../../styles/rd-rich-editor.css'\r\n\r\ninterface EditorConfigProps {\r\n plugins?: string | string[]\r\n language?: string\r\n menubar?: string | string[]\r\n toolbar?: string | string[]\r\n toolbar_mode?: string\r\n min_height?: number\r\n height?: number\r\n /** Enable `#` quick text phrase autocompleter (requires fetchTextPhrases) */\r\n textPhrase?: boolean\r\n}\r\n\r\ntype Props = EditorConfigProps & {\r\n includes?: Pick<EditorConfigProps, 'plugins' | 'menubar' | 'toolbar'>\r\n excludes?: Pick<EditorConfigProps, 'plugins' | 'menubar' | 'toolbar'>\r\n /**\r\n * Image upload hook. Return a publicly accessible URL.\r\n * When omitted, paste/insert image upload fails with a clear message.\r\n */\r\n imagesUploadHandler?: (blob: Blob) => Promise<string>\r\n /** Fetch quick text phrases when textPhrase is true */\r\n fetchTextPhrases?: () => Promise<TextPhraseItem[]>\r\n /** Override TinyMCE baseURL when static assets are not at the default path */\r\n tinymceBaseUrl?: string\r\n /**\r\n * TinyMCE language pack URL (language_url).\r\n * Needed when langs/*.js are not under tinymceBaseUrl (e.g. CDN demos).\r\n */\r\n languageUrl?: string\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n language: 'zh_CN',\r\n textPhrase: false,\r\n})\r\n\r\nconst emits = defineEmits<{\r\n input: [e: Event]\r\n change: [value: string, e: unknown]\r\n}>()\r\n\r\nconst inputValue = defineModel<string>({ default: '' })\r\nconst editorId = `rd-editor_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`\r\nconst isInit = ref(false)\r\nlet editorCtx: TinyMCEEditor | null = null\r\nlet contentObserver: MutationObserver | null = null\r\nlet cachedContent: string | null = null\r\n\r\nconst task: Array<() => void> = []\r\n\r\nsetupDialogZIndexBridge()\r\n\r\nfunction getContentSync() {\r\n const content = editorCtx?.getContent() ?? ''\r\n if (contentObserver) {\r\n contentObserver.takeRecords()\r\n cachedContent = content\r\n }\r\n return content\r\n}\r\n\r\nfunction setContentSync(value: string) {\r\n const result = editorCtx?.setContent(value ?? '')\r\n inputValue.value = getContentSync()\r\n return result\r\n}\r\n\r\nfunction insertContentSync(value: string) {\r\n editorCtx?.insertContent(value)\r\n inputValue.value = getContentSync()\r\n}\r\n\r\n/** Append tokens to a space-separated TinyMCE config string (word match). */\r\nfunction addStr(source: string, target: string | string[]) {\r\n const list = Array.isArray(target) ? target : [target]\r\n let result = source\r\n list.forEach((token) => {\r\n const trimmed = token.trim()\r\n if (!trimmed) return\r\n const re = new RegExp(`(?:^|\\\\s)${escapeRegExp(trimmed)}(?:\\\\s|$)`)\r\n if (!re.test(result)) {\r\n result = `${result} ${trimmed}`.trim()\r\n }\r\n })\r\n return result\r\n}\r\n\r\n/** Remove tokens from a space-separated TinyMCE config string. */\r\nfunction removeStr(source: string, target: string | string[]) {\r\n const list = Array.isArray(target) ? target : [target]\r\n let result = source\r\n list.forEach((token) => {\r\n const trimmed = token.trim()\r\n if (!trimmed) return\r\n const re = new RegExp(`(?:^|\\\\s)${escapeRegExp(trimmed)}(?=\\\\s|$)`, 'g')\r\n result = result.replace(re, ' ').replace(/\\s+/g, ' ').trim()\r\n })\r\n return result\r\n}\r\n\r\nfunction escapeRegExp(s: string) {\r\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\r\n}\r\n\r\nfunction initOpts(): Record<string, unknown> {\r\n const {\r\n includes,\r\n excludes,\r\n textPhrase,\r\n imagesUploadHandler,\r\n fetchTextPhrases,\r\n tinymceBaseUrl,\r\n languageUrl,\r\n ...restProps\r\n } = props\r\n\r\n const language = props.language === 'zh-cn' ? 'zh_CN' : props.language\r\n\r\n const baseOpts: Record<string, unknown> = {\r\n selector: `textarea#${editorId}`,\r\n language,\r\n plugins:\r\n 'advlist autolink link image lists charmap emoticons preview hr anchor pagebreak nonbreaking code table paste insertdatetime wordcount',\r\n menubar: 'file edit view insert format tools',\r\n menu: {\r\n insert: {\r\n title: 'Insert',\r\n items:\r\n 'image link inserttable | charmap emoticons hr | pagebreak nonbreaking anchor | insertdatetime',\r\n },\r\n },\r\n toolbar:\r\n 'undo redo | styleselect | fontsizeselect fontselect | bold italic forecolor backcolor | alignleft aligncenter alignright alignjustify | outdent indent | bullist numlist table | link image | code',\r\n toolbar_mode: 'wrap',\r\n fontsize_formats:\r\n '5px 6px 7px 8px 9px 10px 12px 14px 16px 18px 20px 22px 24px 26px 28px 30px 32px 34px 36px 48px 72px',\r\n font_formats:\r\n 'Andale Mono=andale mono,times;Arial=arial,helvetica,sans-serif;Arial Black=arial black,avant garde;Book Antiqua=book antiqua,palatino;Calibri;Calibri Light;Comic Sans MS=comic sans ms,sans-serif;Courier New=courier new,courier;Georgia=georgia,palatino;Helvetica=helvetica;Impact=impact,chicago;Symbol=symbol;Tahoma=tahoma,arial,;Terminal=terminal,monaco;Times New Roman=times new roman,times;Trebuchet MS=trebuchet ms,geneva;Verdana=verdana,geneva;Webdings=webdings;Wingdings=wingdings,;',\r\n min_height: 480,\r\n table_default_styles: { width: '100%', borderCollapse: 'collapse' },\r\n image_advtab: true,\r\n image_title: false,\r\n paste_data_images: true,\r\n images_upload_handler: (\r\n blobInfo: { blob: () => Blob },\r\n success: (url: string) => void,\r\n fail: (msg: string) => void,\r\n ) => {\r\n if (!imagesUploadHandler) {\r\n fail('imagesUploadHandler is not provided')\r\n return\r\n }\r\n imagesUploadHandler(blobInfo.blob())\r\n .then((url) => {\r\n if (url) success(url)\r\n else fail('Upload failed')\r\n })\r\n .catch(() => fail('Upload failed'))\r\n },\r\n setup(editor: TinyMCEEditor) {\r\n editorCtx = editor\r\n editor.on('init', () => {\r\n isInit.value = true\r\n const body = editorCtx?.getBody()\r\n if (body) {\r\n body.style.fontSize = '16px'\r\n body.style.fontFamily = 'Calibri'\r\n if (typeof MutationObserver !== 'undefined') {\r\n contentObserver = new MutationObserver(() => { cachedContent = null })\r\n contentObserver.observe(body, { subtree: true, childList: true, characterData: true, attributes: true })\r\n }\r\n }\r\n setContentSync(inputValue.value)\r\n while (task.length) {\r\n task.shift()?.()\r\n }\r\n })\r\n editor.on('change', (e) => {\r\n const content = getContentSync()\r\n inputValue.value = content\r\n emits('change', content, e)\r\n })\r\n editor.on('input', (e) => {\r\n cachedContent = null\r\n emits('input', e as Event)\r\n })\r\n editor.on('BeforeSetContent SetContent Undo Redo', () => { cachedContent = null })\r\n if (textPhrase) {\r\n if (!fetchTextPhrases) {\r\n console.warn(\r\n '[RdRichEditor] textPhrase is true but fetchTextPhrases is not provided',\r\n )\r\n } else {\r\n useQuickTextPhrase(editor, fetchTextPhrases)\r\n }\r\n }\r\n },\r\n }\r\n\r\n if (tinymceBaseUrl) {\r\n baseOpts.base_url = tinymceBaseUrl\r\n }\r\n if (languageUrl) {\r\n baseOpts.language_url = languageUrl\r\n }\r\n\r\n const result: Record<string, unknown> = {\r\n ...baseOpts,\r\n ...cleanObject(restProps),\r\n }\r\n\r\n if (includes) {\r\n ;(Object.keys(includes) as Array<keyof typeof includes>).forEach((name) => {\r\n const current = result[name]\r\n const extra = includes[name]\r\n if (typeof current === 'string' && extra != null) {\r\n result[name] = addStr(current, extra)\r\n }\r\n })\r\n }\r\n if (excludes) {\r\n ;(Object.keys(excludes) as Array<keyof typeof excludes>).forEach((name) => {\r\n const current = result[name]\r\n const remove = excludes[name]\r\n if (typeof current === 'string' && remove != null) {\r\n result[name] = removeStr(current, remove)\r\n }\r\n })\r\n }\r\n\r\n return result\r\n}\r\n\r\nfunction transferAsync<T extends (...args: any[]) => any>(fun: T) {\r\n return function (\r\n ...rest: Parameters<T>\r\n ): ReturnType<T> | Promise<ReturnType<T>> {\r\n if (!isInit.value) {\r\n return new Promise<ReturnType<T>>((resolve) => {\r\n task.push(() => resolve(fun(...rest)))\r\n })\r\n }\r\n return fun(...rest)\r\n }\r\n}\r\n\r\nfunction validate() {\r\n const tempDiv = document.createElement('div')\r\n tempDiv.innerHTML = inputValue.value\r\n if (tempDiv.textContent?.trim()) return true\r\n if (tempDiv.querySelector('a.mce-item-anchor, img, table')) return true\r\n return false\r\n}\r\n\r\nwatch(\r\n () => inputValue.value,\r\n (newVal) => {\r\n if (!isInit.value) return\r\n if (contentObserver?.takeRecords().length) cachedContent = null\r\n if (newVal === (cachedContent ?? getContentSync())) return\r\n setContentSync(newVal)\r\n },\r\n)\r\n\r\nonMounted(() => {\r\n const tinymce = window.tinymce\r\n if (!tinymce) {\r\n console.error(\r\n '[RdRichEditor] window.tinymce is missing. Load TinyMCE 5.10.x via <script> before using RdRichEditor.',\r\n )\r\n return\r\n }\r\n if (props.tinymceBaseUrl) {\r\n tinymce.baseURL = props.tinymceBaseUrl\r\n }\r\n void tinymce.init(initOpts())\r\n})\r\n\r\nonBeforeUnmount(() => {\r\n contentObserver?.disconnect()\r\n contentObserver = null\r\n cachedContent = null\r\n editorCtx?.remove()\r\n editorCtx = null\r\n})\r\n\r\ndefineExpose({\r\n getEditor: transferAsync(() => editorCtx),\r\n getContent: transferAsync(getContentSync),\r\n setContent: transferAsync(setContentSync),\r\n insertContent: transferAsync(insertContentSync),\r\n validate,\r\n})\r\n</script>\r\n\r\n<template>\r\n <div class=\"rd-rich-editor\">\r\n <textarea :id=\"editorId\" style=\"visibility: hidden\" />\r\n <div v-if=\"isInit\" class=\"rd-rich-editor-footer-bar\">\r\n <slot name=\"footer-bar\" />\r\n </div>\r\n </div>\r\n</template>\r\n","<script setup lang=\"ts\">\r\ndefineOptions({\r\n name: 'RdRichEditor',\r\n})\r\n\r\nimport { onBeforeUnmount, onMounted, ref, watch } from 'vue'\r\nimport type { TinyMCEEditor } from '../../types/tinymce'\r\nimport { cleanObject } from '../../utils/cleanObject'\r\nimport useQuickTextPhrase, { type TextPhraseItem } from './useQuickTextPhrase'\r\nimport { setupDialogZIndexBridge } from './dialogZIndexBridge'\r\nimport '../../styles/rd-rich-editor.css'\r\n\r\ninterface EditorConfigProps {\r\n plugins?: string | string[]\r\n language?: string\r\n menubar?: string | string[]\r\n toolbar?: string | string[]\r\n toolbar_mode?: string\r\n min_height?: number\r\n height?: number\r\n /** Enable `#` quick text phrase autocompleter (requires fetchTextPhrases) */\r\n textPhrase?: boolean\r\n}\r\n\r\ntype Props = EditorConfigProps & {\r\n includes?: Pick<EditorConfigProps, 'plugins' | 'menubar' | 'toolbar'>\r\n excludes?: Pick<EditorConfigProps, 'plugins' | 'menubar' | 'toolbar'>\r\n /**\r\n * Image upload hook. Return a publicly accessible URL.\r\n * When omitted, paste/insert image upload fails with a clear message.\r\n */\r\n imagesUploadHandler?: (blob: Blob) => Promise<string>\r\n /** Fetch quick text phrases when textPhrase is true */\r\n fetchTextPhrases?: () => Promise<TextPhraseItem[]>\r\n /** Override TinyMCE baseURL when static assets are not at the default path */\r\n tinymceBaseUrl?: string\r\n /**\r\n * TinyMCE language pack URL (language_url).\r\n * Needed when langs/*.js are not under tinymceBaseUrl (e.g. CDN demos).\r\n */\r\n languageUrl?: string\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n language: 'zh_CN',\r\n textPhrase: false,\r\n})\r\n\r\nconst emits = defineEmits<{\r\n input: [e: Event]\r\n change: [value: string, e: unknown]\r\n}>()\r\n\r\nconst inputValue = defineModel<string>({ default: '' })\r\nconst editorId = `rd-editor_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`\r\nconst isInit = ref(false)\r\nlet editorCtx: TinyMCEEditor | null = null\r\nlet contentObserver: MutationObserver | null = null\r\nlet cachedContent: string | null = null\r\n\r\nconst task: Array<() => void> = []\r\n\r\nsetupDialogZIndexBridge()\r\n\r\nfunction getContentSync() {\r\n const content = editorCtx?.getContent() ?? ''\r\n if (contentObserver) {\r\n contentObserver.takeRecords()\r\n cachedContent = content\r\n }\r\n return content\r\n}\r\n\r\nfunction setContentSync(value: string) {\r\n const result = editorCtx?.setContent(value ?? '')\r\n inputValue.value = getContentSync()\r\n return result\r\n}\r\n\r\nfunction insertContentSync(value: string) {\r\n editorCtx?.insertContent(value)\r\n inputValue.value = getContentSync()\r\n}\r\n\r\n/** Append tokens to a space-separated TinyMCE config string (word match). */\r\nfunction addStr(source: string, target: string | string[]) {\r\n const list = Array.isArray(target) ? target : [target]\r\n let result = source\r\n list.forEach((token) => {\r\n const trimmed = token.trim()\r\n if (!trimmed) return\r\n const re = new RegExp(`(?:^|\\\\s)${escapeRegExp(trimmed)}(?:\\\\s|$)`)\r\n if (!re.test(result)) {\r\n result = `${result} ${trimmed}`.trim()\r\n }\r\n })\r\n return result\r\n}\r\n\r\n/** Remove tokens from a space-separated TinyMCE config string. */\r\nfunction removeStr(source: string, target: string | string[]) {\r\n const list = Array.isArray(target) ? target : [target]\r\n let result = source\r\n list.forEach((token) => {\r\n const trimmed = token.trim()\r\n if (!trimmed) return\r\n const re = new RegExp(`(?:^|\\\\s)${escapeRegExp(trimmed)}(?=\\\\s|$)`, 'g')\r\n result = result.replace(re, ' ').replace(/\\s+/g, ' ').trim()\r\n })\r\n return result\r\n}\r\n\r\nfunction escapeRegExp(s: string) {\r\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\r\n}\r\n\r\nfunction initOpts(): Record<string, unknown> {\r\n const {\r\n includes,\r\n excludes,\r\n textPhrase,\r\n imagesUploadHandler,\r\n fetchTextPhrases,\r\n tinymceBaseUrl,\r\n languageUrl,\r\n ...restProps\r\n } = props\r\n\r\n const language = props.language === 'zh-cn' ? 'zh_CN' : props.language\r\n\r\n const baseOpts: Record<string, unknown> = {\r\n selector: `textarea#${editorId}`,\r\n language,\r\n plugins:\r\n 'advlist autolink link image lists charmap emoticons preview hr anchor pagebreak nonbreaking code table paste insertdatetime wordcount',\r\n menubar: 'file edit view insert format tools',\r\n menu: {\r\n insert: {\r\n title: 'Insert',\r\n items:\r\n 'image link inserttable | charmap emoticons hr | pagebreak nonbreaking anchor | insertdatetime',\r\n },\r\n },\r\n toolbar:\r\n 'undo redo | styleselect | fontsizeselect fontselect | bold italic forecolor backcolor | alignleft aligncenter alignright alignjustify | outdent indent | bullist numlist table | link image | code',\r\n toolbar_mode: 'wrap',\r\n fontsize_formats:\r\n '5px 6px 7px 8px 9px 10px 12px 14px 16px 18px 20px 22px 24px 26px 28px 30px 32px 34px 36px 48px 72px',\r\n font_formats:\r\n 'Andale Mono=andale mono,times;Arial=arial,helvetica,sans-serif;Arial Black=arial black,avant garde;Book Antiqua=book antiqua,palatino;Calibri;Calibri Light;Comic Sans MS=comic sans ms,sans-serif;Courier New=courier new,courier;Georgia=georgia,palatino;Helvetica=helvetica;Impact=impact,chicago;Symbol=symbol;Tahoma=tahoma,arial,;Terminal=terminal,monaco;Times New Roman=times new roman,times;Trebuchet MS=trebuchet ms,geneva;Verdana=verdana,geneva;Webdings=webdings;Wingdings=wingdings,;',\r\n min_height: 480,\r\n table_default_styles: { width: '100%', borderCollapse: 'collapse' },\r\n image_advtab: true,\r\n image_title: false,\r\n paste_data_images: true,\r\n images_upload_handler: (\r\n blobInfo: { blob: () => Blob },\r\n success: (url: string) => void,\r\n fail: (msg: string) => void,\r\n ) => {\r\n if (!imagesUploadHandler) {\r\n fail('imagesUploadHandler is not provided')\r\n return\r\n }\r\n imagesUploadHandler(blobInfo.blob())\r\n .then((url) => {\r\n if (url) success(url)\r\n else fail('Upload failed')\r\n })\r\n .catch(() => fail('Upload failed'))\r\n },\r\n setup(editor: TinyMCEEditor) {\r\n editorCtx = editor\r\n editor.on('init', () => {\r\n isInit.value = true\r\n const body = editorCtx?.getBody()\r\n if (body) {\r\n body.style.fontSize = '16px'\r\n body.style.fontFamily = 'Calibri'\r\n if (typeof MutationObserver !== 'undefined') {\r\n contentObserver = new MutationObserver(() => { cachedContent = null })\r\n contentObserver.observe(body, { subtree: true, childList: true, characterData: true, attributes: true })\r\n }\r\n }\r\n setContentSync(inputValue.value)\r\n while (task.length) {\r\n task.shift()?.()\r\n }\r\n })\r\n editor.on('change', (e) => {\r\n const content = getContentSync()\r\n inputValue.value = content\r\n emits('change', content, e)\r\n })\r\n editor.on('input', (e) => {\r\n cachedContent = null\r\n emits('input', e as Event)\r\n })\r\n editor.on('BeforeSetContent SetContent Undo Redo', () => { cachedContent = null })\r\n if (textPhrase) {\r\n if (!fetchTextPhrases) {\r\n console.warn(\r\n '[RdRichEditor] textPhrase is true but fetchTextPhrases is not provided',\r\n )\r\n } else {\r\n useQuickTextPhrase(editor, fetchTextPhrases)\r\n }\r\n }\r\n },\r\n }\r\n\r\n if (tinymceBaseUrl) {\r\n baseOpts.base_url = tinymceBaseUrl\r\n }\r\n if (languageUrl) {\r\n baseOpts.language_url = languageUrl\r\n }\r\n\r\n const result: Record<string, unknown> = {\r\n ...baseOpts,\r\n ...cleanObject(restProps),\r\n }\r\n\r\n if (includes) {\r\n ;(Object.keys(includes) as Array<keyof typeof includes>).forEach((name) => {\r\n const current = result[name]\r\n const extra = includes[name]\r\n if (typeof current === 'string' && extra != null) {\r\n result[name] = addStr(current, extra)\r\n }\r\n })\r\n }\r\n if (excludes) {\r\n ;(Object.keys(excludes) as Array<keyof typeof excludes>).forEach((name) => {\r\n const current = result[name]\r\n const remove = excludes[name]\r\n if (typeof current === 'string' && remove != null) {\r\n result[name] = removeStr(current, remove)\r\n }\r\n })\r\n }\r\n\r\n return result\r\n}\r\n\r\nfunction transferAsync<T extends (...args: any[]) => any>(fun: T) {\r\n return function (\r\n ...rest: Parameters<T>\r\n ): ReturnType<T> | Promise<ReturnType<T>> {\r\n if (!isInit.value) {\r\n return new Promise<ReturnType<T>>((resolve) => {\r\n task.push(() => resolve(fun(...rest)))\r\n })\r\n }\r\n return fun(...rest)\r\n }\r\n}\r\n\r\nfunction validate() {\r\n const tempDiv = document.createElement('div')\r\n tempDiv.innerHTML = inputValue.value\r\n if (tempDiv.textContent?.trim()) return true\r\n if (tempDiv.querySelector('a.mce-item-anchor, img, table')) return true\r\n return false\r\n}\r\n\r\nwatch(\r\n () => inputValue.value,\r\n (newVal) => {\r\n if (!isInit.value) return\r\n if (contentObserver?.takeRecords().length) cachedContent = null\r\n if (newVal === (cachedContent ?? getContentSync())) return\r\n setContentSync(newVal)\r\n },\r\n)\r\n\r\nonMounted(() => {\r\n const tinymce = window.tinymce\r\n if (!tinymce) {\r\n console.error(\r\n '[RdRichEditor] window.tinymce is missing. Load TinyMCE 5.10.x via <script> before using RdRichEditor.',\r\n )\r\n return\r\n }\r\n if (props.tinymceBaseUrl) {\r\n tinymce.baseURL = props.tinymceBaseUrl\r\n }\r\n void tinymce.init(initOpts())\r\n})\r\n\r\nonBeforeUnmount(() => {\r\n contentObserver?.disconnect()\r\n contentObserver = null\r\n cachedContent = null\r\n editorCtx?.remove()\r\n editorCtx = null\r\n})\r\n\r\ndefineExpose({\r\n getEditor: transferAsync(() => editorCtx),\r\n getContent: transferAsync(getContentSync),\r\n setContent: transferAsync(setContentSync),\r\n insertContent: transferAsync(insertContentSync),\r\n validate,\r\n})\r\n</script>\r\n\r\n<template>\r\n <div class=\"rd-rich-editor\">\r\n <textarea :id=\"editorId\" style=\"visibility: hidden\" />\r\n <div v-if=\"isInit\" class=\"rd-rich-editor-footer-bar\">\r\n <slot name=\"footer-bar\" />\r\n </div>\r\n </div>\r\n</template>\r\n","import type { App } from 'vue'\r\nimport { RdRichEditor } from './components/rdRichEditor'\r\nexport type { TextPhraseItem } from './components/rdRichEditor'\r\n\r\nexport { RdRichEditor }\r\nexport { RodelEditorResolver } from './resolver'\r\n\r\nimport './styles/rd-rich-editor.css'\r\n\r\nconst components = [RdRichEditor]\r\n\r\n/**\r\n * Register RdRichEditor. Host must load TinyMCE 5.10.x on window.tinymce.\r\n */\r\nexport function install(app: App): void {\r\n components.forEach((component) => {\r\n const name = component.name || (component as { __name?: string }).__name\r\n if (name) {\r\n app.component(name, component)\r\n }\r\n })\r\n}\r\n\r\nexport default install\r\n"],"mappings":"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"}
package/dist/index.js CHANGED
@@ -8,7 +8,7 @@ function _(e) {
8
8
  //#endregion
9
9
  //#region src/components/rdRichEditor/useQuickTextPhrase.ts
10
10
  function v(e, t) {
11
- let n = 0, r = [];
11
+ let n = null, r = null;
12
12
  function i(e, t) {
13
13
  return e.filter((e) => e.input_code.includes(t) || e.name.includes(t)).map((e) => ({
14
14
  type: "cardmenuitem",
@@ -30,18 +30,9 @@ function v(e, t) {
30
30
  minChars: 0,
31
31
  columns: 1,
32
32
  matches: (e, t, n) => t && t.endsWith("#") || !!n && n.length < 6,
33
- fetch: async (e) => {
34
- if (n === 1) return [];
35
- if (!n) {
36
- n = 1;
37
- try {
38
- r = await t() || [], n = 2;
39
- } catch (e) {
40
- throw n = 0, e;
41
- }
42
- }
43
- return i(r, e);
44
- },
33
+ fetch: async (e) => (n === null && (r ||= t().then((e) => (n = e || [], n)).finally(() => {
34
+ r = null;
35
+ }), await r), i(n, e)),
45
36
  onAction: (t, n, r) => {
46
37
  n.deleteContents(), e.insertContent(r.content), t.hide();
47
38
  }
@@ -90,38 +81,39 @@ var b = { class: "rd-rich-editor" }, x = {
90
81
  }),
91
82
  emits: /*@__PURE__*/ o(["input", "change"], ["update:modelValue"]),
92
83
  setup(e, { expose: i, emit: a }) {
93
- let o = e, m = a, h = f(e, "modelValue"), g = `rd-editor_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`, S = u(!1), C = null, w = [];
84
+ let o = e, m = a, h = f(e, "modelValue"), g = `rd-editor_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`, S = u(!1), C = null, w = null, T = null, E = [];
94
85
  y();
95
- function T() {
96
- return C?.getContent() ?? "";
86
+ function D() {
87
+ let e = C?.getContent() ?? "";
88
+ return w && (w.takeRecords(), T = e), e;
97
89
  }
98
- function E(e) {
90
+ function O(e) {
99
91
  let t = C?.setContent(e ?? "");
100
- return h.value = T(), t;
92
+ return h.value = D(), t;
101
93
  }
102
- function D(e) {
103
- C?.insertContent(e), h.value = T();
94
+ function k(e) {
95
+ C?.insertContent(e), h.value = D();
104
96
  }
105
- function O(e, t) {
97
+ function A(e, t) {
106
98
  let n = Array.isArray(t) ? t : [t], r = e;
107
99
  return n.forEach((e) => {
108
100
  let t = e.trim();
109
- t && (RegExp(`(?:^|\\s)${A(t)}(?:\\s|$)`).test(r) || (r = `${r} ${t}`.trim()));
101
+ t && (RegExp(`(?:^|\\s)${M(t)}(?:\\s|$)`).test(r) || (r = `${r} ${t}`.trim()));
110
102
  }), r;
111
103
  }
112
- function k(e, t) {
104
+ function j(e, t) {
113
105
  let n = Array.isArray(t) ? t : [t], r = e;
114
106
  return n.forEach((e) => {
115
107
  let t = e.trim();
116
108
  if (!t) return;
117
- let n = RegExp(`(?:^|\\s)${A(t)}(?=\\s|$)`, "g");
109
+ let n = RegExp(`(?:^|\\s)${M(t)}(?=\\s|$)`, "g");
118
110
  r = r.replace(n, " ").replace(/\s+/g, " ").trim();
119
111
  }), r;
120
112
  }
121
- function A(e) {
113
+ function M(e) {
122
114
  return e.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
123
115
  }
124
- function j() {
116
+ function N() {
125
117
  let { includes: e, excludes: t, textPhrase: n, imagesUploadHandler: r, fetchTextPhrases: i, tinymceBaseUrl: a, languageUrl: s, ...c } = o, l = o.language === "zh-cn" ? "zh_CN" : o.language, u = {
126
118
  selector: `textarea#${g}`,
127
119
  language: l,
@@ -156,11 +148,21 @@ var b = { class: "rd-rich-editor" }, x = {
156
148
  C = e, e.on("init", () => {
157
149
  S.value = !0;
158
150
  let e = C?.getBody();
159
- for (e && (e.style.fontSize = "16px", e.style.fontFamily = "Calibri"), E(h.value); w.length;) w.shift()?.();
151
+ for (e && (e.style.fontSize = "16px", e.style.fontFamily = "Calibri", typeof MutationObserver < "u" && (w = new MutationObserver(() => {
152
+ T = null;
153
+ }), w.observe(e, {
154
+ subtree: !0,
155
+ childList: !0,
156
+ characterData: !0,
157
+ attributes: !0
158
+ }))), O(h.value); E.length;) E.shift()?.();
160
159
  }), e.on("change", (e) => {
161
- h.value = T(), m("change", h.value, e);
160
+ let t = D();
161
+ h.value = t, m("change", t, e);
162
162
  }), e.on("input", (e) => {
163
- m("input", e);
163
+ T = null, m("input", e);
164
+ }), e.on("BeforeSetContent SetContent Undo Redo", () => {
165
+ T = null;
164
166
  }), n && (i ? v(e, i) : console.warn("[RdRichEditor] textPhrase is true but fetchTextPhrases is not provided"));
165
167
  }
166
168
  };
@@ -171,40 +173,40 @@ var b = { class: "rd-rich-editor" }, x = {
171
173
  };
172
174
  return e && Object.keys(e).forEach((t) => {
173
175
  let n = d[t], r = e[t];
174
- typeof n == "string" && r != null && (d[t] = O(n, r));
176
+ typeof n == "string" && r != null && (d[t] = A(n, r));
175
177
  }), t && Object.keys(t).forEach((e) => {
176
178
  let n = d[e], r = t[e];
177
- typeof n == "string" && r != null && (d[e] = k(n, r));
179
+ typeof n == "string" && r != null && (d[e] = j(n, r));
178
180
  }), d;
179
181
  }
180
- function M(e) {
182
+ function P(e) {
181
183
  return function(...t) {
182
184
  return S.value ? e(...t) : new Promise((n) => {
183
- w.push(() => n(e(...t)));
185
+ E.push(() => n(e(...t)));
184
186
  });
185
187
  };
186
188
  }
187
- function N() {
189
+ function F() {
188
190
  let e = document.createElement("div");
189
191
  return e.innerHTML = h.value, !!(e.textContent?.trim() || e.querySelector("a.mce-item-anchor, img, table"));
190
192
  }
191
193
  return p(() => h.value, (e) => {
192
- !S.value || e === T() || E(e);
194
+ S.value && (w?.takeRecords().length && (T = null), e !== (T ?? D()) && O(e));
193
195
  }), c(() => {
194
196
  let e = window.tinymce;
195
197
  if (!e) {
196
198
  console.error("[RdRichEditor] window.tinymce is missing. Load TinyMCE 5.10.x via <script> before using RdRichEditor.");
197
199
  return;
198
200
  }
199
- o.tinymceBaseUrl && (e.baseURL = o.tinymceBaseUrl), e.init(j());
201
+ o.tinymceBaseUrl && (e.baseURL = o.tinymceBaseUrl), e.init(N());
200
202
  }), s(() => {
201
- C?.remove(), C = null;
203
+ w?.disconnect(), w = null, T = null, C?.remove(), C = null;
202
204
  }), i({
203
- getEditor: M(() => C),
204
- getContent: M(T),
205
- setContent: M(E),
206
- insertContent: M(D),
207
- validate: N
205
+ getEditor: P(() => C),
206
+ getContent: P(D),
207
+ setContent: P(O),
208
+ insertContent: P(k),
209
+ validate: F
208
210
  }), (e, i) => (l(), n("div", b, [r("textarea", {
209
211
  id: g,
210
212
  style: { visibility: "hidden" }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/utils/cleanObject.ts","../src/components/rdRichEditor/useQuickTextPhrase.ts","../src/components/rdRichEditor/dialogZIndexBridge.ts","../src/components/rdRichEditor/RdRichEditor.vue","../src/components/rdRichEditor/RdRichEditor.vue","../src/index.ts"],"sourcesContent":["/**\r\n * Remove empty values from a shallow object (null / undefined / '' / [] / {}).\r\n */\r\nexport function cleanObject<T>(obj: T | null | undefined): Partial<T> {\r\n if (!obj) return {}\r\n\r\n return Object.fromEntries(\r\n Object.entries(obj as Record<string, unknown>).filter(\r\n ([, v]) =>\r\n v !== '' &&\r\n v !== null &&\r\n v !== undefined &&\r\n !(Array.isArray(v) && v.length === 0) &&\r\n !(typeof v === 'object' && v && Object.keys(v as object).length === 0),\r\n ),\r\n ) as Partial<T>\r\n}\r\n","import type { TinyMCEEditor } from '../../types/tinymce'\r\n\r\nexport interface TextPhraseItem {\r\n name: string\r\n input_code: string\r\n content: string\r\n}\r\n\r\n/**\r\n * Register TinyMCE autocompleter for `#` quick text phrases.\r\n * Data is supplied by the host via `fetchTextPhrases`.\r\n */\r\nexport default function useQuickTextPhrase(\r\n editor: TinyMCEEditor,\r\n fetchTextPhrases: () => Promise<TextPhraseItem[]>,\r\n) {\r\n let status = 0 // 0 idle | 1 loading | 2 ready\r\n let quickTextPhrase: TextPhraseItem[] = []\r\n\r\n function filterListByPattern(list: TextPhraseItem[], pattern: string) {\r\n return list\r\n .filter((v) => v.input_code.includes(pattern) || v.name.includes(pattern))\r\n .map((v) => ({\r\n type: 'cardmenuitem',\r\n value: v,\r\n label: v.name,\r\n items: [\r\n {\r\n type: 'cardcontainer',\r\n direction: 'vertical',\r\n items: [\r\n {\r\n type: 'cardtext',\r\n text: v.name,\r\n name: v.input_code,\r\n },\r\n ],\r\n },\r\n ],\r\n }))\r\n }\r\n\r\n editor.ui.registry.addAutocompleter('quick_text_phrase', {\r\n ch: '#',\r\n minChars: 0,\r\n columns: 1,\r\n matches: (_rng: unknown, text: string, pattern: string) =>\r\n (text && text.endsWith('#')) || (!!pattern && pattern.length < 6),\r\n fetch: async (pattern: string) => {\r\n if (status === 1) return []\r\n if (!status) {\r\n status = 1\r\n try {\r\n quickTextPhrase = (await fetchTextPhrases()) || []\r\n status = 2\r\n } catch (err) {\r\n status = 0\r\n throw err\r\n }\r\n }\r\n return filterListByPattern(quickTextPhrase, pattern)\r\n },\r\n onAction: (\r\n autocompleterApi: { hide: () => void },\r\n rng: { deleteContents: () => void },\r\n value: TextPhraseItem,\r\n ) => {\r\n rng.deleteContents()\r\n editor.insertContent(value.content)\r\n autocompleterApi.hide()\r\n },\r\n })\r\n}\r\n","import { inject, onBeforeUnmount, watchEffect } from 'vue'\r\nimport { useZIndex, dialogInjectionKey } from 'element-plus'\r\n\r\n/**\r\n * When RdRichEditor is used inside ElDialog / RdDialog, raise TinyMCE\r\n * popover z-index above the dialog via --el-dialog-z-index.\r\n */\r\nexport function setupDialogZIndexBridge(): void {\r\n const dialogProvide = inject(dialogInjectionKey, null)\r\n if (!dialogProvide) return\r\n\r\n const dialogZIndex = useZIndex().currentZIndex\r\n watchEffect(() => {\r\n if (dialogZIndex.value > 1300) {\r\n document.documentElement.style.setProperty(\r\n '--el-dialog-z-index',\r\n dialogZIndex.value.toString(),\r\n )\r\n }\r\n })\r\n onBeforeUnmount(() => {\r\n document.documentElement.style.removeProperty('--el-dialog-z-index')\r\n })\r\n}\r\n","<script setup lang=\"ts\">\r\ndefineOptions({\r\n name: 'RdRichEditor',\r\n})\r\n\r\nimport { onBeforeUnmount, onMounted, ref, watch } from 'vue'\r\nimport type { TinyMCEEditor } from '../../types/tinymce'\r\nimport { cleanObject } from '../../utils/cleanObject'\r\nimport useQuickTextPhrase, { type TextPhraseItem } from './useQuickTextPhrase'\r\nimport { setupDialogZIndexBridge } from './dialogZIndexBridge'\r\nimport '../../styles/rd-rich-editor.css'\r\n\r\ninterface EditorConfigProps {\r\n plugins?: string | string[]\r\n language?: string\r\n menubar?: string | string[]\r\n toolbar?: string | string[]\r\n toolbar_mode?: string\r\n min_height?: number\r\n height?: number\r\n /** Enable `#` quick text phrase autocompleter (requires fetchTextPhrases) */\r\n textPhrase?: boolean\r\n}\r\n\r\ntype Props = EditorConfigProps & {\r\n includes?: Pick<EditorConfigProps, 'plugins' | 'menubar' | 'toolbar'>\r\n excludes?: Pick<EditorConfigProps, 'plugins' | 'menubar' | 'toolbar'>\r\n /**\r\n * Image upload hook. Return a publicly accessible URL.\r\n * When omitted, paste/insert image upload fails with a clear message.\r\n */\r\n imagesUploadHandler?: (blob: Blob) => Promise<string>\r\n /** Fetch quick text phrases when textPhrase is true */\r\n fetchTextPhrases?: () => Promise<TextPhraseItem[]>\r\n /** Override TinyMCE baseURL when static assets are not at the default path */\r\n tinymceBaseUrl?: string\r\n /**\r\n * TinyMCE language pack URL (language_url).\r\n * Needed when langs/*.js are not under tinymceBaseUrl (e.g. CDN demos).\r\n */\r\n languageUrl?: string\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n language: 'zh_CN',\r\n textPhrase: false,\r\n})\r\n\r\nconst emits = defineEmits<{\r\n input: [e: Event]\r\n change: [value: string, e: unknown]\r\n}>()\r\n\r\nconst inputValue = defineModel<string>({ default: '' })\r\nconst editorId = `rd-editor_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`\r\nconst isInit = ref(false)\r\nlet editorCtx: TinyMCEEditor | null = null\r\n\r\nconst task: Array<() => void> = []\r\n\r\nsetupDialogZIndexBridge()\r\n\r\nfunction getContentSync() {\r\n return editorCtx?.getContent() ?? ''\r\n}\r\n\r\nfunction setContentSync(value: string) {\r\n const result = editorCtx?.setContent(value ?? '')\r\n inputValue.value = getContentSync()\r\n return result\r\n}\r\n\r\nfunction insertContentSync(value: string) {\r\n editorCtx?.insertContent(value)\r\n inputValue.value = getContentSync()\r\n}\r\n\r\n/** Append tokens to a space-separated TinyMCE config string (word match). */\r\nfunction addStr(source: string, target: string | string[]) {\r\n const list = Array.isArray(target) ? target : [target]\r\n let result = source\r\n list.forEach((token) => {\r\n const trimmed = token.trim()\r\n if (!trimmed) return\r\n const re = new RegExp(`(?:^|\\\\s)${escapeRegExp(trimmed)}(?:\\\\s|$)`)\r\n if (!re.test(result)) {\r\n result = `${result} ${trimmed}`.trim()\r\n }\r\n })\r\n return result\r\n}\r\n\r\n/** Remove tokens from a space-separated TinyMCE config string. */\r\nfunction removeStr(source: string, target: string | string[]) {\r\n const list = Array.isArray(target) ? target : [target]\r\n let result = source\r\n list.forEach((token) => {\r\n const trimmed = token.trim()\r\n if (!trimmed) return\r\n const re = new RegExp(`(?:^|\\\\s)${escapeRegExp(trimmed)}(?=\\\\s|$)`, 'g')\r\n result = result.replace(re, ' ').replace(/\\s+/g, ' ').trim()\r\n })\r\n return result\r\n}\r\n\r\nfunction escapeRegExp(s: string) {\r\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\r\n}\r\n\r\nfunction initOpts(): Record<string, unknown> {\r\n const {\r\n includes,\r\n excludes,\r\n textPhrase,\r\n imagesUploadHandler,\r\n fetchTextPhrases,\r\n tinymceBaseUrl,\r\n languageUrl,\r\n ...restProps\r\n } = props\r\n\r\n const language = props.language === 'zh-cn' ? 'zh_CN' : props.language\r\n\r\n const baseOpts: Record<string, unknown> = {\r\n selector: `textarea#${editorId}`,\r\n language,\r\n plugins:\r\n 'advlist autolink link image lists charmap emoticons preview hr anchor pagebreak nonbreaking code table paste insertdatetime wordcount',\r\n menubar: 'file edit view insert format tools',\r\n menu: {\r\n insert: {\r\n title: 'Insert',\r\n items:\r\n 'image link inserttable | charmap emoticons hr | pagebreak nonbreaking anchor | insertdatetime',\r\n },\r\n },\r\n toolbar:\r\n 'undo redo | styleselect | fontsizeselect fontselect | bold italic forecolor backcolor | alignleft aligncenter alignright alignjustify | outdent indent | bullist numlist table | link image | code',\r\n toolbar_mode: 'wrap',\r\n fontsize_formats:\r\n '5px 6px 7px 8px 9px 10px 12px 14px 16px 18px 20px 22px 24px 26px 28px 30px 32px 34px 36px 48px 72px',\r\n font_formats:\r\n 'Andale Mono=andale mono,times;Arial=arial,helvetica,sans-serif;Arial Black=arial black,avant garde;Book Antiqua=book antiqua,palatino;Calibri;Calibri Light;Comic Sans MS=comic sans ms,sans-serif;Courier New=courier new,courier;Georgia=georgia,palatino;Helvetica=helvetica;Impact=impact,chicago;Symbol=symbol;Tahoma=tahoma,arial,;Terminal=terminal,monaco;Times New Roman=times new roman,times;Trebuchet MS=trebuchet ms,geneva;Verdana=verdana,geneva;Webdings=webdings;Wingdings=wingdings,;',\r\n min_height: 480,\r\n table_default_styles: { width: '100%', borderCollapse: 'collapse' },\r\n image_advtab: true,\r\n image_title: false,\r\n paste_data_images: true,\r\n images_upload_handler: (\r\n blobInfo: { blob: () => Blob },\r\n success: (url: string) => void,\r\n fail: (msg: string) => void,\r\n ) => {\r\n if (!imagesUploadHandler) {\r\n fail('imagesUploadHandler is not provided')\r\n return\r\n }\r\n imagesUploadHandler(blobInfo.blob())\r\n .then((url) => {\r\n if (url) success(url)\r\n else fail('Upload failed')\r\n })\r\n .catch(() => fail('Upload failed'))\r\n },\r\n setup(editor: TinyMCEEditor) {\r\n editorCtx = editor\r\n editor.on('init', () => {\r\n isInit.value = true\r\n const body = editorCtx?.getBody()\r\n if (body) {\r\n body.style.fontSize = '16px'\r\n body.style.fontFamily = 'Calibri'\r\n }\r\n setContentSync(inputValue.value)\r\n while (task.length) {\r\n task.shift()?.()\r\n }\r\n })\r\n editor.on('change', (e) => {\r\n inputValue.value = getContentSync()\r\n emits('change', inputValue.value, e)\r\n })\r\n editor.on('input', (e) => {\r\n emits('input', e as Event)\r\n })\r\n if (textPhrase) {\r\n if (!fetchTextPhrases) {\r\n console.warn(\r\n '[RdRichEditor] textPhrase is true but fetchTextPhrases is not provided',\r\n )\r\n } else {\r\n useQuickTextPhrase(editor, fetchTextPhrases)\r\n }\r\n }\r\n },\r\n }\r\n\r\n if (tinymceBaseUrl) {\r\n baseOpts.base_url = tinymceBaseUrl\r\n }\r\n if (languageUrl) {\r\n baseOpts.language_url = languageUrl\r\n }\r\n\r\n const result: Record<string, unknown> = {\r\n ...baseOpts,\r\n ...cleanObject(restProps),\r\n }\r\n\r\n if (includes) {\r\n ;(Object.keys(includes) as Array<keyof typeof includes>).forEach((name) => {\r\n const current = result[name]\r\n const extra = includes[name]\r\n if (typeof current === 'string' && extra != null) {\r\n result[name] = addStr(current, extra)\r\n }\r\n })\r\n }\r\n if (excludes) {\r\n ;(Object.keys(excludes) as Array<keyof typeof excludes>).forEach((name) => {\r\n const current = result[name]\r\n const remove = excludes[name]\r\n if (typeof current === 'string' && remove != null) {\r\n result[name] = removeStr(current, remove)\r\n }\r\n })\r\n }\r\n\r\n return result\r\n}\r\n\r\nfunction transferAsync<T extends (...args: any[]) => any>(fun: T) {\r\n return function (\r\n ...rest: Parameters<T>\r\n ): ReturnType<T> | Promise<ReturnType<T>> {\r\n if (!isInit.value) {\r\n return new Promise<ReturnType<T>>((resolve) => {\r\n task.push(() => resolve(fun(...rest)))\r\n })\r\n }\r\n return fun(...rest)\r\n }\r\n}\r\n\r\nfunction validate() {\r\n const tempDiv = document.createElement('div')\r\n tempDiv.innerHTML = inputValue.value\r\n if (tempDiv.textContent?.trim()) return true\r\n if (tempDiv.querySelector('a.mce-item-anchor, img, table')) return true\r\n return false\r\n}\r\n\r\nwatch(\r\n () => inputValue.value,\r\n (newVal) => {\r\n if (!isInit.value || newVal === getContentSync()) return\r\n setContentSync(newVal)\r\n },\r\n)\r\n\r\nonMounted(() => {\r\n const tinymce = window.tinymce\r\n if (!tinymce) {\r\n console.error(\r\n '[RdRichEditor] window.tinymce is missing. Load TinyMCE 5.10.x via <script> before using RdRichEditor.',\r\n )\r\n return\r\n }\r\n if (props.tinymceBaseUrl) {\r\n tinymce.baseURL = props.tinymceBaseUrl\r\n }\r\n void tinymce.init(initOpts())\r\n})\r\n\r\nonBeforeUnmount(() => {\r\n editorCtx?.remove()\r\n editorCtx = null\r\n})\r\n\r\ndefineExpose({\r\n getEditor: transferAsync(() => editorCtx),\r\n getContent: transferAsync(getContentSync),\r\n setContent: transferAsync(setContentSync),\r\n insertContent: transferAsync(insertContentSync),\r\n validate,\r\n})\r\n</script>\r\n\r\n<template>\r\n <div class=\"rd-rich-editor\">\r\n <textarea :id=\"editorId\" style=\"visibility: hidden\" />\r\n <div v-if=\"isInit\" class=\"rd-rich-editor-footer-bar\">\r\n <slot name=\"footer-bar\" />\r\n </div>\r\n </div>\r\n</template>\r\n","<script setup lang=\"ts\">\r\ndefineOptions({\r\n name: 'RdRichEditor',\r\n})\r\n\r\nimport { onBeforeUnmount, onMounted, ref, watch } from 'vue'\r\nimport type { TinyMCEEditor } from '../../types/tinymce'\r\nimport { cleanObject } from '../../utils/cleanObject'\r\nimport useQuickTextPhrase, { type TextPhraseItem } from './useQuickTextPhrase'\r\nimport { setupDialogZIndexBridge } from './dialogZIndexBridge'\r\nimport '../../styles/rd-rich-editor.css'\r\n\r\ninterface EditorConfigProps {\r\n plugins?: string | string[]\r\n language?: string\r\n menubar?: string | string[]\r\n toolbar?: string | string[]\r\n toolbar_mode?: string\r\n min_height?: number\r\n height?: number\r\n /** Enable `#` quick text phrase autocompleter (requires fetchTextPhrases) */\r\n textPhrase?: boolean\r\n}\r\n\r\ntype Props = EditorConfigProps & {\r\n includes?: Pick<EditorConfigProps, 'plugins' | 'menubar' | 'toolbar'>\r\n excludes?: Pick<EditorConfigProps, 'plugins' | 'menubar' | 'toolbar'>\r\n /**\r\n * Image upload hook. Return a publicly accessible URL.\r\n * When omitted, paste/insert image upload fails with a clear message.\r\n */\r\n imagesUploadHandler?: (blob: Blob) => Promise<string>\r\n /** Fetch quick text phrases when textPhrase is true */\r\n fetchTextPhrases?: () => Promise<TextPhraseItem[]>\r\n /** Override TinyMCE baseURL when static assets are not at the default path */\r\n tinymceBaseUrl?: string\r\n /**\r\n * TinyMCE language pack URL (language_url).\r\n * Needed when langs/*.js are not under tinymceBaseUrl (e.g. CDN demos).\r\n */\r\n languageUrl?: string\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n language: 'zh_CN',\r\n textPhrase: false,\r\n})\r\n\r\nconst emits = defineEmits<{\r\n input: [e: Event]\r\n change: [value: string, e: unknown]\r\n}>()\r\n\r\nconst inputValue = defineModel<string>({ default: '' })\r\nconst editorId = `rd-editor_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`\r\nconst isInit = ref(false)\r\nlet editorCtx: TinyMCEEditor | null = null\r\n\r\nconst task: Array<() => void> = []\r\n\r\nsetupDialogZIndexBridge()\r\n\r\nfunction getContentSync() {\r\n return editorCtx?.getContent() ?? ''\r\n}\r\n\r\nfunction setContentSync(value: string) {\r\n const result = editorCtx?.setContent(value ?? '')\r\n inputValue.value = getContentSync()\r\n return result\r\n}\r\n\r\nfunction insertContentSync(value: string) {\r\n editorCtx?.insertContent(value)\r\n inputValue.value = getContentSync()\r\n}\r\n\r\n/** Append tokens to a space-separated TinyMCE config string (word match). */\r\nfunction addStr(source: string, target: string | string[]) {\r\n const list = Array.isArray(target) ? target : [target]\r\n let result = source\r\n list.forEach((token) => {\r\n const trimmed = token.trim()\r\n if (!trimmed) return\r\n const re = new RegExp(`(?:^|\\\\s)${escapeRegExp(trimmed)}(?:\\\\s|$)`)\r\n if (!re.test(result)) {\r\n result = `${result} ${trimmed}`.trim()\r\n }\r\n })\r\n return result\r\n}\r\n\r\n/** Remove tokens from a space-separated TinyMCE config string. */\r\nfunction removeStr(source: string, target: string | string[]) {\r\n const list = Array.isArray(target) ? target : [target]\r\n let result = source\r\n list.forEach((token) => {\r\n const trimmed = token.trim()\r\n if (!trimmed) return\r\n const re = new RegExp(`(?:^|\\\\s)${escapeRegExp(trimmed)}(?=\\\\s|$)`, 'g')\r\n result = result.replace(re, ' ').replace(/\\s+/g, ' ').trim()\r\n })\r\n return result\r\n}\r\n\r\nfunction escapeRegExp(s: string) {\r\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\r\n}\r\n\r\nfunction initOpts(): Record<string, unknown> {\r\n const {\r\n includes,\r\n excludes,\r\n textPhrase,\r\n imagesUploadHandler,\r\n fetchTextPhrases,\r\n tinymceBaseUrl,\r\n languageUrl,\r\n ...restProps\r\n } = props\r\n\r\n const language = props.language === 'zh-cn' ? 'zh_CN' : props.language\r\n\r\n const baseOpts: Record<string, unknown> = {\r\n selector: `textarea#${editorId}`,\r\n language,\r\n plugins:\r\n 'advlist autolink link image lists charmap emoticons preview hr anchor pagebreak nonbreaking code table paste insertdatetime wordcount',\r\n menubar: 'file edit view insert format tools',\r\n menu: {\r\n insert: {\r\n title: 'Insert',\r\n items:\r\n 'image link inserttable | charmap emoticons hr | pagebreak nonbreaking anchor | insertdatetime',\r\n },\r\n },\r\n toolbar:\r\n 'undo redo | styleselect | fontsizeselect fontselect | bold italic forecolor backcolor | alignleft aligncenter alignright alignjustify | outdent indent | bullist numlist table | link image | code',\r\n toolbar_mode: 'wrap',\r\n fontsize_formats:\r\n '5px 6px 7px 8px 9px 10px 12px 14px 16px 18px 20px 22px 24px 26px 28px 30px 32px 34px 36px 48px 72px',\r\n font_formats:\r\n 'Andale Mono=andale mono,times;Arial=arial,helvetica,sans-serif;Arial Black=arial black,avant garde;Book Antiqua=book antiqua,palatino;Calibri;Calibri Light;Comic Sans MS=comic sans ms,sans-serif;Courier New=courier new,courier;Georgia=georgia,palatino;Helvetica=helvetica;Impact=impact,chicago;Symbol=symbol;Tahoma=tahoma,arial,;Terminal=terminal,monaco;Times New Roman=times new roman,times;Trebuchet MS=trebuchet ms,geneva;Verdana=verdana,geneva;Webdings=webdings;Wingdings=wingdings,;',\r\n min_height: 480,\r\n table_default_styles: { width: '100%', borderCollapse: 'collapse' },\r\n image_advtab: true,\r\n image_title: false,\r\n paste_data_images: true,\r\n images_upload_handler: (\r\n blobInfo: { blob: () => Blob },\r\n success: (url: string) => void,\r\n fail: (msg: string) => void,\r\n ) => {\r\n if (!imagesUploadHandler) {\r\n fail('imagesUploadHandler is not provided')\r\n return\r\n }\r\n imagesUploadHandler(blobInfo.blob())\r\n .then((url) => {\r\n if (url) success(url)\r\n else fail('Upload failed')\r\n })\r\n .catch(() => fail('Upload failed'))\r\n },\r\n setup(editor: TinyMCEEditor) {\r\n editorCtx = editor\r\n editor.on('init', () => {\r\n isInit.value = true\r\n const body = editorCtx?.getBody()\r\n if (body) {\r\n body.style.fontSize = '16px'\r\n body.style.fontFamily = 'Calibri'\r\n }\r\n setContentSync(inputValue.value)\r\n while (task.length) {\r\n task.shift()?.()\r\n }\r\n })\r\n editor.on('change', (e) => {\r\n inputValue.value = getContentSync()\r\n emits('change', inputValue.value, e)\r\n })\r\n editor.on('input', (e) => {\r\n emits('input', e as Event)\r\n })\r\n if (textPhrase) {\r\n if (!fetchTextPhrases) {\r\n console.warn(\r\n '[RdRichEditor] textPhrase is true but fetchTextPhrases is not provided',\r\n )\r\n } else {\r\n useQuickTextPhrase(editor, fetchTextPhrases)\r\n }\r\n }\r\n },\r\n }\r\n\r\n if (tinymceBaseUrl) {\r\n baseOpts.base_url = tinymceBaseUrl\r\n }\r\n if (languageUrl) {\r\n baseOpts.language_url = languageUrl\r\n }\r\n\r\n const result: Record<string, unknown> = {\r\n ...baseOpts,\r\n ...cleanObject(restProps),\r\n }\r\n\r\n if (includes) {\r\n ;(Object.keys(includes) as Array<keyof typeof includes>).forEach((name) => {\r\n const current = result[name]\r\n const extra = includes[name]\r\n if (typeof current === 'string' && extra != null) {\r\n result[name] = addStr(current, extra)\r\n }\r\n })\r\n }\r\n if (excludes) {\r\n ;(Object.keys(excludes) as Array<keyof typeof excludes>).forEach((name) => {\r\n const current = result[name]\r\n const remove = excludes[name]\r\n if (typeof current === 'string' && remove != null) {\r\n result[name] = removeStr(current, remove)\r\n }\r\n })\r\n }\r\n\r\n return result\r\n}\r\n\r\nfunction transferAsync<T extends (...args: any[]) => any>(fun: T) {\r\n return function (\r\n ...rest: Parameters<T>\r\n ): ReturnType<T> | Promise<ReturnType<T>> {\r\n if (!isInit.value) {\r\n return new Promise<ReturnType<T>>((resolve) => {\r\n task.push(() => resolve(fun(...rest)))\r\n })\r\n }\r\n return fun(...rest)\r\n }\r\n}\r\n\r\nfunction validate() {\r\n const tempDiv = document.createElement('div')\r\n tempDiv.innerHTML = inputValue.value\r\n if (tempDiv.textContent?.trim()) return true\r\n if (tempDiv.querySelector('a.mce-item-anchor, img, table')) return true\r\n return false\r\n}\r\n\r\nwatch(\r\n () => inputValue.value,\r\n (newVal) => {\r\n if (!isInit.value || newVal === getContentSync()) return\r\n setContentSync(newVal)\r\n },\r\n)\r\n\r\nonMounted(() => {\r\n const tinymce = window.tinymce\r\n if (!tinymce) {\r\n console.error(\r\n '[RdRichEditor] window.tinymce is missing. Load TinyMCE 5.10.x via <script> before using RdRichEditor.',\r\n )\r\n return\r\n }\r\n if (props.tinymceBaseUrl) {\r\n tinymce.baseURL = props.tinymceBaseUrl\r\n }\r\n void tinymce.init(initOpts())\r\n})\r\n\r\nonBeforeUnmount(() => {\r\n editorCtx?.remove()\r\n editorCtx = null\r\n})\r\n\r\ndefineExpose({\r\n getEditor: transferAsync(() => editorCtx),\r\n getContent: transferAsync(getContentSync),\r\n setContent: transferAsync(setContentSync),\r\n insertContent: transferAsync(insertContentSync),\r\n validate,\r\n})\r\n</script>\r\n\r\n<template>\r\n <div class=\"rd-rich-editor\">\r\n <textarea :id=\"editorId\" style=\"visibility: hidden\" />\r\n <div v-if=\"isInit\" class=\"rd-rich-editor-footer-bar\">\r\n <slot name=\"footer-bar\" />\r\n </div>\r\n </div>\r\n</template>\r\n","import type { App } from 'vue'\r\nimport { RdRichEditor } from './components/rdRichEditor'\r\nexport type { TextPhraseItem } from './components/rdRichEditor'\r\n\r\nexport { RdRichEditor }\r\nexport { RodelEditorResolver } from './resolver'\r\n\r\nimport './styles/rd-rich-editor.css'\r\n\r\nconst components = [RdRichEditor]\r\n\r\n/**\r\n * Register RdRichEditor. Host must load TinyMCE 5.10.x on window.tinymce.\r\n */\r\nexport function install(app: App): void {\r\n components.forEach((component) => {\r\n const name = component.name || (component as { __name?: string }).__name\r\n if (name) {\r\n app.component(name, component)\r\n }\r\n })\r\n}\r\n\r\nexport default install\r\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/utils/cleanObject.ts","../src/components/rdRichEditor/useQuickTextPhrase.ts","../src/components/rdRichEditor/dialogZIndexBridge.ts","../src/components/rdRichEditor/RdRichEditor.vue","../src/components/rdRichEditor/RdRichEditor.vue","../src/index.ts"],"sourcesContent":["/**\r\n * Remove empty values from a shallow object (null / undefined / '' / [] / {}).\r\n */\r\nexport function cleanObject<T>(obj: T | null | undefined): Partial<T> {\r\n if (!obj) return {}\r\n\r\n return Object.fromEntries(\r\n Object.entries(obj as Record<string, unknown>).filter(\r\n ([, v]) =>\r\n v !== '' &&\r\n v !== null &&\r\n v !== undefined &&\r\n !(Array.isArray(v) && v.length === 0) &&\r\n !(typeof v === 'object' && v && Object.keys(v as object).length === 0),\r\n ),\r\n ) as Partial<T>\r\n}\r\n","import type { TinyMCEEditor } from '../../types/tinymce'\r\n\r\nexport interface TextPhraseItem {\r\n name: string\r\n input_code: string\r\n content: string\r\n}\r\n\r\n/**\r\n * Register TinyMCE autocompleter for `#` quick text phrases.\r\n * Data is supplied by the host via `fetchTextPhrases`.\r\n */\r\nexport default function useQuickTextPhrase(\r\n editor: TinyMCEEditor,\r\n fetchTextPhrases: () => Promise<TextPhraseItem[]>,\r\n) {\r\n let quickTextPhrase: TextPhraseItem[] | null = null\r\n let pending: Promise<TextPhraseItem[]> | null = null\r\n\r\n function filterListByPattern(list: TextPhraseItem[], pattern: string) {\r\n return list\r\n .filter((v) => v.input_code.includes(pattern) || v.name.includes(pattern))\r\n .map((v) => ({\r\n type: 'cardmenuitem',\r\n value: v,\r\n label: v.name,\r\n items: [\r\n {\r\n type: 'cardcontainer',\r\n direction: 'vertical',\r\n items: [\r\n {\r\n type: 'cardtext',\r\n text: v.name,\r\n name: v.input_code,\r\n },\r\n ],\r\n },\r\n ],\r\n }))\r\n }\r\n\r\n editor.ui.registry.addAutocompleter('quick_text_phrase', {\r\n ch: '#',\r\n minChars: 0,\r\n columns: 1,\r\n matches: (_rng: unknown, text: string, pattern: string) =>\r\n (text && text.endsWith('#')) || (!!pattern && pattern.length < 6),\r\n fetch: async (pattern: string) => {\r\n if (quickTextPhrase === null) {\r\n if (!pending) {\r\n pending = fetchTextPhrases().then((items) => {\r\n quickTextPhrase = items || []\r\n return quickTextPhrase\r\n }).finally(() => {\r\n pending = null\r\n })\r\n }\r\n await pending\r\n }\r\n return filterListByPattern(quickTextPhrase!, pattern)\r\n },\r\n onAction: (\r\n autocompleterApi: { hide: () => void },\r\n rng: { deleteContents: () => void },\r\n value: TextPhraseItem,\r\n ) => {\r\n rng.deleteContents()\r\n editor.insertContent(value.content)\r\n autocompleterApi.hide()\r\n },\r\n })\r\n}\r\n","import { inject, onBeforeUnmount, watchEffect } from 'vue'\r\nimport { useZIndex, dialogInjectionKey } from 'element-plus'\r\n\r\n/**\r\n * When RdRichEditor is used inside ElDialog / RdDialog, raise TinyMCE\r\n * popover z-index above the dialog via --el-dialog-z-index.\r\n */\r\nexport function setupDialogZIndexBridge(): void {\r\n const dialogProvide = inject(dialogInjectionKey, null)\r\n if (!dialogProvide) return\r\n\r\n const dialogZIndex = useZIndex().currentZIndex\r\n watchEffect(() => {\r\n if (dialogZIndex.value > 1300) {\r\n document.documentElement.style.setProperty(\r\n '--el-dialog-z-index',\r\n dialogZIndex.value.toString(),\r\n )\r\n }\r\n })\r\n onBeforeUnmount(() => {\r\n document.documentElement.style.removeProperty('--el-dialog-z-index')\r\n })\r\n}\r\n","<script setup lang=\"ts\">\r\ndefineOptions({\r\n name: 'RdRichEditor',\r\n})\r\n\r\nimport { onBeforeUnmount, onMounted, ref, watch } from 'vue'\r\nimport type { TinyMCEEditor } from '../../types/tinymce'\r\nimport { cleanObject } from '../../utils/cleanObject'\r\nimport useQuickTextPhrase, { type TextPhraseItem } from './useQuickTextPhrase'\r\nimport { setupDialogZIndexBridge } from './dialogZIndexBridge'\r\nimport '../../styles/rd-rich-editor.css'\r\n\r\ninterface EditorConfigProps {\r\n plugins?: string | string[]\r\n language?: string\r\n menubar?: string | string[]\r\n toolbar?: string | string[]\r\n toolbar_mode?: string\r\n min_height?: number\r\n height?: number\r\n /** Enable `#` quick text phrase autocompleter (requires fetchTextPhrases) */\r\n textPhrase?: boolean\r\n}\r\n\r\ntype Props = EditorConfigProps & {\r\n includes?: Pick<EditorConfigProps, 'plugins' | 'menubar' | 'toolbar'>\r\n excludes?: Pick<EditorConfigProps, 'plugins' | 'menubar' | 'toolbar'>\r\n /**\r\n * Image upload hook. Return a publicly accessible URL.\r\n * When omitted, paste/insert image upload fails with a clear message.\r\n */\r\n imagesUploadHandler?: (blob: Blob) => Promise<string>\r\n /** Fetch quick text phrases when textPhrase is true */\r\n fetchTextPhrases?: () => Promise<TextPhraseItem[]>\r\n /** Override TinyMCE baseURL when static assets are not at the default path */\r\n tinymceBaseUrl?: string\r\n /**\r\n * TinyMCE language pack URL (language_url).\r\n * Needed when langs/*.js are not under tinymceBaseUrl (e.g. CDN demos).\r\n */\r\n languageUrl?: string\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n language: 'zh_CN',\r\n textPhrase: false,\r\n})\r\n\r\nconst emits = defineEmits<{\r\n input: [e: Event]\r\n change: [value: string, e: unknown]\r\n}>()\r\n\r\nconst inputValue = defineModel<string>({ default: '' })\r\nconst editorId = `rd-editor_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`\r\nconst isInit = ref(false)\r\nlet editorCtx: TinyMCEEditor | null = null\r\nlet contentObserver: MutationObserver | null = null\r\nlet cachedContent: string | null = null\r\n\r\nconst task: Array<() => void> = []\r\n\r\nsetupDialogZIndexBridge()\r\n\r\nfunction getContentSync() {\r\n const content = editorCtx?.getContent() ?? ''\r\n if (contentObserver) {\r\n contentObserver.takeRecords()\r\n cachedContent = content\r\n }\r\n return content\r\n}\r\n\r\nfunction setContentSync(value: string) {\r\n const result = editorCtx?.setContent(value ?? '')\r\n inputValue.value = getContentSync()\r\n return result\r\n}\r\n\r\nfunction insertContentSync(value: string) {\r\n editorCtx?.insertContent(value)\r\n inputValue.value = getContentSync()\r\n}\r\n\r\n/** Append tokens to a space-separated TinyMCE config string (word match). */\r\nfunction addStr(source: string, target: string | string[]) {\r\n const list = Array.isArray(target) ? target : [target]\r\n let result = source\r\n list.forEach((token) => {\r\n const trimmed = token.trim()\r\n if (!trimmed) return\r\n const re = new RegExp(`(?:^|\\\\s)${escapeRegExp(trimmed)}(?:\\\\s|$)`)\r\n if (!re.test(result)) {\r\n result = `${result} ${trimmed}`.trim()\r\n }\r\n })\r\n return result\r\n}\r\n\r\n/** Remove tokens from a space-separated TinyMCE config string. */\r\nfunction removeStr(source: string, target: string | string[]) {\r\n const list = Array.isArray(target) ? target : [target]\r\n let result = source\r\n list.forEach((token) => {\r\n const trimmed = token.trim()\r\n if (!trimmed) return\r\n const re = new RegExp(`(?:^|\\\\s)${escapeRegExp(trimmed)}(?=\\\\s|$)`, 'g')\r\n result = result.replace(re, ' ').replace(/\\s+/g, ' ').trim()\r\n })\r\n return result\r\n}\r\n\r\nfunction escapeRegExp(s: string) {\r\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\r\n}\r\n\r\nfunction initOpts(): Record<string, unknown> {\r\n const {\r\n includes,\r\n excludes,\r\n textPhrase,\r\n imagesUploadHandler,\r\n fetchTextPhrases,\r\n tinymceBaseUrl,\r\n languageUrl,\r\n ...restProps\r\n } = props\r\n\r\n const language = props.language === 'zh-cn' ? 'zh_CN' : props.language\r\n\r\n const baseOpts: Record<string, unknown> = {\r\n selector: `textarea#${editorId}`,\r\n language,\r\n plugins:\r\n 'advlist autolink link image lists charmap emoticons preview hr anchor pagebreak nonbreaking code table paste insertdatetime wordcount',\r\n menubar: 'file edit view insert format tools',\r\n menu: {\r\n insert: {\r\n title: 'Insert',\r\n items:\r\n 'image link inserttable | charmap emoticons hr | pagebreak nonbreaking anchor | insertdatetime',\r\n },\r\n },\r\n toolbar:\r\n 'undo redo | styleselect | fontsizeselect fontselect | bold italic forecolor backcolor | alignleft aligncenter alignright alignjustify | outdent indent | bullist numlist table | link image | code',\r\n toolbar_mode: 'wrap',\r\n fontsize_formats:\r\n '5px 6px 7px 8px 9px 10px 12px 14px 16px 18px 20px 22px 24px 26px 28px 30px 32px 34px 36px 48px 72px',\r\n font_formats:\r\n 'Andale Mono=andale mono,times;Arial=arial,helvetica,sans-serif;Arial Black=arial black,avant garde;Book Antiqua=book antiqua,palatino;Calibri;Calibri Light;Comic Sans MS=comic sans ms,sans-serif;Courier New=courier new,courier;Georgia=georgia,palatino;Helvetica=helvetica;Impact=impact,chicago;Symbol=symbol;Tahoma=tahoma,arial,;Terminal=terminal,monaco;Times New Roman=times new roman,times;Trebuchet MS=trebuchet ms,geneva;Verdana=verdana,geneva;Webdings=webdings;Wingdings=wingdings,;',\r\n min_height: 480,\r\n table_default_styles: { width: '100%', borderCollapse: 'collapse' },\r\n image_advtab: true,\r\n image_title: false,\r\n paste_data_images: true,\r\n images_upload_handler: (\r\n blobInfo: { blob: () => Blob },\r\n success: (url: string) => void,\r\n fail: (msg: string) => void,\r\n ) => {\r\n if (!imagesUploadHandler) {\r\n fail('imagesUploadHandler is not provided')\r\n return\r\n }\r\n imagesUploadHandler(blobInfo.blob())\r\n .then((url) => {\r\n if (url) success(url)\r\n else fail('Upload failed')\r\n })\r\n .catch(() => fail('Upload failed'))\r\n },\r\n setup(editor: TinyMCEEditor) {\r\n editorCtx = editor\r\n editor.on('init', () => {\r\n isInit.value = true\r\n const body = editorCtx?.getBody()\r\n if (body) {\r\n body.style.fontSize = '16px'\r\n body.style.fontFamily = 'Calibri'\r\n if (typeof MutationObserver !== 'undefined') {\r\n contentObserver = new MutationObserver(() => { cachedContent = null })\r\n contentObserver.observe(body, { subtree: true, childList: true, characterData: true, attributes: true })\r\n }\r\n }\r\n setContentSync(inputValue.value)\r\n while (task.length) {\r\n task.shift()?.()\r\n }\r\n })\r\n editor.on('change', (e) => {\r\n const content = getContentSync()\r\n inputValue.value = content\r\n emits('change', content, e)\r\n })\r\n editor.on('input', (e) => {\r\n cachedContent = null\r\n emits('input', e as Event)\r\n })\r\n editor.on('BeforeSetContent SetContent Undo Redo', () => { cachedContent = null })\r\n if (textPhrase) {\r\n if (!fetchTextPhrases) {\r\n console.warn(\r\n '[RdRichEditor] textPhrase is true but fetchTextPhrases is not provided',\r\n )\r\n } else {\r\n useQuickTextPhrase(editor, fetchTextPhrases)\r\n }\r\n }\r\n },\r\n }\r\n\r\n if (tinymceBaseUrl) {\r\n baseOpts.base_url = tinymceBaseUrl\r\n }\r\n if (languageUrl) {\r\n baseOpts.language_url = languageUrl\r\n }\r\n\r\n const result: Record<string, unknown> = {\r\n ...baseOpts,\r\n ...cleanObject(restProps),\r\n }\r\n\r\n if (includes) {\r\n ;(Object.keys(includes) as Array<keyof typeof includes>).forEach((name) => {\r\n const current = result[name]\r\n const extra = includes[name]\r\n if (typeof current === 'string' && extra != null) {\r\n result[name] = addStr(current, extra)\r\n }\r\n })\r\n }\r\n if (excludes) {\r\n ;(Object.keys(excludes) as Array<keyof typeof excludes>).forEach((name) => {\r\n const current = result[name]\r\n const remove = excludes[name]\r\n if (typeof current === 'string' && remove != null) {\r\n result[name] = removeStr(current, remove)\r\n }\r\n })\r\n }\r\n\r\n return result\r\n}\r\n\r\nfunction transferAsync<T extends (...args: any[]) => any>(fun: T) {\r\n return function (\r\n ...rest: Parameters<T>\r\n ): ReturnType<T> | Promise<ReturnType<T>> {\r\n if (!isInit.value) {\r\n return new Promise<ReturnType<T>>((resolve) => {\r\n task.push(() => resolve(fun(...rest)))\r\n })\r\n }\r\n return fun(...rest)\r\n }\r\n}\r\n\r\nfunction validate() {\r\n const tempDiv = document.createElement('div')\r\n tempDiv.innerHTML = inputValue.value\r\n if (tempDiv.textContent?.trim()) return true\r\n if (tempDiv.querySelector('a.mce-item-anchor, img, table')) return true\r\n return false\r\n}\r\n\r\nwatch(\r\n () => inputValue.value,\r\n (newVal) => {\r\n if (!isInit.value) return\r\n if (contentObserver?.takeRecords().length) cachedContent = null\r\n if (newVal === (cachedContent ?? getContentSync())) return\r\n setContentSync(newVal)\r\n },\r\n)\r\n\r\nonMounted(() => {\r\n const tinymce = window.tinymce\r\n if (!tinymce) {\r\n console.error(\r\n '[RdRichEditor] window.tinymce is missing. Load TinyMCE 5.10.x via <script> before using RdRichEditor.',\r\n )\r\n return\r\n }\r\n if (props.tinymceBaseUrl) {\r\n tinymce.baseURL = props.tinymceBaseUrl\r\n }\r\n void tinymce.init(initOpts())\r\n})\r\n\r\nonBeforeUnmount(() => {\r\n contentObserver?.disconnect()\r\n contentObserver = null\r\n cachedContent = null\r\n editorCtx?.remove()\r\n editorCtx = null\r\n})\r\n\r\ndefineExpose({\r\n getEditor: transferAsync(() => editorCtx),\r\n getContent: transferAsync(getContentSync),\r\n setContent: transferAsync(setContentSync),\r\n insertContent: transferAsync(insertContentSync),\r\n validate,\r\n})\r\n</script>\r\n\r\n<template>\r\n <div class=\"rd-rich-editor\">\r\n <textarea :id=\"editorId\" style=\"visibility: hidden\" />\r\n <div v-if=\"isInit\" class=\"rd-rich-editor-footer-bar\">\r\n <slot name=\"footer-bar\" />\r\n </div>\r\n </div>\r\n</template>\r\n","<script setup lang=\"ts\">\r\ndefineOptions({\r\n name: 'RdRichEditor',\r\n})\r\n\r\nimport { onBeforeUnmount, onMounted, ref, watch } from 'vue'\r\nimport type { TinyMCEEditor } from '../../types/tinymce'\r\nimport { cleanObject } from '../../utils/cleanObject'\r\nimport useQuickTextPhrase, { type TextPhraseItem } from './useQuickTextPhrase'\r\nimport { setupDialogZIndexBridge } from './dialogZIndexBridge'\r\nimport '../../styles/rd-rich-editor.css'\r\n\r\ninterface EditorConfigProps {\r\n plugins?: string | string[]\r\n language?: string\r\n menubar?: string | string[]\r\n toolbar?: string | string[]\r\n toolbar_mode?: string\r\n min_height?: number\r\n height?: number\r\n /** Enable `#` quick text phrase autocompleter (requires fetchTextPhrases) */\r\n textPhrase?: boolean\r\n}\r\n\r\ntype Props = EditorConfigProps & {\r\n includes?: Pick<EditorConfigProps, 'plugins' | 'menubar' | 'toolbar'>\r\n excludes?: Pick<EditorConfigProps, 'plugins' | 'menubar' | 'toolbar'>\r\n /**\r\n * Image upload hook. Return a publicly accessible URL.\r\n * When omitted, paste/insert image upload fails with a clear message.\r\n */\r\n imagesUploadHandler?: (blob: Blob) => Promise<string>\r\n /** Fetch quick text phrases when textPhrase is true */\r\n fetchTextPhrases?: () => Promise<TextPhraseItem[]>\r\n /** Override TinyMCE baseURL when static assets are not at the default path */\r\n tinymceBaseUrl?: string\r\n /**\r\n * TinyMCE language pack URL (language_url).\r\n * Needed when langs/*.js are not under tinymceBaseUrl (e.g. CDN demos).\r\n */\r\n languageUrl?: string\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n language: 'zh_CN',\r\n textPhrase: false,\r\n})\r\n\r\nconst emits = defineEmits<{\r\n input: [e: Event]\r\n change: [value: string, e: unknown]\r\n}>()\r\n\r\nconst inputValue = defineModel<string>({ default: '' })\r\nconst editorId = `rd-editor_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`\r\nconst isInit = ref(false)\r\nlet editorCtx: TinyMCEEditor | null = null\r\nlet contentObserver: MutationObserver | null = null\r\nlet cachedContent: string | null = null\r\n\r\nconst task: Array<() => void> = []\r\n\r\nsetupDialogZIndexBridge()\r\n\r\nfunction getContentSync() {\r\n const content = editorCtx?.getContent() ?? ''\r\n if (contentObserver) {\r\n contentObserver.takeRecords()\r\n cachedContent = content\r\n }\r\n return content\r\n}\r\n\r\nfunction setContentSync(value: string) {\r\n const result = editorCtx?.setContent(value ?? '')\r\n inputValue.value = getContentSync()\r\n return result\r\n}\r\n\r\nfunction insertContentSync(value: string) {\r\n editorCtx?.insertContent(value)\r\n inputValue.value = getContentSync()\r\n}\r\n\r\n/** Append tokens to a space-separated TinyMCE config string (word match). */\r\nfunction addStr(source: string, target: string | string[]) {\r\n const list = Array.isArray(target) ? target : [target]\r\n let result = source\r\n list.forEach((token) => {\r\n const trimmed = token.trim()\r\n if (!trimmed) return\r\n const re = new RegExp(`(?:^|\\\\s)${escapeRegExp(trimmed)}(?:\\\\s|$)`)\r\n if (!re.test(result)) {\r\n result = `${result} ${trimmed}`.trim()\r\n }\r\n })\r\n return result\r\n}\r\n\r\n/** Remove tokens from a space-separated TinyMCE config string. */\r\nfunction removeStr(source: string, target: string | string[]) {\r\n const list = Array.isArray(target) ? target : [target]\r\n let result = source\r\n list.forEach((token) => {\r\n const trimmed = token.trim()\r\n if (!trimmed) return\r\n const re = new RegExp(`(?:^|\\\\s)${escapeRegExp(trimmed)}(?=\\\\s|$)`, 'g')\r\n result = result.replace(re, ' ').replace(/\\s+/g, ' ').trim()\r\n })\r\n return result\r\n}\r\n\r\nfunction escapeRegExp(s: string) {\r\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\r\n}\r\n\r\nfunction initOpts(): Record<string, unknown> {\r\n const {\r\n includes,\r\n excludes,\r\n textPhrase,\r\n imagesUploadHandler,\r\n fetchTextPhrases,\r\n tinymceBaseUrl,\r\n languageUrl,\r\n ...restProps\r\n } = props\r\n\r\n const language = props.language === 'zh-cn' ? 'zh_CN' : props.language\r\n\r\n const baseOpts: Record<string, unknown> = {\r\n selector: `textarea#${editorId}`,\r\n language,\r\n plugins:\r\n 'advlist autolink link image lists charmap emoticons preview hr anchor pagebreak nonbreaking code table paste insertdatetime wordcount',\r\n menubar: 'file edit view insert format tools',\r\n menu: {\r\n insert: {\r\n title: 'Insert',\r\n items:\r\n 'image link inserttable | charmap emoticons hr | pagebreak nonbreaking anchor | insertdatetime',\r\n },\r\n },\r\n toolbar:\r\n 'undo redo | styleselect | fontsizeselect fontselect | bold italic forecolor backcolor | alignleft aligncenter alignright alignjustify | outdent indent | bullist numlist table | link image | code',\r\n toolbar_mode: 'wrap',\r\n fontsize_formats:\r\n '5px 6px 7px 8px 9px 10px 12px 14px 16px 18px 20px 22px 24px 26px 28px 30px 32px 34px 36px 48px 72px',\r\n font_formats:\r\n 'Andale Mono=andale mono,times;Arial=arial,helvetica,sans-serif;Arial Black=arial black,avant garde;Book Antiqua=book antiqua,palatino;Calibri;Calibri Light;Comic Sans MS=comic sans ms,sans-serif;Courier New=courier new,courier;Georgia=georgia,palatino;Helvetica=helvetica;Impact=impact,chicago;Symbol=symbol;Tahoma=tahoma,arial,;Terminal=terminal,monaco;Times New Roman=times new roman,times;Trebuchet MS=trebuchet ms,geneva;Verdana=verdana,geneva;Webdings=webdings;Wingdings=wingdings,;',\r\n min_height: 480,\r\n table_default_styles: { width: '100%', borderCollapse: 'collapse' },\r\n image_advtab: true,\r\n image_title: false,\r\n paste_data_images: true,\r\n images_upload_handler: (\r\n blobInfo: { blob: () => Blob },\r\n success: (url: string) => void,\r\n fail: (msg: string) => void,\r\n ) => {\r\n if (!imagesUploadHandler) {\r\n fail('imagesUploadHandler is not provided')\r\n return\r\n }\r\n imagesUploadHandler(blobInfo.blob())\r\n .then((url) => {\r\n if (url) success(url)\r\n else fail('Upload failed')\r\n })\r\n .catch(() => fail('Upload failed'))\r\n },\r\n setup(editor: TinyMCEEditor) {\r\n editorCtx = editor\r\n editor.on('init', () => {\r\n isInit.value = true\r\n const body = editorCtx?.getBody()\r\n if (body) {\r\n body.style.fontSize = '16px'\r\n body.style.fontFamily = 'Calibri'\r\n if (typeof MutationObserver !== 'undefined') {\r\n contentObserver = new MutationObserver(() => { cachedContent = null })\r\n contentObserver.observe(body, { subtree: true, childList: true, characterData: true, attributes: true })\r\n }\r\n }\r\n setContentSync(inputValue.value)\r\n while (task.length) {\r\n task.shift()?.()\r\n }\r\n })\r\n editor.on('change', (e) => {\r\n const content = getContentSync()\r\n inputValue.value = content\r\n emits('change', content, e)\r\n })\r\n editor.on('input', (e) => {\r\n cachedContent = null\r\n emits('input', e as Event)\r\n })\r\n editor.on('BeforeSetContent SetContent Undo Redo', () => { cachedContent = null })\r\n if (textPhrase) {\r\n if (!fetchTextPhrases) {\r\n console.warn(\r\n '[RdRichEditor] textPhrase is true but fetchTextPhrases is not provided',\r\n )\r\n } else {\r\n useQuickTextPhrase(editor, fetchTextPhrases)\r\n }\r\n }\r\n },\r\n }\r\n\r\n if (tinymceBaseUrl) {\r\n baseOpts.base_url = tinymceBaseUrl\r\n }\r\n if (languageUrl) {\r\n baseOpts.language_url = languageUrl\r\n }\r\n\r\n const result: Record<string, unknown> = {\r\n ...baseOpts,\r\n ...cleanObject(restProps),\r\n }\r\n\r\n if (includes) {\r\n ;(Object.keys(includes) as Array<keyof typeof includes>).forEach((name) => {\r\n const current = result[name]\r\n const extra = includes[name]\r\n if (typeof current === 'string' && extra != null) {\r\n result[name] = addStr(current, extra)\r\n }\r\n })\r\n }\r\n if (excludes) {\r\n ;(Object.keys(excludes) as Array<keyof typeof excludes>).forEach((name) => {\r\n const current = result[name]\r\n const remove = excludes[name]\r\n if (typeof current === 'string' && remove != null) {\r\n result[name] = removeStr(current, remove)\r\n }\r\n })\r\n }\r\n\r\n return result\r\n}\r\n\r\nfunction transferAsync<T extends (...args: any[]) => any>(fun: T) {\r\n return function (\r\n ...rest: Parameters<T>\r\n ): ReturnType<T> | Promise<ReturnType<T>> {\r\n if (!isInit.value) {\r\n return new Promise<ReturnType<T>>((resolve) => {\r\n task.push(() => resolve(fun(...rest)))\r\n })\r\n }\r\n return fun(...rest)\r\n }\r\n}\r\n\r\nfunction validate() {\r\n const tempDiv = document.createElement('div')\r\n tempDiv.innerHTML = inputValue.value\r\n if (tempDiv.textContent?.trim()) return true\r\n if (tempDiv.querySelector('a.mce-item-anchor, img, table')) return true\r\n return false\r\n}\r\n\r\nwatch(\r\n () => inputValue.value,\r\n (newVal) => {\r\n if (!isInit.value) return\r\n if (contentObserver?.takeRecords().length) cachedContent = null\r\n if (newVal === (cachedContent ?? getContentSync())) return\r\n setContentSync(newVal)\r\n },\r\n)\r\n\r\nonMounted(() => {\r\n const tinymce = window.tinymce\r\n if (!tinymce) {\r\n console.error(\r\n '[RdRichEditor] window.tinymce is missing. Load TinyMCE 5.10.x via <script> before using RdRichEditor.',\r\n )\r\n return\r\n }\r\n if (props.tinymceBaseUrl) {\r\n tinymce.baseURL = props.tinymceBaseUrl\r\n }\r\n void tinymce.init(initOpts())\r\n})\r\n\r\nonBeforeUnmount(() => {\r\n contentObserver?.disconnect()\r\n contentObserver = null\r\n cachedContent = null\r\n editorCtx?.remove()\r\n editorCtx = null\r\n})\r\n\r\ndefineExpose({\r\n getEditor: transferAsync(() => editorCtx),\r\n getContent: transferAsync(getContentSync),\r\n setContent: transferAsync(setContentSync),\r\n insertContent: transferAsync(insertContentSync),\r\n validate,\r\n})\r\n</script>\r\n\r\n<template>\r\n <div class=\"rd-rich-editor\">\r\n <textarea :id=\"editorId\" style=\"visibility: hidden\" />\r\n <div v-if=\"isInit\" class=\"rd-rich-editor-footer-bar\">\r\n <slot name=\"footer-bar\" />\r\n </div>\r\n </div>\r\n</template>\r\n","import type { App } from 'vue'\r\nimport { RdRichEditor } from './components/rdRichEditor'\r\nexport type { TextPhraseItem } from './components/rdRichEditor'\r\n\r\nexport { RdRichEditor }\r\nexport { RodelEditorResolver } from './resolver'\r\n\r\nimport './styles/rd-rich-editor.css'\r\n\r\nconst components = [RdRichEditor]\r\n\r\n/**\r\n * Register RdRichEditor. Host must load TinyMCE 5.10.x on window.tinymce.\r\n */\r\nexport function install(app: App): void {\r\n components.forEach((component) => {\r\n const name = component.name || (component as { __name?: string }).__name\r\n if (name) {\r\n app.component(name, component)\r\n }\r\n })\r\n}\r\n\r\nexport default install\r\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"RdRichEditor.vue.d.ts","sourceRoot":"","sources":["../../../../src/components/rdRichEditor/RdRichEditor.vue"],"names":[],"mappings":"AA+SA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAExD,OAA2B,EAAE,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAE9E,OAAO,iCAAiC,CAAA;AAExC,UAAU,iBAAiB;IACzB,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;IAC3B,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,6EAA6E;IAC7E,UAAU,CAAC,EAAE,OAAO,CAAA;CACrB;AAED,KAAK,KAAK,GAAG,iBAAiB,GAAG;IAC/B,QAAQ,CAAC,EAAE,IAAI,CAAC,iBAAiB,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC,CAAA;IACrE,QAAQ,CAAC,EAAE,IAAI,CAAC,iBAAiB,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC,CAAA;IACrE;;;OAGG;IACH,mBAAmB,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;IACrD,uDAAuD;IACvD,gBAAgB,CAAC,EAAE,MAAM,OAAO,CAAC,cAAc,EAAE,CAAC,CAAA;IAClD,8EAA8E;IAC9E,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,CAAA;AAED,KAAK,WAAW,GAAG,KAAK,CAAC;AA2MzB,iBAAS,QAAQ,YAMhB;AA+CD,KAAK,iBAAiB,GAAG,WAAW,GAAG;IACvC,UAAU,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AA2BF,QAAA,IAAI,OAAO,IAAW,CAAE;AACxB,KAAK,WAAW,GAAG,EAAE,GACnB;IAAE,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,OAAO,KAAK,GAAG,CAAA;CAAE,CAAC;AAuBpD,QAAA,MAAM,eAAe;;;;;;;;;;;;;;;cAnVR,MAAM;gBAOJ,OAAO;6EAqVpB,CAAC;wBACkB,eAAe,CAAC,OAAO,eAAe,EAAE,WAAW,CAAC;AAAzE,wBAA0E;AAa1E,KAAK,eAAe,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG;IAChC,QAAO;QACN,MAAM,EAAE,CAAC,CAAC;KAEV,CAAA;CACD,CAAC"}
1
+ {"version":3,"file":"RdRichEditor.vue.d.ts","sourceRoot":"","sources":["../../../../src/components/rdRichEditor/RdRichEditor.vue"],"names":[],"mappings":"AAkUA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAExD,OAA2B,EAAE,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAE9E,OAAO,iCAAiC,CAAA;AAExC,UAAU,iBAAiB;IACzB,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;IAC3B,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,6EAA6E;IAC7E,UAAU,CAAC,EAAE,OAAO,CAAA;CACrB;AAED,KAAK,KAAK,GAAG,iBAAiB,GAAG;IAC/B,QAAQ,CAAC,EAAE,IAAI,CAAC,iBAAiB,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC,CAAA;IACrE,QAAQ,CAAC,EAAE,IAAI,CAAC,iBAAiB,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC,CAAA;IACrE;;;OAGG;IACH,mBAAmB,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;IACrD,uDAAuD;IACvD,gBAAgB,CAAC,EAAE,MAAM,OAAO,CAAC,cAAc,EAAE,CAAC,CAAA;IAClD,8EAA8E;IAC9E,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,CAAA;AAED,KAAK,WAAW,GAAG,KAAK,CAAC;AAyNzB,iBAAS,QAAQ,YAMhB;AAoDD,KAAK,iBAAiB,GAAG,WAAW,GAAG;IACvC,UAAU,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AA2BF,QAAA,IAAI,OAAO,IAAW,CAAE;AACxB,KAAK,WAAW,GAAG,EAAE,GACnB;IAAE,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,OAAO,KAAK,GAAG,CAAA;CAAE,CAAC;AAuBpD,QAAA,MAAM,eAAe;;;;;;;;;;;;;;;cAtWR,MAAM;gBAOJ,OAAO;6EAwWpB,CAAC;wBACkB,eAAe,CAAC,OAAO,eAAe,EAAE,WAAW,CAAC;AAAzE,wBAA0E;AAa1E,KAAK,eAAe,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG;IAChC,QAAO;QACN,MAAM,EAAE,CAAC,CAAC;KAEV,CAAA;CACD,CAAC"}
package/package.json CHANGED
@@ -1,68 +1,69 @@
1
- {
2
- "name": "@rd-element/editor",
3
- "version": "0.1.0",
4
- "description": "RD Element rich text editor (TinyMCE 5 wrapper) — RdRichEditor",
5
- "type": "module",
6
- "files": [
7
- "dist"
8
- ],
9
- "sideEffects": [
10
- "dist/style.css",
11
- "**/*.css"
12
- ],
13
- "main": "./dist/index.cjs",
14
- "module": "./dist/index.js",
15
- "types": "./dist/index.d.ts",
16
- "exports": {
17
- ".": {
18
- "types": "./dist/index.d.ts",
19
- "import": "./dist/index.js",
20
- "require": "./dist/index.cjs"
21
- },
22
- "./resolver": {
23
- "types": "./dist/resolver.d.ts",
24
- "import": "./dist/resolver.js",
25
- "require": "./dist/resolver.cjs"
26
- },
27
- "./dist/style.css": "./dist/style.css"
28
- },
29
- "publishConfig": {
30
- "access": "public"
31
- },
32
- "keywords": [
33
- "vue",
34
- "vue3",
35
- "tinymce",
36
- "rich-text",
37
- "editor",
38
- "component-library"
39
- ],
40
- "peerDependencies": {
41
- "vue": "^3.5.0",
42
- "element-plus": "^2.8.0"
43
- },
44
- "peerDependenciesMeta": {
45
- "element-plus": {
46
- "optional": true
47
- }
48
- },
49
- "devDependencies": {
50
- "@types/node": "^22.13.10",
51
- "@vitejs/plugin-vue": "^6.0.8",
52
- "@vue/test-utils": "^2.4.6",
53
- "element-plus": "^2.9.7",
54
- "happy-dom": "^17.4.4",
55
- "typescript": "^5.8.2",
56
- "vite": "^8.1.5",
57
- "vite-plugin-dts": "^5.0.3",
58
- "vite-plugin-lib-inject-css": "^2.2.2",
59
- "vitest": "^4.1.10",
60
- "vue": "^3.5.13",
61
- "vue-tsc": "^2.2.8"
62
- },
63
- "scripts": {
64
- "build": "vue-tsc -p tsconfig.build.json --noEmit && vite build",
65
- "typecheck": "vue-tsc -p tsconfig.json --noEmit",
66
- "test": "vitest run"
67
- }
68
- }
1
+ {
2
+ "name": "@rd-element/editor",
3
+ "version": "0.1.1",
4
+ "description": "RD Element rich text editor (TinyMCE 5 wrapper) — RdRichEditor",
5
+ "type": "module",
6
+ "files": [
7
+ "dist"
8
+ ],
9
+ "sideEffects": [
10
+ "dist/style.css",
11
+ "**/*.css"
12
+ ],
13
+ "main": "./dist/index.cjs",
14
+ "module": "./dist/index.js",
15
+ "types": "./dist/index.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./dist/index.d.ts",
19
+ "import": "./dist/index.js",
20
+ "require": "./dist/index.cjs"
21
+ },
22
+ "./resolver": {
23
+ "types": "./dist/resolver.d.ts",
24
+ "import": "./dist/resolver.js",
25
+ "require": "./dist/resolver.cjs"
26
+ },
27
+ "./dist/style.css": "./dist/style.css"
28
+ },
29
+ "publishConfig": {
30
+ "access": "public"
31
+ },
32
+ "scripts": {
33
+ "build": "vue-tsc -p tsconfig.build.json --noEmit && vite build",
34
+ "typecheck": "vue-tsc -p tsconfig.json --noEmit",
35
+ "test": "vitest run",
36
+ "prepublishOnly": "pnpm run build"
37
+ },
38
+ "keywords": [
39
+ "vue",
40
+ "vue3",
41
+ "tinymce",
42
+ "rich-text",
43
+ "editor",
44
+ "component-library"
45
+ ],
46
+ "peerDependencies": {
47
+ "vue": "^3.5.0",
48
+ "element-plus": "^2.8.0"
49
+ },
50
+ "peerDependenciesMeta": {
51
+ "element-plus": {
52
+ "optional": true
53
+ }
54
+ },
55
+ "devDependencies": {
56
+ "@types/node": "^22.13.10",
57
+ "@vitejs/plugin-vue": "^6.0.8",
58
+ "@vue/test-utils": "^2.4.6",
59
+ "element-plus": "^2.9.7",
60
+ "happy-dom": "^17.4.4",
61
+ "typescript": "^5.8.2",
62
+ "vite": "^8.1.5",
63
+ "vite-plugin-dts": "^5.0.3",
64
+ "vite-plugin-lib-inject-css": "^2.2.2",
65
+ "vitest": "^4.1.10",
66
+ "vue": "^3.5.13",
67
+ "vue-tsc": "^2.2.8"
68
+ }
69
+ }