@instructure/canvas-rce 7.3.0 → 7.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/es/enhance-user-content/doc_previews.js +5 -0
  3. package/es/enhance-user-content/enhance_user_content.js +1 -1
  4. package/es/enhance-user-content/instructure_helper.js +1 -0
  5. package/es/rce/AlertMessageArea.d.ts +2 -2
  6. package/es/rce/AlertMessageArea.js +3 -3
  7. package/es/rce/KeyboardShortcutModal.js +1 -1
  8. package/es/rce/RCEWrapper.d.ts +11 -11
  9. package/es/rce/RCEWrapper.js +216 -191
  10. package/es/rce/plugins/instructure_rce_external_tools/components/ExternalToolSelectionDialog/ExternalToolSelectionDialog.d.ts +1 -1
  11. package/es/rce/plugins/instructure_rce_external_tools/components/ExternalToolSelectionDialog/ExternalToolSelectionDialog.js +25 -25
  12. package/es/rce/plugins/instructure_studio_media_options/plugin.js +4 -2
  13. package/es/rce/plugins/instructure_wordcount/components/WordCountModal.js +1 -0
  14. package/es/rce/plugins/shared/StudioLtiSupportUtils.d.ts +2 -1
  15. package/es/rce/plugins/shared/StudioLtiSupportUtils.js +17 -1
  16. package/es/translations/locales/ar.js +15 -0
  17. package/es/translations/locales/ca.js +15 -0
  18. package/es/translations/locales/cy.js +15 -0
  19. package/es/translations/locales/da-x-k12.js +15 -0
  20. package/es/translations/locales/da.js +15 -0
  21. package/es/translations/locales/de.js +15 -0
  22. package/es/translations/locales/en-AU-x-unimelb.js +15 -0
  23. package/es/translations/locales/en-GB-x-ukhe.js +15 -0
  24. package/es/translations/locales/en.js +6 -0
  25. package/es/translations/locales/en_AU.js +15 -0
  26. package/es/translations/locales/en_CA.js +15 -0
  27. package/es/translations/locales/en_CY.js +15 -0
  28. package/es/translations/locales/en_GB.js +15 -0
  29. package/es/translations/locales/es.js +15 -0
  30. package/es/translations/locales/es_ES.js +15 -0
  31. package/es/translations/locales/fi.js +15 -0
  32. package/es/translations/locales/fr.js +15 -0
  33. package/es/translations/locales/fr_CA.js +15 -0
  34. package/es/translations/locales/ga.js +15 -0
  35. package/es/translations/locales/hi.js +15 -0
  36. package/es/translations/locales/ht.js +15 -0
  37. package/es/translations/locales/id.js +15 -0
  38. package/es/translations/locales/is.js +15 -0
  39. package/es/translations/locales/it.js +15 -0
  40. package/es/translations/locales/ja.js +15 -0
  41. package/es/translations/locales/mi.js +15 -0
  42. package/es/translations/locales/ms.js +15 -0
  43. package/es/translations/locales/nb-x-k12.js +15 -0
  44. package/es/translations/locales/nb.js +15 -0
  45. package/es/translations/locales/nl.js +15 -0
  46. package/es/translations/locales/pl.js +15 -0
  47. package/es/translations/locales/pt.js +15 -0
  48. package/es/translations/locales/pt_BR.js +15 -0
  49. package/es/translations/locales/ru.js +15 -0
  50. package/es/translations/locales/sl.js +15 -0
  51. package/es/translations/locales/sv-x-k12.js +15 -0
  52. package/es/translations/locales/sv.js +15 -0
  53. package/es/translations/locales/th.js +15 -0
  54. package/es/translations/locales/vi.js +15 -0
  55. package/es/translations/locales/zh-Hans.js +15 -0
  56. package/es/translations/locales/zh-Hant.js +15 -0
  57. package/es/translations/locales/zh.js +15 -0
  58. package/es/translations/locales/zh_HK.js +15 -0
  59. package/package.json +1 -1
@@ -17,63 +17,63 @@ import _pt from "prop-types";
17
17
  * with this program. If not, see <http://www.gnu.org/licenses/>.
18
18
  */
19
19
 
20
- import React, { Suspense } from "react";
21
- import { Editor } from "@tinymce/tinymce-react";
22
- import tinymce from "tinymce";
23
- import _ from "lodash";
24
- import { StoreProvider } from "./plugins/shared/StoreContext";
25
- import { IconKeyboardShortcutsLine } from "@instructure/ui-icons";
26
- import { Alert } from "@instructure/ui-alerts";
27
- import { Spinner } from "@instructure/ui-spinner";
28
- import { View } from "@instructure/ui-view";
29
- import { debounce } from "@instructure/debounce";
30
- import { uid } from "@instructure/uid";
31
- import { FocusRegionManager } from "@instructure/ui-a11y-utils";
32
- import getCookie from "../common/getCookie";
33
- import formatMessage from "../format-message";
34
- import * as contentInsertion from "./contentInsertion";
35
- import indicatorRegion from "./indicatorRegion";
36
- import { editorLanguage } from "./editorLanguage";
37
- import normalizeLocale from "./normalizeLocale";
38
- import { sanitizePlugins } from "./sanitizePlugins";
39
- import RCEGlobals from "./RCEGlobals";
40
- import defaultTinymceConfig from "../defaultTinymceConfig";
41
- import { FS_CHANGEEVENT, FS_ELEMENT, FS_ENABLED, FS_EXIT, FS_REQUEST, instuiPopupMountNodeFn } from "../util/fullscreenHelpers";
42
- import indicate from "../common/indicate";
43
- import bridge from "../bridge";
44
- import CanvasContentTray from "./plugins/shared/CanvasContentTray";
45
- import StatusBar, { PRETTY_HTML_EDITOR_VIEW, RAW_HTML_EDITOR_VIEW, WYSIWYG_VIEW } from "./StatusBar";
46
- import { VIEW_CHANGE } from "./customEvents";
47
- import ShowOnFocusButton from "./ShowOnFocusButton";
48
- import KeyboardShortcutModal from "./KeyboardShortcutModal";
49
- import AlertMessageArea from "./AlertMessageArea";
50
- import alertHandler from "./alertHandler";
51
- import { isFileLink, isImageEmbed } from "./plugins/shared/ContentSelection";
52
- import { countShouldIgnore } from "./plugins/instructure_wordcount/utils/countContent";
53
- import launchWordcountModal from "./plugins/instructure_wordcount/clickCallback";
54
- import { determineOSDependentKey } from "./userOS";
55
- import skinCSS from "./tinymce.oxide.skin.min.css";
56
- import contentCSS from "./tinymce.oxide.content.min.css";
57
- import { rceWrapperPropTypes } from "./RCEWrapperProps";
58
- import { insertPlaceholder, placeholderInfoFor, removePlaceholder } from "../util/loadingPlaceholder";
59
- import { transformRceContentForEditing } from "./transformContent";
20
+ import React, { Suspense } from 'react';
21
+ import { Editor } from '@tinymce/tinymce-react';
22
+ import tinymce from 'tinymce';
23
+ import _ from 'lodash';
24
+ import { StoreProvider } from './plugins/shared/StoreContext';
25
+ import { IconKeyboardShortcutsLine } from '@instructure/ui-icons';
26
+ import { Alert } from '@instructure/ui-alerts';
27
+ import { Spinner } from '@instructure/ui-spinner';
28
+ import { View } from '@instructure/ui-view';
29
+ import { debounce } from '@instructure/debounce';
30
+ import { uid } from '@instructure/uid';
31
+ import { FocusRegionManager } from '@instructure/ui-a11y-utils';
32
+ import getCookie from '../common/getCookie';
33
+ import formatMessage from '../format-message';
34
+ import * as contentInsertion from './contentInsertion';
35
+ import indicatorRegion from './indicatorRegion';
36
+ import { editorLanguage } from './editorLanguage';
37
+ import normalizeLocale from './normalizeLocale';
38
+ import { sanitizePlugins } from './sanitizePlugins';
39
+ import RCEGlobals from './RCEGlobals';
40
+ import defaultTinymceConfig from '../defaultTinymceConfig';
41
+ import { FS_CHANGEEVENT, FS_ELEMENT, FS_ENABLED, FS_EXIT, FS_REQUEST, instuiPopupMountNodeFn } from '../util/fullscreenHelpers';
42
+ import indicate from '../common/indicate';
43
+ import bridge from '../bridge';
44
+ import CanvasContentTray from './plugins/shared/CanvasContentTray';
45
+ import StatusBar, { PRETTY_HTML_EDITOR_VIEW, RAW_HTML_EDITOR_VIEW, WYSIWYG_VIEW } from './StatusBar';
46
+ import { VIEW_CHANGE } from './customEvents';
47
+ import ShowOnFocusButton from './ShowOnFocusButton';
48
+ import KeyboardShortcutModal from './KeyboardShortcutModal';
49
+ import AlertMessageArea from './AlertMessageArea';
50
+ import alertHandler from './alertHandler';
51
+ import { isFileLink, isImageEmbed } from './plugins/shared/ContentSelection';
52
+ import { countShouldIgnore } from './plugins/instructure_wordcount/utils/countContent';
53
+ import launchWordcountModal from './plugins/instructure_wordcount/clickCallback';
54
+ import { determineOSDependentKey } from './userOS';
55
+ import skinCSS from './tinymce.oxide.skin.min.css';
56
+ import contentCSS from './tinymce.oxide.content.min.css';
57
+ import { rceWrapperPropTypes } from './RCEWrapperProps';
58
+ import { insertPlaceholder, placeholderInfoFor, removePlaceholder } from '../util/loadingPlaceholder';
59
+ import { transformRceContentForEditing } from './transformContent';
60
60
  // @ts-expect-error
