feat: selfhost tinymce

This commit is contained in:
Rotimi Best
2024-05-14 15:22:30 +01:00
parent e243564113
commit a657cf8da0
133 changed files with 7367 additions and 47 deletions
@@ -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/[email protected]/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>&nbsp;</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/[email protected]/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;