/** * @copyright (C) 2019 Open Source Matters, Inc. * @license GNU General Public License version 2 or later; see LICENSE.txt */ if (!Joomla) { throw new Error('Joomla API is not properly initialised'); } /** * Fake TinyMCE object to allow to use TinyMCE translation for the button labels * * @since 3.7.0 */ const tinymce = { langCode: 'en', langStrings: {}, icons: { 'accessibility-check': '', 'align-center': '', 'align-justify': '', 'align-left': '', 'align-none': '', 'align-right': '', 'arrow-left': '', 'arrow-right': '', backcolor: '', bold: '', bookmark: '', 'border-width': '', brightness: '', browse: '', cancel: '', 'change-case': '', 'character-count': '', checklist: '', checkmark: '', 'chevron-down': '', 'chevron-left': '', 'chevron-right': '', 'chevron-up': '', close: '', 'code-sample': '', 'color-levels': '', 'color-picker': '', 'color-swatch-remove-color': '', 'color-swatch': '', comment: '', contrast: '', copy: '', crop: '', cut: '', 'document-properties': '', drag: '', duplicate: '', 'edit-image': '', 'embed-page': '', embed: '', emoji: '', fill: '', 'flip-horizontally': '', 'flip-vertically': '', forecolor: '', 'format-painter': '', fullscreen: '', gamma: '', help: '', home: '', 'horizontal-rule': '', 'image-options': '', image: '', indent: '', indeterminate: '', info: '', 'insert-character': '', 'insert-time': '', invert: '', italic: '', language: '', line: '', lineheight: '', link: '', 'list-bull-circle': '', 'list-bull-default': '', 'list-bull-square': '', 'list-num-default': '', 'list-num-lower-alpha': '', 'list-num-lower-greek': '', 'list-num-lower-roman': '', 'list-num-upper-alpha': '', 'list-num-upper-roman': '', lock: '', ltr: '', 'more-drawer': '', 'new-document': '', 'new-tab': '', 'non-breaking': '', notice: '', 'ordered-list': '', orientation: '', outdent: '', 'page-break': '', paragraph: '', 'paste-text': '', paste: '', 'permanent-pen': '', plus: '', preferences: '', preview: '', print: '', quote: '', redo: '', reload: '', 'remove-formatting': '', remove: '', 'resize-handle': '', resize: '', 'restore-draft': '', 'rotate-left': '', 'rotate-right': '', rtl: '', save: '', search: '', 'select-all': '', selected: '', settings: '', sharpen: '', sourcecode: '', 'spell-check': '', 'strike-through': '', subscript: '', superscript: '', 'table-cell-properties': '', 'table-cell-select-all': '', 'table-cell-select-inner': '', 'table-delete-column': '', 'table-delete-row': '', 'table-delete-table': '', 'table-insert-column-after': '', 'table-insert-column-before': '', 'table-insert-row-above': '', 'table-insert-row-after': '', 'table-left-header': '', 'table-merge-cells': '', 'table-row-properties': '', 'table-split-cells': '', 'table-top-header': '', table: '', template: '', 'temporary-placeholder': '', toc: '', translate: '', underline: '', undo: '', unlink: '', unlock: '', 'unordered-list': '', unselected: '', upload: '', user: '', visualblocks: '', visualchars: '', warning: '', 'zoom-in': '', 'zoom-out': '' }, iconsmap: { aligncenter: 'align-center', alignjustify: 'align-justify', alignleft: 'align-left', alignright: 'align-right', anchor: 'bookmark', blockquote: 'quote', bullist: 'unordered-list', charmap: 'insert-character', code: 'sourcecode', codesample: 'code-sample', emoticons: 'emoji', hr: 'horizontal-rule', insertdatetime: 'insert-time', media: 'embed', nonbreaking: 'non-breaking', numlist: 'ordered-list', pagebreak: 'page-break', pastetext: 'paste-text', removeformat: 'remove-formatting', searchreplace: 'search', strikethrough: 'strike-through' }, addI18n: (code, strings) => { tinymce.langCode = code; tinymce.langStrings = strings || {}; }, translate: string => tinymce.langStrings[string] || string, showIcon: name => { const iconname = tinymce.iconsmap[name] || name; return tinymce.icons[iconname] || tinymce.icons[name] || name; } }; window.tinymce = tinymce; const TinyMCEBuilder = (container, options) => { const $sourceMenu = container.querySelector('.tinymce-builder-menu.source'); const $sourceToolbar = container.querySelector('.tinymce-builder-toolbar.source'); const $targetMenu = container.querySelectorAll('.tinymce-builder-menu.target'); const $targetToolbar = container.querySelectorAll('.tinymce-builder-toolbar.target'); /** * Append input to the button item * @param {HTMLElement} element * @param {String} group * @param {String} set * * @since 3.7.0 */ const appendInput = (element, group, set) => { const name = `${options.formControl}[${set}][${group}][]`; const value = element.getAttribute('data-name'); element.innerHTML += Joomla.sanitizeHtml(``); }; /** * Create the element needed for renderBar() * @param {String} name * @param {Object} info * @param {String} type * * @return {jQuery} * * @since 3.7.0 */ const createButton = (name, info, type) => { const title = tinymce.translate(info.label); let content = ''; let bclass = 'tox-mbtn'; if (type === 'menu') { content = title; } else if (info.text) { const text = tinymce.translate(info.text); bclass += ' tox-tbtn--bespoke'; const chevron = tinymce.showIcon('chevron-down'); content = info.text !== '|' ? `${text}
${chevron}
` : text; } else { content = tinymce.showIcon(name); } return ``; }; /** * Render the toolbar/menubar * * @param {HTMLElement} box The toolbar container * @param {String} type The type toolbar or menu * @param {Array|null} value The value * @param {Boolean} withInput Whether append input * * @since 3.7.0 */ const renderBar = (box, type, val, withInput) => { const group = box.getAttribute('data-group'); const set = box.getAttribute('data-set'); const items = type === 'menu' ? options.menus : options.buttons; const value = val || JSON.parse(box.getAttribute('data-value')) || []; let item; let name; for (let i = 0, l = value.length; i < l; i += 1) { name = value[i]; item = items[name]; if (item) { // Buttons are predefined in this file, so safe box.innerHTML += createButton(name, item, type); const newbutton = box.querySelector('.tox-mbtn:last-child'); // Enable tooltip if (newbutton && newbutton.tooltip) { newbutton.tooltip({ trigger: 'hover' }); } // Add input if (withInput) { appendInput(newbutton, group, set); } } } }; /** * Clear the pane for specific set * @param {Object} sets Options {set: 1} */ const clearPane = sets => { const { set: item } = sets; $targetMenu.forEach(elem => { if (elem.getAttribute('data-set') === item) { elem.innerHTML = ''; } }); $targetToolbar.forEach(elem => { if (elem.getAttribute('data-set') === item) { elem.innerHTML = ''; } }); }; /** * Set Selected preset to specific set * @param {Object} attrib Options {set: 1, preset: 'presetName'} */ const setPreset = attrib => { const { set: item } = attrib; const preset = options.toolbarPreset[attrib.preset] || null; if (!preset) { throw new Error(`Unknown Preset "${attrib.preset}"`); } clearPane(attrib); Object.keys(preset).forEach(group => { const type = group === 'menu' ? 'menu' : 'toolbar'; // Find correct container for current set if (group === 'menu') { $targetMenu.forEach(target => { if (target.getAttribute('data-group') === group && target.getAttribute('data-set') === item) { renderBar(target, type, preset[group], true); } }); } else { $targetToolbar.forEach(target => { if (target.getAttribute('data-group') === group && target.getAttribute('data-set') === item) { renderBar(target, type, preset[group], true); } }); } }); }; // Build menu + toolbar renderBar($sourceMenu, 'menu'); renderBar($sourceToolbar, 'toolbar'); // Initialize drag & drop /* global dragula */ const drakeMenu = dragula([$sourceMenu], { copy: (el, source) => source === $sourceMenu, accepts: (el, target) => target !== $sourceMenu, removeOnSpill: true }).on('drag', () => { $targetMenu.forEach(target => { target.classList.add('drop-area-highlight'); }); }).on('dragend', () => { $targetMenu.forEach(target => { target.classList.remove('drop-area-highlight'); }); }).on('drop', (el, target) => { if (target !== $sourceMenu) { appendInput(el, target.getAttribute('data-group'), target.getAttribute('data-set')); } }); $targetMenu.forEach(target => { renderBar(target, 'menu', null, true); drakeMenu.containers.push(target); }); const drakeToolbar = dragula([$sourceToolbar], { copy: (el, source) => source === $sourceToolbar, accepts: (el, target) => target !== $sourceToolbar, removeOnSpill: true }).on('drag', () => { $targetToolbar.forEach(target => { target.classList.add('drop-area-highlight'); }); }).on('dragend', () => { $targetToolbar.forEach(target => { target.classList.remove('drop-area-highlight'); }); }).on('drop', (el, target) => { if (target !== $sourceToolbar) { appendInput(el, target.getAttribute('data-group'), target.getAttribute('data-set')); } }); $targetToolbar.forEach(target => { renderBar(target, 'toolbar', null, true); drakeToolbar.containers.push(target); }); // Bind actions buttons const actionButtons = container.querySelectorAll('.button-action'); actionButtons.forEach(elem => { elem.addEventListener('click', ({ target }) => { const action = target.getAttribute('data-action'); const actionoptions = {}; [].forEach.call(target.attributes, attrib => { if (/^data-/.test(attrib.name)) { const key = attrib.name.substr(5); actionoptions[key] = attrib.value; } }); // Don't allow wild function calling switch (action) { case 'clearPane': clearPane(actionoptions); break; case 'setPreset': setPreset(actionoptions); break; default: throw new Error(`Unsupported action: ${action}`); } }); }); }; const options = Joomla.getOptions ? Joomla.getOptions('plg_editors_tinymce_builder', {}) : Joomla.optionsStorage.plg_editors_tinymce_builder || {}; const builder = document.getElementById('joomla-tinymce-builder'); document.addEventListener('DOMContentLoaded', () => TinyMCEBuilder(builder, options)); const selects = builder.querySelectorAll('.access-select'); // Allow to select the group only once per the set const toggleAvailableOption = () => { selects.forEach(select => { select.enableAllOptions(); }); // Disable already selected options in the other selects selects.forEach(select => { const values = select.value; selects.forEach(select1 => { if (select === select1) { return; } values.forEach(value => { select1.disableByValue(value); }); }); }); }; window.addEventListener('load', () => toggleAvailableOption()); // Allow to select the group only once per the set selects.forEach(select => { select.addEventListener('change', () => { toggleAvailableOption(); }); });