61
- import { IconMoreSolid } from "@instructure/ui-icons/es/svg";
62
- import EncryptedStorage from "../util/encrypted-storage";
63
- import buildStyle from "./style";
64
- import { getMenubarForVariant, getMenuForVariant, getToolbarForVariant, getStatusBarFeaturesForVariant } from "./RCEVariants";
65
- import { focusFirstMenuButton, focusToolbar, isElementWithinTable, mergeMenu, mergeMenuItems, mergePlugins, mergeToolbar, parsePluginsToExclude, patchAutosavedContent } from "./RCEWrapper.utils";
66
- import { externalToolsForToolbar } from "./plugins/instructure_rce_external_tools/util/externalToolsForToolbar";
67
- import { initScreenreaderOnFormat } from "./screenreaderOnFormat";
68
- import { normalizeContainingContext } from "../util/contextHelper";
69
- const RestoreAutoSaveModal = /*#__PURE__*/React.lazy(() => import("./RestoreAutoSaveModal"));
70
- const RceHtmlEditor = /*#__PURE__*/React.lazy(() => import("./RceHtmlEditor"));
61
+ import { IconMoreSolid } from '@instructure/ui-icons/es/svg';
62
+ import EncryptedStorage from '../util/encrypted-storage';
63
+ import buildStyle from './style';
64
+ import { getMenubarForVariant, getMenuForVariant, getToolbarForVariant, getStatusBarFeaturesForVariant } from './RCEVariants';
65
+ import { focusFirstMenuButton, focusToolbar, isElementWithinTable, mergeMenu, mergeMenuItems, mergePlugins, mergeToolbar, parsePluginsToExclude, patchAutosavedContent } from './RCEWrapper.utils';
66
+ import { externalToolsForToolbar } from './plugins/instructure_rce_external_tools/util/externalToolsForToolbar';
67
+ import { initScreenreaderOnFormat } from './screenreaderOnFormat';
68
+ import { normalizeContainingContext } from '../util/contextHelper';
69
+ const RestoreAutoSaveModal = /*#__PURE__*/React.lazy(() => import('./RestoreAutoSaveModal'));
70
+ const RceHtmlEditor = /*#__PURE__*/React.lazy(() => import('./RceHtmlEditor'));
71
71
  const ASYNC_FOCUS_TIMEOUT = 250;
72
- const DEFAULT_RCE_HEIGHT = "400px";
72
+ const DEFAULT_RCE_HEIGHT = '400px';
73
73
  function addKebabIcon(editor) {
74
74
  // This has to be done here instead of of in plugins/instructure-ui-icons/plugin.ts
75
75
  // presumably because the toolbar gets created before that plugin is loaded?
76
- editor.ui.registry.addIcon("more-drawer", IconMoreSolid.src);
76
+ editor.ui.registry.addIcon('more-drawer', IconMoreSolid.src);
77
77
  }
78
78
 
79
79
  // Get oxide the default skin injected into the DOM before the overrides loaded by themeable
