@instructure/canvas-rce 7.0.0 → 7.2.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 (108) hide show
  1. package/CHANGELOG.md +33 -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/enhance_user_content.js +7 -1
  8. package/es/enhance-user-content/instructure_helper.js +4 -0
  9. package/es/enhance-user-content/youtube_overlay.d.ts +1 -0
  10. package/es/enhance-user-content/youtube_overlay.js +87 -0
  11. package/es/format-message.d.js +1 -0
  12. package/es/format-message.js +5 -0
  13. package/es/index.d.ts +1 -1
  14. package/es/rce/RCE.d.ts +0 -1
  15. package/es/rce/RCE.js +5 -10
  16. package/es/rce/RCEVariants.d.ts +8 -3
  17. package/es/rce/RCEVariants.js +31 -5
  18. package/es/rce/RCEWrapper.d.ts +3 -3
  19. package/es/rce/RCEWrapper.js +66 -57
  20. package/es/rce/RCEWrapperProps.d.ts +1 -1
  21. package/es/rce/ShowOnFocusButton/index.js +4 -2
  22. package/es/rce/StatusBar.js +61 -15
  23. package/es/rce/alertHandler.js +6 -7
  24. package/es/rce/plugins/instructure-ui-icons/plugin.js +2 -2
  25. package/es/rce/plugins/instructure_equation/EquationEditorModal/index.js +6 -10
  26. package/es/rce/plugins/instructure_icon_maker/components/CreateIconMakerForm/ImageSection/Course.d.ts +5 -15
  27. package/es/rce/plugins/instructure_icon_maker/components/CreateIconMakerForm/ImageSection/Course.js +4 -10
  28. package/es/rce/plugins/instructure_image/ImageEmbedOptions.d.ts +7 -0
  29. package/es/rce/plugins/instructure_image/ImageEmbedOptions.js +45 -2
  30. package/es/rce/plugins/instructure_rce_external_tools/components/ExternalToolDialog/ExternalToolDialog.d.ts +1 -8
  31. package/es/rce/plugins/instructure_rce_external_tools/components/ExternalToolDialog/ExternalToolDialog.js +13 -33
  32. package/es/rce/plugins/instructure_rce_external_tools/components/ExternalToolDialog/ExternalToolDialogModal.d.ts +1 -2
  33. package/es/rce/plugins/instructure_rce_external_tools/components/ExternalToolDialog/ExternalToolDialogModal.js +2 -1
  34. package/es/rce/plugins/instructure_record/VideoOptionsTray/index.js +10 -7
  35. package/es/rce/plugins/shared/DimensionsInput/index.js +3 -3
  36. package/es/rce/plugins/shared/FixedContentTray.d.ts +7 -23
  37. package/es/rce/plugins/shared/FixedContentTray.js +7 -16
  38. package/es/rce/plugins/shared/ImageCropper/constants.d.ts +1 -1
  39. package/es/rce/plugins/shared/ImageCropper/constants.js +1 -1
  40. package/es/rce/plugins/shared/ImageCropper/controls/CustomNumberInput.js +1 -1
  41. package/es/rce/plugins/shared/PreviewIcon.js +1 -1
  42. package/es/rce/plugins/shared/ai_tools/aiicons.d.ts +3 -3
  43. package/es/rce/plugins/shared/ai_tools/aiicons.js +11 -11
  44. package/es/rce/plugins/tinymce-a11y-checker/components/checker.js +7 -1
  45. package/es/rce/plugins/tinymce-a11y-checker/utils/indicate.d.ts +1 -1
  46. package/es/rce/plugins/tinymce-a11y-checker/utils/indicate.js +1 -1
  47. package/es/rce/style.js +19 -17
  48. package/es/translations/locales/ar.js +29 -5
  49. package/es/translations/locales/ca.js +32 -8
  50. package/es/translations/locales/cy.js +29 -5
  51. package/es/translations/locales/da-x-k12.js +29 -5
  52. package/es/translations/locales/da.js +29 -5
  53. package/es/translations/locales/de.js +29 -5
  54. package/es/translations/locales/en-AU-x-unimelb.js +29 -5
  55. package/es/translations/locales/en-GB-x-ukhe.js +29 -5
  56. package/es/translations/locales/en.js +17 -5
  57. package/es/translations/locales/en_AU.js +29 -5
  58. package/es/translations/locales/en_CA.js +29 -5
  59. package/es/translations/locales/en_CY.js +29 -5
  60. package/es/translations/locales/en_GB.js +29 -5
  61. package/es/translations/locales/es.js +29 -5
  62. package/es/translations/locales/es_ES.js +29 -5
  63. package/es/translations/locales/fa_IR.js +0 -3
  64. package/es/translations/locales/fi.js +29 -5
  65. package/es/translations/locales/fr.js +29 -5
  66. package/es/translations/locales/fr_CA.js +30 -6
  67. package/es/translations/locales/ga.js +46 -22
  68. package/es/translations/locales/hi.js +29 -5
  69. package/es/translations/locales/ht.js +29 -5
  70. package/es/translations/locales/hu.js +0 -6
  71. package/es/translations/locales/id.js +29 -5
  72. package/es/translations/locales/is.js +23 -5
  73. package/es/translations/locales/it.js +29 -5
  74. package/es/translations/locales/ja.js +29 -5
  75. package/es/translations/locales/mi.js +29 -5
  76. package/es/translations/locales/ms.js +29 -5
  77. package/es/translations/locales/nb-x-k12.js +29 -5
  78. package/es/translations/locales/nb.js +29 -5
  79. package/es/translations/locales/nl.js +29 -5
  80. package/es/translations/locales/nn.js +0 -6
  81. package/es/translations/locales/pl.js +29 -5
  82. package/es/translations/locales/pt.js +29 -5
  83. package/es/translations/locales/pt_BR.js +29 -5
  84. package/es/translations/locales/ru.js +29 -5
  85. package/es/translations/locales/sl.js +29 -5
  86. package/es/translations/locales/sv-x-k12.js +29 -5
  87. package/es/translations/locales/sv.js +29 -5
  88. package/es/translations/locales/th.js +29 -5
  89. package/es/translations/locales/uk_UA.js +0 -6
  90. package/es/translations/locales/vi.js +29 -5
  91. package/es/translations/locales/zh-Hans.js +29 -5
  92. package/es/translations/locales/zh-Hant.js +29 -5
  93. package/es/translations/locales/zh.js +29 -5
  94. package/es/translations/locales/zh_HK.js +29 -5
  95. package/es/util/contextHelper.d.ts +7 -0
  96. package/{testcafe/axe.test.js → es/util/contextHelper.js} +10 -21
  97. package/es/util/loadingPlaceholder.js +11 -11
  98. package/eslint.config.js +3 -25
  99. package/jest/jest-setup.js +27 -2
  100. package/jest.config.js +5 -1
  101. package/package.json +61 -84
  102. package/testcafe/RCEWrapper.test.js +0 -319
  103. package/testcafe/StatusBar.test.js +0 -108
  104. package/testcafe/enhanceUserContent.html +0 -58
  105. package/testcafe/enhanceUserContent.test.js +0 -44
  106. package/testcafe/entry.jsx +0 -77
  107. package/testcafe/testcafe.html +0 -14
  108. package/webpack.testcafe.config.js +0 -61
