/* global I18n GLOBAL_CONSTANTS HelperModule SmartAnnotation TinyMCE */ import tinyMCE from 'tinymce/tinymce'; import 'tinymce/models/dom'; import 'tinymce/icons/default'; import 'tinymce/themes/silver'; import 'tinymce/plugins/table'; import 'tinymce/plugins/autosave'; import 'tinymce/plugins/autoresize'; import 'tinymce/plugins/link'; import 'tinymce/plugins/advlist'; import 'tinymce/plugins/codesample'; import 'tinymce/plugins/code'; import 'tinymce/plugins/autolink'; import 'tinymce/plugins/lists'; import 'tinymce/plugins/image'; import 'tinymce/plugins/charmap'; import 'tinymce/plugins/anchor'; import 'tinymce/plugins/searchreplace'; import 'tinymce/plugins/wordcount'; import 'tinymce/plugins/visualblocks'; import 'tinymce/plugins/visualchars'; import 'tinymce/plugins/insertdatetime'; import 'tinymce/plugins/nonbreaking'; import 'tinymce/plugins/save'; import 'tinymce/plugins/help'; import 'tinymce/plugins/help/js/i18n/keynav/en'; import 'tinymce/plugins/quickbars'; import 'tinymce/plugins/directionality'; import './tinymce/custom_image_uploader/plugin'; import './tinymce/marvinjs/plugin'; import './tinymce/image_toolbar/plugin'; // Content styles, including inline UI like fake cursors // All the above CSS files are loaded on to the page but these two must // be loaded into the editor iframe so they are loaded as strings and passed // to the init function. import 'raw-loader'; import contentCss from '!!raw-loader!tinymce/skins/content/default/content.min.css'; import contentUiCss from '!!raw-loader!tinymce/skins/ui/tinymce-5/content.min.css'; const contentPStyle = `p { margin: 0; padding: 0;}`; const contentBodyStyle = `body { font-family: "SN Inter", "Open Sans", Arial, Helvetica, sans-serif }`; const contentStyle = [contentCss, contentUiCss, contentBodyStyle, contentPStyle].map((s) => s.toString()).join('\n'); // Optional pre-initialization method if (typeof(window.preTinyMceInit) === 'function') { window.preTinyMceInit(); } window.TinyMCE = (() => { function makeItDirty(editor) { const editorForm = $(editor.getContainer()).closest('form'); editorForm.find('.tinymce-status-badge').addClass('hidden'); $(editor.getContainer()).find('.tinymce-save-button').removeClass('hidden'); } // Get LocalStorage auto save path function getAutoSavePrefix(editor) { let prefix = editor.getParam('autosave_prefix', 'tinymce-autosave-{path}{query}{hash}-{id}-'); prefix = prefix.replace(/\{path\}/g, document.location.pathname); prefix = prefix.replace(/\{query\}/g, document.location.search); prefix = prefix.replace(/\{hash\}/g, document.location.hash); prefix = prefix.replace(/\{id\}/g, editor.id); return prefix; } // Handles autosave notification if draft is available in the local storage function restoreDraftNotification(selector, editor) { const prefix = getAutoSavePrefix(editor); const lastDraftTime = parseInt(tinyMCE.util.LocalStorage.getItem(`${prefix}time`), 10); const lastUpdated = $(selector).data('last-updated'); let notificationBar; const restoreBtn = $(''); const cancelBtn = $(''); // Check whether we have draft stored if (editor.plugins.autosave.hasDraft()) { notificationBar = $('
'); if (lastDraftTime < lastUpdated) { notificationBar.html(`${I18n.t('tiny_mce.older_version_available')}`); } else { notificationBar.html(`${I18n.t('tiny_mce.newer_version_available')}`); } // Add notification bar $(notificationBar).append(restoreBtn).append(cancelBtn); $(editor.contentAreaContainer).before(notificationBar); // Prevents save on blur if clicking draft notification $('.restore-draft-notification').on('mousedown', () => { editor.isBlurTempDisabled = true; setTimeout(() => { editor.isBlurTempDisabled = false; }, 500); }); $(restoreBtn).click(() => { editor.plugins.autosave.restoreDraft(); makeItDirty(editor); notificationBar.remove(); }); $(cancelBtn).click(() => { notificationBar.remove(); }); } setTimeout(() => { tinyMCE.activeEditor.execCommand('mceAutoResize') }, 500); } function initCssOverrides(editor) { const editorIframe = $(`#${editor.id}`).next().find('.tox-edit-area iframe'); const primaryColor = '#104da9'; editorIframe.contents().find('head').append(``); editorIframe.contents().find('head').append($('#font-css-pack').clone()); } function draftLocation() { return `tinymce-drafts-${document.location.pathname}`; } function removeDraft(editor, textAreaObject) { const location = draftLocation(); const storedDrafts = JSON.parse(sessionStorage.getItem(location) || '[]'); const draftId = storedDrafts.indexOf(textAreaObject.data('tinymce-object')); if (draftId > -1) { storedDrafts.splice(draftId, 1); } if (storedDrafts.length) { sessionStorage.setItem(location, JSON.stringify(storedDrafts)); } else { sessionStorage.removeItem(location); } } // Update scroll position after exit function updateScrollPosition(editorForm) { if (editorForm.offset().top < $(window).scrollTop()) { $(window).scrollTop(editorForm.offset().top - 150); } } function saveAction(editor) { const editorForm = $(editor.getContainer()).closest('form'); editorForm.clearFormErrors(); editor.setProgressState(1); editor.save(); editorForm.submit(); updateScrollPosition(editorForm); } // returns a public API for TinyMCE editor return { init: (selector, options = {}) => { const textAreaObject = $(selector); let editorToolbaroffset = 0; if (typeof tinyMCE !== 'undefined') { // Hide element containing HTML view of RTE field const tinyMceContainer = $(selector).closest('form').find('.tinymce-view'); const editorForm = $(selector).closest('form'); const tinyMceInitSize = tinyMceContainer.height(); editorForm.parent().height(tinyMceInitSize); tinyMceContainer.addClass('hidden'); const plugins = ` image table autosave autoresize link advlist codesample code autolink lists charmap anchor searchreplace wordcount visualblocks visualchars insertdatetime nonbreaking save directionality customimageuploader marvinjs custom_image_toolbar help quickbars ${window.extraTinyMcePlugins ? window.extraTinyMcePlugins : ''} `; // if (typeof (MarvinJsEditor) !== 'undefined') plugins += ' marvinjsplugin'; if (textAreaObject.data('objectType') === 'step' || textAreaObject.data('objectType') === 'result_text') { document.location.hash = `${textAreaObject.data('objectType')}_${textAreaObject.data('objectId')}`; } editorToolbaroffset = 0; $.each($('.sticky-header-element, .sticky-header'), (_index, element) => { editorToolbaroffset += $(element).outerHeight(); }); return tinyMCE.init({ cache_suffix: '?v=6.5.1-19', // This suffix should be changed any time library is updated selector, skin: false, editimage_fetch_image: img => new Promise((resolve) => { // Appending a timestamp to an image URL bypasses Chrome’s cache, resolving occasional CORS errors fetch(img.src + '?t=' + new Date().getTime()) .then(response => response.blob()) .then(blob => resolve(blob)); }), content_css: false, content_style: contentStyle, convert_urls: false, promotion: false, menu: { insert: { title: 'Insert', items: 'link codesample inserttable | charmap hr | nonbreaking anchor | insertdatetime customimageuploader marvinjs' }, }, block_formats: 'Paragraph=p; Header 1=h1; Header 2=h2; Header 3=h3; Preformatted=pre', menubar: 'file edit view insert format table', toolbar: window.customTinyMceToolbar || 'undo redo restoredraft | insert | styleselect | bold italic | forecolor backcolor | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | table | customimageuploader | marvinjs link codesample | help', plugins, contextmenu: '', autoresize_bottom_margin: 20, placeholder: options.placeholder, toolbar_sticky: true, toolbar_sticky_offset: editorToolbaroffset, codesample_global_prismjs: true, codesample_languages: [ { text: 'R', value: 'r' }, { text: 'MATLAB', value: 'matlab' }, { text: 'Python', value: 'python' }, { text: 'JSON', value: 'javascript' }, { text: 'HTML/XML', value: 'markup' }, { text: 'JavaScript', value: 'javascript' }, { text: 'CSS', value: 'css' }, { text: 'PHP', value: 'php' }, { text: 'Ruby', value: 'ruby' }, { text: 'Java', value: 'java' }, { text: 'C', value: 'c' }, { text: 'C#', value: 'csharp' }, { text: 'C++', value: 'cpp' } ], browser_spellcheck: true, branding: false, fixed_toolbar_container: '#mytoolbar', autosave_restore_when_empty: false, autosave_interval: '1s', autosave_retention: '1440m', removed_menuitems: 'newdocument', object_resizing: true, elementpath: false, quickbars_insert_toolbar: false, toolbar_mode: 'sliding', color_default_background: 'yellow', link_default_target: 'external', mobile: { menubar: 'file edit view insert format table' }, link_target_list: [ { title: 'New page', value: 'external' }, { title: 'Same page', value: '_self' } ], style_formats: [ { title: 'Headers', items: [ { title: 'Header 1', format: 'h1' }, { title: 'Header 2', format: 'h2' }, { title: 'Header 3', format: 'h3' } ] }, { title: 'Inline', items: [ { title: 'Bold', icon: 'bold', format: 'bold' }, { title: 'Italic', icon: 'italic', format: 'italic' }, { title: 'Underline', icon: 'underline', format: 'underline' }, { title: 'Strikethrough', icon: 'strike-through', format: 'strikethrough' }, { title: 'Superscript', icon: 'superscript', format: 'superscript' }, { title: 'Subscript', icon: 'subscript', format: 'subscript' }, { title: 'Code', icon: 'sourcecode', format: 'code' } ] }, { title: 'Blocks', items: [ { title: 'Paragraph', format: 'p' }, { title: 'Blockquote', format: 'blockquote' } ] }, { title: 'Alignment', items: [ { title: 'Left', icon: 'align-left', format: 'alignleft' }, { title: 'Center', icon: 'align-center', format: 'aligncenter' }, { title: 'Right', icon: 'align-right', format: 'alignright' }, { title: 'Justify', icon: 'align-justify', format: 'alignjustify' } ] } ], init_instance_callback: (editor) => { const editorContainer = $(editor.getContainer()); const editorForm = editorContainer.closest('form'); const menuBar = editorForm.find('.tox-menubar'); editorContainer.addClass('tox-tinymce--loaded'); const event = new CustomEvent('tinyMCEOpened', { detail: { target: editorForm.parent(), } }); window.dispatchEvent(event); editorForm.parent().css('height', ''); // Init saved status label if (editor.getContent() !== '') { editorForm.find('.tinymce-status-badge').removeClass('hidden'); } // Remove transition class $('.tox-editor-header').removeClass('tox-editor-dock-fadeout'); // Fixes the overflowing vertical controls bar for inserted text $('.tox-editor-header').css('display', 'contents'); // Init image toolbar initCssOverrides(editor); // Init save/cancel button wrapper $('').appendTo(menuBar); // Init Save button editorForm .find('.tinymce-save-button') .clone() .appendTo(menuBar.find('.tinymce-save-controls')) .on('click', (event) => { event.preventDefault(); saveAction(editor); SmartAnnotation.closePopup(); }); // After save action editorForm .on('ajax:success', (_ev, data) => { editor.save(); editor.setProgressState(0); editorForm.find('.tinymce-status-badge').removeClass('hidden'); editor.remove(); editorForm.find('.tinymce-view').html(data.html).removeClass('hidden'); TinyMCE.wrapTables(editorForm.find('.tinymce-view')); editor.plugins.autosave.removeDraft(); removeDraft(editor, textAreaObject); if (options.onSaveCallback) { options.onSaveCallback(data); } }).on('ajax:error', (_ev, data) => { const model = editor.getElement().dataset.objectType; let form = $(editor.getElement().closest('.form-group')); form.renderFormErrors(model, data.responseJSON); // to show bottom of the tinyMce editor instead at the top form.find('.help-block').insertAfter(form.children().last()); editor.setProgressState(0); if (data.status === 403) { HelperModule.flashAlertMsg(I18n.t('general.no_permissions'), 'danger'); } else if (data.status === 422) { HelperModule.flashAlertMsg(data.responseJSON ? Object.values(data.responseJSON).join(', ') : I18n.t('errors.general'), 'danger'); } }); // Init Cancel button editorForm .find('.tinymce-cancel-button') .clone() .prependTo(menuBar.find('.tinymce-save-controls')) .on('click', (event) => { $(editorForm).find('.form-group').removeClass('has-error'); $(editorForm).find('.help-block').remove(); event.preventDefault(); if (editor.isDirty()) { editor.setContent($(selector).val()); } editorForm.find('.tinymce-status-badge').addClass('hidden'); editorForm.find('.tinymce-view').removeClass('hidden'); editor.remove(); updateScrollPosition(editorForm); if (options.onSaveCallback) { options.onSaveCallback($(selector).val()); } SmartAnnotation.closePopup(); }) .removeClass('hidden'); editor.selection.select(editor.getBody(), true); editor.selection.collapse(false); SmartAnnotation.init($(editor.contentDocument.activeElement), false, options.assignableMyModuleId); SmartAnnotation.preventPropagation('.atwho-user-popover'); if (options.afterInitCallback) { options.afterInitCallback(); } }, setup: (editor) => { editor.isBlurTempDisabled = false; editor.on('keydown', (e) => { if (e.key === 'Enter' && $(editor.contentDocument.activeElement).atwho('isSelecting')) { return false; } return true; }); editor.on('Dirty', () => { makeItDirty(editor); }); editor.on('StoreDraft', () => { const location = draftLocation(); const storedDrafts = JSON.parse(sessionStorage.getItem(location) || '[]'); const draftName = textAreaObject.data('tinymce-object'); if (storedDrafts.includes(draftName) || !draftName) return; storedDrafts.push(draftName); sessionStorage.setItem(location, JSON.stringify(storedDrafts)); }); editor.on('remove', () => { const menuBar = $(editor.getContainer()).find('.tox-menubar'); menuBar.find('.tinymce-save-button').remove(); menuBar.find('.tinymce-cancel-button').remove(); }); editor.on('blur', () => { if (editor.isBlurTempDisabled || editor.blurDisabled) return false; if ($('.atwho-view:visible').length || $('#MarvinJsModal:visible').length) return false; setTimeout(() => { if (editor.isNotDirty === false) { $(editor.container).find('.tinymce-save-button').click(); } else { $(editor.container).find('.tinymce-cancel-button').click(); } }, 0); return true; }); editor.on('init', () => { restoreDraftNotification(selector, editor); }); editor.on('BeforeSetContent GetContent', function(e) { if (e.content && e.content.includes('target="external"')) { const div = document.createElement('div'); div.innerHTML = e.content; const links = div.querySelectorAll('a[target="external"]'); links.forEach(link => { link.removeAttribute('target'); link.setAttribute('rel', 'external'); }); e.content = div.innerHTML; } }); }, save_onsavecallback: (editor) => { saveAction(editor); } }); } return null; }, destroyAll: () => { if (tinyMCE.activeEditor) { tinyMCE.activeEditor.remove(); } }, refresh: () => { this.destroyAll(); this.init(); }, getContent: () => tinyMCE.activeEditor && tinyMCE.activeEditor.getContent(), updateImages: (editor) => { const iframe = $(`#${editor.id}`).next().find('.tox-edit-area iframe').contents(); const images = $.map($('img', iframe), e => e.dataset.mceToken); $(`#${editor.id}`).parent().find('input.tiny-mce-images').val(JSON.stringify(images)); return JSON.stringify(images); }, makeItDirty: (editor) => { makeItDirty(editor); }, wrapTables: (container) => { container.find('table').toArray().forEach((table) => { if ($(table).parent().hasClass('table-wrapper')) return; $(table).css('float', 'none').wrapAll(` `); }); } }; })(); $(document).on('turbolinks:before-visit', (e) => { const editor = tinyMCE.activeEditor; if (editor === null) return true; if (editor.isDirty()) { // eslint-disable-next-line no-alert if (confirm(I18n.t('tiny_mce.leaving_warning'))) { $('.atwho-container').remove(); tinyMCE.activeEditor.remove(); return true; } e.preventDefault(); return false; } return true; });