mirror of
https://github.com/vbenjs/gf-vben-admin.git
synced 2025-01-24 04:10:20 +08:00
perf: review tinymce code
This commit is contained in:
parent
9c02d8ec08
commit
f75425d13b
@ -1,59 +1,169 @@
|
||||
<template>
|
||||
<div class="tinymce-container" :style="{ width: containerWidth }">
|
||||
<tinymce-editor
|
||||
:id="id"
|
||||
:init="initOptions"
|
||||
:modelValue="tinymceContent"
|
||||
@update:modelValue="handleChange"
|
||||
:tinymceScriptSrc="tinymceScriptSrc"
|
||||
></tinymce-editor>
|
||||
<textarea :id="tinymceId" visibility="hidden" ref="elRef"></textarea>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import TinymceEditor from './lib'; // TinyMCE vue wrapper
|
||||
import { defineComponent, computed } from 'vue';
|
||||
import {
|
||||
defineComponent,
|
||||
computed,
|
||||
onMounted,
|
||||
nextTick,
|
||||
ref,
|
||||
unref,
|
||||
watch,
|
||||
onUnmounted,
|
||||
onDeactivated,
|
||||
} from 'vue';
|
||||
import { basicProps } from './props';
|
||||
import toolbar from './toolbar';
|
||||
import plugins from './plugins';
|
||||
import { getTinymce } from './getTinymce';
|
||||
import { useScript } from '/@/hooks/web/useScript';
|
||||
import { snowUuid } from '/@/utils/uuid';
|
||||
import { bindHandlers } from './helper';
|
||||
|
||||
const CDN_URL = 'https://cdn.bootcdn.net/ajax/libs/tinymce/5.5.1';
|
||||
|
||||
const tinymceScriptSrc = `${CDN_URL}/tinymce.min.js`;
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Tinymce',
|
||||
components: { TinymceEditor },
|
||||
props: basicProps,
|
||||
setup(props, { emit }) {
|
||||
const tinymceContent = computed(() => {
|
||||
return props.value;
|
||||
emits: ['change', 'update:modelValue'],
|
||||
setup(props, { emit, attrs }) {
|
||||
const editorRef = ref<any>(null);
|
||||
const elRef = ref<Nullable<HTMLElement>>(null);
|
||||
|
||||
const tinymceId = computed(() => {
|
||||
return snowUuid('tiny-vue');
|
||||
});
|
||||
function handleChange(value: string) {
|
||||
emit('change', value);
|
||||
}
|
||||
|
||||
const tinymceContent = computed(() => {
|
||||
return props.modelValue;
|
||||
});
|
||||
|
||||
const containerWidth = computed(() => {
|
||||
const width = props.width;
|
||||
// Test matches `100`, `'100'`
|
||||
if (/^[\d]+(\.[\d]+)?$/.test(width.toString())) {
|
||||
return `${width}px`;
|
||||
}
|
||||
return width;
|
||||
});
|
||||
|
||||
const initOptions = computed(() => {
|
||||
const { id, height, menubar } = props;
|
||||
const { height, menubar } = props;
|
||||
return {
|
||||
selector: `#${id}`,
|
||||
selector: `#${unref(tinymceId)}`,
|
||||
height: height,
|
||||
toolbar: toolbar,
|
||||
theme: 'silver',
|
||||
menubar: menubar,
|
||||
plugins: plugins,
|
||||
// 语言包
|
||||
language_url: 'resource/tinymce/langs/zh_CN.js',
|
||||
// 中文
|
||||
language: 'zh_CN',
|
||||
default_link_target: '_blank',
|
||||
link_title: false,
|
||||
advlist_bullet_styles: 'square',
|
||||
advlist_number_styles: 'default',
|
||||
object_resizing: false,
|
||||
setup: (editor: any) => {
|
||||
editorRef.value = editor;
|
||||
editor.on('init', (e: Event) => initSetup(e));
|
||||
},
|
||||
};
|
||||
});
|
||||
return { containerWidth, initOptions, tinymceContent, handleChange, tinymceScriptSrc };
|
||||
|
||||
const { toPromise } = useScript({
|
||||
src: tinymceScriptSrc,
|
||||
});
|
||||
|
||||
watch(
|
||||
() => attrs.disabled,
|
||||
() => {
|
||||
const editor = unref(editorRef);
|
||||
if (!editor) return;
|
||||
editor.setMode(attrs.disabled ? 'readonly' : 'design');
|
||||
}
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
init();
|
||||
});
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
destory();
|
||||
});
|
||||
|
||||
onDeactivated(() => {
|
||||
destory();
|
||||
});
|
||||
|
||||
function destory() {
|
||||
if (getTinymce() !== null) {
|
||||
getTinymce().remove(unref(editorRef));
|
||||
}
|
||||
}
|
||||
|
||||
function init() {
|
||||
toPromise().then(() => {
|
||||
initEditor();
|
||||
});
|
||||
}
|
||||
|
||||
function initEditor() {
|
||||
getTinymce().init(unref(initOptions));
|
||||
}
|
||||
|
||||
function initSetup(e: Event) {
|
||||
const editor = unref(editorRef);
|
||||
if (!editor) return;
|
||||
const value = props.modelValue || '';
|
||||
|
||||
editor.setContent(value);
|
||||
bindModelHandlers(editor);
|
||||
bindHandlers(e, attrs, unref(editorRef));
|
||||
}
|
||||
|
||||
function bindModelHandlers(editor: any) {
|
||||
const modelEvents = attrs.modelEvents ? attrs.modelEvents : null;
|
||||
const normalizedEvents = Array.isArray(modelEvents) ? modelEvents.join(' ') : modelEvents;
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(val: string, prevVal: string) => {
|
||||
if (
|
||||
editor &&
|
||||
typeof val === 'string' &&
|
||||
val !== prevVal &&
|
||||
val !== editor.getContent({ format: attrs.outputFormat })
|
||||
) {
|
||||
editor.setContent(val);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
editor.on(normalizedEvents ? normalizedEvents : 'change keyup undo redo', () => {
|
||||
emit('update:modelValue', editor.getContent({ format: attrs.outputFormat }));
|
||||
});
|
||||
}
|
||||
|
||||
function handleChange(value: string) {
|
||||
emit('change', value);
|
||||
}
|
||||
return {
|
||||
containerWidth,
|
||||
initOptions,
|
||||
tinymceContent,
|
||||
handleChange,
|
||||
tinymceScriptSrc,
|
||||
elRef,
|
||||
tinymceId,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
@ -1,9 +1,6 @@
|
||||
const getGlobal = (): any => (typeof window !== 'undefined' ? window : global);
|
||||
|
||||
const getTinymce = () => {
|
||||
export const getTinymce = () => {
|
||||
const global = getGlobal();
|
||||
|
||||
return global && global.tinymce ? global.tinymce : null;
|
||||
};
|
||||
|
||||
export { getTinymce };
|
81
src/components/Tinymce/src/helper.ts
Normal file
81
src/components/Tinymce/src/helper.ts
Normal file
@ -0,0 +1,81 @@
|
||||
const validEvents = [
|
||||
'onActivate',
|
||||
'onAddUndo',
|
||||
'onBeforeAddUndo',
|
||||
'onBeforeExecCommand',
|
||||
'onBeforeGetContent',
|
||||
'onBeforeRenderUI',
|
||||
'onBeforeSetContent',
|
||||
'onBeforePaste',
|
||||
'onBlur',
|
||||
'onChange',
|
||||
'onClearUndos',
|
||||
'onClick',
|
||||
'onContextMenu',
|
||||
'onCopy',
|
||||
'onCut',
|
||||
'onDblclick',
|
||||
'onDeactivate',
|
||||
'onDirty',
|
||||
'onDrag',
|
||||
'onDragDrop',
|
||||
'onDragEnd',
|
||||
'onDragGesture',
|
||||
'onDragOver',
|
||||
'onDrop',
|
||||
'onExecCommand',
|
||||
'onFocus',
|
||||
'onFocusIn',
|
||||
'onFocusOut',
|
||||
'onGetContent',
|
||||
'onHide',
|
||||
'onInit',
|
||||
'onKeyDown',
|
||||
'onKeyPress',
|
||||
'onKeyUp',
|
||||
'onLoadContent',
|
||||
'onMouseDown',
|
||||
'onMouseEnter',
|
||||
'onMouseLeave',
|
||||
'onMouseMove',
|
||||
'onMouseOut',
|
||||
'onMouseOver',
|
||||
'onMouseUp',
|
||||
'onNodeChange',
|
||||
'onObjectResizeStart',
|
||||
'onObjectResized',
|
||||
'onObjectSelected',
|
||||
'onPaste',
|
||||
'onPostProcess',
|
||||
'onPostRender',
|
||||
'onPreProcess',
|
||||
'onProgressState',
|
||||
'onRedo',
|
||||
'onRemove',
|
||||
'onReset',
|
||||
'onSaveContent',
|
||||
'onSelectionChange',
|
||||
'onSetAttrib',
|
||||
'onSetContent',
|
||||
'onShow',
|
||||
'onSubmit',
|
||||
'onUndo',
|
||||
'onVisualAid',
|
||||
];
|
||||
|
||||
const isValidKey = (key: string) => validEvents.indexOf(key) !== -1;
|
||||
|
||||
export const bindHandlers = (initEvent: Event, listeners: any, editor: any): void => {
|
||||
Object.keys(listeners)
|
||||
.filter(isValidKey)
|
||||
.forEach((key: string) => {
|
||||
const handler = listeners[key];
|
||||
if (typeof handler === 'function') {
|
||||
if (key === 'onInit') {
|
||||
handler(initEvent, editor);
|
||||
} else {
|
||||
editor.on(key.substring(2), (e: any) => handler(e, editor));
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
@ -1,72 +0,0 @@
|
||||
import { uuid } from './Utils';
|
||||
|
||||
export type callbackFn = () => void;
|
||||
export interface IStateObj {
|
||||
listeners: callbackFn[];
|
||||
scriptId: string;
|
||||
scriptLoaded: boolean;
|
||||
}
|
||||
|
||||
const createState = (): IStateObj => {
|
||||
return {
|
||||
listeners: [],
|
||||
scriptId: uuid('tiny-script'),
|
||||
scriptLoaded: false
|
||||
};
|
||||
};
|
||||
|
||||
interface ScriptLoader {
|
||||
load: (doc: Document, url: string, callback: callbackFn) => void;
|
||||
reinitialize: () => void;
|
||||
}
|
||||
|
||||
const CreateScriptLoader = (): ScriptLoader => {
|
||||
let state: IStateObj = createState();
|
||||
|
||||
const injectScriptTag = (scriptId: string, doc: Document, url: string, callback: callbackFn) => {
|
||||
const scriptTag = doc.createElement('script');
|
||||
scriptTag.referrerPolicy = 'origin';
|
||||
scriptTag.type = 'application/javascript';
|
||||
scriptTag.id = scriptId;
|
||||
scriptTag.src = url;
|
||||
|
||||
const handler = () => {
|
||||
scriptTag.removeEventListener('load', handler);
|
||||
callback();
|
||||
};
|
||||
scriptTag.addEventListener('load', handler);
|
||||
if (doc.head) {
|
||||
doc.head.appendChild(scriptTag);
|
||||
}
|
||||
};
|
||||
|
||||
const load = (doc: Document, url: string, callback: callbackFn) => {
|
||||
if (state.scriptLoaded) {
|
||||
callback();
|
||||
} else {
|
||||
state.listeners.push(callback);
|
||||
if (!doc.getElementById(state.scriptId)) {
|
||||
injectScriptTag(state.scriptId, doc, url, () => {
|
||||
state.listeners.forEach((fn) => fn());
|
||||
state.scriptLoaded = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Only to be used by tests.
|
||||
const reinitialize = () => {
|
||||
state = createState();
|
||||
};
|
||||
|
||||
return {
|
||||
load,
|
||||
reinitialize
|
||||
};
|
||||
};
|
||||
|
||||
const ScriptLoader = CreateScriptLoader();
|
||||
|
||||
export {
|
||||
ScriptLoader
|
||||
};
|
@ -1,151 +0,0 @@
|
||||
import { ComponentPublicInstance } from 'vue';
|
||||
|
||||
const validEvents = [
|
||||
'onActivate',
|
||||
'onAddUndo',
|
||||
'onBeforeAddUndo',
|
||||
'onBeforeExecCommand',
|
||||
'onBeforeGetContent',
|
||||
'onBeforeRenderUI',
|
||||
'onBeforeSetContent',
|
||||
'onBeforePaste',
|
||||
'onBlur',
|
||||
'onChange',
|
||||
'onClearUndos',
|
||||
'onClick',
|
||||
'onContextMenu',
|
||||
'onCopy',
|
||||
'onCut',
|
||||
'onDblclick',
|
||||
'onDeactivate',
|
||||
'onDirty',
|
||||
'onDrag',
|
||||
'onDragDrop',
|
||||
'onDragEnd',
|
||||
'onDragGesture',
|
||||
'onDragOver',
|
||||
'onDrop',
|
||||
'onExecCommand',
|
||||
'onFocus',
|
||||
'onFocusIn',
|
||||
'onFocusOut',
|
||||
'onGetContent',
|
||||
'onHide',
|
||||
'onInit',
|
||||
'onKeyDown',
|
||||
'onKeyPress',
|
||||
'onKeyUp',
|
||||
'onLoadContent',
|
||||
'onMouseDown',
|
||||
'onMouseEnter',
|
||||
'onMouseLeave',
|
||||
'onMouseMove',
|
||||
'onMouseOut',
|
||||
'onMouseOver',
|
||||
'onMouseUp',
|
||||
'onNodeChange',
|
||||
'onObjectResizeStart',
|
||||
'onObjectResized',
|
||||
'onObjectSelected',
|
||||
'onPaste',
|
||||
'onPostProcess',
|
||||
'onPostRender',
|
||||
'onPreProcess',
|
||||
'onProgressState',
|
||||
'onRedo',
|
||||
'onRemove',
|
||||
'onReset',
|
||||
'onSaveContent',
|
||||
'onSelectionChange',
|
||||
'onSetAttrib',
|
||||
'onSetContent',
|
||||
'onShow',
|
||||
'onSubmit',
|
||||
'onUndo',
|
||||
'onVisualAid'
|
||||
];
|
||||
|
||||
const isValidKey = (key: string) => validEvents.indexOf(key) !== -1;
|
||||
|
||||
const bindHandlers = (initEvent: Event, listeners: any, editor: any): void => {
|
||||
Object.keys(listeners)
|
||||
.filter(isValidKey)
|
||||
.forEach((key: string) => {
|
||||
const handler = listeners[key];
|
||||
if (typeof handler === 'function') {
|
||||
if (key === 'onInit') {
|
||||
handler(initEvent, editor);
|
||||
} else {
|
||||
editor.on(key.substring(2), (e: any) => handler(e, editor));
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const bindModelHandlers = (ctx: ComponentPublicInstance, editor: any) => {
|
||||
const modelEvents = ctx.$props.modelEvents ? ctx.$props.modelEvents : null;
|
||||
const normalizedEvents = Array.isArray(modelEvents) ? modelEvents.join(' ') : modelEvents;
|
||||
// @ts-ignore
|
||||
ctx.$watch('modelValue', (val: string, prevVal: string) => {
|
||||
if (editor && typeof val === 'string' && val !== prevVal && val !== editor.getContent({ format: ctx.$props.outputFormat })) {
|
||||
editor.setContent(val);
|
||||
}
|
||||
});
|
||||
|
||||
editor.on(normalizedEvents ? normalizedEvents : 'change keyup undo redo', () => {
|
||||
ctx.$emit('update:modelValue', editor.getContent({ format: ctx.$props.outputFormat }));
|
||||
});
|
||||
};
|
||||
|
||||
const initEditor = (initEvent: Event, ctx: ComponentPublicInstance, editor: any) => {
|
||||
const value = ctx.$props.modelValue ? ctx.$props.modelValue : '';
|
||||
const initialValue = ctx.$props.initialValue ? ctx.$props.initialValue : '';
|
||||
|
||||
editor.setContent(value || initialValue);
|
||||
|
||||
// checks if the v-model shorthand is used (which sets an v-on:input listener) and then binds either
|
||||
// specified the events or defaults to "change keyup" event and emits the editor content on that event
|
||||
if (ctx.$attrs['onUpdate:modelValue']) {
|
||||
bindModelHandlers(ctx, editor);
|
||||
}
|
||||
|
||||
bindHandlers(initEvent, ctx.$attrs, editor);
|
||||
};
|
||||
|
||||
let unique = 0;
|
||||
|
||||
const uuid = (prefix: string): string => {
|
||||
const time = Date.now();
|
||||
const random = Math.floor(Math.random() * 1000000000);
|
||||
|
||||
unique++;
|
||||
|
||||
return prefix + '_' + random + unique + String(time);
|
||||
};
|
||||
|
||||
const isTextarea = (element: Element | null): element is HTMLTextAreaElement => {
|
||||
return element !== null && element.tagName.toLowerCase() === 'textarea';
|
||||
};
|
||||
|
||||
const normalizePluginArray = (plugins?: string | string[]): string[] => {
|
||||
if (typeof plugins === 'undefined' || plugins === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
return Array.isArray(plugins) ? plugins : plugins.split(' ');
|
||||
};
|
||||
|
||||
const mergePlugins = (initPlugins: string | string[], inputPlugins?: string | string[]) =>
|
||||
normalizePluginArray(initPlugins).concat(normalizePluginArray(inputPlugins));
|
||||
|
||||
const isNullOrUndefined = (value: any): value is null | undefined => value === null || value === undefined;
|
||||
|
||||
export {
|
||||
bindHandlers,
|
||||
bindModelHandlers,
|
||||
initEditor,
|
||||
uuid,
|
||||
isTextarea,
|
||||
mergePlugins,
|
||||
isNullOrUndefined
|
||||
};
|
@ -1,111 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2018-present, Ephox, Inc.
|
||||
*
|
||||
* This source code is licensed under the Apache 2 license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
*/
|
||||
|
||||
// import { ThisTypedComponentOptionsWithRecordProps } from 'vue/types/options';
|
||||
// import { CreateElement, Vue } from 'vue/types/vue';
|
||||
|
||||
import { ScriptLoader } from '../ScriptLoader';
|
||||
import { getTinymce } from '../TinyMCE';
|
||||
import { initEditor, isTextarea, mergePlugins, uuid, isNullOrUndefined } from '../Utils';
|
||||
import { editorProps, IPropTypes } from './EditorPropTypes';
|
||||
import { h, defineComponent, ComponentPublicInstance } from 'vue'
|
||||
|
||||
|
||||
export interface IEditor {
|
||||
$props: Partial<IPropTypes>
|
||||
}
|
||||
|
||||
declare module '@vue/runtime-core' {
|
||||
interface ComponentCustomProperties {
|
||||
elementId: string;
|
||||
element: Element | null;
|
||||
editor: any;
|
||||
inlineEditor: boolean;
|
||||
$props: Partial<IPropTypes>;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const renderInline = (id: string, tagName?: string) => {
|
||||
return h(tagName ? tagName : 'div', {
|
||||
id
|
||||
});
|
||||
};
|
||||
|
||||
const renderIframe = (id: string) => {
|
||||
return h('textarea', {
|
||||
id,
|
||||
visibility: 'hidden'
|
||||
});
|
||||
};
|
||||
|
||||
const initialise = (ctx: ComponentPublicInstance) => () => {
|
||||
const finalInit = {
|
||||
...ctx.$props.init,
|
||||
readonly: ctx.$props.disabled,
|
||||
selector: `#${ctx.elementId}`,
|
||||
plugins: mergePlugins(ctx.$props.init && ctx.$props.init.plugins, ctx.$props.plugins),
|
||||
toolbar: ctx.$props.toolbar || (ctx.$props.init && ctx.$props.init.toolbar),
|
||||
inline: ctx.inlineEditor,
|
||||
setup: (editor: any) => {
|
||||
ctx.editor = editor;
|
||||
editor.on('init', (e: Event) => initEditor(e, ctx, editor));
|
||||
|
||||
if (ctx.$props.init && typeof ctx.$props.init.setup === 'function') {
|
||||
ctx.$props.init.setup(editor);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (isTextarea(ctx.element)) {
|
||||
ctx.element.style.visibility = '';
|
||||
}
|
||||
|
||||
getTinymce().init(finalInit);
|
||||
};
|
||||
|
||||
export const Editor = defineComponent({
|
||||
props: editorProps,
|
||||
created() {
|
||||
this.elementId = this.$props.id || uuid('tiny-vue');
|
||||
this.inlineEditor = (this.$props.init && this.$props.init.inline) || this.$props.inline;
|
||||
},
|
||||
watch: {
|
||||
disabled() {
|
||||
(this as any).editor.setMode(this.disabled ? 'readonly' : 'design');
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.element = this.$el;
|
||||
|
||||
if (getTinymce() !== null) {
|
||||
initialise(this)();
|
||||
} else if (this.element && this.element.ownerDocument) {
|
||||
const channel = this.$props.cloudChannel ? this.$props.cloudChannel : '5';
|
||||
const apiKey = this.$props.apiKey ? this.$props.apiKey : 'no-api-key';
|
||||
|
||||
const scriptSrc = isNullOrUndefined(this.$props.tinymceScriptSrc) ?
|
||||
`https://cdn.tiny.cloud/1/${apiKey}/tinymce/${channel}/tinymce.min.js` :
|
||||
this.$props.tinymceScriptSrc;
|
||||
|
||||
ScriptLoader.load(
|
||||
this.element.ownerDocument,
|
||||
scriptSrc,
|
||||
initialise(this)
|
||||
);
|
||||
}
|
||||
},
|
||||
beforeUnmount() {
|
||||
if (getTinymce() !== null) {
|
||||
getTinymce().remove(this.editor);
|
||||
}
|
||||
},
|
||||
render() {
|
||||
return this.inlineEditor ? renderInline(this.elementId, this.$props.tagName) : renderIframe(this.elementId);
|
||||
}
|
||||
})
|
@ -1,46 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2018-present, Ephox, Inc.
|
||||
*
|
||||
* This source code is licensed under the Apache 2 license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
*/
|
||||
|
||||
export type CopyProps<T> = { [P in keyof T]: any };
|
||||
|
||||
export interface IPropTypes {
|
||||
apiKey: string;
|
||||
cloudChannel: string;
|
||||
id: string;
|
||||
init: any;
|
||||
initialValue: string;
|
||||
outputFormat: 'html' | 'text';
|
||||
inline: boolean;
|
||||
modelEvents: string[] | string;
|
||||
plugins: string[] | string;
|
||||
tagName: string;
|
||||
toolbar: string[] | string;
|
||||
modelValue: string;
|
||||
disabled: boolean;
|
||||
tinymceScriptSrc: string;
|
||||
}
|
||||
|
||||
export const editorProps: CopyProps<IPropTypes> = {
|
||||
apiKey: String,
|
||||
cloudChannel: String,
|
||||
id: String,
|
||||
init: Object,
|
||||
initialValue: String,
|
||||
inline: Boolean,
|
||||
modelEvents: [String, Array],
|
||||
plugins: [String, Array],
|
||||
tagName: String,
|
||||
toolbar: [String, Array],
|
||||
modelValue: String,
|
||||
disabled: Boolean,
|
||||
tinymceScriptSrc: String,
|
||||
outputFormat: {
|
||||
type: String,
|
||||
validator: (prop: string) => prop === 'html' || prop === 'text'
|
||||
},
|
||||
};
|
4
src/components/Tinymce/src/lib/global.d.ts
vendored
4
src/components/Tinymce/src/lib/global.d.ts
vendored
@ -1,4 +0,0 @@
|
||||
// Global compile-time constants
|
||||
declare var __DEV__: boolean
|
||||
declare var __BROWSER__: boolean
|
||||
declare var __CI__: boolean
|
@ -1,3 +0,0 @@
|
||||
import { Editor } from './components/Editor';
|
||||
|
||||
export default Editor;
|
@ -1,12 +1,6 @@
|
||||
import { PropType } from 'vue';
|
||||
|
||||
export const basicProps = {
|
||||
id: {
|
||||
type: String as PropType<string>,
|
||||
default: () => {
|
||||
return `tinymce-${new Date().getTime()}${(Math.random() * 1000).toFixed(0)}`;
|
||||
},
|
||||
},
|
||||
menubar: {
|
||||
type: String as PropType<string>,
|
||||
default: 'file edit insert view format table',
|
||||
@ -15,6 +9,10 @@ export const basicProps = {
|
||||
type: String as PropType<string>,
|
||||
// default: ''
|
||||
},
|
||||
modelValue: {
|
||||
type: String as PropType<string>,
|
||||
// default: ''
|
||||
},
|
||||
// 高度
|
||||
height: {
|
||||
type: [Number, String] as PropType<string | number>,
|
||||
|
@ -6,23 +6,23 @@ const menu: MenuModule = {
|
||||
path: '/charts',
|
||||
children: [
|
||||
{
|
||||
path: '/apexChart',
|
||||
path: 'apexChart',
|
||||
name: 'ApexChart',
|
||||
},
|
||||
{
|
||||
path: '/echarts',
|
||||
path: 'echarts',
|
||||
name: 'Echarts',
|
||||
children: [
|
||||
{
|
||||
path: '/map',
|
||||
path: 'map',
|
||||
name: '地图',
|
||||
},
|
||||
{
|
||||
path: '/line',
|
||||
path: 'line',
|
||||
name: '折线图',
|
||||
},
|
||||
{
|
||||
path: '/pie',
|
||||
path: 'pie',
|
||||
name: '饼图',
|
||||
},
|
||||
],
|
||||
|
@ -6,16 +6,16 @@ const menu: MenuModule = {
|
||||
path: '/comp',
|
||||
children: [
|
||||
{
|
||||
path: '/basic',
|
||||
path: 'basic',
|
||||
name: '基础组件',
|
||||
},
|
||||
{
|
||||
path: '/countTo',
|
||||
path: 'countTo',
|
||||
name: '数字动画',
|
||||
},
|
||||
|
||||
{
|
||||
path: '/scroll',
|
||||
path: 'scroll',
|
||||
name: '滚动组件',
|
||||
children: [
|
||||
{
|
||||
@ -33,53 +33,39 @@ const menu: MenuModule = {
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/modal',
|
||||
path: 'modal',
|
||||
name: '弹窗扩展',
|
||||
},
|
||||
{
|
||||
path: '/drawer',
|
||||
path: 'drawer',
|
||||
name: '抽屉扩展',
|
||||
},
|
||||
{
|
||||
path: '/desc',
|
||||
path: 'desc',
|
||||
name: '详情组件',
|
||||
},
|
||||
{
|
||||
path: '/verify',
|
||||
path: 'verify',
|
||||
name: '验证组件',
|
||||
children: [
|
||||
{
|
||||
path: '/drag',
|
||||
path: 'drag',
|
||||
name: '拖拽校验',
|
||||
},
|
||||
{
|
||||
path: '/rotate',
|
||||
path: 'rotate',
|
||||
name: '图片还原校验',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/qrcode',
|
||||
path: 'qrcode',
|
||||
name: '二维码组件',
|
||||
},
|
||||
{
|
||||
path: '/strength-meter',
|
||||
path: 'strength-meter',
|
||||
name: '密码强度组件',
|
||||
},
|
||||
{
|
||||
path: '/tinymce',
|
||||
name: '富文本',
|
||||
children: [
|
||||
{
|
||||
path: '/index',
|
||||
name: '基础使用',
|
||||
},
|
||||
{
|
||||
path: '/editor',
|
||||
name: '嵌入form使用',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
@ -6,9 +6,23 @@ const menu: MenuModule = {
|
||||
path: '/editor',
|
||||
children: [
|
||||
{
|
||||
path: '/markdown',
|
||||
path: 'markdown',
|
||||
name: 'markdown编辑器',
|
||||
},
|
||||
{
|
||||
path: 'tinymce',
|
||||
name: '富文本',
|
||||
children: [
|
||||
{
|
||||
path: 'index',
|
||||
name: '基础使用',
|
||||
},
|
||||
// {
|
||||
// path: 'editor',
|
||||
// name: '嵌入form使用',
|
||||
// },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
@ -6,23 +6,21 @@ const menu: MenuModule = {
|
||||
path: '/excel',
|
||||
children: [
|
||||
{
|
||||
path: '/customExport',
|
||||
path: 'customExport',
|
||||
name: '选择导出格式',
|
||||
},
|
||||
{
|
||||
path: '/jsonExport',
|
||||
path: 'jsonExport',
|
||||
name: 'JSON数据导出',
|
||||
},
|
||||
{
|
||||
path: '/arrayExport',
|
||||
path: 'arrayExport',
|
||||
name: 'Array数据导出',
|
||||
},
|
||||
{
|
||||
path: '/importExcel',
|
||||
path: 'importExcel',
|
||||
name: '导入',
|
||||
},
|
||||
// ],
|
||||
// },
|
||||
],
|
||||
},
|
||||
};
|
||||
|
@ -6,27 +6,27 @@ const menu: MenuModule = {
|
||||
path: '/exception',
|
||||
children: [
|
||||
{
|
||||
path: '/404',
|
||||
path: '404',
|
||||
name: '404',
|
||||
},
|
||||
{
|
||||
path: '/500',
|
||||
path: '500',
|
||||
name: '500',
|
||||
},
|
||||
{
|
||||
path: '/net-work-error',
|
||||
path: 'net-work-error',
|
||||
name: '网络错误',
|
||||
},
|
||||
{
|
||||
path: '/page-time-out',
|
||||
path: 'page-time-out',
|
||||
name: '页面超时',
|
||||
},
|
||||
{
|
||||
path: '/not-data',
|
||||
path: 'not-data',
|
||||
name: '无数据',
|
||||
},
|
||||
{
|
||||
path: '/error-log',
|
||||
path: 'error-log',
|
||||
name: '错误日志',
|
||||
},
|
||||
],
|
||||
|
@ -6,55 +6,55 @@ const menu: MenuModule = {
|
||||
path: '/feat',
|
||||
children: [
|
||||
{
|
||||
path: '/icon',
|
||||
path: 'icon',
|
||||
name: '图标',
|
||||
},
|
||||
{
|
||||
path: '/tabs',
|
||||
path: 'tabs',
|
||||
name: '标签页操作',
|
||||
},
|
||||
{
|
||||
path: '/context-menu',
|
||||
path: 'context-menu',
|
||||
name: '右键菜单',
|
||||
},
|
||||
{
|
||||
path: '/click-out-side',
|
||||
path: 'click-out-side',
|
||||
name: 'ClickOutSide',
|
||||
},
|
||||
{
|
||||
path: '/img-preview',
|
||||
path: 'img-preview',
|
||||
name: '图片预览',
|
||||
},
|
||||
{
|
||||
path: '/i18n',
|
||||
path: 'i18n',
|
||||
name: '国际化',
|
||||
},
|
||||
{
|
||||
path: '/copy',
|
||||
path: 'copy',
|
||||
name: '剪切板',
|
||||
},
|
||||
{
|
||||
path: '/msg',
|
||||
path: 'msg',
|
||||
name: '消息提示',
|
||||
},
|
||||
{
|
||||
path: '/watermark',
|
||||
path: 'watermark',
|
||||
name: '水印',
|
||||
},
|
||||
{
|
||||
path: '/full-screen',
|
||||
path: 'full-screen',
|
||||
name: '全屏',
|
||||
},
|
||||
{
|
||||
path: '/testTab',
|
||||
path: 'testTab',
|
||||
name: '带参Tab',
|
||||
children: [
|
||||
{
|
||||
path: '/id1',
|
||||
path: 'id1',
|
||||
name: '带参tab1',
|
||||
},
|
||||
{
|
||||
path: '/id2',
|
||||
path: 'id2',
|
||||
name: '带参tab2',
|
||||
},
|
||||
],
|
||||
|
@ -6,31 +6,31 @@ const menu: MenuModule = {
|
||||
name: 'Form',
|
||||
children: [
|
||||
{
|
||||
path: '/basic',
|
||||
path: 'basic',
|
||||
name: '基础表单',
|
||||
},
|
||||
{
|
||||
path: '/useForm',
|
||||
path: 'useForm',
|
||||
name: 'useForm',
|
||||
},
|
||||
{
|
||||
path: '/refForm',
|
||||
path: 'refForm',
|
||||
name: 'RefForm',
|
||||
},
|
||||
{
|
||||
path: '/advancedForm',
|
||||
path: 'advancedForm',
|
||||
name: '可收缩表单',
|
||||
},
|
||||
{
|
||||
path: '/ruleForm',
|
||||
path: 'ruleForm',
|
||||
name: '表单校验',
|
||||
},
|
||||
{
|
||||
path: '/dynamicForm',
|
||||
path: 'dynamicForm',
|
||||
name: '动态表单',
|
||||
},
|
||||
{
|
||||
path: '/customerForm',
|
||||
path: 'customerForm',
|
||||
name: '自定义组件',
|
||||
},
|
||||
],
|
||||
|
@ -6,15 +6,15 @@ const menu: MenuModule = {
|
||||
path: '/frame',
|
||||
children: [
|
||||
{
|
||||
path: '/antv',
|
||||
path: 'antv',
|
||||
name: 'antVue文档(内嵌)',
|
||||
},
|
||||
{
|
||||
path: '/doc',
|
||||
path: 'doc',
|
||||
name: '项目文档(内嵌)',
|
||||
},
|
||||
{
|
||||
path: '/docExternal',
|
||||
path: 'docExternal',
|
||||
name: '项目文档(外链)',
|
||||
},
|
||||
],
|
||||
|
@ -6,37 +6,37 @@ const menu: MenuModule = {
|
||||
path: '/permission',
|
||||
children: [
|
||||
{
|
||||
path: '/front',
|
||||
path: 'front',
|
||||
name: '基于前端',
|
||||
children: [
|
||||
{
|
||||
path: '/page',
|
||||
path: 'page',
|
||||
name: '页面权限',
|
||||
},
|
||||
{
|
||||
path: '/btn',
|
||||
path: 'btn',
|
||||
name: '按钮权限',
|
||||
},
|
||||
{
|
||||
path: '/auth-pageA',
|
||||
path: 'auth-pageA',
|
||||
name: '权限测试页A',
|
||||
},
|
||||
{
|
||||
path: '/auth-pageB',
|
||||
path: 'auth-pageB',
|
||||
name: '权限测试页B',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/back',
|
||||
path: 'back',
|
||||
name: '基于后台',
|
||||
children: [
|
||||
{
|
||||
path: '/page',
|
||||
path: 'page',
|
||||
name: '页面权限',
|
||||
},
|
||||
{
|
||||
path: '/btn',
|
||||
path: 'btn',
|
||||
name: '按钮权限',
|
||||
},
|
||||
],
|
||||
|
@ -6,59 +6,59 @@ const menu: MenuModule = {
|
||||
name: 'Table',
|
||||
children: [
|
||||
{
|
||||
path: '/basic',
|
||||
path: 'basic',
|
||||
name: '基础表格',
|
||||
},
|
||||
{
|
||||
path: '/treeTable',
|
||||
path: 'treeTable',
|
||||
name: '树形表格',
|
||||
},
|
||||
{
|
||||
path: '/fetchTable',
|
||||
path: 'fetchTable',
|
||||
name: '远程加载',
|
||||
},
|
||||
{
|
||||
path: '/fixedColumn',
|
||||
path: 'fixedColumn',
|
||||
name: '固定列',
|
||||
},
|
||||
{
|
||||
path: '/customerCell',
|
||||
path: 'customerCell',
|
||||
name: '自定义列',
|
||||
},
|
||||
{
|
||||
path: '/formTable',
|
||||
path: 'formTable',
|
||||
name: '开启搜索区域',
|
||||
},
|
||||
{
|
||||
path: '/useTable',
|
||||
path: 'useTable',
|
||||
name: 'UseTable',
|
||||
},
|
||||
{
|
||||
path: '/refTable',
|
||||
path: 'refTable',
|
||||
name: 'RefTable',
|
||||
},
|
||||
{
|
||||
path: '/multipleHeader',
|
||||
path: 'multipleHeader',
|
||||
name: '多级表头',
|
||||
},
|
||||
{
|
||||
path: '/mergeHeader',
|
||||
path: 'mergeHeader',
|
||||
name: '合并单元格',
|
||||
},
|
||||
{
|
||||
path: '/expandTable',
|
||||
path: 'expandTable',
|
||||
name: '可展开表格',
|
||||
},
|
||||
{
|
||||
path: '/fixedHeight',
|
||||
path: 'fixedHeight',
|
||||
name: '定高/头部自定义',
|
||||
},
|
||||
{
|
||||
path: '/footerTable',
|
||||
path: 'footerTable',
|
||||
name: '表尾行合计',
|
||||
},
|
||||
{
|
||||
path: '/editCellTable',
|
||||
path: 'editCellTable',
|
||||
name: '可编辑单元格',
|
||||
},
|
||||
],
|
||||
|
@ -136,31 +136,5 @@ export default {
|
||||
title: '密码强度组件',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/tinymce',
|
||||
name: 'TinymceDemo',
|
||||
meta: {
|
||||
title: '富文本',
|
||||
},
|
||||
redirect: '/comp/tinymce/index',
|
||||
children: [
|
||||
{
|
||||
path: 'index',
|
||||
name: 'Tinymce',
|
||||
component: () => import('/@/views/demo/comp/tinymce/index.vue'),
|
||||
meta: {
|
||||
title: '基础使用',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'editor',
|
||||
name: 'TinymceEditor',
|
||||
component: () => import('/@/views/demo/comp/tinymce/Editor.vue'),
|
||||
meta: {
|
||||
title: '嵌入form使用',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
} as AppRouteModule;
|
||||
|
@ -23,5 +23,32 @@ export default {
|
||||
title: 'markdown编辑器',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/tinymce',
|
||||
name: 'TinymceDemo',
|
||||
meta: {
|
||||
title: '富文本',
|
||||
},
|
||||
redirect: '/editor/tinymce/index',
|
||||
children: [
|
||||
{
|
||||
path: 'index',
|
||||
name: 'TinymceBasicDemo',
|
||||
component: () => import('/@/views/demo/editor/tinymce/index.vue'),
|
||||
meta: {
|
||||
title: '基础使用',
|
||||
},
|
||||
},
|
||||
// TODO
|
||||
// {
|
||||
// path: 'editor',
|
||||
// name: 'TinymceFormDemo',
|
||||
// component: () => import('/@/views/demo/comp/tinymce/Editor.vue'),
|
||||
// meta: {
|
||||
// title: '嵌入form使用',
|
||||
// },
|
||||
// },
|
||||
],
|
||||
},
|
||||
],
|
||||
} as AppRouteModule;
|
||||
|
@ -67,3 +67,7 @@ export const isServer = typeof window === 'undefined';
|
||||
export function isImageDom(o: Element) {
|
||||
return o && ['IMAGE', 'IMG'].includes(o.tagName);
|
||||
}
|
||||
|
||||
export const isTextarea = (element: Element | null): element is HTMLTextAreaElement => {
|
||||
return element !== null && element.tagName.toLowerCase() === 'textarea';
|
||||
};
|
||||
|
@ -17,3 +17,11 @@ export function buildUUID(): string {
|
||||
}
|
||||
return uuid.replace(/-/g, '');
|
||||
}
|
||||
|
||||
let unique = 0;
|
||||
export function snowUuid(prefix: string): string {
|
||||
const time = Date.now();
|
||||
const random = Math.floor(Math.random() * 1000000000);
|
||||
unique++;
|
||||
return prefix + '_' + random + unique + String(time);
|
||||
}
|
||||
|
@ -43,7 +43,7 @@
|
||||
},
|
||||
];
|
||||
export default defineComponent({
|
||||
components: { BasicForm, CollapseContainer, Tinymce },
|
||||
components: { BasicForm, CollapseContainer },
|
||||
setup() {
|
||||
const { createMessage } = useMessage();
|
||||
|
@ -1,19 +1,24 @@
|
||||
<template>
|
||||
<div class="flex p-4">
|
||||
<Tinymce value="Hello, World!" @change="handleChange" width="100%" />
|
||||
{{ value }}
|
||||
<Tinymce v-model="value" @change="handleChange" width="100%" />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { defineComponent, ref } from 'vue';
|
||||
import { Tinymce } from '/@/components/Tinymce/index';
|
||||
|
||||
export default defineComponent({
|
||||
components: { Tinymce },
|
||||
setup() {
|
||||
const value = ref('hello world!');
|
||||
function handleChange(value: string) {
|
||||
console.log(value);
|
||||
}
|
||||
return { handleChange };
|
||||
// setTimeout(() => {
|
||||
// value.value = '1233';
|
||||
// }, 5000);
|
||||
return { handleChange, value };
|
||||
},
|
||||
});
|
||||
</script>
|
Loading…
Reference in New Issue
Block a user