/** * 2007-2019 PrestaShop and Contributors * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * https://opensource.org/licenses/OSL-3.0 * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@prestashop.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade PrestaShop to newer * versions in the future. If you wish to customize PrestaShop for your * needs please refer to https://www.prestashop.com for more information. * * @author PrestaShop SA * @copyright 2007-2019 PrestaShop SA and Contributors * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) * International Registered Trademark & Property of PrestaShop SA */ const $ = window.$; /** * This class init TinyMCE instances in the back-office. It is wildly inspired by * the scripts from js/admin And it actually loads TinyMCE from the js/tiny_mce * folder along with its modules. One improvement could be to install TinyMCE via * npm and fully integrate in the back-office theme. */ class TinyMCEEditor { constructor(options) { options = options || {}; this.tinyMCELoaded = false; if (typeof options.baseAdminUrl == 'undefined') { if (typeof window.baseAdminDir != 'undefined') { options.baseAdminUrl = window.baseAdminDir; } else { const pathParts = window.location.pathname.split('/'); pathParts.every(function(pathPart) { if (pathPart !== '') { options.baseAdminUrl = `/${pathPart}/`; return false; } return true; }); } } if (typeof options.langIsRtl == 'undefined') { options.langIsRtl = typeof window.lang_is_rtl != 'undefined' ? window.lang_is_rtl === '1' : false; } this.setupTinyMCE(options); } /** * Initial setup which checks if the tinyMCE library is already loaded. * * @param config */ setupTinyMCE(config) { if (typeof tinyMCE === 'undefined') { this.loadAndInitTinyMCE(config); } else { this.initTinyMCE(config); } } /** * Prepare the config and init all TinyMCE editors * * @param config */ initTinyMCE(config) { config = Object.assign({ selector: '.rte', plugins: 'align colorpicker link image filemanager table media placeholder advlist code table autoresize', browser_spellcheck: true, toolbar1: 'code,colorpicker,bold,italic,underline,strikethrough,blockquote,link,align,bullist,numlist,table,image,media,formatselect', toolbar2: '', external_filemanager_path: config.baseAdminUrl + 'filemanager/', filemanager_title: 'File manager', external_plugins: { 'filemanager': config.baseAdminUrl + 'filemanager/plugin.min.js' }, language: iso_user, content_style : (config.langIsRtl ? 'body {direction:rtl;}' : ''), skin: 'prestashop', menubar: false, statusbar: false, relative_urls: false, convert_urls: false, entity_encoding: 'raw', extended_valid_elements: 'em[class|name|id],@[role|data-*|aria-*]', valid_children: '+*[*]', valid_elements: '*[*]', rel_list:[ { title: 'nofollow', value: 'nofollow' } ], editor_selector :'autoload_rte', init_instance_callback: () => { this.changeToMaterial(); }, setup : (editor) => { this.setupEditor(editor); }, }, config); if (typeof config.editor_selector != 'undefined') { config.selector = '.' + config.editor_selector; } // Change icons in popups $('body').on('click', '.mce-btn, .mce-open, .mce-menu-item', () => { this.changeToMaterial(); }); tinyMCE.init(config); this.watchTabChanges(config); } /** * Setup TinyMCE editor once it has been initialized * * @param editor */ setupEditor(editor) { editor.on('loadContent', (event) => { this.handleCounterTiny(event.target.id); }); editor.on('change', (event) => { tinyMCE.triggerSave(); this.handleCounterTiny(event.target.id); }); editor.on('blur', () => { tinyMCE.triggerSave(); }); } /** * When the editor is inside a tab it can cause a bug on tab switching. * So we check if the editor is contained in a navigation and refresh the editor when its * parent tab is shown. * * @param config */ watchTabChanges(config) { $(config.selector).each((index, textarea) => { const translatedField = $(textarea).closest('.translation-field'); const tabContainer = $(textarea).closest('.translations.tabbable'); if (translatedField.length && tabContainer.length) { const textareaLocale = translatedField.data('locale'); const textareaLinkSelector = '.nav-item a[data-locale="'+textareaLocale+'"]'; $(textareaLinkSelector, tabContainer).on('shown.bs.tab', () => { const editor = tinyMCE.get(textarea.id); if (editor) { //Reset content to force refresh of editor editor.setContent(editor.getContent()); } }); } }); } /** * Loads the TinyMCE javascript library and then init the editors * * @param config */ loadAndInitTinyMCE(config) { if (this.tinyMCELoaded) { return; } this.tinyMCELoaded = true; const pathArray = config.baseAdminUrl.split('/'); pathArray.splice((pathArray.length - 2), 2); const finalPath = pathArray.join('/'); window.tinyMCEPreInit = {}; window.tinyMCEPreInit.base = finalPath+'/js/tiny_mce'; window.tinyMCEPreInit.suffix = '.min'; $.getScript(`${finalPath}/js/tiny_mce/tinymce.min.js`, () => {this.setupTinyMCE(config)}); } /** * Replace initial TinyMCE icons with material icons */ changeToMaterial() { let materialIconAssoc = { 'mce-i-code': 'code', 'mce-i-none': 'format_color_text', 'mce-i-bold': 'format_bold', 'mce-i-italic': 'format_italic', 'mce-i-underline': 'format_underlined', 'mce-i-strikethrough': 'format_strikethrough', 'mce-i-blockquote': 'format_quote', 'mce-i-link': 'link', 'mce-i-alignleft': 'format_align_left', 'mce-i-aligncenter': 'format_align_center', 'mce-i-alignright': 'format_align_right', 'mce-i-alignjustify': 'format_align_justify', 'mce-i-bullist': 'format_list_bulleted', 'mce-i-numlist': 'format_list_numbered', 'mce-i-image': 'image', 'mce-i-table': 'grid_on', 'mce-i-media': 'video_library', 'mce-i-browse': 'attachment', 'mce-i-checkbox': '', }; $.each(materialIconAssoc, function (index, value) { $(`.${index}`).replaceWith(value); }); } /** * Updates the characters counter * * @param id */ handleCounterTiny(id) { const textarea = $(`#${id}`); const counter = textarea.attr('counter'); const counterType = textarea.attr('counter_type'); const max = tinyMCE.activeEditor.getBody().textContent.length; textarea.parent().find('span.currentLength').text(max); if ('recommended' !== counterType && max > counter) { textarea.parent().find('span.maxLength').addClass('text-danger'); } else { textarea.parent().find('span.maxLength').removeClass('text-danger'); } } } export default TinyMCEEditor;