@instructure/canvas-rce 7.0.0 → 7.2.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 +33 -1
- package/__tests__/common/indicate.test.js +5 -6
- package/es/bridge/Bridge.js +2 -4
- package/es/canvasFileBrowser/FileBrowser.js +2 -4
- package/es/defaultTinymceConfig.d.ts +1 -1
- package/es/defaultTinymceConfig.js +149 -114
- package/es/enhance-user-content/enhance_user_content.js +7 -1
- package/es/enhance-user-content/instructure_helper.js +4 -0
- package/es/enhance-user-content/youtube_overlay.d.ts +1 -0
- package/es/enhance-user-content/youtube_overlay.js +87 -0
- package/es/format-message.d.js +1 -0
- package/es/format-message.js +5 -0
- package/es/index.d.ts +1 -1
- package/es/rce/RCE.d.ts +0 -1
- package/es/rce/RCE.js +5 -10
- package/es/rce/RCEVariants.d.ts +8 -3
- package/es/rce/RCEVariants.js +31 -5
- package/es/rce/RCEWrapper.d.ts +3 -3
- package/es/rce/RCEWrapper.js +66 -57
- package/es/rce/RCEWrapperProps.d.ts +1 -1
- package/es/rce/ShowOnFocusButton/index.js +4 -2
- package/es/rce/StatusBar.js +61 -15
- package/es/rce/alertHandler.js +6 -7
- package/es/rce/plugins/instructure-ui-icons/plugin.js +2 -2
- package/es/rce/plugins/instructure_equation/EquationEditorModal/index.js +6 -10
- package/es/rce/plugins/instructure_icon_maker/components/CreateIconMakerForm/ImageSection/Course.d.ts +5 -15
- package/es/rce/plugins/instructure_icon_maker/components/CreateIconMakerForm/ImageSection/Course.js +4 -10
- package/es/rce/plugins/instructure_image/ImageEmbedOptions.d.ts +7 -0
- package/es/rce/plugins/instructure_image/ImageEmbedOptions.js +45 -2
- package/es/rce/plugins/instructure_rce_external_tools/components/ExternalToolDialog/ExternalToolDialog.d.ts +1 -8
- package/es/rce/plugins/instructure_rce_external_tools/components/ExternalToolDialog/ExternalToolDialog.js +13 -33
- package/es/rce/plugins/instructure_rce_external_tools/components/ExternalToolDialog/ExternalToolDialogModal.d.ts +1 -2
- package/es/rce/plugins/instructure_rce_external_tools/components/ExternalToolDialog/ExternalToolDialogModal.js +2 -1
- package/es/rce/plugins/instructure_record/VideoOptionsTray/index.js +10 -7
- package/es/rce/plugins/shared/DimensionsInput/index.js +3 -3
- package/es/rce/plugins/shared/FixedContentTray.d.ts +7 -23
- package/es/rce/plugins/shared/FixedContentTray.js +7 -16
- package/es/rce/plugins/shared/ImageCropper/constants.d.ts +1 -1
- package/es/rce/plugins/shared/ImageCropper/constants.js +1 -1
- package/es/rce/plugins/shared/ImageCropper/controls/CustomNumberInput.js +1 -1
- package/es/rce/plugins/shared/PreviewIcon.js +1 -1
- package/es/rce/plugins/shared/ai_tools/aiicons.d.ts +3 -3
- package/es/rce/plugins/shared/ai_tools/aiicons.js +11 -11
- package/es/rce/plugins/tinymce-a11y-checker/components/checker.js +7 -1
- package/es/rce/plugins/tinymce-a11y-checker/utils/indicate.d.ts +1 -1
- package/es/rce/plugins/tinymce-a11y-checker/utils/indicate.js +1 -1
- package/es/rce/style.js +19 -17
- package/es/translations/locales/ar.js +29 -5
- package/es/translations/locales/ca.js +32 -8
- package/es/translations/locales/cy.js +29 -5
- package/es/translations/locales/da-x-k12.js +29 -5
- package/es/translations/locales/da.js +29 -5
- package/es/translations/locales/de.js +29 -5
- package/es/translations/locales/en-AU-x-unimelb.js +29 -5
- package/es/translations/locales/en-GB-x-ukhe.js +29 -5
- package/es/translations/locales/en.js +17 -5
- package/es/translations/locales/en_AU.js +29 -5
- package/es/translations/locales/en_CA.js +29 -5
- package/es/translations/locales/en_CY.js +29 -5
- package/es/translations/locales/en_GB.js +29 -5
- package/es/translations/locales/es.js +29 -5
- package/es/translations/locales/es_ES.js +29 -5
- package/es/translations/locales/fa_IR.js +0 -3
- package/es/translations/locales/fi.js +29 -5
- package/es/translations/locales/fr.js +29 -5
- package/es/translations/locales/fr_CA.js +30 -6
- package/es/translations/locales/ga.js +46 -22
- package/es/translations/locales/hi.js +29 -5
- package/es/translations/locales/ht.js +29 -5
- package/es/translations/locales/hu.js +0 -6
- package/es/translations/locales/id.js +29 -5
- package/es/translations/locales/is.js +23 -5
- package/es/translations/locales/it.js +29 -5
- package/es/translations/locales/ja.js +29 -5
- package/es/translations/locales/mi.js +29 -5
- package/es/translations/locales/ms.js +29 -5
- package/es/translations/locales/nb-x-k12.js +29 -5
- package/es/translations/locales/nb.js +29 -5
- package/es/translations/locales/nl.js +29 -5
- package/es/translations/locales/nn.js +0 -6
- package/es/translations/locales/pl.js +29 -5
- package/es/translations/locales/pt.js +29 -5
- package/es/translations/locales/pt_BR.js +29 -5
- package/es/translations/locales/ru.js +29 -5
- package/es/translations/locales/sl.js +29 -5
- package/es/translations/locales/sv-x-k12.js +29 -5
- package/es/translations/locales/sv.js +29 -5
- package/es/translations/locales/th.js +29 -5
- package/es/translations/locales/uk_UA.js +0 -6
- package/es/translations/locales/vi.js +29 -5
- package/es/translations/locales/zh-Hans.js +29 -5
- package/es/translations/locales/zh-Hant.js +29 -5
- package/es/translations/locales/zh.js +29 -5
- package/es/translations/locales/zh_HK.js +29 -5
- package/es/util/contextHelper.d.ts +7 -0
- package/{testcafe/axe.test.js → es/util/contextHelper.js} +10 -21
- package/es/util/loadingPlaceholder.js +11 -11
- package/eslint.config.js +3 -25
- package/jest/jest-setup.js +27 -2
- package/jest.config.js +5 -1
- package/package.json +61 -84
- package/testcafe/RCEWrapper.test.js +0 -319
- package/testcafe/StatusBar.test.js +0 -108
- package/testcafe/enhanceUserContent.html +0 -58
- package/testcafe/enhanceUserContent.test.js +0 -44
- package/testcafe/entry.jsx +0 -77
- package/testcafe/testcafe.html +0 -14
- package/webpack.testcafe.config.js +0 -61
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import _pt from "prop-types";
|
|
2
|
+
import React, { useRef, useEffect, useState, useCallback } from 'react';
|
|
3
|
+
import { createRoot } from 'react-dom/client';
|
|
4
|
+
import { Text } from '@instructure/ui-text';
|
|
5
|
+
import { View } from '@instructure/ui-view';
|
|
6
|
+
import { Flex } from '@instructure/ui-flex';
|
|
7
|
+
import { CondensedButton } from '@instructure/ui-buttons';
|
|
8
|
+
import { IconPlayLine } from '@instructure/ui-icons';
|
|
9
|
+
import formatMessage from '../format-message';
|
|
10
|
+
const YoutubeEmbedOverlay = ({
|
|
11
|
+
iframeElement,
|
|
12
|
+
height,
|
|
13
|
+
width
|
|
14
|
+
}) => {
|
|
15
|
+
const [showOverlay, setShowOverlay] = useState(true);
|
|
16
|
+
const containerRef = useRef(null);
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
if (containerRef.current && iframeElement) {
|
|
19
|
+
if (width) {
|
|
20
|
+
containerRef.current.style.width = `${width}px`;
|
|
21
|
+
}
|
|
22
|
+
if (height) {
|
|
23
|
+
containerRef.current.style.height = `${height}px`;
|
|
24
|
+
}
|
|
25
|
+
containerRef.current.appendChild(iframeElement);
|
|
26
|
+
}
|
|
27
|
+
}, [height, iframeElement, width]);
|
|
28
|
+
const handleCloseOverlay = useCallback(() => {
|
|
29
|
+
setShowOverlay(false);
|
|
30
|
+
}, [setShowOverlay]);
|
|
31
|
+
return /*#__PURE__*/React.createElement(View, {
|
|
32
|
+
as: "div",
|
|
33
|
+
position: "relative",
|
|
34
|
+
display: "inline-block"
|
|
35
|
+
}, showOverlay && /*#__PURE__*/React.createElement(View, {
|
|
36
|
+
as: "div",
|
|
37
|
+
position: "absolute",
|
|
38
|
+
width: "100%",
|
|
39
|
+
height: "100%",
|
|
40
|
+
background: "primary",
|
|
41
|
+
themeOverride: {
|
|
42
|
+
backgroundPrimary: 'rgba(10, 71, 91, 0.65)'
|
|
43
|
+
},
|
|
44
|
+
overflowY: "hidden"
|
|
45
|
+
}, /*#__PURE__*/React.createElement(Flex, {
|
|
46
|
+
justifyItems: "center",
|
|
47
|
+
height: "100%"
|
|
48
|
+
}, /*#__PURE__*/React.createElement(Flex.Item, {
|
|
49
|
+
shouldShrink: true,
|
|
50
|
+
shouldGrow: true,
|
|
51
|
+
textAlign: "center"
|
|
52
|
+
}, /*#__PURE__*/React.createElement(Flex, {
|
|
53
|
+
justifyItems: "center",
|
|
54
|
+
margin: "0 0 small"
|
|
55
|
+
}, /*#__PURE__*/React.createElement(Flex.Item, null, /*#__PURE__*/React.createElement(Text, {
|
|
56
|
+
color: "secondary-inverse",
|
|
57
|
+
weight: "bold"
|
|
58
|
+
}, formatMessage('This video may display YouTube ads.')))), /*#__PURE__*/React.createElement(Flex, {
|
|
59
|
+
justifyItems: "center",
|
|
60
|
+
margin: "0 0 medium"
|
|
61
|
+
}, /*#__PURE__*/React.createElement(Flex.Item, null, /*#__PURE__*/React.createElement(CondensedButton, {
|
|
62
|
+
"data-test-id": "youtube-migration-close-overlay",
|
|
63
|
+
color: "primary-inverse",
|
|
64
|
+
onClick: handleCloseOverlay
|
|
65
|
+
}, /*#__PURE__*/React.createElement(IconPlayLine, null), "\xA0", formatMessage('Continue to YouTube content'))))))), /*#__PURE__*/React.createElement("div", {
|
|
66
|
+
ref: containerRef
|
|
67
|
+
}));
|
|
68
|
+
};
|
|
69
|
+
YoutubeEmbedOverlay.propTypes = {
|
|
70
|
+
height: _pt.number.isRequired,
|
|
71
|
+
width: _pt.number.isRequired
|
|
72
|
+
};
|
|
73
|
+
export const createOverlay = iframes => {
|
|
74
|
+
iframes.forEach(iframe => {
|
|
75
|
+
const iframeElement = iframe;
|
|
76
|
+
const height = iframeElement.offsetHeight;
|
|
77
|
+
const width = iframeElement.offsetWidth;
|
|
78
|
+
const container = document.createElement('div');
|
|
79
|
+
container.setAttribute('data-test-id', 'youtube-migration-container');
|
|
80
|
+
iframe.replaceWith(container);
|
|
81
|
+
createRoot(container).render(/*#__PURE__*/React.createElement(YoutubeEmbedOverlay, {
|
|
82
|
+
iframeElement: iframeElement,
|
|
83
|
+
height: height,
|
|
84
|
+
width: width
|
|
85
|
+
}));
|
|
86
|
+
});
|
|
87
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/es/format-message.js
CHANGED
|
@@ -17,7 +17,12 @@
|
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
import formatMessage from 'format-message';
|
|
20
|
+
import generateId from 'format-message-generate-id/underscored_crc32';
|
|
20
21
|
const ns = formatMessage.namespace();
|
|
22
|
+
ns.setup({
|
|
23
|
+
generateId,
|
|
24
|
+
missingTranslation: 'ignore'
|
|
25
|
+
});
|
|
21
26
|
ns.addLocale = translations => {
|
|
22
27
|
ns.setup({
|
|
23
28
|
translations: {
|
package/es/index.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ export declare const defaultConfiguration: {
|
|
|
16
16
|
menubar: undefined;
|
|
17
17
|
menu: undefined;
|
|
18
18
|
toolbar: undefined;
|
|
19
|
+
color_map: string[];
|
|
19
20
|
plugins: undefined;
|
|
20
21
|
branding: boolean;
|
|
21
22
|
browser_spellcheck: boolean;
|
|
@@ -25,7 +26,6 @@ export declare const defaultConfiguration: {
|
|
|
25
26
|
language_load: boolean;
|
|
26
27
|
language_url: string;
|
|
27
28
|
toolbar_mode: string;
|
|
28
|
-
toolbar_ticky: boolean;
|
|
29
29
|
mobile: {
|
|
30
30
|
theme: string;
|
|
31
31
|
};
|
package/es/rce/RCE.d.ts
CHANGED
package/es/rce/RCE.js
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
import React, { forwardRef, useState } from 'react';
|
|
19
|
+
import React, { forwardRef, useEffect, useState } from 'react';
|
|
20
20
|
import formatMessage from '../format-message';
|
|
21
21
|
import RCEWrapper from './RCEWrapper';
|
|
22
22
|
import { editorLanguage } from './editorLanguage';
|
|
@@ -69,15 +69,10 @@ const RCE = /*#__PURE__*/forwardRef(function RCE(props, rceRef) {
|
|
|
69
69
|
locale: normalizeLocale(props.language)
|
|
70
70
|
});
|
|
71
71
|
});
|
|
72
|
-
const [
|
|
72
|
+
const [isTranslationLoading, setIsTranslationLoading] = useState(true);
|
|
73
|
+
useEffect(() => {
|
|
73
74
|
const locale = normalizeLocale(props.language);
|
|
74
|
-
|
|
75
|
-
setTranslations(true);
|
|
76
|
-
}).catch(err => {
|
|
77
|
-
console.error('Failed loading the language file for', locale, '\n Cause:', err);
|
|
78
|
-
setTranslations(false);
|
|
79
|
-
});
|
|
80
|
-
return p;
|
|
75
|
+
getTranslations(locale).catch(err => console.error('Failed loading the language file for', locale, '\n Cause:', err)).finally(() => setIsTranslationLoading(false));
|
|
81
76
|
});
|
|
82
77
|
|
|
83
78
|
// some properties are only used on initialization
|
|
@@ -111,7 +106,7 @@ const RCE = /*#__PURE__*/forwardRef(function RCE(props, rceRef) {
|
|
|
111
106
|
wrapInitCb(mirroredAttrs || {}, iProps.editorOptions);
|
|
112
107
|
return iProps;
|
|
113
108
|
});
|
|
114
|
-
if (
|
|
109
|
+
if (isTranslationLoading) {
|
|
115
110
|
return /*#__PURE__*/React.createElement(React.Fragment, null, formatMessage('Loading...'));
|
|
116
111
|
} else {
|
|
117
112
|
return /*#__PURE__*/React.createElement(RCEWrapper, Object.assign({
|
package/es/rce/RCEVariants.d.ts
CHANGED
|
@@ -7,11 +7,16 @@ interface ToolbarGroupSetting {
|
|
|
7
7
|
name: string;
|
|
8
8
|
items: string[];
|
|
9
9
|
}
|
|
10
|
-
type StatusBarFeature = 'ai_tools' | 'keyboard_shortcuts' | 'a11y_checker' | 'word_count' | 'html_view' | 'fullscreen' | 'resize_handle';
|
|
11
|
-
export declare const RCEVariantValues: readonly ["full", "lite", "text-only", "text-block"];
|
|
10
|
+
type StatusBarFeature = 'ai_tools' | 'keyboard_shortcuts' | 'a11y_checker' | 'word_count' | 'html_view' | 'fullscreen' | 'resize_handle' | 'a11y_resize_handlers';
|
|
11
|
+
export declare const RCEVariantValues: readonly ["full", "lite", "text-only", "text-block", "block-content-editor"];
|
|
12
12
|
export type RCEVariant = (typeof RCEVariantValues)[number];
|
|
13
|
+
export type StatusBarOptions = {
|
|
14
|
+
aiTextTools?: boolean;
|
|
15
|
+
isDesktop?: boolean;
|
|
16
|
+
a11yResizers?: boolean;
|
|
17
|
+
};
|
|
13
18
|
export declare function getMenubarForVariant(variant: RCEVariant): MenuBarSpec;
|
|
14
19
|
export declare function getMenuForVariant(variant: RCEVariant): MenusSpec;
|
|
15
20
|
export declare function getToolbarForVariant(variant: RCEVariant, ltiToolFavorites?: string[]): ToolbarGroupSetting[];
|
|
16
|
-
export declare function getStatusBarFeaturesForVariant(variant: RCEVariant,
|
|
21
|
+
export declare function getStatusBarFeaturesForVariant(variant: RCEVariant, options?: StatusBarOptions): StatusBarFeature[];
|
|
17
22
|
export {};
|
package/es/rce/RCEVariants.js
CHANGED
|
@@ -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
|
*
|
|
@@ -21,7 +21,7 @@ import formatMessage from '../format-message';
|
|
|
21
21
|
|
|
22
22
|
// copied from node_modules/tinymce/tinymce.d.ts:1187
|
|
23
23
|
|
|
24
|
-
export const RCEVariantValues = ['full', 'lite', 'text-only', 'text-block'];
|
|
24
|
+
export const RCEVariantValues = ['full', 'lite', 'text-only', 'text-block', 'block-content-editor'];
|
|
25
25
|
export function getMenubarForVariant(variant) {
|
|
26
26
|
if (variant === 'full') {
|
|
27
27
|
return 'edit view insert format tools table';
|
|
@@ -101,6 +101,24 @@ export function getToolbarForVariant(variant, ltiToolFavorites = []) {
|
|
|
101
101
|
items: ['removeformat', 'instructure_equation']
|
|
102
102
|
}];
|
|
103
103
|
}
|
|
104
|
+
if (variant === 'block-content-editor') {
|
|
105
|
+
return [{
|
|
106
|
+
name: formatMessage('Styles'),
|
|
107
|
+
items: ['fontsizeselect', 'formatselect']
|
|
108
|
+
}, {
|
|
109
|
+
name: formatMessage('Formatting'),
|
|
110
|
+
items: ['bold', 'italic', 'underline', 'instructure_color', 'inst_subscript', 'inst_superscript']
|
|
111
|
+
}, {
|
|
112
|
+
name: formatMessage('Content'),
|
|
113
|
+
items: ['instructure_links', 'instructure_documents']
|
|
114
|
+
}, {
|
|
115
|
+
name: formatMessage('Alignment and Lists'),
|
|
116
|
+
items: ['align', 'bullist', 'inst_indent', 'inst_outdent']
|
|
117
|
+
}, {
|
|
118
|
+
name: formatMessage('Miscellaneous'),
|
|
119
|
+
items: ['removeformat', 'instructure_equation']
|
|
120
|
+
}];
|
|
121
|
+
}
|
|
104
122
|
return [{
|
|
105
123
|
name: formatMessage('Styles'),
|
|
106
124
|
items: ['fontsizeselect', 'formatselect']
|
|
@@ -124,13 +142,21 @@ export function getToolbarForVariant(variant, ltiToolFavorites = []) {
|
|
|
124
142
|
const DESKTOP_FEATURES = ['keyboard_shortcuts', 'a11y_checker', 'word_count'];
|
|
125
143
|
const MOBILE_FEATURES = ['a11y_checker', 'word_count'];
|
|
126
144
|
const EXTENDED_FEATURES = ['html_view', 'fullscreen', 'resize_handle'];
|
|
127
|
-
|
|
145
|
+
const A11Y_RESIZERS = ['a11y_resize_handlers'];
|
|
146
|
+
export function getStatusBarFeaturesForVariant(variant, options = {
|
|
147
|
+
aiTextTools: false,
|
|
148
|
+
isDesktop: true,
|
|
149
|
+
a11yResizers: false
|
|
150
|
+
}) {
|
|
128
151
|
if (variant === 'text-block') {
|
|
129
152
|
return [];
|
|
130
153
|
}
|
|
131
|
-
|
|
154
|
+
if (variant === 'block-content-editor') {
|
|
155
|
+
return ['keyboard_shortcuts', 'word_count'];
|
|
156
|
+
}
|
|
157
|
+
const platformFeatures = options.isDesktop ? DESKTOP_FEATURES : MOBILE_FEATURES;
|
|
132
158
|
if (variant === 'lite' || variant === 'text-only') {
|
|
133
159
|
return platformFeatures;
|
|
134
160
|
}
|
|
135
|
-
return [...platformFeatures, ...EXTENDED_FEATURES, ...(aiTextTools ? ['ai_tools'] : [])];
|
|
161
|
+
return [...platformFeatures, ...EXTENDED_FEATURES, ...(options.a11yResizers ? A11Y_RESIZERS : []), ...(options.aiTextTools ? ['ai_tools'] : [])];
|
|
136
162
|
}
|
package/es/rce/RCEWrapper.d.ts
CHANGED
|
@@ -13,7 +13,6 @@ interface RCEWrapperProps {
|
|
|
13
13
|
autosave?: {
|
|
14
14
|
enabled?: boolean;
|
|
15
15
|
maxAge?: number;
|
|
16
|
-
interval?: number;
|
|
17
16
|
};
|
|
18
17
|
canvasOrigin: string;
|
|
19
18
|
defaultContent?: string;
|
|
@@ -75,6 +74,7 @@ declare class RCEWrapper extends React.Component<RCEWrapperProps, RCEWrapperStat
|
|
|
75
74
|
_showOnFocusButton?: HTMLElement;
|
|
76
75
|
_statusBarId: string;
|
|
77
76
|
_textareaEl?: HTMLTextAreaElement;
|
|
77
|
+
_effectiveContainingContext: RCETrayProps['containingContext'];
|
|
78
78
|
AIToolsTray?: ReactNode;
|
|
79
79
|
editor: TinyMCEEditor | null;
|
|
80
80
|
initialContent?: string;
|
|
@@ -191,7 +191,7 @@ declare class RCEWrapper extends React.Component<RCEWrapperProps, RCEWrapperStat
|
|
|
191
191
|
maxMruTools: import("prop-types").Requireable<number>;
|
|
192
192
|
}>>;
|
|
193
193
|
ai_text_tools: import("prop-types").Requireable<boolean>;
|
|
194
|
-
variant: import("prop-types").Requireable<"full" | "lite" | "text-only" | "text-block">;
|
|
194
|
+
variant: import("prop-types").Requireable<"full" | "lite" | "text-only" | "text-block" | "block-content-editor">;
|
|
195
195
|
};
|
|
196
196
|
static defaultProps: {
|
|
197
197
|
trayProps: null;
|
|
@@ -377,13 +377,13 @@ declare class RCEWrapper extends React.Component<RCEWrapperProps, RCEWrapperStat
|
|
|
377
377
|
auto_focus: boolean;
|
|
378
378
|
body_class: string;
|
|
379
379
|
directionality: string;
|
|
380
|
+
color_map: string[];
|
|
380
381
|
branding: boolean;
|
|
381
382
|
browser_spellcheck: boolean;
|
|
382
383
|
convert_urls: boolean;
|
|
383
384
|
font_formats: string;
|
|
384
385
|
language_load: boolean;
|
|
385
386
|
language_url: string;
|
|
386
|
-
toolbar_ticky: boolean;
|
|
387
387
|
mobile: {
|
|
388
388
|
theme: string;
|
|
389
389
|
};
|
package/es/rce/RCEWrapper.js
CHANGED
|
@@ -65,6 +65,7 @@ import { getMenubarForVariant, getMenuForVariant, getToolbarForVariant, getStatu
|
|
|
65
65
|
import { focusFirstMenuButton, focusToolbar, isElementWithinTable, mergeMenu, mergeMenuItems, mergePlugins, mergeToolbar, parsePluginsToExclude, patchAutosavedContent } from './RCEWrapper.utils';
|
|
66
66
|
import { externalToolsForToolbar } from './plugins/instructure_rce_external_tools/util/externalToolsForToolbar';
|
|
67
67
|
import { initScreenreaderOnFormat } from './screenreaderOnFormat';
|
|
68
|
+
import { normalizeContainingContext } from '../util/contextHelper';
|
|
68
69
|
const RestoreAutoSaveModal = /*#__PURE__*/React.lazy(() => import('./RestoreAutoSaveModal'));
|
|
69
70
|
const RceHtmlEditor = /*#__PURE__*/React.lazy(() => import('./RceHtmlEditor'));
|
|
70
71
|
const ASYNC_FOCUS_TIMEOUT = 250;
|
|
@@ -142,6 +143,7 @@ class RCEWrapper extends React.Component {
|
|
|
142
143
|
this._showOnFocusButton = void 0;
|
|
143
144
|
this._statusBarId = void 0;
|
|
144
145
|
this._textareaEl = void 0;
|
|
146
|
+
this._effectiveContainingContext = void 0;
|
|
145
147
|
this.AIToolsTray = void 0;
|
|
146
148
|
this.editor = void 0;
|
|
147
149
|
this.initialContent = void 0;
|
|
@@ -209,6 +211,7 @@ class RCEWrapper extends React.Component {
|
|
|
209
211
|
this.toggleFullscreen = () => {
|
|
210
212
|
this.handleClickFullscreen();
|
|
211
213
|
};
|
|
214
|
+
// @ts-expect-error
|
|
212
215
|
this._onFullscreenChange = event => {
|
|
213
216
|
if (document[FS_ELEMENT]) {
|
|
214
217
|
// @ts-expect-error
|
|
@@ -239,6 +242,7 @@ class RCEWrapper extends React.Component {
|
|
|
239
242
|
this.handleFocusRCE = () => {
|
|
240
243
|
this.handleFocus();
|
|
241
244
|
};
|
|
245
|
+
// @ts-expect-error
|
|
242
246
|
this.handleBlurRCE = event => {
|
|
243
247
|
if (event.relatedTarget === null) {
|
|
244
248
|
// focus might be moving to tinymce
|
|
@@ -373,6 +377,12 @@ class RCEWrapper extends React.Component {
|
|
|
373
377
|
});
|
|
374
378
|
}
|
|
375
379
|
};
|
|
380
|
+
/**
|
|
381
|
+
* Fix keyboard navigation in the expanded toolbar
|
|
382
|
+
*
|
|
383
|
+
* NOTE: This is a workaround for https://github.com/tinymce/tinymce/issues/8618
|
|
384
|
+
* and should be removed once that issue is resolved and the tinymce dependency is updated to include it.
|
|
385
|
+
*/
|
|
376
386
|
this.fixToolbarKeyboardNavigation = () => {
|
|
377
387
|
// The keyboard navigation config in tinymce for the expanded toolbar is incorrectly configured,
|
|
378
388
|
// and stops at [data-alloy-tabstop] elements.
|
|
@@ -382,6 +392,19 @@ class RCEWrapper extends React.Component {
|
|
|
382
392
|
// in https://github.com/tinymce/tinymce/blob/develop/modules/alloy/src/main/ts/ephox/alloy/ui/schema/SplitSlidingToolbarSchema.ts
|
|
383
393
|
this._elementRef.current?.querySelectorAll('.tox-toolbar-overlord button[data-alloy-tabstop]').forEach(it => it.removeAttribute('data-alloy-tabstop'));
|
|
384
394
|
};
|
|
395
|
+
/**
|
|
396
|
+
* Sets up selection saving and restoration logic.
|
|
397
|
+
*
|
|
398
|
+
* There are certain actions a user can take when the RCE is not focused that clear the selection inside the
|
|
399
|
+
* editor, such as invoking the Find feature of the browser. If the user then tries to insert content without
|
|
400
|
+
* going back to the editor, the content would be inserted at the top of the RCE, instead of where their cursor
|
|
401
|
+
* was.
|
|
402
|
+
*
|
|
403
|
+
* This method adds logic that saves and restores the selection to work around the issue.
|
|
404
|
+
*
|
|
405
|
+
* @private
|
|
406
|
+
*/
|
|
407
|
+
// @ts-expect-error
|
|
385
408
|
this._setupSelectionSaving = editor => {
|
|
386
409
|
// @ts-expect-error
|
|
387
410
|
let savedSelection = null;
|
|
@@ -440,6 +463,7 @@ class RCEWrapper extends React.Component {
|
|
|
440
463
|
});
|
|
441
464
|
};
|
|
442
465
|
this.announcing = 0;
|
|
466
|
+
/* ********** autosave support *************** */
|
|
443
467
|
this.initAutoSave = editor => {
|
|
444
468
|
var _this$props$userCache;
|
|
445
469
|
this.storage = new EncryptedStorage((_this$props$userCache = this.props.userCacheKey) !== null && _this$props$userCache !== void 0 ? _this$props$userCache : '');
|
|
@@ -474,6 +498,7 @@ class RCEWrapper extends React.Component {
|
|
|
474
498
|
}
|
|
475
499
|
}
|
|
476
500
|
};
|
|
501
|
+
// remove any autosaved value that's too old
|
|
477
502
|
this.cleanupAutoSave = (deleteAll = false) => {
|
|
478
503
|
if (this.storage) {
|
|
479
504
|
const expiry = deleteAll ? Date.now() : Date.now() - (this.props.autosave?.maxAge || 0);
|
|
@@ -489,6 +514,7 @@ class RCEWrapper extends React.Component {
|
|
|
489
514
|
}
|
|
490
515
|
}
|
|
491
516
|
};
|
|
517
|
+
// @ts-expect-error
|
|
492
518
|
this.restoreAutoSave = ans => {
|
|
493
519
|
this.setState({
|
|
494
520
|
confirmAutoSave: false
|
|
@@ -503,6 +529,7 @@ class RCEWrapper extends React.Component {
|
|
|
503
529
|
// let the content be restored
|
|
504
530
|
debounce(this.checkAccessibility, 1000)();
|
|
505
531
|
};
|
|
532
|
+
// @ts-expect-error
|
|
506
533
|
this.doAutoSave = (e, retry = false) => {
|
|
507
534
|
if (this.storage) {
|
|
508
535
|
const editor = this.mceInstance();
|
|
@@ -527,6 +554,7 @@ class RCEWrapper extends React.Component {
|
|
|
527
554
|
}
|
|
528
555
|
}
|
|
529
556
|
};
|
|
557
|
+
/* *********** end autosave support *************** */
|
|
530
558
|
this.onWordCountUpdate = e => {
|
|
531
559
|
if (!this.editor) return;
|
|
532
560
|
const shouldIgnore = countShouldIgnore(this.editor, 'body', 'words');
|
|
@@ -539,6 +567,7 @@ class RCEWrapper extends React.Component {
|
|
|
539
567
|
} else return null;
|
|
540
568
|
});
|
|
541
569
|
};
|
|
570
|
+
// @ts-expect-error
|
|
542
571
|
this.onNodeChange = e => {
|
|
543
572
|
// This is basically copied out of the tinymce silver theme code for the status bar
|
|
544
573
|
const path = e.parents.filter(p => p.nodeName !== 'BR' && !p.getAttribute('data-mce-bogus') && p.getAttribute('data-mce-type') !== 'bookmark')
|
|
@@ -574,7 +603,9 @@ class RCEWrapper extends React.Component {
|
|
|
574
603
|
height: newHeight
|
|
575
604
|
});
|
|
576
605
|
// play nice and send the same event that the silver theme would send
|
|
577
|
-
editor.fire('ResizeEditor'
|
|
606
|
+
editor.fire('ResizeEditor', {
|
|
607
|
+
deltaY: coordinates.deltaY
|
|
608
|
+
});
|
|
578
609
|
}
|
|
579
610
|
};
|
|
580
611
|
this.onA11yChecker = triggerElementId => {
|
|
@@ -715,6 +746,9 @@ class RCEWrapper extends React.Component {
|
|
|
715
746
|
};
|
|
716
747
|
});
|
|
717
748
|
};
|
|
749
|
+
/**
|
|
750
|
+
* Used for reseting the value during tests
|
|
751
|
+
*/
|
|
718
752
|
this.resetAlertId = () => {
|
|
719
753
|
if (this.state.messages.length > 0) {
|
|
720
754
|
throw new Error('There are messages currently, you cannot reset when they are non-zero');
|
|
@@ -806,6 +840,7 @@ class RCEWrapper extends React.Component {
|
|
|
806
840
|
this._handleFullscreenResize();
|
|
807
841
|
});
|
|
808
842
|
this.AIToolsTray = undefined;
|
|
843
|
+
this._effectiveContainingContext = normalizeContainingContext(this.props.trayProps?.containingContext);
|
|
809
844
|
}
|
|
810
845
|
|
|
811
846
|
// when the RCE is put into fullscreen we need to move the div
|
|
@@ -1095,8 +1130,7 @@ class RCEWrapper extends React.Component {
|
|
|
1095
1130
|
if (this.editor) {
|
|
1096
1131
|
return this.editor;
|
|
1097
1132
|
}
|
|
1098
|
-
|
|
1099
|
-
return editors.filter(ed => ed.id === this.props.textareaId)[0];
|
|
1133
|
+
return this.props.tinymce.get(this.props.textareaId);
|
|
1100
1134
|
}
|
|
1101
1135
|
|
|
1102
1136
|
// @ts-expect-error
|
|
@@ -1171,9 +1205,6 @@ class RCEWrapper extends React.Component {
|
|
|
1171
1205
|
document[FS_EXIT]();
|
|
1172
1206
|
}
|
|
1173
1207
|
}
|
|
1174
|
-
|
|
1175
|
-
// @ts-expect-error
|
|
1176
|
-
|
|
1177
1208
|
_getStatusBarHeight() {
|
|
1178
1209
|
// the height prop is the height of the editor and does not include
|
|
1179
1210
|
// the status bar. we'll need this later.
|
|
@@ -1319,9 +1350,6 @@ class RCEWrapper extends React.Component {
|
|
|
1319
1350
|
}, ASYNC_FOCUS_TIMEOUT);
|
|
1320
1351
|
}
|
|
1321
1352
|
}
|
|
1322
|
-
|
|
1323
|
-
// @ts-expect-error
|
|
1324
|
-
|
|
1325
1353
|
// @ts-expect-error
|
|
1326
1354
|
call(methodName, ...args) {
|
|
1327
1355
|
// since exists? has a ? and cant be a regular function just return true
|
|
@@ -1332,28 +1360,6 @@ class RCEWrapper extends React.Component {
|
|
|
1332
1360
|
// @ts-expect-error
|
|
1333
1361
|
return this[methodName](...args);
|
|
1334
1362
|
}
|
|
1335
|
-
|
|
1336
|
-
/**
|
|
1337
|
-
* Fix keyboard navigation in the expanded toolbar
|
|
1338
|
-
*
|
|
1339
|
-
* NOTE: This is a workaround for https://github.com/tinymce/tinymce/issues/8618
|
|
1340
|
-
* and should be removed once that issue is resolved and the tinymce dependency is updated to include it.
|
|
1341
|
-
*/
|
|
1342
|
-
|
|
1343
|
-
/**
|
|
1344
|
-
* Sets up selection saving and restoration logic.
|
|
1345
|
-
*
|
|
1346
|
-
* There are certain actions a user can take when the RCE is not focused that clear the selection inside the
|
|
1347
|
-
* editor, such as invoking the Find feature of the browser. If the user then tries to insert content without
|
|
1348
|
-
* going back to the editor, the content would be inserted at the top of the RCE, instead of where their cursor
|
|
1349
|
-
* was.
|
|
1350
|
-
*
|
|
1351
|
-
* This method adds logic that saves and restores the selection to work around the issue.
|
|
1352
|
-
*
|
|
1353
|
-
* @private
|
|
1354
|
-
*/
|
|
1355
|
-
// @ts-expect-error
|
|
1356
|
-
|
|
1357
1363
|
announceContextToolbars(editor) {
|
|
1358
1364
|
editor.on('NodeChange', () => {
|
|
1359
1365
|
const node = editor.selection.getNode();
|
|
@@ -1392,14 +1398,21 @@ class RCEWrapper extends React.Component {
|
|
|
1392
1398
|
this.announcing = 0;
|
|
1393
1399
|
}
|
|
1394
1400
|
});
|
|
1401
|
+
editor.on('ResizeEditor', ({
|
|
1402
|
+
deltaY
|
|
1403
|
+
}) => {
|
|
1404
|
+
if (!deltaY) return;
|
|
1405
|
+
if (deltaY < 0) {
|
|
1406
|
+
this.setState({
|
|
1407
|
+
announcement: formatMessage('The height of Rich Content Area is decreased.')
|
|
1408
|
+
});
|
|
1409
|
+
} else {
|
|
1410
|
+
this.setState({
|
|
1411
|
+
announcement: formatMessage('The height of Rich Content Area is increased.')
|
|
1412
|
+
});
|
|
1413
|
+
}
|
|
1414
|
+
});
|
|
1395
1415
|
}
|
|
1396
|
-
|
|
1397
|
-
/* ********** autosave support *************** */
|
|
1398
|
-
|
|
1399
|
-
// remove any autosaved value that's too old
|
|
1400
|
-
|
|
1401
|
-
// @ts-expect-error
|
|
1402
|
-
|
|
1403
1416
|
getAutoSaved(key) {
|
|
1404
1417
|
let autosaved = null;
|
|
1405
1418
|
try {
|
|
@@ -1424,17 +1437,9 @@ class RCEWrapper extends React.Component {
|
|
|
1424
1437
|
return this.props.autosave?.enabled && editorVisible && document.querySelectorAll('.rce-wrapper').length === 1 && storageAvailable();
|
|
1425
1438
|
}
|
|
1426
1439
|
get autoSaveKey() {
|
|
1427
|
-
|
|
1428
|
-
const userId = this.props.trayProps?.containingContext.userId;
|
|
1440
|
+
const userId = this._effectiveContainingContext?.userId || '-';
|
|
1429
1441
|
return `rceautosave:${userId}${window.location.href}:${this.props.textareaId}`;
|
|
1430
1442
|
}
|
|
1431
|
-
|
|
1432
|
-
// @ts-expect-error
|
|
1433
|
-
|
|
1434
|
-
/* *********** end autosave support *************** */
|
|
1435
|
-
|
|
1436
|
-
// @ts-expect-error
|
|
1437
|
-
|
|
1438
1443
|
componentWillUnmount() {
|
|
1439
1444
|
if (this.state.shouldShowEditor) {
|
|
1440
1445
|
window.clearTimeout(this.blurTimer);
|
|
@@ -1655,11 +1660,6 @@ class RCEWrapper extends React.Component {
|
|
|
1655
1660
|
this.mceInstance().hide();
|
|
1656
1661
|
}
|
|
1657
1662
|
}
|
|
1658
|
-
|
|
1659
|
-
/**
|
|
1660
|
-
* Used for reseting the value during tests
|
|
1661
|
-
*/
|
|
1662
|
-
|
|
1663
1663
|
renderHtmlEditor() {
|
|
1664
1664
|
// the div keeps the editor from collapsing while the code editor is downloaded
|
|
1665
1665
|
return /*#__PURE__*/React.createElement(Suspense, {
|
|
@@ -1708,7 +1708,12 @@ class RCEWrapper extends React.Component {
|
|
|
1708
1708
|
}
|
|
1709
1709
|
});
|
|
1710
1710
|
}
|
|
1711
|
-
const
|
|
1711
|
+
const statusBarOptions = {
|
|
1712
|
+
aiTextTools: this.props.ai_text_tools,
|
|
1713
|
+
isDesktop: tinymce.Env.deviceType.isDesktop(),
|
|
1714
|
+
a11yResizers: !!this.props.features?.rce_a11y_resize
|
|
1715
|
+
};
|
|
1716
|
+
const statusBarFeatures = getStatusBarFeaturesForVariant(this.variant, statusBarOptions);
|
|
1712
1717
|
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("style", null, this.style.css), /*#__PURE__*/React.createElement(StoreProvider, {
|
|
1713
1718
|
jwt: this.props.trayProps?.jwt,
|
|
1714
1719
|
refreshToken: this.props.trayProps?.refreshToken,
|
|
@@ -1796,7 +1801,7 @@ class RCEWrapper extends React.Component {
|
|
|
1796
1801
|
disabledPlugins: this.pluginsToExclude,
|
|
1797
1802
|
features: statusBarFeatures,
|
|
1798
1803
|
onAI: this.handleAIClick
|
|
1799
|
-
}), this.
|
|
1804
|
+
}), this._effectiveContainingContext && /*#__PURE__*/React.createElement(CanvasContentTray, Object.assign({
|
|
1800
1805
|
mountNode: instuiPopupMountNodeFn,
|
|
1801
1806
|
key: this.id,
|
|
1802
1807
|
canvasOrigin: this.getCanvasUrl(),
|
|
@@ -1805,7 +1810,9 @@ class RCEWrapper extends React.Component {
|
|
|
1805
1810
|
onTrayClosing: this.handleContentTrayClosing,
|
|
1806
1811
|
use_rce_icon_maker: this.props.use_rce_icon_maker
|
|
1807
1812
|
}, trayProps, {
|
|
1813
|
+
containingContext: this._effectiveContainingContext
|
|
1808
1814
|
// @ts-expect-error
|
|
1815
|
+
,
|
|
1809
1816
|
storeProps: storeProps
|
|
1810
1817
|
})), /*#__PURE__*/React.createElement(KeyboardShortcutModal, {
|
|
1811
1818
|
onExited: this.KBShortcutModalExited,
|
|
@@ -1834,7 +1841,10 @@ class RCEWrapper extends React.Component {
|
|
|
1834
1841
|
open: this.state.confirmAutoSave,
|
|
1835
1842
|
onNo: () => this.restoreAutoSave(false),
|
|
1836
1843
|
onYes: () => this.restoreAutoSave(true)
|
|
1837
|
-
})) : null,
|
|
1844
|
+
})) : null, this.state.announcement &&
|
|
1845
|
+
/*#__PURE__*/
|
|
1846
|
+
// @ts-expect-error
|
|
1847
|
+
React.createElement(Alert, {
|
|
1838
1848
|
screenReaderOnly: true,
|
|
1839
1849
|
liveRegion: this.props.liveRegion
|
|
1840
1850
|
}, this.state.announcement));
|
|
@@ -1845,8 +1855,7 @@ RCEWrapper.propTypes = {
|
|
|
1845
1855
|
ai_text_tools: _pt.bool,
|
|
1846
1856
|
autosave: _pt.shape({
|
|
1847
1857
|
enabled: _pt.bool,
|
|
1848
|
-
maxAge: _pt.number
|
|
1849
|
-
interval: _pt.number
|
|
1858
|
+
maxAge: _pt.number
|
|
1850
1859
|
}),
|
|
1851
1860
|
canvasOrigin: _pt.string,
|
|
1852
1861
|
defaultContent: _pt.string,
|
|
@@ -170,6 +170,6 @@ export declare const rceWrapperPropTypes: {
|
|
|
170
170
|
maxMruTools: PropTypes.Requireable<number>;
|
|
171
171
|
}>>;
|
|
172
172
|
ai_text_tools: PropTypes.Requireable<boolean>;
|
|
173
|
-
variant: PropTypes.Requireable<"full" | "lite" | "text-only" | "text-block">;
|
|
173
|
+
variant: PropTypes.Requireable<"full" | "lite" | "text-only" | "text-block" | "block-content-editor">;
|
|
174
174
|
};
|
|
175
175
|
export type RCEWrapperProps = PropTypes.InferProps<typeof rceWrapperPropTypes>;
|
|
@@ -26,8 +26,10 @@ import React, { Component } from 'react';
|
|
|
26
26
|
import { func, node, oneOfType, string } from 'prop-types';
|
|
27
27
|
import { IconButton } from '@instructure/ui-buttons';
|
|
28
28
|
const hideStyle = {
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
opacity: 0,
|
|
30
|
+
width: 0,
|
|
31
|
+
height: 0,
|
|
32
|
+
overflow: 'hidden'
|
|
31
33
|
};
|
|
32
34
|
export default class ShowOnFocusButton extends Component {
|
|
33
35
|
constructor(...args) {
|