@@ -81,8 +81,8 @@ let inserted = false;
81
81
  function injectTinySkin() {
82
82
  if (inserted) return;
83
83
  inserted = true;
84
- const style = document.createElement("style");
85
- style.setAttribute("data-skin", "tiny oxide skin");
84
+ const style = document.createElement('style');
85
+ style.setAttribute('data-skin', 'tiny oxide skin');
86
86
  style.appendChild(document.createTextNode(skinCSS));
87
87
  // there's CSS from discussions that turns the instui Selectors bold
88
88
  // and in classic quizzes that also mucks with padding
@@ -90,9 +90,9 @@ function injectTinySkin() {
90
90
  #discussion-edit-view .rce-wrapper input[readonly] {font-weight: normal;}
91
91
  #quiz_edit_wrapper .rce-wrapper input[readonly] {font-weight: normal; padding-left: .75rem;}
92
92
  `));
93
- const beforeMe = document.head.querySelector("style[data-glamor]") ||
93
+ const beforeMe = document.head.querySelector('style[data-glamor]') ||
94
94
  // find instui's themeable stylesheet
95
- document.head.querySelector("style") ||
95
+ document.head.querySelector('style') ||
96
96
  // find any stylesheet
97
97
  document.head.firstElementChild;
98
98
  document.head.insertBefore(style, beforeMe);
@@ -105,7 +105,7 @@ export function storageAvailable() {
105
105
  let storage = null;
106
106
  try {
107
107
  storage = window.localStorage;
108
- const x = "__storage_test__";
108
+ const x = '__storage_test__';
109
109
  storage.setItem(x, x);
110
110
  storage.removeItem(x);
111
111
  return true;
@@ -117,15 +117,15 @@ export function storageAvailable() {
117
117
  e.code === 1014 ||
118
118
  // test name field too, because code might not be present
119
119
  // everything except Firefox
120
- e.name === "QuotaExceededError" ||
120
+ e.name === 'QuotaExceededError' ||
121
121
  // Firefox
122
- e.name === "NS_ERROR_DOM_QUOTA_REACHED") &&
122
+ e.name === 'NS_ERROR_DOM_QUOTA_REACHED') &&
123
123
  // acknowledge QuotaExceededError only if there's something already stored
124
124
  storage && storage.length !== 0;
125
125
  }
126
126
  }
127
127
  function renderLoading() {
128
- return formatMessage("Loading");
128
+ return formatMessage('Loading');
129
129
  }
130
130
  let alertIdValue = 0;
131
131
  class RCEWrapper extends React.Component {
@@ -199,7 +199,7 @@ class RCEWrapper extends React.Component {
199
199
  this.setState(newState);
200
200
  this.checkAccessibility();
201
201
  if (newView === PRETTY_HTML_EDITOR_VIEW || newView === RAW_HTML_EDITOR_VIEW) {
202
- this.storage?.setItem?.("rce.htmleditor", newView);
202
+ this.storage?.setItem?.('rce.htmleditor', newView);
203
203
  }
204
204
 
205
205
  // Emit view change event
@@ -216,7 +216,7 @@ class RCEWrapper extends React.Component {
216
216
  if (document[FS_ELEMENT]) {
217
217
  // @ts-expect-error
218
218
  this.resizeObserver.observe(document[FS_ELEMENT]);
219
- window.visualViewport?.addEventListener("resize", this._handleFullscreenResize);
219
+ window.visualViewport?.addEventListener('resize', this._handleFullscreenResize);
220
220
  this._handleFullscreenResize();
221
221
  // @ts-expect-error
222
222
  this._focusRegion = FocusRegionManager.activateRegion(
@@ -227,7 +227,7 @@ class RCEWrapper extends React.Component {
227
227
  } else {
228
228
  event.target.removeEventListener(FS_CHANGEEVENT, this._onFullscreenChange);
229
229
  this.resizeObserver.unobserve(event.target);
230
- window.visualViewport?.removeEventListener("resize", this._handleFullscreenResize);
230
+ window.visualViewport?.removeEventListener('resize', this._handleFullscreenResize);
231
231
  this._setHeight(this.state.fullscreenState.prevHeight);
232
232
  if (this._focusRegion) {
233
233
  FocusRegionManager.blurRegion(event.target, this._focusRegion.id);
@@ -260,35 +260,35 @@ class RCEWrapper extends React.Component {
260
260
  // what we've got for now.
261
261
  const ifr = this.iframe;
262
262
  if (ifr?.parentElement) {
263
- ifr.parentElement.classList.add("active");
263
+ ifr.parentElement.classList.add('active');
264
264
  }
265
265
  this.handleFocus();
266
266
  };
267
267
  this.handleBlurEditor = event => {
268
268
  const ifr = this.iframe;
269
269
  if (ifr?.parentElement) {
270
- ifr.parentElement.classList.remove("active");
270
+ ifr.parentElement.classList.remove('active');
271
271
  }
272
272
  this.handleBlur(event);
273
273
  };
274
274
  this.handleKey = event => {
275
- if (event.code === "F9" && event.altKey) {
275
+ if (event.code === 'F9' && event.altKey) {
276
276
  event.preventDefault();
277
277
  event.stopPropagation();
278
278
  // @ts-expect-error
279
279
  focusFirstMenuButton(this._elementRef.current);
280
- } else if (event.code === "F10" && event.altKey) {
280
+ } else if (event.code === 'F10' && event.altKey) {
281
281
  event.preventDefault();
282
282
  event.stopPropagation();
283
283
  // @ts-expect-error
284
284
  focusToolbar(this._elementRef.current);
285
- } else if (event.code === "F8" && event.altKey) {
285
+ } else if (event.code === 'F8' && event.altKey) {
286
286
  event.preventDefault();
287
287
  event.stopPropagation();
288
288
  this.openKBShortcutModal();
289
- } else if (event.code === "Escape") {
289
+ } else if (event.code === 'Escape') {
290
290
  bridge.hideTrays();
291
- } else if (["n", "N", "d", "D"].indexOf(event.key) !== -1) {
291
+ } else if (['n', 'N', 'd', 'D'].indexOf(event.key) !== -1) {
292
292
  // Prevent key events from bubbling up on touch screen device
293
293
  event.stopPropagation();
294
294
  }
@@ -317,32 +317,57 @@ class RCEWrapper extends React.Component {
317
317
  // @ts-expect-error
318
318
  textarea.value = this.getCode();
319
319
  textarea.style.height = this.state.height;
320
- textarea.removeAttribute("aria-hidden");
321
- if (document.body.classList.contains("Underline-All-Links__enabled")) {
320
+ textarea.removeAttribute('aria-hidden');
321
+ if (document.body.classList.contains('Underline-All-Links__enabled')) {
322
322
  if (this.iframe?.contentDocument) {
323
- this.iframe.contentDocument.body.classList.add("Underline-All-Links__enabled");
323
+ this.iframe.contentDocument.body.classList.add('Underline-All-Links__enabled');
324
324
  }
325
325
  }
326
- editor.on("wordCountUpdate", this.onWordCountUpdate);
326
+ editor.on('wordCountUpdate', this.onWordCountUpdate);
327
327
  // add an aria-label to the application div that wraps RCE
328
328
  // and change role from "application" to "document" to ensure
329
329
  // the editor gets properly picked up by screen readers
330
330
  const tinyapp = document.querySelector('.tox-tinymce[role="application"]');
331
331
  if (tinyapp) {
332
- tinyapp.setAttribute("aria-label", formatMessage("Rich Content Editor"));
333
- tinyapp.setAttribute("role", "document");
334
- tinyapp.setAttribute("tabIndex", "-1");
332
+ tinyapp.setAttribute('aria-label', formatMessage('Rich Content Editor'));
333
+ tinyapp.setAttribute('role', 'document');
334
+ tinyapp.setAttribute('tabIndex', '-1');
335
+ }
336
+
337
+ // remove role="aplication" attribute from the iframe body
338
+ // tinymce adds this when the editor is wrapped in an iframe
339
+ // which makes RCE input fields inaccessible to screen readers
340
+ const iframe = tinyapp?.querySelector('iframe');
341
+ const body = iframe?.contentDocument?.body;
342
+ if (body) {
343
+ const observer = new MutationObserver(() => {
344
+ try {
345
+ if (body && body.getAttribute('role') === 'application') {
346
+ body.removeAttribute('role');
347
+ }
348
+ } catch (_) {
349
+ /* pass */
350
+ }
351
+ });
352
+ observer.observe(body, {
353
+ attributes: true,
354
+ childList: false,
355
+ subtree: false
356
+ });
357
+ body.setAttribute('data-role-checked', 'true'); // to trigger observer
358
+
359
+ setTimeout(() => observer.disconnect(), 10000);
335
360
  }
336
361
 
337
362
  // Probably should do this in tinymce.scss, but we only want it in new rce
338
- textarea.style.resize = "none";
339
- editor.on("keydown", this.handleKey);
340
- editor.on("FullscreenStateChanged", this._onFullscreenChange);
363
+ textarea.style.resize = 'none';
364
+ editor.on('keydown', this.handleKey);
365
+ editor.on('FullscreenStateChanged', this._onFullscreenChange);
341
366
  // This propagates click events on the editor out of the iframe to the parent
342
367
  // document. We need this so that click events get captured properly by instui
343
368
  // focus-trapping components, so they properly ignore trapping focus on click.
344
- editor.on("click", () => window.document.body.click(), true);
345
- editor.on("Cut Change input Undo Redo", debounce(this.handleInputChange, 1000));
369
+ editor.on('click', () => window.document.body.click(), true);
370
+ editor.on('Cut Change input Undo Redo', debounce(this.handleInputChange, 1000));
346
371
  initScreenreaderOnFormat(editor);
347
372
  this.announceContextToolbars(editor);
348
373
  if (this.isAutoSaving) {
@@ -354,12 +379,12 @@ class RCEWrapper extends React.Component {
354
379
 
355
380
  // readonly should have been handled via the init property passed
356
381
  // to <Editor>, but it's not.
357
- editor.mode.set(this.props.readOnly ? "readonly" : "design");
382
+ editor.mode.set(this.props.readOnly ? 'readonly' : 'design');
358
383
 
359
384
  // Not using iframe_aria_text because compatibility issues.
360
385
  // Not using iframe_attrs because library overwriting.
361
386
  if (this.iframe) {
362
- this.iframe.setAttribute("title", formatMessage("Rich Text Area. Press {OSKey}+F8 for Rich Content Editor shortcuts.", {
387
+ this.iframe.setAttribute('title', formatMessage('Rich Text Area. Press {OSKey}+F8 for Rich Content Editor shortcuts.', {
363
388
  OSKey: determineOSDependentKey()
364
389
  }));
365
390
  }
@@ -372,8 +397,8 @@ class RCEWrapper extends React.Component {
372
397
 
373
398
  // cleans up highlight artifacts from findreplace plugin
374
399
  if (this.getRequiredFeatureStatuses().rce_find_replace) {
375
- editor.on("undo redo", _e => {
376
- if (editor?.dom?.doc?.getElementsByClassName?.("mce-match-marker")?.length > 0) {
400
+ editor.on('undo redo', _e => {
401
+ if (editor?.dom?.doc?.getElementsByClassName?.('mce-match-marker')?.length > 0) {
377
402
  editor.plugins?.searchreplace?.done();
378
403
  }
379
404
  });
@@ -392,7 +417,7 @@ class RCEWrapper extends React.Component {
392
417
  // This workaround removes attribute, thusly causing navigation to work correctly again.
393
418
  // For the correct solution, Keying.config should have { selector: '.tox-toolbar__group' }
394
419
  // in https://github.com/tinymce/tinymce/blob/develop/modules/alloy/src/main/ts/ephox/alloy/ui/schema/SplitSlidingToolbarSchema.ts
395
- this._elementRef.current?.querySelectorAll(".tox-toolbar-overlord button[data-alloy-tabstop]").forEach(it => it.removeAttribute("data-alloy-tabstop"));
420
+ this._elementRef.current?.querySelectorAll('.tox-toolbar-overlord button[data-alloy-tabstop]').forEach(it => it.removeAttribute('data-alloy-tabstop'));
396
421
  };
397
422
  /**
398
423
  * Sets up selection saving and restoration logic.
@@ -419,7 +444,7 @@ class RCEWrapper extends React.Component {
419
444
  selectionWasReset = false;
420
445
  }
421
446
  };
422
- editor.on("blur", () => {
447
+ editor.on('blur', () => {
423
448
  editorHasFocus = false;
424
449
  selectionWasReset = false;
425
450
  if (!this.editor) return;
@@ -428,7 +453,7 @@ class RCEWrapper extends React.Component {
428
453
  isForward: this.editor.selection.isForward()
429
454
  };
430
455
  });
431
- editor.on("focus", () => {
456
+ editor.on('focus', () => {
432
457
  // We need to restore the selection when the editor regains focus because sometimes the editor regains
433
458
  // focus without the user setting the selection themselves (such as when they interact with the toolbar)
434
459
  // and if we didn't, we would end up saving the reset selection before a user managed to actually insert
@@ -437,7 +462,7 @@ class RCEWrapper extends React.Component {
437
462
  editorHasFocus = true;
438
463
  selectionWasReset = false;
439
464
  });
440
- editor.on("SelectionChange", () => {
465
+ editor.on('SelectionChange', () => {
441
466
  if (editorHasFocus) {
442
467
  // We don't care if a selection reset occurs when the editor has focus, the user probably intended that
443
468
  // At least they will see the effect
@@ -447,14 +472,14 @@ class RCEWrapper extends React.Component {
447
472
  const selection = this.editor.selection.normalize();
448
473
 
449
474
  // Detect a browser-reset selection (e.g. From invoking the Find command)
450
- if (selection.startContainer?.nodeName === "BODY" && selection.startContainer === selection.endContainer && selection.startOffset === 0 && selection.endOffset === 0) {
475
+ if (selection.startContainer?.nodeName === 'BODY' && selection.startContainer === selection.endContainer && selection.startOffset === 0 && selection.endOffset === 0) {
451
476
  selectionWasReset = true;
452
477
  }
453
478
  });
454
- editor.on("BeforeExecCommand", () => {
479
+ editor.on('BeforeExecCommand', () => {
455
480
  restoreSelectionIfNecessary();
456
481
  });
457
- editor.on("ExecCommand", (/* event */
482
+ editor.on('ExecCommand', (/* event */
458
483
  ) => {
459
484
  if (!this.editor) return;
460
485
  // Commands may have modified the selection, we need to recapture it
@@ -469,10 +494,10 @@ class RCEWrapper extends React.Component {
469
494
  /* ********** autosave support *************** */
470
495
  this.initAutoSave = editor => {
471
496
  var _this$props$userCache;
472
- this.storage = new EncryptedStorage((_this$props$userCache = this.props.userCacheKey) !== null && _this$props$userCache !== void 0 ? _this$props$userCache : "");
497
+ this.storage = new EncryptedStorage((_this$props$userCache = this.props.userCacheKey) !== null && _this$props$userCache !== void 0 ? _this$props$userCache : '');
473
498
  if (this.storage) {
474
- editor.on("change Undo Redo", this.doAutoSave);
475
- editor.on("blur", this.doAutoSave);
499
+ editor.on('change Undo Redo', this.doAutoSave);
500
+ editor.on('blur', this.doAutoSave);
476
501
  this.cleanupAutoSave();
477
502
  try {
478
503
  const autosaved = this.getAutoSaved(this.autoSaveKey);
@@ -497,7 +522,7 @@ class RCEWrapper extends React.Component {
497
522
  } catch (ex) {
498
523
  // log and ignore
499
524
 
500
- console.error("Failed initializing rce autosave", ex);
525
+ console.error('Failed initializing rce autosave', ex);
501
526
  }
502
527
  }
503
528
  };
@@ -552,7 +577,7 @@ class RCEWrapper extends React.Component {
552
577
  this.cleanupAutoSave(true);
553
578
  this.doAutoSave(e, true);
554
579
  } else {
555
- console.error("Autosave failed:", ex);
580
+ console.error('Autosave failed:', ex);
556
581
  }
557
582
  }
558
583
  }
@@ -560,7 +585,7 @@ class RCEWrapper extends React.Component {
560
585
  /* *********** end autosave support *************** */
561
586
  this.onWordCountUpdate = e => {
562
587
  if (!this.editor) return;
563
- const shouldIgnore = countShouldIgnore(this.editor, "body", "words");
588
+ const shouldIgnore = countShouldIgnore(this.editor, 'body', 'words');
564
589
  const updatedCount = e.wordCount.words - shouldIgnore;
565
590
  this.setState(state => {
566
591
  if (updatedCount !== state.wordCount) {
@@ -573,7 +598,7 @@ class RCEWrapper extends React.Component {
573
598
  // @ts-expect-error
574
599
  this.onNodeChange = e => {
575
600
  // This is basically copied out of the tinymce silver theme code for the status bar
576
- const path = e.parents.filter(p => p.nodeName !== "BR" && !p.getAttribute("data-mce-bogus") && p.getAttribute("data-mce-type") !== "bookmark")
601
+ const path = e.parents.filter(p => p.nodeName !== 'BR' && !p.getAttribute('data-mce-bogus') && p.getAttribute('data-mce-type') !== 'bookmark')
577
602
  // @ts-expect-error
578
603
  .map(p => p.nodeName.toLowerCase()).reverse();
579
604
  this.setState({
@@ -584,7 +609,7 @@ class RCEWrapper extends React.Component {
584
609
  this.props.onContentChange?.(content);
585
610
  // check accessibility when clearing the editor,
586
611
  // all other times should be checked by handleInputChange
587
- if (content === "") {
612
+ if (content === '') {
588
613
  this.checkAccessibility();
589
614
  }
590
615
  };
@@ -606,14 +631,14 @@ class RCEWrapper extends React.Component {
606
631
  height: newHeight
607
632
  });
608
633
  // play nice and send the same event that the silver theme would send
609
- editor.fire("ResizeEditor", {
634
+ editor.fire('ResizeEditor', {
610
635
  deltaY: coordinates.deltaY
611
636
  });
612
637
  }
613
638
  };
614
639
  this.onA11yChecker = triggerElementId => {
615
640
  const editor = this.mceInstance();
616
- editor.execCommand("openAccessibilityChecker", false, {
641
+ editor.execCommand('openAccessibilityChecker', false, {
617
642
  mountNode: instuiPopupMountNodeFn,
618
643
  triggerElementId,
619
644
  onFixError: errors => {
@@ -627,7 +652,7 @@ class RCEWrapper extends React.Component {
627
652
  };
628
653
  this.checkAccessibility = () => {
629
654
  const editor = this.mceInstance();
630
- editor.execCommand("checkAccessibility", false, {
655
+ editor.execCommand('checkAccessibility', false, {
631
656
  // @ts-expect-error
632
657
  done: errors => {
633
658
  this.setState({
@@ -667,7 +692,7 @@ class RCEWrapper extends React.Component {
667
692
  }
668
693
  };
669
694
  this.handleAIClick = () => {
670
- import("./plugins/shared/ai_tools").then(module => {
695
+ import('./plugins/shared/ai_tools').then(module => {
671
696
  // @ts-expect-error
672
697
  this.AIToolsTray = module.AIToolsTray;
673
698
  this.setState({
@@ -675,7 +700,7 @@ class RCEWrapper extends React.Component {
675
700
  AITToolsFocusReturn: document.activeElement
676
701
  });
677
702
  }).catch(ex => {
678
- console.error("Failed loading the AIToolsTray", ex);
703
+ console.error('Failed loading the AIToolsTray', ex);
679
704
  });
680
705
  };
681
706
  this.closeAITools = () => {
@@ -717,10 +742,10 @@ class RCEWrapper extends React.Component {
717
742
  this.getCurrentContentForAI = () => {
718
743
  const selected = this.mceInstance().selection.getContent();
719
744
  return selected ? {
720
- type: "selection",
745
+ type: 'selection',
721
746
  content: selected
722
747
  } : {
723
- type: "full",
748
+ type: 'full',
724
749
  content: this.mceInstance().getContent()
725
750
  };
726
751
  };
@@ -735,7 +760,7 @@ class RCEWrapper extends React.Component {
735
760
  alert.id = alertIdValue++;
736
761
  this.setState(state => {
737
762
  let messages = state.messages.concat(alert);
738
- messages = _.uniqBy(messages, "text"); // Don't show the same message twice
763
+ messages = _.uniqBy(messages, 'text'); // Don't show the same message twice
739
764
  return {
740
765
  messages
741
766
  };
@@ -754,7 +779,7 @@ class RCEWrapper extends React.Component {
754
779
  */
755
780
  this.resetAlertId = () => {
756
781
  if (this.state.messages.length > 0) {
757
- throw new Error("There are messages currently, you cannot reset when they are non-zero");
782
+ throw new Error('There are messages currently, you cannot reset when they are non-zero');
758
783
  }
759
784
  alertIdValue = 0;
760
785
  };
@@ -796,7 +821,7 @@ class RCEWrapper extends React.Component {
796
821
  if (!Number.isNaN(_ht)) {
797
822
  _ht = `${_ht}px`;
798
823
  }
799
- const currentRCECount = document.querySelectorAll(".rce-wrapper").length;
824
+ const currentRCECount = document.querySelectorAll('.rce-wrapper').length;
800
825
  const maxInitRenderedRCEs = Number.isNaN(props.maxInitRenderedRCEs) ? RCEWrapper.defaultProps.maxInitRenderedRCEs : props.maxInitRenderedRCEs;
801
826
  this.state = {
802
827
  path: [],
@@ -807,9 +832,9 @@ class RCEWrapper extends React.Component {
807
832
  messages: [],
808
833
  announcement: null,
809
834
  confirmAutoSave: false,
810
- autoSavedContent: "",
835
+ autoSavedContent: '',
811
836
  // @ts-expect-error
812
- id: this.props.id || this.props.textareaId || `${uid("rce", 2)}`,
837
+ id: this.props.id || this.props.textareaId || `${uid('rce', 2)}`,
813
838
  // @ts-expect-error
814
839
  height: _ht,
815
840
  fullscreenState: {
@@ -817,7 +842,7 @@ class RCEWrapper extends React.Component {
817
842
  prevHeight: _ht
818
843
  },
819
844
  a11yErrorsCount: 0,
820
- shouldShowEditor: typeof IntersectionObserver === "undefined" || maxInitRenderedRCEs <= 0 || currentRCECount < maxInitRenderedRCEs,
845
+ shouldShowEditor: typeof IntersectionObserver === 'undefined' || maxInitRenderedRCEs <= 0 || currentRCECount < maxInitRenderedRCEs,
821
846
  AIToolsOpen: false
822
847
  };
823
848
  this._statusBarId = `${this.state.id}_statusbar`;
@@ -852,11 +877,11 @@ class RCEWrapper extends React.Component {
852
877
  // configure tinymce to say where that div is mounted, do this
853
878
  // is a bit of a hack to tag the div that is this RCE's
854
879
  _tagTinymceAuxDiv() {
855
- const tinyauxlist = document.querySelectorAll(".tox-tinymce-aux");
880
+ const tinyauxlist = document.querySelectorAll('.tox-tinymce-aux');
856
881
  if (tinyauxlist.length) {
857
882
  const myaux = tinyauxlist[tinyauxlist.length - 1];
858
883
  if (myaux.id) {
859
- console.error("Unexpected ID on my tox-tinymce-aux element");
884
+ console.error('Unexpected ID on my tox-tinymce-aux element');
860
885
  }
861
886
  myaux.id = `tinyaux-${this.id}`;
862
887
  }
@@ -916,7 +941,7 @@ class RCEWrapper extends React.Component {
916
941
  let status = true;
917
942
  // Check for remaining placeholders
918
943
  if (this.mceInstance().dom.doc.querySelector(`[data-placeholder-for]`)) {
919
- status = promptFunc(formatMessage("Content is still being uploaded, if you continue it will not be embedded properly."));
944
+ status = promptFunc(formatMessage('Content is still being uploaded, if you continue it will not be embedded properly.'));
920
945
  }
921
946
  return status;
922
947
  }
@@ -976,9 +1001,9 @@ class RCEWrapper extends React.Component {
976
1001
  // @ts-expect-error
977
1002
  ifr.contentDocument.body.clientHeight -
978
1003
  // @ts-expect-error
979
- parseInt(editor_body_style["padding-top"], 10) -
1004
+ parseInt(editor_body_style['padding-top'], 10) -
980
1005
  // @ts-expect-error
981
- parseInt(editor_body_style["padding-bottom"], 10);
1006
+ parseInt(editor_body_style['padding-bottom'], 10);
982
1007
  const para_margin_ht = 24;
983
1008
  const reserve_ht = Math.ceil(height + para_margin_ht);
984
1009
  if (reserve_ht > editor_ht) {
@@ -990,7 +1015,7 @@ class RCEWrapper extends React.Component {
990
1015
  }
991
1016
  }
992
1017
  checkImageLoadError(element) {
993
- if (!element || element.tagName !== "IMG") {
1018
+ if (!element || element.tagName !== 'IMG') {
994
1019
  return;
995
1020
  }
996
1021
  // @ts-expect-error
@@ -1005,9 +1030,9 @@ class RCEWrapper extends React.Component {
1005
1030
  // @ts-expect-error
1006
1031
  if (element.naturalWidth === 0) {
1007
1032
  // @ts-expect-error
1008
- element.style.border = "1px solid #000";
1033
+ element.style.border = '1px solid #000';
1009
1034
  // @ts-expect-error
1010
- element.style.padding = "2px";
1035
+ element.style.padding = '2px';
1011
1036
  }
1012
1037
  }, 0);
1013
1038
  }
@@ -1017,7 +1042,7 @@ class RCEWrapper extends React.Component {
1017
1042
  this.contentInserted(element);
1018
1043
  }
1019
1044
  replaceCode(code) {
1020
- if (code !== "" && window.confirm(formatMessage("Content in the editor will be changed. Press Cancel to keep the original content."))) {
1045
+ if (code !== '' && window.confirm(formatMessage('Content in the editor will be changed. Press Cancel to keep the original content.'))) {
1021
1046
  this.mceInstance().setContent(code);
1022
1047
  }
1023
1048
  }
@@ -1034,12 +1059,12 @@ class RCEWrapper extends React.Component {
1034
1059
  // that there's some embedded content helper
1035
1060
  // From what I've read, "title" is more reliable than "aria-label" for
1036
1061
  // elements like iframes and embeds.
1037
- const temp = document.createElement("div");
1062
+ const temp = document.createElement('div');
1038
1063
  temp.innerHTML = code;
1039
1064
  const code_elem = temp.firstElementChild;
1040
1065
  if (code_elem) {
1041
- if (!code_elem.hasAttribute("title") && !code_elem.hasAttribute("aria-label")) {
1042
- code_elem.setAttribute("title", formatMessage("embedded content"));
1066
+ if (!code_elem.hasAttribute('title') && !code_elem.hasAttribute('aria-label')) {
1067
+ code_elem.setAttribute('title', formatMessage('embedded content'));
1043
1068
  }
1044
1069
  code = code_elem.outerHTML;
1045
1070
  }
@@ -1049,7 +1074,7 @@ class RCEWrapper extends React.Component {
1049
1074
  // and it's often inserted into a <p> on top of that. Find the
1050
1075
  // iframe and use it to flash the indicator.
1051
1076
  const element = contentInsertion.insertContent(editor, code);
1052
- const ifr = element && element.querySelector && element.querySelector("iframe");
1077
+ const ifr = element && element.querySelector && element.querySelector('iframe');
1053
1078
  if (ifr) {
1054
1079
  this.contentInserted(ifr);
1055
1080
  } else {
@@ -1061,7 +1086,7 @@ class RCEWrapper extends React.Component {
1061
1086
  const element = contentInsertion.insertImage(editor, image, this.getCanvasUrl());
1062
1087
 
1063
1088
  // Removes TinyMCE's caret &nbsp; text if exists.
1064
- if (element?.nextSibling?.data?.startsWith("\xA0" /* nbsp */)) {
1089
+ if (element?.nextSibling?.data?.startsWith('\xA0' /* nbsp */)) {
1065
1090
  element.nextSibling.splitText(1);
1066
1091
  element.nextSibling.remove();
1067
1092
  }
@@ -1142,8 +1167,8 @@ class RCEWrapper extends React.Component {
1142
1167
  onTinyMCEInstance(command, ...args) {
1143
1168
  const editor = this.mceInstance();
1144
1169
  if (editor) {
1145
- if (command === "mceRemoveEditor") {
1146
- editor.execCommand("mceNewDocument");
1170
+ if (command === 'mceRemoveEditor') {
1171
+ editor.execCommand('mceNewDocument');
1147
1172
  } // makes sure content can't persist past removal
1148
1173
  editor.execCommand(command, false, ...args);
1149
1174
  }
@@ -1163,17 +1188,17 @@ class RCEWrapper extends React.Component {
1163
1188
  return null;
1164
1189
  }
1165
1190
  textareaValue() {
1166
- return this.getTextarea()?.value || "";
1191
+ return this.getTextarea()?.value || '';
1167
1192
  }
1168
1193
  get id() {
1169
1194
  return this.state.id;
1170
1195
  }
1171
1196
  getHtmlEditorStorage() {
1172
- const cookieValue = getCookie("rce.htmleditor");
1197
+ const cookieValue = getCookie('rce.htmleditor');
1173
1198
  if (cookieValue) {
1174
1199
  document.cookie = `rce.htmleditor=${cookieValue};path=/;max-age=0`;
1175
1200
  }
1176
- const value = cookieValue || this.storage?.getItem?.("rce.htmleditor")?.content;
1201
+ const value = cookieValue || this.storage?.getItem?.('rce.htmleditor')?.content;
1177
1202
  return value === RAW_HTML_EDITOR_VIEW || value === PRETTY_HTML_EDITOR_VIEW ? value : PRETTY_HTML_EDITOR_VIEW;
1178
1203
  }
1179
1204
  _isFullscreen() {
@@ -1190,7 +1215,7 @@ class RCEWrapper extends React.Component {
1190
1215
  this._elementRef.current?.appendChild(tinymenuhost);
1191
1216
  }
1192
1217
  this._elementRef.current?.addEventListener(FS_CHANGEEVENT, this._onFullscreenChange);
1193
- if (typeof this._elementRef.current?.offsetHeight === "number") {
1218
+ if (typeof this._elementRef.current?.offsetHeight === 'number') {
1194
1219
  this.setState({
1195
1220
  fullscreenState: {
1196
1221
  prevHeight: this._elementRef.current.offsetHeight - this._getStatusBarHeight()
@@ -1222,7 +1247,7 @@ class RCEWrapper extends React.Component {
1222
1247
  const container = ed.getContainer();
1223
1248
  if (container) {
1224
1249
  container.style.height = cssHeight;
1225
- ed.fire("ResizeEditor");
1250
+ ed.fire('ResizeEditor');
1226
1251
  }
1227
1252
  const textarea = this.getTextarea();
1228
1253
  if (textarea) {
@@ -1233,10 +1258,10 @@ class RCEWrapper extends React.Component {
1233
1258
  });
1234
1259
  }
1235
1260
  focus() {
1236
- this.onTinyMCEInstance("mceFocus");
1261
+ this.onTinyMCEInstance('mceFocus');
1237
1262
  // tinymce doesn't always call the focus handler.
1238
1263
  // @ts-expect-error
1239
- this.handleFocusEditor(new Event("focus", {
1264
+ this.handleFocusEditor(new Event('focus', {
1240
1265
  target: this.mceInstance()
1241
1266
  }));
1242
1267
  }
@@ -1274,7 +1299,7 @@ class RCEWrapper extends React.Component {
1274
1299
  */
1275
1300
  get _mceSerializedInitialHtml() {
1276
1301
  if (!this._mceSerializedInitialHtmlCached) {
1277
- const el = window.document.createElement("div");
1302
+ const el = window.document.createElement('div');
1278
1303
  // @ts-expect-error
1279
1304
  el.innerHTML = this.initialContent;
1280
1305
  const serializer = this.mceInstance().serializer;
@@ -1328,22 +1353,22 @@ class RCEWrapper extends React.Component {
1328
1353
  // focus is still somewhere w/in me
1329
1354
  return;
1330
1355
  }
1331
- const activeClass = document.activeElement?.getAttribute("class");
1356
+ const activeClass = document.activeElement?.getAttribute('class');
1332
1357
  if (
1333
1358
  // @ts-expect-error
1334
1359
  (event.focusedEditor === undefined ||
1335
1360
  // @ts-expect-error
1336
- event.target.id === event.focusedEditor?.id) && activeClass?.includes("tox-")) {
1361
+ event.target.id === event.focusedEditor?.id) && activeClass?.includes('tox-')) {
1337
1362
  // if a toolbar button has focus, then the user clicks on the "more" button
1338
1363
  // focus jumps to the body, then eventually to the popped up toolbar. This
1339
1364
  // catches that case.
1340
1365
  return;
1341
1366
  }
1342
- if (event?.relatedTarget?.getAttribute("class")?.includes("tox-")) {
1367
+ if (event?.relatedTarget?.getAttribute('class')?.includes('tox-')) {
1343
1368
  // a tinymce popup has focus
1344
1369
  return;
1345
1370
  }
1346
- const popups = document.querySelectorAll("[data-mce-component]");
1371
+ const popups = document.querySelectorAll('[data-mce-component]');
1347
1372
  for (const popup of popups) {
1348
1373
  if (popup.contains(document.activeElement)) {
1349
1374
  // one of our popups has focus
@@ -1361,22 +1386,22 @@ class RCEWrapper extends React.Component {
1361
1386
  call(methodName, ...args) {
1362
1387
  // since exists? has a ? and cant be a regular function just return true
1363
1388
  // rather than calling as a fn on the editor
1364
- if (methodName === "exists?") {
1389
+ if (methodName === 'exists?') {
1365
1390
  return true;
1366
1391
  }
1367
1392
  // @ts-expect-error
1368
1393
  return this[methodName](...args);
1369
1394
  }
1370
1395
  announceContextToolbars(editor) {
1371
- editor.on("NodeChange", () => {
1396
+ editor.on('NodeChange', () => {
1372
1397
  if (!this._isMounted) return;
1373
1398
  const node = editor.selection.getNode();
1374
1399
  // @ts-expect-error
1375
1400
  if (isImageEmbed(node, editor)) {
1376
1401
  if (this.announcing !== 1) {
1377
1402
  this.setState({
1378
- announcement: formatMessage("type Control F9 to access image options. {text}", {
1379
- text: node.getAttribute("alt")
1403
+ announcement: formatMessage('type Control F9 to access image options. {text}', {
1404
+ text: node.getAttribute('alt')
1380
1405
  })
1381
1406
  });
1382
1407
  this.announcing = 1;
@@ -1384,7 +1409,7 @@ class RCEWrapper extends React.Component {
1384
1409
  } else if (isFileLink(node, editor)) {
1385
1410
  if (this.announcing !== 2) {
1386
1411
  this.setState({
1387
- announcement: formatMessage("type Control F9 to access link options. {text}", {
1412
+ announcement: formatMessage('type Control F9 to access link options. {text}', {
1388
1413
  text: node.textContent
1389
1414
  })
1390
1415
  });
@@ -1393,7 +1418,7 @@ class RCEWrapper extends React.Component {
1393
1418
  } else if (isElementWithinTable(node)) {
1394
1419
  if (this.announcing !== 3) {
1395
1420
  this.setState({
1396
- announcement: formatMessage("type Control F9 to access table options. {text}", {
1421
+ announcement: formatMessage('type Control F9 to access table options. {text}', {
1397
1422
  text: node.textContent
1398
1423
  })
1399
1424
  });
@@ -1406,17 +1431,17 @@ class RCEWrapper extends React.Component {
1406
1431
  this.announcing = 0;
1407
1432
  }
1408
1433
  });
1409
- editor.on("ResizeEditor", ({
1434
+ editor.on('ResizeEditor', ({
1410
1435
  deltaY
1411
1436
  }) => {
1412
1437
  if (!this._isMounted || !deltaY) return;
1413
1438
  if (deltaY < 0) {
1414
1439
  this.setState({
1415
- announcement: formatMessage("The height of Rich Content Area is decreased.")
1440
+ announcement: formatMessage('The height of Rich Content Area is decreased.')
1416
1441
  });
1417
1442
  } else {
1418
1443
  this.setState({
1419
- announcement: formatMessage("The height of Rich Content Area is increased.")
1444
+ announcement: formatMessage('The height of Rich Content Area is increased.')
1420
1445
  });
1421
1446
  }
1422
1447
  });
@@ -1442,10 +1467,10 @@ class RCEWrapper extends React.Component {
1442
1467
  // This doesn't apply if the editor is off-screen or has visibility:hidden;
1443
1468
  // only if it isn't rendered or has display:none;
1444
1469
  const editorVisible = this.editor.getContainer().offsetParent;
1445
- return this.props.autosave?.enabled && editorVisible && document.querySelectorAll(".rce-wrapper").length === 1 && storageAvailable();
1470
+ return this.props.autosave?.enabled && editorVisible && document.querySelectorAll('.rce-wrapper').length === 1 && storageAvailable();
1446
1471
  }
1447
1472
  get autoSaveKey() {
1448
- const userId = this._effectiveContainingContext?.userId || "-";
1473
+ const userId = this._effectiveContainingContext?.userId || '-';
1449
1474
  return `rceautosave:${userId}${window.location.href}:${this.props.textareaId}`;
1450
1475
  }
1451
1476
  componentWillUnmount() {
@@ -1456,7 +1481,7 @@ class RCEWrapper extends React.Component {
1456
1481
  this.destroy();
1457
1482
  }
1458
1483
  if (this._elementRef.current) {
1459
- this._elementRef.current.removeEventListener("keydown", this.handleKey, true);
1484
+ this._elementRef.current.removeEventListener('keydown', this.handleKey, true);
1460
1485
  }
1461
1486
  this.mutationObserver?.disconnect();
1462
1487
  this.intersectionObserver?.disconnect();
@@ -1468,27 +1493,27 @@ class RCEWrapper extends React.Component {
1468
1493
 
1469
1494
  // @ts-expect-error
1470
1495
  const setupCallback = options.setup;
1471
- const canvasPlugins = rcsExists ? ["instructure_image", "instructure_documents", "instructure_equation"] : [];
1496
+ const canvasPlugins = rcsExists ? ['instructure_image', 'instructure_documents', 'instructure_equation'] : [];
1472
1497
  if (rcsExists && !this.props.instRecordDisabled) {
1473
- canvasPlugins.splice(2, 0, "instructure_record");
1498
+ canvasPlugins.splice(2, 0, 'instructure_record');
1474
1499
  }
1475
- const pastePlugins = rcsExists ? ["instructure_paste", "paste"] : ["paste"];
1476
- if (rcsExists && this.props.use_rce_icon_maker && this.props.trayProps?.contextType === "course") {
1477
- canvasPlugins.push("instructure_icon_maker");
1500
+ const pastePlugins = rcsExists ? ['instructure_paste', 'paste'] : ['paste'];
1501
+ if (rcsExists && this.props.use_rce_icon_maker && this.props.trayProps?.contextType === 'course') {
1502
+ canvasPlugins.push('instructure_icon_maker');
1478
1503
  }
1479
1504
  if (document[FS_ENABLED]) {
1480
- canvasPlugins.push("instructure_fullscreen");
1505
+ canvasPlugins.push('instructure_fullscreen');
1481
1506
  }
1482
1507
  if (this.getRequiredFeatureStatuses().rce_find_replace) {
1483
- canvasPlugins.push("searchreplace");
1484
- canvasPlugins.push("instructure_search_and_replace");
1508
+ canvasPlugins.push('searchreplace');
1509
+ canvasPlugins.push('instructure_search_and_replace');
1485
1510
  }
1486
- const possibleNewMenubarItems = this.props.editorOptions.menu ? Object.keys(this.props.editorOptions.menu).join(" ") : undefined;
1511
+ const possibleNewMenubarItems = this.props.editorOptions.menu ? Object.keys(this.props.editorOptions.menu).join(' ') : undefined;
1487
1512
  const wrappedOpts = {
1488
1513
  ...defaultTinymceConfig,
1489
1514
  ...options,
1490
1515
  readonly: this.props.readOnly,
1491
- theme: "silver",
1516
+ theme: 'silver',
1492
1517
  // some older code specified 'modern', which doesn't exist any more
1493
1518
 
1494
1519
  // @ts-expect-error
@@ -1497,7 +1522,7 @@ class RCEWrapper extends React.Component {
1497
1522
  document_base_url: this.props.canvasOrigin,
1498
1523
  block_formats:
1499
1524
  // @ts-expect-error
1500
- options.block_formats || [`${formatMessage("Heading 2")}=h2`, `${formatMessage("Heading 3")}=h3`, `${formatMessage("Heading 4")}=h4`, `${formatMessage("Preformatted")}=pre`, `${formatMessage("Paragraph")}=p`].join("; "),
1525
+ options.block_formats || [`${formatMessage('Heading 2')}=h2`, `${formatMessage('Heading 3')}=h3`, `${formatMessage('Heading 4')}=h4`, `${formatMessage('Preformatted')}=pre`, `${formatMessage('Paragraph')}=p`].join('; '),
1501
1526
  setup: editor => {
1502
1527
  addKebabIcon(editor);
1503
1528
  editorWrappers.set(editor, this);
@@ -1510,7 +1535,7 @@ class RCEWrapper extends React.Component {
1510
1535
  // @ts-expect-error
1511
1536
  bridge.userLocale = userLocale;
1512
1537
  bridge.canvasOrigin = this.props.canvasOrigin;
1513
- if (typeof setupCallback === "function") {
1538
+ if (typeof setupCallback === 'function') {
1514
1539
  setupCallback(editor);
1515
1540
  }
1516
1541
  },
@@ -1521,7 +1546,7 @@ class RCEWrapper extends React.Component {
1521
1546
  // @ts-expect-error
1522
1547
  content_css: options.content_css || [],
1523
1548
  // @ts-expect-error
1524
- content_style: contentCSS + (options.content_style || ""),
1549
+ content_style: contentCSS + (options.content_style || ''),
1525
1550
  menubar: mergeMenuItems(getMenubarForVariant(this.variant), possibleNewMenubarItems),
1526
1551
  // default menu options listed at https://www.tiny.cloud/docs/configure/editor-appearance/#menu
1527
1552
  // tinymce's default edit and table menus are fine
@@ -1536,10 +1561,10 @@ class RCEWrapper extends React.Component {
1536
1561
  getToolbarForVariant(this.variant, this.ltiToolFavorites),
1537
1562
  // @ts-expect-error
1538
1563
  options.toolbar),
1539
- contextmenu: "",
1564
+ contextmenu: '',
1540
1565
  // show the browser's native context menu
1541
1566
 
1542
- toolbar_mode: "sliding",
1567
+ toolbar_mode: 'sliding',
1543
1568
  toolbar_sticky: true,
1544
1569
  // In regards to the ability to disable plugins:
1545
1570
  // we only have to explicitly manage the removal of plugins
@@ -1548,16 +1573,16 @@ class RCEWrapper extends React.Component {
1548
1573
  // handles all of that complexity. It that ever changes in the
1549
1574
  // future in an upgraded version, we will have to update the
1550
1575
  // logic in those other places as well.
1551
- plugins: mergePlugins(["autolink", "media", "table", "link", "directionality", "lists", "textpattern", "hr", "instructure_color", "instructure-ui-icons", "instructure_condensed_buttons", "instructure_links", "instructure_html_view", "instructure_media_embed", "a11y_checker", "wordcount", "instructure_wordcount", "instructure_wordcount_header", "instructure_keyboard_shortcuts_header", "instructure_studio_media_options", "instructure_rce_external_tools", ...pastePlugins, ...canvasPlugins],
1576
+ plugins: mergePlugins(['autolink', 'media', 'table', 'link', 'directionality', 'lists', 'textpattern', 'hr', 'instructure_color', 'instructure-ui-icons', 'instructure_condensed_buttons', 'instructure_links', 'instructure_html_view', 'instructure_media_embed', 'a11y_checker', 'wordcount', 'instructure_wordcount', 'instructure_wordcount_header', 'instructure_keyboard_shortcuts_header', 'instructure_studio_media_options', 'instructure_rce_external_tools', ...pastePlugins, ...canvasPlugins],
1552
1577
  // filter out the plugins designated for removal
1553
1578
  // @ts-expect-error
1554
- sanitizePlugins(options.plugins)?.filter(p => p.length > 0 && p[0] !== "-"), this.pluginsToExclude),
1579
+ sanitizePlugins(options.plugins)?.filter(p => p.length > 0 && p[0] !== '-'), this.pluginsToExclude),
1555
1580
  textpattern_patterns: [{
1556
- start: "* ",
1557
- cmd: "InsertUnorderedList"
1581
+ start: '* ',
1582
+ cmd: 'InsertUnorderedList'
1558
1583
  }, {
1559
- start: "- ",
1560
- cmd: "InsertUnorderedList"
1584
+ start: '- ',
1585
+ cmd: 'InsertUnorderedList'
1561
1586
  }]
1562
1587
  };
1563
1588
  if (this.props.trayProps) {
@@ -1579,7 +1604,7 @@ class RCEWrapper extends React.Component {
1579
1604
  }
1580
1605
  unhandleTextareaChange() {
1581
1606
  if (this._textareaEl) {
1582
- this._textareaEl.removeEventListener("input", this.handleTextareaChange);
1607
+ this._textareaEl.removeEventListener('input', this.handleTextareaChange);
1583
1608
  }
1584
1609
  }
1585
1610
  registerTextareaChange() {
@@ -1587,7 +1612,7 @@ class RCEWrapper extends React.Component {
1587
1612
  if (this._textareaEl !== el) {
1588
1613
  this.unhandleTextareaChange();
1589
1614
  if (el) {
1590
- el.addEventListener("input", this.handleTextareaChange);
1615
+ el.addEventListener('input', this.handleTextareaChange);
1591
1616
  if (this.props.textareaClassName) {
1592
1617
  // split the string on whitespace because classList doesn't let you add multiple
1593
1618
  // space seperated classes at a time but does let you add an array of them
@@ -1613,7 +1638,7 @@ class RCEWrapper extends React.Component {
1613
1638
  // initialize the RCE when it gets close to entering the viewport
1614
1639
  {
1615
1640
  root: null,
1616
- rootMargin: "200px 0px",
1641
+ rootMargin: '200px 0px',
1617
1642
  threshold: 0.0
1618
1643
  });
1619
1644
  // @ts-expect-error
@@ -1632,7 +1657,7 @@ class RCEWrapper extends React.Component {
1632
1657
  this.focusCurrentView();
1633
1658
  }
1634
1659
  if (prevProps.readOnly !== this.props.readOnly) {
1635
- this.mceInstance().mode.set(this.props.readOnly ? "readonly" : "design");
1660
+ this.mceInstance().mode.set(this.props.readOnly ? 'readonly' : 'design');
1636
1661
  }
1637
1662
  }
1638
1663
  }
@@ -1646,7 +1671,7 @@ class RCEWrapper extends React.Component {
1646
1671
  this._tagTinymceAuxDiv();
1647
1672
  this.registerTextareaChange();
1648
1673
  // @ts-expect-error
1649
- this._elementRef.current.addEventListener("keydown", this.handleKey, true);
1674
+ this._elementRef.current.addEventListener('keydown', this.handleKey, true);
1650
1675
  // give the textarea its initial size
1651
1676
  this.onResize(null, {
1652
1677
  deltaY: 0
@@ -1654,7 +1679,7 @@ class RCEWrapper extends React.Component {
1654
1679
  // Preload the LTI Tools modal
1655
1680
  // This helps with loading the favorited external tools
1656
1681
  if (this.ltiToolFavorites.length > 0) {
1657
- import("./plugins/instructure_rce_external_tools/components/ExternalToolSelectionDialog/ExternalToolSelectionDialog");
1682
+ import('./plugins/instructure_rce_external_tools/components/ExternalToolSelectionDialog/ExternalToolSelectionDialog');
1658
1683
  }
1659
1684
  bridge.renderEditor(this);
1660
1685
  }
@@ -1676,9 +1701,9 @@ class RCEWrapper extends React.Component {
1676
1701
  fallback: /*#__PURE__*/React.createElement("div", {
1677
1702
  style: {
1678
1703
  height: this.state.height,
1679
- display: "flex",
1680
- justifyContent: "center",
1681
- alignItems: "center"
1704
+ display: 'flex',
1705
+ justifyContent: 'center',
1706
+ alignItems: 'center'
1682
1707
  }
1683
1708
  }, /*#__PURE__*/React.createElement(Spinner, {
1684
1709
  renderTitle: renderLoading,
@@ -1714,7 +1739,7 @@ class RCEWrapper extends React.Component {
1714
1739
  ref: this._editorPlaceholderRef,
1715
1740
  style: {
1716
1741
  height: `${this.props.editorOptions.height}px`,
1717
- border: "1px solid grey"
1742
+ border: '1px solid grey'
1718
1743
  }
1719
1744
  });
1720
1745
  }
@@ -1740,8 +1765,8 @@ class RCEWrapper extends React.Component {
1740
1765
  // @ts-expect-error
1741
1766
  ,
1742
1767
  ref: this._elementRef,
1743
- style: this.variant === "full" ? {
1744
- marginBottom: ".5rem"
1768
+ style: this.variant === 'full' ? {
1769
+ marginBottom: '.5rem'
1745
1770
  } : undefined,
1746
1771
  onFocus: this.handleFocusRCE,
1747
1772
  onBlur: this.handleBlurRCE
@@ -1749,7 +1774,7 @@ class RCEWrapper extends React.Component {
1749
1774
  id: `show-on-focus-btn-${this.id}`,
1750
1775
  onClick: this.openKBShortcutModal,
1751
1776
  margin: "xx-small",
1752
- screenReaderLabel: formatMessage("View keyboard shortcuts")
1777
+ screenReaderLabel: formatMessage('View keyboard shortcuts')
1753
1778
  // @ts-expect-error
1754
1779
  ,
1755
1780
  ref: el => this._showOnFocusButton = el
@@ -1761,7 +1786,7 @@ class RCEWrapper extends React.Component {
1761
1786
  afterDismiss: this.removeAlert
1762
1787
  }), this.state.editorView === PRETTY_HTML_EDITOR_VIEW && this.renderHtmlEditor(), /*#__PURE__*/React.createElement("div", {
1763
1788
  style: {
1764
- display: this.state.editorView === PRETTY_HTML_EDITOR_VIEW ? "none" : "block"
1789
+ display: this.state.editorView === PRETTY_HTML_EDITOR_VIEW ? 'none' : 'block'
1765
1790
  }
1766
1791
  }, /*#__PURE__*/React.createElement(Editor, {
1767
1792
  id: mceProps.textareaId,
@@ -1898,8 +1923,8 @@ RCEWrapper.defaultProps = {
1898
1923
  maxInitRenderedRCEs: -1,
1899
1924
  features: {},
1900
1925
  timezone: Intl?.DateTimeFormat()?.resolvedOptions()?.timeZone,
1901
- canvasOrigin: "",
1902
- variant: "full"
1926
+ canvasOrigin: '',
1927
+ variant: 'full'
1903
1928
  };
1904
1929
  RCEWrapper.skinCssInjected = false;
1905
1930
  export default RCEWrapper;