X-Git-Url: http://source.bookstackapp.com/bookstack/blobdiff_plain/ef211a76aede33136416d50b6c636de7bff3fbed..refs/pull/3693/head:/resources/js/wysiwyg/config.js diff --git a/resources/js/wysiwyg/config.js b/resources/js/wysiwyg/config.js index 1594646d8..52c52592c 100644 --- a/resources/js/wysiwyg/config.js +++ b/resources/js/wysiwyg/config.js @@ -1,12 +1,16 @@ import {register as registerShortcuts} from "./shortcuts"; import {listen as listenForCommonEvents} from "./common-events"; -import {scrollToQueryString, fixScrollForMobile} from "./scrolling"; +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"; 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;'}, @@ -15,7 +19,6 @@ const style_formats = [ {title: "Tiny Header", format: "h5"}, {title: "Paragraph", format: "p", exact: true, classes: ''}, {title: "Blockquote", format: "blockquote"}, - {title: "Inline Code", inline: "code"}, { title: "Callouts", items: [ {title: "Information", format: 'calloutinfo'}, @@ -27,7 +30,6 @@ const style_formats = [ ]; 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'}, @@ -37,6 +39,35 @@ const formats = { calloutdanger: {block: 'p', exact: true, attributes: {class: 'callout danger'}} }; +const color_map = [ + '#BFEDD2', '', + '#FBEEB8', '', + '#F8CAC6', '', + '#ECCAFA', '', + '#C2E0F4', '', + + '#2DC26B', '', + '#F1C40F', '', + '#E03E2D', '', + '#B96AD9', '', + '#3598DB', '', + + '#169179', '', + '#E67E23', '', + '#BA372A', '', + '#843FA1', '', + '#236FA1', '', + + '#ECF0F1', '', + '#CED4D9', '', + '#95A5A6', '', + '#7E8C8D', '', + '#34495E', '', + + '#000000', '', + '#ffffff', '' +]; + function file_picker_callback(callback, value, meta) { // field_name, url, type, win @@ -60,36 +91,12 @@ function file_picker_callback(callback, value, meta) { /** * @param {WysiwygConfigOptions} options - * @return {string} - */ -function buildToolbar(options) { - const textDirPlugins = options.textDirection === 'rtl' ? 'ltr rtl' : ''; - - const toolbar = [ - 'undo redo', - 'styleselect', - 'bold italic underline strikethrough superscript subscript', - 'forecolor backcolor', - 'alignleft aligncenter alignright alignjustify', - 'bullist numlist outdent indent', - textDirPlugins, - 'table imagemanager-insert link hr codeeditor drawio media', - 'removeformat code ${textDirPlugins} fullscreen' - ]; - - return toolbar.filter(row => Boolean(row)).join(' | '); -} - -/** - * @param {WysiwygConfigOptions} options - * @return {string} + * @return {string[]} */ function gatherPlugins(options) { const plugins = [ "image", - "imagetools", "table", - "paste", "link", "autolink", "fullscreen", @@ -100,31 +107,54 @@ function gatherPlugins(options) { "codeeditor", "media", "imagemanager", + "about", + "details", + "tasklist", 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)); + 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)); } /** - * Load custom HTML head content from the settings into the editor. - * TODO: We should be able to get this from current parent page? + * 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() === ''); + const endLineIndex = headContentLines.findIndex(line => line.trim() === ''); + if (startLineIndex === -1 || endLineIndex === -1) { + return '' + } + 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 loadCustomHeadContent(editor) { - window.$http.get(window.baseUrl('/custom-head-content')).then(resp => { - if (!resp.data) return; - let head = editor.getDoc().querySelector('head'); - head.innerHTML += resp.data; +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); + } + } }); } @@ -136,14 +166,17 @@ 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; + registerShortcuts(editor); + }); + + editor.on('PreInit', () => { + setupBrFilter(editor); }); function editorChange() { @@ -154,20 +187,39 @@ function getSetupCallback(options) { window.$events.emit('editor-html-change', content); } - // TODO - Update to standardise across both editors - // Use events within listenForBookStackEditorEvents instead (Different event signature) - window.$events.listen('editor-html-update', html => { - editor.setContent(html); - editor.selection.select(editor.getBody(), true); - editor.selection.collapse(false); - editorChange(html); - }); - // 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} @@ -177,16 +229,20 @@ 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 ? 'oxide-dark' : 'oxide', + skin: options.darkMode ? 'tinymce-5-dark' : 'tinymce-5', body_class: 'page-content', browser_spellcheck: true, relative_urls: false, @@ -195,34 +251,49 @@ 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]', + extended_valid_elements: 'pre[*],svg[*],div[drawio-diagram],details[*],summary[*],div[*],li[class|checked]', automatic_uploads: false, - valid_children: "-div[p|h1|h2|h3|h4|h5|h6|blockquote],+div[pre],+div[img]", + 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: buildToolbar(options), - 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;}`, + 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', + color_map, file_picker_callback, paste_preprocess(plugin, args) { - let content = args.content; + const content = args.content; if (content.indexOf('