Newer
Older
/**
* wysiwyg.js - Replace HTML textareas with WYSIWYG editor.
*/
import parseOptions from './parse_options.js';
import WikiLink from '../cke/wiki-link/wiki-link.js';
const wysiwyg = {
// NOTE keep this function in sync with Markup class
htmlMarker: '<!--HTML-->',
htmlMarkerRegExp: /^\s*<!--\s*HTML.*?-->/i,
isHtml: function isHtml(text) {
// NOTE keep this function in sync with
// Markup::isHtml in Markup.class.php
return this.hasHtmlMarker(text);
},
hasHtmlMarker: function hasHtmlMarker(text) {
// NOTE keep this function in sync with
// Markup::hasHtmlMarker in Markup.class.php
return this.htmlMarkerRegExp.test(text);
},
markAsHtml: function markAsHtml(text) {
// NOTE keep this function in sync with
// Markup::markAsHtml in Markup.class.php
if (this.hasHtmlMarker(text) || text.trim() == '') {
return text; // marker already set, don't set twice
}
return this.htmlMarker + '\n' + text;
},
getEditor,
hasEditor,
replace(textarea) {
if (!hasEditor(textarea)) {
if (isTextareaVisible(textarea)) {
replaceTextarea(textarea);
}
} else if (isEditorHidden(textarea)) {
destroyTextarea(textarea);
}
},
};
export default wysiwyg;
function isTextareaVisible(textarea) {
return $(textarea).is(':visible');
}
function isEditorHidden(textarea) {
if (!hasEditor(textarea)) {
return false;
const editor = getEditor(textarea);
return editor && editor.ui && $(editor.ui.element).is(':hidden');
}
function replaceTextarea(textarea) {
setEditor(textarea, {});
const $textarea = textarea instanceof jQuery ? textarea : $(textarea);
let options = {};
if ($textarea.attr('data-editor')) {
const parsed = parseOptions($textarea.attr('data-editor'));
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
if (parsed.toolbar === 'small') {
options.toolbar = {
removeItems: [
'undo',
'redo',
'findAndReplace',
'strikethrough',
'horizontalLine',
'insertBlockQuote',
'splitBlockQuote',
'removeBlockQuote',
]
};
} else if (parsed.toolbar === 'minimal') {
options.toolbar = {
items: [
'bold',
'italic',
'underline',
'subscript',
'superscript',
'|',
'removeFormat',
'|',
'bulletedList',
'numberedList',
'|',
'fontColor',
'fontBackgroundColor',
'|',
'link',
'math',
'specialCharacters',
]
};
}
if (parsed.removePlugins) {
options.removePlugins = parsed.removePlugins.split(",")
}
if (parsed.extraPlugins) {
const pluginMap = { WikiLink };
options.extraPlugins = parsed.extraPlugins.split(",").reduce((memo, plugin) => {
if (plugin in pluginMap) {
memo.push(pluginMap[plugin]);
}
return memo;
}, []);
}
}
return STUDIP.loadChunk('wysiwyg')
.then(loadMathJax)
.then(createEditor)
.then(setEditorInstance)
.then(enhanceEditor)
.then(emitLoadEvent);
function createEditor(ClassicEditor) {
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
return ClassicEditor.create(textarea, options).then(editor => {
function getViewportOffsetTop() {
const topBar = document.getElementById('top-bar');
const responsiveContentbar = document.getElementById('responsive-contentbar');
let top = topBar.clientHeight + topBar.clientTop;
if (responsiveContentbar) {
top += responsiveContentbar?.clientHeight + responsiveContentbar.clientTop;
}
return top;
}
function updateOffsetTop() {
// This needs to be delayed since some events will fire before
// changing the DOM
setTimeout(() => {
editor.ui.viewportOffset = {top: getViewportOffsetTop()};
editor.ui.update();
}, 50);
}
// Set initial offset top
updateOffsetTop();
// Listen to relevant events that may require the sticky panel to be misplaced
STUDIP.eventBus.on('toggle-compact-navigation', updateOffsetTop);
STUDIP.eventBus.on('switch-focus-mode', updateOffsetTop);
// Stop listening if editor is destroyed
editor.on('destroy', () => {
STUDIP.eventBus.off('toggle-compact-navigation', updateOffsetTop);
STUDIP.eventBus.off('switch-focus-mode', updateOffsetTop);
});
return editor;
});
function setEditorInstance(ckeditor) {
setEditor(textarea, ckeditor);
return ckeditor;
}
function enhanceEditor(ckeditor) {
// make sure HTML marker is always set, in
// case contents are cut-off by the backend
$textarea.closest('form').submit(() => {
ckeditor.sourceElement.value = wysiwyg.markAsHtml(ckeditor.getData());
// focus the editor if requested
if ($textarea.is('[autofocus]')) {
ckeditor.focus();
}
ckeditor.ui.focusTracker.on('change:isFocused', (evt, name, isFocused) => {
if (!isFocused) {
ckeditor.updateSourceElement();
const button = ckeditor.ui.view.toolbar.items.find( item => item.class === "ck-source-editing-button");
if (button) {
button.withText = false;
}
// TODO: Kein updateSourceElement im SourceEditing-Modus
// $(ckeditor.container.$).on('blur', '.CodeMirror', function (event) {
// ckeditor.updateElement(); // also update in source mode
// });
}
function emitLoadEvent(ckeditor) {
$textarea.trigger('load.wysiwyg');
return ckeditor;
}
async function loadMathJax(ckeditor) {
let mathjaxP;
if (window.MathJax && window.MathJax.Hub) {
mathjaxP = Promise.resolve(window.MathJax);
} else if (window.STUDIP && window.STUDIP.loadChunk) {
mathjaxP = window.STUDIP.loadChunk('mathjax');
await mathjaxP;
//console.log('loading MathJaxP...', mathjaxP);
return ckeditor;
}
function destroyTextarea(textarea) {
if (!hasEditor(textarea)) {
throw new Error('Trying to destroy a non-existing editor instance.');
}
const editor = getEditor(textarea);
editor.destroy(true);
unsetEditor(textarea);
}
// create an unused id
function createNewId(prefix) {
var i = 0;
while ($('#' + prefix + i).length > 0) {
i++;
}
return prefix + i;
}
const instances = new Map();
function getEditor(textarea) {
return textarea?.id !== '' ? instances.get(textarea.id) : null;
}
function hasEditor(textarea) {
return textarea.id !== '' && instances.has(textarea.id);
}
function setEditor(textarea, editor) {
if (textarea.id === '') {
textarea.id = createNewId('wysiwyg');
}
instances.set(textarea.id, editor);
}
function unsetEditor(textarea) {
instances.delete(textarea.id);
}