Newer
Older
dmpopidor / app / javascript / utils / tinymce.js.erb
// Import TinyMCE
import tinymce from 'tinymce/tinymce';
// Import TinyMCE theme
import 'tinymce/themes/modern/theme';
// Plugins
import 'tinymce/plugins/table';
import 'tinymce/plugins/lists';
import 'tinymce/plugins/autoresize';
import 'tinymce/plugins/link';
import 'tinymce/plugins/paste';
import 'tinymce/plugins/advlist';
// Other dependencies
import { isObject, isString } from './isType';
// Pull in the rails helper functions
<% helpers = ActionController::Base.helpers %>

// // Configuration extracted from
// // https://www.tinymce.com/docs/advanced/usage-with-module-loaders/
export const defaultOptions = {
  selector: '.tinymce',
  statusbar: true,
  menubar: false,
  toolbar: 'bold italic | bullist numlist | link | table',
  plugins: 'table autoresize link paste advlist lists',
  browser_spellcheck: true,
  advlist_bullet_styles: 'circle,disc,square', // Only disc bullets display on htmltoword
  target_list: false,
  elementpath: false,
  resize: true,
  autoresize_min_height: 130,
  autoresize_bottom_margin: 10,
  branding: false,
  extended_valid_elements: 'iframe[tooltip] , a[href|target=_blank]',
  paste_auto_cleanup_on_paste: true,
  paste_remove_styles: true,
  paste_retain_style_properties: 'none',
  paste_convert_middot_lists: true,
  paste_remove_styles_if_webkit: true,
  paste_remove_spans: true,
  paste_strip_class_attributes: 'all',
  table_default_attributes: {
    border: 1,
  },
  // editorManager.baseURL is not resolved properly for IE since document.currentScript
  // is not supported, see issue https://github.com/tinymce/tinymce/issues/358
  skin_url: '/tinymce/skins/lightgray',
  content_css: ['<%= helpers.asset_path "/assets/blocks/_tinymce_content.css"  %>'],
};
/*
  This function is invoked anytime a new editor is initialised (e.g. Tinymce.init())
  and shrinks a tinymce editor to the minimum height specified at autoresize_min_height
  editor's settings. Since there are cases that tinymce editor is loaded in the DOM
  but has display:none style, the iframe associated gets the height of the screen's device
  and using this function there is no need to wait until the tinymce gains focus to be autoresized.
*/
const resizeEditors = (editors) => {
  editors.forEach((editor) => {
    $(editor.iframeElement).height(editor.settings.autoresize_min_height);
  });
};

/*
  This function is invoked after the Tinymce widget is initialized. It moves the
  connection with the label from the hidden field (that the Tinymce writes to
  behind the scenes) to the Tinymce iframe so that screen readers read the correct
  label when the tinymce iframe receives focus.
 */
const attachLabelToIframe = (tinymceContext) => {
  const iframe = $(tinymceContext).siblings('.mce-container').find('iframe');
  if (isObject(iframe)) {
    const lbl = iframe.closest('form').find('label');
    if (isObject(lbl)) {
      // Connect the label to the iframe
      lbl.attr('for', iframe.attr('id'));
    }
  }
};

export const Tinymce = {
  /*
    Initialises a tinymce editor given the object passed. If a non-valid object is passed,
    the defaultOptions object is used instead
    @param options - An object with tinyMCE properties
  */
  init(options = {}) {
    if (isObject(options)) {
      tinymce.init(Object.assign({}, defaultOptions, options)).then(resizeEditors);
    } else {
      tinymce.init(defaultOptions).then(resizeEditors);
    }

    // Connect the label to the Tinymce iframe
    $(options.selector).each((idx, el) => {
      attachLabelToIframe(el);
    });
  },
  /*
    Finds any tinyMCE editor whose target element/textarea has the className passed
    @param className - A string representing the class name of the tinyMCE editor
    target element/textarea to look for
    @return An Array of tinymce.Editor objects
  */
  findEditorsByClassName(className) {
    if (isString(className)) {
      return tinymce.editors.reduce((acc, e) => {
        if ($(e.getElement()).hasClass(className)) {
          return acc.concat([e]);
        }
        return acc;
      }, []);
    }
    return [];
  },
  /*
    Finds a tinyMCE editor whose target element/textarea has the id passed
    @param id - A string representing the id of the tinyMCE editor target
    element/textarea to look for
    @return tinymce.Editor object, otherwise undefined
  */
  findEditorById(id) {
    if (isString(id)) {
      return tinymce.editors.find(el => el.id === id);
    }
    return undefined;
  },
  /*
    Destroy every editor instance whose target element/textarea has the className passed. This
    method executes for each editor the method defined at tinymce.Editor.destroy (e.g. https://www.tinymce.com/docs/api/tinymce/tinymce.editor/#destroy).
    @param className - A string representing the class name of the tinyMCE editor
    target element/textarea to look for
    @return undefined
  */
  destroyEditorsByClassName(className) {
    const editors = this.findEditorsByClassName(className);
    editors.forEach(ed => ed.destroy(false));
  },
  /*
    Destroy an editor instance whose target element/textarea has HTML id passed. This method
    executes tinymce.Editor.destroy (e.g. https://www.tinymce.com/docs/api/tinymce/tinymce.editor/#destroy) for a successfull id found.
    @return undefined
  */
  destroyEditorById(id) {
    const editor = this.findEditorById(id);
    if (editor) {
      editor.destroy(false);
    }
  },
};