|
// ==UserScript== |
|
// @name TinyMCE Editor for Zammad Helpdesk |
|
// @namespace https://github.com/nazarewk |
|
// @version 1.0 |
|
// @description Adds TinyMCE editor to rich text areas in Zammad |
|
// @homepage https://gist.github.com/nazarewk/adb1d31e66c2b40b7ea8c4a11a53ce12 |
|
// @author nazarewk |
|
// @grant GM_addElement |
|
// @run-at document-idle |
|
// ==/UserScript== |
|
|
|
(function() { |
|
'use strict'; |
|
const tinyMCEVersion = "7.8"; |
|
const markerClass = "kdn-mce-initialized"; |
|
const selector = `[contenteditable="true"].richtext-content:not(.${markerClass})`; |
|
window.kdnTinyMCE = {}; |
|
const self = window.kdnTinyMCE; |
|
|
|
|
|
function tryInitializeOn(element) { |
|
if (typeof tinymce === undefined) { |
|
console.error("tinymce variable is not loaded yet!"); |
|
return false; |
|
} |
|
|
|
if (element.classList.contains(markerClass)) { |
|
return true; |
|
} else { |
|
element.classList.add(markerClass); |
|
} |
|
|
|
let content_style = ` |
|
.mce-content-body code[data-mce-selected="inline-boundary"] { background-color: #333; } |
|
`; |
|
|
|
tinymce.init({ |
|
target: element, |
|
inline: true, |
|
skin: (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'oxide-dark' : 'oxide'), |
|
content_style, |
|
plugins: [ |
|
"advlist", "anchor", "autolink", "charmap", "code", "fullscreen", |
|
"help", "image", "insertdatetime", "link", "lists", "media", |
|
"preview", "searchreplace", "table", "visualblocks", |
|
], |
|
toolbar: [ |
|
"alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image", |
|
"undo redo | styles | bold italic underline strikethrough monospace code-fmt | blockquote pre-fmt", |
|
], |
|
setup: (editor) => { |
|
const toggleMonospace = () => editor.execCommand('mceToggleFormat', false, 'monospace'); |
|
const toggleCode = () => editor.execCommand('mceToggleFormat', false, 'code'); |
|
const togglePre = () => editor.execCommand('mceToggleFormat', false, 'pre'); |
|
|
|
// Add Monospace button |
|
editor.ui.registry.addButton('monospace', { |
|
text: 'M', |
|
tooltip: 'Monospace', |
|
shortcut: 'alt+m', |
|
onAction: toggleMonospace |
|
}); |
|
|
|
// Add Code button |
|
editor.ui.registry.addButton('code-fmt', { |
|
text: '</>', |
|
tooltip: 'Code', |
|
shortcut: 'meta+alt+m', |
|
onAction: toggleCode |
|
}); |
|
|
|
// Add Pre button |
|
editor.ui.registry.addButton('pre-fmt', { |
|
text: 'PRE', |
|
tooltip: 'Preformatted Block (⌘⇧C)', |
|
onAction: togglePre |
|
}); |
|
|
|
// Register keyboard shortcuts |
|
editor.shortcuts.add('alt+m', 'Monospace', toggleMonospace); |
|
editor.shortcuts.add('meta+alt+m', 'Code', toggleCode); |
|
editor.shortcuts.add('meta+shift+c', 'Pre Block', togglePre); |
|
}, |
|
formats: { |
|
monospace: {inline: 'span', styles: {fontFamily: 'monospace'}}, |
|
code: {inline: 'code'}, |
|
pre: {block: 'pre', remove: 'all'} |
|
} |
|
}); |
|
return true; |
|
} |
|
|
|
function discover() { |
|
const elements = document.querySelectorAll(selector); |
|
self.queue.push(...elements); |
|
}; |
|
|
|
function tick() { |
|
if (self.isProcessing) { |
|
console.log("already processing"); |
|
return false; |
|
} |
|
self.isProcessing = true; |
|
self.discover(); |
|
self.process(); |
|
self.isProcessing = false; |
|
return true; |
|
} |
|
|
|
function process() { |
|
let emptyLength = 0; |
|
while (self.queue.length > emptyLength) { |
|
const element = self.queue.shift(); |
|
if (!self.tryInitializeOn(element)) { |
|
self.queue.push(element); |
|
emptyLength += 1; |
|
} |
|
} |
|
} |
|
|
|
const script = GM_addElement('script', { |
|
src: `https://cdn.jsdelivr.net/npm/tinymce@${tinyMCEVersion}/tinymce.min.js`, |
|
type: "text/javascript", |
|
}); |
|
|
|
self.queue = []; |
|
self.discover = discover; |
|
self.process = process; |
|
self.isProcessing = false; |
|
self.tick = tick; |
|
self.tryInitializeOn = tryInitializeOn; |
|
self.tickerId = null; |
|
|
|
|
|
script.addEventListener("load", function () { |
|
self.tickerId = setInterval(tick, 1000); |
|
}); |
|
})(); |