@rd-element/editor 0.1.1 → 0.2.0
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/README.md +24 -0
- package/dist/index.cjs +24 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +142 -81
- package/dist/index.js.map +1 -1
- package/dist/src/components/rdRichEditor/RdRichEditor.vue.d.ts.map +1 -1
- package/dist/src/components/rdRichEditor/toolbarIcons.d.ts +4 -0
- package/dist/src/components/rdRichEditor/toolbarIcons.d.ts.map +1 -0
- package/dist/style.css +1 -1
- package/package.json +3 -1
package/README.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# @rd-element/editor
|
|
2
|
+
|
|
3
|
+
Rodel UI 的独立富文本包,提供 TinyMCE 5 封装组件 `RdRichEditor`。
|
|
4
|
+
|
|
5
|
+
## 安装
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @rd-element/editor vue@^3.5.0 @lucide/vue@^1.0.0
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
引入 `@rd-element/editor/dist/style.css`,并自行加载 TinyMCE 5.10.x 到 `window.tinymce`。组件的详细用法见 Rodel UI 文档站「富文本编辑器」。
|
|
12
|
+
|
|
13
|
+
业务项目使用自己的 Node.js、包管理器和锁文件。开发组件库源码使用 Node.js >= 22.13.0、pnpm 11.20.0,要求记录在源码仓库根目录 `package.json`。
|
|
14
|
+
|
|
15
|
+
## 安装与升级排查
|
|
16
|
+
|
|
17
|
+
- `ERR_PNPM_IGNORED_BUILDS`:按日志核对实际依赖的包名、版本和安装脚本,再使用对应 pnpm 版本的构建许可机制处理,可运行 `pnpm approve-builds` 交互选择已核对的包。
|
|
18
|
+
- `ERR_PNPM_VERIFY_DEPS_BEFORE_RUN`:在业务工程根目录执行 `pnpm install --frozen-lockfile`,再运行该工程的启动命令。
|
|
19
|
+
- 锁文件不一致:确认版本声明与锁文件来自同一次变更;维护者有意升级时更新并提交配套锁文件。
|
|
20
|
+
- peer 依赖冲突:核对 Vue 与 Lucide 的版本;使用业务工程现有的 Vue 实例。
|
|
21
|
+
|
|
22
|
+
本包没有 `preinstall`、`install`、`postinstall` 安装脚本。业务工程按自己的实际依赖处理脚本许可;`allowBuilds` 只控制脚本执行,不锁定依赖版本。
|
|
23
|
+
|
|
24
|
+
升级组件库时检查版本说明和 peer 依赖,提交业务工程自己的锁文件。源码维护和发布的完整流程见 Rodel UI 文档站「指南 → 环境与依赖」。
|
package/dist/index.cjs
CHANGED
|
@@ -1,2 +1,25 @@
|
|
|
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
|
|
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"),r=require("@lucide/vue");function i(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 a(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 o(){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 s={undo:r.Undo2,redo:r.Redo2,bold:r.Bold,italic:r.Italic,"text-color":r.Baseline,"highlight-bg-color":r.Highlighter,"align-left":r.AlignLeft,"align-center":r.AlignCenter,"align-right":r.AlignRight,"align-justify":r.AlignJustify,outdent:r.IndentDecrease,indent:r.IndentIncrease,"unordered-list":r.List,"ordered-list":r.ListOrdered,table:r.Table2,link:r.Link,image:r.Image,sourcecode:r.Code,"chevron-down":r.ChevronDown};function c(e){let n=document.createElement(`div`);Object.entries(s).forEach(([r,i])=>{(0,t.render)((0,t.h)(i,{size:20,strokeWidth:1.5,class:`rd-editor-icon`,style:{fill:`none`},"aria-hidden":`true`},{default:()=>r===`text-color`||r===`highlight-bg-color`?(0,t.h)(`rect`,{id:`tox-icon-${r}__color`,x:3,y:r===`text-color`?19:22,width:18,height:2,fill:`currentColor`,stroke:`none`}):null}),n),e.ui.registry.addIcon(r,n.innerHTML)}),(0,t.render)(null,n)}var l={key:0,class:`rd-rich-editor-footer-bar`},u=(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:r}){let s=e,u=r,d=(0,t.useModel)(e,`modelValue`),f=(0,t.ref)(null),p=`rd-editor_${Date.now()}_${Math.random().toString(36).slice(2,8)}`,m=(0,t.ref)(!1),h=null,g=null,_=null,v=null,y=[];o();function b(){let e=h?.getContent()??``;return g&&(g.takeRecords(),v=e),e}function x(e){let t=h?.setContent(e??``);return d.value=b(),t}function S(e){h?.insertContent(e),d.value=b()}function C(e,t){let n=Array.isArray(t)?t:[t],r=e;return n.forEach(e=>{let t=e.trim();t&&(RegExp(`(?:^|\\s)${T(t)}(?:\\s|$)`).test(r)||(r=`${r} ${t}`.trim()))}),r}function w(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)${T(t)}(?=\\s|$)`,`g`);r=r.replace(n,` `).replace(/\s+/g,` `).trim()}),r}function T(e){return e.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`)}function E(){let{includes:e,excludes:t,textPhrase:n,imagesUploadHandler:r,fetchTextPhrases:o,tinymceBaseUrl:l,languageUrl:S,...T}=s,E=s.language===`zh-cn`?`zh_CN`:s.language,D={selector:`textarea#${p}`,language:E,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,elementpath:!1,content_style:`
|
|
2
|
+
body.mce-content-body {
|
|
3
|
+
background-color: var(--rd-editor-bg, #fff);
|
|
4
|
+
color: var(--rd-editor-text, #202631);
|
|
5
|
+
}
|
|
6
|
+
body.mce-content-body a { color: var(--rd-editor-link, #9f5e53); }
|
|
7
|
+
body.mce-content-body :is(table, td, th) { border-color: var(--rd-editor-border, #d5dbe3); }
|
|
8
|
+
body.mce-content-body [data-mce-selected]:not(td):not(th):not([data-mce-selected="inline-boundary"]),
|
|
9
|
+
body.mce-content-body .mce-edit-focus {
|
|
10
|
+
outline-color: var(--rd-editor-focus, #6e7a8c);
|
|
11
|
+
outline-width: 2px;
|
|
12
|
+
}
|
|
13
|
+
body.mce-content-body div.mce-resizehandle,
|
|
14
|
+
body.mce-content-body div.mce-resizehandle:hover {
|
|
15
|
+
background-color: var(--rd-editor-selection, #e11d48);
|
|
16
|
+
border-color: var(--rd-editor-focus, #6e7a8c);
|
|
17
|
+
border-radius: 2px;
|
|
18
|
+
}
|
|
19
|
+
body.mce-content-body [data-mce-selected="inline-boundary"],
|
|
20
|
+
body.mce-content-body :is(td, th)[data-mce-selected]::after {
|
|
21
|
+
background-color: var(--rd-editor-selection-bg, #fff1f2);
|
|
22
|
+
border-color: var(--rd-editor-focus, #6e7a8c);
|
|
23
|
+
}
|
|
24
|
+
`,table_default_styles:{width:`100%`,borderCollapse:`collapse`},image_advtab:!0,image_title:!1,paste_data_images:!0,images_upload_handler:(e,t,n)=>{if(!r){n(`imagesUploadHandler is not provided`);return}r(e.blob()).then(e=>{e?t(e):n(`Upload failed`)}).catch(()=>n(`Upload failed`))},setup(e){h=e,c(e),e.on(`init`,()=>{m.value=!0;let t=h?.getBody();if(t){t.style.fontSize=`16px`,t.style.fontFamily=`Calibri`;let n=()=>{let e=getComputedStyle(f.value??document.documentElement);t.style.setProperty(`--rd-editor-bg`,e.getPropertyValue(`--rd-color-bg`).trim()||`#fff`),t.style.setProperty(`--rd-editor-text`,e.getPropertyValue(`--rd-color-text`).trim()||`#202631`),t.style.setProperty(`--rd-editor-link`,e.getPropertyValue(`--rd-color-selection-hover`).trim()||`#9f5e53`),t.style.setProperty(`--rd-editor-border`,e.getPropertyValue(`--rd-color-border-input`).trim()||`#d5dbe3`),t.style.colorScheme=e.colorScheme,t.style.setProperty(`--rd-editor-focus`,e.getPropertyValue(`--rd-color-focus`).trim()||`#6e7a8c`),t.style.setProperty(`--rd-editor-selection`,e.getPropertyValue(`--rd-color-selection`).trim()||`#e11d48`),t.style.setProperty(`--rd-editor-selection-bg`,e.getPropertyValue(`--rd-color-selection-bg`).trim()||`#fff1f2`)};n(),e.on(`focus`,n),typeof MutationObserver<`u`&&(_=new MutationObserver(n),_.observe(document.documentElement,{attributes:!0,attributeFilter:[`class`,`style`]}),g=new MutationObserver(()=>{v=null}),g.observe(t,{subtree:!0,childList:!0,characterData:!0,attributes:!0}))}for(x(d.value);y.length;)y.shift()?.()}),e.on(`change`,e=>{let t=b();d.value=t,u(`change`,t,e)}),e.on(`input`,e=>{v=null,u(`input`,e)}),e.on(`BeforeSetContent SetContent Undo Redo`,()=>{v=null}),n&&(o?a(e,o):console.warn(`[RdRichEditor] textPhrase is true but fetchTextPhrases is not provided`))}};l&&(D.base_url=l),S&&(D.language_url=S);let O={...D,...i(T)};return e&&Object.keys(e).forEach(t=>{let n=O[t],r=e[t];typeof n==`string`&&r!=null&&(O[t]=C(n,r))}),t&&Object.keys(t).forEach(e=>{let n=O[e],r=t[e];typeof n==`string`&&r!=null&&(O[e]=w(n,r))}),O}function D(e){return function(...t){return m.value?e(...t):new Promise(n=>{y.push(()=>n(e(...t)))})}}function O(){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=>{m.value&&(g?.takeRecords().length&&(v=null),e!==(v??b())&&x(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}s.tinymceBaseUrl&&(e.baseURL=s.tinymceBaseUrl),e.init(E())}),(0,t.onBeforeUnmount)(()=>{_?.disconnect(),_=null,g?.disconnect(),g=null,v=null,h?.remove(),h=null}),n({getEditor:D(()=>h),getContent:D(b),setContent:D(x),insertContent:D(S),validate:O}),(e,n)=>((0,t.openBlock)(),(0,t.createElementBlock)(`div`,{ref_key:`editorRoot`,ref:f,class:`rd-rich-editor`},[(0,t.createElementVNode)(`textarea`,{id:p,style:{visibility:`hidden`}}),m.value?((0,t.openBlock)(),(0,t.createElementBlock)(`div`,l,[(0,t.renderSlot)(e.$slots,`footer-bar`)])):(0,t.createCommentVNode)(``,!0)],512))}}),d=[u];function f(e){d.forEach(t=>{let n=t.name||t.__name;n&&e.component(n,t)})}exports.RdRichEditor=u,exports.RodelEditorResolver=e.RodelEditorResolver,exports.default=f,exports.install=f;
|
|
2
25
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -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 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
|
+
{"version":3,"file":"index.cjs","names":[],"sources":["../src/utils/cleanObject.ts","../src/components/rdRichEditor/useQuickTextPhrase.ts","../src/components/rdRichEditor/dialogZIndexBridge.ts","../src/components/rdRichEditor/toolbarIcons.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","import { h, render } from 'vue'\r\nimport {\r\n AlignCenter, AlignJustify, AlignLeft, AlignRight, Baseline, Bold, ChevronDown, Code,\r\n Highlighter, Image, IndentDecrease, IndentIncrease, Italic, Link, List,\r\n ListOrdered, Redo2, Table2, Undo2,\r\n} from '@lucide/vue'\r\nimport type { TinyMCEEditor } from '../../types/tinymce'\r\n\r\n// 只替换默认工具栏使用的图标;按钮行为仍交给 TinyMCE,线条与系统 Lucide 图标一致。\r\nconst toolbarIcons = {\r\n undo: Undo2,\r\n redo: Redo2,\r\n bold: Bold,\r\n italic: Italic,\r\n 'text-color': Baseline,\r\n 'highlight-bg-color': Highlighter,\r\n 'align-left': AlignLeft,\r\n 'align-center': AlignCenter,\r\n 'align-right': AlignRight,\r\n 'align-justify': AlignJustify,\r\n outdent: IndentDecrease,\r\n indent: IndentIncrease,\r\n 'unordered-list': List,\r\n 'ordered-list': ListOrdered,\r\n table: Table2,\r\n link: Link,\r\n image: Image,\r\n sourcecode: Code,\r\n 'chevron-down': ChevronDown,\r\n}\r\n\r\n/** 将现有 Vue 图标转换为 TinyMCE 接受的 SVG,不向页面插入额外节点。 */\r\nexport function registerToolbarIcons(editor: TinyMCEEditor) {\r\n const host = document.createElement('div')\r\n Object.entries(toolbarIcons).forEach(([name, icon]) => {\r\n // 20px / 1.5 描边降低视觉重量;内联 fill 防止异步皮肤在悬停、选中时把线条图标重新填实。\r\n render(h(icon, { size: 20, strokeWidth: 1.5, class: 'rd-editor-icon', style: { fill: 'none' }, 'aria-hidden': 'true' }, {\r\n default: () => name === 'text-color' || name === 'highlight-bg-color'\r\n // TinyMCE 依靠这个 ID 更新当前颜色;保留色条,避免换图标后丢失颜色反馈。\r\n ? h('rect', { id: `tox-icon-${name}__color`, x: 3, y: name === 'text-color' ? 19 : 22, width: 18, height: 2, fill: 'currentColor', stroke: 'none' })\r\n : null,\r\n }), host)\r\n editor.ui.registry.addIcon(name, host.innerHTML)\r\n })\r\n render(null, host)\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 { registerToolbarIcons } from './toolbarIcons'\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\n// 读取当前编辑器宿主的主题,支持业务页面局部设置颜色变量。\r\nconst editorRoot = ref<HTMLElement | null>(null)\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\n// iframe 不继承外层主题;独立监听根主题,切换时同步而不重建编辑器或改动正文。\r\nlet themeObserver: 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 // 源码入口并入末尾操作组,避免窄屏时单独占据一整行。\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 // 底栏只保留字数与缩放操作,不展示 P / STRONG 等面向开发者的元素路径。\r\n elementpath: false,\r\n // iframe 使用宿主同步的展示变量;仅改变默认外观,正文中显式设置的颜色仍优先。\r\n content_style: `\r\n body.mce-content-body {\r\n background-color: var(--rd-editor-bg, #fff);\r\n color: var(--rd-editor-text, #202631);\r\n }\r\n body.mce-content-body a { color: var(--rd-editor-link, #9f5e53); }\r\n body.mce-content-body :is(table, td, th) { border-color: var(--rd-editor-border, #d5dbe3); }\r\n body.mce-content-body [data-mce-selected]:not(td):not(th):not([data-mce-selected=\"inline-boundary\"]),\r\n body.mce-content-body .mce-edit-focus {\r\n outline-color: var(--rd-editor-focus, #6e7a8c);\r\n outline-width: 2px;\r\n }\r\n body.mce-content-body div.mce-resizehandle,\r\n body.mce-content-body div.mce-resizehandle:hover {\r\n background-color: var(--rd-editor-selection, #e11d48);\r\n border-color: var(--rd-editor-focus, #6e7a8c);\r\n border-radius: 2px;\r\n }\r\n body.mce-content-body [data-mce-selected=\"inline-boundary\"],\r\n body.mce-content-body :is(td, th)[data-mce-selected]::after {\r\n background-color: var(--rd-editor-selection-bg, #fff1f2);\r\n border-color: var(--rd-editor-focus, #6e7a8c);\r\n }\r\n `,\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 registerToolbarIcons(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 // 只写入 iframe body 的展示变量;保留文档内容、光标、撤销栈及显式文字颜色。\r\n const syncContentTheme = () => {\r\n const theme = getComputedStyle(editorRoot.value ?? document.documentElement)\r\n body.style.setProperty('--rd-editor-bg', theme.getPropertyValue('--rd-color-bg').trim() || '#fff')\r\n body.style.setProperty('--rd-editor-text', theme.getPropertyValue('--rd-color-text').trim() || '#202631')\r\n body.style.setProperty('--rd-editor-link', theme.getPropertyValue('--rd-color-selection-hover').trim() || '#9f5e53')\r\n body.style.setProperty('--rd-editor-border', theme.getPropertyValue('--rd-color-border-input').trim() || '#d5dbe3')\r\n body.style.colorScheme = theme.colorScheme\r\n body.style.setProperty('--rd-editor-focus', theme.getPropertyValue('--rd-color-focus').trim() || '#6e7a8c')\r\n body.style.setProperty('--rd-editor-selection', theme.getPropertyValue('--rd-color-selection').trim() || '#e11d48')\r\n body.style.setProperty('--rd-editor-selection-bg', theme.getPropertyValue('--rd-color-selection-bg').trim() || '#fff1f2')\r\n }\r\n syncContentTheme()\r\n // 聚焦补同步局部宿主变量;全局明暗切换无需重新聚焦即可生效。\r\n editor.on('focus', syncContentTheme)\r\n if (typeof MutationObserver !== 'undefined') {\r\n themeObserver = new MutationObserver(syncContentTheme)\r\n themeObserver.observe(document.documentElement, { attributes: true, attributeFilter: ['class', 'style'] })\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 // 根主题监听与正文缓存监听一同释放,避免已卸载的编辑器继续响应主题变化。\r\n themeObserver?.disconnect()\r\n themeObserver = null\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 ref=\"editorRoot\" 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 { registerToolbarIcons } from './toolbarIcons'\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\n// 读取当前编辑器宿主的主题,支持业务页面局部设置颜色变量。\r\nconst editorRoot = ref<HTMLElement | null>(null)\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\n// iframe 不继承外层主题;独立监听根主题,切换时同步而不重建编辑器或改动正文。\r\nlet themeObserver: 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 // 源码入口并入末尾操作组,避免窄屏时单独占据一整行。\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 // 底栏只保留字数与缩放操作,不展示 P / STRONG 等面向开发者的元素路径。\r\n elementpath: false,\r\n // iframe 使用宿主同步的展示变量;仅改变默认外观,正文中显式设置的颜色仍优先。\r\n content_style: `\r\n body.mce-content-body {\r\n background-color: var(--rd-editor-bg, #fff);\r\n color: var(--rd-editor-text, #202631);\r\n }\r\n body.mce-content-body a { color: var(--rd-editor-link, #9f5e53); }\r\n body.mce-content-body :is(table, td, th) { border-color: var(--rd-editor-border, #d5dbe3); }\r\n body.mce-content-body [data-mce-selected]:not(td):not(th):not([data-mce-selected=\"inline-boundary\"]),\r\n body.mce-content-body .mce-edit-focus {\r\n outline-color: var(--rd-editor-focus, #6e7a8c);\r\n outline-width: 2px;\r\n }\r\n body.mce-content-body div.mce-resizehandle,\r\n body.mce-content-body div.mce-resizehandle:hover {\r\n background-color: var(--rd-editor-selection, #e11d48);\r\n border-color: var(--rd-editor-focus, #6e7a8c);\r\n border-radius: 2px;\r\n }\r\n body.mce-content-body [data-mce-selected=\"inline-boundary\"],\r\n body.mce-content-body :is(td, th)[data-mce-selected]::after {\r\n background-color: var(--rd-editor-selection-bg, #fff1f2);\r\n border-color: var(--rd-editor-focus, #6e7a8c);\r\n }\r\n `,\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 registerToolbarIcons(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 // 只写入 iframe body 的展示变量;保留文档内容、光标、撤销栈及显式文字颜色。\r\n const syncContentTheme = () => {\r\n const theme = getComputedStyle(editorRoot.value ?? document.documentElement)\r\n body.style.setProperty('--rd-editor-bg', theme.getPropertyValue('--rd-color-bg').trim() || '#fff')\r\n body.style.setProperty('--rd-editor-text', theme.getPropertyValue('--rd-color-text').trim() || '#202631')\r\n body.style.setProperty('--rd-editor-link', theme.getPropertyValue('--rd-color-selection-hover').trim() || '#9f5e53')\r\n body.style.setProperty('--rd-editor-border', theme.getPropertyValue('--rd-color-border-input').trim() || '#d5dbe3')\r\n body.style.colorScheme = theme.colorScheme\r\n body.style.setProperty('--rd-editor-focus', theme.getPropertyValue('--rd-color-focus').trim() || '#6e7a8c')\r\n body.style.setProperty('--rd-editor-selection', theme.getPropertyValue('--rd-color-selection').trim() || '#e11d48')\r\n body.style.setProperty('--rd-editor-selection-bg', theme.getPropertyValue('--rd-color-selection-bg').trim() || '#fff1f2')\r\n }\r\n syncContentTheme()\r\n // 聚焦补同步局部宿主变量;全局明暗切换无需重新聚焦即可生效。\r\n editor.on('focus', syncContentTheme)\r\n if (typeof MutationObserver !== 'undefined') {\r\n themeObserver = new MutationObserver(syncContentTheme)\r\n themeObserver.observe(document.documentElement, { attributes: true, attributeFilter: ['class', 'style'] })\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 // 根主题监听与正文缓存监听一同释放,避免已卸载的编辑器继续响应主题变化。\r\n themeObserver?.disconnect()\r\n themeObserver = null\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 ref=\"editorRoot\" 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":"yMAGA,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,EAA2C,KAC3C,EAA4C,KAEhD,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,KACR,IAAoB,OACtB,AACE,IAAU,EAAiB,CAAC,CAAC,KAAM,IACjC,EAAkB,GAAS,CAAC,EACrB,EACR,CAAC,CAAC,YAAc,CACf,EAAU,IACZ,CAAC,EAEH,MAAM,GAED,EAAoB,EAAkB,CAAO,GAEtD,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,CCdA,IAAM,EAAe,CACnB,KAAM,EAAA,MACN,KAAM,EAAA,MACN,KAAM,EAAA,KACN,OAAQ,EAAA,OACR,aAAc,EAAA,SACd,qBAAsB,EAAA,YACtB,aAAc,EAAA,UACd,eAAgB,EAAA,YAChB,cAAe,EAAA,WACf,gBAAiB,EAAA,aACjB,QAAS,EAAA,eACT,OAAQ,EAAA,eACR,iBAAkB,EAAA,KAClB,eAAgB,EAAA,YAChB,MAAO,EAAA,OACP,KAAM,EAAA,KACN,MAAO,EAAA,MACP,WAAY,EAAA,KACZ,eAAgB,EAAA,WAClB,EAGA,SAAgB,EAAqB,EAAuB,CAC1D,IAAM,EAAO,SAAS,cAAc,KAAK,EACzC,OAAO,QAAQ,CAAY,CAAC,CAAC,SAAS,CAAC,EAAM,KAAU,EAErD,EAAA,EAAA,OAAA,EAAA,EAAA,EAAA,EAAA,CAAS,EAAM,CAAE,KAAM,GAAI,YAAa,IAAK,MAAO,iBAAkB,MAAO,CAAE,KAAM,MAAO,EAAG,cAAe,MAAO,EAAG,CACtH,YAAe,IAAS,cAAgB,IAAS,sBAAA,EAAA,EAAA,EAAA,CAE3C,OAAQ,CAAE,GAAI,YAAY,EAAK,SAAU,EAAG,EAAG,EAAG,IAAS,aAAe,GAAK,GAAI,MAAO,GAAI,OAAQ,EAAG,KAAM,eAAgB,OAAQ,MAAO,CAAC,EACjJ,IACN,CAAC,EAAG,CAAI,EACR,EAAO,GAAG,SAAS,QAAQ,EAAM,EAAK,SAAS,CACjD,CAAC,GACD,EAAA,EAAA,OAAA,CAAO,KAAM,CAAI,CACnB,qgBCDA,IAAM,EAAQ,EAKR,EAAQ,EAKR,GAAA,EAAA,EAAA,SAAA,CAAgC,EAAA,YAAgB,EAEhD,GAAA,EAAA,EAAA,IAAA,CAAqC,IAAI,EACzC,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,KAClC,EAA2C,KAE3C,EAAyC,KACzC,EAA+B,KAE7B,EAA0B,CAAC,EAEjC,EAAwB,EAExB,SAAS,GAAiB,CACxB,IAAM,EAAU,GAAW,WAAW,GAAK,GAK3C,OAJI,IACF,EAAgB,YAAY,EAC5B,EAAgB,GAEX,CACT,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,EAEA,QACE,mMACF,aAAc,OACd,iBACE,sGACF,aACE,0eACF,WAAY,IAEZ,YAAa,GAEb,cAAe;;;;;;;;;;;;;;;;;;;;;;;MAwBf,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,EAAqB,CAAM,EAC3B,EAAO,GAAG,WAAc,CACtB,EAAO,MAAQ,GACf,IAAM,EAAO,GAAW,QAAQ,EAChC,GAAI,EAAM,CACR,EAAK,MAAM,SAAW,OACtB,EAAK,MAAM,WAAa,UAExB,IAAM,MAAyB,CAC7B,IAAM,EAAQ,iBAAiB,EAAW,OAAS,SAAS,eAAe,EAC3E,EAAK,MAAM,YAAY,iBAAkB,EAAM,iBAAiB,eAAe,CAAC,CAAC,KAAK,GAAK,MAAM,EACjG,EAAK,MAAM,YAAY,mBAAoB,EAAM,iBAAiB,iBAAiB,CAAC,CAAC,KAAK,GAAK,SAAS,EACxG,EAAK,MAAM,YAAY,mBAAoB,EAAM,iBAAiB,4BAA4B,CAAC,CAAC,KAAK,GAAK,SAAS,EACnH,EAAK,MAAM,YAAY,qBAAsB,EAAM,iBAAiB,yBAAyB,CAAC,CAAC,KAAK,GAAK,SAAS,EAClH,EAAK,MAAM,YAAc,EAAM,YAC/B,EAAK,MAAM,YAAY,oBAAqB,EAAM,iBAAiB,kBAAkB,CAAC,CAAC,KAAK,GAAK,SAAS,EAC1G,EAAK,MAAM,YAAY,wBAAyB,EAAM,iBAAiB,sBAAsB,CAAC,CAAC,KAAK,GAAK,SAAS,EAClH,EAAK,MAAM,YAAY,2BAA4B,EAAM,iBAAiB,yBAAyB,CAAC,CAAC,KAAK,GAAK,SAAS,CAC1H,EACA,EAAiB,EAEjB,EAAO,GAAG,QAAS,CAAgB,EAC/B,OAAO,iBAAqB,MAC9B,EAAgB,IAAI,iBAAiB,CAAgB,EACrD,EAAc,QAAQ,SAAS,gBAAiB,CAAE,WAAY,GAAM,gBAAiB,CAAC,QAAS,OAAO,CAAE,CAAC,EACzG,EAAkB,IAAI,qBAAuB,CAAE,EAAgB,IAAK,CAAC,EACrE,EAAgB,QAAQ,EAAM,CAAE,QAAS,GAAM,UAAW,GAAM,cAAe,GAAM,WAAY,EAAK,CAAC,EAE3G,CAEA,IADA,EAAe,EAAW,KAAK,EACxB,EAAK,QACV,EAAK,MAAM,CAAC,GAAG,CAEnB,CAAC,EACD,EAAO,GAAG,SAAW,GAAM,CACzB,IAAM,EAAU,EAAe,EAC/B,EAAW,MAAQ,EACnB,EAAM,SAAU,EAAS,CAAC,CAC5B,CAAC,EACD,EAAO,GAAG,QAAU,GAAM,CACxB,EAAgB,KAChB,EAAM,QAAS,CAAU,CAC3B,CAAC,EACD,EAAO,GAAG,4CAA+C,CAAE,EAAgB,IAAK,CAAC,EAC7E,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,CACL,EAAO,QACR,GAAiB,YAAY,CAAC,CAAC,SAAQ,EAAgB,MACvD,KAAY,GAAiB,EAAe,IAChD,EAAe,CAAM,EACvB,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,CAEpB,GAAe,WAAW,EAC1B,EAAgB,KAChB,GAAiB,WAAW,EAC5B,EAAkB,KAClB,EAAgB,KAChB,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,MAAA,SALG,aAAJ,IAAI,EAAa,MAAM,4CAC4B,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,EAAA,GAAA,MEpW1B,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"}
|
package/dist/index.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { RodelEditorResolver as e } from "./resolver.js";
|
|
2
|
-
import { createCommentVNode as t, createElementBlock as n, createElementVNode as r, defineComponent as i,
|
|
3
|
-
import { dialogInjectionKey as
|
|
2
|
+
import { createCommentVNode as t, createElementBlock as n, createElementVNode as r, defineComponent as i, h as a, inject as o, mergeModels as s, onBeforeUnmount as c, onMounted as l, openBlock as u, ref as d, render as f, renderSlot as p, useModel as m, watch as h, watchEffect as g } from "vue";
|
|
3
|
+
import { dialogInjectionKey as _, useZIndex as v } from "element-plus";
|
|
4
|
+
import { AlignCenter as y, AlignJustify as b, AlignLeft as x, AlignRight as S, Baseline as C, Bold as w, ChevronDown as T, Code as E, Highlighter as D, Image as O, IndentDecrease as k, IndentIncrease as A, Italic as j, Link as M, List as N, ListOrdered as P, Redo2 as F, Table2 as I, Undo2 as L } from "@lucide/vue";
|
|
4
5
|
import './style.css';//#region src/utils/cleanObject.ts
|
|
5
|
-
function
|
|
6
|
+
function R(e) {
|
|
6
7
|
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))) : {};
|
|
7
8
|
}
|
|
8
9
|
//#endregion
|
|
9
10
|
//#region src/components/rdRichEditor/useQuickTextPhrase.ts
|
|
10
|
-
function
|
|
11
|
+
function z(e, t) {
|
|
11
12
|
let n = null, r = null;
|
|
12
13
|
function i(e, t) {
|
|
13
14
|
return e.filter((e) => e.input_code.includes(t) || e.name.includes(t)).map((e) => ({
|
|
@@ -40,24 +41,67 @@ function v(e, t) {
|
|
|
40
41
|
}
|
|
41
42
|
//#endregion
|
|
42
43
|
//#region src/components/rdRichEditor/dialogZIndexBridge.ts
|
|
43
|
-
function
|
|
44
|
-
if (!
|
|
45
|
-
let e =
|
|
46
|
-
|
|
44
|
+
function B() {
|
|
45
|
+
if (!o(_, null)) return;
|
|
46
|
+
let e = v().currentZIndex;
|
|
47
|
+
g(() => {
|
|
47
48
|
e.value > 1300 && document.documentElement.style.setProperty("--el-dialog-z-index", e.value.toString());
|
|
48
|
-
}),
|
|
49
|
+
}), c(() => {
|
|
49
50
|
document.documentElement.style.removeProperty("--el-dialog-z-index");
|
|
50
51
|
});
|
|
51
52
|
}
|
|
52
53
|
//#endregion
|
|
54
|
+
//#region src/components/rdRichEditor/toolbarIcons.ts
|
|
55
|
+
var V = {
|
|
56
|
+
undo: L,
|
|
57
|
+
redo: F,
|
|
58
|
+
bold: w,
|
|
59
|
+
italic: j,
|
|
60
|
+
"text-color": C,
|
|
61
|
+
"highlight-bg-color": D,
|
|
62
|
+
"align-left": x,
|
|
63
|
+
"align-center": y,
|
|
64
|
+
"align-right": S,
|
|
65
|
+
"align-justify": b,
|
|
66
|
+
outdent: k,
|
|
67
|
+
indent: A,
|
|
68
|
+
"unordered-list": N,
|
|
69
|
+
"ordered-list": P,
|
|
70
|
+
table: I,
|
|
71
|
+
link: M,
|
|
72
|
+
image: O,
|
|
73
|
+
sourcecode: E,
|
|
74
|
+
"chevron-down": T
|
|
75
|
+
};
|
|
76
|
+
function H(e) {
|
|
77
|
+
let t = document.createElement("div");
|
|
78
|
+
Object.entries(V).forEach(([n, r]) => {
|
|
79
|
+
f(a(r, {
|
|
80
|
+
size: 20,
|
|
81
|
+
strokeWidth: 1.5,
|
|
82
|
+
class: "rd-editor-icon",
|
|
83
|
+
style: { fill: "none" },
|
|
84
|
+
"aria-hidden": "true"
|
|
85
|
+
}, { default: () => n === "text-color" || n === "highlight-bg-color" ? a("rect", {
|
|
86
|
+
id: `tox-icon-${n}__color`,
|
|
87
|
+
x: 3,
|
|
88
|
+
y: n === "text-color" ? 19 : 22,
|
|
89
|
+
width: 18,
|
|
90
|
+
height: 2,
|
|
91
|
+
fill: "currentColor",
|
|
92
|
+
stroke: "none"
|
|
93
|
+
}) : null }), t), e.ui.registry.addIcon(n, t.innerHTML);
|
|
94
|
+
}), f(null, t);
|
|
95
|
+
}
|
|
96
|
+
//#endregion
|
|
53
97
|
//#region src/components/rdRichEditor/RdRichEditor.vue?vue&type=script&setup=true&lang.ts
|
|
54
|
-
var
|
|
98
|
+
var U = {
|
|
55
99
|
key: 0,
|
|
56
100
|
class: "rd-rich-editor-footer-bar"
|
|
57
|
-
},
|
|
101
|
+
}, W = /* @__PURE__ */ i({
|
|
58
102
|
name: "RdRichEditor",
|
|
59
103
|
__name: "RdRichEditor",
|
|
60
|
-
props: /*@__PURE__*/
|
|
104
|
+
props: /*@__PURE__*/ s({
|
|
61
105
|
plugins: {},
|
|
62
106
|
language: { default: "zh_CN" },
|
|
63
107
|
menubar: {},
|
|
@@ -79,55 +123,57 @@ var b = { class: "rd-rich-editor" }, x = {
|
|
|
79
123
|
modelValue: { default: "" },
|
|
80
124
|
modelModifiers: {}
|
|
81
125
|
}),
|
|
82
|
-
emits: /*@__PURE__*/
|
|
126
|
+
emits: /*@__PURE__*/ s(["input", "change"], ["update:modelValue"]),
|
|
83
127
|
setup(e, { expose: i, emit: a }) {
|
|
84
|
-
let o = e,
|
|
85
|
-
|
|
86
|
-
function
|
|
87
|
-
let e =
|
|
88
|
-
return
|
|
128
|
+
let o = e, s = a, f = m(e, "modelValue"), g = d(null), _ = `rd-editor_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`, v = d(!1), y = null, b = null, x = null, S = null, C = [];
|
|
129
|
+
B();
|
|
130
|
+
function w() {
|
|
131
|
+
let e = y?.getContent() ?? "";
|
|
132
|
+
return b && (b.takeRecords(), S = e), e;
|
|
89
133
|
}
|
|
90
|
-
function
|
|
91
|
-
let t =
|
|
92
|
-
return
|
|
134
|
+
function T(e) {
|
|
135
|
+
let t = y?.setContent(e ?? "");
|
|
136
|
+
return f.value = w(), t;
|
|
93
137
|
}
|
|
94
|
-
function
|
|
95
|
-
|
|
138
|
+
function E(e) {
|
|
139
|
+
y?.insertContent(e), f.value = w();
|
|
96
140
|
}
|
|
97
|
-
function
|
|
141
|
+
function D(e, t) {
|
|
98
142
|
let n = Array.isArray(t) ? t : [t], r = e;
|
|
99
143
|
return n.forEach((e) => {
|
|
100
144
|
let t = e.trim();
|
|
101
|
-
t && (RegExp(`(?:^|\\s)${
|
|
145
|
+
t && (RegExp(`(?:^|\\s)${k(t)}(?:\\s|$)`).test(r) || (r = `${r} ${t}`.trim()));
|
|
102
146
|
}), r;
|
|
103
147
|
}
|
|
104
|
-
function
|
|
148
|
+
function O(e, t) {
|
|
105
149
|
let n = Array.isArray(t) ? t : [t], r = e;
|
|
106
150
|
return n.forEach((e) => {
|
|
107
151
|
let t = e.trim();
|
|
108
152
|
if (!t) return;
|
|
109
|
-
let n = RegExp(`(?:^|\\s)${
|
|
153
|
+
let n = RegExp(`(?:^|\\s)${k(t)}(?=\\s|$)`, "g");
|
|
110
154
|
r = r.replace(n, " ").replace(/\s+/g, " ").trim();
|
|
111
155
|
}), r;
|
|
112
156
|
}
|
|
113
|
-
function
|
|
157
|
+
function k(e) {
|
|
114
158
|
return e.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
115
159
|
}
|
|
116
|
-
function
|
|
117
|
-
let { includes: e, excludes: t, textPhrase: n, imagesUploadHandler: r, fetchTextPhrases: i, tinymceBaseUrl: a, languageUrl:
|
|
118
|
-
selector: `textarea#${
|
|
119
|
-
language:
|
|
160
|
+
function A() {
|
|
161
|
+
let { includes: e, excludes: t, textPhrase: n, imagesUploadHandler: r, fetchTextPhrases: i, tinymceBaseUrl: a, languageUrl: c, ...l } = o, u = o.language === "zh-cn" ? "zh_CN" : o.language, d = {
|
|
162
|
+
selector: `textarea#${_}`,
|
|
163
|
+
language: u,
|
|
120
164
|
plugins: "advlist autolink link image lists charmap emoticons preview hr anchor pagebreak nonbreaking code table paste insertdatetime wordcount",
|
|
121
165
|
menubar: "file edit view insert format tools",
|
|
122
166
|
menu: { insert: {
|
|
123
167
|
title: "Insert",
|
|
124
168
|
items: "image link inserttable | charmap emoticons hr | pagebreak nonbreaking anchor | insertdatetime"
|
|
125
169
|
} },
|
|
126
|
-
toolbar: "undo redo | styleselect | fontsizeselect fontselect | bold italic forecolor backcolor | alignleft aligncenter alignright alignjustify | outdent indent | bullist numlist table | link image
|
|
170
|
+
toolbar: "undo redo | styleselect | fontsizeselect fontselect | bold italic forecolor backcolor | alignleft aligncenter alignright alignjustify | outdent indent | bullist numlist table | link image code",
|
|
127
171
|
toolbar_mode: "wrap",
|
|
128
172
|
fontsize_formats: "5px 6px 7px 8px 9px 10px 12px 14px 16px 18px 20px 22px 24px 26px 28px 30px 32px 34px 36px 48px 72px",
|
|
129
173
|
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,;",
|
|
130
174
|
min_height: 480,
|
|
175
|
+
elementpath: !1,
|
|
176
|
+
content_style: "\n body.mce-content-body {\n background-color: var(--rd-editor-bg, #fff);\n color: var(--rd-editor-text, #202631);\n }\n body.mce-content-body a { color: var(--rd-editor-link, #9f5e53); }\n body.mce-content-body :is(table, td, th) { border-color: var(--rd-editor-border, #d5dbe3); }\n body.mce-content-body [data-mce-selected]:not(td):not(th):not([data-mce-selected=\"inline-boundary\"]),\n body.mce-content-body .mce-edit-focus {\n outline-color: var(--rd-editor-focus, #6e7a8c);\n outline-width: 2px;\n }\n body.mce-content-body div.mce-resizehandle,\n body.mce-content-body div.mce-resizehandle:hover {\n background-color: var(--rd-editor-selection, #e11d48);\n border-color: var(--rd-editor-focus, #6e7a8c);\n border-radius: 2px;\n }\n body.mce-content-body [data-mce-selected=\"inline-boundary\"],\n body.mce-content-body :is(td, th)[data-mce-selected]::after {\n background-color: var(--rd-editor-selection-bg, #fff1f2);\n border-color: var(--rd-editor-focus, #6e7a8c);\n }\n ",
|
|
131
177
|
table_default_styles: {
|
|
132
178
|
width: "100%",
|
|
133
179
|
borderCollapse: "collapse"
|
|
@@ -145,81 +191,96 @@ var b = { class: "rd-rich-editor" }, x = {
|
|
|
145
191
|
}).catch(() => n("Upload failed"));
|
|
146
192
|
},
|
|
147
193
|
setup(e) {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
let
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
194
|
+
y = e, H(e), e.on("init", () => {
|
|
195
|
+
v.value = !0;
|
|
196
|
+
let t = y?.getBody();
|
|
197
|
+
if (t) {
|
|
198
|
+
t.style.fontSize = "16px", t.style.fontFamily = "Calibri";
|
|
199
|
+
let n = () => {
|
|
200
|
+
let e = getComputedStyle(g.value ?? document.documentElement);
|
|
201
|
+
t.style.setProperty("--rd-editor-bg", e.getPropertyValue("--rd-color-bg").trim() || "#fff"), t.style.setProperty("--rd-editor-text", e.getPropertyValue("--rd-color-text").trim() || "#202631"), t.style.setProperty("--rd-editor-link", e.getPropertyValue("--rd-color-selection-hover").trim() || "#9f5e53"), t.style.setProperty("--rd-editor-border", e.getPropertyValue("--rd-color-border-input").trim() || "#d5dbe3"), t.style.colorScheme = e.colorScheme, t.style.setProperty("--rd-editor-focus", e.getPropertyValue("--rd-color-focus").trim() || "#6e7a8c"), t.style.setProperty("--rd-editor-selection", e.getPropertyValue("--rd-color-selection").trim() || "#e11d48"), t.style.setProperty("--rd-editor-selection-bg", e.getPropertyValue("--rd-color-selection-bg").trim() || "#fff1f2");
|
|
202
|
+
};
|
|
203
|
+
n(), e.on("focus", n), typeof MutationObserver < "u" && (x = new MutationObserver(n), x.observe(document.documentElement, {
|
|
204
|
+
attributes: !0,
|
|
205
|
+
attributeFilter: ["class", "style"]
|
|
206
|
+
}), b = new MutationObserver(() => {
|
|
207
|
+
S = null;
|
|
208
|
+
}), b.observe(t, {
|
|
209
|
+
subtree: !0,
|
|
210
|
+
childList: !0,
|
|
211
|
+
characterData: !0,
|
|
212
|
+
attributes: !0
|
|
213
|
+
}));
|
|
214
|
+
}
|
|
215
|
+
for (T(f.value); C.length;) C.shift()?.();
|
|
159
216
|
}), e.on("change", (e) => {
|
|
160
|
-
let t =
|
|
161
|
-
|
|
217
|
+
let t = w();
|
|
218
|
+
f.value = t, s("change", t, e);
|
|
162
219
|
}), e.on("input", (e) => {
|
|
163
|
-
|
|
220
|
+
S = null, s("input", e);
|
|
164
221
|
}), e.on("BeforeSetContent SetContent Undo Redo", () => {
|
|
165
|
-
|
|
166
|
-
}), n && (i ?
|
|
222
|
+
S = null;
|
|
223
|
+
}), n && (i ? z(e, i) : console.warn("[RdRichEditor] textPhrase is true but fetchTextPhrases is not provided"));
|
|
167
224
|
}
|
|
168
225
|
};
|
|
169
|
-
a && (
|
|
170
|
-
let
|
|
171
|
-
...
|
|
172
|
-
...
|
|
226
|
+
a && (d.base_url = a), c && (d.language_url = c);
|
|
227
|
+
let p = {
|
|
228
|
+
...d,
|
|
229
|
+
...R(l)
|
|
173
230
|
};
|
|
174
231
|
return e && Object.keys(e).forEach((t) => {
|
|
175
|
-
let n =
|
|
176
|
-
typeof n == "string" && r != null && (
|
|
232
|
+
let n = p[t], r = e[t];
|
|
233
|
+
typeof n == "string" && r != null && (p[t] = D(n, r));
|
|
177
234
|
}), t && Object.keys(t).forEach((e) => {
|
|
178
|
-
let n =
|
|
179
|
-
typeof n == "string" && r != null && (
|
|
180
|
-
}),
|
|
235
|
+
let n = p[e], r = t[e];
|
|
236
|
+
typeof n == "string" && r != null && (p[e] = O(n, r));
|
|
237
|
+
}), p;
|
|
181
238
|
}
|
|
182
|
-
function
|
|
239
|
+
function j(e) {
|
|
183
240
|
return function(...t) {
|
|
184
|
-
return
|
|
185
|
-
|
|
241
|
+
return v.value ? e(...t) : new Promise((n) => {
|
|
242
|
+
C.push(() => n(e(...t)));
|
|
186
243
|
});
|
|
187
244
|
};
|
|
188
245
|
}
|
|
189
|
-
function
|
|
246
|
+
function M() {
|
|
190
247
|
let e = document.createElement("div");
|
|
191
|
-
return e.innerHTML =
|
|
248
|
+
return e.innerHTML = f.value, !!(e.textContent?.trim() || e.querySelector("a.mce-item-anchor, img, table"));
|
|
192
249
|
}
|
|
193
|
-
return
|
|
194
|
-
|
|
195
|
-
}),
|
|
250
|
+
return h(() => f.value, (e) => {
|
|
251
|
+
v.value && (b?.takeRecords().length && (S = null), e !== (S ?? w()) && T(e));
|
|
252
|
+
}), l(() => {
|
|
196
253
|
let e = window.tinymce;
|
|
197
254
|
if (!e) {
|
|
198
255
|
console.error("[RdRichEditor] window.tinymce is missing. Load TinyMCE 5.10.x via <script> before using RdRichEditor.");
|
|
199
256
|
return;
|
|
200
257
|
}
|
|
201
|
-
o.tinymceBaseUrl && (e.baseURL = o.tinymceBaseUrl), e.init(
|
|
202
|
-
}),
|
|
203
|
-
|
|
258
|
+
o.tinymceBaseUrl && (e.baseURL = o.tinymceBaseUrl), e.init(A());
|
|
259
|
+
}), c(() => {
|
|
260
|
+
x?.disconnect(), x = null, b?.disconnect(), b = null, S = null, y?.remove(), y = null;
|
|
204
261
|
}), i({
|
|
205
|
-
getEditor:
|
|
206
|
-
getContent:
|
|
207
|
-
setContent:
|
|
208
|
-
insertContent:
|
|
209
|
-
validate:
|
|
210
|
-
}), (e, i) => (
|
|
211
|
-
|
|
262
|
+
getEditor: j(() => y),
|
|
263
|
+
getContent: j(w),
|
|
264
|
+
setContent: j(T),
|
|
265
|
+
insertContent: j(E),
|
|
266
|
+
validate: M
|
|
267
|
+
}), (e, i) => (u(), n("div", {
|
|
268
|
+
ref_key: "editorRoot",
|
|
269
|
+
ref: g,
|
|
270
|
+
class: "rd-rich-editor"
|
|
271
|
+
}, [r("textarea", {
|
|
272
|
+
id: _,
|
|
212
273
|
style: { visibility: "hidden" }
|
|
213
|
-
}),
|
|
274
|
+
}), v.value ? (u(), n("div", U, [p(e.$slots, "footer-bar")])) : t("", !0)], 512));
|
|
214
275
|
}
|
|
215
|
-
}),
|
|
216
|
-
function
|
|
217
|
-
|
|
276
|
+
}), G = [W];
|
|
277
|
+
function K(e) {
|
|
278
|
+
G.forEach((t) => {
|
|
218
279
|
let n = t.name || t.__name;
|
|
219
280
|
n && e.component(n, t);
|
|
220
281
|
});
|
|
221
282
|
}
|
|
222
283
|
//#endregion
|
|
223
|
-
export {
|
|
284
|
+
export { W as RdRichEditor, e as RodelEditorResolver, K as default, K as install };
|
|
224
285
|
|
|
225
286
|
//# sourceMappingURL=index.js.map
|
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 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
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/utils/cleanObject.ts","../src/components/rdRichEditor/useQuickTextPhrase.ts","../src/components/rdRichEditor/dialogZIndexBridge.ts","../src/components/rdRichEditor/toolbarIcons.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","import { h, render } from 'vue'\r\nimport {\r\n AlignCenter, AlignJustify, AlignLeft, AlignRight, Baseline, Bold, ChevronDown, Code,\r\n Highlighter, Image, IndentDecrease, IndentIncrease, Italic, Link, List,\r\n ListOrdered, Redo2, Table2, Undo2,\r\n} from '@lucide/vue'\r\nimport type { TinyMCEEditor } from '../../types/tinymce'\r\n\r\n// 只替换默认工具栏使用的图标;按钮行为仍交给 TinyMCE,线条与系统 Lucide 图标一致。\r\nconst toolbarIcons = {\r\n undo: Undo2,\r\n redo: Redo2,\r\n bold: Bold,\r\n italic: Italic,\r\n 'text-color': Baseline,\r\n 'highlight-bg-color': Highlighter,\r\n 'align-left': AlignLeft,\r\n 'align-center': AlignCenter,\r\n 'align-right': AlignRight,\r\n 'align-justify': AlignJustify,\r\n outdent: IndentDecrease,\r\n indent: IndentIncrease,\r\n 'unordered-list': List,\r\n 'ordered-list': ListOrdered,\r\n table: Table2,\r\n link: Link,\r\n image: Image,\r\n sourcecode: Code,\r\n 'chevron-down': ChevronDown,\r\n}\r\n\r\n/** 将现有 Vue 图标转换为 TinyMCE 接受的 SVG,不向页面插入额外节点。 */\r\nexport function registerToolbarIcons(editor: TinyMCEEditor) {\r\n const host = document.createElement('div')\r\n Object.entries(toolbarIcons).forEach(([name, icon]) => {\r\n // 20px / 1.5 描边降低视觉重量;内联 fill 防止异步皮肤在悬停、选中时把线条图标重新填实。\r\n render(h(icon, { size: 20, strokeWidth: 1.5, class: 'rd-editor-icon', style: { fill: 'none' }, 'aria-hidden': 'true' }, {\r\n default: () => name === 'text-color' || name === 'highlight-bg-color'\r\n // TinyMCE 依靠这个 ID 更新当前颜色;保留色条,避免换图标后丢失颜色反馈。\r\n ? h('rect', { id: `tox-icon-${name}__color`, x: 3, y: name === 'text-color' ? 19 : 22, width: 18, height: 2, fill: 'currentColor', stroke: 'none' })\r\n : null,\r\n }), host)\r\n editor.ui.registry.addIcon(name, host.innerHTML)\r\n })\r\n render(null, host)\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 { registerToolbarIcons } from './toolbarIcons'\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\n// 读取当前编辑器宿主的主题,支持业务页面局部设置颜色变量。\r\nconst editorRoot = ref<HTMLElement | null>(null)\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\n// iframe 不继承外层主题;独立监听根主题,切换时同步而不重建编辑器或改动正文。\r\nlet themeObserver: 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 // 源码入口并入末尾操作组,避免窄屏时单独占据一整行。\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 // 底栏只保留字数与缩放操作,不展示 P / STRONG 等面向开发者的元素路径。\r\n elementpath: false,\r\n // iframe 使用宿主同步的展示变量;仅改变默认外观,正文中显式设置的颜色仍优先。\r\n content_style: `\r\n body.mce-content-body {\r\n background-color: var(--rd-editor-bg, #fff);\r\n color: var(--rd-editor-text, #202631);\r\n }\r\n body.mce-content-body a { color: var(--rd-editor-link, #9f5e53); }\r\n body.mce-content-body :is(table, td, th) { border-color: var(--rd-editor-border, #d5dbe3); }\r\n body.mce-content-body [data-mce-selected]:not(td):not(th):not([data-mce-selected=\"inline-boundary\"]),\r\n body.mce-content-body .mce-edit-focus {\r\n outline-color: var(--rd-editor-focus, #6e7a8c);\r\n outline-width: 2px;\r\n }\r\n body.mce-content-body div.mce-resizehandle,\r\n body.mce-content-body div.mce-resizehandle:hover {\r\n background-color: var(--rd-editor-selection, #e11d48);\r\n border-color: var(--rd-editor-focus, #6e7a8c);\r\n border-radius: 2px;\r\n }\r\n body.mce-content-body [data-mce-selected=\"inline-boundary\"],\r\n body.mce-content-body :is(td, th)[data-mce-selected]::after {\r\n background-color: var(--rd-editor-selection-bg, #fff1f2);\r\n border-color: var(--rd-editor-focus, #6e7a8c);\r\n }\r\n `,\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 registerToolbarIcons(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 // 只写入 iframe body 的展示变量;保留文档内容、光标、撤销栈及显式文字颜色。\r\n const syncContentTheme = () => {\r\n const theme = getComputedStyle(editorRoot.value ?? document.documentElement)\r\n body.style.setProperty('--rd-editor-bg', theme.getPropertyValue('--rd-color-bg').trim() || '#fff')\r\n body.style.setProperty('--rd-editor-text', theme.getPropertyValue('--rd-color-text').trim() || '#202631')\r\n body.style.setProperty('--rd-editor-link', theme.getPropertyValue('--rd-color-selection-hover').trim() || '#9f5e53')\r\n body.style.setProperty('--rd-editor-border', theme.getPropertyValue('--rd-color-border-input').trim() || '#d5dbe3')\r\n body.style.colorScheme = theme.colorScheme\r\n body.style.setProperty('--rd-editor-focus', theme.getPropertyValue('--rd-color-focus').trim() || '#6e7a8c')\r\n body.style.setProperty('--rd-editor-selection', theme.getPropertyValue('--rd-color-selection').trim() || '#e11d48')\r\n body.style.setProperty('--rd-editor-selection-bg', theme.getPropertyValue('--rd-color-selection-bg').trim() || '#fff1f2')\r\n }\r\n syncContentTheme()\r\n // 聚焦补同步局部宿主变量;全局明暗切换无需重新聚焦即可生效。\r\n editor.on('focus', syncContentTheme)\r\n if (typeof MutationObserver !== 'undefined') {\r\n themeObserver = new MutationObserver(syncContentTheme)\r\n themeObserver.observe(document.documentElement, { attributes: true, attributeFilter: ['class', 'style'] })\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 // 根主题监听与正文缓存监听一同释放,避免已卸载的编辑器继续响应主题变化。\r\n themeObserver?.disconnect()\r\n themeObserver = null\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 ref=\"editorRoot\" 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 { registerToolbarIcons } from './toolbarIcons'\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\n// 读取当前编辑器宿主的主题,支持业务页面局部设置颜色变量。\r\nconst editorRoot = ref<HTMLElement | null>(null)\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\n// iframe 不继承外层主题;独立监听根主题,切换时同步而不重建编辑器或改动正文。\r\nlet themeObserver: 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 // 源码入口并入末尾操作组,避免窄屏时单独占据一整行。\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 // 底栏只保留字数与缩放操作,不展示 P / STRONG 等面向开发者的元素路径。\r\n elementpath: false,\r\n // iframe 使用宿主同步的展示变量;仅改变默认外观,正文中显式设置的颜色仍优先。\r\n content_style: `\r\n body.mce-content-body {\r\n background-color: var(--rd-editor-bg, #fff);\r\n color: var(--rd-editor-text, #202631);\r\n }\r\n body.mce-content-body a { color: var(--rd-editor-link, #9f5e53); }\r\n body.mce-content-body :is(table, td, th) { border-color: var(--rd-editor-border, #d5dbe3); }\r\n body.mce-content-body [data-mce-selected]:not(td):not(th):not([data-mce-selected=\"inline-boundary\"]),\r\n body.mce-content-body .mce-edit-focus {\r\n outline-color: var(--rd-editor-focus, #6e7a8c);\r\n outline-width: 2px;\r\n }\r\n body.mce-content-body div.mce-resizehandle,\r\n body.mce-content-body div.mce-resizehandle:hover {\r\n background-color: var(--rd-editor-selection, #e11d48);\r\n border-color: var(--rd-editor-focus, #6e7a8c);\r\n border-radius: 2px;\r\n }\r\n body.mce-content-body [data-mce-selected=\"inline-boundary\"],\r\n body.mce-content-body :is(td, th)[data-mce-selected]::after {\r\n background-color: var(--rd-editor-selection-bg, #fff1f2);\r\n border-color: var(--rd-editor-focus, #6e7a8c);\r\n }\r\n `,\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 registerToolbarIcons(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 // 只写入 iframe body 的展示变量;保留文档内容、光标、撤销栈及显式文字颜色。\r\n const syncContentTheme = () => {\r\n const theme = getComputedStyle(editorRoot.value ?? document.documentElement)\r\n body.style.setProperty('--rd-editor-bg', theme.getPropertyValue('--rd-color-bg').trim() || '#fff')\r\n body.style.setProperty('--rd-editor-text', theme.getPropertyValue('--rd-color-text').trim() || '#202631')\r\n body.style.setProperty('--rd-editor-link', theme.getPropertyValue('--rd-color-selection-hover').trim() || '#9f5e53')\r\n body.style.setProperty('--rd-editor-border', theme.getPropertyValue('--rd-color-border-input').trim() || '#d5dbe3')\r\n body.style.colorScheme = theme.colorScheme\r\n body.style.setProperty('--rd-editor-focus', theme.getPropertyValue('--rd-color-focus').trim() || '#6e7a8c')\r\n body.style.setProperty('--rd-editor-selection', theme.getPropertyValue('--rd-color-selection').trim() || '#e11d48')\r\n body.style.setProperty('--rd-editor-selection-bg', theme.getPropertyValue('--rd-color-selection-bg').trim() || '#fff1f2')\r\n }\r\n syncContentTheme()\r\n // 聚焦补同步局部宿主变量;全局明暗切换无需重新聚焦即可生效。\r\n editor.on('focus', syncContentTheme)\r\n if (typeof MutationObserver !== 'undefined') {\r\n themeObserver = new MutationObserver(syncContentTheme)\r\n themeObserver.observe(document.documentElement, { attributes: true, attributeFilter: ['class', 'style'] })\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 // 根主题监听与正文缓存监听一同释放,避免已卸载的编辑器继续响应主题变化。\r\n themeObserver?.disconnect()\r\n themeObserver = null\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 ref=\"editorRoot\" 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":"
|
|
1
|
+
{"version":3,"file":"RdRichEditor.vue.d.ts","sourceRoot":"","sources":["../../../../src/components/rdRichEditor/RdRichEditor.vue"],"names":[],"mappings":"AAwXA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAExD,OAA2B,EAAE,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAG9E,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;AA2QzB,iBAAS,QAAQ,YAMhB;AAuDD,KAAK,iBAAiB,GAAG,WAAW,GAAG;IACvC,UAAU,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AA6BF,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;AAyBpD,QAAA,MAAM,eAAe;;;;;;;;;;;;;;;cA/ZR,MAAM;gBAOJ,OAAO;6EAiapB,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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toolbarIcons.d.ts","sourceRoot":"","sources":["../../../../src/components/rdRichEditor/toolbarIcons.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAyBxD,gDAAgD;AAChD,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,aAAa,QAazD"}
|
package/dist/style.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
.tox-silver-sink{z-index:calc(var(--el-dialog-z-index,2000) + 10)!important}.tox-statusbar__branding{display:none}.rd-rich-editor-footer-bar{width:100%;color:var(--rd-color-text-secondary,#666);border:1px solid var(--rd-color-border,#ccc);background-color:var(--rd-color-fill-light,#f8f8f8);box-sizing:border-box;border-top:none;align-items:center;column-gap:10px;padding:6px 8px;line-height:24px;display:flex}.rd-rich-editor-footer-bar:empty{display:none}
|
|
1
|
+
.tox-silver-sink{z-index:calc(var(--el-dialog-z-index,2000) + 10)!important}.tox.tox-silver-sink{--rd-color-bg:var(--rd-color-bg-overlay,#fff)}.tox-statusbar__branding{display:none}.tox.tox-silver-sink .tox-dialog{border-color:var(--rd-color-border,#e3e6ec);border-radius:var(--rd-radius-xl,16px);background-color:var(--rd-color-bg,#fff);box-shadow:var(--rd-shadow-xl,0 20px 48px #0f172a1f);color:var(--rd-color-text,#242a33);font-family:var(--rd-font-family,sans-serif);overflow:hidden}.tox.tox-silver-sink .tox-dialog-wrap__backdrop{background-color:#0f172a47}.tox.tox-silver-sink .tox-dialog :is(.tox-dialog__header,.tox-dialog__footer){border-color:var(--rd-color-border-subtle,#eef0f3);background-color:var(--rd-color-bg,#fff);color:inherit;padding:20px 24px}.tox.tox-silver-sink .tox-dialog .tox-dialog__footer{padding-block:16px}.tox.tox-silver-sink .tox-dialog .tox-dialog__body-content{padding:20px 24px}.tox.tox-silver-sink .tox-dialog .tox-dialog__title{color:inherit;font-family:inherit;font-size:16px;font-weight:500}.tox.tox-silver-sink .tox-dialog :is(.tox-dialog__body,.tox-checkbox__label){color:var(--rd-color-text,#242a33);font-size:var(--rd-font-size-body,14px)}.tox.tox-silver-sink .tox-dialog .tox-label{color:var(--rd-color-text-secondary,#60718a);font-size:var(--rd-font-size-caption,12px);margin-bottom:6px;font-weight:500;line-height:1.4}.tox.tox-silver-sink .tox-dialog .tox-form__group{margin-bottom:0}.tox.tox-silver-sink .tox-dialog .tox-form>.tox-form__group:not(:last-child){margin-bottom:16px}.tox.tox-silver-sink .tox-dialog .tox-form__grid{row-gap:16px}.tox.tox-silver-sink .tox-dialog .tox-form__group--inline .tox-label{margin-bottom:0}.tox.tox-silver-sink .tox-dialog .tox-form__controls-h-stack{gap:8px}.tox.tox-silver-sink .tox-dialog .tox-form__controls-h-stack>.tox-form__group{flex-direction:column;display:flex}.tox.tox-silver-sink .tox-dialog .tox-form__controls-h-stack>:not(:first-child){margin-inline:0}.tox.tox-silver-sink .tox-dialog .tox-button{height:var(--rd-control-height,32px);border-radius:var(--rd-control-radius,8px);font-family:inherit;font-size:var(--rd-font-size-body,14px);padding:0 14px;font-weight:500}.tox.tox-silver-sink .tox-dialog .tox-button:not(.tox-button--secondary):not(.tox-button--naked){background-color:var(--rd-color-submit,#242a33);border-color:var(--rd-color-submit,#242a33)}.tox.tox-silver-sink .tox-dialog .tox-button:not(.tox-button--secondary):not(.tox-button--naked):is(:hover,:focus):not(:disabled){background-color:var(--rd-color-submit-hover,#353c47);border-color:var(--rd-color-submit-hover,#353c47)}.tox.tox-silver-sink .tox-dialog .tox-button--secondary{background-color:var(--rd-color-bg,#fff);border-color:var(--rd-color-border,#e3e6ec);color:var(--rd-color-text-secondary,#60718a)}.tox.tox-silver-sink .tox-dialog .tox-button--secondary:is(:hover,:focus):not(:disabled){background-color:var(--rd-color-bg-subtle,#eef0f3);border-color:var(--rd-color-border-strong,#cbd1d9);color:var(--rd-color-text,#242a33)}.tox.tox-silver-sink .tox-dialog .tox-button:disabled{opacity:.5}.tox.tox-silver-sink .tox-dialog .tox-button--naked{width:32px;color:var(--rd-color-text-secondary,#60718a);padding:0}.tox.tox-silver-sink .tox-dialog .tox-button--naked:is(:hover,:focus):not(:disabled){background-color:var(--rd-color-bg-subtle,#eef0f3);color:var(--rd-color-text,#242a33)}.tox.tox-silver-sink .tox-dialog__body:has(>.tox-dialog__body-nav){flex-direction:column}.tox.tox-silver-sink .tox-dialog .tox-dialog__body-nav{max-width:calc(100% - 48px);padding:var(--rd-tab-inset,4px);border-radius:var(--rd-radius-lg,12px);background-color:var(--rd-color-bg-muted,#f6f7f9);box-sizing:border-box;flex-direction:row;flex:none;align-self:flex-start;align-items:center;gap:2px;margin:0 24px;display:flex;overflow-x:auto}.tox.tox-silver-sink .tox-dialog .tox-dialog__body-nav [role=tab]{min-height:calc(var(--rd-tab-height,40px) - 2 * var(--rd-tab-inset,4px));border-radius:var(--rd-radius-base,8px);color:var(--rd-color-text-secondary,#60718a);font-size:var(--rd-font-size-body,14px);background-color:#0000;border:0;flex:none;justify-content:center;align-items:center;margin:0;padding:0 16px;font-weight:500;line-height:20px;display:inline-flex}.tox.tox-silver-sink .tox-dialog .tox-dialog__body-nav [role=tab]:hover{color:var(--rd-color-text,#242a33)}.tox.tox-silver-sink .tox-dialog .tox-dialog__body-nav [role=tab][aria-selected=true]{background-color:var(--rd-color-bg,#fff);box-shadow:var(--rd-shadow-xs,0 1px 2px #0f172a0d);color:var(--rd-color-text,#242a33)}.tox.tox-silver-sink .tox-dialog .tox-dialog__body-nav [role=tab]:focus-visible{outline:2px solid var(--rd-color-focus,#6e7a8c);outline-offset:-2px}.tox.tox-silver-sink .tox-dialog .tox-dropzone{border:1px dashed var(--rd-color-border-strong,#cbd1d9);border-radius:var(--rd-radius-lg,12px);background-color:var(--rd-color-bg-muted,#f6f7f9);gap:16px;min-height:180px;padding:24px}.tox.tox-silver-sink .tox-dialog .tox-dropzone p{color:var(--rd-color-text-secondary,#60718a);font-size:var(--rd-font-size-body,14px);margin:0}.tox.tox-silver-sink .tox-dialog .tox-dropzone:focus-within{border-color:var(--rd-color-focus,#6e7a8c);outline-color:var(--rd-color-focus,#6e7a8c)}:where(html.dark) .tox.tox-silver-sink .tox-dialog .tox-dropzone{border-color:var(--rd-color-border-input,#4b4e5a)}.tox.tox-silver-sink :is(.tox-textfield,.tox-textarea,.tox-listbox--select,.tox-selectfield select){min-height:var(--rd-control-height,32px);border-color:var(--rd-color-border,#e3e6ec);border-radius:var(--rd-control-radius,8px);background-color:var(--rd-color-bg,#fff);color:var(--rd-color-text,#242a33);padding:5px 10px;font-size:14px;line-height:20px}.tox.tox-silver-sink :is(.tox-textfield,.tox-textarea,.tox-listbox--select,.tox-selectfield select):focus{border-color:var(--rd-color-focus,#6e7a8c)}.tox.tox-silver-sink :is(.tox-textfield,.tox-textarea,.tox-listbox--select,.tox-selectfield select):disabled{background-color:var(--rd-color-bg-disabled,#f6f7f9);color:var(--rd-color-text-disabled,#9ca3af)}.tox.tox-silver-sink .tox-checkbox__icons .tox-checkbox-icon__checked svg,.tox.tox-silver-sink .tox-checkbox__icons .tox-checkbox-icon__indeterminate svg{fill:var(--rd-color-selection,#e11d48)}.tox.tox-silver-sink input.tox-checkbox__input:focus+.tox-checkbox__icons{border-radius:var(--rd-radius-md,8px);box-shadow:inset 0 0 0 1px var(--rd-color-focus,#6e7a8c)}.rd-rich-editor .tox-tinymce{border-color:var(--rd-color-border-input,#e3e6ec);border-radius:var(--rd-control-radius,8px)}.rd-rich-editor .tox.tox-tinymce .tox-editor-header{border-bottom:1px solid var(--rd-color-border-input,#e3e6ec);background-color:var(--rd-color-bg,#fff)}.rd-rich-editor .tox.tox-tinymce :is(.tox-edit-area,.tox-edit-area__iframe){background-color:var(--rd-color-bg,#fff)}.rd-rich-editor .tox.tox-tinymce :is(.tox-menubar,.tox-toolbar,.tox-toolbar__primary,.tox-toolbar__overflow){background:0 0;border:0;padding:2px 6px}:is(.rd-rich-editor .tox.tox-tinymce,.tox.tox-silver-sink) :is(.tox-toolbar,.tox-toolbar__primary,.tox-toolbar__overflow) .tox-toolbar__group{border:0}:is(.rd-rich-editor .tox.tox-tinymce,.tox.tox-silver-sink) :is(.tox-toolbar,.tox-toolbar__primary,.tox-toolbar__overflow) .tox-toolbar__group:not(:last-child):after{content:"";background-color:var(--rd-color-border,#e3e6ec);pointer-events:none;flex:0 0 1px;height:16px;margin-inline-start:6px}.rd-rich-editor .tox.tox-tinymce .tox-tbtn--bespoke{min-width:72px;max-width:160px;padding-inline:8px}.rd-rich-editor .tox.tox-tinymce .tox-tbtn--bespoke .tox-tbtn__select-label{flex:1;width:auto;min-width:0}.rd-rich-editor .tox.tox-tinymce .tox-statusbar{border-top:0 solid var(--rd-color-border-input,#e3e6ec);background-color:var(--rd-color-bg,#fff);color:var(--rd-color-text-secondary,#60718a);padding:4px 12px}.rd-rich-editor .tox.tox-tinymce .tox-statusbar :is(.tox-statusbar__wordcount,.tox-statusbar__path-item){color:inherit}.rd-rich-editor .tox.tox-tinymce .tox-statusbar__resize-handle svg{fill:currentColor}:is(.rd-rich-editor .tox.tox-tinymce,.tox.tox-silver-sink) :is(.tox-tbtn,.tox-mbtn,.tox-split-button){border-radius:var(--rd-radius-md,8px);color:var(--rd-color-text-secondary,#60718a)}:is(.rd-rich-editor .tox.tox-tinymce,.tox.tox-silver-sink) :is(.tox-tbtn,.tox-mbtn):is(:hover,:focus):not(:is(:disabled,.tox-tbtn--disabled,.tox-tbtn--enabled,.tox-mbtn--active)){background-color:var(--rd-color-bg-subtle,#eef0f3);color:var(--rd-color-text,#242a33)}:is(.rd-rich-editor .tox.tox-tinymce,.tox.tox-silver-sink) :is(.tox-tbtn--enabled,.tox-mbtn--active):not(:disabled){background-color:var(--rd-color-selection-bg,#fff1f2);color:var(--rd-color-selection,#e11d48)}:is(.rd-rich-editor .tox.tox-tinymce,.tox.tox-silver-sink) .tox-mbtn--active .tox-mbtn__select-label{color:var(--rd-color-text,#242a33)}:is(.rd-rich-editor .tox.tox-tinymce,.tox.tox-silver-sink) .tox-split-button:hover{box-shadow:0 0 0 1px var(--rd-color-border,#e3e6ec) inset}:is(.rd-rich-editor .tox.tox-tinymce,.tox.tox-silver-sink) .tox-split-button>.tox-tbtn{border-radius:0}:is(.rd-rich-editor .tox.tox-tinymce,.tox.tox-silver-sink) :is(.tox-tbtn--disabled,.tox-tbtn:disabled,.tox-mbtn:disabled){color:var(--rd-color-text-disabled,#9ca3af);background-color:#0000}:is(.rd-rich-editor .tox.tox-tinymce,.tox.tox-silver-sink) .tox-tbtn svg:not(.rd-editor-icon){fill:currentColor}.tox svg.rd-editor-icon{color:inherit}.tox .tox-tbtn--disabled svg.rd-editor-icon,.tox .tox-tbtn:disabled svg.rd-editor-icon{opacity:.4}.tox.tox-silver-sink .tox-menu{border-color:var(--rd-color-border,#e3e6ec);border-radius:var(--rd-radius-lg,12px);background-color:var(--rd-color-bg,#fff);box-shadow:var(--rd-shadow-md,0 4px 12px #0f172a14);font-family:var(--rd-font-family,sans-serif);overscroll-behavior:contain;padding:6px;overflow-y:auto;max-height:min(480px,60vh)!important}.tox.tox-silver-sink .tox-menu.tox-collection:is(.tox-collection--list,.tox-collection--toolbar,.tox-collection--grid){padding:6px}.tox.tox-silver-sink .tox-collection .tox-collection__group{border-color:var(--rd-color-border-subtle,#eef0f3)}.tox.tox-silver-sink .tox-collection .tox-collection__item{border-radius:var(--rd-radius-md,8px);box-sizing:border-box;min-height:36px;color:var(--rd-color-text,#242a33);padding:6px 8px}.tox.tox-silver-sink .tox-collection .tox-collection__item-label{font-size:13px;line-height:24px}.tox.tox-silver-sink .tox-collection .tox-collection__item--active:not(.tox-collection__item--state-disabled){background-color:var(--rd-color-bg-subtle,#eef0f3)}.tox.tox-silver-sink .tox-collection .tox-collection__item--enabled:not(.tox-collection__item--state-disabled){background-color:var(--rd-color-selection-bg,#fff1f2);color:var(--rd-color-text,#242a33)}.tox.tox-silver-sink .tox-collection .tox-collection__item--state-disabled{color:var(--rd-color-text-disabled,#9ca3af);background-color:#0000}.tox.tox-silver-sink .tox-collection .tox-collection__item-icon,.tox.tox-silver-sink .tox-collection .tox-collection__item-checkmark{flex:0 0 20px;width:20px;height:20px}.tox.tox-silver-sink .tox-collection .tox-collection__item-icon>svg,.tox.tox-silver-sink .tox-collection .tox-collection__item-checkmark>svg{flex-shrink:0;width:24px;height:24px;transform:scale(.75)}.tox.tox-silver-sink .tox-collection--toolbar-lg .tox-collection__item-icon>svg{width:48px;height:48px;transform:scale(.5)}.tox.tox-silver-sink .tox-collection .tox-collection__item-caret{width:12px;min-height:20px;color:inherit;flex:0 0 12px;justify-content:center}.tox.tox-silver-sink .tox-collection .tox-collection__item-caret>svg{fill:currentColor;flex-shrink:0;width:24px;height:24px;transform:scale(.5)}.tox.tox-silver-sink .tox-collection .tox-collection__group-heading{background-color:var(--rd-color-bg,#fff);color:var(--rd-color-text-secondary,#60718a);font-size:12px}.tox.tox-silver-sink .tox-collection .tox-collection__item-accessory{color:var(--rd-color-text-secondary,#60718a);font-size:12px}.tox.tox-silver-sink .tox-dialog .tox-collection--grid .tox-collection__item-icon{font-size:20px}.tox.tox-silver-sink .tox-dialog .tox-well{border-color:var(--rd-color-border,#e3e6ec);border-radius:var(--rd-control-radius,8px)}.tox.tox-silver-sink .tox-menu .tox-insert-table-picker>div{border-color:var(--rd-color-border,#e3e6ec)}.tox.tox-silver-sink .tox-menu .tox-insert-table-picker .tox-insert-table-picker__selected{border-color:var(--rd-color-selection-border,#fecdd3);background-color:var(--rd-color-selection-bg,#fff1f2)}.tox.tox-silver-sink .tox-swatch{border-radius:var(--rd-radius-xs,3px);width:24px;height:24px;margin:2px}.tox.tox-silver-sink .tox-swatch:is(:hover,:focus){box-shadow:0 0 0 2px var(--rd-color-focus,#6e7a8c) inset;transform:none}.tox.tox-silver-sink .tox-swatches__picker-btn{border-radius:var(--rd-radius-md,8px);width:28px;height:28px;color:var(--rd-color-text-secondary,#60718a)}.tox.tox-silver-sink .tox-swatches__picker-btn:hover{background-color:var(--rd-color-bg-subtle,#eef0f3)}.tox.tox-silver-sink .tox-color-input span{border-color:var(--rd-color-border,#e3e6ec);border-radius:var(--rd-radius-md,8px)}.tox.tox-silver-sink .tox-color-input span:is(:hover,:focus):not([aria-disabled=true]){border-color:var(--rd-color-focus,#6e7a8c)}.tox.tox-silver-sink :is(.tox-pop__dialog,.tox-collection--horizontal,.tox-toolbar__overflow){border-color:var(--rd-color-border,#e3e6ec);border-radius:var(--rd-radius-lg,12px);background-color:var(--rd-color-bg,#fff);box-shadow:var(--rd-shadow-md,0 4px 12px #0f172a14)}.tox.tox-silver-sink .tox-pop__dialog .tox-toolbar{background-color:var(--rd-color-bg,#fff)}:is(.rd-rich-editor .tox.tox-tinymce,.tox.tox-silver-sink) :is(.tox-tbtn__icon-wrap,.tox-dialog .tox-icon,.tox-swatches__picker-btn,.tox-checkbox__icons) svg:not(.rd-editor-icon){flex-shrink:0;width:24px;height:24px;transform:scale(.75)}.tox.tox-silver-sink .tox-tooltip__body{border-radius:var(--rd-radius-md,8px);background-color:var(--rd-color-text,#242a33);box-shadow:var(--rd-shadow-sm,0 1px 3px #0f172a14);color:var(--rd-color-text-inverse,#fff);font-family:var(--rd-font-family,sans-serif);font-size:12px}.tox.tox-silver-sink .tox-tooltip--down .tox-tooltip__arrow{border-top-color:var(--rd-color-text,#242a33)}.tox.tox-silver-sink .tox-tooltip--up .tox-tooltip__arrow{border-bottom-color:var(--rd-color-text,#242a33)}.tox.tox-silver-sink .tox-tooltip--left .tox-tooltip__arrow{border-right-color:var(--rd-color-text,#242a33)}.tox.tox-silver-sink .tox-tooltip--right .tox-tooltip__arrow{border-left-color:var(--rd-color-text,#242a33)}.tox.tox-silver-sink .tox-notification{border-radius:var(--rd-radius-lg,12px);box-shadow:var(--rd-shadow-sm,0 1px 3px #0f172a14)}.tox.tox-silver-sink .tox-notification :is(p,a,svg){color:inherit;fill:currentColor;font-size:13px}.tox.tox-silver-sink .tox-notification--error{background-color:var(--rd-color-danger-bg,#fff0ee);border-color:var(--rd-color-danger,#c0302a);color:var(--rd-color-danger,#c0302a)}.tox.tox-silver-sink .tox-notification--success{background-color:var(--rd-color-success-bg,#f0faf5);border-color:var(--rd-color-success,#0a8055);color:var(--rd-color-success,#0a8055)}.tox.tox-silver-sink :is(.tox-notification--warning,.tox-notification--warn){background-color:var(--rd-color-warning-bg,#fdf6e6);border-color:var(--rd-color-warning,#9a6b00);color:var(--rd-color-warning,#9a6b00)}.tox.tox-silver-sink .tox-notification--info{background-color:var(--rd-color-info-bg,#eef4ff);border-color:var(--rd-color-info,#1e5cb8);color:var(--rd-color-info,#1e5cb8)}.tox.tox-silver-sink .tox-notification__dismiss{border-radius:var(--rd-radius-md,8px);color:inherit}@media (width<=600px){.tox.tox-silver-sink .tox-dialog :is(.tox-dialog__header,.tox-dialog__body-content,.tox-dialog__footer){padding-inline:20px}.tox.tox-silver-sink .tox-dialog .tox-dialog__body-nav{max-width:calc(100% - 40px);margin-inline:20px}.tox.tox-silver-sink .tox-color-picker-container{grid-template-columns:minmax(0,1fr) 20px;gap:12px;height:auto;display:grid}.tox.tox-silver-sink .tox-color-picker-container .tox-sv-palette,.tox.tox-silver-sink .tox-color-picker-container .tox-sv-palette-spectrum{width:100%;min-width:0;height:200px;margin:0}.tox.tox-silver-sink .tox-color-picker-container .tox-hue-slider{height:200px;margin:0}.tox.tox-silver-sink .tox-color-picker-container .tox-rgb-form{grid-column:1/-1;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;display:grid}.tox.tox-silver-sink .tox-rgb-form>div{min-width:0;margin:0}.tox.tox-silver-sink .tox-rgb-form input{width:100%;min-width:0}.tox.tox-silver-sink .tox-rgb-form .tox-rgba-preview{border-color:var(--rd-color-border,#e3e6ec);border-radius:var(--rd-radius-md,8px);grid-column:1/-1;min-height:32px}}@media (pointer:coarse){.tox.tox-silver-sink .tox-dialog .tox-dialog__body-nav [role=tab]{min-height:44px}.tox.tox-silver-sink .tox-collection .tox-collection__item{min-width:44px;min-height:44px}.tox.tox-silver-sink :is(.tox-textfield,.tox-textarea,.tox-listbox--select,.tox-selectfield select){font-size:16px}.tox.tox-silver-sink .tox-swatch{width:40px;height:40px}.tox.tox-silver-sink .tox-swatches__picker-btn{width:44px;height:44px}}.rd-rich-editor:has(>.rd-rich-editor-footer-bar:not(:empty)) .tox-tinymce{border-bottom-right-radius:0;border-bottom-left-radius:0}.rd-rich-editor-footer-bar{width:100%;color:var(--rd-color-text-secondary,#666);border:1px solid var(--rd-color-border,#e3e6ec);border-radius:0 0 var(--rd-control-radius,8px) var(--rd-control-radius,8px);background-color:var(--rd-color-bg,#fff);box-sizing:border-box;border-top:none;align-items:center;column-gap:10px;padding:6px 8px;line-height:24px;display:flex}.rd-rich-editor-footer-bar:empty{display:none}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rd-element/editor",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "RD Element rich text editor (TinyMCE 5 wrapper) — RdRichEditor",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
"component-library"
|
|
45
45
|
],
|
|
46
46
|
"peerDependencies": {
|
|
47
|
+
"@lucide/vue": "^1.0.0",
|
|
47
48
|
"vue": "^3.5.0",
|
|
48
49
|
"element-plus": "^2.8.0"
|
|
49
50
|
},
|
|
@@ -53,6 +54,7 @@
|
|
|
53
54
|
}
|
|
54
55
|
},
|
|
55
56
|
"devDependencies": {
|
|
57
|
+
"@lucide/vue": "^1.17.0",
|
|
56
58
|
"@types/node": "^22.13.10",
|
|
57
59
|
"@vitejs/plugin-vue": "^6.0.8",
|
|
58
60
|
"@vue/test-utils": "^2.4.6",
|