mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-10-01 01:36:00 -04:00
5395ca2f00
Required a lot of working around TinyMCE since it added a preview/wrapper element in the editor which complicates things. Added view new "fixes.js" file so large hacks to default TinyMCe functionality are kept in one place.
304 lines
9.8 KiB
JavaScript
304 lines
9.8 KiB
JavaScript
import {register as registerShortcuts} from './shortcuts';
|
|
import {listen as listenForCommonEvents} from './common-events';
|
|
import {scrollToQueryString} from './scrolling';
|
|
import {listenForDragAndPaste} from './drop-paste-handling';
|
|
import {getPrimaryToolbar, registerAdditionalToolbars} from './toolbars';
|
|
import {registerCustomIcons} from './icons';
|
|
import {setupFilters} from './filters';
|
|
|
|
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';
|
|
import {getPlugin as getDetailsPlugin} from './plugins-details';
|
|
import {getPlugin as getTasklistPlugin} from './plugins-tasklist';
|
|
import {handleEmbedAlignmentChanges} from './fixes';
|
|
|
|
const styleFormats = [
|
|
{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 = {
|
|
alignleft: {selector: 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img,iframe,video,span', classes: 'align-left'},
|
|
aligncenter: {selector: 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img,iframe,video,span', classes: 'align-center'},
|
|
alignright: {selector: 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img,iframe,video,span', 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'}},
|
|
};
|
|
|
|
const colorMap = [
|
|
'#BFEDD2', '',
|
|
'#FBEEB8', '',
|
|
'#F8CAC6', '',
|
|
'#ECCAFA', '',
|
|
'#C2E0F4', '',
|
|
|
|
'#2DC26B', '',
|
|
'#F1C40F', '',
|
|
'#E03E2D', '',
|
|
'#B96AD9', '',
|
|
'#3598DB', '',
|
|
|
|
'#169179', '',
|
|
'#E67E23', '',
|
|
'#BA372A', '',
|
|
'#843FA1', '',
|
|
'#236FA1', '',
|
|
|
|
'#ECF0F1', '',
|
|
'#CED4D9', '',
|
|
'#95A5A6', '',
|
|
'#7E8C8D', '',
|
|
'#34495E', '',
|
|
|
|
'#000000', '',
|
|
'#ffffff', '',
|
|
];
|
|
|
|
function filePickerCallback(callback, value, meta) {
|
|
// field_name, url, type, win
|
|
if (meta.filetype === 'file') {
|
|
/** @type {EntitySelectorPopup} * */
|
|
const selector = window.$components.first('entity-selector-popup');
|
|
const selectionText = this.selection.getContent({format: 'text'}).trim();
|
|
selector.show(entity => {
|
|
callback(entity.link, {
|
|
text: entity.name,
|
|
title: entity.name,
|
|
});
|
|
}, selectionText);
|
|
}
|
|
|
|
if (meta.filetype === 'image') {
|
|
// Show image manager
|
|
/** @type {ImageManager} * */
|
|
const imageManager = window.$components.first('image-manager');
|
|
imageManager.show(image => {
|
|
callback(image.url, {alt: image.name});
|
|
}, 'gallery');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {WysiwygConfigOptions} options
|
|
* @return {string[]}
|
|
*/
|
|
function gatherPlugins(options) {
|
|
const plugins = [
|
|
'image',
|
|
'table',
|
|
'link',
|
|
'autolink',
|
|
'fullscreen',
|
|
'code',
|
|
'customhr',
|
|
'autosave',
|
|
'lists',
|
|
'codeeditor',
|
|
'media',
|
|
'imagemanager',
|
|
'about',
|
|
'details',
|
|
'tasklist',
|
|
options.textDirection === 'rtl' ? 'directionality' : '',
|
|
];
|
|
|
|
window.tinymce.PluginManager.add('codeeditor', getCodeeditorPlugin());
|
|
window.tinymce.PluginManager.add('customhr', getCustomhrPlugin());
|
|
window.tinymce.PluginManager.add('imagemanager', getImagemanagerPlugin());
|
|
window.tinymce.PluginManager.add('about', getAboutPlugin());
|
|
window.tinymce.PluginManager.add('details', getDetailsPlugin());
|
|
window.tinymce.PluginManager.add('tasklist', getTasklistPlugin());
|
|
|
|
if (options.drawioUrl) {
|
|
window.tinymce.PluginManager.add('drawio', getDrawioPlugin(options));
|
|
plugins.push('drawio');
|
|
}
|
|
|
|
return plugins.filter(plugin => Boolean(plugin));
|
|
}
|
|
|
|
/**
|
|
* 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 setupCallback(editor) {
|
|
function editorChange() {
|
|
if (options.darkMode) {
|
|
editor.contentDocument.documentElement.classList.add('dark-mode');
|
|
}
|
|
window.$events.emit('editor-html-change', '');
|
|
}
|
|
|
|
editor.on('ExecCommand change input NodeChange ObjectResized', editorChange);
|
|
listenForCommonEvents(editor);
|
|
listenForDragAndPaste(editor, options);
|
|
|
|
editor.on('init', () => {
|
|
editorChange();
|
|
scrollToQueryString(editor);
|
|
window.editor = editor;
|
|
registerShortcuts(editor);
|
|
});
|
|
|
|
editor.on('PreInit', () => {
|
|
setupFilters(editor);
|
|
});
|
|
|
|
handleEmbedAlignmentChanges(editor);
|
|
|
|
// 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
|
|
*/
|
|
function getContentStyle(options) {
|
|
return `
|
|
html, body, html.dark-mode {
|
|
background: ${options.darkMode ? '#222' : '#fff'};
|
|
}
|
|
body {
|
|
padding-left: 15px !important;
|
|
padding-right: 15px !important;
|
|
height: initial !important;
|
|
margin:0!important;
|
|
margin-left: auto! important;
|
|
margin-right: auto !important;
|
|
overflow-y: hidden !important;
|
|
}`.trim().replace('\n', '');
|
|
}
|
|
|
|
/**
|
|
* @param {WysiwygConfigOptions} options
|
|
* @return {Object}
|
|
*/
|
|
export function build(options) {
|
|
// Set language
|
|
window.tinymce.addI18n(options.language, options.translationMap);
|
|
|
|
// BookStack Version
|
|
const version = document.querySelector('script[src*="/dist/app.js"]').getAttribute('src').split('?version=')[1];
|
|
|
|
// Return config object
|
|
return {
|
|
width: '100%',
|
|
height: '100%',
|
|
selector: '#html-editor',
|
|
cache_suffix: `?version=${version}`,
|
|
content_css: [
|
|
window.baseUrl('/dist/styles.css'),
|
|
],
|
|
branding: false,
|
|
skin: options.darkMode ? 'tinymce-5-dark' : 'tinymce-5',
|
|
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,
|
|
remove_trailing_brs: false,
|
|
statusbar: false,
|
|
menubar: false,
|
|
paste_data_images: false,
|
|
extended_valid_elements: 'pre[*],svg[*],div[drawio-diagram],details[*],summary[*],div[*],li[class|checked|style]',
|
|
automatic_uploads: false,
|
|
custom_elements: 'doc-root,code-block',
|
|
valid_children: [
|
|
'-div[p|h1|h2|h3|h4|h5|h6|blockquote|code-block]',
|
|
'+div[pre|img]',
|
|
'-doc-root[doc-root|#text]',
|
|
'-li[details]',
|
|
'+code-block[pre]',
|
|
'+doc-root[p|h1|h2|h3|h4|h5|h6|blockquote|code-block|div|hr]',
|
|
].join(','),
|
|
plugins: gatherPlugins(options),
|
|
contextmenu: false,
|
|
toolbar: getPrimaryToolbar(options),
|
|
content_style: getContentStyle(options),
|
|
style_formats: styleFormats,
|
|
style_formats_merge: false,
|
|
media_alt_source: false,
|
|
media_poster: false,
|
|
formats,
|
|
table_style_by_css: true,
|
|
table_use_colgroups: true,
|
|
file_picker_types: 'file image',
|
|
color_map: colorMap,
|
|
file_picker_callback: filePickerCallback,
|
|
paste_preprocess(plugin, args) {
|
|
const {content} = args;
|
|
if (content.indexOf('<img src="file://') !== -1) {
|
|
args.content = '';
|
|
}
|
|
},
|
|
init_instance_callback(editor) {
|
|
const head = editor.getDoc().querySelector('head');
|
|
head.innerHTML += fetchCustomHeadContent();
|
|
},
|
|
setup(editor) {
|
|
registerCustomIcons(editor);
|
|
registerAdditionalToolbars(editor);
|
|
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
|
|
*/
|