e0d9380055
- Removed old 'editor-*-update' commands to instead use the aligned 'editor::replace' command that we already have. - Changed the way custom styles are loaded for the WYSIWYG editor so we don't need an API call but instead scape content from the parent page header using comments as identifiers. Added tests to ensure comments exist and align.
270 lines
No EOL
9 KiB
JavaScript
270 lines
No EOL
9 KiB
JavaScript
import {register as registerShortcuts} from "./shortcuts";
|
|
import {listen as listenForCommonEvents} from "./common-events";
|
|
import {scrollToQueryString, fixScrollForMobile} from "./scrolling";
|
|
import {listenForDragAndPaste} from "./drop-paste-handling";
|
|
|
|
import {getPlugin as getCodeeditorPlugin} from "./plugin-codeeditor";
|
|
import {getPlugin as getDrawioPlugin} from "./plugin-drawio";
|
|
import {getPlugin as getCustomhrPlugin} from "./plugins-customhr";
|
|
import {getPlugin as getImagemanagerPlugin} from "./plugins-imagemanager";
|
|
import {getPlugin as getAboutPlugin} from "./plugins-about";
|
|
|
|
const style_formats = [
|
|
{title: "Large Header", format: "h2", preview: 'color: blue;'},
|
|
{title: "Medium Header", format: "h3"},
|
|
{title: "Small Header", format: "h4"},
|
|
{title: "Tiny Header", format: "h5"},
|
|
{title: "Paragraph", format: "p", exact: true, classes: ''},
|
|
{title: "Blockquote", format: "blockquote"},
|
|
{
|
|
title: "Callouts", items: [
|
|
{title: "Information", format: 'calloutinfo'},
|
|
{title: "Success", format: 'calloutsuccess'},
|
|
{title: "Warning", format: 'calloutwarning'},
|
|
{title: "Danger", format: 'calloutdanger'}
|
|
]
|
|
},
|
|
];
|
|
|
|
const formats = {
|
|
codeeditor: {selector: 'p,h1,h2,h3,h4,h5,h6,td,th,div'},
|
|
alignleft: {selector: 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img', classes: 'align-left'},
|
|
aligncenter: {selector: 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img', classes: 'align-center'},
|
|
alignright: {selector: 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img', classes: 'align-right'},
|
|
calloutsuccess: {block: 'p', exact: true, attributes: {class: 'callout success'}},
|
|
calloutinfo: {block: 'p', exact: true, attributes: {class: 'callout info'}},
|
|
calloutwarning: {block: 'p', exact: true, attributes: {class: 'callout warning'}},
|
|
calloutdanger: {block: 'p', exact: true, attributes: {class: 'callout danger'}}
|
|
};
|
|
|
|
function file_picker_callback(callback, value, meta) {
|
|
|
|
// field_name, url, type, win
|
|
if (meta.filetype === 'file') {
|
|
window.EntitySelectorPopup.show(entity => {
|
|
callback(entity.link, {
|
|
text: entity.name,
|
|
title: entity.name,
|
|
});
|
|
});
|
|
}
|
|
|
|
if (meta.filetype === 'image') {
|
|
// Show image manager
|
|
window.ImageManager.show(function (image) {
|
|
callback(image.url, {alt: image.name});
|
|
}, 'gallery');
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* @param {WysiwygConfigOptions} options
|
|
* @return {{toolbar: string, groupButtons: Object<string, Object>}}
|
|
*/
|
|
function buildToolbar(options) {
|
|
const textDirPlugins = options.textDirection === 'rtl' ? 'ltr rtl' : '';
|
|
|
|
const groupButtons = {
|
|
formatoverflow: {
|
|
icon: 'more-drawer',
|
|
tooltip: 'More',
|
|
items: 'strikethrough superscript subscript inlinecode removeformat'
|
|
},
|
|
listoverflow: {
|
|
icon: 'more-drawer',
|
|
tooltip: 'More',
|
|
items: 'outdent indent'
|
|
},
|
|
insertoverflow: {
|
|
icon: 'more-drawer',
|
|
tooltip: 'More',
|
|
items: 'hr codeeditor drawio media'
|
|
}
|
|
};
|
|
|
|
const toolbar = [
|
|
'undo redo',
|
|
'styleselect',
|
|
'bold italic underline forecolor backcolor formatoverflow',
|
|
'alignleft aligncenter alignright alignjustify',
|
|
'bullist numlist listoverflow',
|
|
textDirPlugins,
|
|
'link table imagemanager-insert insertoverflow',
|
|
'code about fullscreen'
|
|
];
|
|
|
|
return {
|
|
toolbar: toolbar.filter(row => Boolean(row)).join(' | '),
|
|
groupButtons,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @param {WysiwygConfigOptions} options
|
|
* @return {string}
|
|
*/
|
|
function gatherPlugins(options) {
|
|
const plugins = [
|
|
"image",
|
|
"imagetools",
|
|
"table",
|
|
"paste",
|
|
"link",
|
|
"autolink",
|
|
"fullscreen",
|
|
"code",
|
|
"customhr",
|
|
"autosave",
|
|
"lists",
|
|
"codeeditor",
|
|
"media",
|
|
"imagemanager",
|
|
"about",
|
|
options.textDirection === 'rtl' ? 'directionality' : '',
|
|
];
|
|
|
|
window.tinymce.PluginManager.add('codeeditor', getCodeeditorPlugin(options));
|
|
window.tinymce.PluginManager.add('customhr', getCustomhrPlugin(options));
|
|
window.tinymce.PluginManager.add('imagemanager', getImagemanagerPlugin(options));
|
|
window.tinymce.PluginManager.add('about', getAboutPlugin(options));
|
|
|
|
if (options.drawioUrl) {
|
|
window.tinymce.PluginManager.add('drawio', getDrawioPlugin(options));
|
|
plugins.push('drawio');
|
|
}
|
|
|
|
return plugins.filter(plugin => Boolean(plugin)).join(' ');
|
|
}
|
|
|
|
/**
|
|
* Fetch custom HTML head content from the parent page head into the editor.
|
|
*/
|
|
function fetchCustomHeadContent() {
|
|
const headContentLines = document.head.innerHTML.split("\n");
|
|
const startLineIndex = headContentLines.findIndex(line => line.trim() === '<!-- Start: custom user content -->');
|
|
const endLineIndex = headContentLines.findIndex(line => line.trim() === '<!-- End: custom user content -->');
|
|
if (startLineIndex === -1 || endLineIndex === -1) {
|
|
return ''
|
|
}
|
|
return headContentLines.slice(startLineIndex + 1, endLineIndex).join('\n');
|
|
}
|
|
|
|
/**
|
|
* @param {WysiwygConfigOptions} options
|
|
* @return {function(Editor)}
|
|
*/
|
|
function getSetupCallback(options) {
|
|
return function(editor) {
|
|
editor.on('ExecCommand change input NodeChange ObjectResized', editorChange);
|
|
listenForCommonEvents(editor);
|
|
registerShortcuts(editor);
|
|
listenForDragAndPaste(editor, options);
|
|
|
|
editor.on('init', () => {
|
|
editorChange();
|
|
scrollToQueryString(editor);
|
|
fixScrollForMobile(editor);
|
|
window.editor = editor;
|
|
});
|
|
|
|
function editorChange() {
|
|
const content = editor.getContent();
|
|
if (options.darkMode) {
|
|
editor.contentDocument.documentElement.classList.add('dark-mode');
|
|
}
|
|
window.$events.emit('editor-html-change', content);
|
|
}
|
|
|
|
// Custom handler hook
|
|
window.$events.emitPublic(options.containerElement, 'editor-tinymce::setup', {editor});
|
|
|
|
// Inline code format button
|
|
editor.ui.registry.addButton('inlinecode', {
|
|
tooltip: 'Inline code',
|
|
icon: 'sourcecode',
|
|
onAction() {
|
|
editor.execCommand('mceToggleFormat', false, 'code');
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {WysiwygConfigOptions} options
|
|
* @return {Object}
|
|
*/
|
|
export function build(options) {
|
|
|
|
// Set language
|
|
window.tinymce.addI18n(options.language, options.translationMap);
|
|
|
|
const {toolbar, groupButtons: toolBarGroupButtons} = buildToolbar(options);
|
|
|
|
// Return config object
|
|
return {
|
|
width: '100%',
|
|
height: '100%',
|
|
selector: '#html-editor',
|
|
content_css: [
|
|
window.baseUrl('/dist/styles.css'),
|
|
],
|
|
branding: false,
|
|
skin: options.darkMode ? 'oxide-dark' : 'oxide',
|
|
body_class: 'page-content',
|
|
browser_spellcheck: true,
|
|
relative_urls: false,
|
|
language: options.language,
|
|
directionality: options.textDirection,
|
|
remove_script_host: false,
|
|
document_base_url: window.baseUrl('/'),
|
|
end_container_on_empty_block: true,
|
|
statusbar: false,
|
|
menubar: false,
|
|
paste_data_images: false,
|
|
extended_valid_elements: 'pre[*],svg[*],div[drawio-diagram]',
|
|
automatic_uploads: false,
|
|
valid_children: "-div[p|h1|h2|h3|h4|h5|h6|blockquote],+div[pre],+div[img]",
|
|
plugins: gatherPlugins(options),
|
|
imagetools_toolbar: 'imageoptions',
|
|
contextmenu: false,
|
|
toolbar: toolbar,
|
|
content_style: `html, body, html.dark-mode {background: ${options.darkMode ? '#222' : '#fff'};} body {padding-left: 15px !important; padding-right: 15px !important; margin:0!important; margin-left:auto!important;margin-right:auto!important;}`,
|
|
style_formats,
|
|
style_formats_merge: false,
|
|
media_alt_source: false,
|
|
media_poster: false,
|
|
formats,
|
|
file_picker_types: 'file image',
|
|
file_picker_callback,
|
|
paste_preprocess(plugin, args) {
|
|
let content = args.content;
|
|
if (content.indexOf('<img src="file://') !== -1) {
|
|
args.content = '';
|
|
}
|
|
},
|
|
init_instance_callback(editor) {
|
|
let head = editor.getDoc().querySelector('head');
|
|
console.log(fetchCustomHeadContent());
|
|
head.innerHTML += fetchCustomHeadContent();
|
|
},
|
|
setup(editor) {
|
|
for (const [key, config] of Object.entries(toolBarGroupButtons)) {
|
|
editor.ui.registry.addGroupToolbarButton(key, config);
|
|
}
|
|
getSetupCallback(options)(editor);
|
|
},
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @typedef {Object} WysiwygConfigOptions
|
|
* @property {Element} containerElement
|
|
* @property {string} language
|
|
* @property {boolean} darkMode
|
|
* @property {string} textDirection
|
|
* @property {string} drawioUrl
|
|
* @property {int} pageId
|
|
* @property {Object} translations
|
|
* @property {Object} translationMap
|
|
*/ |