@rd-element/editor 0.1.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/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +223 -0
- package/dist/index.js.map +1 -0
- package/dist/resolver.cjs +2 -0
- package/dist/resolver.cjs.map +1 -0
- package/dist/resolver.d.ts +2 -0
- package/dist/resolver.js +16 -0
- package/dist/resolver.js.map +1 -0
- package/dist/src/components/rdRichEditor/RdRichEditor.vue.d.ts +66 -0
- package/dist/src/components/rdRichEditor/RdRichEditor.vue.d.ts.map +1 -0
- package/dist/src/components/rdRichEditor/dialogZIndexBridge.d.ts +6 -0
- package/dist/src/components/rdRichEditor/dialogZIndexBridge.d.ts.map +1 -0
- package/dist/src/components/rdRichEditor/index.d.ts +3 -0
- package/dist/src/components/rdRichEditor/index.d.ts.map +1 -0
- package/dist/src/components/rdRichEditor/useQuickTextPhrase.d.ts +12 -0
- package/dist/src/components/rdRichEditor/useQuickTextPhrase.d.ts.map +1 -0
- package/dist/src/index.d.ts +11 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/resolver.d.ts +4 -0
- package/dist/src/resolver.d.ts.map +1 -0
- package/dist/src/utils/cleanObject.d.ts +5 -0
- package/dist/src/utils/cleanObject.d.ts.map +1 -0
- package/dist/src/utils/resolverTypes.d.ts +9 -0
- package/dist/src/utils/resolverTypes.d.ts.map +1 -0
- package/dist/style.css +1 -0
- package/package.json +68 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:`Module`}});require('./style.css');const e=require("./resolver.cjs");let t=require("vue"),n=require("element-plus");function r(e){return e?Object.fromEntries(Object.entries(e).filter(([,e])=>e!==``&&e!=null&&!(Array.isArray(e)&&e.length===0)&&!(typeof e==`object`&&e&&Object.keys(e).length===0))):{}}function i(e,t){let n=0,r=[];function i(e,t){return e.filter(e=>e.input_code.includes(t)||e.name.includes(t)).map(e=>({type:`cardmenuitem`,value:e,label:e.name,items:[{type:`cardcontainer`,direction:`vertical`,items:[{type:`cardtext`,text:e.name,name:e.input_code}]}]}))}e.ui.registry.addAutocompleter(`quick_text_phrase`,{ch:`#`,minChars:0,columns:1,matches:(e,t,n)=>t&&t.endsWith(`#`)||!!n&&n.length<6,fetch:async e=>{if(n===1)return[];if(!n){n=1;try{r=await t()||[],n=2}catch(e){throw n=0,e}}return i(r,e)},onAction:(t,n,r)=>{n.deleteContents(),e.insertContent(r.content),t.hide()}})}function a(){if(!(0,t.inject)(n.dialogInjectionKey,null))return;let e=(0,n.useZIndex)().currentZIndex;(0,t.watchEffect)(()=>{e.value>1300&&document.documentElement.style.setProperty(`--el-dialog-z-index`,e.value.toString())}),(0,t.onBeforeUnmount)(()=>{document.documentElement.style.removeProperty(`--el-dialog-z-index`)})}var o={class:`rd-rich-editor`},s={key:0,class:`rd-rich-editor-footer-bar`},c=(0,t.defineComponent)({name:`RdRichEditor`,__name:`RdRichEditor`,props:(0,t.mergeModels)({plugins:{},language:{default:`zh_CN`},menubar:{},toolbar:{},toolbar_mode:{},min_height:{},height:{},textPhrase:{type:Boolean,default:!1},includes:{},excludes:{},imagesUploadHandler:{},fetchTextPhrases:{},tinymceBaseUrl:{},languageUrl:{}},{modelValue:{default:``},modelModifiers:{}}),emits:(0,t.mergeModels)([`input`,`change`],[`update:modelValue`]),setup(e,{expose:n,emit:c}){let l=e,u=c,d=(0,t.useModel)(e,`modelValue`),f=`rd-editor_${Date.now()}_${Math.random().toString(36).slice(2,8)}`,p=(0,t.ref)(!1),m=null,h=[];a();function g(){return m?.getContent()??``}function _(e){let t=m?.setContent(e??``);return d.value=g(),t}function v(e){m?.insertContent(e),d.value=g()}function y(e,t){let n=Array.isArray(t)?t:[t],r=e;return n.forEach(e=>{let t=e.trim();t&&(RegExp(`(?:^|\\s)${x(t)}(?:\\s|$)`).test(r)||(r=`${r} ${t}`.trim()))}),r}function b(e,t){let n=Array.isArray(t)?t:[t],r=e;return n.forEach(e=>{let t=e.trim();if(!t)return;let n=RegExp(`(?:^|\\s)${x(t)}(?=\\s|$)`,`g`);r=r.replace(n,` `).replace(/\s+/g,` `).trim()}),r}function x(e){return e.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`)}function S(){let{includes:e,excludes:t,textPhrase:n,imagesUploadHandler:a,fetchTextPhrases:o,tinymceBaseUrl:s,languageUrl:c,...v}=l,x=l.language===`zh-cn`?`zh_CN`:l.language,S={selector:`textarea#${f}`,language:x,plugins:`advlist autolink link image lists charmap emoticons preview hr anchor pagebreak nonbreaking code table paste insertdatetime wordcount`,menubar:`file edit view insert format tools`,menu:{insert:{title:`Insert`,items:`image link inserttable | charmap emoticons hr | pagebreak nonbreaking anchor | insertdatetime`}},toolbar:`undo redo | styleselect | fontsizeselect fontselect | bold italic forecolor backcolor | alignleft aligncenter alignright alignjustify | outdent indent | bullist numlist table | link image | code`,toolbar_mode:`wrap`,fontsize_formats:`5px 6px 7px 8px 9px 10px 12px 14px 16px 18px 20px 22px 24px 26px 28px 30px 32px 34px 36px 48px 72px`,font_formats:`Andale Mono=andale mono,times;Arial=arial,helvetica,sans-serif;Arial Black=arial black,avant garde;Book Antiqua=book antiqua,palatino;Calibri;Calibri Light;Comic Sans MS=comic sans ms,sans-serif;Courier New=courier new,courier;Georgia=georgia,palatino;Helvetica=helvetica;Impact=impact,chicago;Symbol=symbol;Tahoma=tahoma,arial,;Terminal=terminal,monaco;Times New Roman=times new roman,times;Trebuchet MS=trebuchet ms,geneva;Verdana=verdana,geneva;Webdings=webdings;Wingdings=wingdings,;`,min_height:480,table_default_styles:{width:`100%`,borderCollapse:`collapse`},image_advtab:!0,image_title:!1,paste_data_images:!0,images_upload_handler:(e,t,n)=>{if(!a){n(`imagesUploadHandler is not provided`);return}a(e.blob()).then(e=>{e?t(e):n(`Upload failed`)}).catch(()=>n(`Upload failed`))},setup(e){m=e,e.on(`init`,()=>{p.value=!0;let e=m?.getBody();for(e&&(e.style.fontSize=`16px`,e.style.fontFamily=`Calibri`),_(d.value);h.length;)h.shift()?.()}),e.on(`change`,e=>{d.value=g(),u(`change`,d.value,e)}),e.on(`input`,e=>{u(`input`,e)}),n&&(o?i(e,o):console.warn(`[RdRichEditor] textPhrase is true but fetchTextPhrases is not provided`))}};s&&(S.base_url=s),c&&(S.language_url=c);let C={...S,...r(v)};return e&&Object.keys(e).forEach(t=>{let n=C[t],r=e[t];typeof n==`string`&&r!=null&&(C[t]=y(n,r))}),t&&Object.keys(t).forEach(e=>{let n=C[e],r=t[e];typeof n==`string`&&r!=null&&(C[e]=b(n,r))}),C}function C(e){return function(...t){return p.value?e(...t):new Promise(n=>{h.push(()=>n(e(...t)))})}}function w(){let e=document.createElement(`div`);return e.innerHTML=d.value,!!(e.textContent?.trim()||e.querySelector(`a.mce-item-anchor, img, table`))}return(0,t.watch)(()=>d.value,e=>{!p.value||e===g()||_(e)}),(0,t.onMounted)(()=>{let e=window.tinymce;if(!e){console.error(`[RdRichEditor] window.tinymce is missing. Load TinyMCE 5.10.x via <script> before using RdRichEditor.`);return}l.tinymceBaseUrl&&(e.baseURL=l.tinymceBaseUrl),e.init(S())}),(0,t.onBeforeUnmount)(()=>{m?.remove(),m=null}),n({getEditor:C(()=>m),getContent:C(g),setContent:C(_),insertContent:C(v),validate:w}),(e,n)=>((0,t.openBlock)(),(0,t.createElementBlock)(`div`,o,[(0,t.createElementVNode)(`textarea`,{id:f,style:{visibility:`hidden`}}),p.value?((0,t.openBlock)(),(0,t.createElementBlock)(`div`,s,[(0,t.renderSlot)(e.$slots,`footer-bar`)])):(0,t.createCommentVNode)(``,!0)]))}}),l=[c];function u(e){l.forEach(t=>{let n=t.name||t.__name;n&&e.component(n,t)})}exports.RdRichEditor=c,exports.RodelEditorResolver=e.RodelEditorResolver,exports.default=u,exports.install=u;
|
|
2
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","names":[],"sources":["../src/utils/cleanObject.ts","../src/components/rdRichEditor/useQuickTextPhrase.ts","../src/components/rdRichEditor/dialogZIndexBridge.ts","../src/components/rdRichEditor/RdRichEditor.vue","../src/components/rdRichEditor/RdRichEditor.vue","../src/index.ts"],"sourcesContent":["/**\r\n * Remove empty values from a shallow object (null / undefined / '' / [] / {}).\r\n */\r\nexport function cleanObject<T>(obj: T | null | undefined): Partial<T> {\r\n if (!obj) return {}\r\n\r\n return Object.fromEntries(\r\n Object.entries(obj as Record<string, unknown>).filter(\r\n ([, v]) =>\r\n v !== '' &&\r\n v !== null &&\r\n v !== undefined &&\r\n !(Array.isArray(v) && v.length === 0) &&\r\n !(typeof v === 'object' && v && Object.keys(v as object).length === 0),\r\n ),\r\n ) as Partial<T>\r\n}\r\n","import type { TinyMCEEditor } from '../../types/tinymce'\r\n\r\nexport interface TextPhraseItem {\r\n name: string\r\n input_code: string\r\n content: string\r\n}\r\n\r\n/**\r\n * Register TinyMCE autocompleter for `#` quick text phrases.\r\n * Data is supplied by the host via `fetchTextPhrases`.\r\n */\r\nexport default function useQuickTextPhrase(\r\n editor: TinyMCEEditor,\r\n fetchTextPhrases: () => Promise<TextPhraseItem[]>,\r\n) {\r\n let status = 0 // 0 idle | 1 loading | 2 ready\r\n let quickTextPhrase: TextPhraseItem[] = []\r\n\r\n function filterListByPattern(list: TextPhraseItem[], pattern: string) {\r\n return list\r\n .filter((v) => v.input_code.includes(pattern) || v.name.includes(pattern))\r\n .map((v) => ({\r\n type: 'cardmenuitem',\r\n value: v,\r\n label: v.name,\r\n items: [\r\n {\r\n type: 'cardcontainer',\r\n direction: 'vertical',\r\n items: [\r\n {\r\n type: 'cardtext',\r\n text: v.name,\r\n name: v.input_code,\r\n },\r\n ],\r\n },\r\n ],\r\n }))\r\n }\r\n\r\n editor.ui.registry.addAutocompleter('quick_text_phrase', {\r\n ch: '#',\r\n minChars: 0,\r\n columns: 1,\r\n matches: (_rng: unknown, text: string, pattern: string) =>\r\n (text && text.endsWith('#')) || (!!pattern && pattern.length < 6),\r\n fetch: async (pattern: string) => {\r\n if (status === 1) return []\r\n if (!status) {\r\n status = 1\r\n try {\r\n quickTextPhrase = (await fetchTextPhrases()) || []\r\n status = 2\r\n } catch (err) {\r\n status = 0\r\n throw err\r\n }\r\n }\r\n return filterListByPattern(quickTextPhrase, pattern)\r\n },\r\n onAction: (\r\n autocompleterApi: { hide: () => void },\r\n rng: { deleteContents: () => void },\r\n value: TextPhraseItem,\r\n ) => {\r\n rng.deleteContents()\r\n editor.insertContent(value.content)\r\n autocompleterApi.hide()\r\n },\r\n })\r\n}\r\n","import { inject, onBeforeUnmount, watchEffect } from 'vue'\r\nimport { useZIndex, dialogInjectionKey } from 'element-plus'\r\n\r\n/**\r\n * When RdRichEditor is used inside ElDialog / RdDialog, raise TinyMCE\r\n * popover z-index above the dialog via --el-dialog-z-index.\r\n */\r\nexport function setupDialogZIndexBridge(): void {\r\n const dialogProvide = inject(dialogInjectionKey, null)\r\n if (!dialogProvide) return\r\n\r\n const dialogZIndex = useZIndex().currentZIndex\r\n watchEffect(() => {\r\n if (dialogZIndex.value > 1300) {\r\n document.documentElement.style.setProperty(\r\n '--el-dialog-z-index',\r\n dialogZIndex.value.toString(),\r\n )\r\n }\r\n })\r\n onBeforeUnmount(() => {\r\n document.documentElement.style.removeProperty('--el-dialog-z-index')\r\n })\r\n}\r\n","<script setup lang=\"ts\">\r\ndefineOptions({\r\n name: 'RdRichEditor',\r\n})\r\n\r\nimport { onBeforeUnmount, onMounted, ref, watch } from 'vue'\r\nimport type { TinyMCEEditor } from '../../types/tinymce'\r\nimport { cleanObject } from '../../utils/cleanObject'\r\nimport useQuickTextPhrase, { type TextPhraseItem } from './useQuickTextPhrase'\r\nimport { setupDialogZIndexBridge } from './dialogZIndexBridge'\r\nimport '../../styles/rd-rich-editor.css'\r\n\r\ninterface EditorConfigProps {\r\n plugins?: string | string[]\r\n language?: string\r\n menubar?: string | string[]\r\n toolbar?: string | string[]\r\n toolbar_mode?: string\r\n min_height?: number\r\n height?: number\r\n /** Enable `#` quick text phrase autocompleter (requires fetchTextPhrases) */\r\n textPhrase?: boolean\r\n}\r\n\r\ntype Props = EditorConfigProps & {\r\n includes?: Pick<EditorConfigProps, 'plugins' | 'menubar' | 'toolbar'>\r\n excludes?: Pick<EditorConfigProps, 'plugins' | 'menubar' | 'toolbar'>\r\n /**\r\n * Image upload hook. Return a publicly accessible URL.\r\n * When omitted, paste/insert image upload fails with a clear message.\r\n */\r\n imagesUploadHandler?: (blob: Blob) => Promise<string>\r\n /** Fetch quick text phrases when textPhrase is true */\r\n fetchTextPhrases?: () => Promise<TextPhraseItem[]>\r\n /** Override TinyMCE baseURL when static assets are not at the default path */\r\n tinymceBaseUrl?: string\r\n /**\r\n * TinyMCE language pack URL (language_url).\r\n * Needed when langs/*.js are not under tinymceBaseUrl (e.g. CDN demos).\r\n */\r\n languageUrl?: string\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n language: 'zh_CN',\r\n textPhrase: false,\r\n})\r\n\r\nconst emits = defineEmits<{\r\n input: [e: Event]\r\n change: [value: string, e: unknown]\r\n}>()\r\n\r\nconst inputValue = defineModel<string>({ default: '' })\r\nconst editorId = `rd-editor_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`\r\nconst isInit = ref(false)\r\nlet editorCtx: TinyMCEEditor | null = null\r\n\r\nconst task: Array<() => void> = []\r\n\r\nsetupDialogZIndexBridge()\r\n\r\nfunction getContentSync() {\r\n return editorCtx?.getContent() ?? ''\r\n}\r\n\r\nfunction setContentSync(value: string) {\r\n const result = editorCtx?.setContent(value ?? '')\r\n inputValue.value = getContentSync()\r\n return result\r\n}\r\n\r\nfunction insertContentSync(value: string) {\r\n editorCtx?.insertContent(value)\r\n inputValue.value = getContentSync()\r\n}\r\n\r\n/** Append tokens to a space-separated TinyMCE config string (word match). */\r\nfunction addStr(source: string, target: string | string[]) {\r\n const list = Array.isArray(target) ? target : [target]\r\n let result = source\r\n list.forEach((token) => {\r\n const trimmed = token.trim()\r\n if (!trimmed) return\r\n const re = new RegExp(`(?:^|\\\\s)${escapeRegExp(trimmed)}(?:\\\\s|$)`)\r\n if (!re.test(result)) {\r\n result = `${result} ${trimmed}`.trim()\r\n }\r\n })\r\n return result\r\n}\r\n\r\n/** Remove tokens from a space-separated TinyMCE config string. */\r\nfunction removeStr(source: string, target: string | string[]) {\r\n const list = Array.isArray(target) ? target : [target]\r\n let result = source\r\n list.forEach((token) => {\r\n const trimmed = token.trim()\r\n if (!trimmed) return\r\n const re = new RegExp(`(?:^|\\\\s)${escapeRegExp(trimmed)}(?=\\\\s|$)`, 'g')\r\n result = result.replace(re, ' ').replace(/\\s+/g, ' ').trim()\r\n })\r\n return result\r\n}\r\n\r\nfunction escapeRegExp(s: string) {\r\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\r\n}\r\n\r\nfunction initOpts(): Record<string, unknown> {\r\n const {\r\n includes,\r\n excludes,\r\n textPhrase,\r\n imagesUploadHandler,\r\n fetchTextPhrases,\r\n tinymceBaseUrl,\r\n languageUrl,\r\n ...restProps\r\n } = props\r\n\r\n const language = props.language === 'zh-cn' ? 'zh_CN' : props.language\r\n\r\n const baseOpts: Record<string, unknown> = {\r\n selector: `textarea#${editorId}`,\r\n language,\r\n plugins:\r\n 'advlist autolink link image lists charmap emoticons preview hr anchor pagebreak nonbreaking code table paste insertdatetime wordcount',\r\n menubar: 'file edit view insert format tools',\r\n menu: {\r\n insert: {\r\n title: 'Insert',\r\n items:\r\n 'image link inserttable | charmap emoticons hr | pagebreak nonbreaking anchor | insertdatetime',\r\n },\r\n },\r\n toolbar:\r\n 'undo redo | styleselect | fontsizeselect fontselect | bold italic forecolor backcolor | alignleft aligncenter alignright alignjustify | outdent indent | bullist numlist table | link image | code',\r\n toolbar_mode: 'wrap',\r\n fontsize_formats:\r\n '5px 6px 7px 8px 9px 10px 12px 14px 16px 18px 20px 22px 24px 26px 28px 30px 32px 34px 36px 48px 72px',\r\n font_formats:\r\n 'Andale Mono=andale mono,times;Arial=arial,helvetica,sans-serif;Arial Black=arial black,avant garde;Book Antiqua=book antiqua,palatino;Calibri;Calibri Light;Comic Sans MS=comic sans ms,sans-serif;Courier New=courier new,courier;Georgia=georgia,palatino;Helvetica=helvetica;Impact=impact,chicago;Symbol=symbol;Tahoma=tahoma,arial,;Terminal=terminal,monaco;Times New Roman=times new roman,times;Trebuchet MS=trebuchet ms,geneva;Verdana=verdana,geneva;Webdings=webdings;Wingdings=wingdings,;',\r\n min_height: 480,\r\n table_default_styles: { width: '100%', borderCollapse: 'collapse' },\r\n image_advtab: true,\r\n image_title: false,\r\n paste_data_images: true,\r\n images_upload_handler: (\r\n blobInfo: { blob: () => Blob },\r\n success: (url: string) => void,\r\n fail: (msg: string) => void,\r\n ) => {\r\n if (!imagesUploadHandler) {\r\n fail('imagesUploadHandler is not provided')\r\n return\r\n }\r\n imagesUploadHandler(blobInfo.blob())\r\n .then((url) => {\r\n if (url) success(url)\r\n else fail('Upload failed')\r\n })\r\n .catch(() => fail('Upload failed'))\r\n },\r\n setup(editor: TinyMCEEditor) {\r\n editorCtx = editor\r\n editor.on('init', () => {\r\n isInit.value = true\r\n const body = editorCtx?.getBody()\r\n if (body) {\r\n body.style.fontSize = '16px'\r\n body.style.fontFamily = 'Calibri'\r\n }\r\n setContentSync(inputValue.value)\r\n while (task.length) {\r\n task.shift()?.()\r\n }\r\n })\r\n editor.on('change', (e) => {\r\n inputValue.value = getContentSync()\r\n emits('change', inputValue.value, e)\r\n })\r\n editor.on('input', (e) => {\r\n emits('input', e as Event)\r\n })\r\n if (textPhrase) {\r\n if (!fetchTextPhrases) {\r\n console.warn(\r\n '[RdRichEditor] textPhrase is true but fetchTextPhrases is not provided',\r\n )\r\n } else {\r\n useQuickTextPhrase(editor, fetchTextPhrases)\r\n }\r\n }\r\n },\r\n }\r\n\r\n if (tinymceBaseUrl) {\r\n baseOpts.base_url = tinymceBaseUrl\r\n }\r\n if (languageUrl) {\r\n baseOpts.language_url = languageUrl\r\n }\r\n\r\n const result: Record<string, unknown> = {\r\n ...baseOpts,\r\n ...cleanObject(restProps),\r\n }\r\n\r\n if (includes) {\r\n ;(Object.keys(includes) as Array<keyof typeof includes>).forEach((name) => {\r\n const current = result[name]\r\n const extra = includes[name]\r\n if (typeof current === 'string' && extra != null) {\r\n result[name] = addStr(current, extra)\r\n }\r\n })\r\n }\r\n if (excludes) {\r\n ;(Object.keys(excludes) as Array<keyof typeof excludes>).forEach((name) => {\r\n const current = result[name]\r\n const remove = excludes[name]\r\n if (typeof current === 'string' && remove != null) {\r\n result[name] = removeStr(current, remove)\r\n }\r\n })\r\n }\r\n\r\n return result\r\n}\r\n\r\nfunction transferAsync<T extends (...args: any[]) => any>(fun: T) {\r\n return function (\r\n ...rest: Parameters<T>\r\n ): ReturnType<T> | Promise<ReturnType<T>> {\r\n if (!isInit.value) {\r\n return new Promise<ReturnType<T>>((resolve) => {\r\n task.push(() => resolve(fun(...rest)))\r\n })\r\n }\r\n return fun(...rest)\r\n }\r\n}\r\n\r\nfunction validate() {\r\n const tempDiv = document.createElement('div')\r\n tempDiv.innerHTML = inputValue.value\r\n if (tempDiv.textContent?.trim()) return true\r\n if (tempDiv.querySelector('a.mce-item-anchor, img, table')) return true\r\n return false\r\n}\r\n\r\nwatch(\r\n () => inputValue.value,\r\n (newVal) => {\r\n if (!isInit.value || newVal === getContentSync()) return\r\n setContentSync(newVal)\r\n },\r\n)\r\n\r\nonMounted(() => {\r\n const tinymce = window.tinymce\r\n if (!tinymce) {\r\n console.error(\r\n '[RdRichEditor] window.tinymce is missing. Load TinyMCE 5.10.x via <script> before using RdRichEditor.',\r\n )\r\n return\r\n }\r\n if (props.tinymceBaseUrl) {\r\n tinymce.baseURL = props.tinymceBaseUrl\r\n }\r\n void tinymce.init(initOpts())\r\n})\r\n\r\nonBeforeUnmount(() => {\r\n editorCtx?.remove()\r\n editorCtx = null\r\n})\r\n\r\ndefineExpose({\r\n getEditor: transferAsync(() => editorCtx),\r\n getContent: transferAsync(getContentSync),\r\n setContent: transferAsync(setContentSync),\r\n insertContent: transferAsync(insertContentSync),\r\n validate,\r\n})\r\n</script>\r\n\r\n<template>\r\n <div class=\"rd-rich-editor\">\r\n <textarea :id=\"editorId\" style=\"visibility: hidden\" />\r\n <div v-if=\"isInit\" class=\"rd-rich-editor-footer-bar\">\r\n <slot name=\"footer-bar\" />\r\n </div>\r\n </div>\r\n</template>\r\n","<script setup lang=\"ts\">\r\ndefineOptions({\r\n name: 'RdRichEditor',\r\n})\r\n\r\nimport { onBeforeUnmount, onMounted, ref, watch } from 'vue'\r\nimport type { TinyMCEEditor } from '../../types/tinymce'\r\nimport { cleanObject } from '../../utils/cleanObject'\r\nimport useQuickTextPhrase, { type TextPhraseItem } from './useQuickTextPhrase'\r\nimport { setupDialogZIndexBridge } from './dialogZIndexBridge'\r\nimport '../../styles/rd-rich-editor.css'\r\n\r\ninterface EditorConfigProps {\r\n plugins?: string | string[]\r\n language?: string\r\n menubar?: string | string[]\r\n toolbar?: string | string[]\r\n toolbar_mode?: string\r\n min_height?: number\r\n height?: number\r\n /** Enable `#` quick text phrase autocompleter (requires fetchTextPhrases) */\r\n textPhrase?: boolean\r\n}\r\n\r\ntype Props = EditorConfigProps & {\r\n includes?: Pick<EditorConfigProps, 'plugins' | 'menubar' | 'toolbar'>\r\n excludes?: Pick<EditorConfigProps, 'plugins' | 'menubar' | 'toolbar'>\r\n /**\r\n * Image upload hook. Return a publicly accessible URL.\r\n * When omitted, paste/insert image upload fails with a clear message.\r\n */\r\n imagesUploadHandler?: (blob: Blob) => Promise<string>\r\n /** Fetch quick text phrases when textPhrase is true */\r\n fetchTextPhrases?: () => Promise<TextPhraseItem[]>\r\n /** Override TinyMCE baseURL when static assets are not at the default path */\r\n tinymceBaseUrl?: string\r\n /**\r\n * TinyMCE language pack URL (language_url).\r\n * Needed when langs/*.js are not under tinymceBaseUrl (e.g. CDN demos).\r\n */\r\n languageUrl?: string\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n language: 'zh_CN',\r\n textPhrase: false,\r\n})\r\n\r\nconst emits = defineEmits<{\r\n input: [e: Event]\r\n change: [value: string, e: unknown]\r\n}>()\r\n\r\nconst inputValue = defineModel<string>({ default: '' })\r\nconst editorId = `rd-editor_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`\r\nconst isInit = ref(false)\r\nlet editorCtx: TinyMCEEditor | null = null\r\n\r\nconst task: Array<() => void> = []\r\n\r\nsetupDialogZIndexBridge()\r\n\r\nfunction getContentSync() {\r\n return editorCtx?.getContent() ?? ''\r\n}\r\n\r\nfunction setContentSync(value: string) {\r\n const result = editorCtx?.setContent(value ?? '')\r\n inputValue.value = getContentSync()\r\n return result\r\n}\r\n\r\nfunction insertContentSync(value: string) {\r\n editorCtx?.insertContent(value)\r\n inputValue.value = getContentSync()\r\n}\r\n\r\n/** Append tokens to a space-separated TinyMCE config string (word match). */\r\nfunction addStr(source: string, target: string | string[]) {\r\n const list = Array.isArray(target) ? target : [target]\r\n let result = source\r\n list.forEach((token) => {\r\n const trimmed = token.trim()\r\n if (!trimmed) return\r\n const re = new RegExp(`(?:^|\\\\s)${escapeRegExp(trimmed)}(?:\\\\s|$)`)\r\n if (!re.test(result)) {\r\n result = `${result} ${trimmed}`.trim()\r\n }\r\n })\r\n return result\r\n}\r\n\r\n/** Remove tokens from a space-separated TinyMCE config string. */\r\nfunction removeStr(source: string, target: string | string[]) {\r\n const list = Array.isArray(target) ? target : [target]\r\n let result = source\r\n list.forEach((token) => {\r\n const trimmed = token.trim()\r\n if (!trimmed) return\r\n const re = new RegExp(`(?:^|\\\\s)${escapeRegExp(trimmed)}(?=\\\\s|$)`, 'g')\r\n result = result.replace(re, ' ').replace(/\\s+/g, ' ').trim()\r\n })\r\n return result\r\n}\r\n\r\nfunction escapeRegExp(s: string) {\r\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\r\n}\r\n\r\nfunction initOpts(): Record<string, unknown> {\r\n const {\r\n includes,\r\n excludes,\r\n textPhrase,\r\n imagesUploadHandler,\r\n fetchTextPhrases,\r\n tinymceBaseUrl,\r\n languageUrl,\r\n ...restProps\r\n } = props\r\n\r\n const language = props.language === 'zh-cn' ? 'zh_CN' : props.language\r\n\r\n const baseOpts: Record<string, unknown> = {\r\n selector: `textarea#${editorId}`,\r\n language,\r\n plugins:\r\n 'advlist autolink link image lists charmap emoticons preview hr anchor pagebreak nonbreaking code table paste insertdatetime wordcount',\r\n menubar: 'file edit view insert format tools',\r\n menu: {\r\n insert: {\r\n title: 'Insert',\r\n items:\r\n 'image link inserttable | charmap emoticons hr | pagebreak nonbreaking anchor | insertdatetime',\r\n },\r\n },\r\n toolbar:\r\n 'undo redo | styleselect | fontsizeselect fontselect | bold italic forecolor backcolor | alignleft aligncenter alignright alignjustify | outdent indent | bullist numlist table | link image | code',\r\n toolbar_mode: 'wrap',\r\n fontsize_formats:\r\n '5px 6px 7px 8px 9px 10px 12px 14px 16px 18px 20px 22px 24px 26px 28px 30px 32px 34px 36px 48px 72px',\r\n font_formats:\r\n 'Andale Mono=andale mono,times;Arial=arial,helvetica,sans-serif;Arial Black=arial black,avant garde;Book Antiqua=book antiqua,palatino;Calibri;Calibri Light;Comic Sans MS=comic sans ms,sans-serif;Courier New=courier new,courier;Georgia=georgia,palatino;Helvetica=helvetica;Impact=impact,chicago;Symbol=symbol;Tahoma=tahoma,arial,;Terminal=terminal,monaco;Times New Roman=times new roman,times;Trebuchet MS=trebuchet ms,geneva;Verdana=verdana,geneva;Webdings=webdings;Wingdings=wingdings,;',\r\n min_height: 480,\r\n table_default_styles: { width: '100%', borderCollapse: 'collapse' },\r\n image_advtab: true,\r\n image_title: false,\r\n paste_data_images: true,\r\n images_upload_handler: (\r\n blobInfo: { blob: () => Blob },\r\n success: (url: string) => void,\r\n fail: (msg: string) => void,\r\n ) => {\r\n if (!imagesUploadHandler) {\r\n fail('imagesUploadHandler is not provided')\r\n return\r\n }\r\n imagesUploadHandler(blobInfo.blob())\r\n .then((url) => {\r\n if (url) success(url)\r\n else fail('Upload failed')\r\n })\r\n .catch(() => fail('Upload failed'))\r\n },\r\n setup(editor: TinyMCEEditor) {\r\n editorCtx = editor\r\n editor.on('init', () => {\r\n isInit.value = true\r\n const body = editorCtx?.getBody()\r\n if (body) {\r\n body.style.fontSize = '16px'\r\n body.style.fontFamily = 'Calibri'\r\n }\r\n setContentSync(inputValue.value)\r\n while (task.length) {\r\n task.shift()?.()\r\n }\r\n })\r\n editor.on('change', (e) => {\r\n inputValue.value = getContentSync()\r\n emits('change', inputValue.value, e)\r\n })\r\n editor.on('input', (e) => {\r\n emits('input', e as Event)\r\n })\r\n if (textPhrase) {\r\n if (!fetchTextPhrases) {\r\n console.warn(\r\n '[RdRichEditor] textPhrase is true but fetchTextPhrases is not provided',\r\n )\r\n } else {\r\n useQuickTextPhrase(editor, fetchTextPhrases)\r\n }\r\n }\r\n },\r\n }\r\n\r\n if (tinymceBaseUrl) {\r\n baseOpts.base_url = tinymceBaseUrl\r\n }\r\n if (languageUrl) {\r\n baseOpts.language_url = languageUrl\r\n }\r\n\r\n const result: Record<string, unknown> = {\r\n ...baseOpts,\r\n ...cleanObject(restProps),\r\n }\r\n\r\n if (includes) {\r\n ;(Object.keys(includes) as Array<keyof typeof includes>).forEach((name) => {\r\n const current = result[name]\r\n const extra = includes[name]\r\n if (typeof current === 'string' && extra != null) {\r\n result[name] = addStr(current, extra)\r\n }\r\n })\r\n }\r\n if (excludes) {\r\n ;(Object.keys(excludes) as Array<keyof typeof excludes>).forEach((name) => {\r\n const current = result[name]\r\n const remove = excludes[name]\r\n if (typeof current === 'string' && remove != null) {\r\n result[name] = removeStr(current, remove)\r\n }\r\n })\r\n }\r\n\r\n return result\r\n}\r\n\r\nfunction transferAsync<T extends (...args: any[]) => any>(fun: T) {\r\n return function (\r\n ...rest: Parameters<T>\r\n ): ReturnType<T> | Promise<ReturnType<T>> {\r\n if (!isInit.value) {\r\n return new Promise<ReturnType<T>>((resolve) => {\r\n task.push(() => resolve(fun(...rest)))\r\n })\r\n }\r\n return fun(...rest)\r\n }\r\n}\r\n\r\nfunction validate() {\r\n const tempDiv = document.createElement('div')\r\n tempDiv.innerHTML = inputValue.value\r\n if (tempDiv.textContent?.trim()) return true\r\n if (tempDiv.querySelector('a.mce-item-anchor, img, table')) return true\r\n return false\r\n}\r\n\r\nwatch(\r\n () => inputValue.value,\r\n (newVal) => {\r\n if (!isInit.value || newVal === getContentSync()) return\r\n setContentSync(newVal)\r\n },\r\n)\r\n\r\nonMounted(() => {\r\n const tinymce = window.tinymce\r\n if (!tinymce) {\r\n console.error(\r\n '[RdRichEditor] window.tinymce is missing. Load TinyMCE 5.10.x via <script> before using RdRichEditor.',\r\n )\r\n return\r\n }\r\n if (props.tinymceBaseUrl) {\r\n tinymce.baseURL = props.tinymceBaseUrl\r\n }\r\n void tinymce.init(initOpts())\r\n})\r\n\r\nonBeforeUnmount(() => {\r\n editorCtx?.remove()\r\n editorCtx = null\r\n})\r\n\r\ndefineExpose({\r\n getEditor: transferAsync(() => editorCtx),\r\n getContent: transferAsync(getContentSync),\r\n setContent: transferAsync(setContentSync),\r\n insertContent: transferAsync(insertContentSync),\r\n validate,\r\n})\r\n</script>\r\n\r\n<template>\r\n <div class=\"rd-rich-editor\">\r\n <textarea :id=\"editorId\" style=\"visibility: hidden\" />\r\n <div v-if=\"isInit\" class=\"rd-rich-editor-footer-bar\">\r\n <slot name=\"footer-bar\" />\r\n </div>\r\n </div>\r\n</template>\r\n","import type { App } from 'vue'\r\nimport { RdRichEditor } from './components/rdRichEditor'\r\nexport type { TextPhraseItem } from './components/rdRichEditor'\r\n\r\nexport { RdRichEditor }\r\nexport { RodelEditorResolver } from './resolver'\r\n\r\nimport './styles/rd-rich-editor.css'\r\n\r\nconst components = [RdRichEditor]\r\n\r\n/**\r\n * Register RdRichEditor. Host must load TinyMCE 5.10.x on window.tinymce.\r\n */\r\nexport function install(app: App): void {\r\n components.forEach((component) => {\r\n const name = component.name || (component as { __name?: string }).__name\r\n if (name) {\r\n app.component(name, component)\r\n }\r\n })\r\n}\r\n\r\nexport default install\r\n"],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { RodelEditorResolver as e } from "./resolver.js";
|
|
2
|
+
import { createCommentVNode as t, createElementBlock as n, createElementVNode as r, defineComponent as i, inject as a, mergeModels as o, onBeforeUnmount as s, onMounted as c, openBlock as l, ref as u, renderSlot as d, useModel as f, watch as p, watchEffect as m } from "vue";
|
|
3
|
+
import { dialogInjectionKey as h, useZIndex as g } from "element-plus";
|
|
4
|
+
import './style.css';//#region src/utils/cleanObject.ts
|
|
5
|
+
function _(e) {
|
|
6
|
+
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
|
+
//#endregion
|
|
9
|
+
//#region src/components/rdRichEditor/useQuickTextPhrase.ts
|
|
10
|
+
function v(e, t) {
|
|
11
|
+
let n = 0, r = [];
|
|
12
|
+
function i(e, t) {
|
|
13
|
+
return e.filter((e) => e.input_code.includes(t) || e.name.includes(t)).map((e) => ({
|
|
14
|
+
type: "cardmenuitem",
|
|
15
|
+
value: e,
|
|
16
|
+
label: e.name,
|
|
17
|
+
items: [{
|
|
18
|
+
type: "cardcontainer",
|
|
19
|
+
direction: "vertical",
|
|
20
|
+
items: [{
|
|
21
|
+
type: "cardtext",
|
|
22
|
+
text: e.name,
|
|
23
|
+
name: e.input_code
|
|
24
|
+
}]
|
|
25
|
+
}]
|
|
26
|
+
}));
|
|
27
|
+
}
|
|
28
|
+
e.ui.registry.addAutocompleter("quick_text_phrase", {
|
|
29
|
+
ch: "#",
|
|
30
|
+
minChars: 0,
|
|
31
|
+
columns: 1,
|
|
32
|
+
matches: (e, t, n) => t && t.endsWith("#") || !!n && n.length < 6,
|
|
33
|
+
fetch: async (e) => {
|
|
34
|
+
if (n === 1) return [];
|
|
35
|
+
if (!n) {
|
|
36
|
+
n = 1;
|
|
37
|
+
try {
|
|
38
|
+
r = await t() || [], n = 2;
|
|
39
|
+
} catch (e) {
|
|
40
|
+
throw n = 0, e;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return i(r, e);
|
|
44
|
+
},
|
|
45
|
+
onAction: (t, n, r) => {
|
|
46
|
+
n.deleteContents(), e.insertContent(r.content), t.hide();
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
//#endregion
|
|
51
|
+
//#region src/components/rdRichEditor/dialogZIndexBridge.ts
|
|
52
|
+
function y() {
|
|
53
|
+
if (!a(h, null)) return;
|
|
54
|
+
let e = g().currentZIndex;
|
|
55
|
+
m(() => {
|
|
56
|
+
e.value > 1300 && document.documentElement.style.setProperty("--el-dialog-z-index", e.value.toString());
|
|
57
|
+
}), s(() => {
|
|
58
|
+
document.documentElement.style.removeProperty("--el-dialog-z-index");
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
//#endregion
|
|
62
|
+
//#region src/components/rdRichEditor/RdRichEditor.vue?vue&type=script&setup=true&lang.ts
|
|
63
|
+
var b = { class: "rd-rich-editor" }, x = {
|
|
64
|
+
key: 0,
|
|
65
|
+
class: "rd-rich-editor-footer-bar"
|
|
66
|
+
}, S = /* @__PURE__ */ i({
|
|
67
|
+
name: "RdRichEditor",
|
|
68
|
+
__name: "RdRichEditor",
|
|
69
|
+
props: /*@__PURE__*/ o({
|
|
70
|
+
plugins: {},
|
|
71
|
+
language: { default: "zh_CN" },
|
|
72
|
+
menubar: {},
|
|
73
|
+
toolbar: {},
|
|
74
|
+
toolbar_mode: {},
|
|
75
|
+
min_height: {},
|
|
76
|
+
height: {},
|
|
77
|
+
textPhrase: {
|
|
78
|
+
type: Boolean,
|
|
79
|
+
default: !1
|
|
80
|
+
},
|
|
81
|
+
includes: {},
|
|
82
|
+
excludes: {},
|
|
83
|
+
imagesUploadHandler: {},
|
|
84
|
+
fetchTextPhrases: {},
|
|
85
|
+
tinymceBaseUrl: {},
|
|
86
|
+
languageUrl: {}
|
|
87
|
+
}, {
|
|
88
|
+
modelValue: { default: "" },
|
|
89
|
+
modelModifiers: {}
|
|
90
|
+
}),
|
|
91
|
+
emits: /*@__PURE__*/ o(["input", "change"], ["update:modelValue"]),
|
|
92
|
+
setup(e, { expose: i, emit: a }) {
|
|
93
|
+
let o = e, m = a, h = f(e, "modelValue"), g = `rd-editor_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`, S = u(!1), C = null, w = [];
|
|
94
|
+
y();
|
|
95
|
+
function T() {
|
|
96
|
+
return C?.getContent() ?? "";
|
|
97
|
+
}
|
|
98
|
+
function E(e) {
|
|
99
|
+
let t = C?.setContent(e ?? "");
|
|
100
|
+
return h.value = T(), t;
|
|
101
|
+
}
|
|
102
|
+
function D(e) {
|
|
103
|
+
C?.insertContent(e), h.value = T();
|
|
104
|
+
}
|
|
105
|
+
function O(e, t) {
|
|
106
|
+
let n = Array.isArray(t) ? t : [t], r = e;
|
|
107
|
+
return n.forEach((e) => {
|
|
108
|
+
let t = e.trim();
|
|
109
|
+
t && (RegExp(`(?:^|\\s)${A(t)}(?:\\s|$)`).test(r) || (r = `${r} ${t}`.trim()));
|
|
110
|
+
}), r;
|
|
111
|
+
}
|
|
112
|
+
function k(e, t) {
|
|
113
|
+
let n = Array.isArray(t) ? t : [t], r = e;
|
|
114
|
+
return n.forEach((e) => {
|
|
115
|
+
let t = e.trim();
|
|
116
|
+
if (!t) return;
|
|
117
|
+
let n = RegExp(`(?:^|\\s)${A(t)}(?=\\s|$)`, "g");
|
|
118
|
+
r = r.replace(n, " ").replace(/\s+/g, " ").trim();
|
|
119
|
+
}), r;
|
|
120
|
+
}
|
|
121
|
+
function A(e) {
|
|
122
|
+
return e.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
123
|
+
}
|
|
124
|
+
function j() {
|
|
125
|
+
let { includes: e, excludes: t, textPhrase: n, imagesUploadHandler: r, fetchTextPhrases: i, tinymceBaseUrl: a, languageUrl: s, ...c } = o, l = o.language === "zh-cn" ? "zh_CN" : o.language, u = {
|
|
126
|
+
selector: `textarea#${g}`,
|
|
127
|
+
language: l,
|
|
128
|
+
plugins: "advlist autolink link image lists charmap emoticons preview hr anchor pagebreak nonbreaking code table paste insertdatetime wordcount",
|
|
129
|
+
menubar: "file edit view insert format tools",
|
|
130
|
+
menu: { insert: {
|
|
131
|
+
title: "Insert",
|
|
132
|
+
items: "image link inserttable | charmap emoticons hr | pagebreak nonbreaking anchor | insertdatetime"
|
|
133
|
+
} },
|
|
134
|
+
toolbar: "undo redo | styleselect | fontsizeselect fontselect | bold italic forecolor backcolor | alignleft aligncenter alignright alignjustify | outdent indent | bullist numlist table | link image | code",
|
|
135
|
+
toolbar_mode: "wrap",
|
|
136
|
+
fontsize_formats: "5px 6px 7px 8px 9px 10px 12px 14px 16px 18px 20px 22px 24px 26px 28px 30px 32px 34px 36px 48px 72px",
|
|
137
|
+
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,;",
|
|
138
|
+
min_height: 480,
|
|
139
|
+
table_default_styles: {
|
|
140
|
+
width: "100%",
|
|
141
|
+
borderCollapse: "collapse"
|
|
142
|
+
},
|
|
143
|
+
image_advtab: !0,
|
|
144
|
+
image_title: !1,
|
|
145
|
+
paste_data_images: !0,
|
|
146
|
+
images_upload_handler: (e, t, n) => {
|
|
147
|
+
if (!r) {
|
|
148
|
+
n("imagesUploadHandler is not provided");
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
r(e.blob()).then((e) => {
|
|
152
|
+
e ? t(e) : n("Upload failed");
|
|
153
|
+
}).catch(() => n("Upload failed"));
|
|
154
|
+
},
|
|
155
|
+
setup(e) {
|
|
156
|
+
C = e, e.on("init", () => {
|
|
157
|
+
S.value = !0;
|
|
158
|
+
let e = C?.getBody();
|
|
159
|
+
for (e && (e.style.fontSize = "16px", e.style.fontFamily = "Calibri"), E(h.value); w.length;) w.shift()?.();
|
|
160
|
+
}), e.on("change", (e) => {
|
|
161
|
+
h.value = T(), m("change", h.value, e);
|
|
162
|
+
}), e.on("input", (e) => {
|
|
163
|
+
m("input", e);
|
|
164
|
+
}), n && (i ? v(e, i) : console.warn("[RdRichEditor] textPhrase is true but fetchTextPhrases is not provided"));
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
a && (u.base_url = a), s && (u.language_url = s);
|
|
168
|
+
let d = {
|
|
169
|
+
...u,
|
|
170
|
+
..._(c)
|
|
171
|
+
};
|
|
172
|
+
return e && Object.keys(e).forEach((t) => {
|
|
173
|
+
let n = d[t], r = e[t];
|
|
174
|
+
typeof n == "string" && r != null && (d[t] = O(n, r));
|
|
175
|
+
}), t && Object.keys(t).forEach((e) => {
|
|
176
|
+
let n = d[e], r = t[e];
|
|
177
|
+
typeof n == "string" && r != null && (d[e] = k(n, r));
|
|
178
|
+
}), d;
|
|
179
|
+
}
|
|
180
|
+
function M(e) {
|
|
181
|
+
return function(...t) {
|
|
182
|
+
return S.value ? e(...t) : new Promise((n) => {
|
|
183
|
+
w.push(() => n(e(...t)));
|
|
184
|
+
});
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
function N() {
|
|
188
|
+
let e = document.createElement("div");
|
|
189
|
+
return e.innerHTML = h.value, !!(e.textContent?.trim() || e.querySelector("a.mce-item-anchor, img, table"));
|
|
190
|
+
}
|
|
191
|
+
return p(() => h.value, (e) => {
|
|
192
|
+
!S.value || e === T() || E(e);
|
|
193
|
+
}), c(() => {
|
|
194
|
+
let e = window.tinymce;
|
|
195
|
+
if (!e) {
|
|
196
|
+
console.error("[RdRichEditor] window.tinymce is missing. Load TinyMCE 5.10.x via <script> before using RdRichEditor.");
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
o.tinymceBaseUrl && (e.baseURL = o.tinymceBaseUrl), e.init(j());
|
|
200
|
+
}), s(() => {
|
|
201
|
+
C?.remove(), C = null;
|
|
202
|
+
}), i({
|
|
203
|
+
getEditor: M(() => C),
|
|
204
|
+
getContent: M(T),
|
|
205
|
+
setContent: M(E),
|
|
206
|
+
insertContent: M(D),
|
|
207
|
+
validate: N
|
|
208
|
+
}), (e, i) => (l(), n("div", b, [r("textarea", {
|
|
209
|
+
id: g,
|
|
210
|
+
style: { visibility: "hidden" }
|
|
211
|
+
}), S.value ? (l(), n("div", x, [d(e.$slots, "footer-bar")])) : t("", !0)]));
|
|
212
|
+
}
|
|
213
|
+
}), C = [S];
|
|
214
|
+
function w(e) {
|
|
215
|
+
C.forEach((t) => {
|
|
216
|
+
let n = t.name || t.__name;
|
|
217
|
+
n && e.component(n, t);
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
//#endregion
|
|
221
|
+
export { S as RdRichEditor, e as RodelEditorResolver, w as default, w as install };
|
|
222
|
+
|
|
223
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/utils/cleanObject.ts","../src/components/rdRichEditor/useQuickTextPhrase.ts","../src/components/rdRichEditor/dialogZIndexBridge.ts","../src/components/rdRichEditor/RdRichEditor.vue","../src/components/rdRichEditor/RdRichEditor.vue","../src/index.ts"],"sourcesContent":["/**\r\n * Remove empty values from a shallow object (null / undefined / '' / [] / {}).\r\n */\r\nexport function cleanObject<T>(obj: T | null | undefined): Partial<T> {\r\n if (!obj) return {}\r\n\r\n return Object.fromEntries(\r\n Object.entries(obj as Record<string, unknown>).filter(\r\n ([, v]) =>\r\n v !== '' &&\r\n v !== null &&\r\n v !== undefined &&\r\n !(Array.isArray(v) && v.length === 0) &&\r\n !(typeof v === 'object' && v && Object.keys(v as object).length === 0),\r\n ),\r\n ) as Partial<T>\r\n}\r\n","import type { TinyMCEEditor } from '../../types/tinymce'\r\n\r\nexport interface TextPhraseItem {\r\n name: string\r\n input_code: string\r\n content: string\r\n}\r\n\r\n/**\r\n * Register TinyMCE autocompleter for `#` quick text phrases.\r\n * Data is supplied by the host via `fetchTextPhrases`.\r\n */\r\nexport default function useQuickTextPhrase(\r\n editor: TinyMCEEditor,\r\n fetchTextPhrases: () => Promise<TextPhraseItem[]>,\r\n) {\r\n let status = 0 // 0 idle | 1 loading | 2 ready\r\n let quickTextPhrase: TextPhraseItem[] = []\r\n\r\n function filterListByPattern(list: TextPhraseItem[], pattern: string) {\r\n return list\r\n .filter((v) => v.input_code.includes(pattern) || v.name.includes(pattern))\r\n .map((v) => ({\r\n type: 'cardmenuitem',\r\n value: v,\r\n label: v.name,\r\n items: [\r\n {\r\n type: 'cardcontainer',\r\n direction: 'vertical',\r\n items: [\r\n {\r\n type: 'cardtext',\r\n text: v.name,\r\n name: v.input_code,\r\n },\r\n ],\r\n },\r\n ],\r\n }))\r\n }\r\n\r\n editor.ui.registry.addAutocompleter('quick_text_phrase', {\r\n ch: '#',\r\n minChars: 0,\r\n columns: 1,\r\n matches: (_rng: unknown, text: string, pattern: string) =>\r\n (text && text.endsWith('#')) || (!!pattern && pattern.length < 6),\r\n fetch: async (pattern: string) => {\r\n if (status === 1) return []\r\n if (!status) {\r\n status = 1\r\n try {\r\n quickTextPhrase = (await fetchTextPhrases()) || []\r\n status = 2\r\n } catch (err) {\r\n status = 0\r\n throw err\r\n }\r\n }\r\n return filterListByPattern(quickTextPhrase, pattern)\r\n },\r\n onAction: (\r\n autocompleterApi: { hide: () => void },\r\n rng: { deleteContents: () => void },\r\n value: TextPhraseItem,\r\n ) => {\r\n rng.deleteContents()\r\n editor.insertContent(value.content)\r\n autocompleterApi.hide()\r\n },\r\n })\r\n}\r\n","import { inject, onBeforeUnmount, watchEffect } from 'vue'\r\nimport { useZIndex, dialogInjectionKey } from 'element-plus'\r\n\r\n/**\r\n * When RdRichEditor is used inside ElDialog / RdDialog, raise TinyMCE\r\n * popover z-index above the dialog via --el-dialog-z-index.\r\n */\r\nexport function setupDialogZIndexBridge(): void {\r\n const dialogProvide = inject(dialogInjectionKey, null)\r\n if (!dialogProvide) return\r\n\r\n const dialogZIndex = useZIndex().currentZIndex\r\n watchEffect(() => {\r\n if (dialogZIndex.value > 1300) {\r\n document.documentElement.style.setProperty(\r\n '--el-dialog-z-index',\r\n dialogZIndex.value.toString(),\r\n )\r\n }\r\n })\r\n onBeforeUnmount(() => {\r\n document.documentElement.style.removeProperty('--el-dialog-z-index')\r\n })\r\n}\r\n","<script setup lang=\"ts\">\r\ndefineOptions({\r\n name: 'RdRichEditor',\r\n})\r\n\r\nimport { onBeforeUnmount, onMounted, ref, watch } from 'vue'\r\nimport type { TinyMCEEditor } from '../../types/tinymce'\r\nimport { cleanObject } from '../../utils/cleanObject'\r\nimport useQuickTextPhrase, { type TextPhraseItem } from './useQuickTextPhrase'\r\nimport { setupDialogZIndexBridge } from './dialogZIndexBridge'\r\nimport '../../styles/rd-rich-editor.css'\r\n\r\ninterface EditorConfigProps {\r\n plugins?: string | string[]\r\n language?: string\r\n menubar?: string | string[]\r\n toolbar?: string | string[]\r\n toolbar_mode?: string\r\n min_height?: number\r\n height?: number\r\n /** Enable `#` quick text phrase autocompleter (requires fetchTextPhrases) */\r\n textPhrase?: boolean\r\n}\r\n\r\ntype Props = EditorConfigProps & {\r\n includes?: Pick<EditorConfigProps, 'plugins' | 'menubar' | 'toolbar'>\r\n excludes?: Pick<EditorConfigProps, 'plugins' | 'menubar' | 'toolbar'>\r\n /**\r\n * Image upload hook. Return a publicly accessible URL.\r\n * When omitted, paste/insert image upload fails with a clear message.\r\n */\r\n imagesUploadHandler?: (blob: Blob) => Promise<string>\r\n /** Fetch quick text phrases when textPhrase is true */\r\n fetchTextPhrases?: () => Promise<TextPhraseItem[]>\r\n /** Override TinyMCE baseURL when static assets are not at the default path */\r\n tinymceBaseUrl?: string\r\n /**\r\n * TinyMCE language pack URL (language_url).\r\n * Needed when langs/*.js are not under tinymceBaseUrl (e.g. CDN demos).\r\n */\r\n languageUrl?: string\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n language: 'zh_CN',\r\n textPhrase: false,\r\n})\r\n\r\nconst emits = defineEmits<{\r\n input: [e: Event]\r\n change: [value: string, e: unknown]\r\n}>()\r\n\r\nconst inputValue = defineModel<string>({ default: '' })\r\nconst editorId = `rd-editor_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`\r\nconst isInit = ref(false)\r\nlet editorCtx: TinyMCEEditor | null = null\r\n\r\nconst task: Array<() => void> = []\r\n\r\nsetupDialogZIndexBridge()\r\n\r\nfunction getContentSync() {\r\n return editorCtx?.getContent() ?? ''\r\n}\r\n\r\nfunction setContentSync(value: string) {\r\n const result = editorCtx?.setContent(value ?? '')\r\n inputValue.value = getContentSync()\r\n return result\r\n}\r\n\r\nfunction insertContentSync(value: string) {\r\n editorCtx?.insertContent(value)\r\n inputValue.value = getContentSync()\r\n}\r\n\r\n/** Append tokens to a space-separated TinyMCE config string (word match). */\r\nfunction addStr(source: string, target: string | string[]) {\r\n const list = Array.isArray(target) ? target : [target]\r\n let result = source\r\n list.forEach((token) => {\r\n const trimmed = token.trim()\r\n if (!trimmed) return\r\n const re = new RegExp(`(?:^|\\\\s)${escapeRegExp(trimmed)}(?:\\\\s|$)`)\r\n if (!re.test(result)) {\r\n result = `${result} ${trimmed}`.trim()\r\n }\r\n })\r\n return result\r\n}\r\n\r\n/** Remove tokens from a space-separated TinyMCE config string. */\r\nfunction removeStr(source: string, target: string | string[]) {\r\n const list = Array.isArray(target) ? target : [target]\r\n let result = source\r\n list.forEach((token) => {\r\n const trimmed = token.trim()\r\n if (!trimmed) return\r\n const re = new RegExp(`(?:^|\\\\s)${escapeRegExp(trimmed)}(?=\\\\s|$)`, 'g')\r\n result = result.replace(re, ' ').replace(/\\s+/g, ' ').trim()\r\n })\r\n return result\r\n}\r\n\r\nfunction escapeRegExp(s: string) {\r\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\r\n}\r\n\r\nfunction initOpts(): Record<string, unknown> {\r\n const {\r\n includes,\r\n excludes,\r\n textPhrase,\r\n imagesUploadHandler,\r\n fetchTextPhrases,\r\n tinymceBaseUrl,\r\n languageUrl,\r\n ...restProps\r\n } = props\r\n\r\n const language = props.language === 'zh-cn' ? 'zh_CN' : props.language\r\n\r\n const baseOpts: Record<string, unknown> = {\r\n selector: `textarea#${editorId}`,\r\n language,\r\n plugins:\r\n 'advlist autolink link image lists charmap emoticons preview hr anchor pagebreak nonbreaking code table paste insertdatetime wordcount',\r\n menubar: 'file edit view insert format tools',\r\n menu: {\r\n insert: {\r\n title: 'Insert',\r\n items:\r\n 'image link inserttable | charmap emoticons hr | pagebreak nonbreaking anchor | insertdatetime',\r\n },\r\n },\r\n toolbar:\r\n 'undo redo | styleselect | fontsizeselect fontselect | bold italic forecolor backcolor | alignleft aligncenter alignright alignjustify | outdent indent | bullist numlist table | link image | code',\r\n toolbar_mode: 'wrap',\r\n fontsize_formats:\r\n '5px 6px 7px 8px 9px 10px 12px 14px 16px 18px 20px 22px 24px 26px 28px 30px 32px 34px 36px 48px 72px',\r\n font_formats:\r\n 'Andale Mono=andale mono,times;Arial=arial,helvetica,sans-serif;Arial Black=arial black,avant garde;Book Antiqua=book antiqua,palatino;Calibri;Calibri Light;Comic Sans MS=comic sans ms,sans-serif;Courier New=courier new,courier;Georgia=georgia,palatino;Helvetica=helvetica;Impact=impact,chicago;Symbol=symbol;Tahoma=tahoma,arial,;Terminal=terminal,monaco;Times New Roman=times new roman,times;Trebuchet MS=trebuchet ms,geneva;Verdana=verdana,geneva;Webdings=webdings;Wingdings=wingdings,;',\r\n min_height: 480,\r\n table_default_styles: { width: '100%', borderCollapse: 'collapse' },\r\n image_advtab: true,\r\n image_title: false,\r\n paste_data_images: true,\r\n images_upload_handler: (\r\n blobInfo: { blob: () => Blob },\r\n success: (url: string) => void,\r\n fail: (msg: string) => void,\r\n ) => {\r\n if (!imagesUploadHandler) {\r\n fail('imagesUploadHandler is not provided')\r\n return\r\n }\r\n imagesUploadHandler(blobInfo.blob())\r\n .then((url) => {\r\n if (url) success(url)\r\n else fail('Upload failed')\r\n })\r\n .catch(() => fail('Upload failed'))\r\n },\r\n setup(editor: TinyMCEEditor) {\r\n editorCtx = editor\r\n editor.on('init', () => {\r\n isInit.value = true\r\n const body = editorCtx?.getBody()\r\n if (body) {\r\n body.style.fontSize = '16px'\r\n body.style.fontFamily = 'Calibri'\r\n }\r\n setContentSync(inputValue.value)\r\n while (task.length) {\r\n task.shift()?.()\r\n }\r\n })\r\n editor.on('change', (e) => {\r\n inputValue.value = getContentSync()\r\n emits('change', inputValue.value, e)\r\n })\r\n editor.on('input', (e) => {\r\n emits('input', e as Event)\r\n })\r\n if (textPhrase) {\r\n if (!fetchTextPhrases) {\r\n console.warn(\r\n '[RdRichEditor] textPhrase is true but fetchTextPhrases is not provided',\r\n )\r\n } else {\r\n useQuickTextPhrase(editor, fetchTextPhrases)\r\n }\r\n }\r\n },\r\n }\r\n\r\n if (tinymceBaseUrl) {\r\n baseOpts.base_url = tinymceBaseUrl\r\n }\r\n if (languageUrl) {\r\n baseOpts.language_url = languageUrl\r\n }\r\n\r\n const result: Record<string, unknown> = {\r\n ...baseOpts,\r\n ...cleanObject(restProps),\r\n }\r\n\r\n if (includes) {\r\n ;(Object.keys(includes) as Array<keyof typeof includes>).forEach((name) => {\r\n const current = result[name]\r\n const extra = includes[name]\r\n if (typeof current === 'string' && extra != null) {\r\n result[name] = addStr(current, extra)\r\n }\r\n })\r\n }\r\n if (excludes) {\r\n ;(Object.keys(excludes) as Array<keyof typeof excludes>).forEach((name) => {\r\n const current = result[name]\r\n const remove = excludes[name]\r\n if (typeof current === 'string' && remove != null) {\r\n result[name] = removeStr(current, remove)\r\n }\r\n })\r\n }\r\n\r\n return result\r\n}\r\n\r\nfunction transferAsync<T extends (...args: any[]) => any>(fun: T) {\r\n return function (\r\n ...rest: Parameters<T>\r\n ): ReturnType<T> | Promise<ReturnType<T>> {\r\n if (!isInit.value) {\r\n return new Promise<ReturnType<T>>((resolve) => {\r\n task.push(() => resolve(fun(...rest)))\r\n })\r\n }\r\n return fun(...rest)\r\n }\r\n}\r\n\r\nfunction validate() {\r\n const tempDiv = document.createElement('div')\r\n tempDiv.innerHTML = inputValue.value\r\n if (tempDiv.textContent?.trim()) return true\r\n if (tempDiv.querySelector('a.mce-item-anchor, img, table')) return true\r\n return false\r\n}\r\n\r\nwatch(\r\n () => inputValue.value,\r\n (newVal) => {\r\n if (!isInit.value || newVal === getContentSync()) return\r\n setContentSync(newVal)\r\n },\r\n)\r\n\r\nonMounted(() => {\r\n const tinymce = window.tinymce\r\n if (!tinymce) {\r\n console.error(\r\n '[RdRichEditor] window.tinymce is missing. Load TinyMCE 5.10.x via <script> before using RdRichEditor.',\r\n )\r\n return\r\n }\r\n if (props.tinymceBaseUrl) {\r\n tinymce.baseURL = props.tinymceBaseUrl\r\n }\r\n void tinymce.init(initOpts())\r\n})\r\n\r\nonBeforeUnmount(() => {\r\n editorCtx?.remove()\r\n editorCtx = null\r\n})\r\n\r\ndefineExpose({\r\n getEditor: transferAsync(() => editorCtx),\r\n getContent: transferAsync(getContentSync),\r\n setContent: transferAsync(setContentSync),\r\n insertContent: transferAsync(insertContentSync),\r\n validate,\r\n})\r\n</script>\r\n\r\n<template>\r\n <div class=\"rd-rich-editor\">\r\n <textarea :id=\"editorId\" style=\"visibility: hidden\" />\r\n <div v-if=\"isInit\" class=\"rd-rich-editor-footer-bar\">\r\n <slot name=\"footer-bar\" />\r\n </div>\r\n </div>\r\n</template>\r\n","<script setup lang=\"ts\">\r\ndefineOptions({\r\n name: 'RdRichEditor',\r\n})\r\n\r\nimport { onBeforeUnmount, onMounted, ref, watch } from 'vue'\r\nimport type { TinyMCEEditor } from '../../types/tinymce'\r\nimport { cleanObject } from '../../utils/cleanObject'\r\nimport useQuickTextPhrase, { type TextPhraseItem } from './useQuickTextPhrase'\r\nimport { setupDialogZIndexBridge } from './dialogZIndexBridge'\r\nimport '../../styles/rd-rich-editor.css'\r\n\r\ninterface EditorConfigProps {\r\n plugins?: string | string[]\r\n language?: string\r\n menubar?: string | string[]\r\n toolbar?: string | string[]\r\n toolbar_mode?: string\r\n min_height?: number\r\n height?: number\r\n /** Enable `#` quick text phrase autocompleter (requires fetchTextPhrases) */\r\n textPhrase?: boolean\r\n}\r\n\r\ntype Props = EditorConfigProps & {\r\n includes?: Pick<EditorConfigProps, 'plugins' | 'menubar' | 'toolbar'>\r\n excludes?: Pick<EditorConfigProps, 'plugins' | 'menubar' | 'toolbar'>\r\n /**\r\n * Image upload hook. Return a publicly accessible URL.\r\n * When omitted, paste/insert image upload fails with a clear message.\r\n */\r\n imagesUploadHandler?: (blob: Blob) => Promise<string>\r\n /** Fetch quick text phrases when textPhrase is true */\r\n fetchTextPhrases?: () => Promise<TextPhraseItem[]>\r\n /** Override TinyMCE baseURL when static assets are not at the default path */\r\n tinymceBaseUrl?: string\r\n /**\r\n * TinyMCE language pack URL (language_url).\r\n * Needed when langs/*.js are not under tinymceBaseUrl (e.g. CDN demos).\r\n */\r\n languageUrl?: string\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n language: 'zh_CN',\r\n textPhrase: false,\r\n})\r\n\r\nconst emits = defineEmits<{\r\n input: [e: Event]\r\n change: [value: string, e: unknown]\r\n}>()\r\n\r\nconst inputValue = defineModel<string>({ default: '' })\r\nconst editorId = `rd-editor_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`\r\nconst isInit = ref(false)\r\nlet editorCtx: TinyMCEEditor | null = null\r\n\r\nconst task: Array<() => void> = []\r\n\r\nsetupDialogZIndexBridge()\r\n\r\nfunction getContentSync() {\r\n return editorCtx?.getContent() ?? ''\r\n}\r\n\r\nfunction setContentSync(value: string) {\r\n const result = editorCtx?.setContent(value ?? '')\r\n inputValue.value = getContentSync()\r\n return result\r\n}\r\n\r\nfunction insertContentSync(value: string) {\r\n editorCtx?.insertContent(value)\r\n inputValue.value = getContentSync()\r\n}\r\n\r\n/** Append tokens to a space-separated TinyMCE config string (word match). */\r\nfunction addStr(source: string, target: string | string[]) {\r\n const list = Array.isArray(target) ? target : [target]\r\n let result = source\r\n list.forEach((token) => {\r\n const trimmed = token.trim()\r\n if (!trimmed) return\r\n const re = new RegExp(`(?:^|\\\\s)${escapeRegExp(trimmed)}(?:\\\\s|$)`)\r\n if (!re.test(result)) {\r\n result = `${result} ${trimmed}`.trim()\r\n }\r\n })\r\n return result\r\n}\r\n\r\n/** Remove tokens from a space-separated TinyMCE config string. */\r\nfunction removeStr(source: string, target: string | string[]) {\r\n const list = Array.isArray(target) ? target : [target]\r\n let result = source\r\n list.forEach((token) => {\r\n const trimmed = token.trim()\r\n if (!trimmed) return\r\n const re = new RegExp(`(?:^|\\\\s)${escapeRegExp(trimmed)}(?=\\\\s|$)`, 'g')\r\n result = result.replace(re, ' ').replace(/\\s+/g, ' ').trim()\r\n })\r\n return result\r\n}\r\n\r\nfunction escapeRegExp(s: string) {\r\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\r\n}\r\n\r\nfunction initOpts(): Record<string, unknown> {\r\n const {\r\n includes,\r\n excludes,\r\n textPhrase,\r\n imagesUploadHandler,\r\n fetchTextPhrases,\r\n tinymceBaseUrl,\r\n languageUrl,\r\n ...restProps\r\n } = props\r\n\r\n const language = props.language === 'zh-cn' ? 'zh_CN' : props.language\r\n\r\n const baseOpts: Record<string, unknown> = {\r\n selector: `textarea#${editorId}`,\r\n language,\r\n plugins:\r\n 'advlist autolink link image lists charmap emoticons preview hr anchor pagebreak nonbreaking code table paste insertdatetime wordcount',\r\n menubar: 'file edit view insert format tools',\r\n menu: {\r\n insert: {\r\n title: 'Insert',\r\n items:\r\n 'image link inserttable | charmap emoticons hr | pagebreak nonbreaking anchor | insertdatetime',\r\n },\r\n },\r\n toolbar:\r\n 'undo redo | styleselect | fontsizeselect fontselect | bold italic forecolor backcolor | alignleft aligncenter alignright alignjustify | outdent indent | bullist numlist table | link image | code',\r\n toolbar_mode: 'wrap',\r\n fontsize_formats:\r\n '5px 6px 7px 8px 9px 10px 12px 14px 16px 18px 20px 22px 24px 26px 28px 30px 32px 34px 36px 48px 72px',\r\n font_formats:\r\n 'Andale Mono=andale mono,times;Arial=arial,helvetica,sans-serif;Arial Black=arial black,avant garde;Book Antiqua=book antiqua,palatino;Calibri;Calibri Light;Comic Sans MS=comic sans ms,sans-serif;Courier New=courier new,courier;Georgia=georgia,palatino;Helvetica=helvetica;Impact=impact,chicago;Symbol=symbol;Tahoma=tahoma,arial,;Terminal=terminal,monaco;Times New Roman=times new roman,times;Trebuchet MS=trebuchet ms,geneva;Verdana=verdana,geneva;Webdings=webdings;Wingdings=wingdings,;',\r\n min_height: 480,\r\n table_default_styles: { width: '100%', borderCollapse: 'collapse' },\r\n image_advtab: true,\r\n image_title: false,\r\n paste_data_images: true,\r\n images_upload_handler: (\r\n blobInfo: { blob: () => Blob },\r\n success: (url: string) => void,\r\n fail: (msg: string) => void,\r\n ) => {\r\n if (!imagesUploadHandler) {\r\n fail('imagesUploadHandler is not provided')\r\n return\r\n }\r\n imagesUploadHandler(blobInfo.blob())\r\n .then((url) => {\r\n if (url) success(url)\r\n else fail('Upload failed')\r\n })\r\n .catch(() => fail('Upload failed'))\r\n },\r\n setup(editor: TinyMCEEditor) {\r\n editorCtx = editor\r\n editor.on('init', () => {\r\n isInit.value = true\r\n const body = editorCtx?.getBody()\r\n if (body) {\r\n body.style.fontSize = '16px'\r\n body.style.fontFamily = 'Calibri'\r\n }\r\n setContentSync(inputValue.value)\r\n while (task.length) {\r\n task.shift()?.()\r\n }\r\n })\r\n editor.on('change', (e) => {\r\n inputValue.value = getContentSync()\r\n emits('change', inputValue.value, e)\r\n })\r\n editor.on('input', (e) => {\r\n emits('input', e as Event)\r\n })\r\n if (textPhrase) {\r\n if (!fetchTextPhrases) {\r\n console.warn(\r\n '[RdRichEditor] textPhrase is true but fetchTextPhrases is not provided',\r\n )\r\n } else {\r\n useQuickTextPhrase(editor, fetchTextPhrases)\r\n }\r\n }\r\n },\r\n }\r\n\r\n if (tinymceBaseUrl) {\r\n baseOpts.base_url = tinymceBaseUrl\r\n }\r\n if (languageUrl) {\r\n baseOpts.language_url = languageUrl\r\n }\r\n\r\n const result: Record<string, unknown> = {\r\n ...baseOpts,\r\n ...cleanObject(restProps),\r\n }\r\n\r\n if (includes) {\r\n ;(Object.keys(includes) as Array<keyof typeof includes>).forEach((name) => {\r\n const current = result[name]\r\n const extra = includes[name]\r\n if (typeof current === 'string' && extra != null) {\r\n result[name] = addStr(current, extra)\r\n }\r\n })\r\n }\r\n if (excludes) {\r\n ;(Object.keys(excludes) as Array<keyof typeof excludes>).forEach((name) => {\r\n const current = result[name]\r\n const remove = excludes[name]\r\n if (typeof current === 'string' && remove != null) {\r\n result[name] = removeStr(current, remove)\r\n }\r\n })\r\n }\r\n\r\n return result\r\n}\r\n\r\nfunction transferAsync<T extends (...args: any[]) => any>(fun: T) {\r\n return function (\r\n ...rest: Parameters<T>\r\n ): ReturnType<T> | Promise<ReturnType<T>> {\r\n if (!isInit.value) {\r\n return new Promise<ReturnType<T>>((resolve) => {\r\n task.push(() => resolve(fun(...rest)))\r\n })\r\n }\r\n return fun(...rest)\r\n }\r\n}\r\n\r\nfunction validate() {\r\n const tempDiv = document.createElement('div')\r\n tempDiv.innerHTML = inputValue.value\r\n if (tempDiv.textContent?.trim()) return true\r\n if (tempDiv.querySelector('a.mce-item-anchor, img, table')) return true\r\n return false\r\n}\r\n\r\nwatch(\r\n () => inputValue.value,\r\n (newVal) => {\r\n if (!isInit.value || newVal === getContentSync()) return\r\n setContentSync(newVal)\r\n },\r\n)\r\n\r\nonMounted(() => {\r\n const tinymce = window.tinymce\r\n if (!tinymce) {\r\n console.error(\r\n '[RdRichEditor] window.tinymce is missing. Load TinyMCE 5.10.x via <script> before using RdRichEditor.',\r\n )\r\n return\r\n }\r\n if (props.tinymceBaseUrl) {\r\n tinymce.baseURL = props.tinymceBaseUrl\r\n }\r\n void tinymce.init(initOpts())\r\n})\r\n\r\nonBeforeUnmount(() => {\r\n editorCtx?.remove()\r\n editorCtx = null\r\n})\r\n\r\ndefineExpose({\r\n getEditor: transferAsync(() => editorCtx),\r\n getContent: transferAsync(getContentSync),\r\n setContent: transferAsync(setContentSync),\r\n insertContent: transferAsync(insertContentSync),\r\n validate,\r\n})\r\n</script>\r\n\r\n<template>\r\n <div class=\"rd-rich-editor\">\r\n <textarea :id=\"editorId\" style=\"visibility: hidden\" />\r\n <div v-if=\"isInit\" class=\"rd-rich-editor-footer-bar\">\r\n <slot name=\"footer-bar\" />\r\n </div>\r\n </div>\r\n</template>\r\n","import type { App } from 'vue'\r\nimport { RdRichEditor } from './components/rdRichEditor'\r\nexport type { TextPhraseItem } from './components/rdRichEditor'\r\n\r\nexport { RdRichEditor }\r\nexport { RodelEditorResolver } from './resolver'\r\n\r\nimport './styles/rd-rich-editor.css'\r\n\r\nconst components = [RdRichEditor]\r\n\r\n/**\r\n * Register RdRichEditor. Host must load TinyMCE 5.10.x on window.tinymce.\r\n */\r\nexport function install(app: App): void {\r\n components.forEach((component) => {\r\n const name = component.name || (component as { __name?: string }).__name\r\n if (name) {\r\n app.component(name, component)\r\n }\r\n })\r\n}\r\n\r\nexport default install\r\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolver.cjs","names":[],"sources":["../src/resolver.ts"],"sourcesContent":["import type { ComponentResolver } from './utils/resolverTypes'\r\n\r\n/** Resolver for unplugin-vue-components — resolves RdRichEditor from @rd-element/editor */\r\nexport function RodelEditorResolver(): ComponentResolver {\r\n return {\r\n type: 'component',\r\n resolve: (name: string) => {\r\n if (name !== 'RdRichEditor') return\r\n return {\r\n name: 'RdRichEditor',\r\n from: '@rd-element/editor',\r\n }\r\n },\r\n }\r\n}\r\n"],"mappings":"mEAGA,SAAgB,GAAyC,CACvD,MAAO,CACL,KAAM,YACN,QAAU,GAAiB,CACrB,OAAS,eACb,MAAO,CACL,KAAM,eACN,KAAM,oBACR,CACF,CACF,CACF"}
|
package/dist/resolver.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
//#region src/resolver.ts
|
|
2
|
+
function e() {
|
|
3
|
+
return {
|
|
4
|
+
type: "component",
|
|
5
|
+
resolve: (e) => {
|
|
6
|
+
if (e === "RdRichEditor") return {
|
|
7
|
+
name: "RdRichEditor",
|
|
8
|
+
from: "@rd-element/editor"
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
//#endregion
|
|
14
|
+
export { e as RodelEditorResolver };
|
|
15
|
+
|
|
16
|
+
//# sourceMappingURL=resolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolver.js","names":[],"sources":["../src/resolver.ts"],"sourcesContent":["import type { ComponentResolver } from './utils/resolverTypes'\r\n\r\n/** Resolver for unplugin-vue-components — resolves RdRichEditor from @rd-element/editor */\r\nexport function RodelEditorResolver(): ComponentResolver {\r\n return {\r\n type: 'component',\r\n resolve: (name: string) => {\r\n if (name !== 'RdRichEditor') return\r\n return {\r\n name: 'RdRichEditor',\r\n from: '@rd-element/editor',\r\n }\r\n },\r\n }\r\n}\r\n"],"mappings":";AAGA,SAAgB,IAAyC;CACvD,OAAO;EACL,MAAM;EACN,UAAU,MAAiB;GACrB,UAAS,gBACb,OAAO;IACL,MAAM;IACN,MAAM;GACR;EACF;CACF;AACF"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { TinyMCEEditor } from '../../types/tinymce';
|
|
2
|
+
import { TextPhraseItem } from './useQuickTextPhrase';
|
|
3
|
+
interface EditorConfigProps {
|
|
4
|
+
plugins?: string | string[];
|
|
5
|
+
language?: string;
|
|
6
|
+
menubar?: string | string[];
|
|
7
|
+
toolbar?: string | string[];
|
|
8
|
+
toolbar_mode?: string;
|
|
9
|
+
min_height?: number;
|
|
10
|
+
height?: number;
|
|
11
|
+
/** Enable `#` quick text phrase autocompleter (requires fetchTextPhrases) */
|
|
12
|
+
textPhrase?: boolean;
|
|
13
|
+
}
|
|
14
|
+
type Props = EditorConfigProps & {
|
|
15
|
+
includes?: Pick<EditorConfigProps, 'plugins' | 'menubar' | 'toolbar'>;
|
|
16
|
+
excludes?: Pick<EditorConfigProps, 'plugins' | 'menubar' | 'toolbar'>;
|
|
17
|
+
/**
|
|
18
|
+
* Image upload hook. Return a publicly accessible URL.
|
|
19
|
+
* When omitted, paste/insert image upload fails with a clear message.
|
|
20
|
+
*/
|
|
21
|
+
imagesUploadHandler?: (blob: Blob) => Promise<string>;
|
|
22
|
+
/** Fetch quick text phrases when textPhrase is true */
|
|
23
|
+
fetchTextPhrases?: () => Promise<TextPhraseItem[]>;
|
|
24
|
+
/** Override TinyMCE baseURL when static assets are not at the default path */
|
|
25
|
+
tinymceBaseUrl?: string;
|
|
26
|
+
/**
|
|
27
|
+
* TinyMCE language pack URL (language_url).
|
|
28
|
+
* Needed when langs/*.js are not under tinymceBaseUrl (e.g. CDN demos).
|
|
29
|
+
*/
|
|
30
|
+
languageUrl?: string;
|
|
31
|
+
};
|
|
32
|
+
type __VLS_Props = Props;
|
|
33
|
+
declare function validate(): boolean;
|
|
34
|
+
type __VLS_PublicProps = __VLS_Props & {
|
|
35
|
+
modelValue?: string;
|
|
36
|
+
};
|
|
37
|
+
declare var __VLS_1: {};
|
|
38
|
+
type __VLS_Slots = {} & {
|
|
39
|
+
'footer-bar'?: (props: typeof __VLS_1) => any;
|
|
40
|
+
};
|
|
41
|
+
declare const __VLS_component: import('vue').DefineComponent<__VLS_PublicProps, {
|
|
42
|
+
getEditor: () => TinyMCEEditor | Promise<TinyMCEEditor | null> | null;
|
|
43
|
+
getContent: () => string | Promise<string>;
|
|
44
|
+
setContent: (value: string) => string | Promise<string | undefined> | undefined;
|
|
45
|
+
insertContent: (value: string) => void | Promise<void>;
|
|
46
|
+
validate: typeof validate;
|
|
47
|
+
}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
|
|
48
|
+
input: (e: Event) => any;
|
|
49
|
+
change: (value: string, e: unknown) => any;
|
|
50
|
+
"update:modelValue": (value: string) => any;
|
|
51
|
+
}, string, import('vue').PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
52
|
+
onInput?: ((e: Event) => any) | undefined;
|
|
53
|
+
onChange?: ((value: string, e: unknown) => any) | undefined;
|
|
54
|
+
"onUpdate:modelValue"?: ((value: string) => any) | undefined;
|
|
55
|
+
}>, {
|
|
56
|
+
language: string;
|
|
57
|
+
textPhrase: boolean;
|
|
58
|
+
}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
|
|
59
|
+
declare const _default: __VLS_WithSlots<typeof __VLS_component, __VLS_Slots>;
|
|
60
|
+
export default _default;
|
|
61
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
62
|
+
new (): {
|
|
63
|
+
$slots: S;
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
//# sourceMappingURL=RdRichEditor.vue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RdRichEditor.vue.d.ts","sourceRoot":"","sources":["../../../../src/components/rdRichEditor/RdRichEditor.vue"],"names":[],"mappings":"AA+SA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAExD,OAA2B,EAAE,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAE9E,OAAO,iCAAiC,CAAA;AAExC,UAAU,iBAAiB;IACzB,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;IAC3B,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,6EAA6E;IAC7E,UAAU,CAAC,EAAE,OAAO,CAAA;CACrB;AAED,KAAK,KAAK,GAAG,iBAAiB,GAAG;IAC/B,QAAQ,CAAC,EAAE,IAAI,CAAC,iBAAiB,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC,CAAA;IACrE,QAAQ,CAAC,EAAE,IAAI,CAAC,iBAAiB,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC,CAAA;IACrE;;;OAGG;IACH,mBAAmB,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;IACrD,uDAAuD;IACvD,gBAAgB,CAAC,EAAE,MAAM,OAAO,CAAC,cAAc,EAAE,CAAC,CAAA;IAClD,8EAA8E;IAC9E,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,CAAA;AAED,KAAK,WAAW,GAAG,KAAK,CAAC;AA2MzB,iBAAS,QAAQ,YAMhB;AA+CD,KAAK,iBAAiB,GAAG,WAAW,GAAG;IACvC,UAAU,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AA2BF,QAAA,IAAI,OAAO,IAAW,CAAE;AACxB,KAAK,WAAW,GAAG,EAAE,GACnB;IAAE,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,OAAO,KAAK,GAAG,CAAA;CAAE,CAAC;AAuBpD,QAAA,MAAM,eAAe;;;;;;;;;;;;;;;cAnVR,MAAM;gBAOJ,OAAO;6EAqVpB,CAAC;wBACkB,eAAe,CAAC,OAAO,eAAe,EAAE,WAAW,CAAC;AAAzE,wBAA0E;AAa1E,KAAK,eAAe,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG;IAChC,QAAO;QACN,MAAM,EAAE,CAAC,CAAC;KAEV,CAAA;CACD,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dialogZIndexBridge.d.ts","sourceRoot":"","sources":["../../../../src/components/rdRichEditor/dialogZIndexBridge.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,wBAAgB,uBAAuB,IAAI,IAAI,CAgB9C"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/rdRichEditor/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAC5D,YAAY,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { TinyMCEEditor } from '../../types/tinymce';
|
|
2
|
+
export interface TextPhraseItem {
|
|
3
|
+
name: string;
|
|
4
|
+
input_code: string;
|
|
5
|
+
content: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Register TinyMCE autocompleter for `#` quick text phrases.
|
|
9
|
+
* Data is supplied by the host via `fetchTextPhrases`.
|
|
10
|
+
*/
|
|
11
|
+
export default function useQuickTextPhrase(editor: TinyMCEEditor, fetchTextPhrases: () => Promise<TextPhraseItem[]>): void;
|
|
12
|
+
//# sourceMappingURL=useQuickTextPhrase.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useQuickTextPhrase.d.ts","sourceRoot":"","sources":["../../../../src/components/rdRichEditor/useQuickTextPhrase.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAExD,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,EAAE,MAAM,CAAA;IAClB,OAAO,EAAE,MAAM,CAAA;CAChB;AAED;;;GAGG;AACH,MAAM,CAAC,OAAO,UAAU,kBAAkB,CACxC,MAAM,EAAE,aAAa,EACrB,gBAAgB,EAAE,MAAM,OAAO,CAAC,cAAc,EAAE,CAAC,QA0DlD"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { App } from 'vue';
|
|
2
|
+
import { RdRichEditor } from './components/rdRichEditor';
|
|
3
|
+
export type { TextPhraseItem } from './components/rdRichEditor';
|
|
4
|
+
export { RdRichEditor };
|
|
5
|
+
export { RodelEditorResolver } from './resolver';
|
|
6
|
+
/**
|
|
7
|
+
* Register RdRichEditor. Host must load TinyMCE 5.10.x on window.tinymce.
|
|
8
|
+
*/
|
|
9
|
+
export declare function install(app: App): void;
|
|
10
|
+
export default install;
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,KAAK,CAAA;AAC9B,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAA;AACxD,YAAY,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAA;AAE/D,OAAO,EAAE,YAAY,EAAE,CAAA;AACvB,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAA;AAEhD,OAAO,6BAA6B,CAAA;AAIpC;;GAEG;AACH,wBAAgB,OAAO,CAAC,GAAG,EAAE,GAAG,GAAG,IAAI,CAOtC;AAED,eAAe,OAAO,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolver.d.ts","sourceRoot":"","sources":["../../src/resolver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AAE9D,2FAA2F;AAC3F,wBAAgB,mBAAmB,IAAI,iBAAiB,CAWvD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cleanObject.d.ts","sourceRoot":"","sources":["../../../src/utils/cleanObject.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAAC,CAAC,CAAC,CAapE"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolverTypes.d.ts","sourceRoot":"","sources":["../../../src/utils/resolverTypes.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,WAAW,GAAG,WAAW,CAAA;IAC/B,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAClB;QACE,IAAI,EAAE,MAAM,CAAA;QACZ,IAAI,EAAE,MAAM,CAAA;QACZ,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;KAChC,GACD,SAAS,CAAA;CACd"}
|
package/dist/style.css
ADDED
|
@@ -0,0 +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}
|
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rd-element/editor",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "RD Element rich text editor (TinyMCE 5 wrapper) — RdRichEditor",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"files": [
|
|
7
|
+
"dist"
|
|
8
|
+
],
|
|
9
|
+
"sideEffects": [
|
|
10
|
+
"dist/style.css",
|
|
11
|
+
"**/*.css"
|
|
12
|
+
],
|
|
13
|
+
"main": "./dist/index.cjs",
|
|
14
|
+
"module": "./dist/index.js",
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"import": "./dist/index.js",
|
|
20
|
+
"require": "./dist/index.cjs"
|
|
21
|
+
},
|
|
22
|
+
"./resolver": {
|
|
23
|
+
"types": "./dist/resolver.d.ts",
|
|
24
|
+
"import": "./dist/resolver.js",
|
|
25
|
+
"require": "./dist/resolver.cjs"
|
|
26
|
+
},
|
|
27
|
+
"./dist/style.css": "./dist/style.css"
|
|
28
|
+
},
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
31
|
+
},
|
|
32
|
+
"keywords": [
|
|
33
|
+
"vue",
|
|
34
|
+
"vue3",
|
|
35
|
+
"tinymce",
|
|
36
|
+
"rich-text",
|
|
37
|
+
"editor",
|
|
38
|
+
"component-library"
|
|
39
|
+
],
|
|
40
|
+
"peerDependencies": {
|
|
41
|
+
"vue": "^3.5.0",
|
|
42
|
+
"element-plus": "^2.8.0"
|
|
43
|
+
},
|
|
44
|
+
"peerDependenciesMeta": {
|
|
45
|
+
"element-plus": {
|
|
46
|
+
"optional": true
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@types/node": "^22.13.10",
|
|
51
|
+
"@vitejs/plugin-vue": "^6.0.8",
|
|
52
|
+
"@vue/test-utils": "^2.4.6",
|
|
53
|
+
"element-plus": "^2.9.7",
|
|
54
|
+
"happy-dom": "^17.4.4",
|
|
55
|
+
"typescript": "^5.8.2",
|
|
56
|
+
"vite": "^8.1.5",
|
|
57
|
+
"vite-plugin-dts": "^5.0.3",
|
|
58
|
+
"vite-plugin-lib-inject-css": "^2.2.2",
|
|
59
|
+
"vitest": "^4.1.10",
|
|
60
|
+
"vue": "^3.5.13",
|
|
61
|
+
"vue-tsc": "^2.2.8"
|
|
62
|
+
},
|
|
63
|
+
"scripts": {
|
|
64
|
+
"build": "vue-tsc -p tsconfig.build.json --noEmit && vite build",
|
|
65
|
+
"typecheck": "vue-tsc -p tsconfig.json --noEmit",
|
|
66
|
+
"test": "vitest run"
|
|
67
|
+
}
|
|
68
|
+
}
|