@instructure/canvas-rce 7.2.0 → 7.3.1
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.
- package/CHANGELOG.md +41 -0
- package/es/enhance-user-content/doc_previews.js +6 -14
- package/es/enhance-user-content/enhance_user_content.js +1 -1
- package/es/enhance-user-content/instructure_helper.js +1 -0
- package/es/index.d.ts +1 -0
- package/es/index.js +2 -1
- package/es/rce/AlertMessageArea.js +1 -3
- package/es/rce/KeyboardShortcutModal.js +1 -1
- package/es/rce/RCEGlobals.d.ts +2 -0
- package/es/rce/RCEGlobals.js +1 -0
- package/es/rce/RCEVariants.js +3 -3
- package/es/rce/RCEWrapper.d.ts +3 -1
- package/es/rce/RCEWrapper.js +43 -11
- package/es/rce/plugins/instructure_keyboard_shortcuts_header/clickCallback.d.ts +2 -0
- package/es/rce/plugins/instructure_keyboard_shortcuts_header/clickCallback.js +45 -0
- package/es/rce/plugins/instructure_keyboard_shortcuts_header/plugin.d.ts +1 -0
- package/es/rce/plugins/instructure_keyboard_shortcuts_header/plugin.js +43 -0
- package/es/rce/plugins/instructure_record/AudioOptionsTray/TrayController.d.ts +1 -1
- package/es/rce/plugins/instructure_record/AudioOptionsTray/TrayController.js +2 -1
- package/es/rce/plugins/instructure_record/VideoOptionsTray/TrayController.d.ts +1 -1
- package/es/rce/plugins/instructure_record/VideoOptionsTray/TrayController.js +2 -1
- package/es/rce/plugins/instructure_record/mediaTranslations.js +1 -1
- package/es/rce/plugins/instructure_studio_media_options/plugin.js +111 -14
- package/es/rce/plugins/instructure_studio_media_options/studioToolbarIcons.d.ts +5 -0
- package/es/rce/plugins/instructure_studio_media_options/studioToolbarIcons.js +23 -0
- package/es/rce/plugins/instructure_wordcount/components/WordCountModal.js +1 -0
- package/es/rce/plugins/instructure_wordcount_header/plugin.d.ts +1 -0
- package/es/rce/plugins/instructure_wordcount_header/plugin.js +75 -0
- package/es/rce/plugins/shared/ContentSelection.d.ts +1 -2
- package/es/rce/plugins/shared/ContentSelection.js +1 -18
- package/es/rce/plugins/shared/StudioLtiSupportUtils.d.ts +10 -1
- package/es/rce/plugins/shared/StudioLtiSupportUtils.js +110 -1
- package/es/rce/plugins/shared/Upload/ComputerPanel.js +1 -1
- package/es/rce/plugins/shared/Upload/UploadFileModal.js +37 -4
- package/es/rce/plugins/shared/Upload/VideoUrlPanel.d.ts +15 -0
- package/es/rce/plugins/shared/Upload/VideoUrlPanel.js +51 -0
- package/es/rce/plugins/shared/Upload/videoValidationUtils.d.ts +7 -0
- package/es/rce/plugins/shared/Upload/videoValidationUtils.js +58 -0
- package/es/rce/plugins/shared/iframeUtils.d.ts +1 -0
- package/es/rce/plugins/shared/iframeUtils.js +37 -0
- package/es/rce/tinyRCE.js +2 -0
- package/es/sidebar/actions/upload.d.ts +1 -0
- package/es/sidebar/actions/upload.js +56 -0
- package/es/sidebar/containers/sidebarHandlers.d.ts +1 -0
- package/es/sidebar/containers/sidebarHandlers.js +2 -1
- package/es/translations/locales/ar.js +30 -6
- package/es/translations/locales/ca.js +30 -6
- package/es/translations/locales/cy.js +30 -6
- package/es/translations/locales/da-x-k12.js +30 -6
- package/es/translations/locales/da.js +30 -6
- package/es/translations/locales/de.js +30 -6
- package/es/translations/locales/el.js +6 -0
- package/es/translations/locales/en-AU-x-unimelb.js +30 -6
- package/es/translations/locales/en-GB-x-ukhe.js +30 -6
- package/es/translations/locales/en.js +36 -6
- package/es/translations/locales/en_AU.js +30 -6
- package/es/translations/locales/en_CA.js +30 -6
- package/es/translations/locales/en_CY.js +30 -6
- package/es/translations/locales/en_GB.js +30 -6
- package/es/translations/locales/es.js +30 -6
- package/es/translations/locales/es_ES.js +30 -6
- package/es/translations/locales/fa_IR.js +6 -3
- package/es/translations/locales/fi.js +30 -6
- package/es/translations/locales/fr.js +30 -6
- package/es/translations/locales/fr_CA.js +34 -10
- package/es/translations/locales/ga.js +30 -6
- package/es/translations/locales/he.js +6 -0
- package/es/translations/locales/hi.js +30 -6
- package/es/translations/locales/ht.js +30 -6
- package/es/translations/locales/hu.js +6 -6
- package/es/translations/locales/hy.js +6 -0
- package/es/translations/locales/id.js +30 -6
- package/es/translations/locales/is.js +36 -6
- package/es/translations/locales/it.js +30 -6
- package/es/translations/locales/ja.js +30 -6
- package/es/translations/locales/ko.js +6 -0
- package/es/translations/locales/mi.js +30 -6
- package/es/translations/locales/ms.js +30 -6
- package/es/translations/locales/nb-x-k12.js +30 -6
- package/es/translations/locales/nb.js +30 -6
- package/es/translations/locales/nl.js +30 -6
- package/es/translations/locales/nn.js +6 -6
- package/es/translations/locales/pl.js +30 -6
- package/es/translations/locales/pt.js +30 -6
- package/es/translations/locales/pt_BR.js +30 -6
- package/es/translations/locales/ru.js +30 -6
- package/es/translations/locales/sl.js +30 -6
- package/es/translations/locales/sv-x-k12.js +30 -6
- package/es/translations/locales/sv.js +30 -6
- package/es/translations/locales/th.js +30 -6
- package/es/translations/locales/tr.js +6 -3
- package/es/translations/locales/uk_UA.js +6 -3
- package/es/translations/locales/vi.js +30 -6
- package/es/translations/locales/zh-Hans.js +30 -6
- package/es/translations/locales/zh-Hant.js +30 -6
- package/es/translations/locales/zh.js +30 -6
- package/es/translations/locales/zh_HK.js +30 -6
- package/package.json +53 -53
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,47 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## 7.3.1 - 2025-11-11
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- WordCountModal not closing with keyboard navigation
|
|
12
|
+
- Accessibility issue for RCE's file preview
|
|
13
|
+
- Assessment questions preview path
|
|
14
|
+
- Formatting in Biome configuration for RCE package
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
- Updated TinyMCE shortcut link text
|
|
18
|
+
|
|
19
|
+
### Localization
|
|
20
|
+
- Updated RCE translations
|
|
21
|
+
|
|
22
|
+
## 7.3.0 - 2025-10-16
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
- Block Content Editor (BCE) support and improvements
|
|
26
|
+
- Media Embed by URL functionality
|
|
27
|
+
- A11y checker for block editor
|
|
28
|
+
- User choices to improved Studio toolbar options
|
|
29
|
+
- Feature flag for upcoming Studio embed improvements
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
- Upgrade to InstUI 10.26.2
|
|
33
|
+
- Unify image upload buttons
|
|
34
|
+
- Adjust the text block footer
|
|
35
|
+
- Remove documents from RCE's toolbar for BCE
|
|
36
|
+
|
|
37
|
+
### Fixed
|
|
38
|
+
- Prevent setState from TinyMCE events after unmount
|
|
39
|
+
- Image upload modal for BCE
|
|
40
|
+
- Revert Alert conditional rendering
|
|
41
|
+
- Revert axios version upgrade
|
|
42
|
+
|
|
43
|
+
### Removed
|
|
44
|
+
- Remove crocodoc from canvas
|
|
45
|
+
|
|
46
|
+
### Localization
|
|
47
|
+
- Updated RCE translations for multiple locales
|
|
48
|
+
|
|
8
49
|
## 7.2.0 - 2025-08-21
|
|
9
50
|
|
|
10
51
|
### Added
|
|
@@ -106,20 +106,10 @@ export function loadDocPreview($container, options) {
|
|
|
106
106
|
});
|
|
107
107
|
}
|
|
108
108
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
iframe.setAttribute('width', opts.width);
|
|
114
|
-
iframe.setAttribute('height', opts.height);
|
|
115
|
-
iframe.setAttribute('allowfullscreen', '1');
|
|
116
|
-
iframe.id = opts.id;
|
|
117
|
-
$container.appendChild(iframe);
|
|
118
|
-
iframe.load(() => {
|
|
119
|
-
tellAppIViewedThisInline('crocodoc');
|
|
120
|
-
if (typeof opts.ready === 'function') opts.ready();
|
|
121
|
-
});
|
|
122
|
-
} else if (opts.canvadoc_session_url) {
|
|
109
|
+
const iframeAriaLabel = opts.attachment_name ? formatMessage('File preview for {fileName}', {
|
|
110
|
+
fileName: opts.attachment_name
|
|
111
|
+
}) : formatMessage('File preview');
|
|
112
|
+
if (opts.canvadoc_session_url) {
|
|
123
113
|
const canvadocWrapper = document.createElement('div');
|
|
124
114
|
canvadocWrapper.setAttribute('style', 'overflow: auto; resize: vertical; border: 1px solid transparent; height: 100%;');
|
|
125
115
|
$container.appendChild(canvadocWrapper);
|
|
@@ -130,6 +120,7 @@ export function loadDocPreview($container, options) {
|
|
|
130
120
|
tellAppIViewedThisInline('canvadocs');
|
|
131
121
|
if (typeof opts.ready === 'function') opts.ready();
|
|
132
122
|
});
|
|
123
|
+
iframe.setAttribute('aria-label', iframeAriaLabel);
|
|
133
124
|
iframe.setAttribute('src', sanitizedUrl);
|
|
134
125
|
iframe.setAttribute('width', opts.width);
|
|
135
126
|
iframe.setAttribute('allowfullscreen', '1');
|
|
@@ -155,6 +146,7 @@ export function loadDocPreview($container, options) {
|
|
|
155
146
|
iframe.setAttribute('src', googleDocPreviewUrl);
|
|
156
147
|
iframe.setAttribute('height', opts.height);
|
|
157
148
|
iframe.setAttribute('width', '100%');
|
|
149
|
+
iframe.setAttribute('aria-label', iframeAriaLabel);
|
|
158
150
|
$container.appendChild(iframe);
|
|
159
151
|
}
|
|
160
152
|
};
|
|
@@ -269,7 +269,7 @@ export function enhanceUserContent(container = document, opts = {}) {
|
|
|
269
269
|
|
|
270
270
|
// Don't attempt to enhance links with no href
|
|
271
271
|
if (!href) return;
|
|
272
|
-
const matchesCanvasFile = href.pathname.match(/(?:\/(courses|groups|users)\/\d+)?\/files\/([\d~]+)(?=[!*'();:@&=+$,/?#\[\]]|$)/);
|
|
272
|
+
const matchesCanvasFile = href.pathname.match(/(?:\/(courses|groups|users|assessment_questions)\/\d+)?\/files\/([\d~]+)(?=[!*'();:@&=+$,/?#\[\]]|$)/);
|
|
273
273
|
if (!matchesCanvasFile) {
|
|
274
274
|
// a bug in the new RCE added instructure_file_link class name to all links
|
|
275
275
|
// only proceed if this is a canvas file link
|
|
@@ -157,6 +157,7 @@ export function showFilePreviewInline(event, canvasOrigin, disableGooglePreviews
|
|
|
157
157
|
mimeType: attachment.content_type,
|
|
158
158
|
public_url: attachment.public_url,
|
|
159
159
|
attachment_preview_processing: attachment.workflow_state === 'pending_upload' || attachment.workflow_state === 'processing',
|
|
160
|
+
attachment_name: attachment.display_name,
|
|
160
161
|
disableGooglePreviews
|
|
161
162
|
});
|
|
162
163
|
const $minimizeLink = document.createElement('a');
|
package/es/index.d.ts
CHANGED
package/es/index.js
CHANGED
|
@@ -29,9 +29,7 @@ export default function AlertMessageArea({
|
|
|
29
29
|
return /*#__PURE__*/React.createElement("div", null, messages.map(message => /*#__PURE__*/React.createElement(Alert, {
|
|
30
30
|
key: message.id,
|
|
31
31
|
variant: message.variant || 'info',
|
|
32
|
-
timeout: 10000
|
|
33
|
-
// @ts-expect-error
|
|
34
|
-
,
|
|
32
|
+
timeout: 10000,
|
|
35
33
|
liveRegion: liveRegion,
|
|
36
34
|
onDismiss: () => afterDismiss(message.id)
|
|
37
35
|
}, message.text)));
|
|
@@ -79,7 +79,7 @@ export default function KeyboardShortcutModal(props) {
|
|
|
79
79
|
}, formatMessage('Other editor shortcuts may be found at'), ' ', /*#__PURE__*/React.createElement("a", {
|
|
80
80
|
href: "https://www.tiny.cloud/docs/advanced/keyboard-shortcuts/",
|
|
81
81
|
target: "rcekbshortcut"
|
|
82
|
-
}, "
|
|
82
|
+
}, "TinyMCE Keyboard Shortcuts")))));
|
|
83
83
|
}
|
|
84
84
|
KeyboardShortcutModal.propTypes = {
|
|
85
85
|
open: bool.isRequired,
|
package/es/rce/RCEGlobals.d.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
export default instance;
|
|
2
2
|
export type Features = {
|
|
3
3
|
file_verifiers_for_quiz_links: boolean;
|
|
4
|
+
rce_studio_embed_improvements: boolean;
|
|
4
5
|
};
|
|
5
6
|
declare const instance: RCEGlobals;
|
|
6
7
|
/**
|
|
7
8
|
* @typedef {Object} Features
|
|
8
9
|
* @property {boolean} file_verifiers_for_quiz_links
|
|
10
|
+
* @property {boolean} rce_studio_embed_improvements
|
|
9
11
|
*/
|
|
10
12
|
declare class RCEGlobals {
|
|
11
13
|
_data: {
|
package/es/rce/RCEGlobals.js
CHANGED
package/es/rce/RCEVariants.js
CHANGED
|
@@ -110,13 +110,13 @@ export function getToolbarForVariant(variant, ltiToolFavorites = []) {
|
|
|
110
110
|
items: ['bold', 'italic', 'underline', 'instructure_color', 'inst_subscript', 'inst_superscript']
|
|
111
111
|
}, {
|
|
112
112
|
name: formatMessage('Content'),
|
|
113
|
-
items: ['instructure_links'
|
|
113
|
+
items: ['instructure_links']
|
|
114
114
|
}, {
|
|
115
115
|
name: formatMessage('Alignment and Lists'),
|
|
116
116
|
items: ['align', 'bullist', 'inst_indent', 'inst_outdent']
|
|
117
117
|
}, {
|
|
118
118
|
name: formatMessage('Miscellaneous'),
|
|
119
|
-
items: ['removeformat', 'instructure_equation']
|
|
119
|
+
items: ['removeformat', 'instructure_equation', 'instructure_keyboard_shortcuts_header', 'instructure_wordcount_header']
|
|
120
120
|
}];
|
|
121
121
|
}
|
|
122
122
|
return [{
|
|
@@ -152,7 +152,7 @@ export function getStatusBarFeaturesForVariant(variant, options = {
|
|
|
152
152
|
return [];
|
|
153
153
|
}
|
|
154
154
|
if (variant === 'block-content-editor') {
|
|
155
|
-
return [
|
|
155
|
+
return [];
|
|
156
156
|
}
|
|
157
157
|
const platformFeatures = options.isDesktop ? DESKTOP_FEATURES : MOBILE_FEATURES;
|
|
158
158
|
if (variant === 'lite' || variant === 'text-only') {
|
package/es/rce/RCEWrapper.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ import EncryptedStorage from '../util/encrypted-storage';
|
|
|
7
7
|
import { RCEVariant } from './RCEVariants';
|
|
8
8
|
import { mergeMenu, mergeMenuItems, mergePlugins, mergeToolbar, parsePluginsToExclude } from './RCEWrapper.utils';
|
|
9
9
|
import { AlertMessage, EditorOptions, RCETrayProps } from './types';
|
|
10
|
-
export declare function storageAvailable(): boolean |
|
|
10
|
+
export declare function storageAvailable(): boolean | null;
|
|
11
11
|
interface RCEWrapperProps {
|
|
12
12
|
ai_text_tools?: boolean;
|
|
13
13
|
autosave?: {
|
|
@@ -214,6 +214,7 @@ declare class RCEWrapper extends React.Component<RCEWrapperProps, RCEWrapperStat
|
|
|
214
214
|
new_math_equation_handling: unknown;
|
|
215
215
|
explicit_latex_typesetting: unknown;
|
|
216
216
|
rce_transform_loaded_content: unknown;
|
|
217
|
+
rce_studio_embed_improvements: unknown;
|
|
217
218
|
file_verifiers_for_quiz_links: unknown;
|
|
218
219
|
rce_find_replace: unknown;
|
|
219
220
|
consolidated_media_player: unknown;
|
|
@@ -315,6 +316,7 @@ declare class RCEWrapper extends React.Component<RCEWrapperProps, RCEWrapperStat
|
|
|
315
316
|
*/
|
|
316
317
|
_setupSelectionSaving: (editor: any) => void;
|
|
317
318
|
announcing: number;
|
|
319
|
+
_isMounted: boolean;
|
|
318
320
|
announceContextToolbars(editor: TinyMCEEditor): void;
|
|
319
321
|
initAutoSave: (editor: TinyMCEEditor) => void;
|
|
320
322
|
cleanupAutoSave: (deleteAll?: boolean) => void;
|
package/es/rce/RCEWrapper.js
CHANGED
|
@@ -102,7 +102,7 @@ const editorWrappers = new WeakMap();
|
|
|
102
102
|
// determines if localStorage is available for our use.
|
|
103
103
|
// see https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API/Using_the_Web_Storage_API
|
|
104
104
|
export function storageAvailable() {
|
|
105
|
-
let storage;
|
|
105
|
+
let storage = null;
|
|
106
106
|
try {
|
|
107
107
|
storage = window.localStorage;
|
|
108
108
|
const x = '__storage_test__';
|
|
@@ -219,7 +219,9 @@ class RCEWrapper extends React.Component {
|
|
|
219
219
|
window.visualViewport?.addEventListener('resize', this._handleFullscreenResize);
|
|
220
220
|
this._handleFullscreenResize();
|
|
221
221
|
// @ts-expect-error
|
|
222
|
-
this._focusRegion = FocusRegionManager.activateRegion(
|
|
222
|
+
this._focusRegion = FocusRegionManager.activateRegion(
|
|
223
|
+
// @ts-expect-error
|
|
224
|
+
document[FS_ELEMENT], {
|
|
223
225
|
shouldContainFocus: true
|
|
224
226
|
});
|
|
225
227
|
} else {
|
|
@@ -332,6 +334,31 @@ class RCEWrapper extends React.Component {
|
|
|
332
334
|
tinyapp.setAttribute('tabIndex', '-1');
|
|
333
335
|
}
|
|
334
336
|
|
|
337
|
+
// remove role="aplication" attribute from the iframe body
|
|
338
|
+
// tinymce adds this when the editor is wrapped in an iframe
|
|
339
|
+
// which makes RCE input fields inaccessible to screen readers
|
|
340
|
+
const iframe = tinyapp?.querySelector('iframe');
|
|
341
|
+
const body = iframe?.contentDocument?.body;
|
|
342
|
+
if (body) {
|
|
343
|
+
const observer = new MutationObserver(() => {
|
|
344
|
+
try {
|
|
345
|
+
if (body && body.getAttribute('role') === 'application') {
|
|
346
|
+
body.removeAttribute('role');
|
|
347
|
+
}
|
|
348
|
+
} catch (_) {
|
|
349
|
+
/* pass */
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
observer.observe(body, {
|
|
353
|
+
attributes: true,
|
|
354
|
+
childList: false,
|
|
355
|
+
subtree: false
|
|
356
|
+
});
|
|
357
|
+
body.setAttribute('data-role-checked', 'true'); // to trigger observer
|
|
358
|
+
|
|
359
|
+
setTimeout(() => observer.disconnect(), 10000);
|
|
360
|
+
}
|
|
361
|
+
|
|
335
362
|
// Probably should do this in tinymce.scss, but we only want it in new rce
|
|
336
363
|
textarea.style.resize = 'none';
|
|
337
364
|
editor.on('keydown', this.handleKey);
|
|
@@ -463,6 +490,7 @@ class RCEWrapper extends React.Component {
|
|
|
463
490
|
});
|
|
464
491
|
};
|
|
465
492
|
this.announcing = 0;
|
|
493
|
+
this._isMounted = false;
|
|
466
494
|
/* ********** autosave support *************** */
|
|
467
495
|
this.initAutoSave = editor => {
|
|
468
496
|
var _this$props$userCache;
|
|
@@ -867,6 +895,7 @@ class RCEWrapper extends React.Component {
|
|
|
867
895
|
explicit_latex_typesetting = false,
|
|
868
896
|
rce_transform_loaded_content = false,
|
|
869
897
|
rce_find_replace = false,
|
|
898
|
+
rce_studio_embed_improvements = false,
|
|
870
899
|
file_verifiers_for_quiz_links = false,
|
|
871
900
|
consolidated_media_player = false
|
|
872
901
|
} = this.props.features;
|
|
@@ -874,6 +903,7 @@ class RCEWrapper extends React.Component {
|
|
|
874
903
|
new_math_equation_handling,
|
|
875
904
|
explicit_latex_typesetting,
|
|
876
905
|
rce_transform_loaded_content,
|
|
906
|
+
rce_studio_embed_improvements,
|
|
877
907
|
file_verifiers_for_quiz_links,
|
|
878
908
|
rce_find_replace,
|
|
879
909
|
consolidated_media_player
|
|
@@ -1323,10 +1353,12 @@ class RCEWrapper extends React.Component {
|
|
|
1323
1353
|
// focus is still somewhere w/in me
|
|
1324
1354
|
return;
|
|
1325
1355
|
}
|
|
1326
|
-
const activeClass = document.activeElement
|
|
1356
|
+
const activeClass = document.activeElement?.getAttribute('class');
|
|
1327
1357
|
if (
|
|
1328
1358
|
// @ts-expect-error
|
|
1329
|
-
(event.focusedEditor === undefined ||
|
|
1359
|
+
(event.focusedEditor === undefined ||
|
|
1360
|
+
// @ts-expect-error
|
|
1361
|
+
event.target.id === event.focusedEditor?.id) && activeClass?.includes('tox-')) {
|
|
1330
1362
|
// if a toolbar button has focus, then the user clicks on the "more" button
|
|
1331
1363
|
// focus jumps to the body, then eventually to the popped up toolbar. This
|
|
1332
1364
|
// catches that case.
|
|
@@ -1362,6 +1394,7 @@ class RCEWrapper extends React.Component {
|
|
|
1362
1394
|
}
|
|
1363
1395
|
announceContextToolbars(editor) {
|
|
1364
1396
|
editor.on('NodeChange', () => {
|
|
1397
|
+
if (!this._isMounted) return;
|
|
1365
1398
|
const node = editor.selection.getNode();
|
|
1366
1399
|
// @ts-expect-error
|
|
1367
1400
|
if (isImageEmbed(node, editor)) {
|
|
@@ -1401,7 +1434,7 @@ class RCEWrapper extends React.Component {
|
|
|
1401
1434
|
editor.on('ResizeEditor', ({
|
|
1402
1435
|
deltaY
|
|
1403
1436
|
}) => {
|
|
1404
|
-
if (!deltaY) return;
|
|
1437
|
+
if (!this._isMounted || !deltaY) return;
|
|
1405
1438
|
if (deltaY < 0) {
|
|
1406
1439
|
this.setState({
|
|
1407
1440
|
announcement: formatMessage('The height of Rich Content Area is decreased.')
|
|
@@ -1441,6 +1474,7 @@ class RCEWrapper extends React.Component {
|
|
|
1441
1474
|
return `rceautosave:${userId}${window.location.href}:${this.props.textareaId}`;
|
|
1442
1475
|
}
|
|
1443
1476
|
componentWillUnmount() {
|
|
1477
|
+
this._isMounted = false;
|
|
1444
1478
|
if (this.state.shouldShowEditor) {
|
|
1445
1479
|
window.clearTimeout(this.blurTimer);
|
|
1446
1480
|
if (!this._destroyCalled) {
|
|
@@ -1539,7 +1573,7 @@ class RCEWrapper extends React.Component {
|
|
|
1539
1573
|
// handles all of that complexity. It that ever changes in the
|
|
1540
1574
|
// future in an upgraded version, we will have to update the
|
|
1541
1575
|
// logic in those other places as well.
|
|
1542
|
-
plugins: mergePlugins(['autolink', 'media', 'table', 'link', 'directionality', 'lists', 'textpattern', 'hr', 'instructure_color', 'instructure-ui-icons', 'instructure_condensed_buttons', 'instructure_links', 'instructure_html_view', 'instructure_media_embed', 'a11y_checker', 'wordcount', 'instructure_wordcount', 'instructure_studio_media_options', 'instructure_rce_external_tools', ...pastePlugins, ...canvasPlugins],
|
|
1576
|
+
plugins: mergePlugins(['autolink', 'media', 'table', 'link', 'directionality', 'lists', 'textpattern', 'hr', 'instructure_color', 'instructure-ui-icons', 'instructure_condensed_buttons', 'instructure_links', 'instructure_html_view', 'instructure_media_embed', 'a11y_checker', 'wordcount', 'instructure_wordcount', 'instructure_wordcount_header', 'instructure_keyboard_shortcuts_header', 'instructure_studio_media_options', 'instructure_rce_external_tools', ...pastePlugins, ...canvasPlugins],
|
|
1543
1577
|
// filter out the plugins designated for removal
|
|
1544
1578
|
// @ts-expect-error
|
|
1545
1579
|
sanitizePlugins(options.plugins)?.filter(p => p.length > 0 && p[0] !== '-'), this.pluginsToExclude),
|
|
@@ -1589,6 +1623,7 @@ class RCEWrapper extends React.Component {
|
|
|
1589
1623
|
}
|
|
1590
1624
|
}
|
|
1591
1625
|
componentDidMount() {
|
|
1626
|
+
this._isMounted = true;
|
|
1592
1627
|
if (this.state.shouldShowEditor) {
|
|
1593
1628
|
this.editorReallyDidMount();
|
|
1594
1629
|
} else {
|
|
@@ -1841,13 +1876,10 @@ class RCEWrapper extends React.Component {
|
|
|
1841
1876
|
open: this.state.confirmAutoSave,
|
|
1842
1877
|
onNo: () => this.restoreAutoSave(false),
|
|
1843
1878
|
onYes: () => this.restoreAutoSave(true)
|
|
1844
|
-
})) : null,
|
|
1845
|
-
/*#__PURE__*/
|
|
1846
|
-
// @ts-expect-error
|
|
1847
|
-
React.createElement(Alert, {
|
|
1879
|
+
})) : null, /*#__PURE__*/React.createElement(Alert, {
|
|
1848
1880
|
screenReaderOnly: true,
|
|
1849
1881
|
liveRegion: this.props.liveRegion
|
|
1850
|
-
}, this.state.announcement));
|
|
1882
|
+
}, this.state.announcement || null));
|
|
1851
1883
|
}));
|
|
1852
1884
|
}
|
|
1853
1885
|
}
|
|
@@ -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 @@
|
|
|
1
|
+
export {};
|
|
@@ -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
|
+
});
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
import React from 'react';
|
|
20
20
|
import ReactDOM from 'react-dom';
|
|
21
21
|
import bridge from '../../../../bridge';
|
|
22
|
-
import { asAudioElement
|
|
22
|
+
import { asAudioElement } from '../../shared/ContentSelection';
|
|
23
|
+
import { findMediaPlayerIframe } from '../../shared/iframeUtils';
|
|
23
24
|
import AudioOptionsTray from '.';
|
|
24
25
|
export const CONTAINER_ID = 'instructure-audio-options-tray-container';
|
|
25
26
|
export default class TrayController {
|
|
@@ -27,7 +27,7 @@ export default class TrayController {
|
|
|
27
27
|
get $container(): HTMLElement;
|
|
28
28
|
get isOpen(): boolean;
|
|
29
29
|
showTrayForEditor(editor: any): void;
|
|
30
|
-
$videoContainer:
|
|
30
|
+
$videoContainer: Element | null | undefined;
|
|
31
31
|
hideTrayForEditor(editor: any): void;
|
|
32
32
|
_applyVideoOptions(videoOptions: any): void;
|
|
33
33
|
_dismissTray(): void;
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
import React from 'react';
|
|
20
20
|
import ReactDOM from 'react-dom';
|
|
21
21
|
import bridge from '../../../../bridge';
|
|
22
|
-
import { asVideoElement
|
|
22
|
+
import { asVideoElement } from '../../shared/ContentSelection';
|
|
23
|
+
import { findMediaPlayerIframe } from '../../shared/iframeUtils';
|
|
23
24
|
import VideoOptionsTray from '.';
|
|
24
25
|
import { isStudioEmbeddedMedia, parseStudioOptions } from '../../shared/StudioLtiSupportUtils';
|
|
25
26
|
import RCEGlobals from '../../../RCEGlobals';
|
|
@@ -24,7 +24,7 @@ import formatMessage from '../../../format-message';
|
|
|
24
24
|
const uploadMediaTranslations = {
|
|
25
25
|
UploadMediaStrings: {
|
|
26
26
|
ADD_CLOSED_CAPTIONS_OR_SUBTITLES: formatMessage('Add CC/Subtitles'),
|
|
27
|
-
CLEAR_FILE_TEXT: formatMessage('
|
|
27
|
+
CLEAR_FILE_TEXT: formatMessage('Remove'),
|
|
28
28
|
CLOSE_TEXT: formatMessage('Close'),
|
|
29
29
|
CLOSED_CAPTIONS_CHOOSE_FILE: formatMessage('Choose caption file'),
|
|
30
30
|
CLOSED_CAPTIONS_SELECT_LANGUAGE: formatMessage('Select Language'),
|
|
@@ -17,24 +17,121 @@
|
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
import tinymce from 'tinymce';
|
|
20
|
-
import { isStudioEmbeddedMedia, handleBeforeObjectSelected } from '../shared/StudioLtiSupportUtils';
|
|
20
|
+
import { isStudioEmbeddedMedia, handleBeforeObjectSelected, notifyStudioEmbedTypeChange, updateStudioIframeDimensions, isValidDimension, isValidEmbedType, isValidResizable } from '../shared/StudioLtiSupportUtils';
|
|
21
21
|
import VideoTrayController from '../instructure_record/VideoOptionsTray/TrayController';
|
|
22
22
|
import formatMessage from '../../../format-message';
|
|
23
|
+
import RCEGlobals from '../../RCEGlobals';
|
|
24
|
+
import { thumbnailViewIcon, learnViewIcon, collabViewIcon, optionsIcon, removeIcon } from './studioToolbarIcons';
|
|
23
25
|
const studioTrayController = new VideoTrayController();
|
|
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);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
24
33
|
tinymce.PluginManager.add('instructure_studio_media_options', function (ed) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
if (RCEGlobals.getFeatures().rce_studio_embed_improvements) {
|
|
35
|
+
window.addEventListener('message', handleStudioMessage);
|
|
36
|
+
ed.on('init', () => {
|
|
37
|
+
const existingStyle = document.getElementById('studio-toolbar-styles');
|
|
38
|
+
if (!existingStyle) {
|
|
39
|
+
const style = document.createElement('style');
|
|
40
|
+
style.id = 'studio-toolbar-styles';
|
|
41
|
+
style.textContent = `
|
|
42
|
+
.tox .tox-pop .tox-tbtn {
|
|
43
|
+
font-size: 16px;
|
|
44
|
+
border-radius: 0;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.tox .tox-pop .tox-tbtn:hover {
|
|
48
|
+
background-color: #2B7ABC;
|
|
49
|
+
color: white;
|
|
50
|
+
}
|
|
51
|
+
`;
|
|
52
|
+
document.head.appendChild(style);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
ed.ui.registry.addIcon('thumbnail-view-icon', thumbnailViewIcon);
|
|
56
|
+
ed.ui.registry.addIcon('learn-view-icon', learnViewIcon);
|
|
57
|
+
ed.ui.registry.addIcon('collab-view-icon', collabViewIcon);
|
|
58
|
+
ed.ui.registry.addIcon('options-icon', optionsIcon);
|
|
59
|
+
ed.ui.registry.addIcon('remove-icon', removeIcon);
|
|
60
|
+
ed.ui.registry.addButton('thumbnail-view', {
|
|
61
|
+
onAction() {
|
|
62
|
+
notifyStudioEmbedTypeChange(ed, 'thumbnail_embed');
|
|
63
|
+
},
|
|
64
|
+
icon: 'thumbnail-view-icon',
|
|
65
|
+
text: formatMessage('Thumbnail'),
|
|
66
|
+
tooltip: formatMessage('Thumbnail')
|
|
67
|
+
});
|
|
68
|
+
ed.ui.registry.addButton('learn-view', {
|
|
69
|
+
onAction() {
|
|
70
|
+
notifyStudioEmbedTypeChange(ed, 'learn_embed');
|
|
71
|
+
},
|
|
72
|
+
icon: 'learn-view-icon',
|
|
73
|
+
text: formatMessage('Learn'),
|
|
74
|
+
tooltip: formatMessage('Learn')
|
|
75
|
+
});
|
|
76
|
+
ed.ui.registry.addButton('collab-view', {
|
|
77
|
+
onAction() {
|
|
78
|
+
notifyStudioEmbedTypeChange(ed, 'collaboration_embed');
|
|
79
|
+
},
|
|
80
|
+
icon: 'collab-view-icon',
|
|
81
|
+
text: formatMessage('Collab'),
|
|
82
|
+
tooltip: formatMessage('Collab')
|
|
83
|
+
});
|
|
84
|
+
ed.ui.registry.addButton('studio-media-options', {
|
|
85
|
+
onAction() {
|
|
86
|
+
if (!studioTrayController.isOpen) {
|
|
87
|
+
studioTrayController.showTrayForEditor(ed);
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
icon: 'options-icon',
|
|
91
|
+
text: formatMessage('Options'),
|
|
92
|
+
tooltip: formatMessage('Options')
|
|
93
|
+
});
|
|
94
|
+
ed.ui.registry.addButton('remove-studio-media', {
|
|
95
|
+
onAction() {
|
|
96
|
+
const selectedElement = ed.selection.getNode();
|
|
97
|
+
if (selectedElement && isStudioEmbeddedMedia(selectedElement)) {
|
|
98
|
+
studioTrayController.hideTrayForEditor(ed);
|
|
99
|
+
|
|
100
|
+
// Hide toolbar, reset selection
|
|
101
|
+
ed.fire('hidecontexttoolbar');
|
|
102
|
+
ed.dom.remove(selectedElement);
|
|
103
|
+
ed.nodeChanged();
|
|
104
|
+
ed.selection.select(ed.getBody());
|
|
105
|
+
|
|
106
|
+
// Force focus back to editor
|
|
107
|
+
ed.focus();
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
icon: 'remove-icon',
|
|
111
|
+
text: formatMessage('Remove'),
|
|
112
|
+
tooltip: formatMessage('Remove Studio Media')
|
|
113
|
+
});
|
|
114
|
+
ed.ui.registry.addContextToolbar('studio-extra-toolbar', {
|
|
115
|
+
items: 'thumbnail-view | learn-view | collab-view | studio-media-options | remove-studio-media',
|
|
116
|
+
position: 'node',
|
|
117
|
+
predicate: isStudioEmbeddedMedia,
|
|
118
|
+
scope: 'node'
|
|
119
|
+
});
|
|
120
|
+
} else {
|
|
121
|
+
ed.ui.registry.addButton('studio-media-options', {
|
|
122
|
+
onAction() {
|
|
123
|
+
studioTrayController.showTrayForEditor(ed);
|
|
124
|
+
},
|
|
125
|
+
text: formatMessage('Studio Media Options'),
|
|
126
|
+
tooltip: formatMessage('Show Studio media options')
|
|
127
|
+
});
|
|
128
|
+
ed.ui.registry.addContextToolbar('studio-media-options-toolbar', {
|
|
129
|
+
items: 'studio-media-options',
|
|
130
|
+
position: 'node',
|
|
131
|
+
predicate: isStudioEmbeddedMedia,
|
|
132
|
+
scope: 'node'
|
|
133
|
+
});
|
|
134
|
+
}
|
|
38
135
|
ed.on('BeforeObjectSelected', handleBeforeObjectSelected);
|
|
39
136
|
ed.on('remove', editor => {
|
|
40
137
|
studioTrayController.hideTrayForEditor(editor);
|