@instructure/canvas-rce 7.3.1 → 8.0.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 +65 -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 +86 -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
@@ -0,0 +1,3 @@
1
+ export declare function mapStudioEmbedOptions(embedOptions: Record<string, boolean>): string[];
2
+ export declare function readViewerRestrictions(viewerRestrictions: Record<string, boolean>): string[];
3
+ export declare function mapViewerRestrictions(viewerRestrictions: string[]): Record<string, boolean>;
@@ -0,0 +1,31 @@
1
+ /*
2
+ * Copyright (C) 2026 - 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
+ export function mapStudioEmbedOptions(embedOptions) {
20
+ return embedOptions ? Object.entries(embedOptions).filter(([, v]) => v).map(([k]) => k) : [];
21
+ }
22
+ const VIEWER_RESTRICTIONS = ['show_rolling_transcript'];
23
+ export function readViewerRestrictions(viewerRestrictions) {
24
+ return viewerRestrictions ? Object.keys(viewerRestrictions).filter(k => viewerRestrictions[k]) : [];
25
+ }
26
+ export function mapViewerRestrictions(viewerRestrictions) {
27
+ return VIEWER_RESTRICTIONS.reduce((acc, restriction) => {
28
+ acc[restriction] = viewerRestrictions.includes(restriction);
29
+ return acc;
30
+ }, {});
31
+ }
@@ -17,19 +17,29 @@
17
17
  */
18
18
 
19
19
  import tinymce from 'tinymce';
20
- import { isStudioEmbeddedMedia, handleBeforeObjectSelected, notifyStudioEmbedTypeChange, updateStudioIframeDimensions, isValidDimension, isValidEmbedType, isValidResizable } from '../shared/StudioLtiSupportUtils';
20
+ import { isStudioEmbeddedMedia, isImprovedStudioEmbed, handleBeforeObjectSelected, notifyStudioEmbedTypeChange, updateStudioIframeDimensions, validateStudioEmbedTypeChangeResponse } from '../shared/StudioLtiSupportUtils';
21
21
  import VideoTrayController from '../instructure_record/VideoOptionsTray/TrayController';
22
22
  import formatMessage from '../../../format-message';
23
23
  import RCEGlobals from '../../RCEGlobals';
24
24
  import { thumbnailViewIcon, learnViewIcon, collabViewIcon, optionsIcon, removeIcon } from './studioToolbarIcons';
25
25
  const studioTrayController = new VideoTrayController();
