Skip to content

Commit

Permalink
feat: selfhost tinymce
Browse files Browse the repository at this point in the history
  • Loading branch information
rotimi-best committed May 14, 2024
1 parent e243564 commit a657cf8
Show file tree
Hide file tree
Showing 133 changed files with 7,367 additions and 47 deletions.
3 changes: 0 additions & 3 deletions apps/dashboard/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,6 @@ PRIVATE_SUPABASE_SERVICE_ROLE=
# Unsplash setup - Optional if you want to use Unsplash images
UNSPLASH_API_KEY=

PUBLIC_TINYMCE_API_KEY=


VITE_SENTRY_AUTH_TOKEN=
VITE_SENTRY_ORG_NAME=
VITE_SENTRY_PROJECT_NAME=
Expand Down
1 change: 0 additions & 1 deletion apps/dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
"@sveltejs/adapter-auto": "^2.1.0",
"@sveltejs/adapter-vercel": "^3.0.3",
"@tailwindcss/forms": "^0.5.4",
"@tinymce/tinymce-svelte": "^1.0.1",
"@types/pluralize": "0.0.30",
"@vercel/speed-insights": "^1.0.1",
"ai": "^2.1.31",
Expand Down
Original file line number Diff line number Diff line change
@@ -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 };
Original file line number Diff line number Diff line change
@@ -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>
15 changes: 9 additions & 6 deletions apps/dashboard/src/lib/components/TextEditor/index.svelte
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -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>
9 changes: 1 addition & 8 deletions apps/dashboard/src/lib/utils/functions/tinymce/plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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.');
}
Expand Down
5 changes: 0 additions & 5 deletions apps/dashboard/src/routes/courses/[id]/lessons/+layout.svelte

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions apps/dashboard/static/js/tinymce/langs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
This is where language files should be placed.

Please DO NOT translate these directly, use this service instead: https://crowdin.com/project/tinymce
6 changes: 6 additions & 0 deletions apps/dashboard/static/js/tinymce/license.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Software License Agreement

**TinyMCE**[<https://github.com/tinymce/tinymce>](https://github.com/tinymce/tinymce)
Copyright (c) 2024, Ephox Corporation DBA Tiny Technologies, Inc.

Licensed under the terms of [GNU General Public License Version 2 or later](http://www.gnu.org/licenses/gpl.html).
4 changes: 4 additions & 0 deletions apps/dashboard/static/js/tinymce/models/dom/model.min.js

Large diffs are not rendered by default.

Loading

0 comments on commit a657cf8

Please sign in to comment.