@@ -0,0 +1,87 @@
1
+ import _pt from "prop-types";
2
+ import React, { useRef, useEffect, useState, useCallback } from 'react';
3
+ import { createRoot } from 'react-dom/client';
4
+ import { Text } from '@instructure/ui-text';
5
+ import { View } from '@instructure/ui-view';
6
+ import { Flex } from '@instructure/ui-flex';
7
+ import { CondensedButton } from '@instructure/ui-buttons';
8
+ import { IconPlayLine } from '@instructure/ui-icons';
9
+ import formatMessage from '../format-message';
10
+ const YoutubeEmbedOverlay = ({
11
+ iframeElement,
12
+ height,
13
+ width
14
+ }) => {
15
+ const [showOverlay, setShowOverlay] = useState(true);
16
+ const containerRef = useRef(null);
17
+ useEffect(() => {
18
+ if (containerRef.current && iframeElement) {
19
+ if (width) {
20
+ containerRef.current.style.width = `${width}px`;
21
+ }
22
+ if (height) {
23
+ containerRef.current.style.height = `${height}px`;
24
+ }
25
+ containerRef.current.appendChild(iframeElement);
26
+ }
27
+ }, [height, iframeElement, width]);
28
+ const handleCloseOverlay = useCallback(() => {
29
+ setShowOverlay(false);
30
+ }, [setShowOverlay]);
31
+ return /*#__PURE__*/React.createElement(View, {
32
+ as: "div",
33
+ position: "relative",
34
+ display: "inline-block"
35
+ }, showOverlay && /*#__PURE__*/React.createElement(View, {
36
+ as: "div",
37
+ position: "absolute",
38
+ width: "100%",
39
+ height: "100%",
40
+ background: "primary",
41
+ themeOverride: {
42
+ backgroundPrimary: 'rgba(10, 71, 91, 0.65)'
43
+ },
44
+ overflowY: "hidden"
45
+ }, /*#__PURE__*/React.createElement(Flex, {
46
+ justifyItems: "center",
47
+ height: "100%"
48
+ }, /*#__PURE__*/React.createElement(Flex.Item, {
49
+ shouldShrink: true,
50
+ shouldGrow: true,
51
+ textAlign: "center"
52
+ }, /*#__PURE__*/React.createElement(Flex, {
53
+ justifyItems: "center",
54
+ margin: "0 0 small"
55
+ }, /*#__PURE__*/React.createElement(Flex.Item, null, /*#__PURE__*/React.createElement(Text, {
56
+ color: "secondary-inverse",
57
+ weight: "bold"
58
+ }, formatMessage('This video may display YouTube ads.')))), /*#__PURE__*/React.createElement(Flex, {
59
+ justifyItems: "center",
60
+ margin: "0 0 medium"
61
+ }, /*#__PURE__*/React.createElement(Flex.Item, null, /*#__PURE__*/React.createElement(CondensedButton, {
62
+ "data-test-id": "youtube-migration-close-overlay",
63
+ color: "primary-inverse",
64
+ onClick: handleCloseOverlay
65
+ }, /*#__PURE__*/React.createElement(IconPlayLine, null), "\xA0", formatMessage('Continue to YouTube content'))))))), /*#__PURE__*/React.createElement("div", {
66
+ ref: containerRef
67
+ }));
68
+ };
69
+ YoutubeEmbedOverlay.propTypes = {
70
+ height: _pt.number.isRequired,
71
+ width: _pt.number.isRequired
72
+ };
73
+ export const createOverlay = iframes => {
74
+ iframes.forEach(iframe => {
75
+ const iframeElement = iframe;
76
+ const height = iframeElement.offsetHeight;
77
+ const width = iframeElement.offsetWidth;
78
+ const container = document.createElement('div');
79
+ container.setAttribute('data-test-id', 'youtube-migration-container');
80
+ iframe.replaceWith(container);
81
+ createRoot(container).render(/*#__PURE__*/React.createElement(YoutubeEmbedOverlay, {
82
+ iframeElement: iframeElement,
83
+ height: height,
84
+ width: width
85
+ }));
86
+ });
87
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -17,7 +17,12 @@
17
17
  */
18
18
 
19
19
  import formatMessage from 'format-message';
20
+ import generateId from 'format-message-generate-id/underscored_crc32';
20
21
  const ns = formatMessage.namespace();
22
+ ns.setup({
23
+ generateId,
24
+ missingTranslation: 'ignore'
25
+ });
21
26
  ns.addLocale = translations => {
22
27
  ns.setup({
23
28
  translations: {
package/es/index.d.ts CHANGED
@@ -16,6 +16,7 @@ export declare const defaultConfiguration: {
16
16
  menubar: undefined;
17
17
  menu: undefined;
18
18
  toolbar: undefined;
19
+ color_map: string[];
19
20
  plugins: undefined;
20
21
  branding: boolean;
21
22
  browser_spellcheck: boolean;
@@ -25,7 +26,6 @@ export declare const defaultConfiguration: {
25
26
  language_load: boolean;
26
27
  language_url: string;
27
28
  toolbar_mode: string;
28
- toolbar_ticky: boolean;
29
29
  mobile: {
30
30
  theme: string;
31
31
  };
package/es/rce/RCE.d.ts CHANGED
@@ -16,7 +16,6 @@ export interface RCEPropTypes {
16
16
  autosave?: {
17
17
  enabled?: boolean;
18
18
  maxAge?: number;
19
- interval?: number;
20
19
  };
21
20
  /**
22
21
  * the protocol://domain:port for this RCE's canvas
package/es/rce/RCE.js CHANGED
@@ -16,7 +16,7 @@
16
16
  * with this program. If not, see <http://www.gnu.org/licenses/>.
17
17
  */
18
18
 
19
- import React, { forwardRef, useState } from 'react';
19
+ import React, { forwardRef, useEffect, useState } from 'react';
20
20
  import formatMessage from '../format-message';
21
21
  import RCEWrapper from './RCEWrapper';
22
22
  import { editorLanguage } from './editorLanguage';
@@ -69,15 +69,10 @@ const RCE = /*#__PURE__*/forwardRef(function RCE(props, rceRef) {
69
69
  locale: normalizeLocale(props.language)
70
70
  });
71
71
  });
72
- const [translations, setTranslations] = useState(() => {
72
+ const [isTranslationLoading, setIsTranslationLoading] = useState(true);
73
+ useEffect(() => {
73
74
  const locale = normalizeLocale(props.language);
74
- const p = getTranslations(locale).then(() => {
75
- setTranslations(true);
76
- }).catch(err => {
77
- console.error('Failed loading the language file for', locale, '\n Cause:', err);
78
- setTranslations(false);
79
- });
80
- return p;
75
+ getTranslations(locale).catch(err => console.error('Failed loading the language file for', locale, '\n Cause:', err)).finally(() => setIsTranslationLoading(false));
81
76
  });
82
77
 
83
78
  // some properties are only used on initialization
@@ -111,7 +106,7 @@ const RCE = /*#__PURE__*/forwardRef(function RCE(props, rceRef) {
111
106
  wrapInitCb(mirroredAttrs || {}, iProps.editorOptions);
112
107
  return iProps;
113
108
  });
114
- if (typeof translations !== 'boolean') {
109
+ if (isTranslationLoading) {
115
110
  return /*#__PURE__*/React.createElement(React.Fragment, null, formatMessage('Loading...'));
116
111
  } else {
117
112
  return /*#__PURE__*/React.createElement(RCEWrapper, Object.assign({
@@ -7,11 +7,16 @@ interface ToolbarGroupSetting {
7
7
  name: string;
8
8
  items: string[];
9
9
  }
10
- type StatusBarFeature = 'ai_tools' | 'keyboard_shortcuts' | 'a11y_checker' | 'word_count' | 'html_view' | 'fullscreen' | 'resize_handle';
11
- export declare const RCEVariantValues: readonly ["full", "lite", "text-only", "text-block"];
10
+ type StatusBarFeature = 'ai_tools' | 'keyboard_shortcuts' | 'a11y_checker' | 'word_count' | 'html_view' | 'fullscreen' | 'resize_handle' | 'a11y_resize_handlers';
11
+ export declare const RCEVariantValues: readonly ["full", "lite", "text-only", "text-block", "block-content-editor"];
12
12
  export type RCEVariant = (typeof RCEVariantValues)[number];
13
+ export type StatusBarOptions = {
14
+ aiTextTools?: boolean;
15
+ isDesktop?: boolean;
16
+ a11yResizers?: boolean;
17
+ };
13
18
  export declare function getMenubarForVariant(variant: RCEVariant): MenuBarSpec;
14
19
  export declare function getMenuForVariant(variant: RCEVariant): MenusSpec;
15
20
  export declare function getToolbarForVariant(variant: RCEVariant, ltiToolFavorites?: string[]): ToolbarGroupSetting[];
16
- export declare function getStatusBarFeaturesForVariant(variant: RCEVariant, aiTextTools?: boolean, isDesktop?: boolean): StatusBarFeature[];
21
+ export declare function getStatusBarFeaturesForVariant(variant: RCEVariant, options?: StatusBarOptions): StatusBarFeature[];
17
22
  export {};
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright (C) 2024 - present Instructure, Inc.
2
+ * Copyright (C) 2025 - present Instructure, Inc.
3
3
  *
4
4
  * This file is part of Canvas.
5
5
  *
@@ -21,7 +21,7 @@ import formatMessage from '../format-message';
21
21
 
22
22
  // copied from node_modules/tinymce/tinymce.d.ts:1187
23
23
 
24
- export const RCEVariantValues = ['full', 'lite', 'text-only', 'text-block'];
24
+ export const RCEVariantValues = ['full', 'lite', 'text-only', 'text-block', 'block-content-editor'];
25
25
  export function getMenubarForVariant(variant) {
26
26
  if (variant === 'full') {
27
27
  return 'edit view insert format tools table';
@@ -101,6 +101,24 @@ export function getToolbarForVariant(variant, ltiToolFavorites = []) {
101
101
  items: ['removeformat', 'instructure_equation']
102
102
  }];
103
103
  }
104
+ if (variant === 'block-content-editor') {
105
+ return [{
106
+ name: formatMessage('Styles'),
107
+ items: ['fontsizeselect', 'formatselect']
108
+ }, {
109
+ name: formatMessage('Formatting'),
110
+ items: ['bold', 'italic', 'underline', 'instructure_color', 'inst_subscript', 'inst_superscript']
111
+ }, {
112
+ name: formatMessage('Content'),
113
+ items: ['instructure_links', 'instructure_documents']
114
+ }, {
115
+ name: formatMessage('Alignment and Lists'),
116
+ items: ['align', 'bullist', 'inst_indent', 'inst_outdent']
117
+ }, {
118
+ name: formatMessage('Miscellaneous'),
119
+ items: ['removeformat', 'instructure_equation']
120
+ }];
121
+ }
104
122
  return [{
105
123
  name: formatMessage('Styles'),
106
124
  items: ['fontsizeselect', 'formatselect']
@@ -124,13 +142,21 @@ export function getToolbarForVariant(variant, ltiToolFavorites = []) {
124
142
  const DESKTOP_FEATURES = ['keyboard_shortcuts', 'a11y_checker', 'word_count'];
125
143
  const MOBILE_FEATURES = ['a11y_checker', 'word_count'];
126
144
  const EXTENDED_FEATURES = ['html_view', 'fullscreen', 'resize_handle'];
127
- export function getStatusBarFeaturesForVariant(variant, aiTextTools = false, isDesktop = true) {
145
+ const A11Y_RESIZERS = ['a11y_resize_handlers'];
146
+ export function getStatusBarFeaturesForVariant(variant, options = {
147
+ aiTextTools: false,
148
+ isDesktop: true,
149
+ a11yResizers: false
150
+ }) {
128
151
  if (variant === 'text-block') {
129
152
  return [];
130
153
  }
131
- const platformFeatures = isDesktop ? DESKTOP_FEATURES : MOBILE_FEATURES;
154
+ if (variant === 'block-content-editor') {
155
+ return ['keyboard_shortcuts', 'word_count'];
156
+ }
157
+ const platformFeatures = options.isDesktop ? DESKTOP_FEATURES : MOBILE_FEATURES;
132
158
  if (variant === 'lite' || variant === 'text-only') {
133
159
  return platformFeatures;
134
160
  }
135
- return [...platformFeatures, ...EXTENDED_FEATURES, ...(aiTextTools ? ['ai_tools'] : [])];
161
+ return [...platformFeatures, ...EXTENDED_FEATURES, ...(options.a11yResizers ? A11Y_RESIZERS : []), ...(options.aiTextTools ? ['ai_tools'] : [])];
136
162
  }
@@ -13,7 +13,6 @@ interface RCEWrapperProps {
13
13
  autosave?: {
14
14
  enabled?: boolean;
15
15
  maxAge?: number;
16
- interval?: number;
17
16
  };
18
17
  canvasOrigin: string;
19
18
  defaultContent?: string;
@@ -75,6 +74,7 @@ declare class RCEWrapper extends React.Component<RCEWrapperProps, RCEWrapperStat
75
74
  _showOnFocusButton?: HTMLElement;
76
75
  _statusBarId: string;
77
76
  _textareaEl?: HTMLTextAreaElement;
77
+ _effectiveContainingContext: RCETrayProps['containingContext'];
78
78
  AIToolsTray?: ReactNode;
79
79
  editor: TinyMCEEditor | null;
80
80
  initialContent?: string;
@@ -191,7 +191,7 @@ declare class RCEWrapper extends React.Component<RCEWrapperProps, RCEWrapperStat
191
191
  maxMruTools: import("prop-types").Requireable<number>;
192
192
  }>>;
193
193
  ai_text_tools: import("prop-types").Requireable<boolean>;
194
- variant: import("prop-types").Requireable<"full" | "lite" | "text-only" | "text-block">;
194
+ variant: import("prop-types").Requireable<"full" | "lite" | "text-only" | "text-block" | "block-content-editor">;
195
195
  };
196
196
  static defaultProps: {
197
197
  trayProps: null;
@@ -377,13 +377,13 @@ declare class RCEWrapper extends React.Component<RCEWrapperProps, RCEWrapperStat
377
377
  auto_focus: boolean;
378
378
  body_class: string;
379
379
  directionality: string;
380
+ color_map: string[];
380
381
  branding: boolean;
381
382
  browser_spellcheck: boolean;
382
383
  convert_urls: boolean;
383
384
  font_formats: string;
384
385
  language_load: boolean;
385
386
  language_url: string;
386
- toolbar_ticky: boolean;
387
387
  mobile: {
388
388
  theme: string;
389
389
  };
@@ -65,6 +65,7 @@ import { getMenubarForVariant, getMenuForVariant, getToolbarForVariant, getStatu
65
65
  import { focusFirstMenuButton, focusToolbar, isElementWithinTable, mergeMenu, mergeMenuItems, mergePlugins, mergeToolbar, parsePluginsToExclude, patchAutosavedContent } from './RCEWrapper.utils';
66
66
  import { externalToolsForToolbar } from './plugins/instructure_rce_external_tools/util/externalToolsForToolbar';
67
67
  import { initScreenreaderOnFormat } from './screenreaderOnFormat';
68
+ import { normalizeContainingContext } from '../util/contextHelper';
68
69
  const RestoreAutoSaveModal = /*#__PURE__*/React.lazy(() => import('./RestoreAutoSaveModal'));
69
70
  const RceHtmlEditor = /*#__PURE__*/React.lazy(() => import('./RceHtmlEditor'));
70
71
  const ASYNC_FOCUS_TIMEOUT = 250;
@@ -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;
@@ -209,6 +211,7 @@ 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
@@ -239,6 +242,7 @@ class RCEWrapper extends React.Component {
239
242
  this.handleFocusRCE = () => {
240
243
  this.handleFocus();
241
244
  };
245
+ // @ts-expect-error
242
246
  this.handleBlurRCE = event => {
243
247
  if (event.relatedTarget === null) {
244
248
  // focus might be moving to tinymce
@@ -373,6 +377,12 @@ class RCEWrapper extends React.Component {
373
377
  });
374
378
  }
375
379
  };
380
+ /**
381
+ * Fix keyboard navigation in the expanded toolbar
382
+ *
383
+ * NOTE: This is a workaround for https://github.com/tinymce/tinymce/issues/8618
384
+ * and should be removed once that issue is resolved and the tinymce dependency is updated to include it.
385
+ */
376
386
  this.fixToolbarKeyboardNavigation = () => {
377
387
  // The keyboard navigation config in tinymce for the expanded toolbar is incorrectly configured,
378
388
  // and stops at [data-alloy-tabstop] elements.
@@ -382,6 +392,19 @@ class RCEWrapper extends React.Component {
382
392
  // in https://github.com/tinymce/tinymce/blob/develop/modules/alloy/src/main/ts/ephox/alloy/ui/schema/SplitSlidingToolbarSchema.ts
383
393
  this._elementRef.current?.querySelectorAll('.tox-toolbar-overlord button[data-alloy-tabstop]').forEach(it => it.removeAttribute('data-alloy-tabstop'));
384
394
  };
395
+ /**
396
+ * Sets up selection saving and restoration logic.
397
+ *
398
+ * There are certain actions a user can take when the RCE is not focused that clear the selection inside the
399
+ * editor, such as invoking the Find feature of the browser. If the user then tries to insert content without
400
+ * going back to the editor, the content would be inserted at the top of the RCE, instead of where their cursor
401
+ * was.
402
+ *
403
+ * This method adds logic that saves and restores the selection to work around the issue.
404
+ *
405
+ * @private
406
+ */
407
+ // @ts-expect-error
385
408
  this._setupSelectionSaving = editor => {
386
409
  // @ts-expect-error
387
410
  let savedSelection = null;
@@ -440,6 +463,7 @@ class RCEWrapper extends React.Component {
440
463
  });
441
464
  };
442
465
  this.announcing = 0;
466
+ /* ********** autosave support *************** */
443
467
  this.initAutoSave = editor => {
444
468
  var _this$props$userCache;
445
469
  this.storage = new EncryptedStorage((_this$props$userCache = this.props.userCacheKey) !== null && _this$props$userCache !== void 0 ? _this$props$userCache : '');
@@ -474,6 +498,7 @@ class RCEWrapper extends React.Component {
474
498
  }
475
499
  }
476
500
  };
501
+ // remove any autosaved value that's too old
477
502
  this.cleanupAutoSave = (deleteAll = false) => {
478
503
  if (this.storage) {
479
504
  const expiry = deleteAll ? Date.now() : Date.now() - (this.props.autosave?.maxAge || 0);
@@ -489,6 +514,7 @@ class RCEWrapper extends React.Component {
489
514
  }
490
515
  }
491
516
  };
517
+ // @ts-expect-error
492
518
  this.restoreAutoSave = ans => {
493
519
  this.setState({
494
520
  confirmAutoSave: false
@@ -503,6 +529,7 @@ class RCEWrapper extends React.Component {
503
529
  // let the content be restored
504
530
  debounce(this.checkAccessibility, 1000)();
505
531
  };
532
+ // @ts-expect-error
506
533
  this.doAutoSave = (e, retry = false) => {
507
534
  if (this.storage) {
508
535
  const editor = this.mceInstance();
@@ -527,6 +554,7 @@ class RCEWrapper extends React.Component {
527
554
  }
528
555
  }
529
556
  };
557
+ /* *********** end autosave support *************** */
530
558
  this.onWordCountUpdate = e => {
531
559
  if (!this.editor) return;
532
560
  const shouldIgnore = countShouldIgnore(this.editor, 'body', 'words');
@@ -539,6 +567,7 @@ class RCEWrapper extends React.Component {
539
567
  } else return null;
540
568
  });
541
569
  };
570
+ // @ts-expect-error
542
571
  this.onNodeChange = e => {
543
572
  // This is basically copied out of the tinymce silver theme code for the status bar
544
573
  const path = e.parents.filter(p => p.nodeName !== 'BR' && !p.getAttribute('data-mce-bogus') && p.getAttribute('data-mce-type') !== 'bookmark')
@@ -574,7 +603,9 @@ class RCEWrapper extends React.Component {
574
603
  height: newHeight
575
604
  });
576
605
  // play nice and send the same event that the silver theme would send
577
- editor.fire('ResizeEditor');
606
+ editor.fire('ResizeEditor', {
607
+ deltaY: coordinates.deltaY
608
+ });
578
609
  }
579
610
  };
580
611
  this.onA11yChecker = triggerElementId => {
@@ -715,6 +746,9 @@ class RCEWrapper extends React.Component {
715
746
  };
716
747
  });
717
748
  };
749
+ /**
750
+ * Used for reseting the value during tests
751
+ */
718
752
  this.resetAlertId = () => {
719
753
  if (this.state.messages.length > 0) {
720
754
  throw new Error('There are messages currently, you cannot reset when they are non-zero');
@@ -806,6 +840,7 @@ class RCEWrapper extends React.Component {
806
840
  this._handleFullscreenResize();
807
841
  });
808
842
  this.AIToolsTray = undefined;
843
+ this._effectiveContainingContext = normalizeContainingContext(this.props.trayProps?.containingContext);
809
844
  }
810
845
 
811
846
  // when the RCE is put into fullscreen we need to move the div
@@ -1095,8 +1130,7 @@ class RCEWrapper extends React.Component {
1095
1130
  if (this.editor) {
1096
1131
  return this.editor;
1097
1132
  }
1098
- const editors = this.props.tinymce.editors || [];
1099
- return editors.filter(ed => ed.id === this.props.textareaId)[0];
1133
+ return this.props.tinymce.get(this.props.textareaId);
1100
1134
  }
1101
1135
 
1102
1136
  // @ts-expect-error
@@ -1171,9 +1205,6 @@ class RCEWrapper extends React.Component {
1171
1205
  document[FS_EXIT]();
1172
1206
  }
1173
1207
  }
1174
-
1175
- // @ts-expect-error
1176
-
1177
1208
  _getStatusBarHeight() {
1178
1209
  // the height prop is the height of the editor and does not include
1179
1210
  // the status bar. we'll need this later.
@@ -1319,9 +1350,6 @@ class RCEWrapper extends React.Component {
1319
1350
  }, ASYNC_FOCUS_TIMEOUT);
1320
1351
  }
1321
1352
  }
1322
-
1323
- // @ts-expect-error
1324
-
1325
1353
  // @ts-expect-error
1326
1354
  call(methodName, ...args) {
1327
1355
  // since exists? has a ? and cant be a regular function just return true
@@ -1332,28 +1360,6 @@ class RCEWrapper extends React.Component {
1332
1360
  // @ts-expect-error
1333
1361
  return this[methodName](...args);
1334
1362
  }
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
1363
  announceContextToolbars(editor) {
1358
1364
  editor.on('NodeChange', () => {
1359
1365
  const node = editor.selection.getNode();
@@ -1392,14 +1398,21 @@ class RCEWrapper extends React.Component {
1392
1398
  this.announcing = 0;
1393
1399
  }
1394
1400
  });
1401
+ editor.on('ResizeEditor', ({
1402
+ deltaY
1403
+ }) => {
1404
+ if (!deltaY) return;
1405
+ if (deltaY < 0) {
1406
+ this.setState({
1407
+ announcement: formatMessage('The height of Rich Content Area is decreased.')
1408
+ });
1409
+ } else {
1410
+ this.setState({
1411
+ announcement: formatMessage('The height of Rich Content Area is increased.')
1412
+ });
1413
+ }
1414
+ });
1395
1415
  }
1396
-
1397
- /* ********** autosave support *************** */
1398
-
1399
- // remove any autosaved value that's too old
1400
-
1401
- // @ts-expect-error
1402
-
1403
1416
  getAutoSaved(key) {
1404
1417
  let autosaved = null;
1405
1418
  try {
@@ -1424,17 +1437,9 @@ class RCEWrapper extends React.Component {
1424
1437
  return this.props.autosave?.enabled && editorVisible && document.querySelectorAll('.rce-wrapper').length === 1 && storageAvailable();
1425
1438
  }
1426
1439
  get autoSaveKey() {
1427
- // @ts-expect-error
1428
- const userId = this.props.trayProps?.containingContext.userId;
1440
+ const userId = this._effectiveContainingContext?.userId || '-';
1429
1441
  return `rceautosave:${userId}${window.location.href}:${this.props.textareaId}`;
1430
1442
  }
1431
-
1432
- // @ts-expect-error
1433
-
1434
- /* *********** end autosave support *************** */
1435
-
1436
- // @ts-expect-error
1437
-
1438
1443
  componentWillUnmount() {
1439
1444
  if (this.state.shouldShowEditor) {
1440
1445
  window.clearTimeout(this.blurTimer);
@@ -1655,11 +1660,6 @@ class RCEWrapper extends React.Component {
1655
1660
  this.mceInstance().hide();
1656
1661
  }
1657
1662
  }
1658
-
1659
- /**
1660
- * Used for reseting the value during tests
1661
- */
1662
-
1663
1663
  renderHtmlEditor() {
1664
1664
  // the div keeps the editor from collapsing while the code editor is downloaded
1665
1665
  return /*#__PURE__*/React.createElement(Suspense, {
@@ -1708,7 +1708,12 @@ class RCEWrapper extends React.Component {
1708
1708
  }
1709
1709
  });
1710
1710
  }
1711
- const statusBarFeatures = getStatusBarFeaturesForVariant(this.variant, this.props.ai_text_tools, tinymce.Env.deviceType.isDesktop());
1711
+ const statusBarOptions = {
1712
+ aiTextTools: this.props.ai_text_tools,
1713
+ isDesktop: tinymce.Env.deviceType.isDesktop(),
1714
+ a11yResizers: !!this.props.features?.rce_a11y_resize
1715
+ };
1716
+ const statusBarFeatures = getStatusBarFeaturesForVariant(this.variant, statusBarOptions);
1712
1717
  return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("style", null, this.style.css), /*#__PURE__*/React.createElement(StoreProvider, {
1713
1718
  jwt: this.props.trayProps?.jwt,
1714
1719
  refreshToken: this.props.trayProps?.refreshToken,
@@ -1796,7 +1801,7 @@ class RCEWrapper extends React.Component {
1796
1801
  disabledPlugins: this.pluginsToExclude,
1797
1802
  features: statusBarFeatures,
1798
1803
  onAI: this.handleAIClick
1799
- }), this.props.trayProps?.containingContext && /*#__PURE__*/React.createElement(CanvasContentTray, Object.assign({
1804
+ }), this._effectiveContainingContext && /*#__PURE__*/React.createElement(CanvasContentTray, Object.assign({
1800
1805
  mountNode: instuiPopupMountNodeFn,
1801
1806
  key: this.id,
1802
1807
  canvasOrigin: this.getCanvasUrl(),
@@ -1805,7 +1810,9 @@ class RCEWrapper extends React.Component {
1805
1810
  onTrayClosing: this.handleContentTrayClosing,
1806
1811
  use_rce_icon_maker: this.props.use_rce_icon_maker
1807
1812
  }, trayProps, {
1813
+ containingContext: this._effectiveContainingContext
1808
1814
  // @ts-expect-error
1815
+ ,
1809
1816
  storeProps: storeProps
1810
1817
  })), /*#__PURE__*/React.createElement(KeyboardShortcutModal, {
1811
1818
  onExited: this.KBShortcutModalExited,
@@ -1834,7 +1841,10 @@ class RCEWrapper extends React.Component {
1834
1841
  open: this.state.confirmAutoSave,
1835
1842
  onNo: () => this.restoreAutoSave(false),
1836
1843
  onYes: () => this.restoreAutoSave(true)
1837
- })) : null, /*#__PURE__*/React.createElement(Alert, {
1844
+ })) : null, this.state.announcement &&
1845
+ /*#__PURE__*/
1846
+ // @ts-expect-error
1847
+ React.createElement(Alert, {
1838
1848
  screenReaderOnly: true,
1839
1849
  liveRegion: this.props.liveRegion
1840
1850
  }, this.state.announcement));
@@ -1845,8 +1855,7 @@ RCEWrapper.propTypes = {
1845
1855
  ai_text_tools: _pt.bool,
1846
1856
  autosave: _pt.shape({
1847
1857
  enabled: _pt.bool,
1848
- maxAge: _pt.number,
1849
- interval: _pt.number
1858
+ maxAge: _pt.number
1850
1859
  }),
1851
1860
  canvasOrigin: _pt.string,
1852
1861
  defaultContent: _pt.string,
@@ -170,6 +170,6 @@ export declare const rceWrapperPropTypes: {
170
170
  maxMruTools: PropTypes.Requireable<number>;
171
171
  }>>;
172
172
  ai_text_tools: PropTypes.Requireable<boolean>;
173
- variant: PropTypes.Requireable<"full" | "lite" | "text-only" | "text-block">;
173
+ variant: PropTypes.Requireable<"full" | "lite" | "text-only" | "text-block" | "block-content-editor">;
174
174
  };
175
175
  export type RCEWrapperProps = PropTypes.InferProps<typeof rceWrapperPropTypes>;
@@ -26,8 +26,10 @@ import React, { Component } from 'react';
26
26
  import { func, node, oneOfType, string } from 'prop-types';
27
27
  import { IconButton } from '@instructure/ui-buttons';
28
28
  const hideStyle = {
29
- position: 'absolute',
30
- left: '-9999px'
29
+ opacity: 0,
30
+ width: 0,
31
+ height: 0,
32
+ overflow: 'hidden'
31
33
  };
32
34
  export default class ShowOnFocusButton extends Component {
33
35
  constructor(...args) {