@instructure/canvas-rce 7.0.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 (152) hide show
  1. package/CHANGELOG.md +60 -1
  2. package/__tests__/common/indicate.test.js +5 -6
  3. package/es/bridge/Bridge.js +2 -4
  4. package/es/canvasFileBrowser/FileBrowser.js +2 -4
  5. package/es/defaultTinymceConfig.d.ts +1 -1
  6. package/es/defaultTinymceConfig.js +149 -114
  7. package/es/enhance-user-content/doc_previews.js +1 -14
  8. package/es/enhance-user-content/enhance_user_content.js +7 -1
  9. package/es/enhance-user-content/instructure_helper.js +4 -0
  10. package/es/enhance-user-content/youtube_overlay.d.ts +1 -0
  11. package/es/enhance-user-content/youtube_overlay.js +87 -0
  12. package/es/format-message.d.js +1 -0
  13. package/es/format-message.js +5 -0
  14. package/es/index.d.ts +2 -1
  15. package/es/index.js +2 -1
  16. package/es/rce/AlertMessageArea.d.ts +2 -2
  17. package/es/rce/AlertMessageArea.js +4 -6
  18. package/es/rce/RCE.d.ts +0 -1
  19. package/es/rce/RCE.js +5 -10
  20. package/es/rce/RCEGlobals.d.ts +2 -0
  21. package/es/rce/RCEGlobals.js +1 -0
  22. package/es/rce/RCEVariants.d.ts +8 -3
  23. package/es/rce/RCEVariants.js +31 -5
  24. package/es/rce/RCEWrapper.d.ts +16 -14
  25. package/es/rce/RCEWrapper.js +260 -244
  26. package/es/rce/RCEWrapperProps.d.ts +1 -1
  27. package/es/rce/ShowOnFocusButton/index.js +4 -2
  28. package/es/rce/StatusBar.js +61 -15
  29. package/es/rce/alertHandler.js +6 -7
  30. package/es/rce/plugins/instructure-ui-icons/plugin.js +2 -2
  31. package/es/rce/plugins/instructure_equation/EquationEditorModal/index.js +6 -10
  32. package/es/rce/plugins/instructure_icon_maker/components/CreateIconMakerForm/ImageSection/Course.d.ts +5 -15
  33. package/es/rce/plugins/instructure_icon_maker/components/CreateIconMakerForm/ImageSection/Course.js +4 -10
  34. package/es/rce/plugins/instructure_image/ImageEmbedOptions.d.ts +7 -0
  35. package/es/rce/plugins/instructure_image/ImageEmbedOptions.js +45 -2
  36. package/es/rce/plugins/instructure_keyboard_shortcuts_header/clickCallback.d.ts +2 -0
  37. package/es/rce/plugins/instructure_keyboard_shortcuts_header/clickCallback.js +45 -0
  38. package/es/rce/plugins/instructure_keyboard_shortcuts_header/plugin.d.ts +1 -0
  39. package/es/rce/plugins/instructure_keyboard_shortcuts_header/plugin.js +43 -0
  40. package/es/rce/plugins/instructure_rce_external_tools/components/ExternalToolDialog/ExternalToolDialog.d.ts +1 -8
  41. package/es/rce/plugins/instructure_rce_external_tools/components/ExternalToolDialog/ExternalToolDialog.js +13 -33
  42. package/es/rce/plugins/instructure_rce_external_tools/components/ExternalToolDialog/ExternalToolDialogModal.d.ts +1 -2
  43. package/es/rce/plugins/instructure_rce_external_tools/components/ExternalToolDialog/ExternalToolDialogModal.js +2 -1
  44. package/es/rce/plugins/instructure_rce_external_tools/components/ExternalToolSelectionDialog/ExternalToolSelectionDialog.d.ts +1 -1
  45. package/es/rce/plugins/instructure_rce_external_tools/components/ExternalToolSelectionDialog/ExternalToolSelectionDialog.js +25 -25
  46. package/es/rce/plugins/instructure_record/AudioOptionsTray/TrayController.d.ts +1 -1
  47. package/es/rce/plugins/instructure_record/AudioOptionsTray/TrayController.js +2 -1
  48. package/es/rce/plugins/instructure_record/VideoOptionsTray/TrayController.d.ts +1 -1
  49. package/es/rce/plugins/instructure_record/VideoOptionsTray/TrayController.js +2 -1
  50. package/es/rce/plugins/instructure_record/VideoOptionsTray/index.js +10 -7
  51. package/es/rce/plugins/instructure_record/mediaTranslations.js +1 -1
  52. package/es/rce/plugins/instructure_studio_media_options/plugin.js +109 -14
  53. package/es/rce/plugins/instructure_studio_media_options/studioToolbarIcons.d.ts +5 -0
  54. package/es/rce/plugins/instructure_studio_media_options/studioToolbarIcons.js +23 -0
  55. package/es/rce/plugins/instructure_wordcount_header/plugin.d.ts +1 -0
  56. package/es/rce/plugins/instructure_wordcount_header/plugin.js +75 -0
  57. package/es/rce/plugins/shared/ContentSelection.d.ts +1 -2
  58. package/es/rce/plugins/shared/ContentSelection.js +1 -18
  59. package/es/rce/plugins/shared/DimensionsInput/index.js +3 -3
  60. package/es/rce/plugins/shared/FixedContentTray.d.ts +7 -23
  61. package/es/rce/plugins/shared/FixedContentTray.js +7 -16
  62. package/es/rce/plugins/shared/ImageCropper/constants.d.ts +1 -1
  63. package/es/rce/plugins/shared/ImageCropper/constants.js +1 -1
  64. package/es/rce/plugins/shared/ImageCropper/controls/CustomNumberInput.js +1 -1
  65. package/es/rce/plugins/shared/PreviewIcon.js +1 -1
  66. package/es/rce/plugins/shared/StudioLtiSupportUtils.d.ts +9 -1
  67. package/es/rce/plugins/shared/StudioLtiSupportUtils.js +94 -1
  68. package/es/rce/plugins/shared/Upload/ComputerPanel.js +1 -1
  69. package/es/rce/plugins/shared/Upload/UploadFileModal.js +37 -4
  70. package/es/rce/plugins/shared/Upload/VideoUrlPanel.d.ts +15 -0
  71. package/es/rce/plugins/shared/Upload/VideoUrlPanel.js +51 -0
  72. package/es/rce/plugins/shared/Upload/videoValidationUtils.d.ts +7 -0
  73. package/es/rce/plugins/shared/Upload/videoValidationUtils.js +58 -0
  74. package/es/rce/plugins/shared/ai_tools/aiicons.d.ts +3 -3
  75. package/es/rce/plugins/shared/ai_tools/aiicons.js +11 -11
  76. package/es/rce/plugins/shared/iframeUtils.d.ts +1 -0
  77. package/es/rce/plugins/shared/iframeUtils.js +37 -0
  78. package/es/rce/plugins/tinymce-a11y-checker/components/checker.js +7 -1
  79. package/es/rce/plugins/tinymce-a11y-checker/utils/indicate.d.ts +1 -1
  80. package/es/rce/plugins/tinymce-a11y-checker/utils/indicate.js +1 -1
  81. package/es/rce/style.js +19 -17
  82. package/es/rce/tinyRCE.js +2 -0
  83. package/es/sidebar/actions/upload.d.ts +1 -0
  84. package/es/sidebar/actions/upload.js +56 -0
  85. package/es/sidebar/containers/sidebarHandlers.d.ts +1 -0
  86. package/es/sidebar/containers/sidebarHandlers.js +2 -1
  87. package/es/translations/locales/ar.js +44 -11
  88. package/es/translations/locales/ca.js +47 -14
  89. package/es/translations/locales/cy.js +44 -11
  90. package/es/translations/locales/da-x-k12.js +44 -11
  91. package/es/translations/locales/da.js +44 -11
  92. package/es/translations/locales/de.js +44 -11
  93. package/es/translations/locales/el.js +6 -0
  94. package/es/translations/locales/en-AU-x-unimelb.js +44 -11
  95. package/es/translations/locales/en-GB-x-ukhe.js +44 -11
  96. package/es/translations/locales/en.js +47 -11
  97. package/es/translations/locales/en_AU.js +44 -11
  98. package/es/translations/locales/en_CA.js +44 -11
  99. package/es/translations/locales/en_CY.js +44 -11
  100. package/es/translations/locales/en_GB.js +44 -11
  101. package/es/translations/locales/es.js +44 -11
  102. package/es/translations/locales/es_ES.js +44 -11
  103. package/es/translations/locales/fa_IR.js +6 -6
  104. package/es/translations/locales/fi.js +44 -11
  105. package/es/translations/locales/fr.js +44 -11
  106. package/es/translations/locales/fr_CA.js +49 -16
  107. package/es/translations/locales/ga.js +61 -28
  108. package/es/translations/locales/he.js +6 -0
  109. package/es/translations/locales/hi.js +44 -11
  110. package/es/translations/locales/ht.js +44 -11
  111. package/es/translations/locales/hu.js +6 -12
  112. package/es/translations/locales/hy.js +6 -0
  113. package/es/translations/locales/id.js +44 -11
  114. package/es/translations/locales/is.js +44 -11
  115. package/es/translations/locales/it.js +44 -11
  116. package/es/translations/locales/ja.js +44 -11
  117. package/es/translations/locales/ko.js +6 -0
  118. package/es/translations/locales/mi.js +44 -11
  119. package/es/translations/locales/ms.js +44 -11
  120. package/es/translations/locales/nb-x-k12.js +44 -11
  121. package/es/translations/locales/nb.js +44 -11
  122. package/es/translations/locales/nl.js +44 -11
  123. package/es/translations/locales/nn.js +6 -12
  124. package/es/translations/locales/pl.js +44 -11
  125. package/es/translations/locales/pt.js +44 -11
  126. package/es/translations/locales/pt_BR.js +44 -11
  127. package/es/translations/locales/ru.js +44 -11
  128. package/es/translations/locales/sl.js +44 -11
  129. package/es/translations/locales/sv-x-k12.js +44 -11
  130. package/es/translations/locales/sv.js +44 -11
  131. package/es/translations/locales/th.js +44 -11
  132. package/es/translations/locales/tr.js +6 -3
  133. package/es/translations/locales/uk_UA.js +6 -9
  134. package/es/translations/locales/vi.js +44 -11
  135. package/es/translations/locales/zh-Hans.js +44 -11
  136. package/es/translations/locales/zh-Hant.js +44 -11
  137. package/es/translations/locales/zh.js +44 -11
  138. package/es/translations/locales/zh_HK.js +44 -11
  139. package/es/util/contextHelper.d.ts +7 -0
  140. package/{testcafe/axe.test.js → es/util/contextHelper.js} +10 -21
  141. package/es/util/loadingPlaceholder.js +11 -11
  142. package/eslint.config.js +3 -25
  143. package/jest/jest-setup.js +27 -2
  144. package/jest.config.js +5 -1
  145. package/package.json +61 -84
  146. package/testcafe/RCEWrapper.test.js +0 -319
  147. package/testcafe/StatusBar.test.js +0 -108
  148. package/testcafe/enhanceUserContent.html +0 -58
  149. package/testcafe/enhanceUserContent.test.js +0 -44
  150. package/testcafe/entry.jsx +0 -77
  151. package/testcafe/testcafe.html +0 -14
  152. package/webpack.testcafe.config.js +0 -61
