forked from vbenjs/vben-admin-thin-next
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
19 changed files
with
1,047 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,3 +5,5 @@ | |
|
||
**/*.svg | ||
**/*.sh | ||
|
||
/public/* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
/dist/* | ||
/public/* |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { default as Tinymce } from './src/Editor.vue'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
<template> | ||
<div class="tinymce-container" :style="{ width: containerWidth }"> | ||
<tinymce-editor | ||
:id="id" | ||
:init="initOptions" | ||
:modelValue="tinymceContent" | ||
@update:modelValue="handleChange" | ||
:tinymceScriptSrc="tinymceScriptSrc" | ||
></tinymce-editor> | ||
</div> | ||
</template> | ||
|
||
<script lang="ts"> | ||
import TinymceEditor from './lib'; // TinyMCE vue wrapper | ||
import { defineComponent, computed } from 'vue'; | ||
import { basicProps } from './props'; | ||
import toolbar from './toolbar'; | ||
import plugins from './plugins'; | ||
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; | ||
}); | ||
function handleChange(value: string) { | ||
emit('change', value); | ||
} | ||
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; | ||
return { | ||
selector: `#${id}`, | ||
height: height, | ||
toolbar: toolbar, | ||
menubar: menubar, | ||
plugins: plugins, | ||
// 语言包 | ||
language_url: 'resource/tinymce/langs/zh_CN.js', | ||
// 中文 | ||
language: 'zh_CN', | ||
}; | ||
}); | ||
return { containerWidth, initOptions, tinymceContent, handleChange, tinymceScriptSrc }; | ||
}, | ||
}); | ||
</script> | ||
|
||
<style lang="less" scoped> | ||
.tinymce-container { | ||
position: relative; | ||
line-height: normal; | ||
.mce-fullscreen { | ||
z-index: 10000; | ||
} | ||
} | ||
.editor-custom-btn-container { | ||
position: absolute; | ||
top: 6px; | ||
right: 6px; | ||
&.fullscreen { | ||
position: fixed; | ||
z-index: 10000; | ||
} | ||
} | ||
.editor-upload-btn { | ||
display: inline-block; | ||
} | ||
textarea { | ||
z-index: -1; | ||
visibility: hidden; | ||
} | ||
</style> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
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 | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
const getGlobal = (): any => (typeof window !== 'undefined' ? window : global); | ||
|
||
const getTinymce = () => { | ||
const global = getGlobal(); | ||
|
||
return global && global.tinymce ? global.tinymce : null; | ||
}; | ||
|
||
export { getTinymce }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
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 | ||
}; |
Oops, something went wrong.