@instructure/canvas-rce 7.2.0 → 7.3.0

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 (97) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/es/enhance-user-content/doc_previews.js +1 -14
  3. package/es/index.d.ts +1 -0
  4. package/es/index.js +2 -1
  5. package/es/rce/AlertMessageArea.d.ts +2 -2
  6. package/es/rce/AlertMessageArea.js +4 -6
  7. package/es/rce/RCEGlobals.d.ts +2 -0
  8. package/es/rce/RCEGlobals.js +1 -0
  9. package/es/rce/RCEVariants.js +3 -3
  10. package/es/rce/RCEWrapper.d.ts +14 -12
  11. package/es/rce/RCEWrapper.js +206 -199
  12. package/es/rce/plugins/instructure_keyboard_shortcuts_header/clickCallback.d.ts +2 -0
  13. package/es/rce/plugins/instructure_keyboard_shortcuts_header/clickCallback.js +45 -0
  14. package/es/rce/plugins/instructure_keyboard_shortcuts_header/plugin.d.ts +1 -0
  15. package/es/rce/plugins/instructure_keyboard_shortcuts_header/plugin.js +43 -0
  16. package/es/rce/plugins/instructure_rce_external_tools/components/ExternalToolSelectionDialog/ExternalToolSelectionDialog.d.ts +1 -1
  17. package/es/rce/plugins/instructure_rce_external_tools/components/ExternalToolSelectionDialog/ExternalToolSelectionDialog.js +25 -25
  18. package/es/rce/plugins/instructure_record/AudioOptionsTray/TrayController.d.ts +1 -1
  19. package/es/rce/plugins/instructure_record/AudioOptionsTray/TrayController.js +2 -1
  20. package/es/rce/plugins/instructure_record/VideoOptionsTray/TrayController.d.ts +1 -1
  21. package/es/rce/plugins/instructure_record/VideoOptionsTray/TrayController.js +2 -1
  22. package/es/rce/plugins/instructure_record/mediaTranslations.js +1 -1
  23. package/es/rce/plugins/instructure_studio_media_options/plugin.js +109 -14
  24. package/es/rce/plugins/instructure_studio_media_options/studioToolbarIcons.d.ts +5 -0
  25. package/es/rce/plugins/instructure_studio_media_options/studioToolbarIcons.js +23 -0
  26. package/es/rce/plugins/instructure_wordcount_header/plugin.d.ts +1 -0
  27. package/es/rce/plugins/instructure_wordcount_header/plugin.js +75 -0
  28. package/es/rce/plugins/shared/ContentSelection.d.ts +1 -2
  29. package/es/rce/plugins/shared/ContentSelection.js +1 -18
  30. package/es/rce/plugins/shared/StudioLtiSupportUtils.d.ts +9 -1
  31. package/es/rce/plugins/shared/StudioLtiSupportUtils.js +94 -1
  32. package/es/rce/plugins/shared/Upload/ComputerPanel.js +1 -1
  33. package/es/rce/plugins/shared/Upload/UploadFileModal.js +37 -4
  34. package/es/rce/plugins/shared/Upload/VideoUrlPanel.d.ts +15 -0
  35. package/es/rce/plugins/shared/Upload/VideoUrlPanel.js +51 -0
  36. package/es/rce/plugins/shared/Upload/videoValidationUtils.d.ts +7 -0
  37. package/es/rce/plugins/shared/Upload/videoValidationUtils.js +58 -0
  38. package/es/rce/plugins/shared/iframeUtils.d.ts +1 -0
  39. package/es/rce/plugins/shared/iframeUtils.js +37 -0
  40. package/es/rce/tinyRCE.js +2 -0
  41. package/es/sidebar/actions/upload.d.ts +1 -0
  42. package/es/sidebar/actions/upload.js +56 -0
  43. package/es/sidebar/containers/sidebarHandlers.d.ts +1 -0
  44. package/es/sidebar/containers/sidebarHandlers.js +2 -1
  45. package/es/translations/locales/ar.js +15 -6
  46. package/es/translations/locales/ca.js +15 -6
  47. package/es/translations/locales/cy.js +15 -6
  48. package/es/translations/locales/da-x-k12.js +15 -6
  49. package/es/translations/locales/da.js +15 -6
  50. package/es/translations/locales/de.js +15 -6
  51. package/es/translations/locales/el.js +6 -0
  52. package/es/translations/locales/en-AU-x-unimelb.js +15 -6
  53. package/es/translations/locales/en-GB-x-ukhe.js +15 -6
  54. package/es/translations/locales/en.js +30 -6
  55. package/es/translations/locales/en_AU.js +15 -6
  56. package/es/translations/locales/en_CA.js +15 -6
  57. package/es/translations/locales/en_CY.js +15 -6
  58. package/es/translations/locales/en_GB.js +15 -6
  59. package/es/translations/locales/es.js +15 -6
  60. package/es/translations/locales/es_ES.js +15 -6
  61. package/es/translations/locales/fa_IR.js +6 -3
  62. package/es/translations/locales/fi.js +15 -6
  63. package/es/translations/locales/fr.js +15 -6
  64. package/es/translations/locales/fr_CA.js +19 -10
  65. package/es/translations/locales/ga.js +15 -6
  66. package/es/translations/locales/he.js +6 -0
  67. package/es/translations/locales/hi.js +15 -6
  68. package/es/translations/locales/ht.js +15 -6
  69. package/es/translations/locales/hu.js +6 -6
  70. package/es/translations/locales/hy.js +6 -0
  71. package/es/translations/locales/id.js +15 -6
  72. package/es/translations/locales/is.js +21 -6
  73. package/es/translations/locales/it.js +15 -6
  74. package/es/translations/locales/ja.js +15 -6
  75. package/es/translations/locales/ko.js +6 -0
  76. package/es/translations/locales/mi.js +15 -6
  77. package/es/translations/locales/ms.js +15 -6
  78. package/es/translations/locales/nb-x-k12.js +15 -6
  79. package/es/translations/locales/nb.js +15 -6
  80. package/es/translations/locales/nl.js +15 -6
  81. package/es/translations/locales/nn.js +6 -6
  82. package/es/translations/locales/pl.js +15 -6
  83. package/es/translations/locales/pt.js +15 -6
  84. package/es/translations/locales/pt_BR.js +15 -6
  85. package/es/translations/locales/ru.js +15 -6
  86. package/es/translations/locales/sl.js +15 -6
  87. package/es/translations/locales/sv-x-k12.js +15 -6
  88. package/es/translations/locales/sv.js +15 -6
  89. package/es/translations/locales/th.js +15 -6
  90. package/es/translations/locales/tr.js +6 -3
  91. package/es/translations/locales/uk_UA.js +6 -3
  92. package/es/translations/locales/vi.js +15 -6
  93. package/es/translations/locales/zh-Hans.js +15 -6
  94. package/es/translations/locales/zh-Hant.js +15 -6
  95. package/es/translations/locales/zh.js +15 -6
  96. package/es/translations/locales/zh_HK.js +15 -6
  97. package/package.json +53 -53
