@instructure/canvas-rce 7.3.1 → 8.1.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 (155) hide show
  1. package/CHANGELOG.md +70 -0
  2. package/{es/rce/plugins/shared/ai_tools/index.js → __mocks__/@instructure/ui-media-player/_mockUiMediaPlayer.js} +4 -4
  3. package/__tests__/common/mimeClass.test.js +25 -1
  4. package/__tests__/rcs/api.test.js +280 -251
  5. package/es/canvasFileBrowser/FileBrowser.d.ts +2 -2
  6. package/es/canvasFileBrowser/FileBrowser.js +8 -7
  7. package/es/common/mimeClass.js +3 -1
  8. package/es/defaultTinymceConfig.js +47 -49
  9. package/es/enhance-user-content/enhance_user_content.js +6 -8
  10. package/es/enhance-user-content/index.d.ts +3 -1
  11. package/es/enhance-user-content/index.js +3 -1
  12. package/es/enhance-user-content/youtube_overlay.js +18 -0
  13. package/es/getThemeVars.d.ts +1 -1
  14. package/es/getThemeVars.js +23 -26
  15. package/es/rce/KeyboardShortcutModal.js +1 -1
  16. package/es/rce/RCE.d.ts +9 -0
  17. package/es/rce/RCE.js +4 -0
  18. package/es/rce/RCEGlobals.d.ts +2 -0
  19. package/es/rce/RCEGlobals.js +1 -0
  20. package/es/rce/RCEVariants.d.ts +1 -2
  21. package/es/rce/RCEVariants.js +1 -2
  22. package/es/rce/RCEWrapper.d.ts +6 -16
  23. package/es/rce/RCEWrapper.js +18 -87
  24. package/es/rce/RCEWrapper.utils.d.ts +1 -1
  25. package/es/rce/RCEWrapperProps.d.ts +2 -1
  26. package/es/rce/RCEWrapperProps.js +2 -1
  27. package/es/rce/StatusBar.d.ts +0 -1
  28. package/es/rce/StatusBar.js +3 -28
  29. package/es/rce/plugins/instructure_equation/EquationEditorModal/advancedOnlySyntax.d.ts +2 -1
  30. package/es/rce/plugins/instructure_equation/EquationEditorModal/advancedOnlySyntax.js +3 -1
  31. package/es/rce/plugins/instructure_equation/EquationEditorModal/index.d.ts +1 -0
  32. package/es/rce/plugins/instructure_equation/EquationEditorModal/index.js +12 -2
  33. package/es/rce/plugins/instructure_icon_maker/components/CreateIconMakerForm/ImageSection/ImageOptions.js +2 -2
  34. package/es/rce/plugins/instructure_icon_maker/components/CreateIconMakerForm/ImageSection/ImageSection.js +3 -3
  35. package/es/rce/plugins/instructure_icon_maker/svg/constants.d.ts +20 -5
  36. package/es/rce/plugins/instructure_icon_maker/svg/utils.d.ts +1 -1
  37. package/es/rce/plugins/instructure_icon_maker/utils/IconMakerFormHasChanges.js +2 -2
  38. package/es/rce/plugins/instructure_image/ImageEmbedOptions.d.ts +0 -2
  39. package/es/rce/plugins/instructure_image/ImageEmbedOptions.js +2 -9
  40. package/es/rce/plugins/instructure_paste/plugin.js +18 -12
  41. package/es/rce/plugins/instructure_rce_external_tools/components/ExternalToolDialog/ExternalToolDialogModal.d.ts +1 -1
  42. package/es/rce/plugins/instructure_rce_external_tools/components/util/ToolLaunchIframe.d.ts +4 -0
  43. package/es/rce/plugins/instructure_rce_external_tools/components/util/ToolLaunchIframe.js +4 -0
  44. package/es/rce/plugins/instructure_record/AudioOptionsTray/TrayController.d.ts +11 -2
  45. package/es/rce/plugins/instructure_record/AudioOptionsTray/TrayController.js +92 -10
  46. package/es/rce/plugins/instructure_record/AudioOptionsTray/index.d.ts +13 -1
  47. package/es/rce/plugins/instructure_record/AudioOptionsTray/index.js +216 -24
  48. package/es/rce/plugins/instructure_record/MediaPanel/index.js +16 -5
  49. package/es/rce/plugins/instructure_record/VideoOptionsTray/TrayController.d.ts +14 -13
  50. package/es/rce/plugins/instructure_record/VideoOptionsTray/TrayController.js +110 -39
  51. package/es/rce/plugins/instructure_record/VideoOptionsTray/index.d.ts +11 -1
  52. package/es/rce/plugins/instructure_record/VideoOptionsTray/index.js +242 -67
  53. package/es/rce/plugins/instructure_record/clickCallback.js +19 -4
  54. package/es/rce/plugins/instructure_record/mediaTranslations.js +1 -1
  55. package/es/rce/plugins/instructure_record/playerLayoutOptions.d.ts +25 -0
  56. package/es/rce/plugins/instructure_record/playerLayoutOptions.js +91 -0
  57. package/es/rce/plugins/instructure_record/plugin.js +2 -5
  58. package/es/rce/plugins/instructure_record/utils.d.ts +3 -0
  59. package/es/rce/plugins/instructure_record/utils.js +31 -0
  60. package/es/rce/plugins/instructure_studio_media_options/plugin.js +82 -26
  61. package/es/rce/plugins/shared/ContentSelection.d.ts +6 -1
  62. package/es/rce/plugins/shared/ContentSelection.js +15 -6
  63. package/es/rce/plugins/shared/DimensionsInput/DimensionInput.js +1 -2
  64. package/es/rce/plugins/shared/DimensionsInput/index.js +11 -12
  65. package/es/rce/plugins/shared/DimensionsInput/useDimensionsState.d.ts +1 -1
  66. package/es/rce/plugins/shared/DimensionsInput/useDimensionsState.js +4 -3
  67. package/es/rce/plugins/shared/StudioLtiSupportUtils.d.ts +27 -6
  68. package/es/rce/plugins/shared/StudioLtiSupportUtils.js +82 -13
  69. package/es/rce/plugins/shared/Upload/UploadFile.js +1 -8
  70. package/es/rce/style.d.ts +2 -1
  71. package/es/rce/style.js +4 -2
  72. package/es/rcs/api.d.ts +5 -10
  73. package/es/rcs/api.js +15 -21
  74. package/es/rcs/fake.d.ts +1 -7
  75. package/es/rcs/fake.js +1 -47
  76. package/es/sidebar/actions/media.d.ts +19 -6
  77. package/es/sidebar/actions/media.js +17 -4
  78. package/es/sidebar/actions/upload.d.ts +3 -3
  79. package/es/sidebar/actions/upload.js +9 -9
  80. package/es/sidebar/containers/Sidebar.js +0 -2
  81. package/es/sidebar/containers/sidebarHandlers.d.ts +2 -4
  82. package/es/sidebar/containers/sidebarHandlers.js +2 -5
  83. package/es/sidebar/reducers/index.d.ts +0 -1
  84. package/es/sidebar/reducers/index.js +0 -2
  85. package/es/sidebar/store/initialState.d.ts +0 -1
  86. package/es/sidebar/store/initialState.js +0 -5
  87. package/es/translations/locales/ar.js +65 -80
  88. package/es/translations/locales/ca.js +65 -80
  89. package/es/translations/locales/cy.js +65 -80
  90. package/es/translations/locales/da-x-k12.js +65 -80
  91. package/es/translations/locales/da.js +65 -80
  92. package/es/translations/locales/de.js +65 -80
  93. package/es/translations/locales/el.js +0 -9
  94. package/es/translations/locales/en-AU-x-unimelb.js +65 -80
  95. package/es/translations/locales/en-GB-x-ukhe.js +65 -80
  96. package/es/translations/locales/en.js +61 -79
  97. package/es/translations/locales/en_AU.js +65 -80
  98. package/es/translations/locales/en_CA.js +65 -80
  99. package/es/translations/locales/en_CY.js +65 -80
  100. package/es/translations/locales/en_GB.js +65 -80
  101. package/es/translations/locales/es.js +65 -80
  102. package/es/translations/locales/es_ES.js +65 -80
  103. package/es/translations/locales/fa_IR.js +0 -9
  104. package/es/translations/locales/fi.js +65 -80
  105. package/es/translations/locales/fr.js +65 -80
  106. package/es/translations/locales/fr_CA.js +65 -80
  107. package/es/translations/locales/ga.js +65 -80
  108. package/es/translations/locales/he.js +0 -9
  109. package/es/translations/locales/hi.js +65 -80
  110. package/es/translations/locales/ht.js +65 -80
  111. package/es/translations/locales/hu.js +0 -36
  112. package/es/translations/locales/hy.js +0 -9
  113. package/es/translations/locales/id.js +65 -80
  114. package/es/translations/locales/is.js +65 -80
  115. package/es/translations/locales/it.js +65 -80
  116. package/es/translations/locales/ja.js +65 -80
  117. package/es/translations/locales/ko.js +2455 -133
  118. package/es/translations/locales/mi.js +65 -80
  119. package/es/translations/locales/ms.js +65 -80
  120. package/es/translations/locales/nb-x-k12.js +65 -80
  121. package/es/translations/locales/nb.js +65 -80
  122. package/es/translations/locales/nl.js +66 -81
  123. package/es/translations/locales/nn.js +0 -36
  124. package/es/translations/locales/pl.js +65 -80
  125. package/es/translations/locales/pt.js +65 -80
  126. package/es/translations/locales/pt_BR.js +65 -80
  127. package/es/translations/locales/ru.js +65 -80
  128. package/es/translations/locales/sl.js +65 -80
  129. package/es/translations/locales/sv-x-k12.js +65 -80
  130. package/es/translations/locales/sv.js +65 -80
  131. package/es/translations/locales/th.js +65 -80
  132. package/es/translations/locales/tr.js +1962 -18
  133. package/es/translations/locales/uk_UA.js +0 -9
  134. package/es/translations/locales/vi.js +65 -80
  135. package/es/translations/locales/zh-Hans.js +65 -80
  136. package/es/translations/locales/zh-Hant.js +65 -80
  137. package/es/translations/locales/zh.js +65 -80
  138. package/es/translations/locales/zh_HK.js +65 -80
  139. package/eslint.config.js +16 -147
  140. package/jest/jest-setup.js +1 -0
  141. package/jest.config.js +2 -0
  142. package/oxlint.json +84 -0
  143. package/package.json +87 -62
  144. package/tsconfig.json +3 -2
  145. package/es/rce/plugins/shared/ai_tools/AIResponseModal.d.ts +0 -10
  146. package/es/rce/plugins/shared/ai_tools/AIResponseModal.js +0 -67
  147. package/es/rce/plugins/shared/ai_tools/AIToolsTray.d.ts +0 -18
  148. package/es/rce/plugins/shared/ai_tools/AIToolsTray.js +0 -489
  149. package/es/rce/plugins/shared/ai_tools/aiicons.d.ts +0 -7
  150. package/es/rce/plugins/shared/ai_tools/aiicons.js +0 -60
  151. package/es/rce/plugins/shared/ai_tools/index.d.ts +0 -3
  152. package/es/sidebar/actions/flickr.d.ts +0 -20
  153. package/es/sidebar/actions/flickr.js +0 -60
  154. package/es/sidebar/reducers/flickr.d.ts +0 -1
  155. package/es/sidebar/reducers/flickr.js +0 -49
