@instructure/canvas-rce 6.0.0 → 7.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.
- package/CHANGELOG.md +14 -0
- package/es/common/browser.js +2 -2
- package/es/common/fileUrl.js +13 -3
- package/es/rce/RCEGlobals.d.ts +0 -2
- package/es/rce/RCEGlobals.js +0 -1
- package/es/rce/RCEVariants.d.ts +1 -1
- package/es/rce/RCEVariants.js +8 -8
- package/es/rce/RCEWrapper.d.ts +0 -2
- package/es/rce/RCEWrapper.js +3 -27
- package/es/rce/plugins/instructure_equation/EquationEditorModal/index.d.ts +1 -1
- package/es/rce/plugins/instructure_image/ImageEmbedOptions.js +4 -7
- package/es/rce/plugins/instructure_record/AudioOptionsTray/TrayController.js +1 -2
- package/es/rce/plugins/instructure_record/VideoOptionsTray/TrayController.js +2 -4
- package/es/rce/plugins/shared/ContentSelection.js +4 -7
- package/es/rce/plugins/shared/fileTypeUtils.js +1 -1
- package/es/rce/plugins/tinymce-a11y-checker/plugin.js +50 -52
- package/es/rce/style.js +29 -29
- package/es/rcs/api.d.ts +4 -1
- package/es/rcs/api.js +9 -13
- package/es/translations/locales/en.js +3 -0
- package/package.json +54 -54
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,20 @@ 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.0.0 - 2025-03-31
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- Screen readers for RCE toolbar on mobile platform
|
|
13
|
+
- Axios CSRF vulnerability
|
|
14
|
+
- Mailto link insertion
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- Upgraded Instui to v10
|
|
19
|
+
- Refactored deprecated plugins to prepare for tinymce upgrade
|
|
20
|
+
- Removed media_links_use_attachment_id feature flag
|
|
21
|
+
|
|
8
22
|
## 6.0.0 - 2025-03-20
|
|
9
23
|
|
|
10
24
|
### Changed
|
package/es/common/browser.js
CHANGED
package/es/common/fileUrl.js
CHANGED
|
@@ -21,18 +21,28 @@
|
|
|
21
21
|
// in mocha tests.
|
|
22
22
|
|
|
23
23
|
import RCEGlobals from '../rce/RCEGlobals';
|
|
24
|
+
const CONTACT_PROTOCOLS = ['mailto:', 'tel:', 'skype:'];
|
|
25
|
+
function parseUrl(url, canvasOrigin = window.location.origin) {
|
|
26
|
+
try {
|
|
27
|
+
// If the URL is already absolute, use it as-is
|
|
28
|
+
return new URL(url);
|
|
29
|
+
} catch {
|
|
30
|
+
return new URL(`${canvasOrigin}${url.startsWith('/') ? '' : '/'}${url}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
24
33
|
function parseCanvasUrl(url, canvasOrigin = window.location.origin) {
|
|
25
34
|
if (!url) {
|
|
26
35
|
return null;
|
|
27
36
|
}
|
|
28
37
|
try {
|
|
29
|
-
|
|
30
|
-
const fullUrl = url.startsWith('http') ? url : `${canvasOrigin}${url.startsWith('/') ? '' : '/'}${url}`;
|
|
31
|
-
const parsed = new URL(fullUrl);
|
|
38
|
+
const parsed = parseUrl(url, canvasOrigin);
|
|
32
39
|
const canvasUrl = new URL(canvasOrigin);
|
|
33
40
|
if (parsed.host && canvasUrl.host !== parsed.host) {
|
|
34
41
|
return null;
|
|
35
42
|
}
|
|
43
|
+
if (CONTACT_PROTOCOLS.includes(parsed.protocol)) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
36
46
|
|
|
37
47
|
// Convert URLSearchParams to query object
|
|
38
48
|
const query = {};
|
package/es/rce/RCEGlobals.d.ts
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
export default instance;
|
|
2
2
|
export type Features = {
|
|
3
|
-
media_links_use_attachment_id: boolean;
|
|
4
3
|
file_verifiers_for_quiz_links: boolean;
|
|
5
4
|
};
|
|
6
5
|
declare const instance: RCEGlobals;
|
|
7
6
|
/**
|
|
8
7
|
* @typedef {Object} Features
|
|
9
|
-
* @property {boolean} media_links_use_attachment_id
|
|
10
8
|
* @property {boolean} file_verifiers_for_quiz_links
|
|
11
9
|
*/
|
|
12
10
|
declare class RCEGlobals {
|
package/es/rce/RCEGlobals.js
CHANGED
package/es/rce/RCEVariants.d.ts
CHANGED
|
@@ -13,5 +13,5 @@ export type RCEVariant = (typeof RCEVariantValues)[number];
|
|
|
13
13
|
export declare function getMenubarForVariant(variant: RCEVariant): MenuBarSpec;
|
|
14
14
|
export declare function getMenuForVariant(variant: RCEVariant): MenusSpec;
|
|
15
15
|
export declare function getToolbarForVariant(variant: RCEVariant, ltiToolFavorites?: string[]): ToolbarGroupSetting[];
|
|
16
|
-
export declare function getStatusBarFeaturesForVariant(variant: RCEVariant,
|
|
16
|
+
export declare function getStatusBarFeaturesForVariant(variant: RCEVariant, aiTextTools?: boolean, isDesktop?: boolean): StatusBarFeature[];
|
|
17
17
|
export {};
|
package/es/rce/RCEVariants.js
CHANGED
|
@@ -121,16 +121,16 @@ export function getToolbarForVariant(variant, ltiToolFavorites = []) {
|
|
|
121
121
|
items: ['removeformat', 'table', 'instructure_equation', 'instructure_media_embed']
|
|
122
122
|
}];
|
|
123
123
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
124
|
+
const DESKTOP_FEATURES = ['keyboard_shortcuts', 'a11y_checker', 'word_count'];
|
|
125
|
+
const MOBILE_FEATURES = ['a11y_checker', 'word_count'];
|
|
126
|
+
const EXTENDED_FEATURES = ['html_view', 'fullscreen', 'resize_handle'];
|
|
127
|
+
export function getStatusBarFeaturesForVariant(variant, aiTextTools = false, isDesktop = true) {
|
|
128
128
|
if (variant === 'text-block') {
|
|
129
129
|
return [];
|
|
130
130
|
}
|
|
131
|
-
const
|
|
132
|
-
if (
|
|
133
|
-
|
|
131
|
+
const platformFeatures = isDesktop ? DESKTOP_FEATURES : MOBILE_FEATURES;
|
|
132
|
+
if (variant === 'lite' || variant === 'text-only') {
|
|
133
|
+
return platformFeatures;
|
|
134
134
|
}
|
|
135
|
-
return
|
|
135
|
+
return [...platformFeatures, ...EXTENDED_FEATURES, ...(aiTextTools ? ['ai_tools'] : [])];
|
|
136
136
|
}
|
package/es/rce/RCEWrapper.d.ts
CHANGED
|
@@ -214,7 +214,6 @@ 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
|
-
media_links_use_attachment_id: unknown;
|
|
218
217
|
file_verifiers_for_quiz_links: unknown;
|
|
219
218
|
rce_find_replace: unknown;
|
|
220
219
|
consolidated_media_player: unknown;
|
|
@@ -348,7 +347,6 @@ declare class RCEWrapper extends React.Component<RCEWrapperProps, RCEWrapperStat
|
|
|
348
347
|
type: string;
|
|
349
348
|
content: string;
|
|
350
349
|
};
|
|
351
|
-
setFocusAbilityForHeader: (focusable: boolean) => void;
|
|
352
350
|
componentWillUnmount(): void;
|
|
353
351
|
wrapOptions(options?: {}): {
|
|
354
352
|
readonly: boolean | undefined;
|
package/es/rce/RCEWrapper.js
CHANGED
|
@@ -19,6 +19,7 @@ import _pt from "prop-types";
|
|
|
19
19
|
|
|
20
20
|
import React, { Suspense } from 'react';
|
|
21
21
|
import { Editor } from '@tinymce/tinymce-react';
|
|
22
|
+
import tinymce from 'tinymce';
|
|
22
23
|
import _ from 'lodash';
|
|
23
24
|
import { StoreProvider } from './plugins/shared/StoreContext';
|
|
24
25
|
import { IconKeyboardShortcutsLine } from '@instructure/ui-icons';
|
|
@@ -268,13 +269,11 @@ class RCEWrapper extends React.Component {
|
|
|
268
269
|
if (event.code === 'F9' && event.altKey) {
|
|
269
270
|
event.preventDefault();
|
|
270
271
|
event.stopPropagation();
|
|
271
|
-
this.setFocusAbilityForHeader(true);
|
|
272
272
|
// @ts-expect-error
|
|
273
273
|
focusFirstMenuButton(this._elementRef.current);
|
|
274
274
|
} else if (event.code === 'F10' && event.altKey) {
|
|
275
275
|
event.preventDefault();
|
|
276
276
|
event.stopPropagation();
|
|
277
|
-
this.setFocusAbilityForHeader(true);
|
|
278
277
|
// @ts-expect-error
|
|
279
278
|
focusToolbar(this._elementRef.current);
|
|
280
279
|
} else if (event.code === 'F8' && event.altKey) {
|
|
@@ -329,20 +328,6 @@ class RCEWrapper extends React.Component {
|
|
|
329
328
|
tinyapp.setAttribute('tabIndex', '-1');
|
|
330
329
|
}
|
|
331
330
|
|
|
332
|
-
// Adds a focusout event listener for handling screen reader navigation focus
|
|
333
|
-
const header = this._elementRef.current?.querySelector('.tox-editor-header');
|
|
334
|
-
if (header) {
|
|
335
|
-
// @ts-expect-error
|
|
336
|
-
header.addEventListener('focusout', e => {
|
|
337
|
-
// @ts-expect-error
|
|
338
|
-
const leavingHeader = !header.contains(e.relatedTarget);
|
|
339
|
-
if (leavingHeader) {
|
|
340
|
-
this.setFocusAbilityForHeader(false);
|
|
341
|
-
}
|
|
342
|
-
});
|
|
343
|
-
}
|
|
344
|
-
this.setFocusAbilityForHeader(false);
|
|
345
|
-
|
|
346
331
|
// Probably should do this in tinymce.scss, but we only want it in new rce
|
|
347
332
|
textarea.style.resize = 'none';
|
|
348
333
|
editor.on('keydown', this.handleKey);
|
|
@@ -705,13 +690,6 @@ class RCEWrapper extends React.Component {
|
|
|
705
690
|
content: this.mceInstance().getContent()
|
|
706
691
|
};
|
|
707
692
|
};
|
|
708
|
-
this.setFocusAbilityForHeader = focusable => {
|
|
709
|
-
// Sets aria-hidden to prevent screen readers focus in RCE menus and toolbar
|
|
710
|
-
const header = this._elementRef.current?.querySelector('.tox-editor-header');
|
|
711
|
-
if (header) {
|
|
712
|
-
header.setAttribute('aria-hidden', focusable ? 'false' : 'true');
|
|
713
|
-
}
|
|
714
|
-
};
|
|
715
693
|
this.handleTextareaChange = () => {
|
|
716
694
|
if (this.isHidden()) {
|
|
717
695
|
this.setCode(this.textareaValue());
|
|
@@ -853,7 +831,6 @@ class RCEWrapper extends React.Component {
|
|
|
853
831
|
new_math_equation_handling = false,
|
|
854
832
|
explicit_latex_typesetting = false,
|
|
855
833
|
rce_transform_loaded_content = false,
|
|
856
|
-
media_links_use_attachment_id = false,
|
|
857
834
|
rce_find_replace = false,
|
|
858
835
|
file_verifiers_for_quiz_links = false,
|
|
859
836
|
consolidated_media_player = false
|
|
@@ -862,7 +839,6 @@ class RCEWrapper extends React.Component {
|
|
|
862
839
|
new_math_equation_handling,
|
|
863
840
|
explicit_latex_typesetting,
|
|
864
841
|
rce_transform_loaded_content,
|
|
865
|
-
media_links_use_attachment_id,
|
|
866
842
|
file_verifiers_for_quiz_links,
|
|
867
843
|
rce_find_replace,
|
|
868
844
|
consolidated_media_player
|
|
@@ -1732,7 +1708,7 @@ class RCEWrapper extends React.Component {
|
|
|
1732
1708
|
}
|
|
1733
1709
|
});
|
|
1734
1710
|
}
|
|
1735
|
-
const statusBarFeatures = getStatusBarFeaturesForVariant(this.variant, this.props.ai_text_tools);
|
|
1711
|
+
const statusBarFeatures = getStatusBarFeaturesForVariant(this.variant, this.props.ai_text_tools, tinymce.Env.deviceType.isDesktop());
|
|
1736
1712
|
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("style", null, this.style.css), /*#__PURE__*/React.createElement(StoreProvider, {
|
|
1737
1713
|
jwt: this.props.trayProps?.jwt,
|
|
1738
1714
|
refreshToken: this.props.trayProps?.refreshToken,
|
|
@@ -1754,7 +1730,7 @@ class RCEWrapper extends React.Component {
|
|
|
1754
1730
|
} : undefined,
|
|
1755
1731
|
onFocus: this.handleFocusRCE,
|
|
1756
1732
|
onBlur: this.handleBlurRCE
|
|
1757
|
-
}, this.state.shouldShowOnFocusButton && /*#__PURE__*/React.createElement(ShowOnFocusButton, {
|
|
1733
|
+
}, this.state.shouldShowOnFocusButton && tinymce.Env.deviceType.isDesktop() && /*#__PURE__*/React.createElement(ShowOnFocusButton, {
|
|
1758
1734
|
id: `show-on-focus-btn-${this.id}`,
|
|
1759
1735
|
onClick: this.openKBShortcutModal,
|
|
1760
1736
|
margin: "xx-small",
|
|
@@ -13,7 +13,7 @@ declare class EquationEditorModal extends React.Component<any, any, any> {
|
|
|
13
13
|
executeCommand: (cmd: any, advancedCmd: any) => void;
|
|
14
14
|
handleModalCancel: () => void;
|
|
15
15
|
handleModalDone: () => void;
|
|
16
|
-
renderMathInAdvancedPreview: import("@instructure/debounce").Debounced
|
|
16
|
+
renderMathInAdvancedPreview: import("@instructure/debounce").Debounced<() => void>;
|
|
17
17
|
setPreviewElementContent(): void;
|
|
18
18
|
toggleAdvanced: () => void;
|
|
19
19
|
toggleAndUpdatePreference: () => void;
|
|
@@ -18,7 +18,6 @@
|
|
|
18
18
|
|
|
19
19
|
import formatMessage from '../../../format-message';
|
|
20
20
|
import { scaleForHeight, scaleForWidth } from '../shared/DimensionUtils';
|
|
21
|
-
import RCEGlobals from '../../RCEGlobals';
|
|
22
21
|
export const MIN_HEIGHT = 10;
|
|
23
22
|
export const MIN_WIDTH = 10;
|
|
24
23
|
export const MIN_WIDTH_VIDEO = 320;
|
|
@@ -117,12 +116,10 @@ export function fromVideoEmbed($element) {
|
|
|
117
116
|
// bad json?
|
|
118
117
|
}
|
|
119
118
|
videoOptions.videoSize = imageSizeFromKnownOptions(videoOptions);
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
videoOptions.attachmentId = matches[1];
|
|
125
|
-
}
|
|
119
|
+
const source = $videoIframe.getAttribute('src');
|
|
120
|
+
const matches = source?.match(/\/media_attachments_iframe\/(\d+)/);
|
|
121
|
+
if (matches) {
|
|
122
|
+
videoOptions.attachmentId = matches[1];
|
|
126
123
|
}
|
|
127
124
|
return videoOptions;
|
|
128
125
|
}
|
|
@@ -21,7 +21,6 @@ import ReactDOM from 'react-dom';
|
|
|
21
21
|
import bridge from '../../../../bridge';
|
|
22
22
|
import { asAudioElement, findMediaPlayerIframe } from '../../shared/ContentSelection';
|
|
23
23
|
import AudioOptionsTray from '.';
|
|
24
|
-
import RCEGlobals from '../../../RCEGlobals';
|
|
25
24
|
export const CONTAINER_ID = 'instructure-audio-options-tray-container';
|
|
26
25
|
export default class TrayController {
|
|
27
26
|
constructor() {
|
|
@@ -74,7 +73,7 @@ export default class TrayController {
|
|
|
74
73
|
return elem.parentNode.removeChild(elem);
|
|
75
74
|
}
|
|
76
75
|
_applyAudioOptions(audioOptions) {
|
|
77
|
-
const hasAttachmentId =
|
|
76
|
+
const hasAttachmentId = audioOptions.attachment_id;
|
|
78
77
|
if (!hasAttachmentId && (!audioOptions.media_object_id || audioOptions.media_object_id === 'undefined')) {
|
|
79
78
|
return;
|
|
80
79
|
}
|
|
@@ -118,11 +118,9 @@ export default class TrayController {
|
|
|
118
118
|
const data = {
|
|
119
119
|
media_object_id: videoOptions.media_object_id,
|
|
120
120
|
title: videoOptions.titleText,
|
|
121
|
-
subtitles: videoOptions.subtitles
|
|
121
|
+
subtitles: videoOptions.subtitles,
|
|
122
|
+
attachment_id: videoOptions.attachment_id
|
|
122
123
|
};
|
|
123
|
-
if (RCEGlobals.getFeatures().media_links_use_attachment_id) {
|
|
124
|
-
data.attachment_id = videoOptions.attachment_id;
|
|
125
|
-
}
|
|
126
124
|
|
|
127
125
|
// If the video just edited came from a file uploaded to canvas
|
|
128
126
|
// and not notorious, we probably don't have a media_object_id.
|
|
@@ -20,7 +20,6 @@ import { fromImageEmbed, fromVideoEmbed } from '../instructure_image/ImageEmbedO
|
|
|
20
20
|
import { isOnlyTextSelected } from '../../contentInsertionUtils';
|
|
21
21
|
import formatMessage from '../../../format-message';
|
|
22
22
|
import { isStudioEmbeddedMedia } from './StudioLtiSupportUtils';
|
|
23
|
-
import RCEGlobals from '../../RCEGlobals';
|
|
24
23
|
import { parseUrlPath } from '../../../util/url-util';
|
|
25
24
|
const FILE_DOWNLOAD_PATH_REGEX = /^\/(courses\/\d+\/)?files\/\d+\/download$/;
|
|
26
25
|
export const LINK_TYPE = 'link';
|
|
@@ -124,12 +123,10 @@ export function asAudioElement($element) {
|
|
|
124
123
|
// eslint-disable-next-line no-empty
|
|
125
124
|
} catch (e) {}
|
|
126
125
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
audioOptions.attachmentId = matches[1];
|
|
132
|
-
}
|
|
126
|
+
const source = $audioIframe.getAttribute('src');
|
|
127
|
+
const matches = source?.match(/\/media_attachments_iframe\/(\d+)/);
|
|
128
|
+
if (matches) {
|
|
129
|
+
audioOptions.attachmentId = matches[1];
|
|
133
130
|
}
|
|
134
131
|
return audioOptions;
|
|
135
132
|
}
|
|
@@ -83,7 +83,7 @@ export function mediaPlayerURLFromFile(file, canvasOrigin) {
|
|
|
83
83
|
if (typeof content_type !== 'string') throw new Error('Invalid content type');
|
|
84
84
|
const type = content_type.replace(/\/.*$/, '');
|
|
85
85
|
const baseOrigin = canvasOrigin !== null && canvasOrigin !== void 0 ? canvasOrigin : window.location.origin;
|
|
86
|
-
if (
|
|
86
|
+
if (isAudioOrVideo(content_type) && file.id) {
|
|
87
87
|
const url = new URL(`/media_attachments_iframe/${file.id}`, baseOrigin);
|
|
88
88
|
url.searchParams.set('type', type);
|
|
89
89
|
url.searchParams.set('embedded', 'true');
|
|
@@ -27,60 +27,58 @@ const pendingInstanceCallbacks = [];
|
|
|
27
27
|
const container = document.createElement('div');
|
|
28
28
|
container.className = 'tinymce-a11y-checker-container';
|
|
29
29
|
document.body.appendChild(container);
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
80
|
-
});
|
|
30
|
+
const AccessibilityChecker = function (ed) {
|
|
31
|
+
ed.addCommand('openAccessibilityChecker', function (ui, {
|
|
32
|
+
done,
|
|
33
|
+
config,
|
|
34
|
+
additionalRules,
|
|
35
|
+
mountNode,
|
|
36
|
+
triggerElementId,
|
|
37
|
+
onFixError
|
|
38
|
+
}) {
|
|
39
|
+
if (!isCheckerOpen) {
|
|
40
|
+
ReactDOM.render(/*#__PURE__*/React.createElement(Checker, {
|
|
41
|
+
getBody: ed.getBody.bind(ed),
|
|
42
|
+
editor: ed,
|
|
43
|
+
additionalRules: additionalRules,
|
|
44
|
+
mountNode: mountNode,
|
|
45
|
+
onClose: () => {
|
|
46
|
+
isCheckerOpen = false;
|
|
47
|
+
if (triggerElementId) {
|
|
48
|
+
const button = document.querySelectorAll(`[data-btn-id=${triggerElementId}]`);
|
|
49
|
+
button[0]?.focus();
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
onFixError: onFixError
|
|
53
|
+
}), container, function () {
|
|
54
|
+
// this is a workaround for react 16 since ReactDOM.render is not
|
|
55
|
+
// guaranteed to return the instance synchronously (especially if called
|
|
56
|
+
// within another component's lifecycle method eg: componentDidMount). see:
|
|
57
|
+
// https://github.com/facebook/react/issues/10309#issuecomment-318434635
|
|
58
|
+
instance = this;
|
|
59
|
+
if (config) getInstance(instance => instance.setConfig(config));
|
|
60
|
+
pendingInstanceCallbacks.forEach(cb => cb(instance));
|
|
61
|
+
instance.check(done);
|
|
62
|
+
});
|
|
63
|
+
isCheckerOpen = true;
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
ed.addCommand('checkAccessibility', function (ui, {
|
|
67
|
+
done,
|
|
68
|
+
config,
|
|
69
|
+
additionalRules
|
|
70
|
+
}) {
|
|
71
|
+
checkNode(ed.getBody(), done, config, additionalRules);
|
|
72
|
+
});
|
|
73
|
+
ed.ui.registry.addButton('check_a11y', {
|
|
74
|
+
title: formatMessage('Check Accessibility'),
|
|
75
|
+
onAction: _ => ed.execCommand('openAccessibilityChecker'),
|
|
76
|
+
icon: 'a11y'
|
|
77
|
+
});
|
|
78
|
+
};
|
|
81
79
|
|
|
82
80
|
// Register plugin
|
|
83
|
-
tinymce.PluginManager.add('a11y_checker',
|
|
81
|
+
tinymce.PluginManager.add('a11y_checker', AccessibilityChecker);
|
|
84
82
|
export function getInstance(cb) {
|
|
85
83
|
if (instance != null) {
|
|
86
84
|
return cb(instance);
|
package/es/rce/style.js
CHANGED
|
@@ -62,44 +62,44 @@ export default function buildStyle() {
|
|
|
62
62
|
break;
|
|
63
63
|
case 'canvas-a11y':
|
|
64
64
|
case 'canvas-high-contrast':
|
|
65
|
-
themeCanvasButtonBackground = variables.colors.
|
|
66
|
-
themeCanvasSecondaryButtonBorderColor = variables.colors.
|
|
65
|
+
themeCanvasButtonBackground = variables.colors.contrasts.grey1111;
|
|
66
|
+
themeCanvasSecondaryButtonBorderColor = variables.colors.contrasts.grey1214;
|
|
67
67
|
themeCanvasLinkDecoration = 'underline';
|
|
68
|
-
themeCanvasFocusBorderColor = variables.colors.
|
|
68
|
+
themeCanvasFocusBorderColor = variables.colors.contrasts.blue4570;
|
|
69
69
|
themeCanvasFocusBoxShadow = `0 0 0 2px ${variables.colors.brand}`;
|
|
70
70
|
themeCanvasBrandColor = variables.colors.brand;
|
|
71
71
|
break;
|
|
72
72
|
default:
|
|
73
73
|
themeCanvasLinkColor = variables.colors.link;
|
|
74
74
|
themeCanvasLinkDecoration = 'none';
|
|
75
|
-
themeCanvasTextColor = variables.colors.
|
|
75
|
+
themeCanvasTextColor = variables.colors.contrasts.grey125125;
|
|
76
76
|
themeCanvasBrandColor = variables.colors.brand;
|
|
77
77
|
themeCanvasFocusBorderColor = variables.borders.brand;
|
|
78
78
|
themeCanvasFocusBoxShadow = `0 0 0 2px ${variables.colors.brand}`;
|
|
79
79
|
themeCanvasEnabledColor = variables.borders.brand;
|
|
80
|
-
themeCanvasPrimaryButtonBackground = variables.colors.
|
|
81
|
-
themeCanvasPrimaryButtonColor = variables.colors.
|
|
82
|
-
themeCanvasPrimaryButtonHoverBackground = darken(variables.colors.
|
|
83
|
-
themeActiveMenuItemBackground = variables.colors.
|
|
84
|
-
themeActiveMenuItemLabelColor = variables.colors.
|
|
80
|
+
themeCanvasPrimaryButtonBackground = variables.colors.contrasts.blue4570;
|
|
81
|
+
themeCanvasPrimaryButtonColor = variables.colors.contrasts.white1010;
|
|
82
|
+
themeCanvasPrimaryButtonHoverBackground = darken(variables.colors.contrasts.blue4570, 10);
|
|
83
|
+
themeActiveMenuItemBackground = variables.colors.contrasts.blue4570;
|
|
84
|
+
themeActiveMenuItemLabelColor = variables.colors.contrasts.white1010;
|
|
85
85
|
themeTableSelectorHighlightColor = alpha(lighten(variables.colors.brand, 10), 50);
|
|
86
|
-
themeCanvasButtonBackground = variables.colors.
|
|
87
|
-
themeCanvasSecondaryButtonBorderColor = darken(variables.colors.
|
|
86
|
+
themeCanvasButtonBackground = variables.colors.contrasts.white1010;
|
|
87
|
+
themeCanvasSecondaryButtonBorderColor = darken(variables.colors.contrasts.grey1111, 10);
|
|
88
88
|
break;
|
|
89
89
|
}
|
|
90
90
|
const classNames = {
|
|
91
91
|
root: 'canvas-rce__skins--root'
|
|
92
92
|
};
|
|
93
|
-
const toolbarButtonHoverBackgroundConst = darken(variables.colors.
|
|
93
|
+
const toolbarButtonHoverBackgroundConst = darken(variables.colors.contrasts.white1010, 5);
|
|
94
94
|
const tinySplitButtonChevronHoverBackgroundConst = darken(toolbarButtonHoverBackgroundConst, 10);
|
|
95
95
|
const theme = {
|
|
96
96
|
canvasBackgroundColor: variables.colors.white,
|
|
97
97
|
canvasTextColor: themeCanvasTextColor,
|
|
98
|
-
canvasErrorColor: variables.colors.
|
|
99
|
-
canvasWarningColor: variables.colors.
|
|
100
|
-
canvasInfoColor: variables.colors.
|
|
101
|
-
canvasSuccessColor: variables.colors.
|
|
102
|
-
canvasBorderColor: variables.colors.
|
|
98
|
+
canvasErrorColor: variables.colors.contrasts.red4570,
|
|
99
|
+
canvasWarningColor: variables.colors.contrasts.orange4570,
|
|
100
|
+
canvasInfoColor: variables.colors.contrasts.blue4570,
|
|
101
|
+
canvasSuccessColor: variables.colors.contrasts.green4570,
|
|
102
|
+
canvasBorderColor: variables.colors.contrasts.grey1214,
|
|
103
103
|
toolbarButtonHoverBackground: toolbarButtonHoverBackgroundConst,
|
|
104
104
|
// copied from INSTUI "light" Button
|
|
105
105
|
tinySplitButtonChevronHoverBackground: tinySplitButtonChevronHoverBackgroundConst,
|
|
@@ -112,10 +112,10 @@ export default function buildStyle() {
|
|
|
112
112
|
// the instui default button
|
|
113
113
|
canvasButtonBackground: themeCanvasButtonBackground,
|
|
114
114
|
canvasButtonBorderColor: 'transparent',
|
|
115
|
-
canvasButtonColor: variables.colors.
|
|
116
|
-
canvasButtonHoverBackground: variables.colors.
|
|
115
|
+
canvasButtonColor: variables.colors.contrasts.grey125125,
|
|
116
|
+
canvasButtonHoverBackground: variables.colors.contrasts.white1010,
|
|
117
117
|
canvasButtonHoverColor: variables.colors.brand,
|
|
118
|
-
canvasButtonActiveBackground: variables.colors.
|
|
118
|
+
canvasButtonActiveBackground: variables.colors.contrasts.white1010,
|
|
119
119
|
canvasButtonFontWeight: variables.typography.fontWeightNormal,
|
|
120
120
|
canvasButtonFontSize: variables.typography.fontSizeMedium,
|
|
121
121
|
canvasButtonLineHeight: variables.forms.inputHeightMedium,
|
|
@@ -125,19 +125,19 @@ export default function buildStyle() {
|
|
|
125
125
|
canvasPrimaryButtonColor: themeCanvasPrimaryButtonColor,
|
|
126
126
|
canvasPrimaryButtonBorderColor: 'transparent',
|
|
127
127
|
canvasPrimaryButtonHoverBackground: themeCanvasPrimaryButtonHoverBackground,
|
|
128
|
-
canvasPrimaryButtonHoverColor: variables.colors.
|
|
128
|
+
canvasPrimaryButtonHoverColor: variables.colors.contrasts.white1010,
|
|
129
129
|
// the instui secondary button
|
|
130
|
-
canvasSecondaryButtonBackground: variables.colors.
|
|
131
|
-
canvasSecondaryButtonColor: variables.colors.
|
|
130
|
+
canvasSecondaryButtonBackground: variables.colors.contrasts.grey1111,
|
|
131
|
+
canvasSecondaryButtonColor: variables.colors.contrasts.grey125125,
|
|
132
132
|
canvasSecondaryButtonBorderColor: themeCanvasSecondaryButtonBorderColor,
|
|
133
|
-
canvasSecondaryButtonHoverBackground: darken(variables.colors.
|
|
134
|
-
canvasSecondaryButtonHoverColor: variables.colors.
|
|
133
|
+
canvasSecondaryButtonHoverBackground: darken(variables.colors.contrasts.grey1111, 10),
|
|
134
|
+
canvasSecondaryButtonHoverColor: variables.colors.contrasts.grey125125,
|
|
135
135
|
canvasFocusBorderColor: themeCanvasFocusBorderColor,
|
|
136
136
|
canvasFocusBorderWidth: variables.borders.widthSmall,
|
|
137
137
|
// canvas really uses widthMedium
|
|
138
138
|
canvasFocusBoxShadow: themeCanvasFocusBoxShadow,
|
|
139
139
|
canvasEnabledColor: themeCanvasEnabledColor,
|
|
140
|
-
canvasEnabledBoxShadow: `inset 0 0 0.1875rem 0.0625rem ${darken(variables.colors.
|
|
140
|
+
canvasEnabledBoxShadow: `inset 0 0 0.1875rem 0.0625rem ${darken(variables.colors.contrasts.white1010, 25)}`,
|
|
141
141
|
canvasFontFamily: variables.typography.fontFamily,
|
|
142
142
|
canvasFontSize: '1rem',
|
|
143
143
|
canvasFontSizeSmall: variables.typography.fontSizeSmall,
|
|
@@ -148,14 +148,14 @@ export default function buildStyle() {
|
|
|
148
148
|
canvasModalHeadingFontWeight: variables.typography.fontWeightNormal,
|
|
149
149
|
canvasModalBodyPadding: variables.spacing.medium,
|
|
150
150
|
canvasModalFooterPadding: variables.spacing.small,
|
|
151
|
-
canvasModalFooterBackground: variables.colors.
|
|
151
|
+
canvasModalFooterBackground: variables.colors.contrasts.grey1111,
|
|
152
152
|
canvasFormElementMargin: `0 0 ${variables.spacing.medium} 0`,
|
|
153
|
-
canvasFormElementLabelColor: variables.colors.
|
|
153
|
+
canvasFormElementLabelColor: variables.colors.contrasts.grey125125,
|
|
154
154
|
canvasFormElementLabelMargin: `0 0 ${variables.spacing.small} 0`,
|
|
155
155
|
canvasFormElementLabelFontSize: variables.typography.fontSizeMedium,
|
|
156
156
|
canvasFormElementLabelFontWeight: variables.typography.fontWeightBold,
|
|
157
157
|
// a11y button badge
|
|
158
|
-
canvasBadgeBackgroundColor: variables.colors.
|
|
158
|
+
canvasBadgeBackgroundColor: variables.colors.contrasts.blue4570
|
|
159
159
|
};
|
|
160
160
|
const css = `
|
|
161
161
|
.${classNames.root} {
|
package/es/rcs/api.d.ts
CHANGED
|
@@ -63,7 +63,10 @@ declare class RceApiSource {
|
|
|
63
63
|
bookmark: any;
|
|
64
64
|
files: any;
|
|
65
65
|
}>;
|
|
66
|
-
fetchMedia(props: any): Promise<
|
|
66
|
+
fetchMedia(props: any): Promise<{
|
|
67
|
+
bookmark: any;
|
|
68
|
+
files: any;
|
|
69
|
+
}>;
|
|
67
70
|
fetchFiles(uri: any): Promise<{
|
|
68
71
|
bookmark: any;
|
|
69
72
|
files: any;
|
package/es/rcs/api.js
CHANGED
|
@@ -21,7 +21,6 @@ import { saveClosedCaptions, saveClosedCaptionsForAttachment, CONSTANTS } from '
|
|
|
21
21
|
import { downloadToWrap, fixupFileUrl } from '../common/fileUrl';
|
|
22
22
|
import alertHandler from '../rce/alertHandler';
|
|
23
23
|
import buildError from './buildError';
|
|
24
|
-
import RCEGlobals from '../rce/RCEGlobals';
|
|
25
24
|
import { parseUrlPath } from '../util/url-util';
|
|
26
25
|
export function headerFor(jwt) {
|
|
27
26
|
return {
|
|
@@ -164,18 +163,15 @@ class RceApiSource {
|
|
|
164
163
|
fetchMedia(props) {
|
|
165
164
|
const media = props.media[props.contextType];
|
|
166
165
|
const uri = media.bookmark || this.uriFor('media', props);
|
|
167
|
-
|
|
168
|
-
|
|
166
|
+
return this.apiFetch(uri, headerFor(this.jwt)).then(({
|
|
167
|
+
bookmark,
|
|
168
|
+
files
|
|
169
|
+
}) => {
|
|
170
|
+
return {
|
|
169
171
|
bookmark,
|
|
170
|
-
files
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
bookmark,
|
|
174
|
-
files: files.map(f => fixupFileUrl(props.contextType, props.contextId, f, this.canvasOrigin))
|
|
175
|
-
};
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
return this.apiFetch(uri, headerFor(this.jwt));
|
|
172
|
+
files: files.map(f => fixupFileUrl(props.contextType, props.contextId, f, this.canvasOrigin))
|
|
173
|
+
};
|
|
174
|
+
});
|
|
179
175
|
}
|
|
180
176
|
fetchFiles(uri) {
|
|
181
177
|
return this.fetchPage(uri).then(({
|
|
@@ -219,7 +215,7 @@ class RceApiSource {
|
|
|
219
215
|
title,
|
|
220
216
|
attachment_id
|
|
221
217
|
}) {
|
|
222
|
-
const uri =
|
|
218
|
+
const uri = attachment_id ? `${this.baseUri('media_attachments', apiProps.host)}/${attachment_id}?user_entered_title=${encodeURIComponent(title)}` : `${this.baseUri('media_objects', apiProps.host)}/${media_object_id}?user_entered_title=${encodeURIComponent(title)}`;
|
|
223
219
|
return this.apiPost(uri, headerFor(this.jwt), null, 'PUT');
|
|
224
220
|
}
|
|
225
221
|
|
|
@@ -1123,6 +1123,9 @@ const locale = {
|
|
|
1123
1123
|
"intersection_cd4590e4": {
|
|
1124
1124
|
"message": "Intersection"
|
|
1125
1125
|
},
|
|
1126
|
+
"invalid_description_991e23bb": {
|
|
1127
|
+
"message": "Invalid description"
|
|
1128
|
+
},
|
|
1126
1129
|
"invalid_entry_f7d2a0f5": {
|
|
1127
1130
|
"message": "Invalid entry."
|
|
1128
1131
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@instructure/canvas-rce",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "7.0.0",
|
|
4
4
|
"description": "A component wrapping Canvas's usage of Tinymce",
|
|
5
5
|
"main": "es/index.js",
|
|
6
6
|
"types": "es/index.d.ts",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"build:canvas": "yarn run build",
|
|
37
37
|
"build:watch": "yarn clean:es && yarn build:es --watch",
|
|
38
38
|
"build:cafe": "wp --config webpack.testcafe.config.js",
|
|
39
|
-
"prepublishOnly": "yarn build",
|
|
39
|
+
"prepublishOnly": "yarn build && yarn test",
|
|
40
40
|
"fmt:check": "biome check",
|
|
41
41
|
"fmt:fix": "biome format --write",
|
|
42
42
|
"clean": "rm -rf lib && yarn clean:es && rm -rf coverage && yarn demo:clean",
|
|
@@ -71,66 +71,66 @@
|
|
|
71
71
|
"instrument": false
|
|
72
72
|
},
|
|
73
73
|
"dependencies": {
|
|
74
|
-
"@instructure/canvas-theme": "
|
|
74
|
+
"@instructure/canvas-theme": "10.11.0",
|
|
75
75
|
"@instructure/canvas-media": "*",
|
|
76
|
-
"@instructure/debounce": "
|
|
77
|
-
"@instructure/emotion": "
|
|
76
|
+
"@instructure/debounce": "10.11.0",
|
|
77
|
+
"@instructure/emotion": "10.11.0",
|
|
78
78
|
"@instructure/k5uploader": "*",
|
|
79
79
|
"@instructure/media-capture": "^9.0.0",
|
|
80
|
-
"@instructure/theme-registry": "
|
|
81
|
-
"@instructure/ui-a11y-content": "
|
|
82
|
-
"@instructure/ui-a11y-utils": "
|
|
83
|
-
"@instructure/ui-alerts": "
|
|
84
|
-
"@instructure/ui-avatar": "
|
|
85
|
-
"@instructure/ui-badge": "
|
|
86
|
-
"@instructure/ui-billboard": "
|
|
87
|
-
"@instructure/ui-buttons": "
|
|
88
|
-
"@instructure/ui-checkbox": "
|
|
89
|
-
"@instructure/ui-source-code-editor": "
|
|
90
|
-
"@instructure/ui-color-picker": "
|
|
91
|
-
"@instructure/ui-color-utils": "
|
|
92
|
-
"@instructure/ui-file-drop": "
|
|
93
|
-
"@instructure/ui-flex": "
|
|
94
|
-
"@instructure/ui-focusable": "
|
|
95
|
-
"@instructure/ui-form-field": "
|
|
96
|
-
"@instructure/ui-grid": "
|
|
97
|
-
"@instructure/ui-heading": "
|
|
98
|
-
"@instructure/ui-icons": "
|
|
99
|
-
"@instructure/ui-img": "
|
|
100
|
-
"@instructure/ui-link": "
|
|
101
|
-
"@instructure/ui-list": "
|
|
80
|
+
"@instructure/theme-registry": "10.11.0",
|
|
81
|
+
"@instructure/ui-a11y-content": "10.11.0",
|
|
82
|
+
"@instructure/ui-a11y-utils": "10.11.0",
|
|
83
|
+
"@instructure/ui-alerts": "10.11.0",
|
|
84
|
+
"@instructure/ui-avatar": "10.11.0",
|
|
85
|
+
"@instructure/ui-badge": "10.11.0",
|
|
86
|
+
"@instructure/ui-billboard": "10.11.0",
|
|
87
|
+
"@instructure/ui-buttons": "10.11.0",
|
|
88
|
+
"@instructure/ui-checkbox": "10.11.0",
|
|
89
|
+
"@instructure/ui-source-code-editor": "10.11.0",
|
|
90
|
+
"@instructure/ui-color-picker": "10.11.0",
|
|
91
|
+
"@instructure/ui-color-utils": "10.11.0",
|
|
92
|
+
"@instructure/ui-file-drop": "10.11.0",
|
|
93
|
+
"@instructure/ui-flex": "10.11.0",
|
|
94
|
+
"@instructure/ui-focusable": "10.11.0",
|
|
95
|
+
"@instructure/ui-form-field": "10.11.0",
|
|
96
|
+
"@instructure/ui-grid": "10.11.0",
|
|
97
|
+
"@instructure/ui-heading": "10.11.0",
|
|
98
|
+
"@instructure/ui-icons": "10.11.0",
|
|
99
|
+
"@instructure/ui-img": "10.11.0",
|
|
100
|
+
"@instructure/ui-link": "10.11.0",
|
|
101
|
+
"@instructure/ui-list": "10.11.0",
|
|
102
102
|
"@instructure/ui-media-player": "^9.0.0",
|
|
103
|
-
"@instructure/ui-menu": "
|
|
104
|
-
"@instructure/ui-modal": "
|
|
105
|
-
"@instructure/ui-motion": "
|
|
106
|
-
"@instructure/ui-number-input": "
|
|
107
|
-
"@instructure/ui-overlays": "
|
|
108
|
-
"@instructure/ui-pagination": "
|
|
109
|
-
"@instructure/ui-pill": "
|
|
110
|
-
"@instructure/ui-popover": "
|
|
111
|
-
"@instructure/ui-radio-input": "
|
|
112
|
-
"@instructure/ui-simple-select": "
|
|
113
|
-
"@instructure/ui-spinner": "
|
|
114
|
-
"@instructure/ui-svg-images": "
|
|
115
|
-
"@instructure/ui-table": "
|
|
116
|
-
"@instructure/ui-tabs": "
|
|
117
|
-
"@instructure/ui-text-area": "
|
|
118
|
-
"@instructure/ui-text-input": "
|
|
119
|
-
"@instructure/ui-text": "
|
|
120
|
-
"@instructure/ui-themes": "
|
|
121
|
-
"@instructure/ui-toggle-details": "
|
|
122
|
-
"@instructure/ui-tooltip": "
|
|
123
|
-
"@instructure/ui-tray": "
|
|
124
|
-
"@instructure/ui-tree-browser": "
|
|
125
|
-
"@instructure/ui-truncate-text": "
|
|
126
|
-
"@instructure/ui-utils": "
|
|
127
|
-
"@instructure/ui-view": "
|
|
128
|
-
"@instructure/uid": "
|
|
103
|
+
"@instructure/ui-menu": "10.11.0",
|
|
104
|
+
"@instructure/ui-modal": "10.11.0",
|
|
105
|
+
"@instructure/ui-motion": "10.11.0",
|
|
106
|
+
"@instructure/ui-number-input": "10.11.0",
|
|
107
|
+
"@instructure/ui-overlays": "10.11.0",
|
|
108
|
+
"@instructure/ui-pagination": "10.11.0",
|
|
109
|
+
"@instructure/ui-pill": "10.11.0",
|
|
110
|
+
"@instructure/ui-popover": "10.11.0",
|
|
111
|
+
"@instructure/ui-radio-input": "10.11.0",
|
|
112
|
+
"@instructure/ui-simple-select": "10.11.0",
|
|
113
|
+
"@instructure/ui-spinner": "10.11.0",
|
|
114
|
+
"@instructure/ui-svg-images": "10.11.0",
|
|
115
|
+
"@instructure/ui-table": "10.11.0",
|
|
116
|
+
"@instructure/ui-tabs": "10.11.0",
|
|
117
|
+
"@instructure/ui-text-area": "10.11.0",
|
|
118
|
+
"@instructure/ui-text-input": "10.11.0",
|
|
119
|
+
"@instructure/ui-text": "10.11.0",
|
|
120
|
+
"@instructure/ui-themes": "10.11.0",
|
|
121
|
+
"@instructure/ui-toggle-details": "10.11.0",
|
|
122
|
+
"@instructure/ui-tooltip": "10.11.0",
|
|
123
|
+
"@instructure/ui-tray": "10.11.0",
|
|
124
|
+
"@instructure/ui-tree-browser": "10.11.0",
|
|
125
|
+
"@instructure/ui-truncate-text": "10.11.0",
|
|
126
|
+
"@instructure/ui-utils": "10.11.0",
|
|
127
|
+
"@instructure/ui-view": "10.11.0",
|
|
128
|
+
"@instructure/uid": "10.11.0",
|
|
129
129
|
"@sheerun/mutationobserver-shim": "^0.3.2",
|
|
130
130
|
"@types/tinycolor2": "^1.4.6",
|
|
131
131
|
"@tinymce/tinymce-react": "~3.8.4",
|
|
132
132
|
"aphrodite": "^2",
|
|
133
|
-
"axios": "^0.
|
|
133
|
+
"axios": "^0.28.0",
|
|
134
134
|
"bloody-offset": "0.0.0",
|
|
135
135
|
"crypto-es": "^2.0.4",
|
|
136
136
|
"classnames": "^2.2.5",
|