feat: selfhost tinymce
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
const validEvents = [
|
||||
'Activate',
|
||||
'AddUndo',
|
||||
'BeforeAddUndo',
|
||||
'BeforeExecCommand',
|
||||
'BeforeGetContent',
|
||||
'BeforeRenderUI',
|
||||
'BeforeSetContent',
|
||||
'BeforePaste',
|
||||
'Blur',
|
||||
'Change',
|
||||
'ClearUndos',
|
||||
'Click',
|
||||
'ContextMenu',
|
||||
'Copy',
|
||||
'Cut',
|
||||
'Dblclick',
|
||||
'Deactivate',
|
||||
'Dirty',
|
||||
'Drag',
|
||||
'DragDrop',
|
||||
'DragEnd',
|
||||
'DragGesture',
|
||||
'DragOver',
|
||||
'Drop',
|
||||
'ExecCommand',
|
||||
'Focus',
|
||||
'FocusIn',
|
||||
'FocusOut',
|
||||
'GetContent',
|
||||
'Hide',
|
||||
'Init',
|
||||
'KeyDown',
|
||||
'KeyPress',
|
||||
'KeyUp',
|
||||
'LoadContent',
|
||||
'MouseDown',
|
||||
'MouseEnter',
|
||||
'MouseLeave',
|
||||
'MouseMove',
|
||||
'MouseOut',
|
||||
'MouseOver',
|
||||
'MouseUp',
|
||||
'NodeChange',
|
||||
'ObjectResizeStart',
|
||||
'ObjectResized',
|
||||
'ObjectSelected',
|
||||
'Paste',
|
||||
'PostProcess',
|
||||
'PostRender',
|
||||
'PreProcess',
|
||||
'ProgressState',
|
||||
'Redo',
|
||||
'Remove',
|
||||
'Reset',
|
||||
'ResizeEditor',
|
||||
'SaveContent',
|
||||
'SelectionChange',
|
||||
'SetAttrib',
|
||||
'SetContent',
|
||||
'Show',
|
||||
'Submit',
|
||||
'Undo',
|
||||
'VisualAid'
|
||||
];
|
||||
|
||||
const bindHandlers = (editor, dispatch) => {
|
||||
validEvents.forEach((eventName) => {
|
||||
editor.on(eventName, (e) => {
|
||||
dispatch(eventName.toLowerCase(), {
|
||||
eventName,
|
||||
event: e,
|
||||
editor
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const injectTiny = (doc, url, cb) => {
|
||||
const script = doc.createElement('script');
|
||||
script.referrerPolicy = 'origin';
|
||||
script.type = 'application/javascript';
|
||||
script.src = url;
|
||||
script.onload = cb;
|
||||
if (doc.head) {
|
||||
doc.head.appendChild(script);
|
||||
}
|
||||
};
|
||||
|
||||
export { bindHandlers, injectTiny };
|
||||
@@ -0,0 +1,160 @@
|
||||
<script lang="ts" context="module">
|
||||
const uuid = (prefix: string): string => {
|
||||
return prefix + '_' + Math.floor(Math.random() * 1000000000) + String(Date.now());
|
||||
};
|
||||
|
||||
const createScriptLoader = () => {
|
||||
let state: {
|
||||
listeners: { (): void }[];
|
||||
scriptId: string;
|
||||
scriptLoaded: boolean;
|
||||
injected: boolean;
|
||||
} = {
|
||||
listeners: [],
|
||||
scriptId: uuid('tiny-script'),
|
||||
scriptLoaded: false,
|
||||
injected: false
|
||||
};
|
||||
|
||||
const injectScript = (scriptId: string, doc: Document, url: string, cb: () => void) => {
|
||||
state.injected = true;
|
||||
const script = doc.createElement('script');
|
||||
script.referrerPolicy = 'origin';
|
||||
script.type = 'application/javascript';
|
||||
script.src = url;
|
||||
script.onload = () => {
|
||||
cb();
|
||||
};
|
||||
if (doc.head) doc.head.appendChild(script);
|
||||
};
|
||||
|
||||
const load = (doc: Document, url: string, callback: () => void) => {
|
||||
if (state.scriptLoaded) {
|
||||
callback();
|
||||
} else {
|
||||
state.listeners.push(callback);
|
||||
// check we can access doc
|
||||
if (!state.injected) {
|
||||
injectScript(state.scriptId, doc, url, () => {
|
||||
state.listeners.forEach((fn) => fn());
|
||||
state.scriptLoaded = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
load
|
||||
};
|
||||
};
|
||||
let scriptLoader = createScriptLoader();
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { onMount, createEventDispatcher, onDestroy } from 'svelte';
|
||||
import { bindHandlers } from './Utils';
|
||||
export let id: string = uuid('tinymce-svelte'); // default values
|
||||
export let inline: boolean | undefined = undefined;
|
||||
export let disabled: boolean = false;
|
||||
// export let apiKey: string = 'no-api-key';
|
||||
// export let channel: string = '6';
|
||||
// export let scriptSrc: string | undefined = undefined;
|
||||
export let conf: any = {};
|
||||
export let modelEvents: string = 'change input undo redo';
|
||||
export let value: string = '';
|
||||
export let text: string = '';
|
||||
export let cssClass: string = 'tinymce-wrapper';
|
||||
|
||||
let container: HTMLElement;
|
||||
let element: HTMLElement;
|
||||
let editorRef: any;
|
||||
|
||||
let lastVal = value;
|
||||
let disablindCache = disabled;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
$: {
|
||||
if (editorRef && lastVal !== value) {
|
||||
editorRef.setContent(value);
|
||||
text = editorRef.getContent({ format: 'text' });
|
||||
}
|
||||
if (editorRef && disabled !== disablindCache) {
|
||||
disablindCache = disabled;
|
||||
if (typeof editorRef.mode?.set === 'function') {
|
||||
editorRef.mode.set(disabled ? 'readonly' : 'design');
|
||||
} else {
|
||||
editorRef.setMode(disabled ? 'readonly' : 'design');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const getTinymce = () => {
|
||||
const getSink = () => {
|
||||
return typeof window !== 'undefined' ? window : global;
|
||||
};
|
||||
const sink = getSink();
|
||||
return sink && sink.tinymce ? sink.tinymce : null;
|
||||
};
|
||||
|
||||
const init = () => {
|
||||
//
|
||||
const finalInit = {
|
||||
...conf,
|
||||
target: element,
|
||||
inline: inline !== undefined ? inline : conf.inline !== undefined ? conf.inline : false,
|
||||
readonly: disabled,
|
||||
setup: (editor: any) => {
|
||||
editorRef = editor;
|
||||
editor.on('init', () => {
|
||||
editor.setContent(value);
|
||||
// bind model events
|
||||
editor.on(modelEvents, () => {
|
||||
lastVal = editor.getContent();
|
||||
if (lastVal !== value) {
|
||||
value = lastVal;
|
||||
text = editor.getContent({ format: 'text' });
|
||||
}
|
||||
});
|
||||
});
|
||||
bindHandlers(editor, dispatch);
|
||||
if (typeof conf.setup === 'function') {
|
||||
conf.setup(editor);
|
||||
}
|
||||
}
|
||||
};
|
||||
element.style.visibility = '';
|
||||
getTinymce().init(finalInit);
|
||||
};
|
||||
|
||||
onMount(() => {
|
||||
if (getTinymce() !== null) {
|
||||
init();
|
||||
} else {
|
||||
const script = window.location.origin + '/js/tinymce/tinymce.min.js';
|
||||
scriptLoader.load(container.ownerDocument, script, () => {
|
||||
init();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
if (editorRef) {
|
||||
getTinymce()?.remove(editorRef);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<div bind:this={container} class={cssClass}>
|
||||
{#if inline}
|
||||
<div {id} bind:this={element}></div>
|
||||
{:else}
|
||||
<textarea {id} bind:this={element} style="visibility:hidden"></textarea>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
:global(.tox .tox-promotion) {
|
||||
display: none !important;
|
||||
}
|
||||
</style>
|
||||
@@ -1,6 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { PUBLIC_TINYMCE_API_KEY } from '$env/static/public';
|
||||
import Editor from '@tinymce/tinymce-svelte';
|
||||
import Editor from './TinymceSvelte/index.svelte';
|
||||
import { globalStore } from '$lib/utils/store/app';
|
||||
import { addMathPlugin } from '$lib/utils/functions/tinymce/plugins';
|
||||
|
||||
@@ -12,8 +11,6 @@
|
||||
export let editorWindowRef: Window | undefined = undefined;
|
||||
export let maxHeight: number | undefined = undefined;
|
||||
|
||||
const apiKey = PUBLIC_TINYMCE_API_KEY;
|
||||
|
||||
let unmount = false;
|
||||
let editorChangeHandlerId;
|
||||
|
||||
@@ -22,7 +19,9 @@
|
||||
return typeof window !== 'undefined' ? window : global;
|
||||
};
|
||||
const sink = getSink();
|
||||
return sink && sink.tinymce ? sink.tinymce : null;
|
||||
const res = sink && sink.tinymce ? sink.tinymce : null;
|
||||
console.log({ res });
|
||||
return res;
|
||||
}
|
||||
|
||||
// editor configuration
|
||||
@@ -93,8 +92,12 @@
|
||||
$: handleModeChange($globalStore.isDark);
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<link rel="stylesheet" href="https://unpkg.com/katex@0.12.0/dist/katex.min.css" />
|
||||
</svelte:head>
|
||||
|
||||
<div>
|
||||
{#if !unmount}
|
||||
<Editor bind:value {apiKey} bind:conf />
|
||||
<Editor bind:value bind:conf />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -75,13 +75,6 @@ export const addMathPlugin = (tinymce: any) => {
|
||||
name: 'submitButton',
|
||||
text: 'Ok',
|
||||
buttonType: 'primary'
|
||||
},
|
||||
{
|
||||
type: 'custom',
|
||||
name: 'logo',
|
||||
text: '',
|
||||
icon: 'logo',
|
||||
align: 'start'
|
||||
}
|
||||
],
|
||||
onSubmit: async function (w) {
|
||||
@@ -104,7 +97,7 @@ export const addMathPlugin = (tinymce: any) => {
|
||||
const html = await response.text();
|
||||
|
||||
// 4. Set the content to the html
|
||||
content = `<p>${html}</p><p> </p>`;
|
||||
content = html;
|
||||
} else {
|
||||
console.error('No src attribute found in the img tag.');
|
||||
}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
<svelte:head>
|
||||
<link rel="stylesheet" href="https://unpkg.com/katex@0.12.0/dist/katex.min.css" />
|
||||
</svelte:head>
|
||||
|
||||
<slot />
|
||||
@@ -15,8 +15,6 @@
|
||||
import { fetchCourses } from '$lib/components/Courses/api';
|
||||
import { t } from '$lib/utils/functions/translations.js';
|
||||
|
||||
export let data;
|
||||
|
||||
let errors: {
|
||||
title?: string;
|
||||
courseId?: string;
|
||||
|
||||
Reference in New Issue
Block a user