@@ -17,62 +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
- const RestoreAutoSaveModal = /*#__PURE__*/React.lazy(() => import('./RestoreAutoSaveModal'));
69
- 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"));
70
71
  const ASYNC_FOCUS_TIMEOUT = 250;
71
- const DEFAULT_RCE_HEIGHT = '400px';
72
+ const DEFAULT_RCE_HEIGHT = "400px";
72
73
  function addKebabIcon(editor) {
73
74
  // This has to be done here instead of of in plugins/instructure-ui-icons/plugin.ts
74
75
  // presumably because the toolbar gets created before that plugin is loaded?
75
- editor.ui.registry.addIcon('more-drawer', IconMoreSolid.src);
76
+ editor.ui.registry.addIcon("more-drawer", IconMoreSolid.src);
76
77
  }
77
78
 
78
79
  // Get oxide the default skin injected into the DOM before the overrides loaded by themeable
@@ -80,8 +81,8 @@ let inserted = false;
80
81
  function injectTinySkin() {
81
82
  if (inserted) return;
82
83
  inserted = true;
83
- const style = document.createElement('style');
84
- style.setAttribute('data-skin', 'tiny oxide skin');
84
+ const style = document.createElement("style");
85
+ style.setAttribute("data-skin", "tiny oxide skin");
85
86
  style.appendChild(document.createTextNode(skinCSS));
86
87
  // there's CSS from discussions that turns the instui Selectors bold
87
88
  // and in classic quizzes that also mucks with padding
@@ -89,9 +90,9 @@ function injectTinySkin() {
89
90
  #discussion-edit-view .rce-wrapper input[readonly] {font-weight: normal;}
90
91
  #quiz_edit_wrapper .rce-wrapper input[readonly] {font-weight: normal; padding-left: .75rem;}
91
92
  `));
92
- const beforeMe = document.head.querySelector('style[data-glamor]') ||
93
+ const beforeMe = document.head.querySelector("style[data-glamor]") ||
93
94
  // find instui's themeable stylesheet
94
- document.head.querySelector('style') ||
95
+ document.head.querySelector("style") ||
95
96
  // find any stylesheet
96
97
  document.head.firstElementChild;
97
98
  document.head.insertBefore(style, beforeMe);
@@ -101,10 +102,10 @@ const editorWrappers = new WeakMap();
101
102
  // determines if localStorage is available for our use.
102
103
  // see https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API/Using_the_Web_Storage_API
103
104
  export function storageAvailable() {
104
- let storage;
105
+ let storage = null;
105
106
  try {
106
107
  storage = window.localStorage;
107
- const x = '__storage_test__';
108
+ const x = "__storage_test__";
108
109
  storage.setItem(x, x);
109
110
  storage.removeItem(x);
110
111
  return true;
@@ -116,15 +117,15 @@ export function storageAvailable() {
116
117
  e.code === 1014 ||
117
118
  // test name field too, because code might not be present
118
119
  // everything except Firefox
119
- e.name === 'QuotaExceededError' ||
120
+ e.name === "QuotaExceededError" ||
120
121
  // Firefox
121
- e.name === 'NS_ERROR_DOM_QUOTA_REACHED') &&
122
+ e.name === "NS_ERROR_DOM_QUOTA_REACHED") &&
122
123
  // acknowledge QuotaExceededError only if there's something already stored
123
124
  storage && storage.length !== 0;
124
125
  }
125
126
  }
126
127
  function renderLoading() {
127
- return formatMessage('Loading');
128
+ return formatMessage("Loading");
128
129
  }
129
130
  let alertIdValue = 0;
130
131
  class RCEWrapper extends React.Component {
@@ -142,6 +143,7 @@ class RCEWrapper extends React.Component {
142
143
  this._showOnFocusButton = void 0;
143
144
  this._statusBarId = void 0;
144
145
  this._textareaEl = void 0;
146
+ this._effectiveContainingContext = void 0;
145
147
  this.AIToolsTray = void 0;
146
148
  this.editor = void 0;
147
149
  this.initialContent = void 0;
@@ -197,7 +199,7 @@ class RCEWrapper extends React.Component {
197
199
  this.setState(newState);
198
200
  this.checkAccessibility();
199
201
  if (newView === PRETTY_HTML_EDITOR_VIEW || newView === RAW_HTML_EDITOR_VIEW) {
200
- this.storage?.setItem?.('rce.htmleditor', newView);
202
+ this.storage?.setItem?.("rce.htmleditor", newView);
201
203
  }
202
204
 
203
205
  // Emit view change event
@@ -209,20 +211,23 @@ class RCEWrapper extends React.Component {
209
211
  this.toggleFullscreen = () => {
210
212
  this.handleClickFullscreen();
211
213
  };
214
+ // @ts-expect-error
212
215
  this._onFullscreenChange = event => {
213
216
  if (document[FS_ELEMENT]) {
214
217
  // @ts-expect-error
215
218
  this.resizeObserver.observe(document[FS_ELEMENT]);
216
- window.visualViewport?.addEventListener('resize', this._handleFullscreenResize);
219
+ window.visualViewport?.addEventListener("resize", this._handleFullscreenResize);
217
220
  this._handleFullscreenResize();
218
221
  // @ts-expect-error
219
- this._focusRegion = FocusRegionManager.activateRegion(document[FS_ELEMENT], {
222
+ this._focusRegion = FocusRegionManager.activateRegion(
223
+ // @ts-expect-error
224
+ document[FS_ELEMENT], {
220
225
  shouldContainFocus: true
221
226
  });
222
227
  } else {
223
228
  event.target.removeEventListener(FS_CHANGEEVENT, this._onFullscreenChange);
224
229
  this.resizeObserver.unobserve(event.target);
225
- window.visualViewport?.removeEventListener('resize', this._handleFullscreenResize);
230
+ window.visualViewport?.removeEventListener("resize", this._handleFullscreenResize);
226
231
  this._setHeight(this.state.fullscreenState.prevHeight);
227
232
  if (this._focusRegion) {
228
233
  FocusRegionManager.blurRegion(event.target, this._focusRegion.id);
@@ -239,6 +244,7 @@ class RCEWrapper extends React.Component {
239
244
  this.handleFocusRCE = () => {
240
245
  this.handleFocus();
241
246
  };
247
+ // @ts-expect-error
242
248
  this.handleBlurRCE = event => {
243
249
  if (event.relatedTarget === null) {
244
250
  // focus might be moving to tinymce
@@ -254,35 +260,35 @@ class RCEWrapper extends React.Component {
254
260
  // what we've got for now.
255
261
  const ifr = this.iframe;
256
262
  if (ifr?.parentElement) {
257
- ifr.parentElement.classList.add('active');
263
+ ifr.parentElement.classList.add("active");
258
264
  }
259
265
  this.handleFocus();
260
266
  };
261
267
  this.handleBlurEditor = event => {
262
268
  const ifr = this.iframe;
263
269
  if (ifr?.parentElement) {
264
- ifr.parentElement.classList.remove('active');
270
+ ifr.parentElement.classList.remove("active");
265
271
  }
266
272
  this.handleBlur(event);
267
273
  };
268
274
  this.handleKey = event => {
269
- if (event.code === 'F9' && event.altKey) {
275
+ if (event.code === "F9" && event.altKey) {
270
276
  event.preventDefault();
271
277
  event.stopPropagation();
272
278
  // @ts-expect-error
273
279
  focusFirstMenuButton(this._elementRef.current);
274
- } else if (event.code === 'F10' && event.altKey) {
280
+ } else if (event.code === "F10" && event.altKey) {
275
281
  event.preventDefault();
276
282
  event.stopPropagation();
277
283
  // @ts-expect-error
278
284
  focusToolbar(this._elementRef.current);
279
- } else if (event.code === 'F8' && event.altKey) {
285
+ } else if (event.code === "F8" && event.altKey) {
280
286
  event.preventDefault();
281
287
  event.stopPropagation();
282
288
  this.openKBShortcutModal();
283
- } else if (event.code === 'Escape') {
289
+ } else if (event.code === "Escape") {
284
290
  bridge.hideTrays();
285
- } else if (['n', 'N', 'd', 'D'].indexOf(event.key) !== -1) {
291
+ } else if (["n", "N", "d", "D"].indexOf(event.key) !== -1) {
286
292
  // Prevent key events from bubbling up on touch screen device
287
293
  event.stopPropagation();
288
294
  }
@@ -311,32 +317,32 @@ class RCEWrapper extends React.Component {
311
317
  // @ts-expect-error
312
318
  textarea.value = this.getCode();
313
319
  textarea.style.height = this.state.height;
314
- textarea.removeAttribute('aria-hidden');
315
- if (document.body.classList.contains('Underline-All-Links__enabled')) {
320
+ textarea.removeAttribute("aria-hidden");
321
+ if (document.body.classList.contains("Underline-All-Links__enabled")) {
316
322
  if (this.iframe?.contentDocument) {
317
- this.iframe.contentDocument.body.classList.add('Underline-All-Links__enabled');
323
+ this.iframe.contentDocument.body.classList.add("Underline-All-Links__enabled");
318
324
  }
319
325
  }
320
- editor.on('wordCountUpdate', this.onWordCountUpdate);
326
+ editor.on("wordCountUpdate", this.onWordCountUpdate);
321
327
  // add an aria-label to the application div that wraps RCE
322
328
  // and change role from "application" to "document" to ensure
323
329
  // the editor gets properly picked up by screen readers
324
330
  const tinyapp = document.querySelector('.tox-tinymce[role="application"]');
325
331
  if (tinyapp) {
326
- tinyapp.setAttribute('aria-label', formatMessage('Rich Content Editor'));
327
- tinyapp.setAttribute('role', 'document');
328
- tinyapp.setAttribute('tabIndex', '-1');
332
+ tinyapp.setAttribute("aria-label", formatMessage("Rich Content Editor"));
333
+ tinyapp.setAttribute("role", "document");
334
+ tinyapp.setAttribute("tabIndex", "-1");
329
335
  }
330
336
 
331
337
  // Probably should do this in tinymce.scss, but we only want it in new rce
332
- textarea.style.resize = 'none';
333
- editor.on('keydown', this.handleKey);
334
- editor.on('FullscreenStateChanged', this._onFullscreenChange);
338
+ textarea.style.resize = "none";
339
+ editor.on("keydown", this.handleKey);
340
+ editor.on("FullscreenStateChanged", this._onFullscreenChange);
335
341
  // This propagates click events on the editor out of the iframe to the parent
336
342
  // document. We need this so that click events get captured properly by instui
337
343
  // focus-trapping components, so they properly ignore trapping focus on click.
338
- editor.on('click', () => window.document.body.click(), true);
339
- 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));
340
346
  initScreenreaderOnFormat(editor);
341
347
  this.announceContextToolbars(editor);
342
348
  if (this.isAutoSaving) {
@@ -348,12 +354,12 @@ class RCEWrapper extends React.Component {
348
354
 
349
355
  // readonly should have been handled via the init property passed
350
356
  // to <Editor>, but it's not.
351
- editor.mode.set(this.props.readOnly ? 'readonly' : 'design');
357
+ editor.mode.set(this.props.readOnly ? "readonly" : "design");
352
358
 
353
359
  // Not using iframe_aria_text because compatibility issues.
354
360
  // Not using iframe_attrs because library overwriting.
355
361
  if (this.iframe) {
356
- 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.", {
357
363
  OSKey: determineOSDependentKey()
358
364
  }));
359
365
  }
@@ -366,13 +372,19 @@ class RCEWrapper extends React.Component {
366
372
 
367
373
  // cleans up highlight artifacts from findreplace plugin
368
374
  if (this.getRequiredFeatureStatuses().rce_find_replace) {
369
- editor.on('undo redo', _e => {
370
- 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) {
371
377
  editor.plugins?.searchreplace?.done();
372
378
  }
373
379
  });
374
380
  }
375
381
  };
382
+ /**
383
+ * Fix keyboard navigation in the expanded toolbar
384
+ *
385
+ * NOTE: This is a workaround for https://github.com/tinymce/tinymce/issues/8618
386
+ * and should be removed once that issue is resolved and the tinymce dependency is updated to include it.
387
+ */
376
388
  this.fixToolbarKeyboardNavigation = () => {
377
389
  // The keyboard navigation config in tinymce for the expanded toolbar is incorrectly configured,
378
390
  // and stops at [data-alloy-tabstop] elements.
@@ -380,8 +392,21 @@ class RCEWrapper extends React.Component {
380
392
  // This workaround removes attribute, thusly causing navigation to work correctly again.
381
393
  // For the correct solution, Keying.config should have { selector: '.tox-toolbar__group' }
382
394
  // in https://github.com/tinymce/tinymce/blob/develop/modules/alloy/src/main/ts/ephox/alloy/ui/schema/SplitSlidingToolbarSchema.ts
383
- 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"));
384
396
  };
397
+ /**
398
+ * Sets up selection saving and restoration logic.
399
+ *
400
+ * There are certain actions a user can take when the RCE is not focused that clear the selection inside the
401
+ * editor, such as invoking the Find feature of the browser. If the user then tries to insert content without
402
+ * going back to the editor, the content would be inserted at the top of the RCE, instead of where their cursor
403
+ * was.
404
+ *
405
+ * This method adds logic that saves and restores the selection to work around the issue.
406
+ *
407
+ * @private
408
+ */
409
+ // @ts-expect-error
385
410
  this._setupSelectionSaving = editor => {
386
411
  // @ts-expect-error
387
412
  let savedSelection = null;
@@ -394,7 +419,7 @@ class RCEWrapper extends React.Component {
394
419
  selectionWasReset = false;
395
420
  }
396
421
  };
397
- editor.on('blur', () => {
422
+ editor.on("blur", () => {
398
423
  editorHasFocus = false;
399
424
  selectionWasReset = false;
400
425
  if (!this.editor) return;
@@ -403,7 +428,7 @@ class RCEWrapper extends React.Component {
403
428
  isForward: this.editor.selection.isForward()
404
429
  };
405
430
  });
406
- editor.on('focus', () => {
431
+ editor.on("focus", () => {
407
432
  // We need to restore the selection when the editor regains focus because sometimes the editor regains
408
433
  // focus without the user setting the selection themselves (such as when they interact with the toolbar)
409
434
  // and if we didn't, we would end up saving the reset selection before a user managed to actually insert
@@ -412,7 +437,7 @@ class RCEWrapper extends React.Component {
412
437
  editorHasFocus = true;
413
438
  selectionWasReset = false;
414
439
  });
415
- editor.on('SelectionChange', () => {
440
+ editor.on("SelectionChange", () => {
416
441
  if (editorHasFocus) {
417
442
  // We don't care if a selection reset occurs when the editor has focus, the user probably intended that
418
443
  // At least they will see the effect
@@ -422,14 +447,14 @@ class RCEWrapper extends React.Component {
422
447
  const selection = this.editor.selection.normalize();
423
448
 
424
449
  // Detect a browser-reset selection (e.g. From invoking the Find command)
425
- 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) {
426
451
  selectionWasReset = true;
427
452
  }
428
453
  });
429
- editor.on('BeforeExecCommand', () => {
454
+ editor.on("BeforeExecCommand", () => {
430
455
  restoreSelectionIfNecessary();
431
456
  });
432
- editor.on('ExecCommand', (/* event */
457
+ editor.on("ExecCommand", (/* event */
433
458
  ) => {
434
459
  if (!this.editor) return;
435
460
  // Commands may have modified the selection, we need to recapture it
@@ -440,12 +465,14 @@ class RCEWrapper extends React.Component {
440
465
  });
441
466
  };
442
467
  this.announcing = 0;
468
+ this._isMounted = false;
469
+ /* ********** autosave support *************** */
443
470
  this.initAutoSave = editor => {
444
471
  var _this$props$userCache;
445
- 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 : "");
446
473
  if (this.storage) {
447
- editor.on('change Undo Redo', this.doAutoSave);
448
- editor.on('blur', this.doAutoSave);
474
+ editor.on("change Undo Redo", this.doAutoSave);
475
+ editor.on("blur", this.doAutoSave);
449
476
  this.cleanupAutoSave();
450
477
  try {
451
478
  const autosaved = this.getAutoSaved(this.autoSaveKey);
@@ -470,10 +497,11 @@ class RCEWrapper extends React.Component {
470
497
  } catch (ex) {
471
498
  // log and ignore
472
499
 
473
- console.error('Failed initializing rce autosave', ex);
500
+ console.error("Failed initializing rce autosave", ex);
474
501
  }
475
502
  }
476
503
  };
504
+ // remove any autosaved value that's too old
477
505
  this.cleanupAutoSave = (deleteAll = false) => {
478
506
  if (this.storage) {
479
507
  const expiry = deleteAll ? Date.now() : Date.now() - (this.props.autosave?.maxAge || 0);
@@ -489,6 +517,7 @@ class RCEWrapper extends React.Component {
489
517
  }
490
518
  }
491
519
  };
520
+ // @ts-expect-error
492
521
  this.restoreAutoSave = ans => {
493
522
  this.setState({
494
523
  confirmAutoSave: false
@@ -503,6 +532,7 @@ class RCEWrapper extends React.Component {
503
532
  // let the content be restored
504
533
  debounce(this.checkAccessibility, 1000)();
505
534
  };
535
+ // @ts-expect-error
506
536
  this.doAutoSave = (e, retry = false) => {
507
537
  if (this.storage) {
508
538
  const editor = this.mceInstance();
@@ -522,14 +552,15 @@ class RCEWrapper extends React.Component {
522
552
  this.cleanupAutoSave(true);
523
553
  this.doAutoSave(e, true);
524
554
  } else {
525
- console.error('Autosave failed:', ex);
555
+ console.error("Autosave failed:", ex);
526
556
  }
527
557
  }
528
558
  }
529
559
  };
560
+ /* *********** end autosave support *************** */
530
561
  this.onWordCountUpdate = e => {
531
562
  if (!this.editor) return;
532
- const shouldIgnore = countShouldIgnore(this.editor, 'body', 'words');
563
+ const shouldIgnore = countShouldIgnore(this.editor, "body", "words");
533
564
  const updatedCount = e.wordCount.words - shouldIgnore;
534
565
  this.setState(state => {
535
566
  if (updatedCount !== state.wordCount) {
@@ -539,9 +570,10 @@ class RCEWrapper extends React.Component {
539
570
  } else return null;
540
571
  });
541
572
  };
573
+ // @ts-expect-error
542
574
  this.onNodeChange = e => {
543
575
  // This is basically copied out of the tinymce silver theme code for the status bar
544
- 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")
545
577
  // @ts-expect-error
546
578
  .map(p => p.nodeName.toLowerCase()).reverse();
547
579
  this.setState({
@@ -552,7 +584,7 @@ class RCEWrapper extends React.Component {
552
584
  this.props.onContentChange?.(content);
553
585
  // check accessibility when clearing the editor,
554
586
  // all other times should be checked by handleInputChange
555
- if (content === '') {
587
+ if (content === "") {
556
588
  this.checkAccessibility();
557
589
  }
558
590
  };
@@ -574,12 +606,14 @@ class RCEWrapper extends React.Component {
574
606
  height: newHeight
575
607
  });
576
608
  // play nice and send the same event that the silver theme would send
577
- editor.fire('ResizeEditor');
609
+ editor.fire("ResizeEditor", {
610
+ deltaY: coordinates.deltaY
611
+ });
578
612
  }
579
613
  };
580
614
  this.onA11yChecker = triggerElementId => {
581
615
  const editor = this.mceInstance();
582
- editor.execCommand('openAccessibilityChecker', false, {
616
+ editor.execCommand("openAccessibilityChecker", false, {
583
617
  mountNode: instuiPopupMountNodeFn,
584
618
  triggerElementId,
585
619
  onFixError: errors => {
@@ -593,7 +627,7 @@ class RCEWrapper extends React.Component {
593
627
  };
594
628
  this.checkAccessibility = () => {
595
629
  const editor = this.mceInstance();
596
- editor.execCommand('checkAccessibility', false, {
630
+ editor.execCommand("checkAccessibility", false, {
597
631
  // @ts-expect-error
598
632
  done: errors => {
599
633
  this.setState({
@@ -633,7 +667,7 @@ class RCEWrapper extends React.Component {
633
667
  }
634
668
  };
635
669
  this.handleAIClick = () => {
636
- import('./plugins/shared/ai_tools').then(module => {
670
+ import("./plugins/shared/ai_tools").then(module => {
637
671
  // @ts-expect-error
638
672
  this.AIToolsTray = module.AIToolsTray;
639
673
  this.setState({
@@ -641,7 +675,7 @@ class RCEWrapper extends React.Component {
641
675
  AITToolsFocusReturn: document.activeElement
642
676
  });
643
677
  }).catch(ex => {
644
- console.error('Failed loading the AIToolsTray', ex);
678
+ console.error("Failed loading the AIToolsTray", ex);
645
679
  });
646
680
  };
647
681
  this.closeAITools = () => {
@@ -683,10 +717,10 @@ class RCEWrapper extends React.Component {
683
717
  this.getCurrentContentForAI = () => {
684
718
  const selected = this.mceInstance().selection.getContent();
685
719
  return selected ? {
686
- type: 'selection',
720
+ type: "selection",
687
721
  content: selected
688
722
  } : {
689
- type: 'full',
723
+ type: "full",
690
724
  content: this.mceInstance().getContent()
691
725
  };
692
726
  };
@@ -701,7 +735,7 @@ class RCEWrapper extends React.Component {
701
735
  alert.id = alertIdValue++;
702
736
  this.setState(state => {
703
737
  let messages = state.messages.concat(alert);
704
- messages = _.uniqBy(messages, 'text'); // Don't show the same message twice
738
+ messages = _.uniqBy(messages, "text"); // Don't show the same message twice
705
739
  return {
706
740
  messages
707
741
  };
@@ -715,9 +749,12 @@ class RCEWrapper extends React.Component {
715
749
  };
716
750
  });
717
751
  };
752
+ /**
753
+ * Used for reseting the value during tests
754
+ */
718
755
  this.resetAlertId = () => {
719
756
  if (this.state.messages.length > 0) {
720
- 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");
721
758
  }
722
759
  alertIdValue = 0;
723
760
  };
@@ -759,7 +796,7 @@ class RCEWrapper extends React.Component {
759
796
  if (!Number.isNaN(_ht)) {
760
797
  _ht = `${_ht}px`;
761
798
  }
762
- const currentRCECount = document.querySelectorAll('.rce-wrapper').length;
799
+ const currentRCECount = document.querySelectorAll(".rce-wrapper").length;
763
800
  const maxInitRenderedRCEs = Number.isNaN(props.maxInitRenderedRCEs) ? RCEWrapper.defaultProps.maxInitRenderedRCEs : props.maxInitRenderedRCEs;
764
801
  this.state = {
765
802
  path: [],
@@ -770,9 +807,9 @@ class RCEWrapper extends React.Component {
770
807
  messages: [],
771
808
  announcement: null,
772
809
  confirmAutoSave: false,
773
- autoSavedContent: '',
810
+ autoSavedContent: "",
774
811
  // @ts-expect-error
775
- id: this.props.id || this.props.textareaId || `${uid('rce', 2)}`,
812
+ id: this.props.id || this.props.textareaId || `${uid("rce", 2)}`,
776
813
  // @ts-expect-error
777
814
  height: _ht,
778
815
  fullscreenState: {
@@ -780,7 +817,7 @@ class RCEWrapper extends React.Component {
780
817
  prevHeight: _ht
781
818
  },
782
819
  a11yErrorsCount: 0,
783
- shouldShowEditor: typeof IntersectionObserver === 'undefined' || maxInitRenderedRCEs <= 0 || currentRCECount < maxInitRenderedRCEs,
820
+ shouldShowEditor: typeof IntersectionObserver === "undefined" || maxInitRenderedRCEs <= 0 || currentRCECount < maxInitRenderedRCEs,
784
821
  AIToolsOpen: false
785
822
  };
786
823
  this._statusBarId = `${this.state.id}_statusbar`;
@@ -806,6 +843,7 @@ class RCEWrapper extends React.Component {
806
843
  this._handleFullscreenResize();
807
844
  });
808
845
  this.AIToolsTray = undefined;
846
+ this._effectiveContainingContext = normalizeContainingContext(this.props.trayProps?.containingContext);
809
847
  }
810
848
 
811
849
  // when the RCE is put into fullscreen we need to move the div
@@ -814,11 +852,11 @@ class RCEWrapper extends React.Component {
814
852
  // configure tinymce to say where that div is mounted, do this
815
853
  // is a bit of a hack to tag the div that is this RCE's
816
854
  _tagTinymceAuxDiv() {
817
- const tinyauxlist = document.querySelectorAll('.tox-tinymce-aux');
855
+ const tinyauxlist = document.querySelectorAll(".tox-tinymce-aux");
818
856
  if (tinyauxlist.length) {
819
857
  const myaux = tinyauxlist[tinyauxlist.length - 1];
820
858
  if (myaux.id) {
821
- console.error('Unexpected ID on my tox-tinymce-aux element');
859
+ console.error("Unexpected ID on my tox-tinymce-aux element");
822
860
  }
823
861
  myaux.id = `tinyaux-${this.id}`;
824
862
  }
@@ -832,6 +870,7 @@ class RCEWrapper extends React.Component {
832
870
  explicit_latex_typesetting = false,
833
871
  rce_transform_loaded_content = false,
834
872
  rce_find_replace = false,
873
+ rce_studio_embed_improvements = false,
835
874
  file_verifiers_for_quiz_links = false,
836
875
  consolidated_media_player = false
837
876
  } = this.props.features;
@@ -839,6 +878,7 @@ class RCEWrapper extends React.Component {
839
878
  new_math_equation_handling,
840
879
  explicit_latex_typesetting,
841
880
  rce_transform_loaded_content,
881
+ rce_studio_embed_improvements,
842
882
  file_verifiers_for_quiz_links,
843
883
  rce_find_replace,
844
884
  consolidated_media_player
@@ -876,7 +916,7 @@ class RCEWrapper extends React.Component {
876
916
  let status = true;
877
917
  // Check for remaining placeholders
878
918
  if (this.mceInstance().dom.doc.querySelector(`[data-placeholder-for]`)) {
879
- 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."));
880
920
  }
881
921
  return status;
882
922
  }
@@ -936,9 +976,9 @@ class RCEWrapper extends React.Component {
936
976
  // @ts-expect-error
937
977
  ifr.contentDocument.body.clientHeight -
938
978
  // @ts-expect-error
939
- parseInt(editor_body_style['padding-top'], 10) -
979
+ parseInt(editor_body_style["padding-top"], 10) -
940
980
  // @ts-expect-error
941
- parseInt(editor_body_style['padding-bottom'], 10);
981
+ parseInt(editor_body_style["padding-bottom"], 10);
942
982
  const para_margin_ht = 24;
943
983
  const reserve_ht = Math.ceil(height + para_margin_ht);
944
984
  if (reserve_ht > editor_ht) {
@@ -950,7 +990,7 @@ class RCEWrapper extends React.Component {
950
990
  }
951
991
  }
952
992
  checkImageLoadError(element) {
953
- if (!element || element.tagName !== 'IMG') {
993
+ if (!element || element.tagName !== "IMG") {
954
994
  return;
955
995
  }
956
996
  // @ts-expect-error
@@ -965,9 +1005,9 @@ class RCEWrapper extends React.Component {
965
1005
  // @ts-expect-error
966
1006
  if (element.naturalWidth === 0) {
967
1007
  // @ts-expect-error
968
- element.style.border = '1px solid #000';
1008
+ element.style.border = "1px solid #000";
969
1009
  // @ts-expect-error
970
- element.style.padding = '2px';
1010
+ element.style.padding = "2px";
971
1011
  }
972
1012
  }, 0);
973
1013
  }
@@ -977,7 +1017,7 @@ class RCEWrapper extends React.Component {
977
1017
  this.contentInserted(element);
978
1018
  }
979
1019
  replaceCode(code) {
980
- 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."))) {
981
1021
  this.mceInstance().setContent(code);
982
1022
  }
983
1023
  }
@@ -994,12 +1034,12 @@ class RCEWrapper extends React.Component {
994
1034
  // that there's some embedded content helper
995
1035
  // From what I've read, "title" is more reliable than "aria-label" for
996
1036
  // elements like iframes and embeds.
997
- const temp = document.createElement('div');
1037
+ const temp = document.createElement("div");
998
1038
  temp.innerHTML = code;
999
1039
  const code_elem = temp.firstElementChild;
1000
1040
  if (code_elem) {
1001
- if (!code_elem.hasAttribute('title') && !code_elem.hasAttribute('aria-label')) {
1002
- 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"));
1003
1043
  }
1004
1044
  code = code_elem.outerHTML;
1005
1045
  }
@@ -1009,7 +1049,7 @@ class RCEWrapper extends React.Component {
1009
1049
  // and it's often inserted into a <p> on top of that. Find the
1010
1050
  // iframe and use it to flash the indicator.
1011
1051
  const element = contentInsertion.insertContent(editor, code);
1012
- const ifr = element && element.querySelector && element.querySelector('iframe');
1052
+ const ifr = element && element.querySelector && element.querySelector("iframe");
1013
1053
  if (ifr) {
1014
1054
  this.contentInserted(ifr);
1015
1055
  } else {
@@ -1021,7 +1061,7 @@ class RCEWrapper extends React.Component {
1021
1061
  const element = contentInsertion.insertImage(editor, image, this.getCanvasUrl());
1022
1062
 
1023
1063
  // Removes TinyMCE's caret &nbsp; text if exists.
1024
- if (element?.nextSibling?.data?.startsWith('\xA0' /* nbsp */)) {
1064
+ if (element?.nextSibling?.data?.startsWith("\xA0" /* nbsp */)) {
1025
1065
  element.nextSibling.splitText(1);
1026
1066
  element.nextSibling.remove();
1027
1067
  }
@@ -1095,16 +1135,15 @@ class RCEWrapper extends React.Component {
1095
1135
  if (this.editor) {
1096
1136
  return this.editor;
1097
1137
  }
1098
- const editors = this.props.tinymce.editors || [];
1099
- return editors.filter(ed => ed.id === this.props.textareaId)[0];
1138
+ return this.props.tinymce.get(this.props.textareaId);
1100
1139
  }
1101
1140
 
1102
1141
  // @ts-expect-error
1103
1142
  onTinyMCEInstance(command, ...args) {
1104
1143
  const editor = this.mceInstance();
1105
1144
  if (editor) {
1106
- if (command === 'mceRemoveEditor') {
1107
- editor.execCommand('mceNewDocument');
1145
+ if (command === "mceRemoveEditor") {
1146
+ editor.execCommand("mceNewDocument");
1108
1147
  } // makes sure content can't persist past removal
1109
1148
  editor.execCommand(command, false, ...args);
1110
1149
  }
@@ -1124,17 +1163,17 @@ class RCEWrapper extends React.Component {
1124
1163
  return null;
1125
1164
  }
1126
1165
  textareaValue() {
1127
- return this.getTextarea()?.value || '';
1166
+ return this.getTextarea()?.value || "";
1128
1167
  }
1129
1168
  get id() {
1130
1169
  return this.state.id;
1131
1170
  }
1132
1171
  getHtmlEditorStorage() {
1133
- const cookieValue = getCookie('rce.htmleditor');
1172
+ const cookieValue = getCookie("rce.htmleditor");
1134
1173
  if (cookieValue) {
1135
1174
  document.cookie = `rce.htmleditor=${cookieValue};path=/;max-age=0`;
1136
1175
  }
1137
- const value = cookieValue || this.storage?.getItem?.('rce.htmleditor')?.content;
1176
+ const value = cookieValue || this.storage?.getItem?.("rce.htmleditor")?.content;
1138
1177
  return value === RAW_HTML_EDITOR_VIEW || value === PRETTY_HTML_EDITOR_VIEW ? value : PRETTY_HTML_EDITOR_VIEW;
1139
1178
  }
1140
1179
  _isFullscreen() {
@@ -1151,7 +1190,7 @@ class RCEWrapper extends React.Component {
1151
1190
  this._elementRef.current?.appendChild(tinymenuhost);
1152
1191
  }
1153
1192
  this._elementRef.current?.addEventListener(FS_CHANGEEVENT, this._onFullscreenChange);
1154
- if (typeof this._elementRef.current?.offsetHeight === 'number') {
1193
+ if (typeof this._elementRef.current?.offsetHeight === "number") {
1155
1194
  this.setState({
1156
1195
  fullscreenState: {
1157
1196
  prevHeight: this._elementRef.current.offsetHeight - this._getStatusBarHeight()
@@ -1171,9 +1210,6 @@ class RCEWrapper extends React.Component {
1171
1210
  document[FS_EXIT]();
1172
1211
  }
1173
1212
  }
1174
-
1175
- // @ts-expect-error
1176
-
1177
1213
  _getStatusBarHeight() {
1178
1214
  // the height prop is the height of the editor and does not include
1179
1215
  // the status bar. we'll need this later.
@@ -1186,7 +1222,7 @@ class RCEWrapper extends React.Component {
1186
1222
  const container = ed.getContainer();
1187
1223
  if (container) {
1188
1224
  container.style.height = cssHeight;
1189
- ed.fire('ResizeEditor');
1225
+ ed.fire("ResizeEditor");
1190
1226
  }
1191
1227
  const textarea = this.getTextarea();
1192
1228
  if (textarea) {
@@ -1197,10 +1233,10 @@ class RCEWrapper extends React.Component {
1197
1233
  });
1198
1234
  }
1199
1235
  focus() {
1200
- this.onTinyMCEInstance('mceFocus');
1236
+ this.onTinyMCEInstance("mceFocus");
1201
1237
  // tinymce doesn't always call the focus handler.
1202
1238
  // @ts-expect-error
1203
- this.handleFocusEditor(new Event('focus', {
1239
+ this.handleFocusEditor(new Event("focus", {
1204
1240
  target: this.mceInstance()
1205
1241
  }));
1206
1242
  }
@@ -1238,7 +1274,7 @@ class RCEWrapper extends React.Component {
1238
1274
  */
1239
1275
  get _mceSerializedInitialHtml() {
1240
1276
  if (!this._mceSerializedInitialHtmlCached) {
1241
- const el = window.document.createElement('div');
1277
+ const el = window.document.createElement("div");
1242
1278
  // @ts-expect-error
1243
1279
  el.innerHTML = this.initialContent;
1244
1280
  const serializer = this.mceInstance().serializer;
@@ -1292,20 +1328,22 @@ class RCEWrapper extends React.Component {
1292
1328
  // focus is still somewhere w/in me
1293
1329
  return;
1294
1330
  }
1295
- const activeClass = document.activeElement && document.activeElement.getAttribute('class');
1331
+ const activeClass = document.activeElement?.getAttribute("class");
1296
1332
  if (
1297
1333
  // @ts-expect-error
1298
- (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-")) {
1299
1337
  // if a toolbar button has focus, then the user clicks on the "more" button
1300
1338
  // focus jumps to the body, then eventually to the popped up toolbar. This
1301
1339
  // catches that case.
1302
1340
  return;
1303
1341
  }
1304
- if (event?.relatedTarget?.getAttribute('class')?.includes('tox-')) {
1342
+ if (event?.relatedTarget?.getAttribute("class")?.includes("tox-")) {
1305
1343
  // a tinymce popup has focus
1306
1344
  return;
1307
1345
  }
1308
- const popups = document.querySelectorAll('[data-mce-component]');
1346
+ const popups = document.querySelectorAll("[data-mce-component]");
1309
1347
  for (const popup of popups) {
1310
1348
  if (popup.contains(document.activeElement)) {
1311
1349
  // one of our popups has focus
@@ -1319,50 +1357,26 @@ class RCEWrapper extends React.Component {
1319
1357
  }, ASYNC_FOCUS_TIMEOUT);
1320
1358
  }
1321
1359
  }
1322
-
1323
- // @ts-expect-error
1324
-
1325
1360
  // @ts-expect-error
1326
1361
  call(methodName, ...args) {
1327
1362
  // since exists? has a ? and cant be a regular function just return true
1328
1363
  // rather than calling as a fn on the editor
1329
- if (methodName === 'exists?') {
1364
+ if (methodName === "exists?") {
1330
1365
  return true;
1331
1366
  }
1332
1367
  // @ts-expect-error
1333
1368
  return this[methodName](...args);
1334
1369
  }
1335
-
1336
- /**
1337
- * Fix keyboard navigation in the expanded toolbar
1338
- *
1339
- * NOTE: This is a workaround for https://github.com/tinymce/tinymce/issues/8618
1340
- * and should be removed once that issue is resolved and the tinymce dependency is updated to include it.
1341
- */
1342
-
1343
- /**
1344
- * Sets up selection saving and restoration logic.
1345
- *
1346
- * There are certain actions a user can take when the RCE is not focused that clear the selection inside the
1347
- * editor, such as invoking the Find feature of the browser. If the user then tries to insert content without
1348
- * going back to the editor, the content would be inserted at the top of the RCE, instead of where their cursor
1349
- * was.
1350
- *
1351
- * This method adds logic that saves and restores the selection to work around the issue.
1352
- *
1353
- * @private
1354
- */
1355
- // @ts-expect-error
1356
-
1357
1370
  announceContextToolbars(editor) {
1358
- editor.on('NodeChange', () => {
1371
+ editor.on("NodeChange", () => {
1372
+ if (!this._isMounted) return;
1359
1373
  const node = editor.selection.getNode();
1360
1374
  // @ts-expect-error
1361
1375
  if (isImageEmbed(node, editor)) {
1362
1376
  if (this.announcing !== 1) {
1363
1377
  this.setState({
1364
- announcement: formatMessage('type Control F9 to access image options. {text}', {
1365
- text: node.getAttribute('alt')
1378
+ announcement: formatMessage("type Control F9 to access image options. {text}", {
1379
+ text: node.getAttribute("alt")
1366
1380
  })
1367
1381
  });
1368
1382
  this.announcing = 1;
@@ -1370,7 +1384,7 @@ class RCEWrapper extends React.Component {
1370
1384
  } else if (isFileLink(node, editor)) {
1371
1385
  if (this.announcing !== 2) {
1372
1386
  this.setState({
1373
- announcement: formatMessage('type Control F9 to access link options. {text}', {
1387
+ announcement: formatMessage("type Control F9 to access link options. {text}", {
1374
1388
  text: node.textContent
1375
1389
  })
1376
1390
  });
@@ -1379,7 +1393,7 @@ class RCEWrapper extends React.Component {
1379
1393
  } else if (isElementWithinTable(node)) {
1380
1394
  if (this.announcing !== 3) {
1381
1395
  this.setState({
1382
- announcement: formatMessage('type Control F9 to access table options. {text}', {
1396
+ announcement: formatMessage("type Control F9 to access table options. {text}", {
1383
1397
  text: node.textContent
1384
1398
  })
1385
1399
  });
@@ -1392,14 +1406,21 @@ class RCEWrapper extends React.Component {
1392
1406
  this.announcing = 0;
1393
1407
  }
1394
1408
  });
1409
+ editor.on("ResizeEditor", ({
1410
+ deltaY
1411
+ }) => {
1412
+ if (!this._isMounted || !deltaY) return;
1413
+ if (deltaY < 0) {
1414
+ this.setState({
1415
+ announcement: formatMessage("The height of Rich Content Area is decreased.")
1416
+ });
1417
+ } else {
1418
+ this.setState({
1419
+ announcement: formatMessage("The height of Rich Content Area is increased.")
1420
+ });
1421
+ }
1422
+ });
1395
1423
  }
1396
-
1397
- /* ********** autosave support *************** */
1398
-
1399
- // remove any autosaved value that's too old
1400
-
1401
- // @ts-expect-error
1402
-
1403
1424
  getAutoSaved(key) {
1404
1425
  let autosaved = null;
1405
1426
  try {
@@ -1421,28 +1442,21 @@ class RCEWrapper extends React.Component {
1421
1442
  // This doesn't apply if the editor is off-screen or has visibility:hidden;
1422
1443
  // only if it isn't rendered or has display:none;
1423
1444
  const editorVisible = this.editor.getContainer().offsetParent;
1424
- 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();
1425
1446
  }
1426
1447
  get autoSaveKey() {
1427
- // @ts-expect-error
1428
- const userId = this.props.trayProps?.containingContext.userId;
1448
+ const userId = this._effectiveContainingContext?.userId || "-";
1429
1449
  return `rceautosave:${userId}${window.location.href}:${this.props.textareaId}`;
1430
1450
  }
1431
-
1432
- // @ts-expect-error
1433
-
1434
- /* *********** end autosave support *************** */
1435
-
1436
- // @ts-expect-error
1437
-
1438
1451
  componentWillUnmount() {
1452
+ this._isMounted = false;
1439
1453
  if (this.state.shouldShowEditor) {
1440
1454
  window.clearTimeout(this.blurTimer);
1441
1455
  if (!this._destroyCalled) {
1442
1456
  this.destroy();
1443
1457
  }
1444
1458
  if (this._elementRef.current) {
1445
- this._elementRef.current.removeEventListener('keydown', this.handleKey, true);
1459
+ this._elementRef.current.removeEventListener("keydown", this.handleKey, true);
1446
1460
  }
1447
1461
  this.mutationObserver?.disconnect();
1448
1462
  this.intersectionObserver?.disconnect();
@@ -1454,27 +1468,27 @@ class RCEWrapper extends React.Component {
1454
1468
 
1455
1469
  // @ts-expect-error
1456
1470
  const setupCallback = options.setup;
1457
- const canvasPlugins = rcsExists ? ['instructure_image', 'instructure_documents', 'instructure_equation'] : [];
1471
+ const canvasPlugins = rcsExists ? ["instructure_image", "instructure_documents", "instructure_equation"] : [];
1458
1472
  if (rcsExists && !this.props.instRecordDisabled) {
1459
- canvasPlugins.splice(2, 0, 'instructure_record');
1473
+ canvasPlugins.splice(2, 0, "instructure_record");
1460
1474
  }
1461
- const pastePlugins = rcsExists ? ['instructure_paste', 'paste'] : ['paste'];
1462
- if (rcsExists && this.props.use_rce_icon_maker && this.props.trayProps?.contextType === 'course') {
1463
- 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");
1464
1478
  }
1465
1479
  if (document[FS_ENABLED]) {
1466
- canvasPlugins.push('instructure_fullscreen');
1480
+ canvasPlugins.push("instructure_fullscreen");
1467
1481
  }
1468
1482
  if (this.getRequiredFeatureStatuses().rce_find_replace) {
1469
- canvasPlugins.push('searchreplace');
1470
- canvasPlugins.push('instructure_search_and_replace');
1483
+ canvasPlugins.push("searchreplace");
1484
+ canvasPlugins.push("instructure_search_and_replace");
1471
1485
  }
1472
- 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;
1473
1487
  const wrappedOpts = {
1474
1488
  ...defaultTinymceConfig,
1475
1489
  ...options,
1476
1490
  readonly: this.props.readOnly,
1477
- theme: 'silver',
1491
+ theme: "silver",
1478
1492
  // some older code specified 'modern', which doesn't exist any more
1479
1493
 
1480
1494
  // @ts-expect-error
@@ -1483,7 +1497,7 @@ class RCEWrapper extends React.Component {
1483
1497
  document_base_url: this.props.canvasOrigin,
1484
1498
  block_formats:
1485
1499
  // @ts-expect-error
1486
- 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("; "),
1487
1501
  setup: editor => {
1488
1502
  addKebabIcon(editor);
1489
1503
  editorWrappers.set(editor, this);
@@ -1496,7 +1510,7 @@ class RCEWrapper extends React.Component {
1496
1510
  // @ts-expect-error
1497
1511
  bridge.userLocale = userLocale;
1498
1512
  bridge.canvasOrigin = this.props.canvasOrigin;
1499
- if (typeof setupCallback === 'function') {
1513
+ if (typeof setupCallback === "function") {
1500
1514
  setupCallback(editor);
1501
1515
  }
1502
1516
  },
@@ -1507,7 +1521,7 @@ class RCEWrapper extends React.Component {
1507
1521
  // @ts-expect-error
1508
1522
  content_css: options.content_css || [],
1509
1523
  // @ts-expect-error
1510
- content_style: contentCSS + (options.content_style || ''),
1524
+ content_style: contentCSS + (options.content_style || ""),
1511
1525
  menubar: mergeMenuItems(getMenubarForVariant(this.variant), possibleNewMenubarItems),
1512
1526
  // default menu options listed at https://www.tiny.cloud/docs/configure/editor-appearance/#menu
1513
1527
  // tinymce's default edit and table menus are fine
@@ -1522,10 +1536,10 @@ class RCEWrapper extends React.Component {
1522
1536
  getToolbarForVariant(this.variant, this.ltiToolFavorites),
1523
1537
  // @ts-expect-error
1524
1538
  options.toolbar),
1525
- contextmenu: '',
1539
+ contextmenu: "",
1526
1540
  // show the browser's native context menu
1527
1541
 
1528
- toolbar_mode: 'sliding',
1542
+ toolbar_mode: "sliding",
1529
1543
  toolbar_sticky: true,
1530
1544
  // In regards to the ability to disable plugins:
1531
1545
  // we only have to explicitly manage the removal of plugins
@@ -1534,16 +1548,16 @@ class RCEWrapper extends React.Component {
1534
1548
  // handles all of that complexity. It that ever changes in the
1535
1549
  // future in an upgraded version, we will have to update the
1536
1550
  // logic in those other places as well.
1537
- 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],
1538
1552
  // filter out the plugins designated for removal
1539
1553
  // @ts-expect-error
1540
- 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),
1541
1555
  textpattern_patterns: [{
1542
- start: '* ',
1543
- cmd: 'InsertUnorderedList'
1556
+ start: "* ",
1557
+ cmd: "InsertUnorderedList"
1544
1558
  }, {
1545
- start: '- ',
1546
- cmd: 'InsertUnorderedList'
1559
+ start: "- ",
1560
+ cmd: "InsertUnorderedList"
1547
1561
  }]
1548
1562
  };
1549
1563
  if (this.props.trayProps) {
@@ -1565,7 +1579,7 @@ class RCEWrapper extends React.Component {
1565
1579
  }
1566
1580
  unhandleTextareaChange() {
1567
1581
  if (this._textareaEl) {
1568
- this._textareaEl.removeEventListener('input', this.handleTextareaChange);
1582
+ this._textareaEl.removeEventListener("input", this.handleTextareaChange);
1569
1583
  }
1570
1584
  }
1571
1585
  registerTextareaChange() {
@@ -1573,7 +1587,7 @@ class RCEWrapper extends React.Component {
1573
1587
  if (this._textareaEl !== el) {
1574
1588
  this.unhandleTextareaChange();
1575
1589
  if (el) {
1576
- el.addEventListener('input', this.handleTextareaChange);
1590
+ el.addEventListener("input", this.handleTextareaChange);
1577
1591
  if (this.props.textareaClassName) {
1578
1592
  // split the string on whitespace because classList doesn't let you add multiple
1579
1593
  // space seperated classes at a time but does let you add an array of them
@@ -1584,6 +1598,7 @@ class RCEWrapper extends React.Component {
1584
1598
  }
1585
1599
  }
1586
1600
  componentDidMount() {
1601
+ this._isMounted = true;
1587
1602
  if (this.state.shouldShowEditor) {
1588
1603
  this.editorReallyDidMount();
1589
1604
  } else {
@@ -1598,7 +1613,7 @@ class RCEWrapper extends React.Component {
1598
1613
  // initialize the RCE when it gets close to entering the viewport
1599
1614
  {
1600
1615
  root: null,
1601
- rootMargin: '200px 0px',
1616
+ rootMargin: "200px 0px",
1602
1617
  threshold: 0.0
1603
1618
  });
1604
1619
  // @ts-expect-error
@@ -1617,7 +1632,7 @@ class RCEWrapper extends React.Component {
1617
1632
  this.focusCurrentView();
1618
1633
  }
1619
1634
  if (prevProps.readOnly !== this.props.readOnly) {
1620
- this.mceInstance().mode.set(this.props.readOnly ? 'readonly' : 'design');
1635
+ this.mceInstance().mode.set(this.props.readOnly ? "readonly" : "design");
1621
1636
  }
1622
1637
  }
1623
1638
  }
@@ -1631,7 +1646,7 @@ class RCEWrapper extends React.Component {
1631
1646
  this._tagTinymceAuxDiv();
1632
1647
  this.registerTextareaChange();
1633
1648
  // @ts-expect-error
1634
- this._elementRef.current.addEventListener('keydown', this.handleKey, true);
1649
+ this._elementRef.current.addEventListener("keydown", this.handleKey, true);
1635
1650
  // give the textarea its initial size
1636
1651
  this.onResize(null, {
1637
1652
  deltaY: 0
@@ -1639,7 +1654,7 @@ class RCEWrapper extends React.Component {
1639
1654
  // Preload the LTI Tools modal
1640
1655
  // This helps with loading the favorited external tools
1641
1656
  if (this.ltiToolFavorites.length > 0) {
1642
- import('./plugins/instructure_rce_external_tools/components/ExternalToolSelectionDialog/ExternalToolSelectionDialog');
1657
+ import("./plugins/instructure_rce_external_tools/components/ExternalToolSelectionDialog/ExternalToolSelectionDialog");
1643
1658
  }
1644
1659
  bridge.renderEditor(this);
1645
1660
  }
@@ -1655,20 +1670,15 @@ class RCEWrapper extends React.Component {
1655
1670
  this.mceInstance().hide();
1656
1671
  }
1657
1672
  }
1658
-
1659
- /**
1660
- * Used for reseting the value during tests
1661
- */
1662
-
1663
1673
  renderHtmlEditor() {
1664
1674
  // the div keeps the editor from collapsing while the code editor is downloaded
1665
1675
  return /*#__PURE__*/React.createElement(Suspense, {
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,11 +1714,16 @@ 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
  }
1711
- const statusBarFeatures = getStatusBarFeaturesForVariant(this.variant, this.props.ai_text_tools, tinymce.Env.deviceType.isDesktop());
1721
+ const statusBarOptions = {
1722
+ aiTextTools: this.props.ai_text_tools,
1723
+ isDesktop: tinymce.Env.deviceType.isDesktop(),
1724
+ a11yResizers: !!this.props.features?.rce_a11y_resize
1725
+ };
1726
+ const statusBarFeatures = getStatusBarFeaturesForVariant(this.variant, statusBarOptions);
1712
1727
  return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("style", null, this.style.css), /*#__PURE__*/React.createElement(StoreProvider, {
1713
1728
  jwt: this.props.trayProps?.jwt,
1714
1729
  refreshToken: this.props.trayProps?.refreshToken,
@@ -1725,8 +1740,8 @@ class RCEWrapper extends React.Component {
1725
1740
  // @ts-expect-error
1726
1741
  ,
1727
1742
  ref: this._elementRef,
1728
- style: this.variant === 'full' ? {
1729
- marginBottom: '.5rem'
1743
+ style: this.variant === "full" ? {
1744
+ marginBottom: ".5rem"
1730
1745
  } : undefined,
1731
1746
  onFocus: this.handleFocusRCE,
1732
1747
  onBlur: this.handleBlurRCE
@@ -1734,7 +1749,7 @@ class RCEWrapper extends React.Component {
1734
1749
  id: `show-on-focus-btn-${this.id}`,
1735
1750
  onClick: this.openKBShortcutModal,
1736
1751
  margin: "xx-small",
1737
- screenReaderLabel: formatMessage('View keyboard shortcuts')
1752
+ screenReaderLabel: formatMessage("View keyboard shortcuts")
1738
1753
  // @ts-expect-error
1739
1754
  ,
1740
1755
  ref: el => this._showOnFocusButton = el
@@ -1746,7 +1761,7 @@ class RCEWrapper extends React.Component {
1746
1761
  afterDismiss: this.removeAlert
1747
1762
  }), this.state.editorView === PRETTY_HTML_EDITOR_VIEW && this.renderHtmlEditor(), /*#__PURE__*/React.createElement("div", {
1748
1763
  style: {
1749
- display: this.state.editorView === PRETTY_HTML_EDITOR_VIEW ? 'none' : 'block'
1764
+ display: this.state.editorView === PRETTY_HTML_EDITOR_VIEW ? "none" : "block"
1750
1765
  }
1751
1766
  }, /*#__PURE__*/React.createElement(Editor, {
1752
1767
  id: mceProps.textareaId,
@@ -1796,7 +1811,7 @@ class RCEWrapper extends React.Component {
1796
1811
  disabledPlugins: this.pluginsToExclude,
1797
1812
  features: statusBarFeatures,
1798
1813
  onAI: this.handleAIClick
1799
- }), this.props.trayProps?.containingContext && /*#__PURE__*/React.createElement(CanvasContentTray, Object.assign({
1814
+ }), this._effectiveContainingContext && /*#__PURE__*/React.createElement(CanvasContentTray, Object.assign({
1800
1815
  mountNode: instuiPopupMountNodeFn,
1801
1816
  key: this.id,
1802
1817
  canvasOrigin: this.getCanvasUrl(),
@@ -1805,7 +1820,9 @@ class RCEWrapper extends React.Component {
1805
1820
  onTrayClosing: this.handleContentTrayClosing,
1806
1821
  use_rce_icon_maker: this.props.use_rce_icon_maker
1807
1822
  }, trayProps, {
1823
+ containingContext: this._effectiveContainingContext
1808
1824
  // @ts-expect-error
1825
+ ,
1809
1826
  storeProps: storeProps
1810
1827
  })), /*#__PURE__*/React.createElement(KeyboardShortcutModal, {
1811
1828
  onExited: this.KBShortcutModalExited,
@@ -1837,7 +1854,7 @@ class RCEWrapper extends React.Component {
1837
1854
  })) : null, /*#__PURE__*/React.createElement(Alert, {
1838
1855
  screenReaderOnly: true,
1839
1856
  liveRegion: this.props.liveRegion
1840
- }, this.state.announcement));
1857
+ }, this.state.announcement || null));
1841
1858
  }));
1842
1859
  }
1843
1860
  }
@@ -1845,8 +1862,7 @@ RCEWrapper.propTypes = {
1845
1862
  ai_text_tools: _pt.bool,
1846
1863
  autosave: _pt.shape({
1847
1864
  enabled: _pt.bool,
1848
- maxAge: _pt.number,
1849
- interval: _pt.number
1865
+ maxAge: _pt.number
1850
1866
  }),
1851
1867
  canvasOrigin: _pt.string,
1852
1868
  defaultContent: _pt.string,
@@ -1882,8 +1898,8 @@ RCEWrapper.defaultProps = {
1882
1898
  maxInitRenderedRCEs: -1,
1883
1899
  features: {},
1884
1900
  timezone: Intl?.DateTimeFormat()?.resolvedOptions()?.timeZone,
1885
- canvasOrigin: '',
1886
- variant: 'full'
1901
+ canvasOrigin: "",
1902
+ variant: "full"
1887
1903
  };
1888
1904
  RCEWrapper.skinCssInjected = false;
1889
1905
  export default RCEWrapper;