@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
@@ -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) {
@@ -28,7 +28,7 @@ import { Badge } from '@instructure/ui-badge';
28
28
  import { InstUISettingsProvider } from '@instructure/emotion';
29
29
  import { Text } from '@instructure/ui-text';
30
30
  import { SVGIcon } from '@instructure/ui-svg-images';
31
- import { IconA11yLine, IconKeyboardShortcutsLine, IconMiniArrowEndLine, IconFullScreenLine, IconExitFullScreenLine } from '@instructure/ui-icons';
31
+ import { IconA11yLine, IconKeyboardShortcutsLine, IconMiniArrowEndLine, IconFullScreenLine, IconExitFullScreenLine, IconAddLine, IconCheckMarkIndeterminateLine } from '@instructure/ui-icons';
32
32
  import formatMessage from '../format-message';
33
33
  import ResizeHandle from './ResizeHandle';
34
34
  import { FS_ENABLED } from '../util/fullscreenHelpers';
@@ -62,7 +62,7 @@ StatusBar.propTypes = {
62
62
  onAI: func
63
63
  };
64
64
  StatusBar.defaultProps = {
65
- a11yBadgeColor: '#0374B5',
65
+ a11yBadgeColor: '#2B7ABC',
66
66
  a11yErrorsCount: 0,
67
67
  disabledPlugins: []
68
68
  };
@@ -80,16 +80,15 @@ function renderPathString({
80
80
  }
81
81
  function emptyTagIcon() {
82
82
  return /*#__PURE__*/React.createElement(SVGIcon, {
83
- viewBox: "0 0 24 24",
84
- fontSize: "24px"
83
+ viewBox: "0 0 1920 1920",
84
+ width: "1em",
85
+ height: "1em"
85
86
  }, /*#__PURE__*/React.createElement("g", {
86
- role: "presentation"
87
- }, /*#__PURE__*/React.createElement("text", {
88
- textAnchor: "middle",
89
- x: "12px",
90
- y: "18px",
91
- fontSize: "16"
92
- }, "</>")));
87
+ role: "presentation",
88
+ transform: "scale(28.7) translate(0, 8)"
89
+ }, /*#__PURE__*/React.createElement("path", {
90
+ d: "M0 29.61L0 25.51L23.71 15.50L23.71 19.87L4.91 27.59L23.71 35.38L23.71 39.75L0 29.61ZM26.46 45.87L36.84 8.86L40.36 8.86L30.00 45.87L26.46 45.87ZM66.80 29.61L43.09 39.75L43.09 35.38L61.87 27.59L43.09 19.87L43.09 15.50L66.80 25.51L66.80 29.61Z"
91
+ })));
93
92
  }
94
93
  function findFocusable(el) {
95
94
  // eslint-disable-next-line react/no-find-dom-node
@@ -287,10 +286,15 @@ export default function StatusBar(props) {
287
286
  title: formatMessage('View word and character counts')
288
287
  }, wordCount)));
289
288
  }
290
- function renderSection3(html_view, fullscreen, resize_handle) {
289
+ function renderSection3({
290
+ html_view,
291
+ fullscreen,
292
+ resize_handle,
293
+ a11y_resize_handlers
294
+ }) {
291
295
  return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
292
296
  className: css(styles.separator)
293
- }), html_view && renderToggleHtml(), fullscreen && renderFullscreen(), resize_handle && renderResizeHandle());
297
+ }), html_view && renderToggleHtml(), a11y_resize_handlers && renderAccessibleResizeHandle(), fullscreen && renderFullscreen(), resize_handle && renderResizeHandle());
294
298
  }
295
299
  function descMsg() {
296
300
  return preferredHtmlEditor() === RAW_HTML_EDITOR_VIEW ? formatMessage('Shift-O to open the pretty html editor.') : formatMessage('The pretty html editor is not keyboard accessible. Press Shift O to open the raw html editor.');
@@ -351,7 +355,11 @@ export default function StatusBar(props) {
351
355
  screenReaderLabel: fullscreen,
352
356
  withBackground: false,
353
357
  withBorder: false
354
- }, props.rceIsFullscreen ? /*#__PURE__*/React.createElement(IconExitFullScreenLine, null) : /*#__PURE__*/React.createElement(IconFullScreenLine, null));
358
+ }, /*#__PURE__*/React.createElement("div", {
359
+ style: {
360
+ fontSize: '0.9rem'
361
+ }
362
+ }, props.rceIsFullscreen ? /*#__PURE__*/React.createElement(IconExitFullScreenLine, null) : /*#__PURE__*/React.createElement(IconFullScreenLine, null)));
355
363
  }
