Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tinymce v6 #3308

Merged
merged 8 commits into from
Apr 12, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
357 changes: 183 additions & 174 deletions app/javascript/src/answers/edit.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion app/javascript/src/guidances/newEdit.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Tinymce } from '../utils/tinymce.js.erb';
import { Tinymce } from '../utils/tinymce.js';

$(() => {
Tinymce.init({ selector: '#guidance_text' });
Expand Down
6 changes: 4 additions & 2 deletions app/javascript/src/notes/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Tinymce } from '../utils/tinymce.js.erb';
import { Tinymce } from '../utils/tinymce.js';
import { isObject, isString } from '../utils/isType';
import TimeagoFactory from '../utils/timeagoFactory.js.erb';

Expand Down Expand Up @@ -160,7 +160,9 @@ $(() => {
$('.archive_note button[type="button"]')[attachment]('click', noteCancelHandler);
};
const initOrReload = () => {
Tinymce.init({ selector: '.note' });
$('.note').each((_idx, el) => {
Tinymce.init({ selector: `#${$(el).attr('id')}` });
});
eventHandlers({ attachment: 'on' });
TimeagoFactory.render($('time.timeago'));
};
Expand Down
45 changes: 20 additions & 25 deletions app/javascript/src/orgAdmin/phases/newEdit.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// import 'bootstrap-sass/assets/javascripts/bootstrap/collapse';
import { Tinymce } from '../../utils/tinymce.js.erb';
import { Tinymce } from '../../utils/tinymce.js';
import { isObject, isString } from '../../utils/isType';
import getConstant from '../../utils/constants';
import { addAsterisks } from '../../utils/requiredField';
Expand All @@ -9,7 +9,7 @@ import initQuestionOption from '../questionOptions/index';
import updateConditions from '../conditions/updateConditions';

$(() => {
Tinymce.init({ selector: '.phase' });
Tinymce.init({ selector: '#phase_description' });
const parentSelector = '.section-group';

const initQuestion = (context) => {
Expand Down Expand Up @@ -61,36 +61,29 @@ $(() => {
// For some reason the toolbar options are retained after the call to Tinymce.init() on
// the views/notifications/edit.js file. Tried 'Object.assign' instead of '$.extend' but it
// made no difference
const prefix = 'collapseSection'
let sectionId = selector;
if (sectionId.startsWith(prefix)) {
sectionId = `sc_${sectionId.replace(prefix, '')}_section_description`
}

Tinymce.init({
selector: `${selector} .section`,
init_instance_callback(editor) {
selector: `#${sectionId}`,
init_instance_callback: (editor) => {
// When the text editor changes to blank, set the corresponding destroy
// field to true (if present).
editor.on('Change', () => {
const $texteditor = $(editor.targetElm);
editor.on('Change', (ed) => {
const $texteditor = $(ed.getContentAreaContainer());
const $fieldset = $texteditor.parents('fieldset');
const $hiddenField = $fieldset.find('input[type=hidden][id$="_destroy"]');
$hiddenField.val(editor.getContent() === '');
$hiddenField.val(ed.getContent() === '');
});
},
});

const questionForm = $(selector).find('.question_form');
if (questionForm.length > 0) {
// Load Tinymce when the 'show' form has a question form.
// ONLY applicable for template customizations
Tinymce.init({
selector: `${selector} .question_form .question`,
init_instance_callback(editor) {
// When the text editor changes to blank, set the corresponding destroy
// field to true (if present).
editor.on('Change', () => {
const $texteditor = $(editor.targetElm);
const $fieldset = $texteditor.parents('fieldset');
const $hiddenField = $fieldset.find('input[type=hidden][id$="_destroy"]');
$hiddenField.val(editor.getContent() === '');
});
},
});
initQuestion(selector);
}
}
};
Expand All @@ -108,8 +101,9 @@ $(() => {
// Display the section's html
panelBody.attr('data-loaded', 'true');
panelBody.html(e.detail[0].html);

// Wire up the section
initSection(`#${panel.attr('id')}`);
initSection(`${panel.attr('id')}`);
}
});

Expand Down Expand Up @@ -158,8 +152,9 @@ $(() => {
// Handle the section that has focus on initial page load
const currentSection = $('.section-group .in');
if (currentSection.length > 0) {
initSection(`#${currentSection.attr('id')}`);
initSection(`${currentSection.attr('id')}`);
}
// Handle the new section
initSection('#new_section_new_section');
// initSection('#new_section_section_description');
Tinymce.init({ selector: '#new_section_section_description' });
});
4 changes: 2 additions & 2 deletions app/javascript/src/orgAdmin/templates/edit.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { Tinymce } from '../../utils/tinymce.js.erb';
import { Tinymce } from '../../utils/tinymce.js';
import { eachLinks } from '../../utils/links';
import { isObject, isString } from '../../utils/isType';
import { renderNotice, renderAlert } from '../../utils/notificationHelper';
import { scrollTo } from '../../utils/scrollTo';

$(() => {
Tinymce.init({
selector: '.template',
selector: '#template_description',
init_instance_callback(editor) {
// When the text editor changes to blank, set the corresponding destroy
// field to true (if present).
Expand Down
4 changes: 2 additions & 2 deletions app/javascript/src/orgAdmin/templates/new.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { Tinymce } from '../../utils/tinymce.js.erb';
import { Tinymce } from '../../utils/tinymce.js';
import { eachLinks } from '../../utils/links';

$(() => {
Tinymce.init({
selector: '.template',
selector: '#template_description',
init_instance_callback(editor) {
// When the text editor changes to blank, set the corresponding destroy
// field to true (if present).
Expand Down
2 changes: 1 addition & 1 deletion app/javascript/src/orgs/adminEdit.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// TODO: we need to be able to swap in the appropriate locale here
import 'number-to-text/converters/en-us';
import { isObject } from '../utils/isType';
import { Tinymce } from '../utils/tinymce.js.erb';
import { Tinymce } from '../utils/tinymce.js';
import { eachLinks } from '../utils/links';
import { initAutocomplete, scrubOrgSelectionParamsOnSubmit } from '../utils/autoComplete';

Expand Down
6 changes: 3 additions & 3 deletions app/javascript/src/plans/editDetails.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { initAutocomplete, scrubOrgSelectionParamsOnSubmit } from '../utils/autoComplete';
import { Tinymce } from '../utils/tinymce.js.erb';
import { Tinymce } from '../utils/tinymce.js';
import toggleConditionalFields from '../utils/conditionalFields';
import getConstant from '../utils/constants';

Expand All @@ -10,8 +10,8 @@ $(() => {
const form = $('form.edit_plan');

if (form.length > 0) {
Tinymce.init({ selector: '#plan_description' });
Tinymce.init({ selector: '#plan_ethical_issues_description' });
Tinymce.init({ selector: 'textarea#plan_description' });
Tinymce.init({ selector: 'textarea#plan_ethical_issues_description' });

$('#is_test').click((e) => {
$('#plan_visibility').val($(e.target).is(':checked') ? 'is_test' : 'privately_visible');
Expand Down
2 changes: 1 addition & 1 deletion app/javascript/src/researchOutputs/form.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import getConstant from '../utils/constants';
import { isUndefined, isObject } from '../utils/isType';
import { Tinymce } from '../utils/tinymce.js.erb';
import { Tinymce } from '../utils/tinymce.js';

$(() => {
const form = $('.research_output_form');
Expand Down
4 changes: 2 additions & 2 deletions app/javascript/src/superAdmin/notifications/edit.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { Tinymce } from '../../utils/tinymce.js.erb';
import { Tinymce } from '../../utils/tinymce.js';

// add the info on selecting the check from notification suitable
import { paginableSelector } from '../../utils/paginable';
import * as notifier from '../../utils/notificationHelper';

$(() => {
Tinymce.init({ selector: '.notification-text', forced_root_block: '' });
Tinymce.init({ selector: '#notification_body', forced_root_block: '' });

$(paginableSelector).on('click, change', '.enable_notification input[type="checkbox"]', (e) => {
const form = $(e.target).closest('form');
Expand Down
2 changes: 1 addition & 1 deletion app/javascript/src/superAdmin/themes/newEdit.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Tinymce } from '../../utils/tinymce.js.erb';
import { Tinymce } from '../../utils/tinymce.js';

$(() => {
Tinymce.init({ selector: '#theme_description' });
Expand Down
2 changes: 1 addition & 1 deletion app/javascript/src/utils/conditionalFields.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
// For example see: app/views/plans/_edit_details.html.erb
// app/javascript/src/plans/editDetails.js
//
import { Tinymce } from './tinymce.js.erb';
import { Tinymce } from './tinymce.js';

// Expecting `context` to be the field that triggers the hide/show of the corresponding fields
export default function toggleConditionalFields(context, showThem) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
// Import TinyMCE
import tinymce from 'tinymce/tinymce';
// Import TinyMCE theme
import 'tinymce/themes/silver/theme';

// TinyMCE DOM helpers
import 'tinymce/models/dom/';

// TinyMCE toolbar icons
import 'tinymce/icons/default';
// Plugins

// TinyMCE theme
import 'tinymce/themes/silver';

// TinyMCE 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 %>
import { isObject, isString, isUndefined } from './isType';

// // Configuration extracted from
// // https://www.tinymce.com/docs/advanced/usage-with-module-loaders/
Expand All @@ -24,7 +27,7 @@ export const defaultOptions = {
statusbar: true,
menubar: false,
toolbar: 'bold italic | bullist numlist | link | table',
plugins: 'table autoresize link paste advlist lists',
plugins: 'table autoresize link advlist lists',
browser_spellcheck: true,
advlist_bullet_styles: 'circle,disc,square', // Only disc bullets display on htmltoword
target_list: false,
Expand All @@ -34,12 +37,14 @@ export const defaultOptions = {
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_convert_middot_lists: true,
paste_as_text: true,
paste_block_drop: true,
paste_merge_formats: true,
paste_tab_spaces: 4,
smart_paste: true,
paste_data_images: true,
paste_remove_styles_if_webkit: true,
paste_remove_spans: true,
paste_strip_class_attributes: 'all',
paste_webkit_styles: 'none',
table_default_attributes: {
border: 1,
},
Expand All @@ -48,17 +53,16 @@ export const defaultOptions = {
skin_url: '/tinymce/skins/oxide',
content_css: ['/tinymce/tinymce.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 determines whether or not the editor is a TinyMCE editor
*/
const isTinymceEditor = (editor) => {
if (isObject(editor)) {
return editor.hasOwnProperty('id') && typeof editor.getContainer === 'function';
} else {
return false;
}
};

/*
Expand All @@ -67,16 +71,14 @@ const resizeEditors = (editors) => {
behind the scenes) to the Tinymce iframe so that screen readers read the correct
label when the tinymce iframe receives focus.
*/
const attachLabelToIframe = (tinymceContext, hiddenFieldSelector) => {
const iframe = $(tinymceContext).siblings('.mce-container').find('iframe');
const hiddenField = $(hiddenFieldSelector);
const attachLabelToIframe = (editor) => {
if (isTinymceEditor(editor)) {
const iframe = editor.getContainer().querySelector('iframe');
const lbl = document.querySelector(`label[for="${editor.id}"]`);

if (isObject(iframe) && isObject(hiddenField)) {
const id = hiddenField.attr('id');
const lbl = iframe.closest('form').find(`label[for="${id}"]`);
if (isObject(lbl)) {
// Connect the label to the iframe
lbl.attr('for', iframe.attr('id'));
// If the iframe and label could be found, then set the label's 'for' attribute to the id of the iframe
if (isObject(iframe) && isObject(lbl)) {
lbl.setAttribute('for', iframe.getAttribute('id'));
}
}
};
Expand All @@ -88,17 +90,23 @@ export const Tinymce = {
@param options - An object with tinyMCE properties
*/
init(options = {}) {
if (isObject(options)) {
tinymce.init($.extend(true, defaultOptions, options)).then(resizeEditors);
} else {
tinymce.init(defaultOptions).then(resizeEditors);
}
// If any options were specified, merge them with the default options.
let opts = {
...defaultOptions,
...options
};

// Connect the label to the Tinymce iframe
$(options.selector).each((idx, el) => {
attachLabelToIframe(el, options.selector);
tinymce.init(opts).then((editors) => {
if (editors.length > 0) {
for (const editor of editors) {
// auto-resize the editor and connect the form label to the TinyMCE iframe
editor.execCommand('mceAutoResize');
attachLabelToIframe(editor, editor.id);
}
}
});
},

/*
Finds any tinyMCE editor whose target element/textarea has the className passed
@param className - A string representing the class name of the tinyMCE editor
Expand All @@ -107,12 +115,11 @@ export const Tinymce = {
*/
findEditorsByClassName(className) {
if (isString(className)) {
return tinymce.editors.reduce((acc, e) => {
if ($(e.getElement()).hasClass(className)) {
return acc.concat([e]);
}
return acc;
}, []);
const elements = Array.from(document.getElementsByClassName(className));
// Fetch the textarea elements and then return the TinyMCE editors associated with the element ids
return elements.map((el) => {
return Tinymce.findEditorById(el.getAttribute('id'));
});
}
return [];
},
Expand All @@ -124,7 +131,7 @@ export const Tinymce = {
*/
findEditorById(id) {
if (isString(id)) {
return tinymce.editors.find(el => el.id === id);
return tinymce.get(id);
}
return undefined;
},
Expand All @@ -137,7 +144,14 @@ export const Tinymce = {
*/
destroyEditorsByClassName(className) {
const editors = this.findEditorsByClassName(className);
editors.forEach(ed => ed.destroy(false));
if (editors.length > 0) {
/* editors.forEach(ed => ed.destroy(false)); */
for (const editor of editors) {
if (isTinymceEditor(editor)) {
editor.destroy(false);
}
}
}
},
/*
Destroy an editor instance whose target element/textarea has HTML id passed. This method
Expand All @@ -146,7 +160,7 @@ export const Tinymce = {
*/
destroyEditorById(id) {
const editor = this.findEditorById(id);
if (editor) {
if (isTinymceEditor(editor)) {
editor.destroy(false);
}
},
Expand Down
Loading