feat: add math equation support in Tinymce (#233)
This commit is contained in:
@@ -30,6 +30,7 @@
|
||||
"express": "^4.17.1",
|
||||
"express-rate-limit": "^6.9.0",
|
||||
"form-data": "^4.0.0",
|
||||
"katex": "^0.16.10",
|
||||
"marked": "^7.0.3",
|
||||
"multer": "^1.4.3",
|
||||
"nodemon": "^3.0.1",
|
||||
|
||||
@@ -9,6 +9,7 @@ const uploadVideo = require('./src/routes/uploadVideo');
|
||||
const downloadCertificate = require('./src/routes/downloadCertificate');
|
||||
const downloadLesson = require('./src/routes/downloadLesson');
|
||||
const downloadCourse = require('./src/routes/downloadCourse');
|
||||
const katex = require('./src/routes/katex');
|
||||
|
||||
// Express server
|
||||
const app = express();
|
||||
@@ -32,6 +33,7 @@ app.use('/downloadCertificate', downloadCertificate);
|
||||
app.use('/downloadLesson', downloadLesson);
|
||||
app.use('/downloadCourse', downloadCourse);
|
||||
app.use('/uploadVideo', uploadVideo);
|
||||
app.use('/katex', katex);
|
||||
|
||||
app.get('/', (req, res) => {
|
||||
res.send('Welcome to ClassroomIO');
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
const express = require('express');
|
||||
// https://ben.page/node-latex-to-html
|
||||
const katex = require('katex');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// localhost:3002/katex
|
||||
router.get('/', async (req, res) => {
|
||||
const original = req._parsedUrl.search;
|
||||
|
||||
const latexString = original.replace('?', '').replaceAll('+', '+').replaceAll('&space;', '');
|
||||
console.log({ latexString });
|
||||
|
||||
const html = katex.renderToString(latexString, {
|
||||
throwOnError: false,
|
||||
output: 'mathml'
|
||||
});
|
||||
|
||||
console.log({ html });
|
||||
|
||||
res.send(html);
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -2,6 +2,7 @@
|
||||
import { PUBLIC_TINYMCE_API_KEY } from '$env/static/public';
|
||||
import Editor from '@tinymce/tinymce-svelte';
|
||||
import { globalStore } from '$lib/utils/store/app';
|
||||
import { addMathPlugin } from '$lib/utils/functions/tinymce/plugins';
|
||||
|
||||
export let id = '';
|
||||
export let value: string | undefined = '';
|
||||
@@ -13,12 +14,19 @@
|
||||
|
||||
const apiKey = PUBLIC_TINYMCE_API_KEY;
|
||||
|
||||
let tinmycEditor: any;
|
||||
let unmount = false;
|
||||
|
||||
function getTinymce() {
|
||||
const getSink = () => {
|
||||
return typeof window !== 'undefined' ? window : global;
|
||||
};
|
||||
const sink = getSink();
|
||||
return sink && sink.tinymce ? sink.tinymce : null;
|
||||
}
|
||||
|
||||
// editor configuration
|
||||
let conf = {
|
||||
plugins: 'lists, link, emoticons, code, media',
|
||||
plugins: 'lists, link, emoticons, code, media, eqneditor',
|
||||
toolbar: [
|
||||
{ name: 'history', items: ['undo', 'redo'] },
|
||||
{ name: 'styles', items: ['styles'] },
|
||||
@@ -29,7 +37,8 @@
|
||||
{ name: 'link', items: ['link'] },
|
||||
{ name: 'insert', items: ['emoticons'] },
|
||||
{ name: 'code', items: ['code'] },
|
||||
{ name: 'media', items: ['media'] }
|
||||
{ name: 'media', items: ['media'] },
|
||||
{ name: 'eqneditor', items: ['eqneditor'] }
|
||||
],
|
||||
lists_indent_on_tab: false,
|
||||
min_height: height,
|
||||
@@ -37,10 +46,13 @@
|
||||
placeholder: placeholder,
|
||||
skin: 'oxide-dark',
|
||||
content_css: 'dark',
|
||||
extended_valid_elements: '*[.*]',
|
||||
setup: () => {
|
||||
addMathPlugin(getTinymce());
|
||||
},
|
||||
init_instance_callback: function (editor: any) {
|
||||
editorWindowRef = editor.iframeElement?.contentWindow;
|
||||
|
||||
tinmycEditor = editor;
|
||||
editor.on('Change', function () {
|
||||
const html = editor.getContent();
|
||||
if (onChange) {
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
import { PUBLIC_SERVER_URL } from '$env/static/public';
|
||||
|
||||
|
||||
var win, output, textarea;
|
||||
|
||||
// Follow this guide to get source code: https://editor.codecogs.com/docs/3-Tiny_MCE_v6.php
|
||||
export const addMathPlugin = (tinymce: any) => {
|
||||
tinymce.PluginManager.add('eqneditor', function (editor, url) {
|
||||
// Load necessary javascript for editor from CodeCogs
|
||||
var sl = new tinymce.dom.ScriptLoader();
|
||||
sl.add('https://pub-19b235c5098f4ff1a6b1099331c8d00e.r2.dev/eqneditor.api.min.js');
|
||||
var loaded = sl.loadQueue();
|
||||
|
||||
// Load custom CSS
|
||||
tinymce.DOM.loadCSS('https://pub-19b235c5098f4ff1a6b1099331c8d00e.r2.dev/eqneditor_1.css');
|
||||
|
||||
editor.ui.registry.addIcon(
|
||||
'logo',
|
||||
`<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="18px" height="20px" viewBox="0 0 18 20" enable-background="new 0 0 18 20" xml:space="preserve">
|
||||
<rect x="4.4" y="5.1" fill="none" width="38.6" height="43.4"/>
|
||||
<g enable-background="new ">
|
||||
<path d="M4,9.5H2L2.3,7h2.1l0.3-1.8C4.7,4.6,4.9,4.1,5,3.6c0.2-0.5,0.4-0.9,0.7-1.3c0.3-0.4,0.8-0.7,1.3-0.9C7.7,1.1,8.4,1,9.2,1
|
||||
c0.3,0,0.6,0,0.9,0c0.3,0,0.6,0.1,0.9,0.1l-0.5,2.7c-0.2-0.1-0.3-0.1-0.5-0.2c-0.1,0-0.3-0.1-0.5-0.1C9,3.7,8.6,3.8,8.4,4
|
||||
C8.1,4.2,7.9,4.7,7.8,5.4L7.6,7h2.3L9.5,9.5H7.2l-1.1,7.7H3L4,9.5z"/>
|
||||
</g>
|
||||
<g enable-background="new ">
|
||||
<path d="M11.1,15.3l-2.3-3.2h2.5l1.2,1.8l1.2-1.8H16l-2.2,3.2l2.6,3.6h-2.5l-1.5-2.3L10.8,19H8.4L11.1,15.3z"/>
|
||||
</g>
|
||||
</svg>`
|
||||
);
|
||||
|
||||
editor.ui.registry.addIcon(
|
||||
'menu',
|
||||
`<svg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' width='11.473512pt' height='12.266481pt' viewBox='-.239051 -.242965 11.473512 12.266481'>
|
||||
<defs>
|
||||
<path id='g0-120' d='M3.993026-3.180075C3.642341-3.092403 3.626401-2.781569 3.626401-2.749689C3.626401-2.574346 3.761893-2.454795 3.937235-2.454795S4.383562-2.590286 4.383562-2.933001C4.383562-3.387298 3.881445-3.514819 3.58655-3.514819C3.211955-3.514819 2.909091-3.251806 2.725778-2.940971C2.550436-3.363387 2.13599-3.514819 1.809215-3.514819C.940473-3.514819 .454296-2.518555 .454296-2.295392C.454296-2.223661 .510087-2.191781 .573848-2.191781C.669489-2.191781 .68543-2.231631 .70934-2.327273C.892653-2.909091 1.370859-3.291656 1.785305-3.291656C2.096139-3.291656 2.247572-3.068493 2.247572-2.781569C2.247572-2.622167 2.15193-2.255542 2.088169-2.000498C2.032379-1.769365 1.857036-1.060025 1.817186-.908593C1.705604-.478207 1.41868-.143462 1.060025-.143462C1.028144-.143462 .820922-.143462 .653549-.255044C1.020174-.342715 1.020174-.67746 1.020174-.68543C1.020174-.868742 .876712-.980324 .70137-.980324C.486177-.980324 .255044-.797011 .255044-.494147C.255044-.127522 .645579 .079701 1.052055 .079701C1.474471 .079701 1.769365-.239103 1.912827-.494147C2.088169-.103611 2.454795 .079701 2.83736 .079701C3.706102 .079701 4.184309-.916563 4.184309-1.139726C4.184309-1.219427 4.120548-1.243337 4.064757-1.243337C3.969116-1.243337 3.953176-1.187547 3.929265-1.107846C3.769863-.573848 3.315567-.143462 2.8533-.143462C2.590286-.143462 2.399004-.318804 2.399004-.653549C2.399004-.812951 2.446824-.996264 2.558406-1.44259C2.614197-1.681694 2.789539-2.383064 2.82939-2.534496C2.940971-2.948941 3.219925-3.291656 3.57858-3.291656C3.618431-3.291656 3.825654-3.291656 3.993026-3.180075Z'/>
|
||||
<path id='g1-102' d='M5.332005-4.805978C5.571108-4.805978 5.66675-4.805978 5.66675-5.033126C5.66675-5.152677 5.571108-5.152677 5.355915-5.152677H4.387547C4.614695-6.38406 4.782067-7.232877 4.877709-7.615442C4.94944-7.902366 5.200498-8.177335 5.511333-8.177335C5.762391-8.177335 6.01345-8.069738 6.133001-7.962142C5.66675-7.914321 5.523288-7.567621 5.523288-7.364384C5.523288-7.12528 5.702615-6.981818 5.929763-6.981818C6.168867-6.981818 6.527522-7.185056 6.527522-7.639352C6.527522-8.141469 6.025405-8.416438 5.499377-8.416438C4.985305-8.416438 4.483188-8.033873 4.244085-7.567621C4.028892-7.149191 3.90934-6.718804 3.634371-5.152677H2.833375C2.606227-5.152677 2.486675-5.152677 2.486675-4.937484C2.486675-4.805978 2.558406-4.805978 2.797509-4.805978H3.56264C3.347447-3.694147 2.857285-.992279 2.582316 .286924C2.379078 1.327024 2.199751 2.199751 1.601993 2.199751C1.566127 2.199751 1.219427 2.199751 1.004234 1.972603C1.613948 1.924782 1.613948 1.398755 1.613948 1.3868C1.613948 1.147696 1.43462 1.004234 1.207472 1.004234C.968369 1.004234 .609714 1.207472 .609714 1.661768C.609714 2.175841 1.135741 2.438854 1.601993 2.438854C2.82142 2.438854 3.323537 .251059 3.455044-.3467C3.670237-1.267248 4.25604-4.447323 4.315816-4.805978H5.332005Z'/>
|
||||
</defs>
|
||||
<g id='page1' transform='matrix(1.13 0 0 1.13 -63.986043 -65.03376)'>
|
||||
<use x='56.413267' y='65.753425' xlink:href='#g1-102'/>
|
||||
<use x='62.183256' y='67.546688' xlink:href='#g0-120'/>
|
||||
</g>
|
||||
</svg>`
|
||||
);
|
||||
|
||||
function showDialog(inp = '') {
|
||||
var http = 'https:' == document.location.protocol ? 'https://' : 'http://';
|
||||
|
||||
win = editor.windowManager.open({
|
||||
title: 'Equation Editor',
|
||||
size: 'medium',
|
||||
body: {
|
||||
type: 'panel',
|
||||
items: [
|
||||
{
|
||||
type: 'htmlpanel',
|
||||
html:
|
||||
'<div id="equation-editor">' +
|
||||
'<div id="history"></div>' +
|
||||
'<div id="toolbar"></div>' +
|
||||
'<div id="latexInput" autocorrect="off"></div>' +
|
||||
'<div id="equation-output">' +
|
||||
'<img id="output"></div></div>'
|
||||
}
|
||||
]
|
||||
},
|
||||
buttons: [
|
||||
{
|
||||
type: 'cancel',
|
||||
name: 'closeButton',
|
||||
text: 'Cancel'
|
||||
},
|
||||
{
|
||||
type: 'submit',
|
||||
name: 'submitButton',
|
||||
text: 'Ok',
|
||||
buttonType: 'primary'
|
||||
},
|
||||
{
|
||||
type: 'custom',
|
||||
name: 'logo',
|
||||
text: '',
|
||||
icon: 'logo',
|
||||
align: 'start'
|
||||
}
|
||||
],
|
||||
onSubmit: async function (w) {
|
||||
let content = output.exportAs('html');
|
||||
|
||||
// The generated content always points to https://latex.codecogs.com/svg.image, this means we have to always rely on their server to serve formulas. We have a dedicated endpoint for generating formulas on our backend service: GET `/katex`. We will need to manually update the URL in the generate HTML to point to our server:
|
||||
// https://latex.codecogs.com/svg.image
|
||||
|
||||
// 1. Get only the URL in the img tag generated by codecogs library
|
||||
const srcRegex = /<img[^>]+src\s*=\s*['"]([^'"]+)['"][^>]*>/i;
|
||||
const srcMatch = content.match(srcRegex);
|
||||
|
||||
if (srcMatch) {
|
||||
// 2. Point the formula to our server
|
||||
const customAPI = PUBLIC_SERVER_URL + '/katex'
|
||||
const url = srcMatch[1]?.replaceAll('https://latex.codecogs.com/svg.image', customAPI);
|
||||
|
||||
// 3. Fetch the HTML from the server
|
||||
const response = await fetch(url);
|
||||
const html = await response.text();
|
||||
|
||||
// 4. Set the content to the html
|
||||
content = `<p>${html}</p><p> </p>`;
|
||||
} else {
|
||||
console.error("No src attribute found in the img tag.");
|
||||
}
|
||||
|
||||
tinymce.activeEditor.execCommand('mceInsertContent', false, content);
|
||||
textarea.pushToHistory();
|
||||
w.close();
|
||||
},
|
||||
onCancel: function (w) {
|
||||
w.close();
|
||||
},
|
||||
onAction: function (w, dets) {
|
||||
if (dets.name == 'logo') {
|
||||
window.open(http + 'editor.codecogs.com', '_blank')?.focus();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Ensure EqnEditor js is loaded before continuing
|
||||
loaded.then(() => {
|
||||
output = new EqEditor.Output('output');
|
||||
textarea = EqEditor.TextArea.link('latexInput')
|
||||
.addOutput(output)
|
||||
.addHistoryMenu(new EqEditor.History('history'));
|
||||
EqEditor.Toolbar.link('toolbar').addTextArea(textarea);
|
||||
|
||||
var nodes = document.getElementById('history')?.childNodes || [];
|
||||
for (var i = 0; i < nodes.length; i++) {
|
||||
nodes[i].style.padding = 'revert';
|
||||
}
|
||||
|
||||
textarea.clear();
|
||||
textarea.insert(inp.replace(/&space;/g, ' '), inp.length);
|
||||
});
|
||||
}
|
||||
|
||||
editor.ui.registry.addButton('eqneditor', {
|
||||
title: 'Equation',
|
||||
icon: 'menu',
|
||||
tooltip: 'Insert Equation',
|
||||
onAction: () => {
|
||||
showDialog();
|
||||
}
|
||||
// stateSelector: 'img[src*="latex"]'
|
||||
});
|
||||
|
||||
editor.on('dblclick', function (e) {
|
||||
if (e.target.nodeName.toLowerCase() == 'img') {
|
||||
var sName = e.target.src.match(/(gif|svg)\.image\?(.*)/);
|
||||
if (sName != null) showDialog(sName[2]);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
getMetadata: () => ({
|
||||
name: 'EqnEditor',
|
||||
url: 'https://editor.codecogs.com/docs/3-Tiny_MCE_v4x.php'
|
||||
})
|
||||
};
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,5 @@
|
||||
<svelte:head>
|
||||
<link rel="stylesheet" href="https://unpkg.com/katex@0.12.0/dist/katex.min.css" />
|
||||
</svelte:head>
|
||||
|
||||
<slot />
|
||||
Generated
+30
-9
@@ -71,6 +71,9 @@ importers:
|
||||
form-data:
|
||||
specifier: ^4.0.0
|
||||
version: 4.0.0
|
||||
katex:
|
||||
specifier: ^0.16.10
|
||||
version: 0.16.10
|
||||
marked:
|
||||
specifier: ^7.0.3
|
||||
version: 7.0.5
|
||||
@@ -1586,7 +1589,7 @@ packages:
|
||||
'@babel/traverse': 7.23.4
|
||||
'@babel/types': 7.23.4
|
||||
convert-source-map: 1.9.0
|
||||
debug: 4.3.4(supports-color@5.5.0)
|
||||
debug: 4.3.4
|
||||
gensync: 1.0.0-beta.2
|
||||
json5: 2.2.3
|
||||
semver: 6.3.1
|
||||
@@ -1609,7 +1612,7 @@ packages:
|
||||
'@babel/traverse': 7.23.4
|
||||
'@babel/types': 7.23.4
|
||||
convert-source-map: 2.0.0
|
||||
debug: 4.3.4(supports-color@5.5.0)
|
||||
debug: 4.3.4
|
||||
gensync: 1.0.0-beta.2
|
||||
json5: 2.2.3
|
||||
semver: 6.3.1
|
||||
@@ -1766,7 +1769,7 @@ packages:
|
||||
'@babel/core': 7.22.9
|
||||
'@babel/helper-compilation-targets': 7.22.15
|
||||
'@babel/helper-plugin-utils': 7.22.5
|
||||
debug: 4.3.4(supports-color@5.5.0)
|
||||
debug: 4.3.4
|
||||
lodash.debounce: 4.0.8
|
||||
resolve: 1.22.8
|
||||
transitivePeerDependencies:
|
||||
@@ -3081,7 +3084,7 @@ packages:
|
||||
'@babel/helper-split-export-declaration': 7.22.6
|
||||
'@babel/parser': 7.23.4
|
||||
'@babel/types': 7.23.4
|
||||
debug: 4.3.4(supports-color@5.5.0)
|
||||
debug: 4.3.4
|
||||
globals: 11.12.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
@@ -5749,7 +5752,7 @@ packages:
|
||||
vite: ^4.0.0
|
||||
dependencies:
|
||||
'@sveltejs/vite-plugin-svelte': 2.5.3(svelte@4.1.2)(vite@4.4.8)
|
||||
debug: 4.3.4(supports-color@5.5.0)
|
||||
debug: 4.3.4
|
||||
svelte: 4.1.2
|
||||
vite: 4.4.8(@types/node@18.11.10)(sass@1.64.2)
|
||||
transitivePeerDependencies:
|
||||
@@ -5763,7 +5766,7 @@ packages:
|
||||
vite: ^4.0.0
|
||||
dependencies:
|
||||
'@sveltejs/vite-plugin-svelte-inspector': 1.0.4(@sveltejs/vite-plugin-svelte@2.5.3)(svelte@4.1.2)(vite@4.4.8)
|
||||
debug: 4.3.4(supports-color@5.5.0)
|
||||
debug: 4.3.4
|
||||
deepmerge: 4.3.1
|
||||
kleur: 4.1.5
|
||||
magic-string: 0.30.5
|
||||
@@ -6542,7 +6545,7 @@ packages:
|
||||
resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
|
||||
engines: {node: '>= 6.0.0'}
|
||||
dependencies:
|
||||
debug: 4.3.4(supports-color@5.5.0)
|
||||
debug: 4.3.4
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
@@ -8353,6 +8356,17 @@ packages:
|
||||
ms: 2.1.3
|
||||
dev: true
|
||||
|
||||
/debug@4.3.4:
|
||||
resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
|
||||
engines: {node: '>=6.0'}
|
||||
peerDependencies:
|
||||
supports-color: '*'
|
||||
peerDependenciesMeta:
|
||||
supports-color:
|
||||
optional: true
|
||||
dependencies:
|
||||
ms: 2.1.2
|
||||
|
||||
/debug@4.3.4(supports-color@5.5.0):
|
||||
resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
|
||||
engines: {node: '>=6.0'}
|
||||
@@ -10449,7 +10463,7 @@ packages:
|
||||
engines: {node: '>= 6'}
|
||||
dependencies:
|
||||
agent-base: 6.0.2
|
||||
debug: 4.3.4(supports-color@5.5.0)
|
||||
debug: 4.3.4
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
@@ -10976,7 +10990,7 @@ packages:
|
||||
resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==}
|
||||
engines: {node: '>=10'}
|
||||
dependencies:
|
||||
debug: 4.3.4(supports-color@5.5.0)
|
||||
debug: 4.3.4
|
||||
istanbul-lib-coverage: 3.2.2
|
||||
source-map: 0.6.1
|
||||
transitivePeerDependencies:
|
||||
@@ -11552,6 +11566,13 @@ packages:
|
||||
resolution: {integrity: sha512-0H4miIAWZYpnpg7oD/Y/PBb77ISSHAETif5xK9EnwIgYCO6oC8ErkJxDumMUTR44shSOwptRIArRuvNuvN/hOw==}
|
||||
dev: true
|
||||
|
||||
/katex@0.16.10:
|
||||
resolution: {integrity: sha512-ZiqaC04tp2O5utMsl2TEZTXxa6WSC4yo0fv5ML++D3QZv/vx2Mct0mTlRx3O+uUkjfuAgOkzsCmq5MiUEsDDdA==}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
commander: 8.3.0
|
||||
dev: false
|
||||
|
||||
/katex@0.16.9:
|
||||
resolution: {integrity: sha512-fsSYjWS0EEOwvy81j3vRA8TEAhQhKiqO+FQaKWp0m39qwOzHVBgAUBIXWj1pB+O2W3fIpNa6Y9KSKCVbfPhyAQ==}
|
||||
hasBin: true
|
||||
|
||||
Reference in New Issue
Block a user