356
364
  function renderResizeHandle() {
357
365
  if (props.rceIsFullscreen) return null;
@@ -364,10 +372,43 @@ export default function StatusBar(props) {
364
372
  }
365
373
  });
366
374
  }
375
+ function renderAccessibleResizeHandle() {
376
+ if (props.rceIsFullscreen) return null;
377
+ const increaseBtnId = 'rce-resize-increase-btn';
378
+ const decreaseBtnId = 'rce-resize-decrease-btn';
379
+ const handleResize = deltaY => {
380
+ props.onResize(null, {
381
+ deltaY,
382
+ deltaX: 0
383
+ });
384
+ };
385
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(IconButton, {
386
+ "data-btn-id": increaseBtnId,
387
+ "data-testid": increaseBtnId,
388
+ color: "secondary",
389
+ title: formatMessage('Increase Rich Content Area'),
390
+ tabIndex: tabIndexForBtn(increaseBtnId),
391
+ onFocus: () => setFocusedBtnId(increaseBtnId),
392
+ withBackground: false,
393
+ withBorder: false,
394
+ onClick: () => handleResize(5)
395
+ }, /*#__PURE__*/React.createElement(IconAddLine, null)), /*#__PURE__*/React.createElement(IconButton, {
396
+ "data-btn-id": decreaseBtnId,
397
+ "data-testid": decreaseBtnId,
398
+ color: "secondary",
399
+ title: formatMessage('Decrease Rich Content Area'),
400
+ tabIndex: tabIndexForBtn(decreaseBtnId),
401
+ onFocus: () => setFocusedBtnId(decreaseBtnId),
402
+ withBackground: false,
403
+ withBorder: false,
404
+ onClick: () => handleResize(-5)
405
+ }, /*#__PURE__*/React.createElement(IconCheckMarkIndeterminateLine, null)));
406
+ }
367
407
  const flexJustify = isHtmlView() ? 'end' : 'start';
368
408
  const html_view = isFeature('html_view') && isAvailable('instructure_html_view');
369
409
  const fullscreen = isFeature('fullscreen') && isAvailable('instructure_fullscreen');
370
410
  const resize_handle = isFeature('resize_handle');
411
+ const a11y_resize_handlers = isFeature('a11y_resize_handlers');
371
412
  return /*#__PURE__*/React.createElement(InstUISettingsProvider, {
372
413
  theme: {
373
414
  componentOverrides: {
@@ -388,7 +429,12 @@ export default function StatusBar(props) {
388
429
  }, isHtmlView() ? renderHtmlEditorMessage() : renderPath()), /*#__PURE__*/React.createElement(Flex.Item, {
389
430
  role: "toolbar",
390
431
  title: formatMessage('Editor Status Bar')
391
- }, renderIconButtons(), isFeature('word_count') && isAvailable('instructure_wordcount') && renderWordCount(), (html_view || fullscreen || resize_handle) && renderSection3(html_view, fullscreen, resize_handle))));
432
+ }, renderIconButtons(), isFeature('word_count') && isAvailable('instructure_wordcount') && renderWordCount(), (html_view || fullscreen || resize_handle) && renderSection3({
433
+ html_view,
434
+ fullscreen,
435
+ resize_handle,
436
+ a11y_resize_handlers
437
+ }))));
392
438
  }
