@instructure/canvas-rce 7.0.0 → 7.3.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 +60 -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/doc_previews.js +1 -14
- 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 +2 -1
- package/es/index.js +2 -1
- package/es/rce/AlertMessageArea.d.ts +2 -2
- package/es/rce/AlertMessageArea.js +4 -6
- package/es/rce/RCE.d.ts +0 -1
- package/es/rce/RCE.js +5 -10
- package/es/rce/RCEGlobals.d.ts +2 -0
- package/es/rce/RCEGlobals.js +1 -0
- package/es/rce/RCEVariants.d.ts +8 -3
- package/es/rce/RCEVariants.js +31 -5
- package/es/rce/RCEWrapper.d.ts +16 -14
- package/es/rce/RCEWrapper.js +260 -244
- 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_keyboard_shortcuts_header/clickCallback.d.ts +2 -0
- package/es/rce/plugins/instructure_keyboard_shortcuts_header/clickCallback.js +45 -0
- package/es/rce/plugins/instructure_keyboard_shortcuts_header/plugin.d.ts +1 -0
- package/es/rce/plugins/instructure_keyboard_shortcuts_header/plugin.js +43 -0
- package/es/rce/plugins/instructure_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_rce_external_tools/components/ExternalToolSelectionDialog/ExternalToolSelectionDialog.d.ts +1 -1
- package/es/rce/plugins/instructure_rce_external_tools/components/ExternalToolSelectionDialog/ExternalToolSelectionDialog.js +25 -25
- package/es/rce/plugins/instructure_record/AudioOptionsTray/TrayController.d.ts +1 -1
- package/es/rce/plugins/instructure_record/AudioOptionsTray/TrayController.js +2 -1
- package/es/rce/plugins/instructure_record/VideoOptionsTray/TrayController.d.ts +1 -1
- package/es/rce/plugins/instructure_record/VideoOptionsTray/TrayController.js +2 -1
- package/es/rce/plugins/instructure_record/VideoOptionsTray/index.js +10 -7
- package/es/rce/plugins/instructure_record/mediaTranslations.js +1 -1
- package/es/rce/plugins/instructure_studio_media_options/plugin.js +109 -14
- package/es/rce/plugins/instructure_studio_media_options/studioToolbarIcons.d.ts +5 -0
- package/es/rce/plugins/instructure_studio_media_options/studioToolbarIcons.js +23 -0
- package/es/rce/plugins/instructure_wordcount_header/plugin.d.ts +1 -0
- package/es/rce/plugins/instructure_wordcount_header/plugin.js +75 -0
- package/es/rce/plugins/shared/ContentSelection.d.ts +1 -2
- package/es/rce/plugins/shared/ContentSelection.js +1 -18
- package/es/rce/plugins/shared/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/StudioLtiSupportUtils.d.ts +9 -1
- package/es/rce/plugins/shared/StudioLtiSupportUtils.js +94 -1
- package/es/rce/plugins/shared/Upload/ComputerPanel.js +1 -1
- package/es/rce/plugins/shared/Upload/UploadFileModal.js +37 -4
- package/es/rce/plugins/shared/Upload/VideoUrlPanel.d.ts +15 -0
- package/es/rce/plugins/shared/Upload/VideoUrlPanel.js +51 -0
- package/es/rce/plugins/shared/Upload/videoValidationUtils.d.ts +7 -0
- package/es/rce/plugins/shared/Upload/videoValidationUtils.js +58 -0
- package/es/rce/plugins/shared/ai_tools/aiicons.d.ts +3 -3
- package/es/rce/plugins/shared/ai_tools/aiicons.js +11 -11
- package/es/rce/plugins/shared/iframeUtils.d.ts +1 -0
- package/es/rce/plugins/shared/iframeUtils.js +37 -0
- 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/rce/tinyRCE.js +2 -0
- package/es/sidebar/actions/upload.d.ts +1 -0
- package/es/sidebar/actions/upload.js +56 -0
- package/es/sidebar/containers/sidebarHandlers.d.ts +1 -0
- package/es/sidebar/containers/sidebarHandlers.js +2 -1
- package/es/translations/locales/ar.js +44 -11
- package/es/translations/locales/ca.js +47 -14
- package/es/translations/locales/cy.js +44 -11
- package/es/translations/locales/da-x-k12.js +44 -11
- package/es/translations/locales/da.js +44 -11
- package/es/translations/locales/de.js +44 -11
- package/es/translations/locales/el.js +6 -0
- package/es/translations/locales/en-AU-x-unimelb.js +44 -11
- package/es/translations/locales/en-GB-x-ukhe.js +44 -11
- package/es/translations/locales/en.js +47 -11
- package/es/translations/locales/en_AU.js +44 -11
- package/es/translations/locales/en_CA.js +44 -11
- package/es/translations/locales/en_CY.js +44 -11
- package/es/translations/locales/en_GB.js +44 -11
- package/es/translations/locales/es.js +44 -11
- package/es/translations/locales/es_ES.js +44 -11
- package/es/translations/locales/fa_IR.js +6 -6
- package/es/translations/locales/fi.js +44 -11
- package/es/translations/locales/fr.js +44 -11
- package/es/translations/locales/fr_CA.js +49 -16
- package/es/translations/locales/ga.js +61 -28
- package/es/translations/locales/he.js +6 -0
- package/es/translations/locales/hi.js +44 -11
- package/es/translations/locales/ht.js +44 -11
- package/es/translations/locales/hu.js +6 -12
- package/es/translations/locales/hy.js +6 -0
- package/es/translations/locales/id.js +44 -11
- package/es/translations/locales/is.js +44 -11
- package/es/translations/locales/it.js +44 -11
- package/es/translations/locales/ja.js +44 -11
- package/es/translations/locales/ko.js +6 -0
- package/es/translations/locales/mi.js +44 -11
- package/es/translations/locales/ms.js +44 -11
- package/es/translations/locales/nb-x-k12.js +44 -11
- package/es/translations/locales/nb.js +44 -11
- package/es/translations/locales/nl.js +44 -11
- package/es/translations/locales/nn.js +6 -12
- package/es/translations/locales/pl.js +44 -11
- package/es/translations/locales/pt.js +44 -11
- package/es/translations/locales/pt_BR.js +44 -11
- package/es/translations/locales/ru.js +44 -11
- package/es/translations/locales/sl.js +44 -11
- package/es/translations/locales/sv-x-k12.js +44 -11
- package/es/translations/locales/sv.js +44 -11
- package/es/translations/locales/th.js +44 -11
- package/es/translations/locales/tr.js +6 -3
- package/es/translations/locales/uk_UA.js +6 -9
- package/es/translations/locales/vi.js +44 -11
- package/es/translations/locales/zh-Hans.js +44 -11
- package/es/translations/locales/zh-Hant.js +44 -11
- package/es/translations/locales/zh.js +44 -11
- package/es/translations/locales/zh_HK.js +44 -11
- 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
package/es/rce/RCEWrapper.js
CHANGED
|
@@ -17,62 +17,63 @@ import _pt from "prop-types";
|
|
|
17
17
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
import React, { Suspense } from
|
|
21
|
-
import { Editor } from
|
|
22
|
-
import tinymce from
|
|
23
|
-
import _ from
|
|
24
|
-
import { StoreProvider } from
|
|
25
|
-
import { IconKeyboardShortcutsLine } from
|
|
26
|
-
import { Alert } from
|
|
27
|
-
import { Spinner } from
|
|
28
|
-
import { View } from
|
|
29
|
-
import { debounce } from
|
|
30
|
-
import { uid } from
|
|
31
|
-
import { FocusRegionManager } from
|
|
32
|
-
import getCookie from
|
|
33
|
-
import formatMessage from
|
|
34
|
-
import * as contentInsertion from
|
|
35
|
-
import indicatorRegion from
|
|
36
|
-
import { editorLanguage } from
|
|
37
|
-
import normalizeLocale from
|
|
38
|
-
import { sanitizePlugins } from
|
|
39
|
-
import RCEGlobals from
|
|
40
|
-
import defaultTinymceConfig from
|
|
41
|
-
import { FS_CHANGEEVENT, FS_ELEMENT, FS_ENABLED, FS_EXIT, FS_REQUEST, instuiPopupMountNodeFn } from
|
|
42
|
-
import indicate from
|
|
43
|
-
import bridge from
|
|
44
|
-
import CanvasContentTray from
|
|
45
|
-
import StatusBar, { PRETTY_HTML_EDITOR_VIEW, RAW_HTML_EDITOR_VIEW, WYSIWYG_VIEW } from
|
|
46
|
-
import { VIEW_CHANGE } from
|
|
47
|
-
import ShowOnFocusButton from
|
|
48
|
-
import KeyboardShortcutModal from
|
|
49
|
-
import AlertMessageArea from
|
|
50
|
-
import alertHandler from
|
|
51
|
-
import { isFileLink, isImageEmbed } from
|
|
52
|
-
import { countShouldIgnore } from
|
|
53
|
-
import launchWordcountModal from
|
|
54
|
-
import { determineOSDependentKey } from
|
|
55
|
-
import skinCSS from
|
|
56
|
-
import contentCSS from
|
|
57
|
-
import { rceWrapperPropTypes } from
|
|
58
|
-
import { insertPlaceholder, placeholderInfoFor, removePlaceholder } from
|
|
59
|
-
import { transformRceContentForEditing } from
|
|
20
|
+
import React, { Suspense } from "react";
|
|
21
|
+
import { Editor } from "@tinymce/tinymce-react";
|
|
22
|
+
import tinymce from "tinymce";
|
|
23
|
+
import _ from "lodash";
|
|
24
|
+
import { StoreProvider } from "./plugins/shared/StoreContext";
|
|
25
|
+
import { IconKeyboardShortcutsLine } from "@instructure/ui-icons";
|
|
26
|
+
import { Alert } from "@instructure/ui-alerts";
|
|
27
|
+
import { Spinner } from "@instructure/ui-spinner";
|
|
28
|
+
import { View } from "@instructure/ui-view";
|
|
29
|
+
import { debounce } from "@instructure/debounce";
|
|
30
|
+
import { uid } from "@instructure/uid";
|
|
31
|
+
import { FocusRegionManager } from "@instructure/ui-a11y-utils";
|
|
32
|
+
import getCookie from "../common/getCookie";
|
|
33
|
+
import formatMessage from "../format-message";
|
|
34
|
+
import * as contentInsertion from "./contentInsertion";
|
|
35
|
+
import indicatorRegion from "./indicatorRegion";
|
|
36
|
+
import { editorLanguage } from "./editorLanguage";
|
|
37
|
+
import normalizeLocale from "./normalizeLocale";
|
|
38
|
+
import { sanitizePlugins } from "./sanitizePlugins";
|
|
39
|
+
import RCEGlobals from "./RCEGlobals";
|
|
40
|
+
import defaultTinymceConfig from "../defaultTinymceConfig";
|
|
41
|
+
import { FS_CHANGEEVENT, FS_ELEMENT, FS_ENABLED, FS_EXIT, FS_REQUEST, instuiPopupMountNodeFn } from "../util/fullscreenHelpers";
|
|
42
|
+
import indicate from "../common/indicate";
|
|
43
|
+
import bridge from "../bridge";
|
|
44
|
+
import CanvasContentTray from "./plugins/shared/CanvasContentTray";
|
|
45
|
+
import StatusBar, { PRETTY_HTML_EDITOR_VIEW, RAW_HTML_EDITOR_VIEW, WYSIWYG_VIEW } from "./StatusBar";
|
|
46
|
+
import { VIEW_CHANGE } from "./customEvents";
|
|
47
|
+
import ShowOnFocusButton from "./ShowOnFocusButton";
|
|
48
|
+
import KeyboardShortcutModal from "./KeyboardShortcutModal";
|
|
49
|
+
import AlertMessageArea from "./AlertMessageArea";
|
|
50
|
+
import alertHandler from "./alertHandler";
|
|
51
|
+
import { isFileLink, isImageEmbed } from "./plugins/shared/ContentSelection";
|
|
52
|
+
import { countShouldIgnore } from "./plugins/instructure_wordcount/utils/countContent";
|
|
53
|
+
import launchWordcountModal from "./plugins/instructure_wordcount/clickCallback";
|
|
54
|
+
import { determineOSDependentKey } from "./userOS";
|
|
55
|
+
import skinCSS from "./tinymce.oxide.skin.min.css";
|
|
56
|
+
import contentCSS from "./tinymce.oxide.content.min.css";
|
|
57
|
+
import { rceWrapperPropTypes } from "./RCEWrapperProps";
|
|
58
|
+
import { insertPlaceholder, placeholderInfoFor, removePlaceholder } from "../util/loadingPlaceholder";
|
|
59
|
+
import { transformRceContentForEditing } from "./transformContent";
|
|
60
60
|
// @ts-expect-error
|
|
61
|
-
import { IconMoreSolid } from
|
|
62
|
-
import EncryptedStorage from
|
|
63
|
-
import buildStyle from
|
|
64
|
-
import { getMenubarForVariant, getMenuForVariant, getToolbarForVariant, getStatusBarFeaturesForVariant } from
|
|
65
|
-
import { focusFirstMenuButton, focusToolbar, isElementWithinTable, mergeMenu, mergeMenuItems, mergePlugins, mergeToolbar, parsePluginsToExclude, patchAutosavedContent } from
|
|
66
|
-
import { externalToolsForToolbar } from
|
|
67
|
-
import { initScreenreaderOnFormat } from
|
|
68
|
-
|
|
69
|
-
const
|
|
61
|
+
import { IconMoreSolid } from "@instructure/ui-icons/es/svg";
|
|
62
|
+
import EncryptedStorage from "../util/encrypted-storage";
|
|
63
|
+
import buildStyle from "./style";
|
|
64
|
+
import { getMenubarForVariant, getMenuForVariant, getToolbarForVariant, getStatusBarFeaturesForVariant } from "./RCEVariants";
|
|
65
|
+
import { focusFirstMenuButton, focusToolbar, isElementWithinTable, mergeMenu, mergeMenuItems, mergePlugins, mergeToolbar, parsePluginsToExclude, patchAutosavedContent } from "./RCEWrapper.utils";
|
|
66
|
+
import { externalToolsForToolbar } from "./plugins/instructure_rce_external_tools/util/externalToolsForToolbar";
|
|
67
|
+
import { initScreenreaderOnFormat } from "./screenreaderOnFormat";
|
|
68
|
+
import { normalizeContainingContext } from "../util/contextHelper";
|
|
69
|
+
const RestoreAutoSaveModal = /*#__PURE__*/React.lazy(() => import("./RestoreAutoSaveModal"));
|
|
70
|
+
const RceHtmlEditor = /*#__PURE__*/React.lazy(() => import("./RceHtmlEditor"));
|
|
70
71
|
const ASYNC_FOCUS_TIMEOUT = 250;
|
|
71
|
-
const DEFAULT_RCE_HEIGHT =
|
|
72
|
+
const DEFAULT_RCE_HEIGHT = "400px";
|
|
72
73
|
function addKebabIcon(editor) {
|
|
73
74
|
// This has to be done here instead of of in plugins/instructure-ui-icons/plugin.ts
|
|
74
75
|
// presumably because the toolbar gets created before that plugin is loaded?
|
|
75
|
-
editor.ui.registry.addIcon(
|
|
76
|
+
editor.ui.registry.addIcon("more-drawer", IconMoreSolid.src);
|
|
76
77
|
}
|
|
77
78
|
|
|
78
79
|
// Get oxide the default skin injected into the DOM before the overrides loaded by themeable
|
|
@@ -80,8 +81,8 @@ let inserted = false;
|
|
|
80
81
|
function injectTinySkin() {
|
|
81
82
|
if (inserted) return;
|
|
82
83
|
inserted = true;
|
|
83
|
-
const style = document.createElement(
|
|
84
|
-
style.setAttribute(
|
|
84
|
+
const style = document.createElement("style");
|
|
85
|
+
style.setAttribute("data-skin", "tiny oxide skin");
|
|
85
86
|
style.appendChild(document.createTextNode(skinCSS));
|
|
86
87
|
// there's CSS from discussions that turns the instui Selectors bold
|
|
87
88
|
// and in classic quizzes that also mucks with padding
|
|
@@ -89,9 +90,9 @@ function injectTinySkin() {
|
|
|
89
90
|
#discussion-edit-view .rce-wrapper input[readonly] {font-weight: normal;}
|
|
90
91
|
#quiz_edit_wrapper .rce-wrapper input[readonly] {font-weight: normal; padding-left: .75rem;}
|
|
91
92
|
`));
|
|
92
|
-
const beforeMe = document.head.querySelector(
|
|
93
|
+
const beforeMe = document.head.querySelector("style[data-glamor]") ||
|
|
93
94
|
// find instui's themeable stylesheet
|
|
94
|
-
document.head.querySelector(
|
|
95
|
+
document.head.querySelector("style") ||
|
|
95
96
|
// find any stylesheet
|
|
96
97
|
document.head.firstElementChild;
|
|
97
98
|
document.head.insertBefore(style, beforeMe);
|
|
@@ -101,10 +102,10 @@ const editorWrappers = new WeakMap();
|
|
|
101
102
|
// determines if localStorage is available for our use.
|
|
102
103
|
// see https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API/Using_the_Web_Storage_API
|
|
103
104
|
export function storageAvailable() {
|
|
104
|
-
let storage;
|
|
105
|
+
let storage = null;
|
|
105
106
|
try {
|
|
106
107
|
storage = window.localStorage;
|
|
107
|
-
const x =
|
|
108
|
+
const x = "__storage_test__";
|
|
108
109
|
storage.setItem(x, x);
|
|
109
110
|
storage.removeItem(x);
|
|
110
111
|
return true;
|
|
@@ -116,15 +117,15 @@ export function storageAvailable() {
|
|
|
116
117
|
e.code === 1014 ||
|
|
117
118
|
// test name field too, because code might not be present
|
|
118
119
|
// everything except Firefox
|
|
119
|
-
e.name ===
|
|
120
|
+
e.name === "QuotaExceededError" ||
|
|
120
121
|
// Firefox
|
|
121
|
-
e.name ===
|
|
122
|
+
e.name === "NS_ERROR_DOM_QUOTA_REACHED") &&
|
|
122
123
|
// acknowledge QuotaExceededError only if there's something already stored
|
|
123
124
|
storage && storage.length !== 0;
|
|
124
125
|
}
|
|
125
126
|
}
|
|
126
127
|
function renderLoading() {
|
|
127
|
-
return formatMessage(
|
|
128
|
+
return formatMessage("Loading");
|
|
128
129
|
}
|
|
129
130
|
let alertIdValue = 0;
|
|
130
131
|
class RCEWrapper extends React.Component {
|
|
@@ -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;
|
|
@@ -197,7 +199,7 @@ class RCEWrapper extends React.Component {
|
|
|
197
199
|
this.setState(newState);
|
|
198
200
|
this.checkAccessibility();
|
|
199
201
|
if (newView === PRETTY_HTML_EDITOR_VIEW || newView === RAW_HTML_EDITOR_VIEW) {
|
|
200
|
-
this.storage?.setItem?.(
|
|
202
|
+
this.storage?.setItem?.("rce.htmleditor", newView);
|
|
201
203
|
}
|
|
202
204
|
|
|
203
205
|
// Emit view change event
|
|
@@ -209,20 +211,23 @@ 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
|
|
215
218
|
this.resizeObserver.observe(document[FS_ELEMENT]);
|
|
216
|
-
window.visualViewport?.addEventListener(
|
|
219
|
+
window.visualViewport?.addEventListener("resize", this._handleFullscreenResize);
|
|
217
220
|
this._handleFullscreenResize();
|
|
218
221
|
// @ts-expect-error
|
|
219
|
-
this._focusRegion = FocusRegionManager.activateRegion(
|
|
222
|
+
this._focusRegion = FocusRegionManager.activateRegion(
|
|
223
|
+
// @ts-expect-error
|
|
224
|
+
document[FS_ELEMENT], {
|
|
220
225
|
shouldContainFocus: true
|
|
221
226
|
});
|
|
222
227
|
} else {
|
|
223
228
|
event.target.removeEventListener(FS_CHANGEEVENT, this._onFullscreenChange);
|
|
224
229
|
this.resizeObserver.unobserve(event.target);
|
|
225
|
-
window.visualViewport?.removeEventListener(
|
|
230
|
+
window.visualViewport?.removeEventListener("resize", this._handleFullscreenResize);
|
|
226
231
|
this._setHeight(this.state.fullscreenState.prevHeight);
|
|
227
232
|
if (this._focusRegion) {
|
|
228
233
|
FocusRegionManager.blurRegion(event.target, this._focusRegion.id);
|
|
@@ -239,6 +244,7 @@ class RCEWrapper extends React.Component {
|
|
|
239
244
|
this.handleFocusRCE = () => {
|
|
240
245
|
this.handleFocus();
|
|
241
246
|
};
|
|
247
|
+
// @ts-expect-error
|
|
242
248
|
this.handleBlurRCE = event => {
|
|
243
249
|
if (event.relatedTarget === null) {
|
|
244
250
|
// focus might be moving to tinymce
|
|
@@ -254,35 +260,35 @@ class RCEWrapper extends React.Component {
|
|
|
254
260
|
// what we've got for now.
|
|
255
261
|
const ifr = this.iframe;
|
|
256
262
|
if (ifr?.parentElement) {
|
|
257
|
-
ifr.parentElement.classList.add(
|
|
263
|
+
ifr.parentElement.classList.add("active");
|
|
258
264
|
}
|
|
259
265
|
this.handleFocus();
|
|
260
266
|
};
|
|
261
267
|
this.handleBlurEditor = event => {
|
|
262
268
|
const ifr = this.iframe;
|
|
263
269
|
if (ifr?.parentElement) {
|
|
264
|
-
ifr.parentElement.classList.remove(
|
|
270
|
+
ifr.parentElement.classList.remove("active");
|
|
265
271
|
}
|
|
266
272
|
this.handleBlur(event);
|
|
267
273
|
};
|
|
268
274
|
this.handleKey = event => {
|
|
269
|
-
if (event.code ===
|
|
275
|
+
if (event.code === "F9" && event.altKey) {
|
|
270
276
|
event.preventDefault();
|
|
271
277
|
event.stopPropagation();
|
|
272
278
|
// @ts-expect-error
|
|
273
279
|
focusFirstMenuButton(this._elementRef.current);
|
|
274
|
-
} else if (event.code ===
|
|
280
|
+
} else if (event.code === "F10" && event.altKey) {
|
|
275
281
|
event.preventDefault();
|
|
276
282
|
event.stopPropagation();
|
|
277
283
|
// @ts-expect-error
|
|
278
284
|
focusToolbar(this._elementRef.current);
|
|
279
|
-
} else if (event.code ===
|
|
285
|
+
} else if (event.code === "F8" && event.altKey) {
|
|
280
286
|
event.preventDefault();
|
|
281
287
|
event.stopPropagation();
|
|
282
288
|
this.openKBShortcutModal();
|
|
283
|
-
} else if (event.code ===
|
|
289
|
+
} else if (event.code === "Escape") {
|
|
284
290
|
bridge.hideTrays();
|
|
285
|
-
} else if ([
|
|
291
|
+
} else if (["n", "N", "d", "D"].indexOf(event.key) !== -1) {
|
|
286
292
|
// Prevent key events from bubbling up on touch screen device
|
|
287
293
|
event.stopPropagation();
|
|
288
294
|
}
|
|
@@ -311,32 +317,32 @@ class RCEWrapper extends React.Component {
|
|
|
311
317
|
// @ts-expect-error
|
|
312
318
|
textarea.value = this.getCode();
|
|
313
319
|
textarea.style.height = this.state.height;
|
|
314
|
-
textarea.removeAttribute(
|
|
315
|
-
if (document.body.classList.contains(
|
|
320
|
+
textarea.removeAttribute("aria-hidden");
|
|
321
|
+
if (document.body.classList.contains("Underline-All-Links__enabled")) {
|
|
316
322
|
if (this.iframe?.contentDocument) {
|
|
317
|
-
this.iframe.contentDocument.body.classList.add(
|
|
323
|
+
this.iframe.contentDocument.body.classList.add("Underline-All-Links__enabled");
|
|
318
324
|
}
|
|
319
325
|
}
|
|
320
|
-
editor.on(
|
|
326
|
+
editor.on("wordCountUpdate", this.onWordCountUpdate);
|
|
321
327
|
// add an aria-label to the application div that wraps RCE
|
|
322
328
|
// and change role from "application" to "document" to ensure
|
|
323
329
|
// the editor gets properly picked up by screen readers
|
|
324
330
|
const tinyapp = document.querySelector('.tox-tinymce[role="application"]');
|
|
325
331
|
if (tinyapp) {
|
|
326
|
-
tinyapp.setAttribute(
|
|
327
|
-
tinyapp.setAttribute(
|
|
328
|
-
tinyapp.setAttribute(
|
|
332
|
+
tinyapp.setAttribute("aria-label", formatMessage("Rich Content Editor"));
|
|
333
|
+
tinyapp.setAttribute("role", "document");
|
|
334
|
+
tinyapp.setAttribute("tabIndex", "-1");
|
|
329
335
|
}
|
|
330
336
|
|
|
331
337
|
// Probably should do this in tinymce.scss, but we only want it in new rce
|
|
332
|
-
textarea.style.resize =
|
|
333
|
-
editor.on(
|
|
334
|
-
editor.on(
|
|
338
|
+
textarea.style.resize = "none";
|
|
339
|
+
editor.on("keydown", this.handleKey);
|
|
340
|
+
editor.on("FullscreenStateChanged", this._onFullscreenChange);
|
|
335
341
|
// This propagates click events on the editor out of the iframe to the parent
|
|
336
342
|
// document. We need this so that click events get captured properly by instui
|
|
337
343
|
// focus-trapping components, so they properly ignore trapping focus on click.
|
|
338
|
-
editor.on(
|
|
339
|
-
editor.on(
|
|
344
|
+
editor.on("click", () => window.document.body.click(), true);
|
|
345
|
+
editor.on("Cut Change input Undo Redo", debounce(this.handleInputChange, 1000));
|
|
340
346
|
initScreenreaderOnFormat(editor);
|
|
341
347
|
this.announceContextToolbars(editor);
|
|
342
348
|
if (this.isAutoSaving) {
|
|
@@ -348,12 +354,12 @@ class RCEWrapper extends React.Component {
|
|
|
348
354
|
|
|
349
355
|
// readonly should have been handled via the init property passed
|
|
350
356
|
// to <Editor>, but it's not.
|
|
351
|
-
editor.mode.set(this.props.readOnly ?
|
|
357
|
+
editor.mode.set(this.props.readOnly ? "readonly" : "design");
|
|
352
358
|
|
|
353
359
|
// Not using iframe_aria_text because compatibility issues.
|
|
354
360
|
// Not using iframe_attrs because library overwriting.
|
|
355
361
|
if (this.iframe) {
|
|
356
|
-
this.iframe.setAttribute(
|
|
362
|
+
this.iframe.setAttribute("title", formatMessage("Rich Text Area. Press {OSKey}+F8 for Rich Content Editor shortcuts.", {
|
|
357
363
|
OSKey: determineOSDependentKey()
|
|
358
364
|
}));
|
|
359
365
|
}
|
|
@@ -366,13 +372,19 @@ class RCEWrapper extends React.Component {
|
|
|
366
372
|
|
|
367
373
|
// cleans up highlight artifacts from findreplace plugin
|
|
368
374
|
if (this.getRequiredFeatureStatuses().rce_find_replace) {
|
|
369
|
-
editor.on(
|
|
370
|
-
if (editor?.dom?.doc?.getElementsByClassName?.(
|
|
375
|
+
editor.on("undo redo", _e => {
|
|
376
|
+
if (editor?.dom?.doc?.getElementsByClassName?.("mce-match-marker")?.length > 0) {
|
|
371
377
|
editor.plugins?.searchreplace?.done();
|
|
372
378
|
}
|
|
373
379
|
});
|
|
374
380
|
}
|
|
375
381
|
};
|
|
382
|
+
/**
|
|
383
|
+
* Fix keyboard navigation in the expanded toolbar
|
|
384
|
+
*
|
|
385
|
+
* NOTE: This is a workaround for https://github.com/tinymce/tinymce/issues/8618
|
|
386
|
+
* and should be removed once that issue is resolved and the tinymce dependency is updated to include it.
|
|
387
|
+
*/
|
|
376
388
|
this.fixToolbarKeyboardNavigation = () => {
|
|
377
389
|
// The keyboard navigation config in tinymce for the expanded toolbar is incorrectly configured,
|
|
378
390
|
// and stops at [data-alloy-tabstop] elements.
|
|
@@ -380,8 +392,21 @@ class RCEWrapper extends React.Component {
|
|
|
380
392
|
// This workaround removes attribute, thusly causing navigation to work correctly again.
|
|
381
393
|
// For the correct solution, Keying.config should have { selector: '.tox-toolbar__group' }
|
|
382
394
|
// in https://github.com/tinymce/tinymce/blob/develop/modules/alloy/src/main/ts/ephox/alloy/ui/schema/SplitSlidingToolbarSchema.ts
|
|
383
|
-
this._elementRef.current?.querySelectorAll(
|
|
395
|
+
this._elementRef.current?.querySelectorAll(".tox-toolbar-overlord button[data-alloy-tabstop]").forEach(it => it.removeAttribute("data-alloy-tabstop"));
|
|
384
396
|
};
|
|
397
|
+
/**
|
|
398
|
+
* Sets up selection saving and restoration logic.
|
|
399
|
+
*
|
|
400
|
+
* There are certain actions a user can take when the RCE is not focused that clear the selection inside the
|
|
401
|
+
* editor, such as invoking the Find feature of the browser. If the user then tries to insert content without
|
|
402
|
+
* going back to the editor, the content would be inserted at the top of the RCE, instead of where their cursor
|
|
403
|
+
* was.
|
|
404
|
+
*
|
|
405
|
+
* This method adds logic that saves and restores the selection to work around the issue.
|
|
406
|
+
*
|
|
407
|
+
* @private
|
|
408
|
+
*/
|
|
409
|
+
// @ts-expect-error
|
|
385
410
|
this._setupSelectionSaving = editor => {
|
|
386
411
|
// @ts-expect-error
|
|
387
412
|
let savedSelection = null;
|
|
@@ -394,7 +419,7 @@ class RCEWrapper extends React.Component {
|
|
|
394
419
|
selectionWasReset = false;
|
|
395
420
|
}
|
|
396
421
|
};
|
|
397
|
-
editor.on(
|
|
422
|
+
editor.on("blur", () => {
|
|
398
423
|
editorHasFocus = false;
|
|
399
424
|
selectionWasReset = false;
|
|
400
425
|
if (!this.editor) return;
|
|
@@ -403,7 +428,7 @@ class RCEWrapper extends React.Component {
|
|
|
403
428
|
isForward: this.editor.selection.isForward()
|
|
404
429
|
};
|
|
405
430
|
});
|
|
406
|
-
editor.on(
|
|
431
|
+
editor.on("focus", () => {
|
|
407
432
|
// We need to restore the selection when the editor regains focus because sometimes the editor regains
|
|
408
433
|
// focus without the user setting the selection themselves (such as when they interact with the toolbar)
|
|
409
434
|
// and if we didn't, we would end up saving the reset selection before a user managed to actually insert
|
|
@@ -412,7 +437,7 @@ class RCEWrapper extends React.Component {
|
|
|
412
437
|
editorHasFocus = true;
|
|
413
438
|
selectionWasReset = false;
|
|
414
439
|
});
|
|
415
|
-
editor.on(
|
|
440
|
+
editor.on("SelectionChange", () => {
|
|
416
441
|
if (editorHasFocus) {
|
|
417
442
|
// We don't care if a selection reset occurs when the editor has focus, the user probably intended that
|
|
418
443
|
// At least they will see the effect
|
|
@@ -422,14 +447,14 @@ class RCEWrapper extends React.Component {
|
|
|
422
447
|
const selection = this.editor.selection.normalize();
|
|
423
448
|
|
|
424
449
|
// Detect a browser-reset selection (e.g. From invoking the Find command)
|
|
425
|
-
if (selection.startContainer?.nodeName ===
|
|
450
|
+
if (selection.startContainer?.nodeName === "BODY" && selection.startContainer === selection.endContainer && selection.startOffset === 0 && selection.endOffset === 0) {
|
|
426
451
|
selectionWasReset = true;
|
|
427
452
|
}
|
|
428
453
|
});
|
|
429
|
-
editor.on(
|
|
454
|
+
editor.on("BeforeExecCommand", () => {
|
|
430
455
|
restoreSelectionIfNecessary();
|
|
431
456
|
});
|
|
432
|
-
editor.on(
|
|
457
|
+
editor.on("ExecCommand", (/* event */
|
|
433
458
|
) => {
|
|
434
459
|
if (!this.editor) return;
|
|
435
460
|
// Commands may have modified the selection, we need to recapture it
|
|
@@ -440,12 +465,14 @@ class RCEWrapper extends React.Component {
|
|
|
440
465
|
});
|
|
441
466
|
};
|
|
442
467
|
this.announcing = 0;
|
|
468
|
+
this._isMounted = false;
|
|
469
|
+
/* ********** autosave support *************** */
|
|
443
470
|
this.initAutoSave = editor => {
|
|
444
471
|
var _this$props$userCache;
|
|
445
|
-
this.storage = new EncryptedStorage((_this$props$userCache = this.props.userCacheKey) !== null && _this$props$userCache !== void 0 ? _this$props$userCache :
|
|
472
|
+
this.storage = new EncryptedStorage((_this$props$userCache = this.props.userCacheKey) !== null && _this$props$userCache !== void 0 ? _this$props$userCache : "");
|
|
446
473
|
if (this.storage) {
|
|
447
|
-
editor.on(
|
|
448
|
-
editor.on(
|
|
474
|
+
editor.on("change Undo Redo", this.doAutoSave);
|
|
475
|
+
editor.on("blur", this.doAutoSave);
|
|
449
476
|
this.cleanupAutoSave();
|
|
450
477
|
try {
|
|
451
478
|
const autosaved = this.getAutoSaved(this.autoSaveKey);
|
|
@@ -470,10 +497,11 @@ class RCEWrapper extends React.Component {
|
|
|
470
497
|
} catch (ex) {
|
|
471
498
|
// log and ignore
|
|
472
499
|
|
|
473
|
-
console.error(
|
|
500
|
+
console.error("Failed initializing rce autosave", ex);
|
|
474
501
|
}
|
|
475
502
|
}
|
|
476
503
|
};
|
|
504
|
+
// remove any autosaved value that's too old
|
|
477
505
|
this.cleanupAutoSave = (deleteAll = false) => {
|
|
478
506
|
if (this.storage) {
|
|
479
507
|
const expiry = deleteAll ? Date.now() : Date.now() - (this.props.autosave?.maxAge || 0);
|
|
@@ -489,6 +517,7 @@ class RCEWrapper extends React.Component {
|
|
|
489
517
|
}
|
|
490
518
|
}
|
|
491
519
|
};
|
|
520
|
+
// @ts-expect-error
|
|
492
521
|
this.restoreAutoSave = ans => {
|
|
493
522
|
this.setState({
|
|
494
523
|
confirmAutoSave: false
|
|
@@ -503,6 +532,7 @@ class RCEWrapper extends React.Component {
|
|
|
503
532
|
// let the content be restored
|
|
504
533
|
debounce(this.checkAccessibility, 1000)();
|
|
505
534
|
};
|
|
535
|
+
// @ts-expect-error
|
|
506
536
|
this.doAutoSave = (e, retry = false) => {
|
|
507
537
|
if (this.storage) {
|
|
508
538
|
const editor = this.mceInstance();
|
|
@@ -522,14 +552,15 @@ class RCEWrapper extends React.Component {
|
|
|
522
552
|
this.cleanupAutoSave(true);
|
|
523
553
|
this.doAutoSave(e, true);
|
|
524
554
|
} else {
|
|
525
|
-
console.error(
|
|
555
|
+
console.error("Autosave failed:", ex);
|
|
526
556
|
}
|
|
527
557
|
}
|
|
528
558
|
}
|
|
529
559
|
};
|
|
560
|
+
/* *********** end autosave support *************** */
|
|
530
561
|
this.onWordCountUpdate = e => {
|
|
531
562
|
if (!this.editor) return;
|
|
532
|
-
const shouldIgnore = countShouldIgnore(this.editor,
|
|
563
|
+
const shouldIgnore = countShouldIgnore(this.editor, "body", "words");
|
|
533
564
|
const updatedCount = e.wordCount.words - shouldIgnore;
|
|
534
565
|
this.setState(state => {
|
|
535
566
|
if (updatedCount !== state.wordCount) {
|
|
@@ -539,9 +570,10 @@ class RCEWrapper extends React.Component {
|
|
|
539
570
|
} else return null;
|
|
540
571
|
});
|
|
541
572
|
};
|
|
573
|
+
// @ts-expect-error
|
|
542
574
|
this.onNodeChange = e => {
|
|
543
575
|
// This is basically copied out of the tinymce silver theme code for the status bar
|
|
544
|
-
const path = e.parents.filter(p => p.nodeName !==
|
|
576
|
+
const path = e.parents.filter(p => p.nodeName !== "BR" && !p.getAttribute("data-mce-bogus") && p.getAttribute("data-mce-type") !== "bookmark")
|
|
545
577
|
// @ts-expect-error
|
|
546
578
|
.map(p => p.nodeName.toLowerCase()).reverse();
|
|
547
579
|
this.setState({
|
|
@@ -552,7 +584,7 @@ class RCEWrapper extends React.Component {
|
|
|
552
584
|
this.props.onContentChange?.(content);
|
|
553
585
|
// check accessibility when clearing the editor,
|
|
554
586
|
// all other times should be checked by handleInputChange
|
|
555
|
-
if (content ===
|
|
587
|
+
if (content === "") {
|
|
556
588
|
this.checkAccessibility();
|
|
557
589
|
}
|
|
558
590
|
};
|
|
@@ -574,12 +606,14 @@ class RCEWrapper extends React.Component {
|
|
|
574
606
|
height: newHeight
|
|
575
607
|
});
|
|
576
608
|
// play nice and send the same event that the silver theme would send
|
|
577
|
-
editor.fire(
|
|
609
|
+
editor.fire("ResizeEditor", {
|
|
610
|
+
deltaY: coordinates.deltaY
|
|
611
|
+
});
|
|
578
612
|
}
|
|
579
613
|
};
|
|
580
614
|
this.onA11yChecker = triggerElementId => {
|
|
581
615
|
const editor = this.mceInstance();
|
|
582
|
-
editor.execCommand(
|
|
616
|
+
editor.execCommand("openAccessibilityChecker", false, {
|
|
583
617
|
mountNode: instuiPopupMountNodeFn,
|
|
584
618
|
triggerElementId,
|
|
585
619
|
onFixError: errors => {
|
|
@@ -593,7 +627,7 @@ class RCEWrapper extends React.Component {
|
|
|
593
627
|
};
|
|
594
628
|
this.checkAccessibility = () => {
|
|
595
629
|
const editor = this.mceInstance();
|
|
596
|
-
editor.execCommand(
|
|
630
|
+
editor.execCommand("checkAccessibility", false, {
|
|
597
631
|
// @ts-expect-error
|
|
598
632
|
done: errors => {
|
|
599
633
|
this.setState({
|
|
@@ -633,7 +667,7 @@ class RCEWrapper extends React.Component {
|
|
|
633
667
|
}
|
|
634
668
|
};
|
|
635
669
|
this.handleAIClick = () => {
|
|
636
|
-
import(
|
|
670
|
+
import("./plugins/shared/ai_tools").then(module => {
|
|
637
671
|
// @ts-expect-error
|
|
638
672
|
this.AIToolsTray = module.AIToolsTray;
|
|
639
673
|
this.setState({
|
|
@@ -641,7 +675,7 @@ class RCEWrapper extends React.Component {
|
|
|
641
675
|
AITToolsFocusReturn: document.activeElement
|
|
642
676
|
});
|
|
643
677
|
}).catch(ex => {
|
|
644
|
-
console.error(
|
|
678
|
+
console.error("Failed loading the AIToolsTray", ex);
|
|
645
679
|
});
|
|
646
680
|
};
|
|
647
681
|
this.closeAITools = () => {
|
|
@@ -683,10 +717,10 @@ class RCEWrapper extends React.Component {
|
|
|
683
717
|
this.getCurrentContentForAI = () => {
|
|
684
718
|
const selected = this.mceInstance().selection.getContent();
|
|
685
719
|
return selected ? {
|
|
686
|
-
type:
|
|
720
|
+
type: "selection",
|
|
687
721
|
content: selected
|
|
688
722
|
} : {
|
|
689
|
-
type:
|
|
723
|
+
type: "full",
|
|
690
724
|
content: this.mceInstance().getContent()
|
|
691
725
|
};
|
|
692
726
|
};
|
|
@@ -701,7 +735,7 @@ class RCEWrapper extends React.Component {
|
|
|
701
735
|
alert.id = alertIdValue++;
|
|
702
736
|
this.setState(state => {
|
|
703
737
|
let messages = state.messages.concat(alert);
|
|
704
|
-
messages = _.uniqBy(messages,
|
|
738
|
+
messages = _.uniqBy(messages, "text"); // Don't show the same message twice
|
|
705
739
|
return {
|
|
706
740
|
messages
|
|
707
741
|
};
|
|
@@ -715,9 +749,12 @@ class RCEWrapper extends React.Component {
|
|
|
715
749
|
};
|
|
716
750
|
});
|
|
717
751
|
};
|
|
752
|
+
/**
|
|
753
|
+
* Used for reseting the value during tests
|
|
754
|
+
*/
|
|
718
755
|
this.resetAlertId = () => {
|
|
719
756
|
if (this.state.messages.length > 0) {
|
|
720
|
-
throw new Error(
|
|
757
|
+
throw new Error("There are messages currently, you cannot reset when they are non-zero");
|
|
721
758
|
}
|
|
722
759
|
alertIdValue = 0;
|
|
723
760
|
};
|
|
@@ -759,7 +796,7 @@ class RCEWrapper extends React.Component {
|
|
|
759
796
|
if (!Number.isNaN(_ht)) {
|
|
760
797
|
_ht = `${_ht}px`;
|
|
761
798
|
}
|
|
762
|
-
const currentRCECount = document.querySelectorAll(
|
|
799
|
+
const currentRCECount = document.querySelectorAll(".rce-wrapper").length;
|
|
763
800
|
const maxInitRenderedRCEs = Number.isNaN(props.maxInitRenderedRCEs) ? RCEWrapper.defaultProps.maxInitRenderedRCEs : props.maxInitRenderedRCEs;
|
|
764
801
|
this.state = {
|
|
765
802
|
path: [],
|
|
@@ -770,9 +807,9 @@ class RCEWrapper extends React.Component {
|
|
|
770
807
|
messages: [],
|
|
771
808
|
announcement: null,
|
|
772
809
|
confirmAutoSave: false,
|
|
773
|
-
autoSavedContent:
|
|
810
|
+
autoSavedContent: "",
|
|
774
811
|
// @ts-expect-error
|
|
775
|
-
id: this.props.id || this.props.textareaId || `${uid(
|
|
812
|
+
id: this.props.id || this.props.textareaId || `${uid("rce", 2)}`,
|
|
776
813
|
// @ts-expect-error
|
|
777
814
|
height: _ht,
|
|
778
815
|
fullscreenState: {
|
|
@@ -780,7 +817,7 @@ class RCEWrapper extends React.Component {
|
|
|
780
817
|
prevHeight: _ht
|
|
781
818
|
},
|
|
782
819
|
a11yErrorsCount: 0,
|
|
783
|
-
shouldShowEditor: typeof IntersectionObserver ===
|
|
820
|
+
shouldShowEditor: typeof IntersectionObserver === "undefined" || maxInitRenderedRCEs <= 0 || currentRCECount < maxInitRenderedRCEs,
|
|
784
821
|
AIToolsOpen: false
|
|
785
822
|
};
|
|
786
823
|
this._statusBarId = `${this.state.id}_statusbar`;
|
|
@@ -806,6 +843,7 @@ class RCEWrapper extends React.Component {
|
|
|
806
843
|
this._handleFullscreenResize();
|
|
807
844
|
});
|
|
808
845
|
this.AIToolsTray = undefined;
|
|
846
|
+
this._effectiveContainingContext = normalizeContainingContext(this.props.trayProps?.containingContext);
|
|
809
847
|
}
|
|
810
848
|
|
|
811
849
|
// when the RCE is put into fullscreen we need to move the div
|
|
@@ -814,11 +852,11 @@ class RCEWrapper extends React.Component {
|
|
|
814
852
|
// configure tinymce to say where that div is mounted, do this
|
|
815
853
|
// is a bit of a hack to tag the div that is this RCE's
|
|
816
854
|
_tagTinymceAuxDiv() {
|
|
817
|
-
const tinyauxlist = document.querySelectorAll(
|
|
855
|
+
const tinyauxlist = document.querySelectorAll(".tox-tinymce-aux");
|
|
818
856
|
if (tinyauxlist.length) {
|
|
819
857
|
const myaux = tinyauxlist[tinyauxlist.length - 1];
|
|
820
858
|
if (myaux.id) {
|
|
821
|
-
console.error(
|
|
859
|
+
console.error("Unexpected ID on my tox-tinymce-aux element");
|
|
822
860
|
}
|
|
823
861
|
myaux.id = `tinyaux-${this.id}`;
|
|
824
862
|
}
|
|
@@ -832,6 +870,7 @@ class RCEWrapper extends React.Component {
|
|
|
832
870
|
explicit_latex_typesetting = false,
|
|
833
871
|
rce_transform_loaded_content = false,
|
|
834
872
|
rce_find_replace = false,
|
|
873
|
+
rce_studio_embed_improvements = false,
|
|
835
874
|
file_verifiers_for_quiz_links = false,
|
|
836
875
|
consolidated_media_player = false
|
|
837
876
|
} = this.props.features;
|
|
@@ -839,6 +878,7 @@ class RCEWrapper extends React.Component {
|
|
|
839
878
|
new_math_equation_handling,
|
|
840
879
|
explicit_latex_typesetting,
|
|
841
880
|
rce_transform_loaded_content,
|
|
881
|
+
rce_studio_embed_improvements,
|
|
842
882
|
file_verifiers_for_quiz_links,
|
|
843
883
|
rce_find_replace,
|
|
844
884
|
consolidated_media_player
|
|
@@ -876,7 +916,7 @@ class RCEWrapper extends React.Component {
|
|
|
876
916
|
let status = true;
|
|
877
917
|
// Check for remaining placeholders
|
|
878
918
|
if (this.mceInstance().dom.doc.querySelector(`[data-placeholder-for]`)) {
|
|
879
|
-
status = promptFunc(formatMessage(
|
|
919
|
+
status = promptFunc(formatMessage("Content is still being uploaded, if you continue it will not be embedded properly."));
|
|
880
920
|
}
|
|
881
921
|
return status;
|
|
882
922
|
}
|
|
@@ -936,9 +976,9 @@ class RCEWrapper extends React.Component {
|
|
|
936
976
|
// @ts-expect-error
|
|
937
977
|
ifr.contentDocument.body.clientHeight -
|
|
938
978
|
// @ts-expect-error
|
|
939
|
-
parseInt(editor_body_style[
|
|
979
|
+
parseInt(editor_body_style["padding-top"], 10) -
|
|
940
980
|
// @ts-expect-error
|
|
941
|
-
parseInt(editor_body_style[
|
|
981
|
+
parseInt(editor_body_style["padding-bottom"], 10);
|
|
942
982
|
const para_margin_ht = 24;
|
|
943
983
|
const reserve_ht = Math.ceil(height + para_margin_ht);
|
|
944
984
|
if (reserve_ht > editor_ht) {
|
|
@@ -950,7 +990,7 @@ class RCEWrapper extends React.Component {
|
|
|
950
990
|
}
|
|
951
991
|
}
|
|
952
992
|
checkImageLoadError(element) {
|
|
953
|
-
if (!element || element.tagName !==
|
|
993
|
+
if (!element || element.tagName !== "IMG") {
|
|
954
994
|
return;
|
|
955
995
|
}
|
|
956
996
|
// @ts-expect-error
|
|
@@ -965,9 +1005,9 @@ class RCEWrapper extends React.Component {
|
|
|
965
1005
|
// @ts-expect-error
|
|
966
1006
|
if (element.naturalWidth === 0) {
|
|
967
1007
|
// @ts-expect-error
|
|
968
|
-
element.style.border =
|
|
1008
|
+
element.style.border = "1px solid #000";
|
|
969
1009
|
// @ts-expect-error
|
|
970
|
-
element.style.padding =
|
|
1010
|
+
element.style.padding = "2px";
|
|
971
1011
|
}
|
|
972
1012
|
}, 0);
|
|
973
1013
|
}
|
|
@@ -977,7 +1017,7 @@ class RCEWrapper extends React.Component {
|
|
|
977
1017
|
this.contentInserted(element);
|
|
978
1018
|
}
|
|
979
1019
|
replaceCode(code) {
|
|
980
|
-
if (code !==
|
|
1020
|
+
if (code !== "" && window.confirm(formatMessage("Content in the editor will be changed. Press Cancel to keep the original content."))) {
|
|
981
1021
|
this.mceInstance().setContent(code);
|
|
982
1022
|
}
|
|
983
1023
|
}
|
|
@@ -994,12 +1034,12 @@ class RCEWrapper extends React.Component {
|
|
|
994
1034
|
// that there's some embedded content helper
|
|
995
1035
|
// From what I've read, "title" is more reliable than "aria-label" for
|
|
996
1036
|
// elements like iframes and embeds.
|
|
997
|
-
const temp = document.createElement(
|
|
1037
|
+
const temp = document.createElement("div");
|
|
998
1038
|
temp.innerHTML = code;
|
|
999
1039
|
const code_elem = temp.firstElementChild;
|
|
1000
1040
|
if (code_elem) {
|
|
1001
|
-
if (!code_elem.hasAttribute(
|
|
1002
|
-
code_elem.setAttribute(
|
|
1041
|
+
if (!code_elem.hasAttribute("title") && !code_elem.hasAttribute("aria-label")) {
|
|
1042
|
+
code_elem.setAttribute("title", formatMessage("embedded content"));
|
|
1003
1043
|
}
|
|
1004
1044
|
code = code_elem.outerHTML;
|
|
1005
1045
|
}
|
|
@@ -1009,7 +1049,7 @@ class RCEWrapper extends React.Component {
|
|
|
1009
1049
|
// and it's often inserted into a <p> on top of that. Find the
|
|
1010
1050
|
// iframe and use it to flash the indicator.
|
|
1011
1051
|
const element = contentInsertion.insertContent(editor, code);
|
|
1012
|
-
const ifr = element && element.querySelector && element.querySelector(
|
|
1052
|
+
const ifr = element && element.querySelector && element.querySelector("iframe");
|
|
1013
1053
|
if (ifr) {
|
|
1014
1054
|
this.contentInserted(ifr);
|
|
1015
1055
|
} else {
|
|
@@ -1021,7 +1061,7 @@ class RCEWrapper extends React.Component {
|
|
|
1021
1061
|
const element = contentInsertion.insertImage(editor, image, this.getCanvasUrl());
|
|
1022
1062
|
|
|
1023
1063
|
// Removes TinyMCE's caret text if exists.
|
|
1024
|
-
if (element?.nextSibling?.data?.startsWith(
|
|
1064
|
+
if (element?.nextSibling?.data?.startsWith("\xA0" /* nbsp */)) {
|
|
1025
1065
|
element.nextSibling.splitText(1);
|
|
1026
1066
|
element.nextSibling.remove();
|
|
1027
1067
|
}
|
|
@@ -1095,16 +1135,15 @@ class RCEWrapper extends React.Component {
|
|
|
1095
1135
|
if (this.editor) {
|
|
1096
1136
|
return this.editor;
|
|
1097
1137
|
}
|
|
1098
|
-
|
|
1099
|
-
return editors.filter(ed => ed.id === this.props.textareaId)[0];
|
|
1138
|
+
return this.props.tinymce.get(this.props.textareaId);
|
|
1100
1139
|
}
|
|
1101
1140
|
|
|
1102
1141
|
// @ts-expect-error
|
|
1103
1142
|
onTinyMCEInstance(command, ...args) {
|
|
1104
1143
|
const editor = this.mceInstance();
|
|
1105
1144
|
if (editor) {
|
|
1106
|
-
if (command ===
|
|
1107
|
-
editor.execCommand(
|
|
1145
|
+
if (command === "mceRemoveEditor") {
|
|
1146
|
+
editor.execCommand("mceNewDocument");
|
|
1108
1147
|
} // makes sure content can't persist past removal
|
|
1109
1148
|
editor.execCommand(command, false, ...args);
|
|
1110
1149
|
}
|
|
@@ -1124,17 +1163,17 @@ class RCEWrapper extends React.Component {
|
|
|
1124
1163
|
return null;
|
|
1125
1164
|
}
|
|
1126
1165
|
textareaValue() {
|
|
1127
|
-
return this.getTextarea()?.value ||
|
|
1166
|
+
return this.getTextarea()?.value || "";
|
|
1128
1167
|
}
|
|
1129
1168
|
get id() {
|
|
1130
1169
|
return this.state.id;
|
|
1131
1170
|
}
|
|
1132
1171
|
getHtmlEditorStorage() {
|
|
1133
|
-
const cookieValue = getCookie(
|
|
1172
|
+
const cookieValue = getCookie("rce.htmleditor");
|
|
1134
1173
|
if (cookieValue) {
|
|
1135
1174
|
document.cookie = `rce.htmleditor=${cookieValue};path=/;max-age=0`;
|
|
1136
1175
|
}
|
|
1137
|
-
const value = cookieValue || this.storage?.getItem?.(
|
|
1176
|
+
const value = cookieValue || this.storage?.getItem?.("rce.htmleditor")?.content;
|
|
1138
1177
|
return value === RAW_HTML_EDITOR_VIEW || value === PRETTY_HTML_EDITOR_VIEW ? value : PRETTY_HTML_EDITOR_VIEW;
|
|
1139
1178
|
}
|
|
1140
1179
|
_isFullscreen() {
|
|
@@ -1151,7 +1190,7 @@ class RCEWrapper extends React.Component {
|
|
|
1151
1190
|
this._elementRef.current?.appendChild(tinymenuhost);
|
|
1152
1191
|
}
|
|
1153
1192
|
this._elementRef.current?.addEventListener(FS_CHANGEEVENT, this._onFullscreenChange);
|
|
1154
|
-
if (typeof this._elementRef.current?.offsetHeight ===
|
|
1193
|
+
if (typeof this._elementRef.current?.offsetHeight === "number") {
|
|
1155
1194
|
this.setState({
|
|
1156
1195
|
fullscreenState: {
|
|
1157
1196
|
prevHeight: this._elementRef.current.offsetHeight - this._getStatusBarHeight()
|
|
@@ -1171,9 +1210,6 @@ class RCEWrapper extends React.Component {
|
|
|
1171
1210
|
document[FS_EXIT]();
|
|
1172
1211
|
}
|
|
1173
1212
|
}
|
|
1174
|
-
|
|
1175
|
-
// @ts-expect-error
|
|
1176
|
-
|
|
1177
1213
|
_getStatusBarHeight() {
|
|
1178
1214
|
// the height prop is the height of the editor and does not include
|
|
1179
1215
|
// the status bar. we'll need this later.
|
|
@@ -1186,7 +1222,7 @@ class RCEWrapper extends React.Component {
|
|
|
1186
1222
|
const container = ed.getContainer();
|
|
1187
1223
|
if (container) {
|
|
1188
1224
|
container.style.height = cssHeight;
|
|
1189
|
-
ed.fire(
|
|
1225
|
+
ed.fire("ResizeEditor");
|
|
1190
1226
|
}
|
|
1191
1227
|
const textarea = this.getTextarea();
|
|
1192
1228
|
if (textarea) {
|
|
@@ -1197,10 +1233,10 @@ class RCEWrapper extends React.Component {
|
|
|
1197
1233
|
});
|
|
1198
1234
|
}
|
|
1199
1235
|
focus() {
|
|
1200
|
-
this.onTinyMCEInstance(
|
|
1236
|
+
this.onTinyMCEInstance("mceFocus");
|
|
1201
1237
|
// tinymce doesn't always call the focus handler.
|
|
1202
1238
|
// @ts-expect-error
|
|
1203
|
-
this.handleFocusEditor(new Event(
|
|
1239
|
+
this.handleFocusEditor(new Event("focus", {
|
|
1204
1240
|
target: this.mceInstance()
|
|
1205
1241
|
}));
|
|
1206
1242
|
}
|
|
@@ -1238,7 +1274,7 @@ class RCEWrapper extends React.Component {
|
|
|
1238
1274
|
*/
|
|
1239
1275
|
get _mceSerializedInitialHtml() {
|
|
1240
1276
|
if (!this._mceSerializedInitialHtmlCached) {
|
|
1241
|
-
const el = window.document.createElement(
|
|
1277
|
+
const el = window.document.createElement("div");
|
|
1242
1278
|
// @ts-expect-error
|
|
1243
1279
|
el.innerHTML = this.initialContent;
|
|
1244
1280
|
const serializer = this.mceInstance().serializer;
|
|
@@ -1292,20 +1328,22 @@ class RCEWrapper extends React.Component {
|
|
|
1292
1328
|
// focus is still somewhere w/in me
|
|
1293
1329
|
return;
|
|
1294
1330
|
}
|
|
1295
|
-
const activeClass = document.activeElement
|
|
1331
|
+
const activeClass = document.activeElement?.getAttribute("class");
|
|
1296
1332
|
if (
|
|
1297
1333
|
// @ts-expect-error
|
|
1298
|
-
(event.focusedEditor === undefined ||
|
|
1334
|
+
(event.focusedEditor === undefined ||
|
|
1335
|
+
// @ts-expect-error
|
|
1336
|
+
event.target.id === event.focusedEditor?.id) && activeClass?.includes("tox-")) {
|
|
1299
1337
|
// if a toolbar button has focus, then the user clicks on the "more" button
|
|
1300
1338
|
// focus jumps to the body, then eventually to the popped up toolbar. This
|
|
1301
1339
|
// catches that case.
|
|
1302
1340
|
return;
|
|
1303
1341
|
}
|
|
1304
|
-
if (event?.relatedTarget?.getAttribute(
|
|
1342
|
+
if (event?.relatedTarget?.getAttribute("class")?.includes("tox-")) {
|
|
1305
1343
|
// a tinymce popup has focus
|
|
1306
1344
|
return;
|
|
1307
1345
|
}
|
|
1308
|
-
const popups = document.querySelectorAll(
|
|
1346
|
+
const popups = document.querySelectorAll("[data-mce-component]");
|
|
1309
1347
|
for (const popup of popups) {
|
|
1310
1348
|
if (popup.contains(document.activeElement)) {
|
|
1311
1349
|
// one of our popups has focus
|
|
@@ -1319,50 +1357,26 @@ class RCEWrapper extends React.Component {
|
|
|
1319
1357
|
}, ASYNC_FOCUS_TIMEOUT);
|
|
1320
1358
|
}
|
|
1321
1359
|
}
|
|
1322
|
-
|
|
1323
|
-
// @ts-expect-error
|
|
1324
|
-
|
|
1325
1360
|
// @ts-expect-error
|
|
1326
1361
|
call(methodName, ...args) {
|
|
1327
1362
|
// since exists? has a ? and cant be a regular function just return true
|
|
1328
1363
|
// rather than calling as a fn on the editor
|
|
1329
|
-
if (methodName ===
|
|
1364
|
+
if (methodName === "exists?") {
|
|
1330
1365
|
return true;
|
|
1331
1366
|
}
|
|
1332
1367
|
// @ts-expect-error
|
|
1333
1368
|
return this[methodName](...args);
|
|
1334
1369
|
}
|
|
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
1370
|
announceContextToolbars(editor) {
|
|
1358
|
-
editor.on(
|
|
1371
|
+
editor.on("NodeChange", () => {
|
|
1372
|
+
if (!this._isMounted) return;
|
|
1359
1373
|
const node = editor.selection.getNode();
|
|
1360
1374
|
// @ts-expect-error
|
|
1361
1375
|
if (isImageEmbed(node, editor)) {
|
|
1362
1376
|
if (this.announcing !== 1) {
|
|
1363
1377
|
this.setState({
|
|
1364
|
-
announcement: formatMessage(
|
|
1365
|
-
text: node.getAttribute(
|
|
1378
|
+
announcement: formatMessage("type Control F9 to access image options. {text}", {
|
|
1379
|
+
text: node.getAttribute("alt")
|
|
1366
1380
|
})
|
|
1367
1381
|
});
|
|
1368
1382
|
this.announcing = 1;
|
|
@@ -1370,7 +1384,7 @@ class RCEWrapper extends React.Component {
|
|
|
1370
1384
|
} else if (isFileLink(node, editor)) {
|
|
1371
1385
|
if (this.announcing !== 2) {
|
|
1372
1386
|
this.setState({
|
|
1373
|
-
announcement: formatMessage(
|
|
1387
|
+
announcement: formatMessage("type Control F9 to access link options. {text}", {
|
|
1374
1388
|
text: node.textContent
|
|
1375
1389
|
})
|
|
1376
1390
|
});
|
|
@@ -1379,7 +1393,7 @@ class RCEWrapper extends React.Component {
|
|
|
1379
1393
|
} else if (isElementWithinTable(node)) {
|
|
1380
1394
|
if (this.announcing !== 3) {
|
|
1381
1395
|
this.setState({
|
|
1382
|
-
announcement: formatMessage(
|
|
1396
|
+
announcement: formatMessage("type Control F9 to access table options. {text}", {
|
|
1383
1397
|
text: node.textContent
|
|
1384
1398
|
})
|
|
1385
1399
|
});
|
|
@@ -1392,14 +1406,21 @@ class RCEWrapper extends React.Component {
|
|
|
1392
1406
|
this.announcing = 0;
|
|
1393
1407
|
}
|
|
1394
1408
|
});
|
|
1409
|
+
editor.on("ResizeEditor", ({
|
|
1410
|
+
deltaY
|
|
1411
|
+
}) => {
|
|
1412
|
+
if (!this._isMounted || !deltaY) return;
|
|
1413
|
+
if (deltaY < 0) {
|
|
1414
|
+
this.setState({
|
|
1415
|
+
announcement: formatMessage("The height of Rich Content Area is decreased.")
|
|
1416
|
+
});
|
|
1417
|
+
} else {
|
|
1418
|
+
this.setState({
|
|
1419
|
+
announcement: formatMessage("The height of Rich Content Area is increased.")
|
|
1420
|
+
});
|
|
1421
|
+
}
|
|
1422
|
+
});
|
|
1395
1423
|
}
|
|
1396
|
-
|
|
1397
|
-
/* ********** autosave support *************** */
|
|
1398
|
-
|
|
1399
|
-
// remove any autosaved value that's too old
|
|
1400
|
-
|
|
1401
|
-
// @ts-expect-error
|
|
1402
|
-
|
|
1403
1424
|
getAutoSaved(key) {
|
|
1404
1425
|
let autosaved = null;
|
|
1405
1426
|
try {
|
|
@@ -1421,28 +1442,21 @@ class RCEWrapper extends React.Component {
|
|
|
1421
1442
|
// This doesn't apply if the editor is off-screen or has visibility:hidden;
|
|
1422
1443
|
// only if it isn't rendered or has display:none;
|
|
1423
1444
|
const editorVisible = this.editor.getContainer().offsetParent;
|
|
1424
|
-
return this.props.autosave?.enabled && editorVisible && document.querySelectorAll(
|
|
1445
|
+
return this.props.autosave?.enabled && editorVisible && document.querySelectorAll(".rce-wrapper").length === 1 && storageAvailable();
|
|
1425
1446
|
}
|
|
1426
1447
|
get autoSaveKey() {
|
|
1427
|
-
|
|
1428
|
-
const userId = this.props.trayProps?.containingContext.userId;
|
|
1448
|
+
const userId = this._effectiveContainingContext?.userId || "-";
|
|
1429
1449
|
return `rceautosave:${userId}${window.location.href}:${this.props.textareaId}`;
|
|
1430
1450
|
}
|
|
1431
|
-
|
|
1432
|
-
// @ts-expect-error
|
|
1433
|
-
|
|
1434
|
-
/* *********** end autosave support *************** */
|
|
1435
|
-
|
|
1436
|
-
// @ts-expect-error
|
|
1437
|
-
|
|
1438
1451
|
componentWillUnmount() {
|
|
1452
|
+
this._isMounted = false;
|
|
1439
1453
|
if (this.state.shouldShowEditor) {
|
|
1440
1454
|
window.clearTimeout(this.blurTimer);
|
|
1441
1455
|
if (!this._destroyCalled) {
|
|
1442
1456
|
this.destroy();
|
|
1443
1457
|
}
|
|
1444
1458
|
if (this._elementRef.current) {
|
|
1445
|
-
this._elementRef.current.removeEventListener(
|
|
1459
|
+
this._elementRef.current.removeEventListener("keydown", this.handleKey, true);
|
|
1446
1460
|
}
|
|
1447
1461
|
this.mutationObserver?.disconnect();
|
|
1448
1462
|
this.intersectionObserver?.disconnect();
|
|
@@ -1454,27 +1468,27 @@ class RCEWrapper extends React.Component {
|
|
|
1454
1468
|
|
|
1455
1469
|
// @ts-expect-error
|
|
1456
1470
|
const setupCallback = options.setup;
|
|
1457
|
-
const canvasPlugins = rcsExists ? [
|
|
1471
|
+
const canvasPlugins = rcsExists ? ["instructure_image", "instructure_documents", "instructure_equation"] : [];
|
|
1458
1472
|
if (rcsExists && !this.props.instRecordDisabled) {
|
|
1459
|
-
canvasPlugins.splice(2, 0,
|
|
1473
|
+
canvasPlugins.splice(2, 0, "instructure_record");
|
|
1460
1474
|
}
|
|
1461
|
-
const pastePlugins = rcsExists ? [
|
|
1462
|
-
if (rcsExists && this.props.use_rce_icon_maker && this.props.trayProps?.contextType ===
|
|
1463
|
-
canvasPlugins.push(
|
|
1475
|
+
const pastePlugins = rcsExists ? ["instructure_paste", "paste"] : ["paste"];
|
|
1476
|
+
if (rcsExists && this.props.use_rce_icon_maker && this.props.trayProps?.contextType === "course") {
|
|
1477
|
+
canvasPlugins.push("instructure_icon_maker");
|
|
1464
1478
|
}
|
|
1465
1479
|
if (document[FS_ENABLED]) {
|
|
1466
|
-
canvasPlugins.push(
|
|
1480
|
+
canvasPlugins.push("instructure_fullscreen");
|
|
1467
1481
|
}
|
|
1468
1482
|
if (this.getRequiredFeatureStatuses().rce_find_replace) {
|
|
1469
|
-
canvasPlugins.push(
|
|
1470
|
-
canvasPlugins.push(
|
|
1483
|
+
canvasPlugins.push("searchreplace");
|
|
1484
|
+
canvasPlugins.push("instructure_search_and_replace");
|
|
1471
1485
|
}
|
|
1472
|
-
const possibleNewMenubarItems = this.props.editorOptions.menu ? Object.keys(this.props.editorOptions.menu).join(
|
|
1486
|
+
const possibleNewMenubarItems = this.props.editorOptions.menu ? Object.keys(this.props.editorOptions.menu).join(" ") : undefined;
|
|
1473
1487
|
const wrappedOpts = {
|
|
1474
1488
|
...defaultTinymceConfig,
|
|
1475
1489
|
...options,
|
|
1476
1490
|
readonly: this.props.readOnly,
|
|
1477
|
-
theme:
|
|
1491
|
+
theme: "silver",
|
|
1478
1492
|
// some older code specified 'modern', which doesn't exist any more
|
|
1479
1493
|
|
|
1480
1494
|
// @ts-expect-error
|
|
@@ -1483,7 +1497,7 @@ class RCEWrapper extends React.Component {
|
|
|
1483
1497
|
document_base_url: this.props.canvasOrigin,
|
|
1484
1498
|
block_formats:
|
|
1485
1499
|
// @ts-expect-error
|
|
1486
|
-
options.block_formats || [`${formatMessage(
|
|
1500
|
+
options.block_formats || [`${formatMessage("Heading 2")}=h2`, `${formatMessage("Heading 3")}=h3`, `${formatMessage("Heading 4")}=h4`, `${formatMessage("Preformatted")}=pre`, `${formatMessage("Paragraph")}=p`].join("; "),
|
|
1487
1501
|
setup: editor => {
|
|
1488
1502
|
addKebabIcon(editor);
|
|
1489
1503
|
editorWrappers.set(editor, this);
|
|
@@ -1496,7 +1510,7 @@ class RCEWrapper extends React.Component {
|
|
|
1496
1510
|
// @ts-expect-error
|
|
1497
1511
|
bridge.userLocale = userLocale;
|
|
1498
1512
|
bridge.canvasOrigin = this.props.canvasOrigin;
|
|
1499
|
-
if (typeof setupCallback ===
|
|
1513
|
+
if (typeof setupCallback === "function") {
|
|
1500
1514
|
setupCallback(editor);
|
|
1501
1515
|
}
|
|
1502
1516
|
},
|
|
@@ -1507,7 +1521,7 @@ class RCEWrapper extends React.Component {
|
|
|
1507
1521
|
// @ts-expect-error
|
|
1508
1522
|
content_css: options.content_css || [],
|
|
1509
1523
|
// @ts-expect-error
|
|
1510
|
-
content_style: contentCSS + (options.content_style ||
|
|
1524
|
+
content_style: contentCSS + (options.content_style || ""),
|
|
1511
1525
|
menubar: mergeMenuItems(getMenubarForVariant(this.variant), possibleNewMenubarItems),
|
|
1512
1526
|
// default menu options listed at https://www.tiny.cloud/docs/configure/editor-appearance/#menu
|
|
1513
1527
|
// tinymce's default edit and table menus are fine
|
|
@@ -1522,10 +1536,10 @@ class RCEWrapper extends React.Component {
|
|
|
1522
1536
|
getToolbarForVariant(this.variant, this.ltiToolFavorites),
|
|
1523
1537
|
// @ts-expect-error
|
|
1524
1538
|
options.toolbar),
|
|
1525
|
-
contextmenu:
|
|
1539
|
+
contextmenu: "",
|
|
1526
1540
|
// show the browser's native context menu
|
|
1527
1541
|
|
|
1528
|
-
toolbar_mode:
|
|
1542
|
+
toolbar_mode: "sliding",
|
|
1529
1543
|
toolbar_sticky: true,
|
|
1530
1544
|
// In regards to the ability to disable plugins:
|
|
1531
1545
|
// we only have to explicitly manage the removal of plugins
|
|
@@ -1534,16 +1548,16 @@ class RCEWrapper extends React.Component {
|
|
|
1534
1548
|
// handles all of that complexity. It that ever changes in the
|
|
1535
1549
|
// future in an upgraded version, we will have to update the
|
|
1536
1550
|
// logic in those other places as well.
|
|
1537
|
-
plugins: mergePlugins([
|
|
1551
|
+
plugins: mergePlugins(["autolink", "media", "table", "link", "directionality", "lists", "textpattern", "hr", "instructure_color", "instructure-ui-icons", "instructure_condensed_buttons", "instructure_links", "instructure_html_view", "instructure_media_embed", "a11y_checker", "wordcount", "instructure_wordcount", "instructure_wordcount_header", "instructure_keyboard_shortcuts_header", "instructure_studio_media_options", "instructure_rce_external_tools", ...pastePlugins, ...canvasPlugins],
|
|
1538
1552
|
// filter out the plugins designated for removal
|
|
1539
1553
|
// @ts-expect-error
|
|
1540
|
-
sanitizePlugins(options.plugins)?.filter(p => p.length > 0 && p[0] !==
|
|
1554
|
+
sanitizePlugins(options.plugins)?.filter(p => p.length > 0 && p[0] !== "-"), this.pluginsToExclude),
|
|
1541
1555
|
textpattern_patterns: [{
|
|
1542
|
-
start:
|
|
1543
|
-
cmd:
|
|
1556
|
+
start: "* ",
|
|
1557
|
+
cmd: "InsertUnorderedList"
|
|
1544
1558
|
}, {
|
|
1545
|
-
start:
|
|
1546
|
-
cmd:
|
|
1559
|
+
start: "- ",
|
|
1560
|
+
cmd: "InsertUnorderedList"
|
|
1547
1561
|
}]
|
|
1548
1562
|
};
|
|
1549
1563
|
if (this.props.trayProps) {
|
|
@@ -1565,7 +1579,7 @@ class RCEWrapper extends React.Component {
|
|
|
1565
1579
|
}
|
|
1566
1580
|
unhandleTextareaChange() {
|
|
1567
1581
|
if (this._textareaEl) {
|
|
1568
|
-
this._textareaEl.removeEventListener(
|
|
1582
|
+
this._textareaEl.removeEventListener("input", this.handleTextareaChange);
|
|
1569
1583
|
}
|
|
1570
1584
|
}
|
|
1571
1585
|
registerTextareaChange() {
|
|
@@ -1573,7 +1587,7 @@ class RCEWrapper extends React.Component {
|
|
|
1573
1587
|
if (this._textareaEl !== el) {
|
|
1574
1588
|
this.unhandleTextareaChange();
|
|
1575
1589
|
if (el) {
|
|
1576
|
-
el.addEventListener(
|
|
1590
|
+
el.addEventListener("input", this.handleTextareaChange);
|
|
1577
1591
|
if (this.props.textareaClassName) {
|
|
1578
1592
|
// split the string on whitespace because classList doesn't let you add multiple
|
|
1579
1593
|
// space seperated classes at a time but does let you add an array of them
|
|
@@ -1584,6 +1598,7 @@ class RCEWrapper extends React.Component {
|
|
|
1584
1598
|
}
|
|
1585
1599
|
}
|
|
1586
1600
|
componentDidMount() {
|
|
1601
|
+
this._isMounted = true;
|
|
1587
1602
|
if (this.state.shouldShowEditor) {
|
|
1588
1603
|
this.editorReallyDidMount();
|
|
1589
1604
|
} else {
|
|
@@ -1598,7 +1613,7 @@ class RCEWrapper extends React.Component {
|
|
|
1598
1613
|
// initialize the RCE when it gets close to entering the viewport
|
|
1599
1614
|
{
|
|
1600
1615
|
root: null,
|
|
1601
|
-
rootMargin:
|
|
1616
|
+
rootMargin: "200px 0px",
|
|
1602
1617
|
threshold: 0.0
|
|
1603
1618
|
});
|
|
1604
1619
|
// @ts-expect-error
|
|
@@ -1617,7 +1632,7 @@ class RCEWrapper extends React.Component {
|
|
|
1617
1632
|
this.focusCurrentView();
|
|
1618
1633
|
}
|
|
1619
1634
|
if (prevProps.readOnly !== this.props.readOnly) {
|
|
1620
|
-
this.mceInstance().mode.set(this.props.readOnly ?
|
|
1635
|
+
this.mceInstance().mode.set(this.props.readOnly ? "readonly" : "design");
|
|
1621
1636
|
}
|
|
1622
1637
|
}
|
|
1623
1638
|
}
|
|
@@ -1631,7 +1646,7 @@ class RCEWrapper extends React.Component {
|
|
|
1631
1646
|
this._tagTinymceAuxDiv();
|
|
1632
1647
|
this.registerTextareaChange();
|
|
1633
1648
|
// @ts-expect-error
|
|
1634
|
-
this._elementRef.current.addEventListener(
|
|
1649
|
+
this._elementRef.current.addEventListener("keydown", this.handleKey, true);
|
|
1635
1650
|
// give the textarea its initial size
|
|
1636
1651
|
this.onResize(null, {
|
|
1637
1652
|
deltaY: 0
|
|
@@ -1639,7 +1654,7 @@ class RCEWrapper extends React.Component {
|
|
|
1639
1654
|
// Preload the LTI Tools modal
|
|
1640
1655
|
// This helps with loading the favorited external tools
|
|
1641
1656
|
if (this.ltiToolFavorites.length > 0) {
|
|
1642
|
-
import(
|
|
1657
|
+
import("./plugins/instructure_rce_external_tools/components/ExternalToolSelectionDialog/ExternalToolSelectionDialog");
|
|
1643
1658
|
}
|
|
1644
1659
|
bridge.renderEditor(this);
|
|
1645
1660
|
}
|
|
@@ -1655,20 +1670,15 @@ class RCEWrapper extends React.Component {
|
|
|
1655
1670
|
this.mceInstance().hide();
|
|
1656
1671
|
}
|
|
1657
1672
|
}
|
|
1658
|
-
|
|
1659
|
-
/**
|
|
1660
|
-
* Used for reseting the value during tests
|
|
1661
|
-
*/
|
|
1662
|
-
|
|
1663
1673
|
renderHtmlEditor() {
|
|
1664
1674
|
// the div keeps the editor from collapsing while the code editor is downloaded
|
|
1665
1675
|
return /*#__PURE__*/React.createElement(Suspense, {
|
|
1666
1676
|
fallback: /*#__PURE__*/React.createElement("div", {
|
|
1667
1677
|
style: {
|
|
1668
1678
|
height: this.state.height,
|
|
1669
|
-
display:
|
|
1670
|
-
justifyContent:
|
|
1671
|
-
alignItems:
|
|
1679
|
+
display: "flex",
|
|
1680
|
+
justifyContent: "center",
|
|
1681
|
+
alignItems: "center"
|
|
1672
1682
|
}
|
|
1673
1683
|
}, /*#__PURE__*/React.createElement(Spinner, {
|
|
1674
1684
|
renderTitle: renderLoading,
|
|
@@ -1704,11 +1714,16 @@ class RCEWrapper extends React.Component {
|
|
|
1704
1714
|
ref: this._editorPlaceholderRef,
|
|
1705
1715
|
style: {
|
|
1706
1716
|
height: `${this.props.editorOptions.height}px`,
|
|
1707
|
-
border:
|
|
1717
|
+
border: "1px solid grey"
|
|
1708
1718
|
}
|
|
1709
1719
|
});
|
|
1710
1720
|
}
|
|
1711
|
-
const
|
|
1721
|
+
const statusBarOptions = {
|
|
1722
|
+
aiTextTools: this.props.ai_text_tools,
|
|
1723
|
+
isDesktop: tinymce.Env.deviceType.isDesktop(),
|
|
1724
|
+
a11yResizers: !!this.props.features?.rce_a11y_resize
|
|
1725
|
+
};
|
|
1726
|
+
const statusBarFeatures = getStatusBarFeaturesForVariant(this.variant, statusBarOptions);
|
|
1712
1727
|
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("style", null, this.style.css), /*#__PURE__*/React.createElement(StoreProvider, {
|
|
1713
1728
|
jwt: this.props.trayProps?.jwt,
|
|
1714
1729
|
refreshToken: this.props.trayProps?.refreshToken,
|
|
@@ -1725,8 +1740,8 @@ class RCEWrapper extends React.Component {
|
|
|
1725
1740
|
// @ts-expect-error
|
|
1726
1741
|
,
|
|
1727
1742
|
ref: this._elementRef,
|
|
1728
|
-
style: this.variant ===
|
|
1729
|
-
marginBottom:
|
|
1743
|
+
style: this.variant === "full" ? {
|
|
1744
|
+
marginBottom: ".5rem"
|
|
1730
1745
|
} : undefined,
|
|
1731
1746
|
onFocus: this.handleFocusRCE,
|
|
1732
1747
|
onBlur: this.handleBlurRCE
|
|
@@ -1734,7 +1749,7 @@ class RCEWrapper extends React.Component {
|
|
|
1734
1749
|
id: `show-on-focus-btn-${this.id}`,
|
|
1735
1750
|
onClick: this.openKBShortcutModal,
|
|
1736
1751
|
margin: "xx-small",
|
|
1737
|
-
screenReaderLabel: formatMessage(
|
|
1752
|
+
screenReaderLabel: formatMessage("View keyboard shortcuts")
|
|
1738
1753
|
// @ts-expect-error
|
|
1739
1754
|
,
|
|
1740
1755
|
ref: el => this._showOnFocusButton = el
|
|
@@ -1746,7 +1761,7 @@ class RCEWrapper extends React.Component {
|
|
|
1746
1761
|
afterDismiss: this.removeAlert
|
|
1747
1762
|
}), this.state.editorView === PRETTY_HTML_EDITOR_VIEW && this.renderHtmlEditor(), /*#__PURE__*/React.createElement("div", {
|
|
1748
1763
|
style: {
|
|
1749
|
-
display: this.state.editorView === PRETTY_HTML_EDITOR_VIEW ?
|
|
1764
|
+
display: this.state.editorView === PRETTY_HTML_EDITOR_VIEW ? "none" : "block"
|
|
1750
1765
|
}
|
|
1751
1766
|
}, /*#__PURE__*/React.createElement(Editor, {
|
|
1752
1767
|
id: mceProps.textareaId,
|
|
@@ -1796,7 +1811,7 @@ class RCEWrapper extends React.Component {
|
|
|
1796
1811
|
disabledPlugins: this.pluginsToExclude,
|
|
1797
1812
|
features: statusBarFeatures,
|
|
1798
1813
|
onAI: this.handleAIClick
|
|
1799
|
-
}), this.
|
|
1814
|
+
}), this._effectiveContainingContext && /*#__PURE__*/React.createElement(CanvasContentTray, Object.assign({
|
|
1800
1815
|
mountNode: instuiPopupMountNodeFn,
|
|
1801
1816
|
key: this.id,
|
|
1802
1817
|
canvasOrigin: this.getCanvasUrl(),
|
|
@@ -1805,7 +1820,9 @@ class RCEWrapper extends React.Component {
|
|
|
1805
1820
|
onTrayClosing: this.handleContentTrayClosing,
|
|
1806
1821
|
use_rce_icon_maker: this.props.use_rce_icon_maker
|
|
1807
1822
|
}, trayProps, {
|
|
1823
|
+
containingContext: this._effectiveContainingContext
|
|
1808
1824
|
// @ts-expect-error
|
|
1825
|
+
,
|
|
1809
1826
|
storeProps: storeProps
|
|
1810
1827
|
})), /*#__PURE__*/React.createElement(KeyboardShortcutModal, {
|
|
1811
1828
|
onExited: this.KBShortcutModalExited,
|
|
@@ -1837,7 +1854,7 @@ class RCEWrapper extends React.Component {
|
|
|
1837
1854
|
})) : null, /*#__PURE__*/React.createElement(Alert, {
|
|
1838
1855
|
screenReaderOnly: true,
|
|
1839
1856
|
liveRegion: this.props.liveRegion
|
|
1840
|
-
}, this.state.announcement));
|
|
1857
|
+
}, this.state.announcement || null));
|
|
1841
1858
|
}));
|
|
1842
1859
|
}
|
|
1843
1860
|
}
|
|
@@ -1845,8 +1862,7 @@ RCEWrapper.propTypes = {
|
|
|
1845
1862
|
ai_text_tools: _pt.bool,
|
|
1846
1863
|
autosave: _pt.shape({
|
|
1847
1864
|
enabled: _pt.bool,
|
|
1848
|
-
maxAge: _pt.number
|
|
1849
|
-
interval: _pt.number
|
|
1865
|
+
maxAge: _pt.number
|
|
1850
1866
|
}),
|
|
1851
1867
|
canvasOrigin: _pt.string,
|
|
1852
1868
|
defaultContent: _pt.string,
|
|
@@ -1882,8 +1898,8 @@ RCEWrapper.defaultProps = {
|
|
|
1882
1898
|
maxInitRenderedRCEs: -1,
|
|
1883
1899
|
features: {},
|
|
1884
1900
|
timezone: Intl?.DateTimeFormat()?.resolvedOptions()?.timeZone,
|
|
1885
|
-
canvasOrigin:
|
|
1886
|
-
variant:
|
|
1901
|
+
canvasOrigin: "",
|
|
1902
|
+
variant: "full"
|
|
1887
1903
|
};
|
|
1888
1904
|
RCEWrapper.skinCssInjected = false;
|
|
1889
1905
|
export default RCEWrapper;
|