@instructure/canvas-rce 5.15.7 → 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 +43 -0
- package/Dockerfile +1 -1
- package/es/bridge/Bridge.d.ts +6 -0
- package/es/common/browser.js +2 -2
- package/es/common/fileUrl.js +13 -3
- package/es/defaultTinymceConfig.js +165 -4
- package/es/enhance-user-content/enhance_user_content.js +1 -1
- package/es/enhance-user-content/instructure_helper.js +7 -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 +6 -27
- package/es/rce/contentRendering.js +3 -2
- 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_image/ImageOptionsTray/index.js +33 -9
- package/es/rce/plugins/instructure_rce_external_tools/RceToolWrapper.d.ts +1 -1
- package/es/rce/plugins/instructure_record/AudioOptionsTray/TrayController.js +1 -2
- package/es/rce/plugins/instructure_record/VideoOptionsTray/TrayController.d.ts +13 -3
- package/es/rce/plugins/instructure_record/VideoOptionsTray/TrayController.js +12 -4
- package/es/rce/plugins/instructure_record/mediaTranslations.d.ts +3 -0
- package/es/rce/plugins/instructure_record/mediaTranslations.js +4 -1
- package/es/rce/plugins/shared/ContentSelection.js +4 -7
- package/es/rce/plugins/shared/DimensionsInput/DimensionInput.d.ts +4 -2
- package/es/rce/plugins/shared/DimensionsInput/DimensionInput.js +10 -3
- package/es/rce/plugins/shared/DimensionsInput/index.d.ts +2 -0
- package/es/rce/plugins/shared/DimensionsInput/index.js +9 -5
- package/es/rce/plugins/shared/ImageOptionsForm.d.ts +4 -1
- package/es/rce/plugins/shared/ImageOptionsForm.js +13 -3
- package/es/rce/plugins/shared/Upload/UrlPanel.d.ts +9 -3
- package/es/rce/plugins/shared/Upload/UrlPanel.js +13 -4
- package/es/rce/plugins/shared/do-fetch-api-effect/parse-link-header.js +1 -1
- package/es/rce/plugins/shared/fileTypeUtils.js +1 -1
- package/es/rce/plugins/tinymce-a11y-checker/node-checker.js +3 -2
- package/es/rce/plugins/tinymce-a11y-checker/plugin.js +50 -52
- package/es/rce/plugins/tinymce-a11y-checker/utils/dom.d.ts +6 -0
- package/es/rce/plugins/tinymce-a11y-checker/utils/dom.js +15 -0
- package/es/rce/plugins/tinymce-a11y-checker/utils/rule-enhancer.d.ts +14 -0
- package/es/rce/plugins/tinymce-a11y-checker/utils/rule-enhancer.js +53 -0
- package/es/rce/screenreaderOnFormat.d.ts +2 -0
- package/es/rce/screenreaderOnFormat.js +109 -0
- 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/ar.js +42 -0
- package/es/translations/locales/ca.js +42 -0
- package/es/translations/locales/cy.js +42 -0
- package/es/translations/locales/da-x-k12.js +42 -0
- package/es/translations/locales/da.js +42 -0
- package/es/translations/locales/de.js +42 -0
- package/es/translations/locales/en-AU-x-unimelb.js +42 -0
- package/es/translations/locales/en-GB-x-ukhe.js +42 -0
- package/es/translations/locales/en.js +54 -0
- package/es/translations/locales/en_AU.js +42 -0
- package/es/translations/locales/en_CA.js +42 -0
- package/es/translations/locales/en_CY.js +42 -0
- package/es/translations/locales/en_GB.js +42 -0
- package/es/translations/locales/es.js +42 -0
- package/es/translations/locales/es_ES.js +42 -0
- package/es/translations/locales/fi.js +42 -0
- package/es/translations/locales/fr.js +42 -0
- package/es/translations/locales/fr_CA.js +42 -0
- package/es/translations/locales/ga.js +114 -0
- package/es/translations/locales/hi.js +42 -0
- package/es/translations/locales/ht.js +42 -0
- package/es/translations/locales/id.js +42 -0
- package/es/translations/locales/is.js +42 -0
- package/es/translations/locales/it.js +42 -0
- package/es/translations/locales/ja.js +42 -0
- package/es/translations/locales/mi.js +42 -0
- package/es/translations/locales/ms.js +42 -0
- package/es/translations/locales/nb-x-k12.js +42 -0
- package/es/translations/locales/nb.js +42 -0
- package/es/translations/locales/nl.js +42 -0
- package/es/translations/locales/pl.js +42 -0
- package/es/translations/locales/pt.js +42 -0
- package/es/translations/locales/pt_BR.js +42 -0
- package/es/translations/locales/ru.js +42 -0
- package/es/translations/locales/sl.js +42 -0
- package/es/translations/locales/sv-x-k12.js +42 -0
- package/es/translations/locales/sv.js +42 -0
- package/es/translations/locales/th.js +42 -0
- package/es/translations/locales/vi.js +42 -0
- package/es/translations/locales/zh-Hans.js +42 -0
- package/es/translations/locales/zh-Hant.js +42 -0
- package/es/translations/locales/zh.js +42 -0
- package/es/translations/locales/zh_HK.js +42 -0
- package/es/util/loadingPlaceholder.js +4 -3
- package/package.json +55 -54
- package/coverage/canvas-rce-jest.xml +0 -7028
- package/tsconfig.tsbuildinfo +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* Copyright (C)
|
|
2
|
+
* Copyright (C) 2025 - present Instructure, Inc.
|
|
3
3
|
*
|
|
4
4
|
* This file is part of Canvas.
|
|
5
5
|
*
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
import React, { useState, useEffect } from 'react';
|
|
19
|
+
import React, { useState, useEffect, useRef } from 'react';
|
|
20
20
|
import { bool, func, number, shape, string } from 'prop-types';
|
|
21
21
|
import { Button, CloseButton } from '@instructure/ui-buttons';
|
|
22
22
|
import { Heading } from '@instructure/ui-heading';
|
|
@@ -60,6 +60,17 @@ export default function ImageOptionsTray(props) {
|
|
|
60
60
|
minWidth: MIN_WIDTH,
|
|
61
61
|
minPercentage: MIN_PERCENTAGE
|
|
62
62
|
});
|
|
63
|
+
const [altHasError, setAltHasError] = useState(false);
|
|
64
|
+
const [urlHasError, setUrlHasError] = useState(false);
|
|
65
|
+
const [dimensionsHasError, setDimensionsHasError] = useState(false);
|
|
66
|
+
const urlRef = useRef(null);
|
|
67
|
+
const altRef = useRef(null);
|
|
68
|
+
const dimensionsRef = useRef(null);
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
setUrlHasError(showUrlField && url === '');
|
|
71
|
+
setAltHasError(displayAs === 'embed' && !isDecorativeImage && altText === '');
|
|
72
|
+
setDimensionsHasError(imageSize === CUSTOM && !dimensionsState?.isValid);
|
|
73
|
+
}, [isDecorativeImage, altText, url, displayAs, showUrlField, imageSize, dimensionsState?.isValid]);
|
|
63
74
|
function handleUrlChange(newUrl) {
|
|
64
75
|
setUrl(newUrl);
|
|
65
76
|
}
|
|
@@ -72,7 +83,7 @@ export default function ImageOptionsTray(props) {
|
|
|
72
83
|
function handleDisplayAsChange(event) {
|
|
73
84
|
setDisplayAs(event.target.value);
|
|
74
85
|
}
|
|
75
|
-
function handleImageSizeChange(
|
|
86
|
+
function handleImageSizeChange(_e, selectedOption) {
|
|
76
87
|
setImageSize(selectedOption.value);
|
|
77
88
|
if (selectedOption.value === CUSTOM) {
|
|
78
89
|
setImageHeight(currentHeight);
|
|
@@ -88,6 +99,18 @@ export default function ImageOptionsTray(props) {
|
|
|
88
99
|
}
|
|
89
100
|
function handleSave(event) {
|
|
90
101
|
event.preventDefault();
|
|
102
|
+
if (urlHasError || altHasError || dimensionsHasError) {
|
|
103
|
+
if (altHasError) {
|
|
104
|
+
altRef?.current?.focus();
|
|
105
|
+
}
|
|
106
|
+
if (urlHasError) {
|
|
107
|
+
urlRef?.current?.focus();
|
|
108
|
+
}
|
|
109
|
+
if (dimensionsHasError) {
|
|
110
|
+
dimensionsRef?.current?.focus();
|
|
111
|
+
}
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
91
114
|
const savedAltText = isDecorativeImage ? '' : altText;
|
|
92
115
|
let appliedHeight = imageHeight;
|
|
93
116
|
let appliedWidth = imageWidth;
|
|
@@ -134,9 +157,6 @@ export default function ImageOptionsTray(props) {
|
|
|
134
157
|
type: 'hint'
|
|
135
158
|
});
|
|
136
159
|
}
|
|
137
|
-
const disableForIcons = isIconMaker && !isDecorativeImage && altText === '';
|
|
138
|
-
const disableForImages = url === '' || displayAs === 'embed' && (!isDecorativeImage && altText === '' || imageSize === CUSTOM && !dimensionsState?.isValid);
|
|
139
|
-
const saveDisabled = isIconMaker ? disableForIcons : disableForImages;
|
|
140
160
|
const trayLabel = isIconMaker ? formatMessage('Icon Options Tray') : formatMessage('Image Options Tray');
|
|
141
161
|
const trayHeading = isIconMaker ? formatMessage('Icon Options') : formatMessage('Image Options');
|
|
142
162
|
return /*#__PURE__*/React.createElement(Tray, {
|
|
@@ -183,7 +203,9 @@ export default function ImageOptionsTray(props) {
|
|
|
183
203
|
padding: "small"
|
|
184
204
|
}, /*#__PURE__*/React.createElement(UrlPanel, {
|
|
185
205
|
fileUrl: url,
|
|
186
|
-
|
|
206
|
+
urlRef: urlRef,
|
|
207
|
+
setFileUrl: handleUrlChange,
|
|
208
|
+
urlHasError: urlHasError
|
|
187
209
|
})), /*#__PURE__*/React.createElement(ImageOptionsForm, {
|
|
188
210
|
id: "image-options-form",
|
|
189
211
|
imageSize: imageSize,
|
|
@@ -197,14 +219,16 @@ export default function ImageOptionsTray(props) {
|
|
|
197
219
|
handleDisplayAsChange: handleDisplayAsChange,
|
|
198
220
|
handleImageSizeChange: handleImageSizeChange,
|
|
199
221
|
messagesForSize: messagesForSize,
|
|
200
|
-
isIconMaker: isIconMaker
|
|
222
|
+
isIconMaker: isIconMaker,
|
|
223
|
+
altHasError: altHasError,
|
|
224
|
+
altRef: altRef,
|
|
225
|
+
dimensionsRef: dimensionsRef
|
|
201
226
|
})), /*#__PURE__*/React.createElement(Flex.Item, {
|
|
202
227
|
background: "secondary",
|
|
203
228
|
borderWidth: "small none none none",
|
|
204
229
|
padding: "small medium",
|
|
205
230
|
textAlign: "end"
|
|
206
231
|
}, /*#__PURE__*/React.createElement(Button, {
|
|
207
|
-
disabled: saveDisabled,
|
|
208
232
|
onClick: handleSave,
|
|
209
233
|
color: "primary"
|
|
210
234
|
}, formatMessage('Done')))))));
|
|
@@ -33,7 +33,7 @@ export declare class RceToolWrapper {
|
|
|
33
33
|
width: import("prop-types").Requireable<number>;
|
|
34
34
|
use_tray: import("prop-types").Requireable<boolean>;
|
|
35
35
|
canvas_icon_class: import("prop-types").Requireable<any>;
|
|
36
|
-
}, "
|
|
36
|
+
}, "id" | "name" | "width" | "height" | "description" | "favorite" | "on_by_default" | "icon_url" | "use_tray" | "canvas_icon_class">>>)[], mruIds?: string[]): RceToolWrapper[];
|
|
37
37
|
readonly iconId: string | null | undefined;
|
|
38
38
|
isMruTool: boolean;
|
|
39
39
|
get editor(): import("../../types").ExternalToolsEditor | null;
|
|
@@ -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
|
}
|
|
@@ -3,12 +3,22 @@ export namespace VIDEO_SIZE_DEFAULT {
|
|
|
3
3
|
let height: string;
|
|
4
4
|
let width: string;
|
|
5
5
|
}
|
|
6
|
-
export namespace
|
|
7
|
-
let width_1: string;
|
|
8
|
-
export { width_1 as width };
|
|
6
|
+
export namespace STUDIO_PLAYER_VIDEO_SIZE_DEFAULT {
|
|
9
7
|
let height_1: string;
|
|
10
8
|
export { height_1 as height };
|
|
9
|
+
let width_1: string;
|
|
10
|
+
export { width_1 as width };
|
|
11
|
+
}
|
|
12
|
+
export namespace AUDIO_PLAYER_SIZE {
|
|
13
|
+
let width_2: string;
|
|
14
|
+
export { width_2 as width };
|
|
15
|
+
let height_2: string;
|
|
16
|
+
export { height_2 as height };
|
|
11
17
|
}
|
|
18
|
+
export function videoDefaultSize(): {
|
|
19
|
+
height: string;
|
|
20
|
+
width: string;
|
|
21
|
+
};
|
|
12
22
|
export default class TrayController {
|
|
13
23
|
_editor: any;
|
|
14
24
|
_isOpen: boolean;
|
|
@@ -28,10 +28,20 @@ export const VIDEO_SIZE_DEFAULT = {
|
|
|
28
28
|
height: '225px',
|
|
29
29
|
width: '400px'
|
|
30
30
|
}; // AKA "LARGE"
|
|
31
|
+
export const STUDIO_PLAYER_VIDEO_SIZE_DEFAULT = {
|
|
32
|
+
height: '300px',
|
|
33
|
+
width: '480px'
|
|
34
|
+
};
|
|
31
35
|
export const AUDIO_PLAYER_SIZE = {
|
|
32
36
|
width: '320px',
|
|
33
37
|
height: '14.25rem'
|
|
34
38
|
};
|
|
39
|
+
export const videoDefaultSize = () => {
|
|
40
|
+
if (RCEGlobals.getFeatures().consolidated_media_player) {
|
|
41
|
+
return STUDIO_PLAYER_VIDEO_SIZE_DEFAULT;
|
|
42
|
+
}
|
|
43
|
+
return VIDEO_SIZE_DEFAULT;
|
|
44
|
+
};
|
|
35
45
|
export default class TrayController {
|
|
36
46
|
constructor() {
|
|
37
47
|
this._editor = null;
|
|
@@ -108,11 +118,9 @@ export default class TrayController {
|
|
|
108
118
|
const data = {
|
|
109
119
|
media_object_id: videoOptions.media_object_id,
|
|
110
120
|
title: videoOptions.titleText,
|
|
111
|
-
subtitles: videoOptions.subtitles
|
|
121
|
+
subtitles: videoOptions.subtitles,
|
|
122
|
+
attachment_id: videoOptions.attachment_id
|
|
112
123
|
};
|
|
113
|
-
if (RCEGlobals.getFeatures().media_links_use_attachment_id) {
|
|
114
|
-
data.attachment_id = videoOptions.attachment_id;
|
|
115
|
-
}
|
|
116
124
|
|
|
117
125
|
// If the video just edited came from a file uploaded to canvas
|
|
118
126
|
// and not notorious, we probably don't have a media_object_id.
|
|
@@ -25,6 +25,9 @@ declare namespace uploadMediaTranslations {
|
|
|
25
25
|
let ADDED_CAPTION: string;
|
|
26
26
|
let DELETED_CAPTION: string;
|
|
27
27
|
let PROGRESS_LABEL: string;
|
|
28
|
+
let SELECT_SUPPORTED_FILE_TYPE: string;
|
|
29
|
+
let CHOOSE_FILE_TO_UPLOAD: string;
|
|
30
|
+
let ENTER_FILE_NAME: string;
|
|
28
31
|
}
|
|
29
32
|
namespace SelectStrings {
|
|
30
33
|
let USE_ARROWS: string;
|
|
@@ -46,7 +46,10 @@ const uploadMediaTranslations = {
|
|
|
46
46
|
ADD_NEW_CAPTION_OR_SUBTITLE: formatMessage('Add another'),
|
|
47
47
|
ADDED_CAPTION: 'Captions for {lang} added',
|
|
48
48
|
DELETED_CAPTION: 'Deleted captions for {lang}',
|
|
49
|
-
PROGRESS_LABEL: formatMessage('Uploading')
|
|
49
|
+
PROGRESS_LABEL: formatMessage('Uploading'),
|
|
50
|
+
SELECT_SUPPORTED_FILE_TYPE: formatMessage('Please select a file of a supported type'),
|
|
51
|
+
CHOOSE_FILE_TO_UPLOAD: formatMessage('Please choose a file'),
|
|
52
|
+
ENTER_FILE_NAME: formatMessage('Please enter a file name')
|
|
50
53
|
},
|
|
51
54
|
SelectStrings: {
|
|
52
55
|
USE_ARROWS: 'Use arrow keys to navigate options.',
|
|
@@ -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
|
}
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
declare function DimensionInput(props: any): React.JSX.Element;
|
|
2
2
|
declare namespace DimensionInput {
|
|
3
3
|
namespace propTypes {
|
|
4
|
-
let dimensionState: import("prop-types").Requireable<import("prop-types").InferProps<{
|
|
4
|
+
export let dimensionState: import("prop-types").Requireable<import("prop-types").InferProps<{
|
|
5
5
|
addOffset: import("prop-types").Validator<(...args: any[]) => any>;
|
|
6
6
|
inputValue: import("prop-types").Validator<string>;
|
|
7
7
|
setInputValue: import("prop-types").Validator<(...args: any[]) => any>;
|
|
8
8
|
}>>;
|
|
9
|
-
|
|
9
|
+
export { object as dimensionsRef };
|
|
10
|
+
export let label: import("prop-types").Validator<string>;
|
|
10
11
|
}
|
|
11
12
|
}
|
|
12
13
|
export default DimensionInput;
|
|
13
14
|
import React from 'react';
|
|
15
|
+
import { object } from 'prop-types';
|
|
@@ -17,14 +17,15 @@
|
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
import React from 'react';
|
|
20
|
-
import { func, shape, string } from 'prop-types';
|
|
20
|
+
import { func, shape, string, object } from 'prop-types';
|
|
21
21
|
import { ScreenReaderContent } from '@instructure/ui-a11y-content';
|
|
22
22
|
import { NumberInput } from '@instructure/ui-number-input';
|
|
23
23
|
export default function DimensionInput(props) {
|
|
24
24
|
const {
|
|
25
25
|
dimensionState,
|
|
26
26
|
label,
|
|
27
|
-
messages
|
|
27
|
+
messages,
|
|
28
|
+
dimensionsRef
|
|
28
29
|
} = props;
|
|
29
30
|
const {
|
|
30
31
|
addOffset,
|
|
@@ -49,7 +50,12 @@ export default function DimensionInput(props) {
|
|
|
49
50
|
isRequired: true,
|
|
50
51
|
showArrows: false,
|
|
51
52
|
value: inputValue,
|
|
52
|
-
messages: messages
|
|
53
|
+
messages: messages,
|
|
54
|
+
inputRef: ref => {
|
|
55
|
+
if (dimensionsRef) {
|
|
56
|
+
dimensionsRef.current = ref;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
53
59
|
});
|
|
54
60
|
}
|
|
55
61
|
DimensionInput.propTypes = {
|
|
@@ -58,5 +64,6 @@ DimensionInput.propTypes = {
|
|
|
58
64
|
inputValue: string.isRequired,
|
|
59
65
|
setInputValue: func.isRequired
|
|
60
66
|
}),
|
|
67
|
+
dimensionsRef: object,
|
|
61
68
|
label: string.isRequired
|
|
62
69
|
};
|
|
@@ -25,6 +25,7 @@ declare namespace DimensionsInput {
|
|
|
25
25
|
export let minWidth: import("prop-types").Validator<number>;
|
|
26
26
|
export let minPercentage: import("prop-types").Validator<number>;
|
|
27
27
|
export { bool as hidePercentage };
|
|
28
|
+
export { object as dimensionsRef };
|
|
28
29
|
}
|
|
29
30
|
namespace defaultProps {
|
|
30
31
|
let hidePercentage: boolean;
|
|
@@ -34,3 +35,4 @@ export default DimensionsInput;
|
|
|
34
35
|
export { default as useDimensionsState } from "./useDimensionsState";
|
|
35
36
|
import React from 'react';
|
|
36
37
|
import { bool } from 'prop-types';
|
|
38
|
+
import { object } from 'prop-types';
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
import React from 'react';
|
|
20
|
-
import { bool, func, number, shape, string } from 'prop-types';
|
|
20
|
+
import { bool, func, number, shape, string, object } from 'prop-types';
|
|
21
21
|
import { ScreenReaderContent } from '@instructure/ui-a11y-content';
|
|
22
22
|
import { FormFieldGroup } from '@instructure/ui-form-field';
|
|
23
23
|
import { IconLockLine, IconWarningSolid } from '@instructure/ui-icons';
|
|
@@ -79,7 +79,8 @@ export default function DimensionsInput(props) {
|
|
|
79
79
|
minHeight,
|
|
80
80
|
minWidth,
|
|
81
81
|
minPercentage,
|
|
82
|
-
hidePercentage
|
|
82
|
+
hidePercentage,
|
|
83
|
+
dimensionsRef
|
|
83
84
|
} = props;
|
|
84
85
|
const handleDimensionTypeChange = e => {
|
|
85
86
|
dimensionsState.setUsePercentageUnits(e.target.value === 'percentage');
|
|
@@ -131,7 +132,8 @@ export default function DimensionsInput(props) {
|
|
|
131
132
|
}, /*#__PURE__*/React.createElement(DimensionInput, {
|
|
132
133
|
dimensionState: dimensionsState.percentageState,
|
|
133
134
|
label: formatMessage('Percentage'),
|
|
134
|
-
messages: [secondaryMessage]
|
|
135
|
+
messages: [secondaryMessage],
|
|
136
|
+
dimensionsRef: dimensionsRef
|
|
135
137
|
})), /*#__PURE__*/React.createElement(Flex.Item, {
|
|
136
138
|
padding: "x-small small"
|
|
137
139
|
}, "%")) : /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Flex.Item, {
|
|
@@ -140,7 +142,8 @@ export default function DimensionsInput(props) {
|
|
|
140
142
|
dimensionState: dimensionsState.widthState,
|
|
141
143
|
label: formatMessage('Width'),
|
|
142
144
|
minValue: minWidth,
|
|
143
|
-
messages: [secondaryMessage]
|
|
145
|
+
messages: [secondaryMessage],
|
|
146
|
+
dimensionsRef: dimensionsRef
|
|
144
147
|
})), /*#__PURE__*/React.createElement(Flex.Item, {
|
|
145
148
|
padding: "x-small small"
|
|
146
149
|
}, /*#__PURE__*/React.createElement(IconLockLine, null)), /*#__PURE__*/React.createElement(Flex.Item, {
|
|
@@ -176,7 +179,8 @@ DimensionsInput.propTypes = {
|
|
|
176
179
|
minHeight: number.isRequired,
|
|
177
180
|
minWidth: number.isRequired,
|
|
178
181
|
minPercentage: number.isRequired,
|
|
179
|
-
hidePercentage: bool
|
|
182
|
+
hidePercentage: bool,
|
|
183
|
+
dimensionsRef: object
|
|
180
184
|
};
|
|
181
185
|
DimensionsInput.defaultProps = {
|
|
182
186
|
hidePercentage: false
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export default ImageOptionsForm;
|
|
2
|
-
declare function ImageOptionsForm({ imageSize, displayAs, isDecorativeImage, altText, isLinked, dimensionsState, handleAltTextChange, handleIsDecorativeChange, handleDisplayAsChange, handleImageSizeChange, messagesForSize, hideDimensions, id, isIconMaker, forBlockEditorUse, }: {
|
|
2
|
+
declare function ImageOptionsForm({ imageSize, displayAs, isDecorativeImage, altText, isLinked, dimensionsState, handleAltTextChange, handleIsDecorativeChange, handleDisplayAsChange, handleImageSizeChange, messagesForSize, hideDimensions, id, isIconMaker, forBlockEditorUse, altHasError, altRef, dimensionsRef }: {
|
|
3
3
|
imageSize: any;
|
|
4
4
|
displayAs: any;
|
|
5
5
|
isDecorativeImage: any;
|
|
@@ -15,5 +15,8 @@ declare function ImageOptionsForm({ imageSize, displayAs, isDecorativeImage, alt
|
|
|
15
15
|
id?: string | undefined;
|
|
16
16
|
isIconMaker?: boolean | undefined;
|
|
17
17
|
forBlockEditorUse?: boolean | undefined;
|
|
18
|
+
altHasError?: boolean | undefined;
|
|
19
|
+
altRef?: null | undefined;
|
|
20
|
+
dimensionsRef?: null | undefined;
|
|
18
21
|
}): React.JSX.Element;
|
|
19
22
|
import React from 'react';
|
|
@@ -44,7 +44,10 @@ const ImageOptionsForm = ({
|
|
|
44
44
|
hideDimensions,
|
|
45
45
|
id = 'image-options-form',
|
|
46
46
|
isIconMaker = false,
|
|
47
|
-
forBlockEditorUse = false
|
|
47
|
+
forBlockEditorUse = false,
|
|
48
|
+
altHasError = false,
|
|
49
|
+
altRef = null,
|
|
50
|
+
dimensionsRef = null
|
|
48
51
|
}) => {
|
|
49
52
|
const TYPE = isIconMaker ? formatMessage('icon') : formatMessage('image');
|
|
50
53
|
const tooltipText = formatMessage('Used by screen readers to describe the content of an {TYPE}', {
|
|
@@ -76,6 +79,7 @@ const ImageOptionsForm = ({
|
|
|
76
79
|
}, /*#__PURE__*/React.createElement(Flex.Item, {
|
|
77
80
|
padding: "small"
|
|
78
81
|
}, /*#__PURE__*/React.createElement(TextArea, {
|
|
82
|
+
"data-testid": "alt-text-field",
|
|
79
83
|
disabled: isDecorativeImage,
|
|
80
84
|
"aria-describedby": "alt-text-label-tooltip",
|
|
81
85
|
height: "4rem",
|
|
@@ -85,7 +89,12 @@ const ImageOptionsForm = ({
|
|
|
85
89
|
TYPE
|
|
86
90
|
}),
|
|
87
91
|
resize: "vertical",
|
|
88
|
-
value: altText
|
|
92
|
+
value: altText,
|
|
93
|
+
messages: altHasError ? [{
|
|
94
|
+
text: formatMessage('Invalid description'),
|
|
95
|
+
type: 'error'
|
|
96
|
+
}] : [],
|
|
97
|
+
ref: altRef
|
|
89
98
|
})), /*#__PURE__*/React.createElement(Flex.Item, {
|
|
90
99
|
padding: "small"
|
|
91
100
|
}, /*#__PURE__*/React.createElement(Checkbox, {
|
|
@@ -133,7 +142,8 @@ const ImageOptionsForm = ({
|
|
|
133
142
|
disabled: displayAs !== 'embed',
|
|
134
143
|
minHeight: MIN_HEIGHT,
|
|
135
144
|
minWidth: MIN_WIDTH,
|
|
136
|
-
minPercentage: MIN_PERCENTAGE
|
|
145
|
+
minPercentage: MIN_PERCENTAGE,
|
|
146
|
+
dimensionsRef: dimensionsRef
|
|
137
147
|
})))));
|
|
138
148
|
};
|
|
139
149
|
export default ImageOptionsForm;
|
|
@@ -1,12 +1,18 @@
|
|
|
1
|
-
declare function UrlPanel({ fileUrl, setFileUrl }: {
|
|
1
|
+
declare function UrlPanel({ fileUrl, setFileUrl, urlHasError, urlRef }: {
|
|
2
2
|
fileUrl: any;
|
|
3
3
|
setFileUrl: any;
|
|
4
|
+
urlHasError: any;
|
|
5
|
+
urlRef: any;
|
|
4
6
|
}): React.JSX.Element;
|
|
5
7
|
declare namespace UrlPanel {
|
|
6
8
|
namespace propTypes {
|
|
7
|
-
let fileUrl: import("prop-types").Validator<string>;
|
|
8
|
-
let setFileUrl: import("prop-types").Validator<(...args: any[]) => any>;
|
|
9
|
+
export let fileUrl: import("prop-types").Validator<string>;
|
|
10
|
+
export let setFileUrl: import("prop-types").Validator<(...args: any[]) => any>;
|
|
11
|
+
export { bool as urlHasError };
|
|
12
|
+
export { object as urlRef };
|
|
9
13
|
}
|
|
10
14
|
}
|
|
11
15
|
export default UrlPanel;
|
|
12
16
|
import React from 'react';
|
|
17
|
+
import { bool } from 'prop-types';
|
|
18
|
+
import { object } from 'prop-types';
|
|
@@ -17,22 +17,31 @@
|
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
import React from 'react';
|
|
20
|
-
import { string, func } from 'prop-types';
|
|
20
|
+
import { string, func, bool, object } from 'prop-types';
|
|
21
21
|
import { TextInput } from '@instructure/ui-text-input';
|
|
22
22
|
import formatMessage from '../../../../format-message';
|
|
23
23
|
export default function UrlPanel({
|
|
24
24
|
fileUrl,
|
|
25
|
-
setFileUrl
|
|
25
|
+
setFileUrl,
|
|
26
|
+
urlHasError,
|
|
27
|
+
urlRef
|
|
26
28
|
}) {
|
|
27
29
|
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(TextInput, {
|
|
28
30
|
name: "file-url",
|
|
29
31
|
renderLabel: formatMessage('File URL'),
|
|
30
32
|
type: "url",
|
|
31
33
|
value: fileUrl,
|
|
32
|
-
onChange: (
|
|
34
|
+
onChange: (_e, val) => setFileUrl(val),
|
|
35
|
+
messages: urlHasError ? [{
|
|
36
|
+
text: formatMessage('Invalid URL'),
|
|
37
|
+
type: 'error'
|
|
38
|
+
}] : [],
|
|
39
|
+
ref: urlRef
|
|
33
40
|
}));
|
|
34
41
|
}
|
|
35
42
|
UrlPanel.propTypes = {
|
|
36
43
|
fileUrl: string.isRequired,
|
|
37
|
-
setFileUrl: func.isRequired
|
|
44
|
+
setFileUrl: func.isRequired,
|
|
45
|
+
urlHasError: bool,
|
|
46
|
+
urlRef: object
|
|
38
47
|
};
|
|
@@ -83,7 +83,7 @@ function intoRels(acc, x) {
|
|
|
83
83
|
});
|
|
84
84
|
return acc;
|
|
85
85
|
}
|
|
86
|
-
const PARSE_LINK_HEADER_MAXLEN =
|
|
86
|
+
const PARSE_LINK_HEADER_MAXLEN = 4000;
|
|
87
87
|
const PARSE_LINK_HEADER_THROW_ON_MAXLEN_EXCEEDED = process.env.PARSE_LINK_HEADER_THROW_ON_MAXLEN_EXCEEDED != null;
|
|
88
88
|
function checkHeader(linkHeader) {
|
|
89
89
|
if (!linkHeader) return false;
|
|
@@ -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');
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
|
|
19
19
|
import * as dom from './utils/dom';
|
|
20
20
|
import rules from './rules';
|
|
21
|
+
import { enhanceRules } from './utils/rule-enhancer';
|
|
21
22
|
export default function checkNode(node, done, config = {}, additionalRules = []) {
|
|
22
23
|
if (!node) {
|
|
23
24
|
return;
|
|
@@ -25,9 +26,9 @@ export default function checkNode(node, done, config = {}, additionalRules = [])
|
|
|
25
26
|
const errors = [];
|
|
26
27
|
const childNodeCheck = child => {
|
|
27
28
|
if (child.hasAttribute('data-ignore-a11y-check')) return;
|
|
28
|
-
|
|
29
|
+
// Enhance all rules with TinyMCE notification functionality
|
|
30
|
+
const composedRules = enhanceRules(rules.concat(additionalRules));
|
|
29
31
|
for (const rule of composedRules) {
|
|
30
|
-
// eslint-disable-next-line promise/catch-or-return
|
|
31
32
|
Promise.resolve(rule.test(child, config)).then(result => {
|
|
32
33
|
if (!result) {
|
|
33
34
|
errors.push({
|
|
@@ -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);
|
|
@@ -8,4 +8,10 @@ export function onlyContainsLink(elem: any): boolean;
|
|
|
8
8
|
export function splitStyleAttribute(styleString: any): any;
|
|
9
9
|
export function createStyleString(styleObj: any): string;
|
|
10
10
|
export function hasTextNode(elem: any): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Notifies TinyMCE that a change has been made
|
|
13
|
+
* This ensures that changes persist in the editor's state without requiring additional user actions
|
|
14
|
+
* @returns {void}
|
|
15
|
+
*/
|
|
16
|
+
export function notifyTinyMCE(): void;
|
|
11
17
|
import indicate from './indicate';
|