26
26
  const handleStudioMessage = e => {
27
- if (e.data && e.data.subject === 'studio.embedTypeChanged.response' && isValidDimension(e.data.width) && isValidDimension(e.data.height) && isValidEmbedType(e.data.embedType)) {
28
- // resizable is optional - only pass it if it's a valid boolean
29
- const resizable = isValidResizable(e.data.resizable) ? e.data.resizable : undefined;
30
- updateStudioIframeDimensions(tinymce.activeEditor, e.data.width, e.data.height, e.data.embedType, resizable);
27
+ // can be extended in the future to handle more message types from Studio LTI
28
+ switch (e.data?.subject) {
29
+ case 'studio.embedTypeChanged.response':
30
+ if (validateStudioEmbedTypeChangeResponse(e.data)) {
31
+ updateStudioIframeDimensions(tinymce.activeEditor, e.data);
32
+ }
33
+ break;
34
+ default:
35
+ return;
31
36
  }
32
37
  };
38
+ const isEmbedButtonActive = (buttonName, currentSelectedElement) => {
39
+ return (currentSelectedElement.getAttribute('data-mce-p-src') || '').includes(buttonName);
40
+ };
41
+ const isImprovedStudioEmbeddedMedia = element => isStudioEmbeddedMedia(element) && isImprovedStudioEmbed(element);
42
+ const isNonImprovedStudioEmbeddedMedia = element => isStudioEmbeddedMedia(element) && !isImprovedStudioEmbed(element);
33
43
  tinymce.PluginManager.add('instructure_studio_media_options', function (ed) {
34
44
  if (RCEGlobals.getFeatures().rce_studio_embed_improvements) {
35
45
  window.addEventListener('message', handleStudioMessage);
@@ -48,6 +58,16 @@ tinymce.PluginManager.add('instructure_studio_media_options', function (ed) {
48
58
  background-color: #2B7ABC;
49
59
  color: white;
50
60
  }
61
+
62
+ .tox .tox-pop .tox-tbtn.tox-tbtn--enabled {
63
+ background-color: #6A7883;
64
+ color: white;
65
+ }
66
+
67
+ .tox .tox-pop .tox-tbtn.tox-tbtn--enabled:hover {
68
+ background-color: #2B7ABC;
69
+ color: white;
70
+ }
51
71
  `;
52
72
  document.head.appendChild(style);
53
73
  }
@@ -57,39 +77,66 @@ tinymce.PluginManager.add('instructure_studio_media_options', function (ed) {
57
77
  ed.ui.registry.addIcon('collab-view-icon', collabViewIcon);
58
78
  ed.ui.registry.addIcon('options-icon', optionsIcon);
59
79
  ed.ui.registry.addIcon('remove-icon', removeIcon);
60
- ed.ui.registry.addButton('thumbnail-view', {
80
+ ed.ui.registry.addToggleButton('thumbnail-view', {
81
+ onSetup: buttonApi => {
82
+ buttonApi.setActive(isEmbedButtonActive('thumbnail_embed', ed.selection.getNode()));
83
+ const editorEventCallback = eventApi => {
84
+ buttonApi.setActive(isEmbedButtonActive('thumbnail_embed', eventApi.element));
85
+ };
86
+ ed.on('NodeChange', editorEventCallback);
87
+ return () => ed.off('NodeChange', editorEventCallback);
88
+ },
61
89
  onAction() {
62
90
  notifyStudioEmbedTypeChange(ed, 'thumbnail_embed');
63
91
  },
64
92
  icon: 'thumbnail-view-icon',
65
- text: formatMessage('Thumbnail'),
66
- tooltip: formatMessage('Thumbnail')
93
+ text: formatMessage('Thumbnail')
67
94
  });
68
- ed.ui.registry.addButton('learn-view', {
95
+ ed.ui.registry.addToggleButton('learn-view', {
96
+ onSetup: buttonApi => {
97
+ buttonApi.setActive(isEmbedButtonActive('learn_embed', ed.selection.getNode()));
98
+ const editorEventCallback = eventApi => {
99
+ buttonApi.setActive(isEmbedButtonActive('learn_embed', eventApi.element));
100
+ };
101
+ ed.on('NodeChange', editorEventCallback);
102
+ return () => ed.off('NodeChange', editorEventCallback);
103
+ },
69
104
  onAction() {
70
105
  notifyStudioEmbedTypeChange(ed, 'learn_embed');
71
106
  },
72
107
  icon: 'learn-view-icon',
73
- text: formatMessage('Learn'),
74
- tooltip: formatMessage('Learn')
108
+ text: formatMessage('Learn')
75
109
  });
76
- ed.ui.registry.addButton('collab-view', {
110
+ ed.ui.registry.addToggleButton('collab-view', {
111
+ onSetup: buttonApi => {
112
+ buttonApi.setActive(isEmbedButtonActive('collaboration_embed', ed.selection.getNode()));
113
+ const editorEventCallback = eventApi => {
114
+ buttonApi.setActive(isEmbedButtonActive('collaboration_embed', eventApi.element));
115
+ };
116
+ ed.on('NodeChange', editorEventCallback);
117
+ return () => ed.off('NodeChange', editorEventCallback);
118
+ },
77
119
  onAction() {
78
120
  notifyStudioEmbedTypeChange(ed, 'collaboration_embed');
79
121
  },
80
122
  icon: 'collab-view-icon',
81
- text: formatMessage('Collab'),
82
- tooltip: formatMessage('Collab')
123
+ text: formatMessage('Collab')
83
124
  });
125
+ const openStudioTray = () => {
126
+ if (!studioTrayController.isOpen) {
127
+ studioTrayController.showTrayForEditor(ed);
128
+ ed.focus();
129
+ }
130
+ };
84
131
  ed.ui.registry.addButton('studio-media-options', {
85
- onAction() {
86
- if (!studioTrayController.isOpen) {
87
- studioTrayController.showTrayForEditor(ed);
88
- }
89
- },
132
+ onAction: openStudioTray,
133
+ icon: 'options-icon',
134
+ text: formatMessage('Options')
135
+ });
136
+ ed.ui.registry.addButton('studio-media-options-basic', {
137
+ onAction: openStudioTray,
90
138
  icon: 'options-icon',
91
- text: formatMessage('Options'),
92
- tooltip: formatMessage('Options')
139
+ text: formatMessage('Studio Media Options')
93
140
  });
94
141
  ed.ui.registry.addButton('remove-studio-media', {
95
142
  onAction() {
@@ -108,22 +155,31 @@ tinymce.PluginManager.add('instructure_studio_media_options', function (ed) {
108
155
  }
109
156
  },
110
157
  icon: 'remove-icon',
111
- text: formatMessage('Remove'),
112
- tooltip: formatMessage('Remove Studio Media')
158
+ text: formatMessage('Remove')
113
159
  });
114
160
  ed.ui.registry.addContextToolbar('studio-extra-toolbar', {
115
161
  items: 'thumbnail-view | learn-view | collab-view | studio-media-options | remove-studio-media',
116
162
  position: 'node',
117
- predicate: isStudioEmbeddedMedia,
163
+ predicate: isImprovedStudioEmbeddedMedia,
164
+ scope: 'node'
165
+ });
166
+ ed.ui.registry.addContextToolbar('studio-basic-toolbar', {
167
+ items: 'studio-media-options-basic | remove-studio-media',
168
+ position: 'node',
169
+ predicate: isNonImprovedStudioEmbeddedMedia,
118
170
  scope: 'node'
119
171
  });
172
+ ed.on('NodeChange', () => {
173
+ if (studioTrayController.isOpen) {
174
+ studioTrayController.hideTrayForEditor(ed, true);
175
+ }
176
+ });
120
177
  } else {
121
178
  ed.ui.registry.addButton('studio-media-options', {
122
179
  onAction() {
123
180
  studioTrayController.showTrayForEditor(ed);
124
181
  },
125
- text: formatMessage('Studio Media Options'),
126
- tooltip: formatMessage('Show Studio media options')
182
+ text: formatMessage('Studio Media Options')
127
183
  });
128
184
  ed.ui.registry.addContextToolbar('studio-media-options-toolbar', {
129
185
  items: 'studio-media-options',
@@ -23,10 +23,11 @@ export function asLink($element: any, editor: any): {
23
23
  fileName: any;
24
24
  published: boolean;
25
25
  } | null;
26
- export function asVideoElement($element: any): {
26
+ export function asVideoElement($element: any, isStudioVideo?: boolean): {
27
27
  $element: any;
28
28
  type: string;
29
29
  id: string | null;
30
+ viewerRestrictions: any;
30
31
  titleText: any;
31
32
  appliedHeight: any;
32
33
  appliedWidth: any;
@@ -37,6 +38,10 @@ export function asVideoElement($element: any): {
37
38
  export function asAudioElement($element: any): {
38
39
  titleText: any;
39
40
  id: any;
41
+ containerDimensions: {
42
+ width: any;
43
+ height: any;
44
+ };
40
45
  } | null;
41
46
  export function getContentFromElement($element: any, editor: any): {
42
47
  $element: any;
@@ -16,12 +16,12 @@
16
16
  * with this program. If not, see <http://www.gnu.org/licenses/>.
17
17
  */
18
18
 
19
- import { fromImageEmbed, fromVideoEmbed } from '../instructure_image/ImageEmbedOptions';
20
- import { isOnlyTextSelected } from '../../contentInsertionUtils';
21
19
  import formatMessage from '../../../format-message';
22
- import { isStudioEmbeddedMedia } from './StudioLtiSupportUtils';
23
20
  import { parseUrlPath } from '../../../util/url-util';
21
+ import { isOnlyTextSelected } from '../../contentInsertionUtils';
22
+ import { fromImageEmbed, fromVideoEmbed } from '../instructure_image/ImageEmbedOptions';
24
23
  import { findMediaPlayerIframe } from './iframeUtils';
24
+ import { isStudioEmbeddedMedia } from './StudioLtiSupportUtils';
25
25
  const FILE_DOWNLOAD_PATH_REGEX = /^\/(courses\/\d+\/)?files\/\d+\/download$/;
26
26
  export const LINK_TYPE = 'link';
27
27
  export const FILE_LINK_TYPE = 'file-link';
@@ -91,7 +91,8 @@ export function asLink($element, editor) {
91
91
  // and it's attributes, even though this could change with future
92
92
  // tinymce releases.
93
93
  // see https://github.com/tinymce/tinymce/issues/5181
94
- export function asVideoElement($element) {
94
+ export function asVideoElement($element, isStudioVideo = false) {
95
+ var _$videoElem$contentWi;
95
96
  const $videoElem = findMediaPlayerIframe($element);
96
97
  if (!isVideoElement($videoElem) && !isStudioEmbeddedMedia($videoElem)) {
97
98
  return null;
@@ -100,19 +101,26 @@ export function asVideoElement($element) {
100
101
  ...fromVideoEmbed($videoElem),
101
102
  $element,
102
103
  type: VIDEO_EMBED_TYPE,
103
- id: $videoElem.parentElement?.getAttribute('data-mce-p-data-media-id') || $videoElem.getAttribute('data-mce-p-data-media-id')
104
+ id: $videoElem.parentElement?.getAttribute('data-mce-p-data-media-id') || $videoElem.getAttribute('data-mce-p-data-media-id'),
105
+ viewerRestrictions: isStudioVideo ? {} : (_$videoElem$contentWi = $videoElem.contentWindow?.['env'.toUpperCase()]?.media_object?.viewer_restrictions) !== null && _$videoElem$contentWi !== void 0 ? _$videoElem$contentWi : {}
104
106
  };
105
107
  }
106
108
  export function asAudioElement($element) {
109
+ var _$audioIframe$content;
107
110
  if (!$element) {
108
111
  return null;
109
112
  }
110
113
  const $audioIframe = $element.tagName === 'IFRAME' ? $element : $element.firstElementChild;
111
114
  const $tinymceIframeShim = $audioIframe.parentElement;
112
115
  const title = ($audioIframe.getAttribute('title') || $tinymceIframeShim.getAttribute('data-mce-p-title') || '').replace(formatMessage('Video player for '), '');
116
+ const containerRect = $element.getBoundingClientRect();
113
117
  const audioOptions = {
114
118
  titleText: title,
115
- id: $element.parentElement?.getAttribute('data-mce-p-data-media-id') || $element.getAttribute('data-mce-p-data-media-id')
119
+ id: $element.parentElement?.getAttribute('data-mce-p-data-media-id') || $element.getAttribute('data-mce-p-data-media-id'),
120
+ containerDimensions: {
121
+ width: containerRect.width,
122
+ height: containerRect.height
123
+ }
116
124
  };
117
125
  if ($audioIframe.tagName === 'IFRAME') {
118
126
  const audioDoc = $audioIframe.contentDocument;
@@ -129,6 +137,7 @@ export function asAudioElement($element) {
129
137
  if (matches) {
130
138
  audioOptions.attachmentId = matches[1];
131
139
  }
140
+ audioOptions.viewerRestrictions = (_$audioIframe$content = $audioIframe.contentWindow?.['env'.toUpperCase()]?.media_object?.viewer_restrictions) !== null && _$audioIframe$content !== void 0 ? _$audioIframe$content : {};
132
141
  return audioOptions;
133
142
  }
134
143
  function asText($element, editor) {
@@ -18,7 +18,6 @@
18
18
 
19
19
  import React from 'react';
20
20
  import { func, shape, string, object } from 'prop-types';
21
- import { ScreenReaderContent } from '@instructure/ui-a11y-content';
22
21
  import { NumberInput } from '@instructure/ui-number-input';
23
22
  export default function DimensionInput(props) {
24
23
  const {
@@ -43,7 +42,7 @@ export default function DimensionInput(props) {
43
42
  }
44
43
  return /*#__PURE__*/React.createElement(NumberInput, {
45
44
  allowStringValue: true,
46
- renderLabel: /*#__PURE__*/React.createElement(ScreenReaderContent, null, label),
45
+ renderLabel: label,
47
46
  onChange: handleChange,
48
47
  onDecrement: handleDecrement,
49
48
  onIncrement: handleIncrement,
@@ -17,13 +17,13 @@
17
17
  */
18
18
 
19
19
  import React from 'react';
20
- import { bool, func, number, shape, string, object } from 'prop-types';
20
+ import { bool, func, number, object, shape, string } from 'prop-types';
21
21
  import { ScreenReaderContent } from '@instructure/ui-a11y-content';
22
+ import { Flex } from '@instructure/ui-flex';
22
23
  import { FormFieldGroup } from '@instructure/ui-form-field';
23
24
  import { IconLockLine, IconWarningSolid } from '@instructure/ui-icons';
24
- import { Flex } from '@instructure/ui-flex';
25
- import { Text } from '@instructure/ui-text';
26
25
  import { RadioInput, RadioInputGroup } from '@instructure/ui-radio-input';
26
+ import { Text } from '@instructure/ui-text';
27
27
  import formatMessage from '../../../../format-message';
28
28
  import DimensionInput from './DimensionInput';
29
29
  export { default as useDimensionsState } from './useDimensionsState';
@@ -39,8 +39,9 @@ const errorMessage = message => {
39
39
  }, message));
40
40
  };
41
41
  const getMessage = (dimensionsState, minWidth, minHeight, minPercentage) => {
42
+ const baseHint = formatMessage('Aspect ratio will be preserved');
42
43
  let result = {
43
- text: formatMessage('Aspect ratio will be preserved'),
44
+ text: baseHint,
44
45
  type: 'hint'
45
46
  };
46
47
  if (dimensionsState.usePercentageUnits) {
@@ -102,11 +103,9 @@ export default function DimensionsInput(props) {
102
103
  };
103
104
  return /*#__PURE__*/React.createElement(Flex, {
104
105
  direction: "column"
105
- }, /*#__PURE__*/React.createElement(Flex.Item, {
106
+ }, !hidePercentage && /*#__PURE__*/React.createElement(Flex.Item, {
106
107
  padding: "small"
107
- }, hidePercentage ? /*#__PURE__*/React.createElement(Text, {
108
- weight: "bold"
109
- }, formatMessage('Custom width and height (Pixels)')) : /*#__PURE__*/React.createElement(RadioInputGroup, {
108
+ }, /*#__PURE__*/React.createElement(RadioInputGroup, {
110
109
  "data-testid": "dimension-type",
111
110
  name: "dimension-type",
112
111
  description: formatMessage('Dimension Type'),
@@ -119,11 +118,11 @@ export default function DimensionsInput(props) {
119
118
  label: formatMessage('Pixels'),
120
119
  value: "pixels"
121
120
  }))), /*#__PURE__*/React.createElement(Flex.Item, {
122
- padding: "small"
121
+ overflowX: "hidden"
123
122
  }, /*#__PURE__*/React.createElement(FormFieldGroup, {
124
- description: /*#__PURE__*/React.createElement(ScreenReaderContent, null, formatMessage('Dimensions'))
123
+ description: /*#__PURE__*/React.createElement(ScreenReaderContent, null, formatMessage('Custom Dimensions'))
125
124
  }, /*#__PURE__*/React.createElement(Flex, {
126
- alignItems: "start",
125
+ alignItems: "end",
127
126
  direction: "row",
128
127
  "data-testid": "input-number-container"
129
128
  }, dimensionsState.usePercentageUnits ? /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Flex.Item, {
@@ -145,7 +144,7 @@ export default function DimensionsInput(props) {
145
144
  messages: [secondaryMessage],
146
145
  dimensionsRef: dimensionsRef
147
146
  })), /*#__PURE__*/React.createElement(Flex.Item, {
148
- padding: "x-small small"
147
+ padding: "0 x-small small"
149
148
  }, /*#__PURE__*/React.createElement(IconLockLine, null)), /*#__PURE__*/React.createElement(Flex.Item, {
150
149
  shouldShrink: true
151
150
  }, /*#__PURE__*/React.createElement(DimensionInput, {
@@ -1,4 +1,4 @@
1
- export default function useDimensionsState(initialDimensions: any, constraints: any): {
1
+ export default function useDimensionsState(initialDimensions: any, constraints: any, options?: {}): {
2
2
  widthState: {
3
3
  inputValue: string;
4
4
  addOffset(offset: any): void;
@@ -34,7 +34,8 @@ function parseAsInteger(inputString) {
34
34
  function inputValueFor(initialNumber) {
35
35
  return Number.isFinite(initialNumber) ? `${initialNumber}` : '';
36
36
  }
37
- export default function useDimensionsState(initialDimensions, constraints) {
37
+ export default function useDimensionsState(initialDimensions, constraints, options = {}) {
38
+ var _options$scaleFns$hei, _options$scaleFns$wid;
38
39
  const {
39
40
  appliedHeight,
40
41
  appliedWidth,
@@ -73,8 +74,8 @@ export default function useDimensionsState(initialDimensions, constraints) {
73
74
  percentage: minPercentage
74
75
  };
75
76
  const dimensionScaleFns = {
76
- height: scaleForHeight,
77
- width: scaleForWidth
77
+ height: (_options$scaleFns$hei = options.scaleFns?.height) !== null && _options$scaleFns$hei !== void 0 ? _options$scaleFns$hei : scaleForHeight,
78
+ width: (_options$scaleFns$wid = options.scaleFns?.width) !== null && _options$scaleFns$wid !== void 0 ? _options$scaleFns$wid : scaleForWidth
78
79
  };
79
80
  function updateDimensions(attributes) {
80
81
  setDimensions({
@@ -18,14 +18,29 @@ export interface StudioMediaOptionsAttributes {
18
18
  export declare const parsedStudioOptionsPropType: import("prop-types").Requireable<import("prop-types").InferProps<{
19
19
  resizable: import("prop-types").Validator<boolean>;
20
20
  convertibleToLink: import("prop-types").Validator<boolean>;
21
+ isImprovedEmbed: import("prop-types").Validator<boolean>;
21
22
  }>>;
22
23
  export type ParsedStudioOptions = {
23
24
  resizable: boolean;
24
25
  convertibleToLink: boolean;
26
+ isImprovedEmbed: boolean;
27
+ embedOptions: StudioEmbedOptions;
28
+ };
29
+ type ValidStudioEmbedType = 'thumbnail_embed' | 'learn_embed' | 'collaboration_embed';
30
+ export type StudioEmbedTypeChangedResponse = {
31
+ subject: 'studio.embedTypeChanged.response';
32
+ width: number;
33
+ height: number;
34
+ embedType: ValidStudioEmbedType;
35
+ resizable?: boolean;
25
36
  };
26
37
  export declare function isStudioContentItemCustomJson(input: any): input is StudioContentItemCustomJson;
38
+ export declare const isValidEmbedType: (embedType: any) => embedType is ValidStudioEmbedType;
39
+ export declare const isValidDimension: (value: any) => value is number;
40
+ export declare const isValidResizable: (value: any) => value is boolean;
27
41
  export declare function studioAttributesFrom(customJson: StudioContentItemCustomJson): StudioMediaOptionsAttributes;
28
42
  export declare function displayStyleFrom(studioAttributes: StudioMediaOptionsAttributes | null): 'inline-block' | '';
43
+ export declare function isImprovedStudioEmbed(element: Element): boolean;
29
44
  export declare function isStudioEmbeddedMedia(element: Element): boolean;
30
45
  export declare function parseStudioOptions(element: Element | null): ParsedStudioOptions;
31
46
  /**
@@ -36,11 +51,17 @@ export declare function parseStudioOptions(element: Element | null): ParsedStudi
36
51
  */
37
52
  export declare function handleBeforeObjectSelected(e: EditorEvent<Events.ObjectSelectedEvent>): void;
38
53
  export declare function findStudioLtiIframeFromSelection(selectedNode: Node): HTMLIFrameElement | null;
39
- export declare const notifyStudioEmbedTypeChange: (editor: Editor, embedType: "thumbnail_embed" | "learn_embed" | "collaboration_embed") => void;
40
54
  export type EmbedType = 'thumbnail_embed' | 'learn_embed' | 'collaboration_embed';
41
- export declare const updateStudioIframeDimensions: (editor: Editor, width: number, height: number, embedType: EmbedType, resizable?: boolean) => void;
42
- type ValidStudioEmbedType = 'thumbnail_embed' | 'learn_embed' | 'collaboration_embed';
43
- export declare const isValidEmbedType: (embedType: any) => embedType is ValidStudioEmbedType;
44
- export declare const isValidDimension: (value: any) => value is number;
45
- export declare const isValidResizable: (value: any) => value is boolean;
55
+ export declare const notifyStudioEmbedTypeChange: (editor: Editor, embedType: EmbedType) => void;
56
+ export declare const validateStudioEmbedTypeChangeResponse: (data: any) => data is StudioEmbedTypeChangedResponse;
57
+ export declare const updateStudioIframeDimensions: (editor: Editor, data: StudioEmbedTypeChangedResponse) => void;
58
+ export type StudioEmbedOptions = {
59
+ enableMediaDownload: boolean;
60
+ enableTranscriptDownload: boolean;
61
+ lockSpeed: boolean;
62
+ isExternal: boolean;
63
+ showRollingTranscript: boolean;
64
+ };
65
+ export declare function validateStudioEmbedOptions(input: any): input is StudioEmbedOptions;
66
+ export declare function updateStudioEmbedOptions(editor: Editor, embedOptions: StudioEmbedOptions, videoContainer: Element | null): void;
46
67
  export {};
@@ -28,11 +28,21 @@ import { findMediaPlayerIframe } from './iframeUtils';
28
28
 
29
29
  export const parsedStudioOptionsPropType = shape({
30
30
  resizable: bool.isRequired,
31
- convertibleToLink: bool.isRequired
31
+ convertibleToLink: bool.isRequired,
32
+ isImprovedEmbed: bool.isRequired
32
33
  });
33
34
  export function isStudioContentItemCustomJson(input) {
34
35
  return typeof input === 'object' && input.source === 'studio';
35
36
  }
37
+ export const isValidEmbedType = embedType => {
38
+ return typeof embedType === 'string' && ['thumbnail_embed', 'learn_embed', 'collaboration_embed'].includes(embedType);
39
+ };
40
+ export const isValidDimension = value => {
41
+ return typeof value === 'number' && !isNaN(value) && isFinite(value) && value > 0;
42
+ };
43
+ export const isValidResizable = value => {
44
+ return typeof value === 'boolean';
45
+ };
36
46
  export function studioAttributesFrom(customJson) {
37
47
  var _customJson$resizable, _customJson$enableMed;
38
48
  return {
@@ -45,6 +55,10 @@ export function displayStyleFrom(studioAttributes) {
45
55
  if (!studioAttributes) return '';
46
56
  return studioAttributes['data-studio-resizable'] || studioAttributes['data-studio-tray-enabled'] ? 'inline-block' : '';
47
57
  }
58
+ export function isImprovedStudioEmbed(element) {
59
+ const src = element.getAttribute('data-mce-p-src') || '';
60
+ return src.includes('thumbnail_embed') || src.includes('learn_embed') || src.includes('collaboration_embed');
61
+ }
48
62
  export function isStudioEmbeddedMedia(element) {
49
63
  // Borrowing this structure from isMediaElement in ContentSelection.js
50
64
  const tinymceIframeShim = element?.tagName === 'IFRAME' ? element?.parentElement : element;
@@ -55,9 +69,26 @@ export function isStudioEmbeddedMedia(element) {
55
69
  }
56
70
  export function parseStudioOptions(element) {
57
71
  const tinymceIframeShim = element?.tagName === 'IFRAME' ? element?.parentElement : element;
72
+ const embedOptions = {};
73
+ const href = tinymceIframeShim?.getAttribute('data-mce-p-src');
74
+ if (href) {
75
+ // parse out embed options from url params
76
+ const urlMatch = href.match(/url=([^&]*)$/);
77
+ if (urlMatch) {
78
+ const url = new URL(decodeURIComponent(urlMatch[1]));
79
+ const params = url.searchParams;
80
+ embedOptions['enableMediaDownload'] = params.get('custom_arc_display_download') === 'true';
81
+ embedOptions['enableTranscriptDownload'] = params.get('custom_arc_transcript_downloadable') === 'true';
82
+ embedOptions['showRollingTranscript'] = params.get('custom_arc_show_rolling_transcript') === 'true';
83
+ embedOptions['lockSpeed'] = params.get('custom_arc_lock_speed') === 'true';
84
+ embedOptions['isExternal'] = params.get('custom_arc_is_external') === 'true';
85
+ }
86
+ }
58
87
  return {
59
88
  resizable: tinymceIframeShim?.getAttribute('data-mce-p-data-studio-resizable') === 'true',
60
- convertibleToLink: tinymceIframeShim?.getAttribute('data-mce-p-data-studio-convertible-to-link') === 'true'
89
+ convertibleToLink: tinymceIframeShim?.getAttribute('data-mce-p-data-studio-convertible-to-link') === 'true',
90
+ isImprovedEmbed: tinymceIframeShim ? isImprovedStudioEmbed(tinymceIframeShim) : false,
91
+ embedOptions
61
92
  };
62
93
  }
63
94
 
@@ -123,14 +154,23 @@ export const notifyStudioEmbedTypeChange = (editor, embedType) => {
123
154
  }, '*');
124
155
  }
125
156
  };
126
- export const updateStudioIframeDimensions = (editor, width, height, embedType, resizable) => {
157
+ export const validateStudioEmbedTypeChangeResponse = data => {
158
+ return isValidDimension(data.width) && isValidDimension(data.height) && isValidEmbedType(data.embedType);
159
+ };
160
+ export const updateStudioIframeDimensions = (editor, data) => {
161
+ const {
162
+ width,
163
+ height,
164
+ embedType,
165
+ resizable
166
+ } = data;
127
167
  const selectedNode = editor.selection.getNode();
128
168
  const videoContainer = findMediaPlayerIframe(selectedNode);
129
169
  if (videoContainer?.tagName !== 'IFRAME') {
130
170
  return;
131
171
  }
132
172
  const tinymceIframeShim = videoContainer.parentElement;
133
- if (!tinymceIframeShim) {
173
+ if (!tinymceIframeShim || !videoContainer) {
134
174
  return;
135
175
  }
136
176
  editor.dom.setStyles(tinymceIframeShim, {
@@ -141,7 +181,7 @@ export const updateStudioIframeDimensions = (editor, width, height, embedType, r
141
181
  width: `${width}px`,
142
182
  height: `${height}px`
143
183
  });
144
- if (resizable !== undefined) {
184
+ if (resizable !== undefined && isValidResizable(resizable)) {
145
185
  // Update both the actual attribute and the TinyMCE prefixed version
146
186
  // This ensures they stay in sync when content is saved and reloaded
147
187
  editor.dom.setAttrib(tinymceIframeShim, 'data-studio-resizable', String(resizable));
@@ -172,12 +212,41 @@ export const updateStudioIframeDimensions = (editor, width, height, embedType, r
172
212
  height
173
213
  });
174
214
  };
175
- export const isValidEmbedType = embedType => {
176
- return typeof embedType === 'string' && ['thumbnail_embed', 'learn_embed', 'collaboration_embed'].includes(embedType);
215
+ const embedOptionsKeyMap = {
216
+ enableMediaDownload: 'custom_arc_display_download',
217
+ enableTranscriptDownload: 'custom_arc_transcript_downloadable',
218
+ showRollingTranscript: 'custom_arc_show_rolling_transcript',
219
+ lockSpeed: 'custom_arc_lock_speed',
220
+ isExternal: 'custom_arc_is_external'
177
221
  };
178
- export const isValidDimension = value => {
179
- return typeof value === 'number' && !isNaN(value) && isFinite(value) && value > 0;
180
- };
181
- export const isValidResizable = value => {
182
- return typeof value === 'boolean';
183
- };
222
+ export function validateStudioEmbedOptions(input) {
223
+ return typeof input === 'object' && (Object.keys(input).length === 0 || typeof input.isExternal === 'boolean' || typeof input.enableMediaDownload === 'boolean' || typeof input.enableTranscriptDownload === 'boolean' || typeof input.showRollingTranscript === 'boolean' || typeof input.lockSpeed === 'boolean');
224
+ }
225
+ export function updateStudioEmbedOptions(editor, embedOptions, videoContainer) {
226
+ if (videoContainer?.tagName !== 'IFRAME') {
227
+ return;
228
+ }
229
+ const tinymceIframeShim = videoContainer.parentElement;
230
+ if (!tinymceIframeShim) {
231
+ return;
232
+ }
233
+ const href = editor.dom.getAttrib(tinymceIframeShim, 'data-mce-p-src');
234
+ if (!href) {
235
+ return;
236
+ }
237
+ const urlMatch = href.match(/url=([^&]*)$/);
238
+ const url = new URL(decodeURIComponent(urlMatch ? urlMatch[1] : ''));
239
+ const params = url.searchParams;
240
+ for (const param of Object.values(embedOptionsKeyMap)) {
241
+ params.delete(param);
242
+ }
243
+ if (embedOptions) {
244
+ for (const [option, param] of Object.entries(embedOptionsKeyMap)) {
245
+ if (embedOptions[option]) {
246
+ params.set(param, 'true');
247
+ }
248
+ }
249
+ }
250
+ const newHref = href.replace(/(url=)(.*)$/, `$1${encodeURIComponent(url.toString())}`);
251
+ editor.dom.setAttrib(tinymceIframeShim, 'data-mce-p-src', newHref);
252
+ }
@@ -21,7 +21,6 @@ import React, { useEffect, useState } from 'react';
21
21
  import ReactDOM from 'react-dom';
22
22
  import { px } from '@instructure/ui-utils';
23
23
  import indicatorRegion from '../../../indicatorRegion';
24
- import { isAudioOrVideo, isImage } from '../fileTypeUtils';
25
24
  import indicate from '../../../../common/indicate';
26
25
  import { StoreProvider } from '../StoreContext';
27
26
  import Bridge from '../../../../bridge';
@@ -63,13 +62,7 @@ _source, afterInsert = () => undefined) => {
63
62
  displayAs,
64
63
  usageRights: uploadData?.usageRights?.usageRight === 'choose' ? undefined : uploadData?.usageRights
65
64
  };
66
- let tabContext = 'documents';
67
- if (isImage(theFile.type)) {
68
- tabContext = 'images';
69
- } else if (isAudioOrVideo(theFile.type)) {
70
- tabContext = 'media';
71
- }
72
- storeProps.startMediaUpload(tabContext, fileMetaData);
65
+ storeProps.startMediaUpload(fileMetaData);
73
66
  break;
74
67
  }
75
68
  case 'URL':
package/es/rce/style.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- export default function buildStyle(): {
1
+ /** @param {boolean} useHighContrast @param {string} [fontFamily] */
2
+ export default function buildStyle(useHighContrast?: boolean, fontFamily?: string): {
2
3
  css: string;
3
4
  classNames: {
4
5
  root: string;