393
439
  const styles = StyleSheet.create({
394
440
  separator: {
@@ -24,6 +24,12 @@
24
24
  */
25
25
  export class AlertHandler {
26
26
  constructor(alertFunc) {
27
+ /**
28
+ * Calls the registered alertFunc assuming one has been set, otherwise
29
+ * it throws.
30
+ *
31
+ * @memberof AlertHandler
32
+ */
27
33
  this.handleAlert = alert => {
28
34
  if (this.alertFunc == null) {
29
35
  throw new Error('Tried to alert without alertFunc being set first');
@@ -32,12 +38,5 @@ export class AlertHandler {
32
38
  };
33
39
  this.alertFunc = alertFunc;
34
40
  }
35
-
36
- /**
37
- * Calls the registered alertFunc assuming one has been set, otherwise
38
- * it throws.
39
- *
40
- * @memberof AlertHandler
41
- */
42
41
  }
43
42
  export default new AlertHandler();
@@ -112,13 +112,13 @@ tinymce.PluginManager.add('instructure-ui-icons', function (editor) {
112
112
  embed: {
113
113
  src: `<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
114
114
  <path fill-rule="evenodd" clip-rule="evenodd" d="M13.7647 5.21417C13.6694 5.21417 13.5773 5.23988 13.482 5.24631C12.8329 3.36281 11.0795 2 9 2C6.53506 2 4.52435 3.91029 4.28294 6.34234C4.09341 6.31127 3.90176 6.28556 3.70588 6.28556C1.66235 6.28556 0 7.96764 0 10.0354C0 12.1032 1.66235 13.7853 3.70588 13.7853L5 13.7853V12.7139L3.70588 12.7139C2.24682 12.7139 1.05882 11.5129 1.05882 10.0354C1.05882 8.55798 2.24682 7.35695 3.70588 7.35695C4.40259 7.35695 5.06012 7.62908 5.55882 8.12192L6.29894 7.35588C6.00565 7.0666 5.66788 6.84483 5.30894 6.66912C5.38941 4.67419 7.00835 3.07139 9 3.07139C11.0435 3.07139 12.7059 4.75347 12.7059 6.82126C12.7059 7.1491 12.6635 7.47266 12.582 7.78658L13.6059 8.06085C13.7107 7.65801 13.7647 7.24124 13.7647 6.82126C13.7647 6.64019 13.7308 6.4677 13.7118 6.29199C13.7298 6.29199 13.7467 6.28556 13.7647 6.28556C15.516 6.28556 16.9412 7.72765 16.9412 9.49973C16.9412 11.2718 15.516 12.7139 13.7647 12.7139L13 12.7139V13.7853L13.7647 13.7853C16.1005 13.7853 18 11.8632 18 9.49973C18 7.13624 16.1005 5.21417 13.7647 5.21417Z" fill="#2B3B46"/>
115
- <path fill-rule="evenodd" clip-rule="evenodd" d="M7.72039 10.6479L8.3603 11.1813L6.75882 13.1025L8.36029 15.0239L7.72038 15.5573L5.6748 13.1024L7.72039 10.6479ZM10.2802 10.6479L12.3258 13.1024L10.2802 15.5573L9.64031 15.0239L11.2418 13.1025L9.6403 11.1813L10.2802 10.6479Z" fill="#2D3B45"/>
115
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M7.72039 10.6479L8.3603 11.1813L6.75882 13.1025L8.36029 15.0239L7.72038 15.5573L5.6748 13.1024L7.72039 10.6479ZM10.2802 10.6479L12.3258 13.1024L10.2802 15.5573L9.64031 15.0239L11.2418 13.1025L9.6403 11.1813L10.2802 10.6479Z" fill="#273540"/>
116
116
  </svg>`
117
117
  },
118
118
  buttons: {
119
119
  src: `
120
120
  <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 18 18">
121
- <g fill="#2D3B45" clip-path="url(#clip0)">
121
+ <g fill="#273540" clip-path="url(#clip0)">
122
122
  <path fill-rule="evenodd" d="M.999993 11l.000001 6H6.99999v-6H.999993zm-.99999933 7H7.99999v-8H-.00000668l3.5e-7 8zM4 7c1.65685 0 3-1.34315 3-3S5.65685 1 4 1 1 2.34315 1 4s1.34315 3 3 3zm0 1c2.20914 0 4-1.79086 4-4S6.20914 0 4 0-3e-7 1.79086-3e-7 4 1.79086 8 4 8z" clip-rule="evenodd"/>
123
123
  <path d="M12.5 10h1v8h-1v-8z"/>
124
124
  <path d="M17 13.5v1H9v-1h8z"/>
@@ -49,6 +49,9 @@ export default class EquationEditorModal extends Component {
49
49
  };
50
50
  this.previewElement = /*#__PURE__*/React.createRef();
51
51
  this.advancedEditor = /*#__PURE__*/React.createRef();
52
+ // ********* //
53
+ // Callbacks //
54
+ // ********* //
52
55
  this.executeCommand = (cmd, advancedCmd) => {
53
56
  if (this.state.advanced) {
54
57
  const insertionText = advancedCmd || cmd;
@@ -123,6 +126,9 @@ export default class EquationEditorModal extends Component {
123
126
  this.handleFieldRef = node => {
124
127
  this.mathField = node;
125
128
  };
129
+ // ******************* //
130
+ // Rendering functions //
131
+ // ******************* //
126
132
  this.renderFooter = () => {
127
133
  return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Button, {
128
134
  "data-testid": "equation-editor-modal-cancel",
@@ -247,11 +253,6 @@ export default class EquationEditorModal extends Component {
247
253
  const normalizedLatex = latex.replace(/\s+/, '');
248
254
  return containsAdvancedSyntax(normalizedLatex);
249
255
  }
250
-
251
- // ********* //
252
- // Callbacks //
253
- // ********* //
254
-
255
256
  setPreviewElementContent() {
256
257
  if (this.state.workingFormula) {
257
258
  this.renderMathInAdvancedPreview();
@@ -259,11 +260,6 @@ export default class EquationEditorModal extends Component {
259
260
  this.previewElement.current.innerHTML = '';
260
261
  }
261
262
  }
262
-
263
- // ******************* //
264
- // Rendering functions //
265
- // ******************* //
266
-
267
263
  componentDidMount() {
268
264
  this.registerBasicEditorListener();
269
265
  this.setPreviewElementContent();
@@ -1,9 +1,9 @@
1
1
  export default Course;
2
- declare function Course({ dispatch, onChange, onLoading, onLoaded, canvasOrigin }: {
3
- dispatch: any;
4
- onChange: any;
5
- onLoading: any;
6
- onLoaded: any;
2
+ declare function Course({ dispatch, onChange, onLoading, onLoaded, canvasOrigin, }: {
3
+ dispatch?: (() => void) | undefined;
4
+ onChange?: (() => void) | undefined;
5
+ onLoading?: (() => void) | undefined;
6
+ onLoaded?: (() => void) | undefined;
7
7
  canvasOrigin: any;
8
8
  }): React.JSX.Element;
9
9
  declare namespace Course {
@@ -14,16 +14,6 @@ declare namespace Course {
14
14
  let onLoaded: PropTypes.Requireable<(...args: any[]) => any>;
15
15
  let canvasOrigin: PropTypes.Validator<string>;
16
16
  }
17
- namespace defaultProps {
18
- export function dispatch_1(): void;
19
- export { dispatch_1 as dispatch };
20
- export function onChange_1(): void;
21
- export { onChange_1 as onChange };
22
- export function onLoading_1(): void;
23
- export { onLoading_1 as onLoading };
24
- export function onLoaded_1(): void;
25
- export { onLoaded_1 as onLoaded };
26
- }
27
17
  }
28
18
  import React from 'react';
29
19
  import PropTypes from 'prop-types';
@@ -80,10 +80,10 @@ const dispatchImage = async (dispatch, onChange, dataUrl, dataBlob) => {
80
80
  });
81
81
  };
82
82
  const Course = ({
83
- dispatch,
84
- onChange,
85
- onLoading,
86
- onLoaded,
83
+ dispatch = () => {},
84
+ onChange = () => {},
85
+ onLoading = () => {},
86
+ onLoaded = () => {},
87
87
  canvasOrigin
88
88
  }) => {
89
89
  const storeProps = useStoreProps();
@@ -161,10 +161,4 @@ Course.propTypes = {
161
161
  onLoaded: PropTypes.func,
162
162
  canvasOrigin: PropTypes.string.isRequired
163
163
  };
164
- Course.defaultProps = {
165
- dispatch: () => {},
166
- onChange: () => {},
167
- onLoading: () => {},
168
- onLoaded: () => {}
169
- };
170
164
  export default Course;
@@ -35,6 +35,10 @@ export function scaleToSize(imageSize: any, naturalWidth: any, naturalHeight: an
35
35
  width: any;
36
36
  height: any;
37
37
  };
38
+ export function scaleVideoSize(videoSize: any, naturalWidth: any, naturalHeight: any): {
39
+ width: any;
40
+ height: any;
41
+ };
38
42
  export function labelForImageSize(imageSize: any): string;
39
43
  export const MIN_HEIGHT: 10;
40
44
  export const MIN_WIDTH: 10;
@@ -47,3 +51,6 @@ export const EXTRA_LARGE: "extra-large";
47
51
  export const CUSTOM: "custom";
48
52
  export const imageSizes: string[];
49
53
  export const videoSizes: string[];
54
+ export const studioPlayerSizes: string[];
55
+ export const MIN_WIDTH_STUDIO_PLAYER: number;
56
+ export const MIN_HEIGHT_STUDIO_PLAYER: number;
@@ -18,6 +18,7 @@
18
18
 
19
19
  import formatMessage from '../../../format-message';
20
20
  import { scaleForHeight, scaleForWidth } from '../shared/DimensionUtils';
21
+ import RCEGlobals from '../../../rce/RCEGlobals';
21
22
  export const MIN_HEIGHT = 10;
22
23
  export const MIN_WIDTH = 10;
23
24
  export const MIN_WIDTH_VIDEO = 320;
@@ -29,12 +30,29 @@ export const EXTRA_LARGE = 'extra-large';
29
30
  export const CUSTOM = 'custom';
30
31
  export const imageSizes = [SMALL, MEDIUM, LARGE, EXTRA_LARGE, CUSTOM];
31
32
  export const videoSizes = [MEDIUM, LARGE, EXTRA_LARGE, CUSTOM];
33
+ export const studioPlayerSizes = [SMALL, MEDIUM, LARGE, CUSTOM];
32
34
  const sizeByMaximumDimension = {
33
35
  200: SMALL,
34
36
  320: MEDIUM,
35
37
  400: LARGE,
36
38
  640: EXTRA_LARGE
37
39
  };
40
+ const studioPlayerDimensions = {
41
+ [SMALL]: {
42
+ width: 320,
43
+ height: 254
44
+ },
45
+ [MEDIUM]: {
46
+ width: 480,
47
+ height: 300
48
+ },
49
+ [LARGE]: {
50
+ width: 700,
51
+ height: 441
52
+ }
53
+ };
54
+ export const MIN_WIDTH_STUDIO_PLAYER = studioPlayerDimensions[SMALL].width;
55
+ export const MIN_HEIGHT_STUDIO_PLAYER = studioPlayerDimensions[SMALL].height;
38
56
  function parsedOrNull($element, attribute) {
39
57
  // when the image is first inserted into the rce, it's size
40
58
  // is constrained by a style attribute with max-width, max-height.
@@ -49,6 +67,9 @@ function imageSizeFromKnownOptions(imageOptions) {
49
67
  const largestDimension = Math.max(intendedWidth, intendedHeight);
50
68
  return sizeByMaximumDimension[largestDimension] || CUSTOM;
51
69
  }
70
+ function hasHeightAndWidth($element) {
71
+ return $element.hasAttribute('width') && $element.hasAttribute('height');
72
+ }
52
73
  function getPercentageUnitsFromAttributes($element) {
53
74
  const getAttribute = attribute => $element.hasAttribute(attribute) ? $element.getAttribute(attribute) : $element[attribute];
54
75
  const widthValue = getAttribute('width');
@@ -65,7 +86,8 @@ export function fromImageEmbed($element) {
65
86
  naturalWidth: $element.naturalWidth,
66
87
  naturalHeight: $element.naturalHeight,
67
88
  appliedPercentage: percentageUnits || 100,
68
- usePercentageUnits: !!percentageUnits,
89
+ // by default use percentage units
90
+ usePercentageUnits: hasHeightAndWidth($element) ? !!percentageUnits : true,
69
91
  altText: altText || '',
70
92
  isDecorativeImage: altText !== null && altText.replace(/\s/g, '') === '',
71
93
  url: $element.src
@@ -115,7 +137,12 @@ export function fromVideoEmbed($element) {
115
137
  } catch (_ignore) {
116
138
  // bad json?
117
139
  }
118
- videoOptions.videoSize = imageSizeFromKnownOptions(videoOptions);
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
+ }
119
146
  const source = $videoIframe.getAttribute('src');
120
147
  const matches = source?.match(/\/media_attachments_iframe\/(\d+)/);
121
148
  if (matches) {
@@ -151,6 +178,22 @@ export function scaleToSize(imageSize, naturalWidth, naturalHeight) {
151
178
  width: Math.round(naturalWidth * scaleFactor)
152
179
  };
153
180
  }
181
+ export function scaleVideoSize(videoSize, naturalWidth, naturalHeight) {
182
+ if (videoSize === CUSTOM) {
183
+ return {
184
+ width: naturalWidth,
185
+ height: naturalHeight
186
+ };
187
+ }
188
+ const {
189
+ width,
190
+ height
191
+ } = studioPlayerDimensions[videoSize];
192
+ return {
193
+ width,
194
+ height
195
+ };
196
+ }
154
197
  export function labelForImageSize(imageSize) {
155
198
  switch (imageSize) {
156
199
  case SMALL:
@@ -0,0 +1,2 @@
1
+ import { Editor } from 'tinymce';
2
+ export default function (ed: Editor, document: Document): Promise<void>;
@@ -0,0 +1,45 @@
1
+ /*
2
+ * Copyright (C) 2024 - present Instructure, Inc.
3
+ *
4
+ * This file is part of Canvas.
5
+ *
6
+ * Canvas is free software: you can redistribute it and/or modify it under
7
+ * the terms of the GNU Affero General Public License as published by the Free
8
+ * Software Foundation, version 3 of the License.
9
+ *
10
+ * Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
11
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12
+ * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
13
+ * details.
14
+ *
15
+ * You should have received a copy of the GNU Affero General Public License along
16
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
17
+ */
18
+
19
+ import React from 'react';
20
+ import ReactDOM from 'react-dom';
21
+ const MODAL_ID = 'canvas-rce-keyboard-shortcuts-container';
22
+ export default function (ed, document) {
23
+ return import('../../KeyboardShortcutModal').then(({
24
+ default: KeyboardShortcutModal
25
+ }) => {
26
+ let container = document.querySelector(`#${MODAL_ID}`);
27
+ if (!container) {
28
+ container = document.createElement('div');
29
+ container.id = MODAL_ID;
30
+ document.body.appendChild(container);
31
+ }
32
+ const handleDismiss = () => {
33
+ if (container) {
34
+ ReactDOM.unmountComponentAtNode(container);
35
+ }
36
+ ed.focus();
37
+ };
38
+ ReactDOM.render(/*#__PURE__*/React.createElement(KeyboardShortcutModal, {
39
+ open: true,
40
+ onClose: handleDismiss,
41
+ onDismiss: handleDismiss,
42
+ onExited: handleDismiss
43
+ }), container);
44
+ });
45
+ }
@@ -0,0 +1,43 @@
1
+ /*
2
+ * Copyright (C) 2024 - present Instructure, Inc.
3
+ *
4
+ * This file is part of Canvas.
5
+ *
6
+ * Canvas is free software: you can redistribute it and/or modify it under
7
+ * the terms of the GNU Affero General Public License as published by the Free
8
+ * Software Foundation, version 3 of the License.
9
+ *
10
+ * Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
11
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12
+ * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
13
+ * details.
14
+ *
15
+ * You should have received a copy of the GNU Affero General Public License along
16
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
17
+ */
18
+
19
+ import formatMessage from '../../../format-message';
20
+ // @ts-expect-error
21
+ import { IconKeyboardShortcutsLine } from '@instructure/ui-icons/es/svg';
22
+
23
+ // Dynamically import the callback to avoid module resolution issues
24
+ const clickCallbackPromise = import('./clickCallback');
25
+
26
+ // @ts-expect-error: tinymce is available as a global variable
27
+ tinymce.PluginManager.add('instructure_keyboard_shortcuts_header', function (ed) {
28
+ // Register custom icon
29
+ ed.ui.registry.addIcon('keyboard-shortcuts', IconKeyboardShortcutsLine.src);
30
+ ed.addCommand('instructureKeyboardShortcuts', () => {
31
+ clickCallbackPromise.then(module => module.default(ed, document));
32
+ });
33
+ ed.ui.registry.addButton('instructure_keyboard_shortcuts_header', {
34
+ icon: 'keyboard-shortcuts',
35
+ tooltip: formatMessage('View keyboard shortcuts'),
36
+ onAction: () => ed.execCommand('instructureKeyboardShortcuts')
37
+ });
38
+ ed.ui.registry.addMenuItem('instructure_keyboard_shortcuts_header', {
39
+ icon: 'keyboard-shortcuts',
40
+ text: formatMessage('Keyboard Shortcuts'),
41
+ onAction: () => ed.execCommand('instructureKeyboardShortcuts')
42
+ });
43
+ });
@@ -10,29 +10,22 @@ export default class ExternalToolDialog extends React.Component<ExternalToolDial
10
10
  static defaultProps: Partial<ExternalToolDialogProps>;
11
11
  state: ExternalToolDialogState;
12
12
  formRef: React.RefObject<HTMLFormElement>;
13
- beforeInfoAlertRef: React.RefObject<HTMLDivElement>;
14
- afterInfoAlertRef: React.RefObject<HTMLDivElement>;
15
13
  iframeRef: React.RefObject<HTMLIFrameElement>;
16
14
  open(button: RceToolWrapper): void;
17
15
  close(): void;
18
16
  handleBeforeUnload: (ev: Event) => string;
19
17
  private handleExternalContentReady;
20
18
  get resourceSelectionOrigin(): string;
21
- handlePostedMessage: (ev: Pick<MessageEvent, "origin" | "data">) => void;
19
+ handlePostedMessage: (ev: Pick<MessageEvent, "origin" | "data" | "source">) => void;
22
20
  handleClose: () => void;
23
21
  handleOpen: () => void;
24
22
  handleRemove: () => void;
25
- handleInfoAlertFocus: (ev: {
26
- target: Element;
27
- }) => void;
28
- handleInfoAlertBlur: () => void;
29
23
  calcIFrameHeight: () => string;
30
24
  render(): React.JSX.Element;
31
25
  }
32
26
  interface ExternalToolDialogState {
33
27
  open: boolean;
34
28
  button: RceToolWrapper | null;
35
- infoAlert: Element | null;
36
29
  form: ExternalToolDialogForm;
37
30
  iframeLoaded: boolean;
38
31
  }
@@ -21,7 +21,6 @@ import _pt from "prop-types";
21
21
 
22
22
  import { replaceTags } from '../../helpers/tags';
23
23
  import React, { createRef } from 'react';
24
- import { Alert } from '@instructure/ui-alerts';
25
24
  import { Spinner } from '@instructure/ui-spinner';
26
25
  import { Flex } from '@instructure/ui-flex';
27
26
  import ToolLaunchIframe from '../util/ToolLaunchIframe';
@@ -39,13 +38,10 @@ export default class ExternalToolDialog extends React.Component {
39
38
  this.state = {
40
39
  open: false,
41
40
  button: null,
42
- infoAlert: null,
43
41
  form: EMPTY_FORM,
44
42
  iframeLoaded: false
45
43
  };
46
44
  this.formRef = /*#__PURE__*/createRef();
47
- this.beforeInfoAlertRef = /*#__PURE__*/createRef();
48
- this.afterInfoAlertRef = /*#__PURE__*/createRef();
49
45
  this.iframeRef = /*#__PURE__*/createRef();
50
46
  this.handleBeforeUnload = ev => ev.returnValue = formatMessage('Changes you made may not be saved.');
51
47
  this.handleExternalContentReady = data => {
@@ -78,6 +74,7 @@ export default class ExternalToolDialog extends React.Component {
78
74
  this.close();
79
75
  };
80
76
  this.handlePostedMessage = ev => {
77
+ // messages from Canvas in the tool launch frame
81
78
  if (ev.origin === this.resourceSelectionOrigin) {
82
79
  const data = ev.data;
83
80
  if (data?.subject === 'LtiDeepLinkingResponse') {
@@ -89,6 +86,16 @@ export default class ExternalToolDialog extends React.Component {
89
86
  this.handleExternalContentReady(ev.data);
90
87
  }
91
88
  }
89
+ // messages from the tool
90
+ const data = ev.data;
91
+ if (data?.subject === 'lti.close') {
92
+ // Note we currently don't support this message from the forwarder
93
+ // iframe as it's not required by 1EdTech spec and requires more
94
+ // complicated source checking here (see INTEROP-9213)
95
+ if (ev.source === this.iframeRef?.current?.contentWindow) {
96
+ this.handleClose();
97
+ }
98
+ }
92
99
  };
93
100
  this.handleClose = () => {
94
101
  const msg = formatMessage('Are you sure you want to cancel? Changes you made may not be saved.');
@@ -108,12 +115,6 @@ export default class ExternalToolDialog extends React.Component {
108
115
  // force tinyMCE to redraw sticky toolbar otherwise it never goes away
109
116
  window.dispatchEvent(new Event('resize'));
110
117
  };
111
- this.handleInfoAlertFocus = ev => this.setState({
112
- infoAlert: ev.target
113
- });
114
- this.handleInfoAlertBlur = () => this.setState({
115
- infoAlert: null
116
- });
117
118
  this.calcIFrameHeight = () => {
118
119
  if (this.state.button?.use_tray) {
119
120
  return '100%';
@@ -239,16 +240,7 @@ export default class ExternalToolDialog extends React.Component {
239
240
  onClose: this.handleRemove,
240
241
  onCloseButton: this.handleClose,
241
242
  name: (_state$button$title = state.button?.title) !== null && _state$button$title !== void 0 ? _state$button$title : ' '
242
- }, /*#__PURE__*/React.createElement("div", {
243
- ref: this.beforeInfoAlertRef,
244
- tabIndex: 0 // eslint-disable-line jsx-a11y/no-noninteractive-tabindex
245
- ,
246
- onFocus: this.handleInfoAlertFocus,
247
- onBlur: this.handleInfoAlertBlur,
248
- className: this.beforeInfoAlertRef.current != null && state.infoAlert === this.beforeInfoAlertRef.current ? '' : 'screenreader-only'
249
- }, /*#__PURE__*/React.createElement(Alert, {
250
- margin: "small"
251
- }, formatMessage('The following content is partner provided'))), !state.iframeLoaded && /*#__PURE__*/React.createElement(Flex, {
243
+ }, !state.iframeLoaded && /*#__PURE__*/React.createElement(Flex, {
252
244
  alignItems: "center",
253
245
  justifyItems: "center"
254
246
  }, /*#__PURE__*/React.createElement(Flex.Item, null, /*#__PURE__*/React.createElement(Spinner, {
@@ -272,19 +264,7 @@ export default class ExternalToolDialog extends React.Component {
272
264
  onLoad: () => this.setState({
273
265
  iframeLoaded: true
274
266
  })
275
- }), /*#__PURE__*/React.createElement("div", {
276
- ref: this.afterInfoAlertRef,
277
- tabIndex: 0 // eslint-disable-line jsx-a11y/no-noninteractive-tabindex
278
- ,
279
- onFocus: this.handleInfoAlertFocus,
280
- onBlur: this.handleInfoAlertBlur,
281
- style: this.afterInfoAlertRef.current != null && state.infoAlert === this.afterInfoAlertRef.current ? {} : {
282
- bottom: '0'
283
- },
284
- className: this.afterInfoAlertRef.current != null && state.infoAlert === this.afterInfoAlertRef.current ? '' : 'screenreader-only'
285
- }, /*#__PURE__*/React.createElement(Alert, {
286
- margin: "small"
287
- }, formatMessage('The preceding content is partner provided')))));
267
+ })));
288
268
  }
289
269
  }
290
270
  ExternalToolDialog.propTypes = {
@@ -1,8 +1,7 @@
1
1
  import React from 'react';
2
- import { ReactNodeLike } from 'prop-types';
3
2
  import { ModalProps } from '@instructure/ui-modal/types';
4
3
  export declare function ExternalToolDialogModal(props: Pick<ModalProps, 'label' | 'open' | 'onOpen' | 'onClose' | 'mountNode'> & {
5
4
  onCloseButton: () => void;
6
5
  name: string;
7
- children: ReactNodeLike;
6
+ children: React.ReactNode;
8
7
  }): React.JSX.Element;
@@ -30,7 +30,8 @@ export function ExternalToolDialogModal(props) {
30
30
  open: props.open,
31
31
  onOpen: props.onOpen,
32
32
  onClose: props.onClose,
33
- mountNode: props.mountNode
33
+ mountNode: props.mountNode,
34
+ shouldCloseOnDocumentClick: false
34
35
  }, /*#__PURE__*/React.createElement(Modal.Header, null, /*#__PURE__*/React.createElement(Heading, null, props.name), /*#__PURE__*/React.createElement(CloseButton, {
35
36
  placement: "end",
36
37
  offset: "medium",