@@ -20,7 +20,7 @@ import _pt from "prop-types";
20
20
  import React, { Suspense } from 'react';
21
21
  import { Editor } from '@tinymce/tinymce-react';
22
22
  import tinymce from 'tinymce';
23
- import _ from 'lodash';
23
+ import { uniqBy } from 'es-toolkit/compat';
24
24
  import { StoreProvider } from './plugins/shared/StoreContext';
25
25
  import { IconKeyboardShortcutsLine } from '@instructure/ui-icons';
26
26
  import { Alert } from '@instructure/ui-alerts';
@@ -144,7 +144,6 @@ class RCEWrapper extends React.Component {
144
144
  this._statusBarId = void 0;
145
145
  this._textareaEl = void 0;
146
146
  this._effectiveContainingContext = void 0;
147
- this.AIToolsTray = void 0;
148
147
  this.editor = void 0;
149
148
  this.initialContent = void 0;
150
149
  this.intersectionObserver = void 0;
@@ -332,6 +331,13 @@ class RCEWrapper extends React.Component {
332
331
  tinyapp.setAttribute('aria-label', formatMessage('Rich Content Editor'));
333
332
  tinyapp.setAttribute('role', 'document');
334
333
  tinyapp.setAttribute('tabIndex', '-1');
334
+
335
+ // tinyMCE browser detection is wrong for Edge tinymce.Env.browser.isEdge
336
+ const isEdge = /edg/i.test(navigator.userAgent);
337
+ if (isEdge) {
338
+ // Edge translation service breaks the editor
339
+ tinyapp.setAttribute('translate', 'no');
340
+ }
335
341
  }
336
342
 
337
343
  // remove role="aplication" attribute from the iframe body
@@ -512,7 +518,6 @@ class RCEWrapper extends React.Component {
512
518
  if (autosavedContent !== editorContent) {
513
519
  this.setState({
514
520
  confirmAutoSave: true,
515
- // @ts-expect-error
516
521
  autoSavedContent: patchAutosavedContent(autosaved.content)
517
522
  });
518
523
  } else {
@@ -691,64 +696,6 @@ class RCEWrapper extends React.Component {
691
696
  this.state.KBShortcutFocusReturn?.focus();
692
697
  }
693
698
  };
694
- this.handleAIClick = () => {
695
- import('./plugins/shared/ai_tools').then(module => {
696
- // @ts-expect-error
697
- this.AIToolsTray = module.AIToolsTray;
698
- this.setState({
699
- AIToolsOpen: true,
700
- AITToolsFocusReturn: document.activeElement
701
- });
702
- }).catch(ex => {
703
- console.error('Failed loading the AIToolsTray', ex);
704
- });
705
- };
706
- this.closeAITools = () => {
707
- this.setState({
708
- AIToolsOpen: false
709
- });
710
- };
711
- this.AIToolsExited = () => {
712
- if (this.state.AITToolsFocusReturn === this.iframe) {
713
- // launched using a kb shortcut
714
- // the iframe has focus so we need to forward it on to tinymce editor
715
- if (this.editor) {
716
- this.editor.focus(false);
717
- }
718
- } else if (this.state.AITToolsFocusReturn === document.getElementById(`show-on-focus-btn-${this.id}`)) {
719
- // launched from showOnFocus button
720
- // edge case where focusing KBShortcutFocusReturn doesn't work
721
- this._showOnFocusButton?.focus();
722
- } else {
723
- // launched from kb shortcut button on status bar
724
- // @ts-expect-error
725
- this.state.AITToolsFocusReturn?.focus();
726
- }
727
- };
728
- this.handleInsertAIContent = content => {
729
- const editor = this.mceInstance();
730
- contentInsertion.insertContent(editor, content);
731
- };
732
- this.handleReplaceAIContent = content => {
733
- const ed = this.mceInstance();
734
- const selection = ed.selection;
735
- if (selection.getContent().length > 0) {
736
- selection.setContent(content);
737
- } else {
738
- ed.selection.select(ed.getBody(), true);
739
- selection.setContent(content);
740
- }
741
- };
742
- this.getCurrentContentForAI = () => {
743
- const selected = this.mceInstance().selection.getContent();
744
- return selected ? {
745
- type: 'selection',
746
- content: selected
747
- } : {
748
- type: 'full',
749
- content: this.mceInstance().getContent()
750
- };
751
- };
752
699
  this.handleTextareaChange = () => {
753
700
  if (this.isHidden()) {
754
701
  this.setCode(this.textareaValue());
@@ -760,7 +707,7 @@ class RCEWrapper extends React.Component {
760
707
  alert.id = alertIdValue++;
761
708
  this.setState(state => {
762
709
  let messages = state.messages.concat(alert);
763
- messages = _.uniqBy(messages, 'text'); // Don't show the same message twice
710
+ messages = uniqBy(messages, 'text'); // Don't show the same message twice
764
711
  return {
765
712
  messages
766
713
  };
@@ -783,7 +730,7 @@ class RCEWrapper extends React.Component {
783
730
  }
784
731
  alertIdValue = 0;
785
732
  };
786
- this.style = buildStyle();
733
+ this.style = buildStyle(!!props.useHighContrast, props.fontFamily);
787
734
 
788
735
  // Set up some limited global state that can be referenced
789
736
  // as needed in RCE's components and function / plugin definitions
@@ -842,8 +789,7 @@ class RCEWrapper extends React.Component {
842
789
  prevHeight: _ht
843
790
  },
844
791
  a11yErrorsCount: 0,
845
- shouldShowEditor: typeof IntersectionObserver === 'undefined' || maxInitRenderedRCEs <= 0 || currentRCECount < maxInitRenderedRCEs,
846
- AIToolsOpen: false
792
+ shouldShowEditor: typeof IntersectionObserver === 'undefined' || maxInitRenderedRCEs <= 0 || currentRCECount < maxInitRenderedRCEs
847
793
  };
848
794
  this._statusBarId = `${this.state.id}_statusbar`;
849
795
  this.pendingEventHandlers = [];
@@ -867,7 +813,6 @@ class RCEWrapper extends React.Component {
867
813
  this.resizeObserver = new ResizeObserver(() => {
868
814
  this._handleFullscreenResize();
869
815
  });
870
- this.AIToolsTray = undefined;
871
816
  this._effectiveContainingContext = normalizeContainingContext(this.props.trayProps?.containingContext);
872
817
  }
873
818
 
@@ -896,17 +841,17 @@ class RCEWrapper extends React.Component {
896
841
  rce_transform_loaded_content = false,
897
842
  rce_find_replace = false,
898
843
  rce_studio_embed_improvements = false,
899
- file_verifiers_for_quiz_links = false,
900
- consolidated_media_player = false
844
+ rce_asr_captioning_improvements = false,
845
+ file_verifiers_for_quiz_links = false
901
846
  } = this.props.features;
902
847
  return {
903
848
  new_math_equation_handling,
904
849
  explicit_latex_typesetting,
905
850
  rce_transform_loaded_content,
906
851
  rce_studio_embed_improvements,
852
+ rce_asr_captioning_improvements,
907
853
  file_verifiers_for_quiz_links,
908
- rce_find_replace,
909
- consolidated_media_player
854
+ rce_find_replace
910
855
  };
911
856
  }
912
857
  getRequiredConfigValues() {
@@ -1744,7 +1689,6 @@ class RCEWrapper extends React.Component {
1744
1689
  });
1745
1690
  }
1746
1691
  const statusBarOptions = {
1747
- aiTextTools: this.props.ai_text_tools,
1748
1692
  isDesktop: tinymce.Env.deviceType.isDesktop(),
1749
1693
  a11yResizers: !!this.props.features?.rce_a11y_resize
1750
1694
  };
@@ -1834,8 +1778,7 @@ class RCEWrapper extends React.Component {
1834
1778
  skipEditorFocus: true
1835
1779
  }),
1836
1780
  disabledPlugins: this.pluginsToExclude,
1837
- features: statusBarFeatures,
1838
- onAI: this.handleAIClick
1781
+ features: statusBarFeatures
1839
1782
  }), this._effectiveContainingContext && /*#__PURE__*/React.createElement(CanvasContentTray, Object.assign({
1840
1783
  mountNode: instuiPopupMountNodeFn,
1841
1784
  key: this.id,
@@ -1853,19 +1796,6 @@ class RCEWrapper extends React.Component {
1853
1796
  onExited: this.KBShortcutModalExited,
1854
1797
  onDismiss: this.closeKBShortcutModal,
1855
1798
  open: this.state.KBShortcutModalOpen
1856
- }), this.props.ai_text_tools && this.AIToolsTray &&
1857
- /*#__PURE__*/
1858
- // @ts-expect-error
1859
- React.createElement(this.AIToolsTray, {
1860
- open: this.state.AIToolsOpen,
1861
- container: document.querySelector('[role="main"]'),
1862
- mountNode: instuiPopupMountNodeFn,
1863
- contextId: trayProps.contextId,
1864
- contextType: trayProps.contextId,
1865
- currentContent: this.getCurrentContentForAI(),
1866
- onClose: this.closeAITools,
1867
- onInsertContent: this.handleInsertAIContent,
1868
- onReplaceContent: this.handleReplaceAIContent
1869
1799
  }), this.state.confirmAutoSave ? /*#__PURE__*/React.createElement(Suspense, {
1870
1800
  fallback: /*#__PURE__*/React.createElement(Spinner, {
1871
1801
  renderTitle: renderLoading,
@@ -1884,7 +1814,6 @@ class RCEWrapper extends React.Component {
1884
1814
  }
1885
1815
  }
1886
1816
  RCEWrapper.propTypes = {
1887
- ai_text_tools: _pt.bool,
1888
1817
  autosave: _pt.shape({
1889
1818
  enabled: _pt.bool,
1890
1819
  maxAge: _pt.number
@@ -1910,6 +1839,8 @@ RCEWrapper.propTypes = {
1910
1839
  textareaId: _pt.string,
1911
1840
  tinymce: _pt.any.isRequired,
1912
1841
  use_rce_icon_maker: _pt.bool,
1842
+ useHighContrast: _pt.bool,
1843
+ fontFamily: _pt.string,
1913
1844
  userCacheKey: _pt.string
1914
1845
  };
1915
1846
  RCEWrapper.propTypes = rceWrapperPropTypes;
@@ -21,4 +21,4 @@ export declare function focusToolbar(el: HTMLElement): void;
21
21
  export declare function focusFirstMenuButton(el: HTMLElement): void;
22
22
  export declare function isElementWithinTable(node: Element | null): boolean;
23
23
  export declare function parsePluginsToExclude(plugins: string[]): string[];
24
- export declare function patchAutosavedContent(content: string, asText?: boolean): string | null;
24
+ export declare function patchAutosavedContent(content: string, asText?: boolean): string;
@@ -154,6 +154,8 @@ export declare const rceWrapperPropTypes: {
154
154
  }>;
155
155
  instRecordDisabled: PropTypes.Requireable<boolean>;
156
156
  highContrastCSS: PropTypes.Requireable<(string | null | undefined)[]>;
157
+ useHighContrast: PropTypes.Requireable<boolean>;
158
+ fontFamily: PropTypes.Requireable<string>;
157
159
  maxInitRenderedRCEs: PropTypes.Requireable<number>;
158
160
  use_rce_icon_maker: PropTypes.Requireable<boolean>;
159
161
  features: PropTypes.Requireable<{
@@ -169,7 +171,6 @@ export declare const rceWrapperPropTypes: {
169
171
  isA2StudentView: PropTypes.Requireable<boolean>;
170
172
  maxMruTools: PropTypes.Requireable<number>;
171
173
  }>>;
172
- ai_text_tools: PropTypes.Requireable<boolean>;
173
174
  variant: PropTypes.Requireable<"full" | "lite" | "text-only" | "text-block" | "block-content-editor">;
174
175
  };
175
176
  export type RCEWrapperProps = PropTypes.InferProps<typeof rceWrapperPropTypes>;
@@ -126,6 +126,8 @@ export const rceWrapperPropTypes = {
126
126
  menu: menuPropType,
127
127
  instRecordDisabled: PropTypes.bool,
128
128
  highContrastCSS: PropTypes.arrayOf(PropTypes.string),
129
+ useHighContrast: PropTypes.bool,
130
+ fontFamily: PropTypes.string,
129
131
  maxInitRenderedRCEs: PropTypes.number,
130
132
  use_rce_icon_maker: PropTypes.bool,
131
133
  features: PropTypes.objectOf(PropTypes.bool),
@@ -133,6 +135,5 @@ export const rceWrapperPropTypes = {
133
135
  timezone: PropTypes.string,
134
136
  userCacheKey: PropTypes.string,
135
137
  externalToolsConfig: externalToolsConfigPropType,
136
- ai_text_tools: PropTypes.bool,
137
138
  variant: PropTypes.oneOf(RCEVariantValues)
138
139
  };
@@ -18,7 +18,6 @@ declare namespace StatusBar {
18
18
  export let onWordcountModalOpen: import("prop-types").Validator<(...args: any[]) => any>;
19
19
  export let disabledPlugins: import("prop-types").Requireable<(string | null | undefined)[]>;
20
20
  export let features: import("prop-types").Requireable<(string | null | undefined)[]>;
21
- export { func as onAI };
22
21
  }
23
22
  namespace defaultProps {
24
23
  export let a11yBadgeColor: string;
@@ -32,7 +32,6 @@ import { IconA11yLine, IconKeyboardShortcutsLine, IconMiniArrowEndLine, IconFull
32
32
  import formatMessage from '../format-message';
33
33
  import ResizeHandle from './ResizeHandle';
34
34
  import { FS_ENABLED } from '../util/fullscreenHelpers';
35
- import { AIWandSVG } from './plugins/shared/ai_tools';
36
35
  export const WYSIWYG_VIEW = 'WYSIWYG';
37
36
  export const PRETTY_HTML_EDITOR_VIEW = 'PRETTY';
38
37
  export const RAW_HTML_EDITOR_VIEW = 'RAW';
@@ -57,9 +56,7 @@ StatusBar.propTypes = {
57
56
  a11yErrorsCount: number,
58
57
  onWordcountModalOpen: func.isRequired,
59
58
  disabledPlugins: arrayOf(string),
60
- features: arrayOf(string),
61
- // StatusBarFeature[]
62
- onAI: func
59
+ features: arrayOf(string) // StatusBarFeature[]
63
60
  };
64
61
  StatusBar.defaultProps = {
65
62
  a11yBadgeColor: '#2B7ABC',
@@ -218,36 +215,14 @@ export default function StatusBar(props) {
218
215
  }
219
216
  function renderIconButtons() {
220
217
  if (isHtmlView()) return null;
221
- const ai_tools = isFeature('ai_tools');
222
218
  const kb_shortcuts = isFeature('keyboard_shortcuts');
223
219
  const a11y_checker = isFeature('a11y_checker');
224
- if (!(ai_tools || kb_shortcuts || a11y_checker)) return null;
220
+ if (!(kb_shortcuts || a11y_checker)) return null;
225
221
  const kbshortcut = formatMessage('View keyboard shortcuts');
226
222
  return /*#__PURE__*/React.createElement(View, {
227
223
  display: "inline-block",
228
224
  padding: "0 x-small"
229
- }, ai_tools && props.onAI && /*#__PURE__*/React.createElement(IconButton, {
230
- "data-btn-id": "rce-ai-btn",
231
- color: "secondary",
232
- "aria-haspopup": "dialog",
233
- title: formatMessage('AI Tools'),
234
- tabIndex: tabIndexForBtn('rce-ai-btn'),
235
- onClick: event => {
236
- event.target.focus(); // FF doesn't focus buttons on click
237
- props.onAI();
238
- },
239
- onFocus: () => setFocusedBtnId('rce-ai-btn'),
240
- screenReaderLabel: formatMessage('AI Tools'),
241
- withBackground: false,
242
- withBorder: false
243
- }, /*#__PURE__*/React.createElement("span", {
244
- style: {
245
- color: 'dodgerBlue'
246
- }
247
- }, /*#__PURE__*/React.createElement(SVGIcon, {
248
- src: AIWandSVG,
249
- size: "x-small"
250
- }))), kb_shortcuts && /*#__PURE__*/React.createElement(IconButton, {
225
+ }, kb_shortcuts && /*#__PURE__*/React.createElement(IconButton, {
251
226
  "data-btn-id": "rce-kbshortcut-btn",
252
227
  color: "secondary",
253
228
  "aria-haspopup": "dialog",
@@ -1,2 +1,3 @@
1
1
  export const advancedOnlyCommands: string[];
2
- export const containsAdvancedSyntax: ((latex: any) => boolean) & import("lodash").MemoizedFunction;
2
+ /** @type {(latex: string) => boolean} */
3
+ export const containsAdvancedSyntax: (latex: string) => boolean;
@@ -16,12 +16,14 @@
16
16
  * with this program. If not, see <http://www.gnu.org/licenses/>.
17
17
  */
18
18
 
19
- import { memoize } from 'lodash';
19
+ import { memoize } from 'es-toolkit/compat';
20
20
 
21
21
  // These commands all work fine with MathJax but either don't work, don't work well
22
22
  // (bad UX for editing), or look strange when rendered by Mathlive. Add new ones
23
23
  // here if you discover anything else or customers report unexpected experiences.
24
24
  const advancedOnlyCommands = ['begin', 'end', 'cases', 'cr', 'rm', 'text', 'hbox', 'mbox', 'unicode', 'cal', 'frak', 'it', 'scr', 'sf', '#', 'def', 'newcommand', 'operatorname', 'DeclareMathOperator', 'displaystyle', 'textstyle', 'scriptstyle', 'scriptscriptstyle', 'displaylines', 'abovewithdelims', 'array', 'bmatrix', 'buildrel', 'ddddot', 'dddot', 'eqalign', 'eqalignno', 'gcd', 'genfrac', 'hdashline', 'hfil', 'hfill', 'hfilll', 'hline', 'idotsint', 'iiiint', 'injlim', 'kern', 'label', 'LaTeX', 'leftroot', 'lgroup', 'lower', 'mathchoice', 'mathfrak', 'matrix', 'mit', 'mkern', 'mspace', 'negmedspace', 'negthickspace', 'negthinspace', 'newline', 'nobreakspace', 'oldstyle', 'overset', 'pmatrix', 'raise', 'rgroup', 'rule', 'Rule', 'skew', 'space', 'tag', 'TeX', 'underbrace', 'uproot', 'varinjlim', 'varliminf', 'varlimsup', 'varprojlim', 'vcenter', 'vmatrix'];
25
25
  const advancedOnlyRegex = new RegExp(advancedOnlyCommands.join('|'), 'm');
26
+
27
+ /** @type {(latex: string) => boolean} */
26
28
  const containsAdvancedSyntax = memoize(latex => advancedOnlyRegex.test(latex));
27
29
  export { advancedOnlyCommands, containsAdvancedSyntax };
@@ -11,6 +11,7 @@ declare class EquationEditorModal extends React.Component<any, any, any> {
11
11
  insertNewRange(): void;
12
12
  advancedModeOnly(latex: any): boolean;
13
13
  executeCommand: (cmd: any, advancedCmd: any) => void;
14
+ clearMathFieldBeforeDismiss: () => void;
14
15
  handleModalCancel: () => void;
15
16
  handleModalDone: () => void;
16
17
  renderMathInAdvancedPreview: import("@instructure/debounce").Debounced<() => void>;
@@ -68,7 +68,16 @@ export default class EquationEditorModal extends Component {
68
68
  });
69
69
  }
70
70
  };
71
+ // Clear the math-field before the modal unmounts to prevent XSS.
72
+ // Mathlive's dispose method sets element.innerHTML = this.model.getValue(),
73
+ // which would parse any HTML in the formula as real DOM elements.
74
+ this.clearMathFieldBeforeDismiss = () => {
75
+ if (this.mathField) {
76
+ this.setMathField('');
77
+ }
78
+ };
71
79
  this.handleModalCancel = () => {
80
+ this.clearMathFieldBeforeDismiss();
72
81
  this.props.onModalDismiss();
73
82
  };
74
83
  this.handleModalDone = () => {
@@ -80,11 +89,12 @@ export default class EquationEditorModal extends Component {
80
89
  if (output) {
81
90
  onEquationSubmit(output);
82
91
  }
92
+ this.clearMathFieldBeforeDismiss();
83
93
  onModalDismiss();
84
94
  };
85
95
  this.renderMathInAdvancedPreview = debounce(() => {
86
96
  if (this.previewElement.current) {
87
- this.previewElement.current.innerHTML = String.raw`\(${this.state.workingFormula}\)`;
97
+ this.previewElement.current.textContent = String.raw`\(${this.state.workingFormula}\)`;
88
98
  this.mathml.processNewMathInElem(this.previewElement.current);
89
99
  }
90
100
  }, EquationEditorModal.debounceRate, {
@@ -257,7 +267,7 @@ export default class EquationEditorModal extends Component {
257
267
  if (this.state.workingFormula) {
258
268
  this.renderMathInAdvancedPreview();
259
269
  } else {
260
- this.previewElement.current.innerHTML = '';
270
+ this.previewElement.current.textContent = '';
261
271
  }
262
272
  }
263
273
  componentDidMount() {
@@ -35,7 +35,7 @@ import { MAX_IMAGE_SIZE_BYTES } from '../../../../shared/compressionUtils';
35
35
  import { createCroppedImageSvg } from '../../../../shared/ImageCropper/imageCropUtils';
36
36
  import { convertFileToBase64 } from '../../../../shared/fileUtils';
37
37
  import { ImageSettingsPropTypes } from './propTypes';
38
- import _ from 'lodash';
38
+ import { isEqual } from 'es-toolkit/compat';
39
39
  const getCompressionMessage = () => formatMessage('Your image has been compressed for Icon Maker. Images less than {size} KB will not be compressed.', {
40
40
  size: MAX_IMAGE_SIZE_BYTES / 1024
41
41
  });
@@ -99,7 +99,7 @@ export const ImageOptions = ({
99
99
 
100
100
  // After submitting cropper modal a new embedded image should be generated
101
101
  useEffect(() => {
102
- if (state.cropperSettings && settings.imageSettings && !_.isEqual(state.cropperSettings, settings.imageSettings?.cropperSettings)) {
102
+ if (state.cropperSettings && settings.imageSettings && !isEqual(state.cropperSettings, settings.imageSettings?.cropperSettings)) {
103
103
  if (state.cropperSettings.shape !== settings.shape) {
104
104
  trayDispatch({
105
105
  shape: state.cropperSettings.shape
@@ -17,7 +17,7 @@
17
17
  */
18
18
 
19
19
  import React, { useReducer, useEffect, useRef, Suspense } from 'react';
20
- import _ from 'lodash';
20
+ import { isEqual } from 'es-toolkit/compat';
21
21
  import PropTypes from 'prop-types';
22
22
  import formatMessage from '../../../../../../format-message';
23
23
  import reducer, { actions, initialState, modes } from '../../../reducers/imageSection';
@@ -161,7 +161,7 @@ export const ImageSection = ({
161
161
  // eslint-disable-next-line react-hooks/exhaustive-deps
162
162
  }, [settings.shape]);
163
163
  useEffect(() => {
164
- if (settings.imageSettings && !isMetadataLoaded.current && !_.isEqual(settings.imageSettings, metadata)) {
164
+ if (settings.imageSettings && !isMetadataLoaded.current && !isEqual(settings.imageSettings, metadata)) {
165
165
  isMetadataLoaded.current = true;
166
166
  dispatch({
167
167
  type: actions.UPDATE_SETTINGS.type,
@@ -171,7 +171,7 @@ export const ImageSection = ({
171
171
  // eslint-disable-next-line react-hooks/exhaustive-deps
172
172
  }, [settings.imageSettings]);
173
173
  useEffect(() => {
174
- if (!_.isEqual(metadata, settings.imageSettings)) {
174
+ if (!isEqual(metadata, settings.imageSettings)) {
175
175
  onChange({
176
176
  type: svgActions.SET_IMAGE_SETTINGS,
177
177
  payload: metadata
@@ -47,19 +47,34 @@ export namespace DEFAULT_SETTINGS {
47
47
  export let error: null;
48
48
  }
49
49
  export const BASE_SIZE: {
50
- [x: string]: number;
50
+ [Size.ExtraSmall]: number;
51
+ [Size.Small]: number;
52
+ [Size.Medium]: number;
53
+ [Size.Large]: number;
51
54
  };
52
55
  export const STROKE_WIDTH: {
53
- [x: string]: number;
56
+ [Size.None]: number;
57
+ [Size.Small]: number;
58
+ [Size.Medium]: number;
59
+ [Size.Large]: number;
54
60
  };
55
61
  export const TEXT_SIZE: {
56
- [x: string]: number;
62
+ [Size.Small]: number;
63
+ [Size.Medium]: number;
64
+ [Size.Large]: number;
65
+ [Size.ExtraLarge]: number;
57
66
  };
58
67
  export const TEXT_SIZE_FONT_DIFF: {
59
- [x: string]: number;
68
+ [Size.Small]: number;
69
+ [Size.Medium]: number;
70
+ [Size.Large]: number;
71
+ [Size.ExtraLarge]: number;
60
72
  };
61
73
  export const MAX_CHAR_COUNT: {
62
- [x: string]: number;
74
+ [Size.Small]: number;
75
+ [Size.Medium]: number;
76
+ [Size.Large]: number;
77
+ [Size.ExtraLarge]: number;
63
78
  };
64
79
  export const MAX_TOTAL_TEXT_CHARS: 32;
65
80
  export const TEXT_BACKGROUND_PADDING: 4;
@@ -1,3 +1,3 @@
1
1
  export function createSvgElement(tag: any, attributes?: {}): any;
2
2
  export function splitTextIntoLines(text: any, maxChars: any): string[];
3
- export function decode(input: any): string | null;
3
+ export function decode(input: any): string;
@@ -16,7 +16,7 @@
16
16
  * with this program. If not, see <http://www.gnu.org/licenses/>.
17
17
  */
18
18
 
19
- import _ from 'lodash';
19
+ import { isEqual } from 'es-toolkit/compat';
20
20
  export class IconMakerFormHasChanges {
21
21
  constructor(initSettings, currSettings) {
22
22
  this.initialSettings = void 0;
@@ -61,7 +61,7 @@ export class IconMakerFormHasChanges {
61
61
  return 'textPosition' in this.currentSettings && this.initialSettings.textPosition !== this.currentSettings.textPosition;
62
62
  }
63
63
  hasImageSettingsChange() {
64
- return 'imageSettings' in this.currentSettings && !_.isEqual(this.initialSettings.imageSettings, this.currentSettings.imageSettings);
64
+ return 'imageSettings' in this.currentSettings && !isEqual(this.initialSettings.imageSettings, this.currentSettings.imageSettings);
65
65
  }
66
66
  hasChanges() {
67
67
  return this.hasNameChange() || this.hasAltChange() || this.hasShapeNameChange() || this.hasShapeSizeChange() || this.hasColorNameChange() || this.hasOutlineColorChange() || this.hasOutlineSizeChange() || this.hasTextChange() || this.hasTextSizeChange() || this.hasTextColorChange() || this.hasTextBackgroundColorChange() || this.hasTextPositionChange() || this.hasImageSettingsChange();
@@ -42,7 +42,6 @@ export function scaleVideoSize(videoSize: any, naturalWidth: any, naturalHeight:
42
42
  export function labelForImageSize(imageSize: any): string;
43
43
  export const MIN_HEIGHT: 10;
44
44
  export const MIN_WIDTH: 10;
45
- export const MIN_WIDTH_VIDEO: 320;
46
45
  export const MIN_PERCENTAGE: 10;
47
46
  export const SMALL: "small";
48
47
  export const MEDIUM: "medium";
@@ -50,7 +49,6 @@ export const LARGE: "large";
50
49
  export const EXTRA_LARGE: "extra-large";
51
50
  export const CUSTOM: "custom";
52
51
  export const imageSizes: string[];
53
- export const videoSizes: string[];
54
52
  export const studioPlayerSizes: string[];
55
53
  export const MIN_WIDTH_STUDIO_PLAYER: number;
56
54
  export const MIN_HEIGHT_STUDIO_PLAYER: number;
@@ -18,10 +18,8 @@
18
18
 
19
19
  import formatMessage from '../../../format-message';
20
20
  import { scaleForHeight, scaleForWidth } from '../shared/DimensionUtils';
21
- import RCEGlobals from '../../../rce/RCEGlobals';
22
21
  export const MIN_HEIGHT = 10;
23
22
  export const MIN_WIDTH = 10;
24
- export const MIN_WIDTH_VIDEO = 320;
25
23
  export const MIN_PERCENTAGE = 10;
26
24
  export const SMALL = 'small';
27
25
  export const MEDIUM = 'medium';
@@ -29,7 +27,6 @@ export const LARGE = 'large';
29
27
  export const EXTRA_LARGE = 'extra-large';
30
28
  export const CUSTOM = 'custom';
31
29
  export const imageSizes = [SMALL, MEDIUM, LARGE, EXTRA_LARGE, CUSTOM];
32
- export const videoSizes = [MEDIUM, LARGE, EXTRA_LARGE, CUSTOM];
33
30
  export const studioPlayerSizes = [SMALL, MEDIUM, LARGE, CUSTOM];
34
31
  const sizeByMaximumDimension = {
35
32
  200: SMALL,
@@ -137,12 +134,8 @@ export function fromVideoEmbed($element) {
137
134
  } catch (_ignore) {
138
135
  // bad json?
139
136
  }
140
- if (RCEGlobals.getFeatures()?.consolidated_media_player) {
141
- const width = videoOptions.appliedWidth || videoOptions.naturalWidth;
142
- videoOptions.videoSize = [SMALL, MEDIUM, LARGE].find(size => width === studioPlayerDimensions[size].width) || CUSTOM;
143
- } else {
144
- videoOptions.videoSize = imageSizeFromKnownOptions(videoOptions);
145
- }
137
+ const width = videoOptions.appliedWidth || videoOptions.naturalWidth;
138
+ videoOptions.videoSize = [SMALL, MEDIUM, LARGE].find(size => width === studioPlayerDimensions[size].width) || CUSTOM;
146
139
  const source = $videoIframe.getAttribute('src');
147
140
  const matches = source?.match(/\/media_attachments_iframe\/(\d+)/);
148
141
  if (matches) {
@@ -16,16 +16,19 @@
16
16
  * with this program. If not, see <http://www.gnu.org/licenses/>.
17
17
  */
18
18
 
19
+ import { trackPendoEvent } from '@instructure/canvas-media';
19
20
  import tinymce from 'tinymce';
20
21
  import bridge from '../../../bridge';
21
- import configureStore from '../../../sidebar/store/configureStore';
22
+ import { showFlashAlert } from '../../../common/FlashAlert';
23
+ import formatMessage from '../../../format-message';
22
24
  import { get as getSession } from '../../../sidebar/actions/session';
23
25
  import { uploadToMediaFolder } from '../../../sidebar/actions/upload';
24
- import doFileUpload from '../shared/Upload/doFileUpload';
25
- import formatMessage from '../../../format-message';
26
- import { isAudioOrVideo, isImage } from '../shared/fileTypeUtils';
27
- import { showFlashAlert } from '../../../common/FlashAlert';
26
+ import configureStore from '../../../sidebar/store/configureStore';
27
+ import RCEGlobals from '../../RCEGlobals';
28
28
  import { isMicrosoftWordContentInEvent } from '../shared/EventUtils';
29
+ import { isAudioOrVideo } from '../shared/fileTypeUtils';
30
+ import doFileUpload from '../shared/Upload/doFileUpload';
31
+
29
32
  // assume that if there are multiple RCEs on the page,
30
33
  // they all talk to the same canvas
31
34
  const config = {
@@ -110,13 +113,7 @@ tinymce.PluginManager.add('instructure_paste', function (editor) {
110
113
  size: file.size,
111
114
  domObject: file
112
115
  };
113
- let tabContext = 'documents';
114
- if (isImage(file.type)) {
115
- tabContext = 'images';
116
- } else if (isAudioOrVideo(file.type)) {
117
- tabContext = 'media';
118
- }
119
- store.dispatch(uploadToMediaFolder(tabContext, fileMetaProps));
116
+ store.dispatch(uploadToMediaFolder(fileMetaProps));
120
117
  return 'submitted';
121
118
  }
122
119
  }
@@ -158,6 +155,15 @@ tinymce.PluginManager.add('instructure_paste', function (editor) {
158
155
  // Skip audio and video files when disabled
159
156
  continue;
160
157
  }
158
+ if (isAudioOrVideo(file.type) && RCEGlobals.getFeatures()?.rce_asr_captioning_improvements) {
159
+ const trayProps = bridge.trayProps.get(editor);
160
+ trackPendoEvent('canvas_native_media_embedded', {
161
+ insertion_method: isPaste ? 'paste_file' : 'drag_drop',
162
+ media_kind: file.type.startsWith('audio/') ? 'audio' : 'video',
163
+ resourceType: trayProps?.contextType,
164
+ resourceId: trayProps?.contextId
165
+ });
166
+ }
161
167
 
162
168
  // This will finish once the dialog is closed, if one was created, putting this in a loop allows us
163
169
  // to show a dialog for each file without them conflicting.
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { ModalProps } from '@instructure/ui-modal/types';
2
+ import { type ModalProps } from '@instructure/ui-modal';
3
3
  export declare function ExternalToolDialogModal(props: Pick<ModalProps, 'label' | 'open' | 'onOpen' | 'onClose' | 'mountNode'> & {
4
4
  onCloseButton: () => void;
5
5
  name: string;
@@ -3,6 +3,10 @@ import React from 'react';
3
3
  * Provide an iframe for launching an LTI tool directly from the frontend.
4
4
  * Works just like all existing usages of the LTI <iframe> element, including
5
5
  * extracting a ref of the <iframe> directly and setting things on it later.
6
+ *
7
+ * TODO: consider moving allow stuff here instead of having it be the
8
+ * responsibility of the caller, need to check if RCE uses same
9
+ * iframeAllowances() as elsewhere
6
10
  */
7
11
  declare const ToolLaunchIframe: React.ForwardRefExoticComponent<React.IframeHTMLAttributes<HTMLIFrameElement> & {
8
12
  children?: React.ReactNode | undefined;
@@ -23,6 +23,10 @@ import formatMessage from '../../../../../format-message';
23
23
  * Provide an iframe for launching an LTI tool directly from the frontend.
24
24
  * Works just like all existing usages of the LTI <iframe> element, including
25
25
  * extracting a ref of the <iframe> directly and setting things on it later.
26
+ *
27
+ * TODO: consider moving allow stuff here instead of having it be the
28
+ * responsibility of the caller, need to check if RCE uses same
29
+ * iframeAllowances() as elsewhere
26
30
  */
27
31
  const ToolLaunchIframe = /*#__PURE__*/React.forwardRef((props, ref) => {
28
32
  return /*#__PURE__*/React.createElement("iframe", Object.assign({