X-Git-Url: http://source.bookstackapp.com/bookstack/blobdiff_plain/536ad142764bd2ddf32e43aa4123f079b5057afb..refs/pull/3593/head:/resources/js/wysiwyg/config.js diff --git a/resources/js/wysiwyg/config.js b/resources/js/wysiwyg/config.js index 7fa3b0f26..66c22d98e 100644 --- a/resources/js/wysiwyg/config.js +++ b/resources/js/wysiwyg/config.js @@ -2,6 +2,7 @@ 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 {getPlugin as getCodeeditorPlugin} from "./plugin-codeeditor"; import {getPlugin as getDrawioPlugin} from "./plugin-drawio"; @@ -9,6 +10,7 @@ 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"; const style_formats = [ {title: "Large Header", format: "h2", preview: 'color: blue;'}, @@ -60,56 +62,12 @@ function file_picker_callback(callback, value, meta) { /** * @param {WysiwygConfigOptions} options - * @return {{toolbar: string, groupButtons: 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 details' - } - }; - - 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} + * @return {string[]} */ function gatherPlugins(options) { const plugins = [ "image", - "imagetools", "table", - "paste", "link", "autolink", "fullscreen", @@ -122,6 +80,7 @@ function gatherPlugins(options) { "imagemanager", "about", "details", + "tasklist", options.textDirection === 'rtl' ? 'directionality' : '', ]; @@ -130,13 +89,14 @@ function gatherPlugins(options) { window.tinymce.PluginManager.add('imagemanager', getImagemanagerPlugin(options)); window.tinymce.PluginManager.add('about', getAboutPlugin(options)); window.tinymce.PluginManager.add('details', getDetailsPlugin(options)); + window.tinymce.PluginManager.add('tasklist', getTasklistPlugin(options)); if (options.drawioUrl) { window.tinymce.PluginManager.add('drawio', getDrawioPlugin(options)); plugins.push('drawio'); } - return plugins.filter(plugin => Boolean(plugin)).join(' '); + return plugins.filter(plugin => Boolean(plugin)); } /** @@ -152,6 +112,23 @@ function fetchCustomHeadContent() { return headContentLines.slice(startLineIndex + 1, endLineIndex).join('\n'); } +/** + * Setup a serializer filter for
tags to ensure they're not rendered + * within code blocks and that we use newlines there instead. + * @param {Editor} editor + */ +function setupBrFilter(editor) { + editor.serializer.addNodeFilter('br', function(nodes) { + for (const node of nodes) { + if (node.parent && node.parent.name === 'code') { + const newline = tinymce.html.Node.create('#text'); + newline.value = '\n'; + node.replace(newline); + } + } + }); +} + /** * @param {WysiwygConfigOptions} options * @return {function(Editor)} @@ -169,6 +146,10 @@ function getSetupCallback(options) { window.editor = editor; }); + editor.on('PreInit', () => { + setupBrFilter(editor); + }); + function editorChange() { const content = editor.getContent(); if (options.darkMode) { @@ -210,16 +191,6 @@ body { }`.trim().replace('\n', ''); } -// Custom "Document Root" element, a custom element to identify/define -// block that may act as another "editable body". -// Using a custom node means we can identify and add/remove these as desired -// without affecting user content. -class DocRootElement extends HTMLDivElement { - constructor() { - super(); - } -} - /** * @param {WysiwygConfigOptions} options * @return {Object} @@ -228,21 +199,21 @@ export function build(options) { // Set language window.tinymce.addI18n(options.language, options.translationMap); - // Build toolbar content - const {toolbar, groupButtons: toolBarGroupButtons} = buildToolbar(options); - // Define our custom root node - customElements.define('doc-root', DocRootElement, {extends: 'div'}); + + // 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 ? 'oxide-dark' : 'oxide', + skin: options.darkMode ? 'tinymce-5-dark' : 'tinymce-5', body_class: 'page-content', browser_spellcheck: true, relative_urls: false, @@ -251,23 +222,32 @@ export function build(options) { 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[*],doc-root', + extended_valid_elements: 'pre[*],svg[*],div[drawio-diagram],details[*],summary[*],div[*],li[class|checked]', automatic_uploads: false, - custom_elements: 'doc-root', - valid_children: "-div[p|h1|h2|h3|h4|h5|h6|blockquote|div],+div[pre],+div[img],+doc-root[p|h1|h2|h3|h4|h5|h6|blockquote|pre|img|ul|ol],-doc-root[doc-root|#text]", + 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]" + ].join(','), plugins: gatherPlugins(options), - imagetools_toolbar: 'imageoptions', contextmenu: false, - toolbar: toolbar, + toolbar: getPrimaryToolbar(options), content_style: getContentStyle(options), style_formats, 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', file_picker_callback, paste_preprocess(plugin, args) { @@ -281,9 +261,7 @@ export function build(options) { head.innerHTML += fetchCustomHeadContent(); }, setup(editor) { - for (const [key, config] of Object.entries(toolBarGroupButtons)) { - editor.ui.registry.addGroupToolbarButton(key, config); - } + registerAdditionalToolbars(editor, options); getSetupCallback(options)(editor); }, };