26 changed files with 349 additions and 534 deletions
@ -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 }; |
|||
@ -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' |
|||
}, |
|||
}; |
|||
@ -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,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