@@ -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);
@@ -102,10 +102,10 @@ const editorWrappers = new WeakMap();
102
102
  // determines if localStorage is available for our use.
103
103
  // see https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API/Using_the_Web_Storage_API
104
104
  export function storageAvailable() {
105
- let storage;
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,16 +216,18 @@ 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
- this._focusRegion = FocusRegionManager.activateRegion(document[FS_ELEMENT], {
222
+ this._focusRegion = FocusRegionManager.activateRegion(
223
+ // @ts-expect-error
224
+ document[FS_ELEMENT], {
223
225
  shouldContainFocus: true
224
226
  });
225
227
  } else {
226
228
  event.target.removeEventListener(FS_CHANGEEVENT, this._onFullscreenChange);
227
229
  this.resizeObserver.unobserve(event.target);
228
- window.visualViewport?.removeEventListener('resize', this._handleFullscreenResize);
230
+ window.visualViewport?.removeEventListener("resize", this._handleFullscreenResize);
229
231
  this._setHeight(this.state.fullscreenState.prevHeight);
230
232
  if (this._focusRegion) {
231
233
  FocusRegionManager.blurRegion(event.target, this._focusRegion.id);
@@ -258,35 +260,35 @@ class RCEWrapper extends React.Component {
258
260
  // what we've got for now.
259
261
  const ifr = this.iframe;
260
262
  if (ifr?.parentElement) {
261
- ifr.parentElement.classList.add('active');
263
+ ifr.parentElement.classList.add("active");
262
264
  }
263
265
  this.handleFocus();
264
266
  };
265
267
  this.handleBlurEditor = event => {
266
268
  const ifr = this.iframe;
267
269
  if (ifr?.parentElement) {
268
- ifr.parentElement.classList.remove('active');
270
+ ifr.parentElement.classList.remove("active");
269
271
  }
270
272
  this.handleBlur(event);
271
273
  };
272
274
  this.handleKey = event => {
273
- if (event.code === 'F9' && event.altKey) {
275
+ if (event.code === "F9" && event.altKey) {
274
276
  event.preventDefault();
275
277
  event.stopPropagation();
276
278
  // @ts-expect-error
277
279
  focusFirstMenuButton(this._elementRef.current);
278
- } else if (event.code === 'F10' && event.altKey) {
280
+ } else if (event.code === "F10" && event.altKey) {
279
281
  event.preventDefault();
280
282
  event.stopPropagation();
281
283
  // @ts-expect-error
282
284
  focusToolbar(this._elementRef.current);
283
- } else if (event.code === 'F8' && event.altKey) {
285
+ } else if (event.code === "F8" && event.altKey) {
284
286
  event.preventDefault();
285
287
  event.stopPropagation();
286
288
  this.openKBShortcutModal();
287
- } else if (event.code === 'Escape') {
289
+ } else if (event.code === "Escape") {
288
290
  bridge.hideTrays();
289
- } else if (['n', 'N', 'd', 'D'].indexOf(event.key) !== -1) {
291
+ } else if (["n", "N", "d", "D"].indexOf(event.key) !== -1) {
290
292
  // Prevent key events from bubbling up on touch screen device
291
293
  event.stopPropagation();
292
294
  }
@@ -315,32 +317,32 @@ class RCEWrapper extends React.Component {
315
317
  // @ts-expect-error
316
318
  textarea.value = this.getCode();
317
319
  textarea.style.height = this.state.height;
318
- textarea.removeAttribute('aria-hidden');
319
- if (document.body.classList.contains('Underline-All-Links__enabled')) {
320
+ textarea.removeAttribute("aria-hidden");
321
+ if (document.body.classList.contains("Underline-All-Links__enabled")) {
320
322
  if (this.iframe?.contentDocument) {
321
- this.iframe.contentDocument.body.classList.add('Underline-All-Links__enabled');
323
+ this.iframe.contentDocument.body.classList.add("Underline-All-Links__enabled");
322
324
  }
323
325
  }
324
- editor.on('wordCountUpdate', this.onWordCountUpdate);
326
+ editor.on("wordCountUpdate", this.onWordCountUpdate);
325
327
  // add an aria-label to the application div that wraps RCE
326
328
  // and change role from "application" to "document" to ensure
327
329
  // the editor gets properly picked up by screen readers
328
330
  const tinyapp = document.querySelector('.tox-tinymce[role="application"]');
329
331
  if (tinyapp) {
330
- tinyapp.setAttribute('aria-label', formatMessage('Rich Content Editor'));
331
- tinyapp.setAttribute('role', 'document');
332
- tinyapp.setAttribute('tabIndex', '-1');
332
+ tinyapp.setAttribute("aria-label", formatMessage("Rich Content Editor"));
333
+ tinyapp.setAttribute("role", "document");
334
+ tinyapp.setAttribute("tabIndex", "-1");
333
335
  }
334
336
 
335
337
  // Probably should do this in tinymce.scss, but we only want it in new rce
336
- textarea.style.resize = 'none';
337
- editor.on('keydown', this.handleKey);
338
- editor.on('FullscreenStateChanged', this._onFullscreenChange);
338
+ textarea.style.resize = "none";
339
+ editor.on("keydown", this.handleKey);
340
+ editor.on("FullscreenStateChanged", this._onFullscreenChange);
339
341
  // This propagates click events on the editor out of the iframe to the parent
340
342
  // document. We need this so that click events get captured properly by instui
341
343
  // focus-trapping components, so they properly ignore trapping focus on click.
342
- editor.on('click', () => window.document.body.click(), true);
343
- editor.on('Cut Change input Undo Redo', debounce(this.handleInputChange, 1000));
344
+ editor.on("click", () => window.document.body.click(), true);
345
+ editor.on("Cut Change input Undo Redo", debounce(this.handleInputChange, 1000));
344
346
  initScreenreaderOnFormat(editor);
345
347
  this.announceContextToolbars(editor);
346
348
  if (this.isAutoSaving) {
@@ -352,12 +354,12 @@ class RCEWrapper extends React.Component {
352
354
 
353
355
  // readonly should have been handled via the init property passed
354
356
  // to <Editor>, but it's not.
355
- editor.mode.set(this.props.readOnly ? 'readonly' : 'design');
357
+ editor.mode.set(this.props.readOnly ? "readonly" : "design");
356
358
 
357
359
  // Not using iframe_aria_text because compatibility issues.
358
360
  // Not using iframe_attrs because library overwriting.
359
361
  if (this.iframe) {
360
- this.iframe.setAttribute('title', formatMessage('Rich Text Area. Press {OSKey}+F8 for Rich Content Editor shortcuts.', {
362
+ this.iframe.setAttribute("title", formatMessage("Rich Text Area. Press {OSKey}+F8 for Rich Content Editor shortcuts.", {
361
363
  OSKey: determineOSDependentKey()
362
364
  }));
363
365
  }
@@ -370,8 +372,8 @@ class RCEWrapper extends React.Component {
370
372
 
371
373
  // cleans up highlight artifacts from findreplace plugin
372
374
  if (this.getRequiredFeatureStatuses().rce_find_replace) {
373
- editor.on('undo redo', _e => {
374
- if (editor?.dom?.doc?.getElementsByClassName?.('mce-match-marker')?.length > 0) {
375
+ editor.on("undo redo", _e => {
376
+ if (editor?.dom?.doc?.getElementsByClassName?.("mce-match-marker")?.length > 0) {
375
377
  editor.plugins?.searchreplace?.done();
376
378
  }
377
379
  });
@@ -390,7 +392,7 @@ class RCEWrapper extends React.Component {
390
392
  // This workaround removes attribute, thusly causing navigation to work correctly again.
391
393
  // For the correct solution, Keying.config should have { selector: '.tox-toolbar__group' }
392
394
  // in https://github.com/tinymce/tinymce/blob/develop/modules/alloy/src/main/ts/ephox/alloy/ui/schema/SplitSlidingToolbarSchema.ts
393
- this._elementRef.current?.querySelectorAll('.tox-toolbar-overlord button[data-alloy-tabstop]').forEach(it => it.removeAttribute('data-alloy-tabstop'));
395
+ this._elementRef.current?.querySelectorAll(".tox-toolbar-overlord button[data-alloy-tabstop]").forEach(it => it.removeAttribute("data-alloy-tabstop"));
394
396
  };
395
397
  /**
396
398
  * Sets up selection saving and restoration logic.
@@ -417,7 +419,7 @@ class RCEWrapper extends React.Component {
417
419
  selectionWasReset = false;
418
420
  }
419
421
  };
420
- editor.on('blur', () => {
422
+ editor.on("blur", () => {
421
423
  editorHasFocus = false;
422
424
  selectionWasReset = false;
423
425
  if (!this.editor) return;
@@ -426,7 +428,7 @@ class RCEWrapper extends React.Component {
426
428
  isForward: this.editor.selection.isForward()
427
429
  };
428
430
  });
429
- editor.on('focus', () => {
431
+ editor.on("focus", () => {
430
432
  // We need to restore the selection when the editor regains focus because sometimes the editor regains
431
433
  // focus without the user setting the selection themselves (such as when they interact with the toolbar)
432
434
  // and if we didn't, we would end up saving the reset selection before a user managed to actually insert
@@ -435,7 +437,7 @@ class RCEWrapper extends React.Component {
435
437
  editorHasFocus = true;
436
438
  selectionWasReset = false;
437
439
  });
438
- editor.on('SelectionChange', () => {
440
+ editor.on("SelectionChange", () => {
439
441
  if (editorHasFocus) {
440
442
  // We don't care if a selection reset occurs when the editor has focus, the user probably intended that
441
443
  // At least they will see the effect
@@ -445,14 +447,14 @@ class RCEWrapper extends React.Component {
445
447
  const selection = this.editor.selection.normalize();
446
448
 
447
449
  // Detect a browser-reset selection (e.g. From invoking the Find command)
448
- if (selection.startContainer?.nodeName === 'BODY' && selection.startContainer === selection.endContainer && selection.startOffset === 0 && selection.endOffset === 0) {
450
+ if (selection.startContainer?.nodeName === "BODY" && selection.startContainer === selection.endContainer && selection.startOffset === 0 && selection.endOffset === 0) {
449
451
  selectionWasReset = true;
450
452
  }
451
453
  });
452
- editor.on('BeforeExecCommand', () => {
454
+ editor.on("BeforeExecCommand", () => {
453
455
  restoreSelectionIfNecessary();
454
456
  });
455
- editor.on('ExecCommand', (/* event */
457
+ editor.on("ExecCommand", (/* event */
456
458
  ) => {
457
459
  if (!this.editor) return;
458
460
  // Commands may have modified the selection, we need to recapture it
@@ -463,13 +465,14 @@ class RCEWrapper extends React.Component {
463
465
  });
464
466
  };
465
467
  this.announcing = 0;
468
+ this._isMounted = false;
466
469
  /* ********** autosave support *************** */
467
470
  this.initAutoSave = editor => {
468
471
  var _this$props$userCache;
469
- this.storage = new EncryptedStorage((_this$props$userCache = this.props.userCacheKey) !== null && _this$props$userCache !== void 0 ? _this$props$userCache : '');
472
+ this.storage = new EncryptedStorage((_this$props$userCache = this.props.userCacheKey) !== null && _this$props$userCache !== void 0 ? _this$props$userCache : "");
470
473
  if (this.storage) {
471
- editor.on('change Undo Redo', this.doAutoSave);
472
- editor.on('blur', this.doAutoSave);
474
+ editor.on("change Undo Redo", this.doAutoSave);
475
+ editor.on("blur", this.doAutoSave);
473
476
  this.cleanupAutoSave();
474
477
  try {
475
478
  const autosaved = this.getAutoSaved(this.autoSaveKey);
@@ -494,7 +497,7 @@ class RCEWrapper extends React.Component {
494
497
  } catch (ex) {
495
498
  // log and ignore
496
499
 
497
- console.error('Failed initializing rce autosave', ex);
500
+ console.error("Failed initializing rce autosave", ex);
498
501
  }
499
502
  }
500
503
  };
@@ -549,7 +552,7 @@ class RCEWrapper extends React.Component {
549
552
  this.cleanupAutoSave(true);
550
553
  this.doAutoSave(e, true);
551
554
  } else {
552
- console.error('Autosave failed:', ex);
555
+ console.error("Autosave failed:", ex);
553
556
  }
554
557
  }
555
558
  }
@@ -557,7 +560,7 @@ class RCEWrapper extends React.Component {
557
560
  /* *********** end autosave support *************** */
558
561
  this.onWordCountUpdate = e => {
559
562
  if (!this.editor) return;
560
- const shouldIgnore = countShouldIgnore(this.editor, 'body', 'words');
563
+ const shouldIgnore = countShouldIgnore(this.editor, "body", "words");
561
564
  const updatedCount = e.wordCount.words - shouldIgnore;
562
565
  this.setState(state => {
563
566
  if (updatedCount !== state.wordCount) {
@@ -570,7 +573,7 @@ class RCEWrapper extends React.Component {
570
573
  // @ts-expect-error
571
574
  this.onNodeChange = e => {
572
575
  // This is basically copied out of the tinymce silver theme code for the status bar
573
- const path = e.parents.filter(p => p.nodeName !== 'BR' && !p.getAttribute('data-mce-bogus') && p.getAttribute('data-mce-type') !== 'bookmark')
576
+ const path = e.parents.filter(p => p.nodeName !== "BR" && !p.getAttribute("data-mce-bogus") && p.getAttribute("data-mce-type") !== "bookmark")
574
577
  // @ts-expect-error
575
578
  .map(p => p.nodeName.toLowerCase()).reverse();
576
579
  this.setState({
@@ -581,7 +584,7 @@ class RCEWrapper extends React.Component {
581
584
  this.props.onContentChange?.(content);
582
585
  // check accessibility when clearing the editor,
583
586
  // all other times should be checked by handleInputChange
584
- if (content === '') {
587
+ if (content === "") {
585
588
  this.checkAccessibility();
586
589
  }
587
590
  };
@@ -603,14 +606,14 @@ class RCEWrapper extends React.Component {
603
606
  height: newHeight
604
607
  });
605
608
  // play nice and send the same event that the silver theme would send
606
- editor.fire('ResizeEditor', {
609
+ editor.fire("ResizeEditor", {
607
610
  deltaY: coordinates.deltaY
608
611
  });
609
612
  }
610
613
  };
611
614
  this.onA11yChecker = triggerElementId => {
612
615
  const editor = this.mceInstance();
613
- editor.execCommand('openAccessibilityChecker', false, {
616
+ editor.execCommand("openAccessibilityChecker", false, {
614
617
  mountNode: instuiPopupMountNodeFn,
615
618
  triggerElementId,
616
619
  onFixError: errors => {
@@ -624,7 +627,7 @@ class RCEWrapper extends React.Component {
624
627
  };
625
628
  this.checkAccessibility = () => {
626
629
  const editor = this.mceInstance();
627
- editor.execCommand('checkAccessibility', false, {
630
+ editor.execCommand("checkAccessibility", false, {
628
631
  // @ts-expect-error
629
632
  done: errors => {
630
633
  this.setState({
@@ -664,7 +667,7 @@ class RCEWrapper extends React.Component {
664
667
  }
665
668
  };
666
669
  this.handleAIClick = () => {
667
- import('./plugins/shared/ai_tools').then(module => {
670
+ import("./plugins/shared/ai_tools").then(module => {
668
671
  // @ts-expect-error
669
672
  this.AIToolsTray = module.AIToolsTray;
670
673
  this.setState({
@@ -672,7 +675,7 @@ class RCEWrapper extends React.Component {
672
675
  AITToolsFocusReturn: document.activeElement
673
676
  });
674
677
  }).catch(ex => {
675
- console.error('Failed loading the AIToolsTray', ex);
678
+ console.error("Failed loading the AIToolsTray", ex);
676
679
  });
677
680
  };
678
681
  this.closeAITools = () => {
@@ -714,10 +717,10 @@ class RCEWrapper extends React.Component {
714
717
  this.getCurrentContentForAI = () => {
715
718
  const selected = this.mceInstance().selection.getContent();
716
719
  return selected ? {
717
- type: 'selection',
720
+ type: "selection",
718
721
  content: selected
719
722
  } : {
720
- type: 'full',
723
+ type: "full",
721
724
  content: this.mceInstance().getContent()
722
725
  };
723
726
  };
@@ -732,7 +735,7 @@ class RCEWrapper extends React.Component {
732
735
  alert.id = alertIdValue++;
733
736
  this.setState(state => {
734
737
  let messages = state.messages.concat(alert);
735
- messages = _.uniqBy(messages, 'text'); // Don't show the same message twice
738
+ messages = _.uniqBy(messages, "text"); // Don't show the same message twice
736
739
  return {
737
740
  messages
738
741
  };
@@ -751,7 +754,7 @@ class RCEWrapper extends React.Component {
751
754
  */
752
755
  this.resetAlertId = () => {
753
756
  if (this.state.messages.length > 0) {
754
- throw new Error('There are messages currently, you cannot reset when they are non-zero');
757
+ throw new Error("There are messages currently, you cannot reset when they are non-zero");
755
758
  }
756
759
  alertIdValue = 0;
757
760
  };
@@ -793,7 +796,7 @@ class RCEWrapper extends React.Component {
793
796
  if (!Number.isNaN(_ht)) {
794
797
  _ht = `${_ht}px`;
795
798
  }
796
- const currentRCECount = document.querySelectorAll('.rce-wrapper').length;
799
+ const currentRCECount = document.querySelectorAll(".rce-wrapper").length;
797
800
  const maxInitRenderedRCEs = Number.isNaN(props.maxInitRenderedRCEs) ? RCEWrapper.defaultProps.maxInitRenderedRCEs : props.maxInitRenderedRCEs;
798
801
  this.state = {
799
802
  path: [],
@@ -804,9 +807,9 @@ class RCEWrapper extends React.Component {
804
807
  messages: [],
805
808
  announcement: null,
806
809
  confirmAutoSave: false,
807
- autoSavedContent: '',
810
+ autoSavedContent: "",
808
811
  // @ts-expect-error
809
- id: this.props.id || this.props.textareaId || `${uid('rce', 2)}`,
812
+ id: this.props.id || this.props.textareaId || `${uid("rce", 2)}`,
810
813
  // @ts-expect-error
811
814
  height: _ht,
812
815
  fullscreenState: {
@@ -814,7 +817,7 @@ class RCEWrapper extends React.Component {
814
817
  prevHeight: _ht
815
818
  },
816
819
  a11yErrorsCount: 0,
817
- shouldShowEditor: typeof IntersectionObserver === 'undefined' || maxInitRenderedRCEs <= 0 || currentRCECount < maxInitRenderedRCEs,
820
+ shouldShowEditor: typeof IntersectionObserver === "undefined" || maxInitRenderedRCEs <= 0 || currentRCECount < maxInitRenderedRCEs,
818
821
  AIToolsOpen: false
819
822
  };
820
823
  this._statusBarId = `${this.state.id}_statusbar`;
@@ -849,11 +852,11 @@ class RCEWrapper extends React.Component {
849
852
  // configure tinymce to say where that div is mounted, do this
850
853
  // is a bit of a hack to tag the div that is this RCE's
851
854
  _tagTinymceAuxDiv() {
852
- const tinyauxlist = document.querySelectorAll('.tox-tinymce-aux');
855
+ const tinyauxlist = document.querySelectorAll(".tox-tinymce-aux");
853
856
  if (tinyauxlist.length) {
854
857
  const myaux = tinyauxlist[tinyauxlist.length - 1];
855
858
  if (myaux.id) {
856
- console.error('Unexpected ID on my tox-tinymce-aux element');
859
+ console.error("Unexpected ID on my tox-tinymce-aux element");
857
860
  }
858
861
  myaux.id = `tinyaux-${this.id}`;
859
862
  }
@@ -867,6 +870,7 @@ class RCEWrapper extends React.Component {
867
870
  explicit_latex_typesetting = false,
868
871
  rce_transform_loaded_content = false,
869
872
  rce_find_replace = false,
873
+ rce_studio_embed_improvements = false,
870
874
  file_verifiers_for_quiz_links = false,
871
875
  consolidated_media_player = false
872
876
  } = this.props.features;
@@ -874,6 +878,7 @@ class RCEWrapper extends React.Component {
874
878
  new_math_equation_handling,
875
879
  explicit_latex_typesetting,
876
880
  rce_transform_loaded_content,
881
+ rce_studio_embed_improvements,
877
882
  file_verifiers_for_quiz_links,
878
883
  rce_find_replace,
879
884
  consolidated_media_player
@@ -911,7 +916,7 @@ class RCEWrapper extends React.Component {
911
916
  let status = true;
912
917
  // Check for remaining placeholders
913
918
  if (this.mceInstance().dom.doc.querySelector(`[data-placeholder-for]`)) {
914
- status = promptFunc(formatMessage('Content is still being uploaded, if you continue it will not be embedded properly.'));
919
+ status = promptFunc(formatMessage("Content is still being uploaded, if you continue it will not be embedded properly."));
915
920
  }
916
921
  return status;
917
922
  }
@@ -971,9 +976,9 @@ class RCEWrapper extends React.Component {
971
976
  // @ts-expect-error
972
977
  ifr.contentDocument.body.clientHeight -
973
978
  // @ts-expect-error
974
- parseInt(editor_body_style['padding-top'], 10) -
979
+ parseInt(editor_body_style["padding-top"], 10) -
975
980
  // @ts-expect-error
976
- parseInt(editor_body_style['padding-bottom'], 10);
981
+ parseInt(editor_body_style["padding-bottom"], 10);
977
982
  const para_margin_ht = 24;
978
983
  const reserve_ht = Math.ceil(height + para_margin_ht);
979
984
  if (reserve_ht > editor_ht) {
@@ -985,7 +990,7 @@ class RCEWrapper extends React.Component {
985
990
  }
986
991
  }
987
992
  checkImageLoadError(element) {
988
- if (!element || element.tagName !== 'IMG') {
993
+ if (!element || element.tagName !== "IMG") {
989
994
  return;
990
995
  }
991
996
  // @ts-expect-error
@@ -1000,9 +1005,9 @@ class RCEWrapper extends React.Component {
1000
1005
  // @ts-expect-error
1001
1006
  if (element.naturalWidth === 0) {
1002
1007
  // @ts-expect-error
1003
- element.style.border = '1px solid #000';
1008
+ element.style.border = "1px solid #000";
1004
1009
  // @ts-expect-error
1005
- element.style.padding = '2px';
1010
+ element.style.padding = "2px";
1006
1011
  }
1007
1012
  }, 0);
1008
1013
  }
@@ -1012,7 +1017,7 @@ class RCEWrapper extends React.Component {
1012
1017
  this.contentInserted(element);
1013
1018
  }
1014
1019
  replaceCode(code) {
1015
- if (code !== '' && window.confirm(formatMessage('Content in the editor will be changed. Press Cancel to keep the original content.'))) {
1020
+ if (code !== "" && window.confirm(formatMessage("Content in the editor will be changed. Press Cancel to keep the original content."))) {
1016
1021
  this.mceInstance().setContent(code);
1017
1022
  }
1018
1023
  }
@@ -1029,12 +1034,12 @@ class RCEWrapper extends React.Component {
1029
1034
  // that there's some embedded content helper
1030
1035
  // From what I've read, "title" is more reliable than "aria-label" for
1031
1036
  // elements like iframes and embeds.
1032
- const temp = document.createElement('div');
1037
+ const temp = document.createElement("div");
1033
1038
  temp.innerHTML = code;
1034
1039
  const code_elem = temp.firstElementChild;
1035
1040
  if (code_elem) {
1036
- if (!code_elem.hasAttribute('title') && !code_elem.hasAttribute('aria-label')) {
1037
- code_elem.setAttribute('title', formatMessage('embedded content'));
1041
+ if (!code_elem.hasAttribute("title") && !code_elem.hasAttribute("aria-label")) {
1042
+ code_elem.setAttribute("title", formatMessage("embedded content"));
1038
1043
  }
1039
1044
  code = code_elem.outerHTML;
1040
1045
  }
@@ -1044,7 +1049,7 @@ class RCEWrapper extends React.Component {
1044
1049
  // and it's often inserted into a <p> on top of that. Find the
1045
1050
  // iframe and use it to flash the indicator.
1046
1051
  const element = contentInsertion.insertContent(editor, code);
1047
- const ifr = element && element.querySelector && element.querySelector('iframe');
1052
+ const ifr = element && element.querySelector && element.querySelector("iframe");
1048
1053
  if (ifr) {
1049
1054
  this.contentInserted(ifr);
1050
1055
  } else {
@@ -1056,7 +1061,7 @@ class RCEWrapper extends React.Component {
1056
1061
  const element = contentInsertion.insertImage(editor, image, this.getCanvasUrl());
1057
1062
 
1058
1063
  // Removes TinyMCE's caret &nbsp; text if exists.
1059
- if (element?.nextSibling?.data?.startsWith('\xA0' /* nbsp */)) {
1064
+ if (element?.nextSibling?.data?.startsWith("\xA0" /* nbsp */)) {
1060
1065
  element.nextSibling.splitText(1);
1061
1066
  element.nextSibling.remove();
1062
1067
  }
@@ -1137,8 +1142,8 @@ class RCEWrapper extends React.Component {
1137
1142
  onTinyMCEInstance(command, ...args) {
1138
1143
  const editor = this.mceInstance();
1139
1144
  if (editor) {
1140
- if (command === 'mceRemoveEditor') {
1141
- editor.execCommand('mceNewDocument');
1145
+ if (command === "mceRemoveEditor") {
1146
+ editor.execCommand("mceNewDocument");
1142
1147
  } // makes sure content can't persist past removal
1143
1148
  editor.execCommand(command, false, ...args);
1144
1149
  }
@@ -1158,17 +1163,17 @@ class RCEWrapper extends React.Component {
1158
1163
  return null;
1159
1164
  }
1160
1165
  textareaValue() {
1161
- return this.getTextarea()?.value || '';
1166
+ return this.getTextarea()?.value || "";
1162
1167
  }
1163
1168
  get id() {
1164
1169
  return this.state.id;
1165
1170
  }
1166
1171
  getHtmlEditorStorage() {
1167
- const cookieValue = getCookie('rce.htmleditor');
1172
+ const cookieValue = getCookie("rce.htmleditor");
1168
1173
  if (cookieValue) {
1169
1174
  document.cookie = `rce.htmleditor=${cookieValue};path=/;max-age=0`;
1170
1175
  }
1171
- const value = cookieValue || this.storage?.getItem?.('rce.htmleditor')?.content;
1176
+ const value = cookieValue || this.storage?.getItem?.("rce.htmleditor")?.content;
1172
1177
  return value === RAW_HTML_EDITOR_VIEW || value === PRETTY_HTML_EDITOR_VIEW ? value : PRETTY_HTML_EDITOR_VIEW;
1173
1178
  }
1174
1179
  _isFullscreen() {
@@ -1185,7 +1190,7 @@ class RCEWrapper extends React.Component {
1185
1190
  this._elementRef.current?.appendChild(tinymenuhost);
1186
1191
  }
1187
1192
  this._elementRef.current?.addEventListener(FS_CHANGEEVENT, this._onFullscreenChange);
1188
- if (typeof this._elementRef.current?.offsetHeight === 'number') {
1193
+ if (typeof this._elementRef.current?.offsetHeight === "number") {
1189
1194
  this.setState({
1190
1195
  fullscreenState: {
1191
1196
  prevHeight: this._elementRef.current.offsetHeight - this._getStatusBarHeight()
@@ -1217,7 +1222,7 @@ class RCEWrapper extends React.Component {
1217
1222
  const container = ed.getContainer();
1218
1223
  if (container) {
1219
1224
  container.style.height = cssHeight;
1220
- ed.fire('ResizeEditor');
1225
+ ed.fire("ResizeEditor");
1221
1226
  }
1222
1227
  const textarea = this.getTextarea();
1223
1228
  if (textarea) {
@@ -1228,10 +1233,10 @@ class RCEWrapper extends React.Component {
1228
1233
  });
1229
1234
  }
1230
1235
  focus() {
1231
- this.onTinyMCEInstance('mceFocus');
1236
+ this.onTinyMCEInstance("mceFocus");
1232
1237
  // tinymce doesn't always call the focus handler.
1233
1238
  // @ts-expect-error
1234
- this.handleFocusEditor(new Event('focus', {
1239
+ this.handleFocusEditor(new Event("focus", {
1235
1240
  target: this.mceInstance()
1236
1241
  }));
1237
1242
  }
@@ -1269,7 +1274,7 @@ class RCEWrapper extends React.Component {
1269
1274
  */
1270
1275
  get _mceSerializedInitialHtml() {
1271
1276
  if (!this._mceSerializedInitialHtmlCached) {
1272
- const el = window.document.createElement('div');
1277
+ const el = window.document.createElement("div");
1273
1278
  // @ts-expect-error
1274
1279
  el.innerHTML = this.initialContent;
1275
1280
  const serializer = this.mceInstance().serializer;
@@ -1323,20 +1328,22 @@ class RCEWrapper extends React.Component {
1323
1328
  // focus is still somewhere w/in me
1324
1329
  return;
1325
1330
  }
1326
- const activeClass = document.activeElement && document.activeElement.getAttribute('class');
1331
+ const activeClass = document.activeElement?.getAttribute("class");
1327
1332
  if (
1328
1333
  // @ts-expect-error
1329
- (event.focusedEditor === undefined || event.target.id === event.focusedEditor?.id) && activeClass?.includes('tox-')) {
1334
+ (event.focusedEditor === undefined ||
1335
+ // @ts-expect-error
1336
+ event.target.id === event.focusedEditor?.id) && activeClass?.includes("tox-")) {
1330
1337
  // if a toolbar button has focus, then the user clicks on the "more" button
1331
1338
  // focus jumps to the body, then eventually to the popped up toolbar. This
1332
1339
  // catches that case.
1333
1340
  return;
1334
1341
  }
1335
- if (event?.relatedTarget?.getAttribute('class')?.includes('tox-')) {
1342
+ if (event?.relatedTarget?.getAttribute("class")?.includes("tox-")) {
1336
1343
  // a tinymce popup has focus
1337
1344
  return;
1338
1345
  }
1339
- const popups = document.querySelectorAll('[data-mce-component]');
1346
+ const popups = document.querySelectorAll("[data-mce-component]");
1340
1347
  for (const popup of popups) {
1341
1348
  if (popup.contains(document.activeElement)) {
1342
1349
  // one of our popups has focus
@@ -1354,21 +1361,22 @@ class RCEWrapper extends React.Component {
1354
1361
  call(methodName, ...args) {
1355
1362
  // since exists? has a ? and cant be a regular function just return true
1356
1363
  // rather than calling as a fn on the editor
1357
- if (methodName === 'exists?') {
1364
+ if (methodName === "exists?") {
1358
1365
  return true;
1359
1366
  }
1360
1367
  // @ts-expect-error
1361
1368
  return this[methodName](...args);
1362
1369
  }
1363
1370
  announceContextToolbars(editor) {
1364
- editor.on('NodeChange', () => {
1371
+ editor.on("NodeChange", () => {
1372
+ if (!this._isMounted) return;
1365
1373
  const node = editor.selection.getNode();
1366
1374
  // @ts-expect-error
1367
1375
  if (isImageEmbed(node, editor)) {
1368
1376
  if (this.announcing !== 1) {
1369
1377
  this.setState({
1370
- announcement: formatMessage('type Control F9 to access image options. {text}', {
1371
- text: node.getAttribute('alt')
1378
+ announcement: formatMessage("type Control F9 to access image options. {text}", {
1379
+ text: node.getAttribute("alt")
1372
1380
  })
1373
1381
  });
1374
1382
  this.announcing = 1;
@@ -1376,7 +1384,7 @@ class RCEWrapper extends React.Component {
1376
1384
  } else if (isFileLink(node, editor)) {
1377
1385
  if (this.announcing !== 2) {
1378
1386
  this.setState({
1379
- announcement: formatMessage('type Control F9 to access link options. {text}', {
1387
+ announcement: formatMessage("type Control F9 to access link options. {text}", {
1380
1388
  text: node.textContent
1381
1389
  })
1382
1390
  });
@@ -1385,7 +1393,7 @@ class RCEWrapper extends React.Component {
1385
1393
  } else if (isElementWithinTable(node)) {
1386
1394
  if (this.announcing !== 3) {
1387
1395
  this.setState({
1388
- announcement: formatMessage('type Control F9 to access table options. {text}', {
1396
+ announcement: formatMessage("type Control F9 to access table options. {text}", {
1389
1397
  text: node.textContent
1390
1398
  })
1391
1399
  });
@@ -1398,17 +1406,17 @@ class RCEWrapper extends React.Component {
1398
1406
  this.announcing = 0;
1399
1407
  }
1400
1408
  });
1401
- editor.on('ResizeEditor', ({
1409
+ editor.on("ResizeEditor", ({
1402
1410
  deltaY
1403
1411
  }) => {
1404
- if (!deltaY) return;
1412
+ if (!this._isMounted || !deltaY) return;
1405
1413
  if (deltaY < 0) {
1406
1414
  this.setState({
1407
- announcement: formatMessage('The height of Rich Content Area is decreased.')
1415
+ announcement: formatMessage("The height of Rich Content Area is decreased.")
1408
1416
  });
1409
1417
  } else {
1410
1418
  this.setState({
1411
- announcement: formatMessage('The height of Rich Content Area is increased.')
1419
+ announcement: formatMessage("The height of Rich Content Area is increased.")
1412
1420
  });
1413
1421
  }
1414
1422
  });
@@ -1434,20 +1442,21 @@ class RCEWrapper extends React.Component {
1434
1442
  // This doesn't apply if the editor is off-screen or has visibility:hidden;
1435
1443
  // only if it isn't rendered or has display:none;
1436
1444
  const editorVisible = this.editor.getContainer().offsetParent;
1437
- return this.props.autosave?.enabled && editorVisible && document.querySelectorAll('.rce-wrapper').length === 1 && storageAvailable();
1445
+ return this.props.autosave?.enabled && editorVisible && document.querySelectorAll(".rce-wrapper").length === 1 && storageAvailable();
1438
1446
  }
1439
1447
  get autoSaveKey() {
1440
- const userId = this._effectiveContainingContext?.userId || '-';
1448
+ const userId = this._effectiveContainingContext?.userId || "-";
1441
1449
  return `rceautosave:${userId}${window.location.href}:${this.props.textareaId}`;
1442
1450
  }
1443
1451
  componentWillUnmount() {
1452
+ this._isMounted = false;
1444
1453
  if (this.state.shouldShowEditor) {
1445
1454
  window.clearTimeout(this.blurTimer);
1446
1455
  if (!this._destroyCalled) {
1447
1456
  this.destroy();
1448
1457
  }
1449
1458
  if (this._elementRef.current) {
1450
- this._elementRef.current.removeEventListener('keydown', this.handleKey, true);
1459
+ this._elementRef.current.removeEventListener("keydown", this.handleKey, true);
1451
1460
  }
1452
1461
  this.mutationObserver?.disconnect();
1453
1462
  this.intersectionObserver?.disconnect();
@@ -1459,27 +1468,27 @@ class RCEWrapper extends React.Component {
1459
1468
 
1460
1469
  // @ts-expect-error
1461
1470
  const setupCallback = options.setup;
1462
- const canvasPlugins = rcsExists ? ['instructure_image', 'instructure_documents', 'instructure_equation'] : [];
1471
+ const canvasPlugins = rcsExists ? ["instructure_image", "instructure_documents", "instructure_equation"] : [];
1463
1472
  if (rcsExists && !this.props.instRecordDisabled) {
1464
- canvasPlugins.splice(2, 0, 'instructure_record');
1473
+ canvasPlugins.splice(2, 0, "instructure_record");
1465
1474
  }
1466
- const pastePlugins = rcsExists ? ['instructure_paste', 'paste'] : ['paste'];
1467
- if (rcsExists && this.props.use_rce_icon_maker && this.props.trayProps?.contextType === 'course') {
1468
- canvasPlugins.push('instructure_icon_maker');
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");
1469
1478
  }
1470
1479
  if (document[FS_ENABLED]) {
1471
- canvasPlugins.push('instructure_fullscreen');
1480
+ canvasPlugins.push("instructure_fullscreen");
1472
1481
  }
1473
1482
  if (this.getRequiredFeatureStatuses().rce_find_replace) {
1474
- canvasPlugins.push('searchreplace');
1475
- canvasPlugins.push('instructure_search_and_replace');
1483
+ canvasPlugins.push("searchreplace");
1484
+ canvasPlugins.push("instructure_search_and_replace");
1476
1485
  }
1477
- const possibleNewMenubarItems = this.props.editorOptions.menu ? Object.keys(this.props.editorOptions.menu).join(' ') : undefined;
1486
+ const possibleNewMenubarItems = this.props.editorOptions.menu ? Object.keys(this.props.editorOptions.menu).join(" ") : undefined;
1478
1487
  const wrappedOpts = {
1479
1488
  ...defaultTinymceConfig,
1480
1489
  ...options,
1481
1490
  readonly: this.props.readOnly,
1482
- theme: 'silver',
1491
+ theme: "silver",
1483
1492
  // some older code specified 'modern', which doesn't exist any more
1484
1493
 
1485
1494
  // @ts-expect-error
@@ -1488,7 +1497,7 @@ class RCEWrapper extends React.Component {
1488
1497
  document_base_url: this.props.canvasOrigin,
1489
1498
  block_formats:
1490
1499
  // @ts-expect-error
1491
- options.block_formats || [`${formatMessage('Heading 2')}=h2`, `${formatMessage('Heading 3')}=h3`, `${formatMessage('Heading 4')}=h4`, `${formatMessage('Preformatted')}=pre`, `${formatMessage('Paragraph')}=p`].join('; '),
1500
+ options.block_formats || [`${formatMessage("Heading 2")}=h2`, `${formatMessage("Heading 3")}=h3`, `${formatMessage("Heading 4")}=h4`, `${formatMessage("Preformatted")}=pre`, `${formatMessage("Paragraph")}=p`].join("; "),
1492
1501
  setup: editor => {
1493
1502
  addKebabIcon(editor);
1494
1503
  editorWrappers.set(editor, this);
@@ -1501,7 +1510,7 @@ class RCEWrapper extends React.Component {
1501
1510
  // @ts-expect-error
1502
1511
  bridge.userLocale = userLocale;
1503
1512
  bridge.canvasOrigin = this.props.canvasOrigin;
1504
- if (typeof setupCallback === 'function') {
1513
+ if (typeof setupCallback === "function") {
1505
1514
  setupCallback(editor);
1506
1515
  }
1507
1516
  },
@@ -1512,7 +1521,7 @@ class RCEWrapper extends React.Component {
1512
1521
  // @ts-expect-error
1513
1522
  content_css: options.content_css || [],
1514
1523
  // @ts-expect-error
1515
- content_style: contentCSS + (options.content_style || ''),
1524
+ content_style: contentCSS + (options.content_style || ""),
1516
1525
  menubar: mergeMenuItems(getMenubarForVariant(this.variant), possibleNewMenubarItems),
1517
1526
  // default menu options listed at https://www.tiny.cloud/docs/configure/editor-appearance/#menu
1518
1527
  // tinymce's default edit and table menus are fine
@@ -1527,10 +1536,10 @@ class RCEWrapper extends React.Component {
1527
1536
  getToolbarForVariant(this.variant, this.ltiToolFavorites),
1528
1537
  // @ts-expect-error
1529
1538
  options.toolbar),
1530
- contextmenu: '',
1539
+ contextmenu: "",
1531
1540
  // show the browser's native context menu
1532
1541
 
1533
- toolbar_mode: 'sliding',
1542
+ toolbar_mode: "sliding",
1534
1543
  toolbar_sticky: true,
1535
1544
  // In regards to the ability to disable plugins:
1536
1545
  // we only have to explicitly manage the removal of plugins
@@ -1539,16 +1548,16 @@ class RCEWrapper extends React.Component {
1539
1548
  // handles all of that complexity. It that ever changes in the
1540
1549
  // future in an upgraded version, we will have to update the
1541
1550
  // logic in those other places as well.
1542
- 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_studio_media_options', 'instructure_rce_external_tools', ...pastePlugins, ...canvasPlugins],
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],
1543
1552
  // filter out the plugins designated for removal
1544
1553
  // @ts-expect-error
1545
- sanitizePlugins(options.plugins)?.filter(p => p.length > 0 && p[0] !== '-'), this.pluginsToExclude),
1554
+ sanitizePlugins(options.plugins)?.filter(p => p.length > 0 && p[0] !== "-"), this.pluginsToExclude),
1546
1555
  textpattern_patterns: [{
1547
- start: '* ',
1548
- cmd: 'InsertUnorderedList'
1556
+ start: "* ",
1557
+ cmd: "InsertUnorderedList"
1549
1558
  }, {
1550
- start: '- ',
1551
- cmd: 'InsertUnorderedList'
1559
+ start: "- ",
1560
+ cmd: "InsertUnorderedList"
1552
1561
  }]
1553
1562
  };
1554
1563
  if (this.props.trayProps) {
@@ -1570,7 +1579,7 @@ class RCEWrapper extends React.Component {
1570
1579
  }
1571
1580
  unhandleTextareaChange() {
1572
1581
  if (this._textareaEl) {
1573
- this._textareaEl.removeEventListener('input', this.handleTextareaChange);
1582
+ this._textareaEl.removeEventListener("input", this.handleTextareaChange);
1574
1583
  }
1575
1584
  }
1576
1585
  registerTextareaChange() {
@@ -1578,7 +1587,7 @@ class RCEWrapper extends React.Component {
1578
1587
  if (this._textareaEl !== el) {
1579
1588
  this.unhandleTextareaChange();
1580
1589
  if (el) {
1581
- el.addEventListener('input', this.handleTextareaChange);
1590
+ el.addEventListener("input", this.handleTextareaChange);
1582
1591
  if (this.props.textareaClassName) {
1583
1592
  // split the string on whitespace because classList doesn't let you add multiple
1584
1593
  // space seperated classes at a time but does let you add an array of them
@@ -1589,6 +1598,7 @@ class RCEWrapper extends React.Component {
1589
1598
  }
1590
1599
  }
1591
1600
  componentDidMount() {
1601
+ this._isMounted = true;
1592
1602
  if (this.state.shouldShowEditor) {
1593
1603
  this.editorReallyDidMount();
1594
1604
  } else {
@@ -1603,7 +1613,7 @@ class RCEWrapper extends React.Component {
1603
1613
  // initialize the RCE when it gets close to entering the viewport
1604
1614
  {
1605
1615
  root: null,
1606
- rootMargin: '200px 0px',
1616
+ rootMargin: "200px 0px",
1607
1617
  threshold: 0.0
1608
1618
  });
1609
1619
  // @ts-expect-error
@@ -1622,7 +1632,7 @@ class RCEWrapper extends React.Component {
1622
1632
  this.focusCurrentView();
1623
1633
  }
1624
1634
  if (prevProps.readOnly !== this.props.readOnly) {
1625
- this.mceInstance().mode.set(this.props.readOnly ? 'readonly' : 'design');
1635
+ this.mceInstance().mode.set(this.props.readOnly ? "readonly" : "design");
1626
1636
  }
1627
1637
  }
1628
1638
  }
@@ -1636,7 +1646,7 @@ class RCEWrapper extends React.Component {
1636
1646
  this._tagTinymceAuxDiv();
1637
1647
  this.registerTextareaChange();
1638
1648
  // @ts-expect-error
1639
- this._elementRef.current.addEventListener('keydown', this.handleKey, true);
1649
+ this._elementRef.current.addEventListener("keydown", this.handleKey, true);
1640
1650
  // give the textarea its initial size
1641
1651
  this.onResize(null, {
1642
1652
  deltaY: 0
@@ -1644,7 +1654,7 @@ class RCEWrapper extends React.Component {
1644
1654
  // Preload the LTI Tools modal
1645
1655
  // This helps with loading the favorited external tools
1646
1656
  if (this.ltiToolFavorites.length > 0) {
1647
- import('./plugins/instructure_rce_external_tools/components/ExternalToolSelectionDialog/ExternalToolSelectionDialog');
1657
+ import("./plugins/instructure_rce_external_tools/components/ExternalToolSelectionDialog/ExternalToolSelectionDialog");
1648
1658
  }
1649
1659
  bridge.renderEditor(this);
1650
1660
  }
@@ -1666,9 +1676,9 @@ class RCEWrapper extends React.Component {
1666
1676
  fallback: /*#__PURE__*/React.createElement("div", {
1667
1677
  style: {
1668
1678
  height: this.state.height,
1669
- display: 'flex',
1670
- justifyContent: 'center',
1671
- alignItems: 'center'
1679
+ display: "flex",
1680
+ justifyContent: "center",
1681
+ alignItems: "center"
1672
1682
  }
1673
1683
  }, /*#__PURE__*/React.createElement(Spinner, {
1674
1684
  renderTitle: renderLoading,
@@ -1704,7 +1714,7 @@ class RCEWrapper extends React.Component {
1704
1714
  ref: this._editorPlaceholderRef,
1705
1715
  style: {
1706
1716
  height: `${this.props.editorOptions.height}px`,
1707
- border: '1px solid grey'
1717
+ border: "1px solid grey"
1708
1718
  }
1709
1719
  });
1710
1720
  }
@@ -1730,8 +1740,8 @@ class RCEWrapper extends React.Component {
1730
1740
  // @ts-expect-error
1731
1741
  ,
1732
1742
  ref: this._elementRef,
1733
- style: this.variant === 'full' ? {
1734
- marginBottom: '.5rem'
1743
+ style: this.variant === "full" ? {
1744
+ marginBottom: ".5rem"
1735
1745
  } : undefined,
1736
1746
  onFocus: this.handleFocusRCE,
1737
1747
  onBlur: this.handleBlurRCE
@@ -1739,7 +1749,7 @@ class RCEWrapper extends React.Component {
1739
1749
  id: `show-on-focus-btn-${this.id}`,
1740
1750
  onClick: this.openKBShortcutModal,
1741
1751
  margin: "xx-small",
1742
- screenReaderLabel: formatMessage('View keyboard shortcuts')
1752
+ screenReaderLabel: formatMessage("View keyboard shortcuts")
1743
1753
  // @ts-expect-error
1744
1754
  ,
1745
1755
  ref: el => this._showOnFocusButton = el
@@ -1751,7 +1761,7 @@ class RCEWrapper extends React.Component {
1751
1761
  afterDismiss: this.removeAlert
1752
1762
  }), this.state.editorView === PRETTY_HTML_EDITOR_VIEW && this.renderHtmlEditor(), /*#__PURE__*/React.createElement("div", {
1753
1763
  style: {
1754
- display: this.state.editorView === PRETTY_HTML_EDITOR_VIEW ? 'none' : 'block'
1764
+ display: this.state.editorView === PRETTY_HTML_EDITOR_VIEW ? "none" : "block"
1755
1765
  }
1756
1766
  }, /*#__PURE__*/React.createElement(Editor, {
1757
1767
  id: mceProps.textareaId,
@@ -1841,13 +1851,10 @@ class RCEWrapper extends React.Component {
1841
1851
  open: this.state.confirmAutoSave,
1842
1852
  onNo: () => this.restoreAutoSave(false),
1843
1853
  onYes: () => this.restoreAutoSave(true)
1844
- })) : null, this.state.announcement &&
1845
- /*#__PURE__*/
1846
- // @ts-expect-error
1847
- React.createElement(Alert, {
1854
+ })) : null, /*#__PURE__*/React.createElement(Alert, {
1848
1855
  screenReaderOnly: true,
1849
1856
  liveRegion: this.props.liveRegion
1850
- }, this.state.announcement));
1857
+ }, this.state.announcement || null));
1851
1858
  }));
1852
1859
  }
1853
1860
  }
@@ -1891,8 +1898,8 @@ RCEWrapper.defaultProps = {
1891
1898
  maxInitRenderedRCEs: -1,
1892
1899
  features: {},
1893
1900
  timezone: Intl?.DateTimeFormat()?.resolvedOptions()?.timeZone,
1894
- canvasOrigin: '',
1895
- variant: 'full'
1901
+ canvasOrigin: "",
1902
+ variant: "full"
1896
1903
  };
1897
1904
  RCEWrapper.skinCssInjected = false;
1898
1905
  export default RCEWrapper;