@instructure/canvas-rce 7.3.0 → 8.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +79 -0
- package/{es/rce/plugins/shared/ai_tools/index.js → __mocks__/@instructure/ui-media-player/_mockUiMediaPlayer.js} +4 -4
- package/__tests__/common/mimeClass.test.js +25 -1
- package/__tests__/rcs/api.test.js +280 -251
- package/es/canvasFileBrowser/FileBrowser.d.ts +2 -2
- package/es/canvasFileBrowser/FileBrowser.js +8 -7
- package/es/common/mimeClass.js +3 -1
- package/es/defaultTinymceConfig.js +47 -49
- package/es/enhance-user-content/doc_previews.js +5 -0
- package/es/enhance-user-content/enhance_user_content.js +6 -8
- package/es/enhance-user-content/index.d.ts +3 -1
- package/es/enhance-user-content/index.js +3 -1
- package/es/enhance-user-content/instructure_helper.js +1 -0
- package/es/enhance-user-content/youtube_overlay.js +18 -0
- package/es/getThemeVars.d.ts +1 -1
- package/es/getThemeVars.js +23 -26
- package/es/rce/AlertMessageArea.d.ts +2 -2
- package/es/rce/AlertMessageArea.js +3 -3
- package/es/rce/KeyboardShortcutModal.js +2 -2
- package/es/rce/RCE.d.ts +9 -0
- package/es/rce/RCE.js +4 -0
- package/es/rce/RCEGlobals.d.ts +2 -0
- package/es/rce/RCEGlobals.js +1 -0
- package/es/rce/RCEVariants.d.ts +1 -2
- package/es/rce/RCEVariants.js +1 -2
- package/es/rce/RCEWrapper.d.ts +16 -26
- package/es/rce/RCEWrapper.js +227 -271
- package/es/rce/RCEWrapper.utils.d.ts +1 -1
- package/es/rce/RCEWrapperProps.d.ts +2 -1
- package/es/rce/RCEWrapperProps.js +2 -1
- package/es/rce/StatusBar.d.ts +0 -1
- package/es/rce/StatusBar.js +3 -28
- package/es/rce/plugins/instructure_equation/EquationEditorModal/advancedOnlySyntax.d.ts +2 -1
- package/es/rce/plugins/instructure_equation/EquationEditorModal/advancedOnlySyntax.js +3 -1
- package/es/rce/plugins/instructure_equation/EquationEditorModal/index.d.ts +1 -0
- package/es/rce/plugins/instructure_equation/EquationEditorModal/index.js +12 -2
- package/es/rce/plugins/instructure_icon_maker/components/CreateIconMakerForm/ImageSection/ImageOptions.js +2 -2
- package/es/rce/plugins/instructure_icon_maker/components/CreateIconMakerForm/ImageSection/ImageSection.js +3 -3
- package/es/rce/plugins/instructure_icon_maker/svg/constants.d.ts +20 -5
- package/es/rce/plugins/instructure_icon_maker/svg/utils.d.ts +1 -1
- package/es/rce/plugins/instructure_icon_maker/utils/IconMakerFormHasChanges.js +2 -2
- package/es/rce/plugins/instructure_image/ImageEmbedOptions.d.ts +0 -2
- package/es/rce/plugins/instructure_image/ImageEmbedOptions.js +2 -9
- package/es/rce/plugins/instructure_paste/plugin.js +18 -12
- package/es/rce/plugins/instructure_rce_external_tools/components/ExternalToolDialog/ExternalToolDialogModal.d.ts +1 -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_rce_external_tools/components/util/ToolLaunchIframe.d.ts +4 -0
- package/es/rce/plugins/instructure_rce_external_tools/components/util/ToolLaunchIframe.js +4 -0
- package/es/rce/plugins/instructure_record/AudioOptionsTray/TrayController.d.ts +11 -2
- package/es/rce/plugins/instructure_record/AudioOptionsTray/TrayController.js +92 -10
- package/es/rce/plugins/instructure_record/AudioOptionsTray/index.d.ts +13 -1
- package/es/rce/plugins/instructure_record/AudioOptionsTray/index.js +216 -24
- package/es/rce/plugins/instructure_record/MediaPanel/index.js +16 -5
- package/es/rce/plugins/instructure_record/VideoOptionsTray/TrayController.d.ts +14 -13
- package/es/rce/plugins/instructure_record/VideoOptionsTray/TrayController.js +110 -39
- package/es/rce/plugins/instructure_record/VideoOptionsTray/index.d.ts +11 -1
- package/es/rce/plugins/instructure_record/VideoOptionsTray/index.js +242 -67
- package/es/rce/plugins/instructure_record/clickCallback.js +19 -4
- package/es/rce/plugins/instructure_record/mediaTranslations.js +1 -1
- package/es/rce/plugins/instructure_record/playerLayoutOptions.d.ts +25 -0
- package/es/rce/plugins/instructure_record/playerLayoutOptions.js +91 -0
- package/es/rce/plugins/instructure_record/plugin.js +2 -5
- package/es/rce/plugins/instructure_record/utils.d.ts +3 -0
- package/es/rce/plugins/instructure_record/utils.js +31 -0
- package/es/rce/plugins/instructure_studio_media_options/plugin.js +82 -24
- package/es/rce/plugins/instructure_wordcount/components/WordCountModal.js +1 -0
- package/es/rce/plugins/shared/ContentSelection.d.ts +6 -1
- package/es/rce/plugins/shared/ContentSelection.js +15 -6
- package/es/rce/plugins/shared/DimensionsInput/DimensionInput.js +1 -2
- package/es/rce/plugins/shared/DimensionsInput/index.js +11 -12
- package/es/rce/plugins/shared/DimensionsInput/useDimensionsState.d.ts +1 -1
- package/es/rce/plugins/shared/DimensionsInput/useDimensionsState.js +4 -3
- package/es/rce/plugins/shared/StudioLtiSupportUtils.d.ts +27 -5
- package/es/rce/plugins/shared/StudioLtiSupportUtils.js +94 -9
- package/es/rce/plugins/shared/Upload/UploadFile.js +1 -8
- package/es/rce/style.d.ts +2 -1
- package/es/rce/style.js +4 -2
- package/es/rcs/api.d.ts +5 -10
- package/es/rcs/api.js +15 -21
- package/es/rcs/fake.d.ts +1 -7
- package/es/rcs/fake.js +1 -47
- package/es/sidebar/actions/media.d.ts +19 -6
- package/es/sidebar/actions/media.js +17 -4
- package/es/sidebar/actions/upload.d.ts +3 -3
- package/es/sidebar/actions/upload.js +9 -9
- package/es/sidebar/containers/Sidebar.js +0 -2
- package/es/sidebar/containers/sidebarHandlers.d.ts +2 -4
- package/es/sidebar/containers/sidebarHandlers.js +2 -5
- package/es/sidebar/reducers/index.d.ts +0 -1
- package/es/sidebar/reducers/index.js +0 -2
- package/es/sidebar/store/initialState.d.ts +0 -1
- package/es/sidebar/store/initialState.js +0 -5
- package/es/translations/locales/ar.js +77 -77
- package/es/translations/locales/ca.js +77 -77
- package/es/translations/locales/cy.js +77 -77
- package/es/translations/locales/da-x-k12.js +77 -77
- package/es/translations/locales/da.js +77 -77
- package/es/translations/locales/de.js +77 -77
- package/es/translations/locales/el.js +0 -9
- package/es/translations/locales/en-AU-x-unimelb.js +77 -77
- package/es/translations/locales/en-GB-x-ukhe.js +77 -77
- package/es/translations/locales/en.js +67 -79
- package/es/translations/locales/en_AU.js +77 -77
- package/es/translations/locales/en_CA.js +77 -77
- package/es/translations/locales/en_CY.js +77 -77
- package/es/translations/locales/en_GB.js +77 -77
- package/es/translations/locales/es.js +77 -77
- package/es/translations/locales/es_ES.js +77 -77
- package/es/translations/locales/fa_IR.js +0 -9
- package/es/translations/locales/fi.js +77 -77
- package/es/translations/locales/fr.js +77 -77
- package/es/translations/locales/fr_CA.js +77 -77
- package/es/translations/locales/ga.js +77 -77
- package/es/translations/locales/he.js +0 -9
- package/es/translations/locales/hi.js +77 -77
- package/es/translations/locales/ht.js +77 -77
- package/es/translations/locales/hu.js +0 -36
- package/es/translations/locales/hy.js +0 -9
- package/es/translations/locales/id.js +77 -77
- package/es/translations/locales/is.js +77 -77
- package/es/translations/locales/it.js +77 -77
- package/es/translations/locales/ja.js +77 -77
- package/es/translations/locales/ko.js +2455 -133
- package/es/translations/locales/mi.js +77 -77
- package/es/translations/locales/ms.js +77 -77
- package/es/translations/locales/nb-x-k12.js +77 -77
- package/es/translations/locales/nb.js +77 -77
- package/es/translations/locales/nl.js +78 -78
- package/es/translations/locales/nn.js +0 -36
- package/es/translations/locales/pl.js +77 -77
- package/es/translations/locales/pt.js +77 -77
- package/es/translations/locales/pt_BR.js +77 -77
- package/es/translations/locales/ru.js +77 -77
- package/es/translations/locales/sl.js +77 -77
- package/es/translations/locales/sv-x-k12.js +77 -77
- package/es/translations/locales/sv.js +77 -77
- package/es/translations/locales/th.js +77 -77
- package/es/translations/locales/tr.js +1962 -18
- package/es/translations/locales/uk_UA.js +0 -9
- package/es/translations/locales/vi.js +77 -77
- package/es/translations/locales/zh-Hans.js +77 -77
- package/es/translations/locales/zh-Hant.js +77 -77
- package/es/translations/locales/zh.js +77 -77
- package/es/translations/locales/zh_HK.js +77 -77
- package/eslint.config.js +16 -147
- package/jest/jest-setup.js +1 -0
- package/jest.config.js +2 -0
- package/oxlint.json +84 -0
- package/package.json +86 -62
- package/tsconfig.json +3 -2
- package/es/rce/plugins/shared/ai_tools/AIResponseModal.d.ts +0 -10
- package/es/rce/plugins/shared/ai_tools/AIResponseModal.js +0 -67
- package/es/rce/plugins/shared/ai_tools/AIToolsTray.d.ts +0 -18
- package/es/rce/plugins/shared/ai_tools/AIToolsTray.js +0 -489
- package/es/rce/plugins/shared/ai_tools/aiicons.d.ts +0 -7
- package/es/rce/plugins/shared/ai_tools/aiicons.js +0 -60
- package/es/rce/plugins/shared/ai_tools/index.d.ts +0 -3
- package/es/sidebar/actions/flickr.d.ts +0 -20
- package/es/sidebar/actions/flickr.js +0 -60
- package/es/sidebar/reducers/flickr.d.ts +0 -1
- package/es/sidebar/reducers/flickr.js +0 -49
package/es/rce/RCEWrapper.js
CHANGED
|
@@ -17,63 +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
|
|
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 { uniqBy } from 'es-toolkit/compat';
|
|
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
|
-
import { normalizeContainingContext } from
|
|
69
|
-
const RestoreAutoSaveModal = /*#__PURE__*/React.lazy(() => import(
|
|
70
|
-
const RceHtmlEditor = /*#__PURE__*/React.lazy(() => import(
|
|
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'));
|
|
71
71
|
const ASYNC_FOCUS_TIMEOUT = 250;
|
|
72
|
-
const DEFAULT_RCE_HEIGHT =
|
|
72
|
+
const DEFAULT_RCE_HEIGHT = '400px';
|
|
73
73
|
function addKebabIcon(editor) {
|
|
74
74
|
// This has to be done here instead of of in plugins/instructure-ui-icons/plugin.ts
|
|
75
75
|
// presumably because the toolbar gets created before that plugin is loaded?
|
|
76
|
-
editor.ui.registry.addIcon(
|
|
76
|
+
editor.ui.registry.addIcon('more-drawer', IconMoreSolid.src);
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
// Get oxide the default skin injected into the DOM before the overrides loaded by themeable
|
|
@@ -81,8 +81,8 @@ let inserted = false;
|
|
|
81
81
|
function injectTinySkin() {
|
|
82
82
|
if (inserted) return;
|
|
83
83
|
inserted = true;
|
|
84
|
-
const style = document.createElement(
|
|
85
|
-
style.setAttribute(
|
|
84
|
+
const style = document.createElement('style');
|
|
85
|
+
style.setAttribute('data-skin', 'tiny oxide skin');
|
|
86
86
|
style.appendChild(document.createTextNode(skinCSS));
|
|
87
87
|
// there's CSS from discussions that turns the instui Selectors bold
|
|
88
88
|
// and in classic quizzes that also mucks with padding
|
|
@@ -90,9 +90,9 @@ function injectTinySkin() {
|
|
|
90
90
|
#discussion-edit-view .rce-wrapper input[readonly] {font-weight: normal;}
|
|
91
91
|
#quiz_edit_wrapper .rce-wrapper input[readonly] {font-weight: normal; padding-left: .75rem;}
|
|
92
92
|
`));
|
|
93
|
-
const beforeMe = document.head.querySelector(
|
|
93
|
+
const beforeMe = document.head.querySelector('style[data-glamor]') ||
|
|
94
94
|
// find instui's themeable stylesheet
|
|
95
|
-
document.head.querySelector(
|
|
95
|
+
document.head.querySelector('style') ||
|
|
96
96
|
// find any stylesheet
|
|
97
97
|
document.head.firstElementChild;
|
|
98
98
|
document.head.insertBefore(style, beforeMe);
|
|
@@ -105,7 +105,7 @@ export function storageAvailable() {
|
|
|
105
105
|
let storage = null;
|
|
106
106
|
try {
|
|
107
107
|
storage = window.localStorage;
|
|
108
|
-
const x =
|
|
108
|
+
const x = '__storage_test__';
|
|
109
109
|
storage.setItem(x, x);
|
|
110
110
|
storage.removeItem(x);
|
|
111
111
|
return true;
|
|
@@ -117,15 +117,15 @@ export function storageAvailable() {
|
|
|
117
117
|
e.code === 1014 ||
|
|
118
118
|
// test name field too, because code might not be present
|
|
119
119
|
// everything except Firefox
|
|
120
|
-
e.name ===
|
|
120
|
+
e.name === 'QuotaExceededError' ||
|
|
121
121
|
// Firefox
|
|
122
|
-
e.name ===
|
|
122
|
+
e.name === 'NS_ERROR_DOM_QUOTA_REACHED') &&
|
|
123
123
|
// acknowledge QuotaExceededError only if there's something already stored
|
|
124
124
|
storage && storage.length !== 0;
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
127
|
function renderLoading() {
|
|
128
|
-
return formatMessage(
|
|
128
|
+
return formatMessage('Loading');
|
|
129
129
|
}
|
|
130
130
|
let alertIdValue = 0;
|
|
131
131
|
class RCEWrapper extends React.Component {
|
|
@@ -144,7 +144,6 @@ class RCEWrapper extends React.Component {
|
|
|
144
144
|
this._statusBarId = void 0;
|
|
145
145
|
this._textareaEl = void 0;
|
|
146
146
|
this._effectiveContainingContext = void 0;
|
|
147
|
-
this.AIToolsTray = void 0;
|
|
148
147
|
this.editor = void 0;
|
|
149
148
|
this.initialContent = void 0;
|
|
150
149
|
this.intersectionObserver = void 0;
|
|
@@ -199,7 +198,7 @@ class RCEWrapper extends React.Component {
|
|
|
199
198
|
this.setState(newState);
|
|
200
199
|
this.checkAccessibility();
|
|
201
200
|
if (newView === PRETTY_HTML_EDITOR_VIEW || newView === RAW_HTML_EDITOR_VIEW) {
|
|
202
|
-
this.storage?.setItem?.(
|
|
201
|
+
this.storage?.setItem?.('rce.htmleditor', newView);
|
|
203
202
|
}
|
|
204
203
|
|
|
205
204
|
// Emit view change event
|
|
@@ -216,7 +215,7 @@ class RCEWrapper extends React.Component {
|
|
|
216
215
|
if (document[FS_ELEMENT]) {
|
|
217
216
|
// @ts-expect-error
|
|
218
217
|
this.resizeObserver.observe(document[FS_ELEMENT]);
|
|
219
|
-
window.visualViewport?.addEventListener(
|
|
218
|
+
window.visualViewport?.addEventListener('resize', this._handleFullscreenResize);
|
|
220
219
|
this._handleFullscreenResize();
|
|
221
220
|
// @ts-expect-error
|
|
222
221
|
this._focusRegion = FocusRegionManager.activateRegion(
|
|
@@ -227,7 +226,7 @@ class RCEWrapper extends React.Component {
|
|
|
227
226
|
} else {
|
|
228
227
|
event.target.removeEventListener(FS_CHANGEEVENT, this._onFullscreenChange);
|
|
229
228
|
this.resizeObserver.unobserve(event.target);
|
|
230
|
-
window.visualViewport?.removeEventListener(
|
|
229
|
+
window.visualViewport?.removeEventListener('resize', this._handleFullscreenResize);
|
|
231
230
|
this._setHeight(this.state.fullscreenState.prevHeight);
|
|
232
231
|
if (this._focusRegion) {
|
|
233
232
|
FocusRegionManager.blurRegion(event.target, this._focusRegion.id);
|
|
@@ -260,35 +259,35 @@ class RCEWrapper extends React.Component {
|
|
|
260
259
|
// what we've got for now.
|
|
261
260
|
const ifr = this.iframe;
|
|
262
261
|
if (ifr?.parentElement) {
|
|
263
|
-
ifr.parentElement.classList.add(
|
|
262
|
+
ifr.parentElement.classList.add('active');
|
|
264
263
|
}
|
|
265
264
|
this.handleFocus();
|
|
266
265
|
};
|
|
267
266
|
this.handleBlurEditor = event => {
|
|
268
267
|
const ifr = this.iframe;
|
|
269
268
|
if (ifr?.parentElement) {
|
|
270
|
-
ifr.parentElement.classList.remove(
|
|
269
|
+
ifr.parentElement.classList.remove('active');
|
|
271
270
|
}
|
|
272
271
|
this.handleBlur(event);
|
|
273
272
|
};
|
|
274
273
|
this.handleKey = event => {
|
|
275
|
-
if (event.code ===
|
|
274
|
+
if (event.code === 'F9' && event.altKey) {
|
|
276
275
|
event.preventDefault();
|
|
277
276
|
event.stopPropagation();
|
|
278
277
|
// @ts-expect-error
|
|
279
278
|
focusFirstMenuButton(this._elementRef.current);
|
|
280
|
-
} else if (event.code ===
|
|
279
|
+
} else if (event.code === 'F10' && event.altKey) {
|
|
281
280
|
event.preventDefault();
|
|
282
281
|
event.stopPropagation();
|
|
283
282
|
// @ts-expect-error
|
|
284
283
|
focusToolbar(this._elementRef.current);
|
|
285
|
-
} else if (event.code ===
|
|
284
|
+
} else if (event.code === 'F8' && event.altKey) {
|
|
286
285
|
event.preventDefault();
|
|
287
286
|
event.stopPropagation();
|
|
288
287
|
this.openKBShortcutModal();
|
|
289
|
-
} else if (event.code ===
|
|
288
|
+
} else if (event.code === 'Escape') {
|
|
290
289
|
bridge.hideTrays();
|
|
291
|
-
} else if ([
|
|
290
|
+
} else if (['n', 'N', 'd', 'D'].indexOf(event.key) !== -1) {
|
|
292
291
|
// Prevent key events from bubbling up on touch screen device
|
|
293
292
|
event.stopPropagation();
|
|
294
293
|
}
|
|
@@ -317,32 +316,64 @@ class RCEWrapper extends React.Component {
|
|
|
317
316
|
// @ts-expect-error
|
|
318
317
|
textarea.value = this.getCode();
|
|
319
318
|
textarea.style.height = this.state.height;
|
|
320
|
-
textarea.removeAttribute(
|
|
321
|
-
if (document.body.classList.contains(
|
|
319
|
+
textarea.removeAttribute('aria-hidden');
|
|
320
|
+
if (document.body.classList.contains('Underline-All-Links__enabled')) {
|
|
322
321
|
if (this.iframe?.contentDocument) {
|
|
323
|
-
this.iframe.contentDocument.body.classList.add(
|
|
322
|
+
this.iframe.contentDocument.body.classList.add('Underline-All-Links__enabled');
|
|
324
323
|
}
|
|
325
324
|
}
|
|
326
|
-
editor.on(
|
|
325
|
+
editor.on('wordCountUpdate', this.onWordCountUpdate);
|
|
327
326
|
// add an aria-label to the application div that wraps RCE
|
|
328
327
|
// and change role from "application" to "document" to ensure
|
|
329
328
|
// the editor gets properly picked up by screen readers
|
|
330
329
|
const tinyapp = document.querySelector('.tox-tinymce[role="application"]');
|
|
331
330
|
if (tinyapp) {
|
|
332
|
-
tinyapp.setAttribute(
|
|
333
|
-
tinyapp.setAttribute(
|
|
334
|
-
tinyapp.setAttribute(
|
|
331
|
+
tinyapp.setAttribute('aria-label', formatMessage('Rich Content Editor'));
|
|
332
|
+
tinyapp.setAttribute('role', 'document');
|
|
333
|
+
tinyapp.setAttribute('tabIndex', '-1');
|
|
334
|
+
|
|
335
|
+
// tinyMCE browser detection is wrong for Edge tinymce.Env.browser.isEdge
|
|
336
|
+
const isEdge = /edg/i.test(navigator.userAgent);
|
|
337
|
+
if (isEdge) {
|
|
338
|
+
// Edge translation service breaks the editor
|
|
339
|
+
tinyapp.setAttribute('translate', 'no');
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// remove role="aplication" attribute from the iframe body
|
|
344
|
+
// tinymce adds this when the editor is wrapped in an iframe
|
|
345
|
+
// which makes RCE input fields inaccessible to screen readers
|
|
346
|
+
const iframe = tinyapp?.querySelector('iframe');
|
|
347
|
+
const body = iframe?.contentDocument?.body;
|
|
348
|
+
if (body) {
|
|
349
|
+
const observer = new MutationObserver(() => {
|
|
350
|
+
try {
|
|
351
|
+
if (body && body.getAttribute('role') === 'application') {
|
|
352
|
+
body.removeAttribute('role');
|
|
353
|
+
}
|
|
354
|
+
} catch (_) {
|
|
355
|
+
/* pass */
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
observer.observe(body, {
|
|
359
|
+
attributes: true,
|
|
360
|
+
childList: false,
|
|
361
|
+
subtree: false
|
|
362
|
+
});
|
|
363
|
+
body.setAttribute('data-role-checked', 'true'); // to trigger observer
|
|
364
|
+
|
|
365
|
+
setTimeout(() => observer.disconnect(), 10000);
|
|
335
366
|
}
|
|
336
367
|
|
|
337
368
|
// Probably should do this in tinymce.scss, but we only want it in new rce
|
|
338
|
-
textarea.style.resize =
|
|
339
|
-
editor.on(
|
|
340
|
-
editor.on(
|
|
369
|
+
textarea.style.resize = 'none';
|
|
370
|
+
editor.on('keydown', this.handleKey);
|
|
371
|
+
editor.on('FullscreenStateChanged', this._onFullscreenChange);
|
|
341
372
|
// This propagates click events on the editor out of the iframe to the parent
|
|
342
373
|
// document. We need this so that click events get captured properly by instui
|
|
343
374
|
// focus-trapping components, so they properly ignore trapping focus on click.
|
|
344
|
-
editor.on(
|
|
345
|
-
editor.on(
|
|
375
|
+
editor.on('click', () => window.document.body.click(), true);
|
|
376
|
+
editor.on('Cut Change input Undo Redo', debounce(this.handleInputChange, 1000));
|
|
346
377
|
initScreenreaderOnFormat(editor);
|
|
347
378
|
this.announceContextToolbars(editor);
|
|
348
379
|
if (this.isAutoSaving) {
|
|
@@ -354,12 +385,12 @@ class RCEWrapper extends React.Component {
|
|
|
354
385
|
|
|
355
386
|
// readonly should have been handled via the init property passed
|
|
356
387
|
// to <Editor>, but it's not.
|
|
357
|
-
editor.mode.set(this.props.readOnly ?
|
|
388
|
+
editor.mode.set(this.props.readOnly ? 'readonly' : 'design');
|
|
358
389
|
|
|
359
390
|
// Not using iframe_aria_text because compatibility issues.
|
|
360
391
|
// Not using iframe_attrs because library overwriting.
|
|
361
392
|
if (this.iframe) {
|
|
362
|
-
this.iframe.setAttribute(
|
|
393
|
+
this.iframe.setAttribute('title', formatMessage('Rich Text Area. Press {OSKey}+F8 for Rich Content Editor shortcuts.', {
|
|
363
394
|
OSKey: determineOSDependentKey()
|
|
364
395
|
}));
|
|
365
396
|
}
|
|
@@ -372,8 +403,8 @@ class RCEWrapper extends React.Component {
|
|
|
372
403
|
|
|
373
404
|
// cleans up highlight artifacts from findreplace plugin
|
|
374
405
|
if (this.getRequiredFeatureStatuses().rce_find_replace) {
|
|
375
|
-
editor.on(
|
|
376
|
-
if (editor?.dom?.doc?.getElementsByClassName?.(
|
|
406
|
+
editor.on('undo redo', _e => {
|
|
407
|
+
if (editor?.dom?.doc?.getElementsByClassName?.('mce-match-marker')?.length > 0) {
|
|
377
408
|
editor.plugins?.searchreplace?.done();
|
|
378
409
|
}
|
|
379
410
|
});
|
|
@@ -392,7 +423,7 @@ class RCEWrapper extends React.Component {
|
|
|
392
423
|
// This workaround removes attribute, thusly causing navigation to work correctly again.
|
|
393
424
|
// For the correct solution, Keying.config should have { selector: '.tox-toolbar__group' }
|
|
394
425
|
// in https://github.com/tinymce/tinymce/blob/develop/modules/alloy/src/main/ts/ephox/alloy/ui/schema/SplitSlidingToolbarSchema.ts
|
|
395
|
-
this._elementRef.current?.querySelectorAll(
|
|
426
|
+
this._elementRef.current?.querySelectorAll('.tox-toolbar-overlord button[data-alloy-tabstop]').forEach(it => it.removeAttribute('data-alloy-tabstop'));
|
|
396
427
|
};
|
|
397
428
|
/**
|
|
398
429
|
* Sets up selection saving and restoration logic.
|
|
@@ -419,7 +450,7 @@ class RCEWrapper extends React.Component {
|
|
|
419
450
|
selectionWasReset = false;
|
|
420
451
|
}
|
|
421
452
|
};
|
|
422
|
-
editor.on(
|
|
453
|
+
editor.on('blur', () => {
|
|
423
454
|
editorHasFocus = false;
|
|
424
455
|
selectionWasReset = false;
|
|
425
456
|
if (!this.editor) return;
|
|
@@ -428,7 +459,7 @@ class RCEWrapper extends React.Component {
|
|
|
428
459
|
isForward: this.editor.selection.isForward()
|
|
429
460
|
};
|
|
430
461
|
});
|
|
431
|
-
editor.on(
|
|
462
|
+
editor.on('focus', () => {
|
|
432
463
|
// We need to restore the selection when the editor regains focus because sometimes the editor regains
|
|
433
464
|
// focus without the user setting the selection themselves (such as when they interact with the toolbar)
|
|
434
465
|
// and if we didn't, we would end up saving the reset selection before a user managed to actually insert
|
|
@@ -437,7 +468,7 @@ class RCEWrapper extends React.Component {
|
|
|
437
468
|
editorHasFocus = true;
|
|
438
469
|
selectionWasReset = false;
|
|
439
470
|
});
|
|
440
|
-
editor.on(
|
|
471
|
+
editor.on('SelectionChange', () => {
|
|
441
472
|
if (editorHasFocus) {
|
|
442
473
|
// We don't care if a selection reset occurs when the editor has focus, the user probably intended that
|
|
443
474
|
// At least they will see the effect
|
|
@@ -447,14 +478,14 @@ class RCEWrapper extends React.Component {
|
|
|
447
478
|
const selection = this.editor.selection.normalize();
|
|
448
479
|
|
|
449
480
|
// Detect a browser-reset selection (e.g. From invoking the Find command)
|
|
450
|
-
if (selection.startContainer?.nodeName ===
|
|
481
|
+
if (selection.startContainer?.nodeName === 'BODY' && selection.startContainer === selection.endContainer && selection.startOffset === 0 && selection.endOffset === 0) {
|
|
451
482
|
selectionWasReset = true;
|
|
452
483
|
}
|
|
453
484
|
});
|
|
454
|
-
editor.on(
|
|
485
|
+
editor.on('BeforeExecCommand', () => {
|
|
455
486
|
restoreSelectionIfNecessary();
|
|
456
487
|
});
|
|
457
|
-
editor.on(
|
|
488
|
+
editor.on('ExecCommand', (/* event */
|
|
458
489
|
) => {
|
|
459
490
|
if (!this.editor) return;
|
|
460
491
|
// Commands may have modified the selection, we need to recapture it
|
|
@@ -469,10 +500,10 @@ class RCEWrapper extends React.Component {
|
|
|
469
500
|
/* ********** autosave support *************** */
|
|
470
501
|
this.initAutoSave = editor => {
|
|
471
502
|
var _this$props$userCache;
|
|
472
|
-
this.storage = new EncryptedStorage((_this$props$userCache = this.props.userCacheKey) !== null && _this$props$userCache !== void 0 ? _this$props$userCache :
|
|
503
|
+
this.storage = new EncryptedStorage((_this$props$userCache = this.props.userCacheKey) !== null && _this$props$userCache !== void 0 ? _this$props$userCache : '');
|
|
473
504
|
if (this.storage) {
|
|
474
|
-
editor.on(
|
|
475
|
-
editor.on(
|
|
505
|
+
editor.on('change Undo Redo', this.doAutoSave);
|
|
506
|
+
editor.on('blur', this.doAutoSave);
|
|
476
507
|
this.cleanupAutoSave();
|
|
477
508
|
try {
|
|
478
509
|
const autosaved = this.getAutoSaved(this.autoSaveKey);
|
|
@@ -487,7 +518,6 @@ class RCEWrapper extends React.Component {
|
|
|
487
518
|
if (autosavedContent !== editorContent) {
|
|
488
519
|
this.setState({
|
|
489
520
|
confirmAutoSave: true,
|
|
490
|
-
// @ts-expect-error
|
|
491
521
|
autoSavedContent: patchAutosavedContent(autosaved.content)
|
|
492
522
|
});
|
|
493
523
|
} else {
|
|
@@ -497,7 +527,7 @@ class RCEWrapper extends React.Component {
|
|
|
497
527
|
} catch (ex) {
|
|
498
528
|
// log and ignore
|
|
499
529
|
|
|
500
|
-
console.error(
|
|
530
|
+
console.error('Failed initializing rce autosave', ex);
|
|
501
531
|
}
|
|
502
532
|
}
|
|
503
533
|
};
|
|
@@ -552,7 +582,7 @@ class RCEWrapper extends React.Component {
|
|
|
552
582
|
this.cleanupAutoSave(true);
|
|
553
583
|
this.doAutoSave(e, true);
|
|
554
584
|
} else {
|
|
555
|
-
console.error(
|
|
585
|
+
console.error('Autosave failed:', ex);
|
|
556
586
|
}
|
|
557
587
|
}
|
|
558
588
|
}
|
|
@@ -560,7 +590,7 @@ class RCEWrapper extends React.Component {
|
|
|
560
590
|
/* *********** end autosave support *************** */
|
|
561
591
|
this.onWordCountUpdate = e => {
|
|
562
592
|
if (!this.editor) return;
|
|
563
|
-
const shouldIgnore = countShouldIgnore(this.editor,
|
|
593
|
+
const shouldIgnore = countShouldIgnore(this.editor, 'body', 'words');
|
|
564
594
|
const updatedCount = e.wordCount.words - shouldIgnore;
|
|
565
595
|
this.setState(state => {
|
|
566
596
|
if (updatedCount !== state.wordCount) {
|
|
@@ -573,7 +603,7 @@ class RCEWrapper extends React.Component {
|
|
|
573
603
|
// @ts-expect-error
|
|
574
604
|
this.onNodeChange = e => {
|
|
575
605
|
// This is basically copied out of the tinymce silver theme code for the status bar
|
|
576
|
-
const path = e.parents.filter(p => p.nodeName !==
|
|
606
|
+
const path = e.parents.filter(p => p.nodeName !== 'BR' && !p.getAttribute('data-mce-bogus') && p.getAttribute('data-mce-type') !== 'bookmark')
|
|
577
607
|
// @ts-expect-error
|
|
578
608
|
.map(p => p.nodeName.toLowerCase()).reverse();
|
|
579
609
|
this.setState({
|
|
@@ -584,7 +614,7 @@ class RCEWrapper extends React.Component {
|
|
|
584
614
|
this.props.onContentChange?.(content);
|
|
585
615
|
// check accessibility when clearing the editor,
|
|
586
616
|
// all other times should be checked by handleInputChange
|
|
587
|
-
if (content ===
|
|
617
|
+
if (content === '') {
|
|
588
618
|
this.checkAccessibility();
|
|
589
619
|
}
|
|
590
620
|
};
|
|
@@ -606,14 +636,14 @@ class RCEWrapper extends React.Component {
|
|
|
606
636
|
height: newHeight
|
|
607
637
|
});
|
|
608
638
|
// play nice and send the same event that the silver theme would send
|
|
609
|
-
editor.fire(
|
|
639
|
+
editor.fire('ResizeEditor', {
|
|
610
640
|
deltaY: coordinates.deltaY
|
|
611
641
|
});
|
|
612
642
|
}
|
|
613
643
|
};
|
|
614
644
|
this.onA11yChecker = triggerElementId => {
|
|
615
645
|
const editor = this.mceInstance();
|
|
616
|
-
editor.execCommand(
|
|
646
|
+
editor.execCommand('openAccessibilityChecker', false, {
|
|
617
647
|
mountNode: instuiPopupMountNodeFn,
|
|
618
648
|
triggerElementId,
|
|
619
649
|
onFixError: errors => {
|
|
@@ -627,7 +657,7 @@ class RCEWrapper extends React.Component {
|
|
|
627
657
|
};
|
|
628
658
|
this.checkAccessibility = () => {
|
|
629
659
|
const editor = this.mceInstance();
|
|
630
|
-
editor.execCommand(
|
|
660
|
+
editor.execCommand('checkAccessibility', false, {
|
|
631
661
|
// @ts-expect-error
|
|
632
662
|
done: errors => {
|
|
633
663
|
this.setState({
|
|
@@ -666,64 +696,6 @@ class RCEWrapper extends React.Component {
|
|
|
666
696
|
this.state.KBShortcutFocusReturn?.focus();
|
|
667
697
|
}
|
|
668
698
|
};
|
|
669
|
-
this.handleAIClick = () => {
|
|
670
|
-
import("./plugins/shared/ai_tools").then(module => {
|
|
671
|
-
// @ts-expect-error
|
|
672
|
-
this.AIToolsTray = module.AIToolsTray;
|
|
673
|
-
this.setState({
|
|
674
|
-
AIToolsOpen: true,
|
|
675
|
-
AITToolsFocusReturn: document.activeElement
|
|
676
|
-
});
|
|
677
|
-
}).catch(ex => {
|
|
678
|
-
console.error("Failed loading the AIToolsTray", ex);
|
|
679
|
-
});
|
|
680
|
-
};
|
|
681
|
-
this.closeAITools = () => {
|
|
682
|
-
this.setState({
|
|
683
|
-
AIToolsOpen: false
|
|
684
|
-
});
|
|
685
|
-
};
|
|
686
|
-
this.AIToolsExited = () => {
|
|
687
|
-
if (this.state.AITToolsFocusReturn === this.iframe) {
|
|
688
|
-
// launched using a kb shortcut
|
|
689
|
-
// the iframe has focus so we need to forward it on to tinymce editor
|
|
690
|
-
if (this.editor) {
|
|
691
|
-
this.editor.focus(false);
|
|
692
|
-
}
|
|
693
|
-
} else if (this.state.AITToolsFocusReturn === document.getElementById(`show-on-focus-btn-${this.id}`)) {
|
|
694
|
-
// launched from showOnFocus button
|
|
695
|
-
// edge case where focusing KBShortcutFocusReturn doesn't work
|
|
696
|
-
this._showOnFocusButton?.focus();
|
|
697
|
-
} else {
|
|
698
|
-
// launched from kb shortcut button on status bar
|
|
699
|
-
// @ts-expect-error
|
|
700
|
-
this.state.AITToolsFocusReturn?.focus();
|
|
701
|
-
}
|
|
702
|
-
};
|
|
703
|
-
this.handleInsertAIContent = content => {
|
|
704
|
-
const editor = this.mceInstance();
|
|
705
|
-
contentInsertion.insertContent(editor, content);
|
|
706
|
-
};
|
|
707
|
-
this.handleReplaceAIContent = content => {
|
|
708
|
-
const ed = this.mceInstance();
|
|
709
|
-
const selection = ed.selection;
|
|
710
|
-
if (selection.getContent().length > 0) {
|
|
711
|
-
selection.setContent(content);
|
|
712
|
-
} else {
|
|
713
|
-
ed.selection.select(ed.getBody(), true);
|
|
714
|
-
selection.setContent(content);
|
|
715
|
-
}
|
|
716
|
-
};
|
|
717
|
-
this.getCurrentContentForAI = () => {
|
|
718
|
-
const selected = this.mceInstance().selection.getContent();
|
|
719
|
-
return selected ? {
|
|
720
|
-
type: "selection",
|
|
721
|
-
content: selected
|
|
722
|
-
} : {
|
|
723
|
-
type: "full",
|
|
724
|
-
content: this.mceInstance().getContent()
|
|
725
|
-
};
|
|
726
|
-
};
|
|
727
699
|
this.handleTextareaChange = () => {
|
|
728
700
|
if (this.isHidden()) {
|
|
729
701
|
this.setCode(this.textareaValue());
|
|
@@ -735,7 +707,7 @@ class RCEWrapper extends React.Component {
|
|
|
735
707
|
alert.id = alertIdValue++;
|
|
736
708
|
this.setState(state => {
|
|
737
709
|
let messages = state.messages.concat(alert);
|
|
738
|
-
messages =
|
|
710
|
+
messages = uniqBy(messages, 'text'); // Don't show the same message twice
|
|
739
711
|
return {
|
|
740
712
|
messages
|
|
741
713
|
};
|
|
@@ -754,11 +726,11 @@ class RCEWrapper extends React.Component {
|
|
|
754
726
|
*/
|
|
755
727
|
this.resetAlertId = () => {
|
|
756
728
|
if (this.state.messages.length > 0) {
|
|
757
|
-
throw new Error(
|
|
729
|
+
throw new Error('There are messages currently, you cannot reset when they are non-zero');
|
|
758
730
|
}
|
|
759
731
|
alertIdValue = 0;
|
|
760
732
|
};
|
|
761
|
-
this.style = buildStyle();
|
|
733
|
+
this.style = buildStyle(!!props.useHighContrast, props.fontFamily);
|
|
762
734
|
|
|
763
735
|
// Set up some limited global state that can be referenced
|
|
764
736
|
// as needed in RCE's components and function / plugin definitions
|
|
@@ -796,7 +768,7 @@ class RCEWrapper extends React.Component {
|
|
|
796
768
|
if (!Number.isNaN(_ht)) {
|
|
797
769
|
_ht = `${_ht}px`;
|
|
798
770
|
}
|
|
799
|
-
const currentRCECount = document.querySelectorAll(
|
|
771
|
+
const currentRCECount = document.querySelectorAll('.rce-wrapper').length;
|
|
800
772
|
const maxInitRenderedRCEs = Number.isNaN(props.maxInitRenderedRCEs) ? RCEWrapper.defaultProps.maxInitRenderedRCEs : props.maxInitRenderedRCEs;
|
|
801
773
|
this.state = {
|
|
802
774
|
path: [],
|
|
@@ -807,9 +779,9 @@ class RCEWrapper extends React.Component {
|
|
|
807
779
|
messages: [],
|
|
808
780
|
announcement: null,
|
|
809
781
|
confirmAutoSave: false,
|
|
810
|
-
autoSavedContent:
|
|
782
|
+
autoSavedContent: '',
|
|
811
783
|
// @ts-expect-error
|
|
812
|
-
id: this.props.id || this.props.textareaId || `${uid(
|
|
784
|
+
id: this.props.id || this.props.textareaId || `${uid('rce', 2)}`,
|
|
813
785
|
// @ts-expect-error
|
|
814
786
|
height: _ht,
|
|
815
787
|
fullscreenState: {
|
|
@@ -817,8 +789,7 @@ class RCEWrapper extends React.Component {
|
|
|
817
789
|
prevHeight: _ht
|
|
818
790
|
},
|
|
819
791
|
a11yErrorsCount: 0,
|
|
820
|
-
shouldShowEditor: typeof IntersectionObserver ===
|
|
821
|
-
AIToolsOpen: false
|
|
792
|
+
shouldShowEditor: typeof IntersectionObserver === 'undefined' || maxInitRenderedRCEs <= 0 || currentRCECount < maxInitRenderedRCEs
|
|
822
793
|
};
|
|
823
794
|
this._statusBarId = `${this.state.id}_statusbar`;
|
|
824
795
|
this.pendingEventHandlers = [];
|
|
@@ -842,7 +813,6 @@ class RCEWrapper extends React.Component {
|
|
|
842
813
|
this.resizeObserver = new ResizeObserver(() => {
|
|
843
814
|
this._handleFullscreenResize();
|
|
844
815
|
});
|
|
845
|
-
this.AIToolsTray = undefined;
|
|
846
816
|
this._effectiveContainingContext = normalizeContainingContext(this.props.trayProps?.containingContext);
|
|
847
817
|
}
|
|
848
818
|
|
|
@@ -852,11 +822,11 @@ class RCEWrapper extends React.Component {
|
|
|
852
822
|
// configure tinymce to say where that div is mounted, do this
|
|
853
823
|
// is a bit of a hack to tag the div that is this RCE's
|
|
854
824
|
_tagTinymceAuxDiv() {
|
|
855
|
-
const tinyauxlist = document.querySelectorAll(
|
|
825
|
+
const tinyauxlist = document.querySelectorAll('.tox-tinymce-aux');
|
|
856
826
|
if (tinyauxlist.length) {
|
|
857
827
|
const myaux = tinyauxlist[tinyauxlist.length - 1];
|
|
858
828
|
if (myaux.id) {
|
|
859
|
-
console.error(
|
|
829
|
+
console.error('Unexpected ID on my tox-tinymce-aux element');
|
|
860
830
|
}
|
|
861
831
|
myaux.id = `tinyaux-${this.id}`;
|
|
862
832
|
}
|
|
@@ -871,17 +841,17 @@ class RCEWrapper extends React.Component {
|
|
|
871
841
|
rce_transform_loaded_content = false,
|
|
872
842
|
rce_find_replace = false,
|
|
873
843
|
rce_studio_embed_improvements = false,
|
|
874
|
-
|
|
875
|
-
|
|
844
|
+
rce_asr_captioning_improvements = false,
|
|
845
|
+
file_verifiers_for_quiz_links = false
|
|
876
846
|
} = this.props.features;
|
|
877
847
|
return {
|
|
878
848
|
new_math_equation_handling,
|
|
879
849
|
explicit_latex_typesetting,
|
|
880
850
|
rce_transform_loaded_content,
|
|
881
851
|
rce_studio_embed_improvements,
|
|
852
|
+
rce_asr_captioning_improvements,
|
|
882
853
|
file_verifiers_for_quiz_links,
|
|
883
|
-
rce_find_replace
|
|
884
|
-
consolidated_media_player
|
|
854
|
+
rce_find_replace
|
|
885
855
|
};
|
|
886
856
|
}
|
|
887
857
|
getRequiredConfigValues() {
|
|
@@ -916,7 +886,7 @@ class RCEWrapper extends React.Component {
|
|
|
916
886
|
let status = true;
|
|
917
887
|
// Check for remaining placeholders
|
|
918
888
|
if (this.mceInstance().dom.doc.querySelector(`[data-placeholder-for]`)) {
|
|
919
|
-
status = promptFunc(formatMessage(
|
|
889
|
+
status = promptFunc(formatMessage('Content is still being uploaded, if you continue it will not be embedded properly.'));
|
|
920
890
|
}
|
|
921
891
|
return status;
|
|
922
892
|
}
|
|
@@ -976,9 +946,9 @@ class RCEWrapper extends React.Component {
|
|
|
976
946
|
// @ts-expect-error
|
|
977
947
|
ifr.contentDocument.body.clientHeight -
|
|
978
948
|
// @ts-expect-error
|
|
979
|
-
parseInt(editor_body_style[
|
|
949
|
+
parseInt(editor_body_style['padding-top'], 10) -
|
|
980
950
|
// @ts-expect-error
|
|
981
|
-
parseInt(editor_body_style[
|
|
951
|
+
parseInt(editor_body_style['padding-bottom'], 10);
|
|
982
952
|
const para_margin_ht = 24;
|
|
983
953
|
const reserve_ht = Math.ceil(height + para_margin_ht);
|
|
984
954
|
if (reserve_ht > editor_ht) {
|
|
@@ -990,7 +960,7 @@ class RCEWrapper extends React.Component {
|
|
|
990
960
|
}
|
|
991
961
|
}
|
|
992
962
|
checkImageLoadError(element) {
|
|
993
|
-
if (!element || element.tagName !==
|
|
963
|
+
if (!element || element.tagName !== 'IMG') {
|
|
994
964
|
return;
|
|
995
965
|
}
|
|
996
966
|
// @ts-expect-error
|
|
@@ -1005,9 +975,9 @@ class RCEWrapper extends React.Component {
|
|
|
1005
975
|
// @ts-expect-error
|
|
1006
976
|
if (element.naturalWidth === 0) {
|
|
1007
977
|
// @ts-expect-error
|
|
1008
|
-
element.style.border =
|
|
978
|
+
element.style.border = '1px solid #000';
|
|
1009
979
|
// @ts-expect-error
|
|
1010
|
-
element.style.padding =
|
|
980
|
+
element.style.padding = '2px';
|
|
1011
981
|
}
|
|
1012
982
|
}, 0);
|
|
1013
983
|
}
|
|
@@ -1017,7 +987,7 @@ class RCEWrapper extends React.Component {
|
|
|
1017
987
|
this.contentInserted(element);
|
|
1018
988
|
}
|
|
1019
989
|
replaceCode(code) {
|
|
1020
|
-
if (code !==
|
|
990
|
+
if (code !== '' && window.confirm(formatMessage('Content in the editor will be changed. Press Cancel to keep the original content.'))) {
|
|
1021
991
|
this.mceInstance().setContent(code);
|
|
1022
992
|
}
|
|
1023
993
|
}
|
|
@@ -1034,12 +1004,12 @@ class RCEWrapper extends React.Component {
|
|
|
1034
1004
|
// that there's some embedded content helper
|
|
1035
1005
|
// From what I've read, "title" is more reliable than "aria-label" for
|
|
1036
1006
|
// elements like iframes and embeds.
|
|
1037
|
-
const temp = document.createElement(
|
|
1007
|
+
const temp = document.createElement('div');
|
|
1038
1008
|
temp.innerHTML = code;
|
|
1039
1009
|
const code_elem = temp.firstElementChild;
|
|
1040
1010
|
if (code_elem) {
|
|
1041
|
-
if (!code_elem.hasAttribute(
|
|
1042
|
-
code_elem.setAttribute(
|
|
1011
|
+
if (!code_elem.hasAttribute('title') && !code_elem.hasAttribute('aria-label')) {
|
|
1012
|
+
code_elem.setAttribute('title', formatMessage('embedded content'));
|
|
1043
1013
|
}
|
|
1044
1014
|
code = code_elem.outerHTML;
|
|
1045
1015
|
}
|
|
@@ -1049,7 +1019,7 @@ class RCEWrapper extends React.Component {
|
|
|
1049
1019
|
// and it's often inserted into a <p> on top of that. Find the
|
|
1050
1020
|
// iframe and use it to flash the indicator.
|
|
1051
1021
|
const element = contentInsertion.insertContent(editor, code);
|
|
1052
|
-
const ifr = element && element.querySelector && element.querySelector(
|
|
1022
|
+
const ifr = element && element.querySelector && element.querySelector('iframe');
|
|
1053
1023
|
if (ifr) {
|
|
1054
1024
|
this.contentInserted(ifr);
|
|
1055
1025
|
} else {
|
|
@@ -1061,7 +1031,7 @@ class RCEWrapper extends React.Component {
|
|
|
1061
1031
|
const element = contentInsertion.insertImage(editor, image, this.getCanvasUrl());
|
|
1062
1032
|
|
|
1063
1033
|
// Removes TinyMCE's caret text if exists.
|
|
1064
|
-
if (element?.nextSibling?.data?.startsWith(
|
|
1034
|
+
if (element?.nextSibling?.data?.startsWith('\xA0' /* nbsp */)) {
|
|
1065
1035
|
element.nextSibling.splitText(1);
|
|
1066
1036
|
element.nextSibling.remove();
|
|
1067
1037
|
}
|
|
@@ -1142,8 +1112,8 @@ class RCEWrapper extends React.Component {
|
|
|
1142
1112
|
onTinyMCEInstance(command, ...args) {
|
|
1143
1113
|
const editor = this.mceInstance();
|
|
1144
1114
|
if (editor) {
|
|
1145
|
-
if (command ===
|
|
1146
|
-
editor.execCommand(
|
|
1115
|
+
if (command === 'mceRemoveEditor') {
|
|
1116
|
+
editor.execCommand('mceNewDocument');
|
|
1147
1117
|
} // makes sure content can't persist past removal
|
|
1148
1118
|
editor.execCommand(command, false, ...args);
|
|
1149
1119
|
}
|
|
@@ -1163,17 +1133,17 @@ class RCEWrapper extends React.Component {
|
|
|
1163
1133
|
return null;
|
|
1164
1134
|
}
|
|
1165
1135
|
textareaValue() {
|
|
1166
|
-
return this.getTextarea()?.value ||
|
|
1136
|
+
return this.getTextarea()?.value || '';
|
|
1167
1137
|
}
|
|
1168
1138
|
get id() {
|
|
1169
1139
|
return this.state.id;
|
|
1170
1140
|
}
|
|
1171
1141
|
getHtmlEditorStorage() {
|
|
1172
|
-
const cookieValue = getCookie(
|
|
1142
|
+
const cookieValue = getCookie('rce.htmleditor');
|
|
1173
1143
|
if (cookieValue) {
|
|
1174
1144
|
document.cookie = `rce.htmleditor=${cookieValue};path=/;max-age=0`;
|
|
1175
1145
|
}
|
|
1176
|
-
const value = cookieValue || this.storage?.getItem?.(
|
|
1146
|
+
const value = cookieValue || this.storage?.getItem?.('rce.htmleditor')?.content;
|
|
1177
1147
|
return value === RAW_HTML_EDITOR_VIEW || value === PRETTY_HTML_EDITOR_VIEW ? value : PRETTY_HTML_EDITOR_VIEW;
|
|
1178
1148
|
}
|
|
1179
1149
|
_isFullscreen() {
|
|
@@ -1190,7 +1160,7 @@ class RCEWrapper extends React.Component {
|
|
|
1190
1160
|
this._elementRef.current?.appendChild(tinymenuhost);
|
|
1191
1161
|
}
|
|
1192
1162
|
this._elementRef.current?.addEventListener(FS_CHANGEEVENT, this._onFullscreenChange);
|
|
1193
|
-
if (typeof this._elementRef.current?.offsetHeight ===
|
|
1163
|
+
if (typeof this._elementRef.current?.offsetHeight === 'number') {
|
|
1194
1164
|
this.setState({
|
|
1195
1165
|
fullscreenState: {
|
|
1196
1166
|
prevHeight: this._elementRef.current.offsetHeight - this._getStatusBarHeight()
|
|
@@ -1222,7 +1192,7 @@ class RCEWrapper extends React.Component {
|
|
|
1222
1192
|
const container = ed.getContainer();
|
|
1223
1193
|
if (container) {
|
|
1224
1194
|
container.style.height = cssHeight;
|
|
1225
|
-
ed.fire(
|
|
1195
|
+
ed.fire('ResizeEditor');
|
|
1226
1196
|
}
|
|
1227
1197
|
const textarea = this.getTextarea();
|
|
1228
1198
|
if (textarea) {
|
|
@@ -1233,10 +1203,10 @@ class RCEWrapper extends React.Component {
|
|
|
1233
1203
|
});
|
|
1234
1204
|
}
|
|
1235
1205
|
focus() {
|
|
1236
|
-
this.onTinyMCEInstance(
|
|
1206
|
+
this.onTinyMCEInstance('mceFocus');
|
|
1237
1207
|
// tinymce doesn't always call the focus handler.
|
|
1238
1208
|
// @ts-expect-error
|
|
1239
|
-
this.handleFocusEditor(new Event(
|
|
1209
|
+
this.handleFocusEditor(new Event('focus', {
|
|
1240
1210
|
target: this.mceInstance()
|
|
1241
1211
|
}));
|
|
1242
1212
|
}
|
|
@@ -1274,7 +1244,7 @@ class RCEWrapper extends React.Component {
|
|
|
1274
1244
|
*/
|
|
1275
1245
|
get _mceSerializedInitialHtml() {
|
|
1276
1246
|
if (!this._mceSerializedInitialHtmlCached) {
|
|
1277
|
-
const el = window.document.createElement(
|
|
1247
|
+
const el = window.document.createElement('div');
|
|
1278
1248
|
// @ts-expect-error
|
|
1279
1249
|
el.innerHTML = this.initialContent;
|
|
1280
1250
|
const serializer = this.mceInstance().serializer;
|
|
@@ -1328,22 +1298,22 @@ class RCEWrapper extends React.Component {
|
|
|
1328
1298
|
// focus is still somewhere w/in me
|
|
1329
1299
|
return;
|
|
1330
1300
|
}
|
|
1331
|
-
const activeClass = document.activeElement?.getAttribute(
|
|
1301
|
+
const activeClass = document.activeElement?.getAttribute('class');
|
|
1332
1302
|
if (
|
|
1333
1303
|
// @ts-expect-error
|
|
1334
1304
|
(event.focusedEditor === undefined ||
|
|
1335
1305
|
// @ts-expect-error
|
|
1336
|
-
event.target.id === event.focusedEditor?.id) && activeClass?.includes(
|
|
1306
|
+
event.target.id === event.focusedEditor?.id) && activeClass?.includes('tox-')) {
|
|
1337
1307
|
// if a toolbar button has focus, then the user clicks on the "more" button
|
|
1338
1308
|
// focus jumps to the body, then eventually to the popped up toolbar. This
|
|
1339
1309
|
// catches that case.
|
|
1340
1310
|
return;
|
|
1341
1311
|
}
|
|
1342
|
-
if (event?.relatedTarget?.getAttribute(
|
|
1312
|
+
if (event?.relatedTarget?.getAttribute('class')?.includes('tox-')) {
|
|
1343
1313
|
// a tinymce popup has focus
|
|
1344
1314
|
return;
|
|
1345
1315
|
}
|
|
1346
|
-
const popups = document.querySelectorAll(
|
|
1316
|
+
const popups = document.querySelectorAll('[data-mce-component]');
|
|
1347
1317
|
for (const popup of popups) {
|
|
1348
1318
|
if (popup.contains(document.activeElement)) {
|
|
1349
1319
|
// one of our popups has focus
|
|
@@ -1361,22 +1331,22 @@ class RCEWrapper extends React.Component {
|
|
|
1361
1331
|
call(methodName, ...args) {
|
|
1362
1332
|
// since exists? has a ? and cant be a regular function just return true
|
|
1363
1333
|
// rather than calling as a fn on the editor
|
|
1364
|
-
if (methodName ===
|
|
1334
|
+
if (methodName === 'exists?') {
|
|
1365
1335
|
return true;
|
|
1366
1336
|
}
|
|
1367
1337
|
// @ts-expect-error
|
|
1368
1338
|
return this[methodName](...args);
|
|
1369
1339
|
}
|
|
1370
1340
|
announceContextToolbars(editor) {
|
|
1371
|
-
editor.on(
|
|
1341
|
+
editor.on('NodeChange', () => {
|
|
1372
1342
|
if (!this._isMounted) return;
|
|
1373
1343
|
const node = editor.selection.getNode();
|
|
1374
1344
|
// @ts-expect-error
|
|
1375
1345
|
if (isImageEmbed(node, editor)) {
|
|
1376
1346
|
if (this.announcing !== 1) {
|
|
1377
1347
|
this.setState({
|
|
1378
|
-
announcement: formatMessage(
|
|
1379
|
-
text: node.getAttribute(
|
|
1348
|
+
announcement: formatMessage('type Control F9 to access image options. {text}', {
|
|
1349
|
+
text: node.getAttribute('alt')
|
|
1380
1350
|
})
|
|
1381
1351
|
});
|
|
1382
1352
|
this.announcing = 1;
|
|
@@ -1384,7 +1354,7 @@ class RCEWrapper extends React.Component {
|
|
|
1384
1354
|
} else if (isFileLink(node, editor)) {
|
|
1385
1355
|
if (this.announcing !== 2) {
|
|
1386
1356
|
this.setState({
|
|
1387
|
-
announcement: formatMessage(
|
|
1357
|
+
announcement: formatMessage('type Control F9 to access link options. {text}', {
|
|
1388
1358
|
text: node.textContent
|
|
1389
1359
|
})
|
|
1390
1360
|
});
|
|
@@ -1393,7 +1363,7 @@ class RCEWrapper extends React.Component {
|
|
|
1393
1363
|
} else if (isElementWithinTable(node)) {
|
|
1394
1364
|
if (this.announcing !== 3) {
|
|
1395
1365
|
this.setState({
|
|
1396
|
-
announcement: formatMessage(
|
|
1366
|
+
announcement: formatMessage('type Control F9 to access table options. {text}', {
|
|
1397
1367
|
text: node.textContent
|
|
1398
1368
|
})
|
|
1399
1369
|
});
|
|
@@ -1406,17 +1376,17 @@ class RCEWrapper extends React.Component {
|
|
|
1406
1376
|
this.announcing = 0;
|
|
1407
1377
|
}
|
|
1408
1378
|
});
|
|
1409
|
-
editor.on(
|
|
1379
|
+
editor.on('ResizeEditor', ({
|
|
1410
1380
|
deltaY
|
|
1411
1381
|
}) => {
|
|
1412
1382
|
if (!this._isMounted || !deltaY) return;
|
|
1413
1383
|
if (deltaY < 0) {
|
|
1414
1384
|
this.setState({
|
|
1415
|
-
announcement: formatMessage(
|
|
1385
|
+
announcement: formatMessage('The height of Rich Content Area is decreased.')
|
|
1416
1386
|
});
|
|
1417
1387
|
} else {
|
|
1418
1388
|
this.setState({
|
|
1419
|
-
announcement: formatMessage(
|
|
1389
|
+
announcement: formatMessage('The height of Rich Content Area is increased.')
|
|
1420
1390
|
});
|
|
1421
1391
|
}
|
|
1422
1392
|
});
|
|
@@ -1442,10 +1412,10 @@ class RCEWrapper extends React.Component {
|
|
|
1442
1412
|
// This doesn't apply if the editor is off-screen or has visibility:hidden;
|
|
1443
1413
|
// only if it isn't rendered or has display:none;
|
|
1444
1414
|
const editorVisible = this.editor.getContainer().offsetParent;
|
|
1445
|
-
return this.props.autosave?.enabled && editorVisible && document.querySelectorAll(
|
|
1415
|
+
return this.props.autosave?.enabled && editorVisible && document.querySelectorAll('.rce-wrapper').length === 1 && storageAvailable();
|
|
1446
1416
|
}
|
|
1447
1417
|
get autoSaveKey() {
|
|
1448
|
-
const userId = this._effectiveContainingContext?.userId ||
|
|
1418
|
+
const userId = this._effectiveContainingContext?.userId || '-';
|
|
1449
1419
|
return `rceautosave:${userId}${window.location.href}:${this.props.textareaId}`;
|
|
1450
1420
|
}
|
|
1451
1421
|
componentWillUnmount() {
|
|
@@ -1456,7 +1426,7 @@ class RCEWrapper extends React.Component {
|
|
|
1456
1426
|
this.destroy();
|
|
1457
1427
|
}
|
|
1458
1428
|
if (this._elementRef.current) {
|
|
1459
|
-
this._elementRef.current.removeEventListener(
|
|
1429
|
+
this._elementRef.current.removeEventListener('keydown', this.handleKey, true);
|
|
1460
1430
|
}
|
|
1461
1431
|
this.mutationObserver?.disconnect();
|
|
1462
1432
|
this.intersectionObserver?.disconnect();
|
|
@@ -1468,27 +1438,27 @@ class RCEWrapper extends React.Component {
|
|
|
1468
1438
|
|
|
1469
1439
|
// @ts-expect-error
|
|
1470
1440
|
const setupCallback = options.setup;
|
|
1471
|
-
const canvasPlugins = rcsExists ? [
|
|
1441
|
+
const canvasPlugins = rcsExists ? ['instructure_image', 'instructure_documents', 'instructure_equation'] : [];
|
|
1472
1442
|
if (rcsExists && !this.props.instRecordDisabled) {
|
|
1473
|
-
canvasPlugins.splice(2, 0,
|
|
1443
|
+
canvasPlugins.splice(2, 0, 'instructure_record');
|
|
1474
1444
|
}
|
|
1475
|
-
const pastePlugins = rcsExists ? [
|
|
1476
|
-
if (rcsExists && this.props.use_rce_icon_maker && this.props.trayProps?.contextType ===
|
|
1477
|
-
canvasPlugins.push(
|
|
1445
|
+
const pastePlugins = rcsExists ? ['instructure_paste', 'paste'] : ['paste'];
|
|
1446
|
+
if (rcsExists && this.props.use_rce_icon_maker && this.props.trayProps?.contextType === 'course') {
|
|
1447
|
+
canvasPlugins.push('instructure_icon_maker');
|
|
1478
1448
|
}
|
|
1479
1449
|
if (document[FS_ENABLED]) {
|
|
1480
|
-
canvasPlugins.push(
|
|
1450
|
+
canvasPlugins.push('instructure_fullscreen');
|
|
1481
1451
|
}
|
|
1482
1452
|
if (this.getRequiredFeatureStatuses().rce_find_replace) {
|
|
1483
|
-
canvasPlugins.push(
|
|
1484
|
-
canvasPlugins.push(
|
|
1453
|
+
canvasPlugins.push('searchreplace');
|
|
1454
|
+
canvasPlugins.push('instructure_search_and_replace');
|
|
1485
1455
|
}
|
|
1486
|
-
const possibleNewMenubarItems = this.props.editorOptions.menu ? Object.keys(this.props.editorOptions.menu).join(
|
|
1456
|
+
const possibleNewMenubarItems = this.props.editorOptions.menu ? Object.keys(this.props.editorOptions.menu).join(' ') : undefined;
|
|
1487
1457
|
const wrappedOpts = {
|
|
1488
1458
|
...defaultTinymceConfig,
|
|
1489
1459
|
...options,
|
|
1490
1460
|
readonly: this.props.readOnly,
|
|
1491
|
-
theme:
|
|
1461
|
+
theme: 'silver',
|
|
1492
1462
|
// some older code specified 'modern', which doesn't exist any more
|
|
1493
1463
|
|
|
1494
1464
|
// @ts-expect-error
|
|
@@ -1497,7 +1467,7 @@ class RCEWrapper extends React.Component {
|
|
|
1497
1467
|
document_base_url: this.props.canvasOrigin,
|
|
1498
1468
|
block_formats:
|
|
1499
1469
|
// @ts-expect-error
|
|
1500
|
-
options.block_formats || [`${formatMessage(
|
|
1470
|
+
options.block_formats || [`${formatMessage('Heading 2')}=h2`, `${formatMessage('Heading 3')}=h3`, `${formatMessage('Heading 4')}=h4`, `${formatMessage('Preformatted')}=pre`, `${formatMessage('Paragraph')}=p`].join('; '),
|
|
1501
1471
|
setup: editor => {
|
|
1502
1472
|
addKebabIcon(editor);
|
|
1503
1473
|
editorWrappers.set(editor, this);
|
|
@@ -1510,7 +1480,7 @@ class RCEWrapper extends React.Component {
|
|
|
1510
1480
|
// @ts-expect-error
|
|
1511
1481
|
bridge.userLocale = userLocale;
|
|
1512
1482
|
bridge.canvasOrigin = this.props.canvasOrigin;
|
|
1513
|
-
if (typeof setupCallback ===
|
|
1483
|
+
if (typeof setupCallback === 'function') {
|
|
1514
1484
|
setupCallback(editor);
|
|
1515
1485
|
}
|
|
1516
1486
|
},
|
|
@@ -1521,7 +1491,7 @@ class RCEWrapper extends React.Component {
|
|
|
1521
1491
|
// @ts-expect-error
|
|
1522
1492
|
content_css: options.content_css || [],
|
|
1523
1493
|
// @ts-expect-error
|
|
1524
|
-
content_style: contentCSS + (options.content_style ||
|
|
1494
|
+
content_style: contentCSS + (options.content_style || ''),
|
|
1525
1495
|
menubar: mergeMenuItems(getMenubarForVariant(this.variant), possibleNewMenubarItems),
|
|
1526
1496
|
// default menu options listed at https://www.tiny.cloud/docs/configure/editor-appearance/#menu
|
|
1527
1497
|
// tinymce's default edit and table menus are fine
|
|
@@ -1536,10 +1506,10 @@ class RCEWrapper extends React.Component {
|
|
|
1536
1506
|
getToolbarForVariant(this.variant, this.ltiToolFavorites),
|
|
1537
1507
|
// @ts-expect-error
|
|
1538
1508
|
options.toolbar),
|
|
1539
|
-
contextmenu:
|
|
1509
|
+
contextmenu: '',
|
|
1540
1510
|
// show the browser's native context menu
|
|
1541
1511
|
|
|
1542
|
-
toolbar_mode:
|
|
1512
|
+
toolbar_mode: 'sliding',
|
|
1543
1513
|
toolbar_sticky: true,
|
|
1544
1514
|
// In regards to the ability to disable plugins:
|
|
1545
1515
|
// we only have to explicitly manage the removal of plugins
|
|
@@ -1548,16 +1518,16 @@ class RCEWrapper extends React.Component {
|
|
|
1548
1518
|
// handles all of that complexity. It that ever changes in the
|
|
1549
1519
|
// future in an upgraded version, we will have to update the
|
|
1550
1520
|
// logic in those other places as well.
|
|
1551
|
-
plugins: mergePlugins([
|
|
1521
|
+
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],
|
|
1552
1522
|
// filter out the plugins designated for removal
|
|
1553
1523
|
// @ts-expect-error
|
|
1554
|
-
sanitizePlugins(options.plugins)?.filter(p => p.length > 0 && p[0] !==
|
|
1524
|
+
sanitizePlugins(options.plugins)?.filter(p => p.length > 0 && p[0] !== '-'), this.pluginsToExclude),
|
|
1555
1525
|
textpattern_patterns: [{
|
|
1556
|
-
start:
|
|
1557
|
-
cmd:
|
|
1526
|
+
start: '* ',
|
|
1527
|
+
cmd: 'InsertUnorderedList'
|
|
1558
1528
|
}, {
|
|
1559
|
-
start:
|
|
1560
|
-
cmd:
|
|
1529
|
+
start: '- ',
|
|
1530
|
+
cmd: 'InsertUnorderedList'
|
|
1561
1531
|
}]
|
|
1562
1532
|
};
|
|
1563
1533
|
if (this.props.trayProps) {
|
|
@@ -1579,7 +1549,7 @@ class RCEWrapper extends React.Component {
|
|
|
1579
1549
|
}
|
|
1580
1550
|
unhandleTextareaChange() {
|
|
1581
1551
|
if (this._textareaEl) {
|
|
1582
|
-
this._textareaEl.removeEventListener(
|
|
1552
|
+
this._textareaEl.removeEventListener('input', this.handleTextareaChange);
|
|
1583
1553
|
}
|
|
1584
1554
|
}
|
|
1585
1555
|
registerTextareaChange() {
|
|
@@ -1587,7 +1557,7 @@ class RCEWrapper extends React.Component {
|
|
|
1587
1557
|
if (this._textareaEl !== el) {
|
|
1588
1558
|
this.unhandleTextareaChange();
|
|
1589
1559
|
if (el) {
|
|
1590
|
-
el.addEventListener(
|
|
1560
|
+
el.addEventListener('input', this.handleTextareaChange);
|
|
1591
1561
|
if (this.props.textareaClassName) {
|
|
1592
1562
|
// split the string on whitespace because classList doesn't let you add multiple
|
|
1593
1563
|
// space seperated classes at a time but does let you add an array of them
|
|
@@ -1613,7 +1583,7 @@ class RCEWrapper extends React.Component {
|
|
|
1613
1583
|
// initialize the RCE when it gets close to entering the viewport
|
|
1614
1584
|
{
|
|
1615
1585
|
root: null,
|
|
1616
|
-
rootMargin:
|
|
1586
|
+
rootMargin: '200px 0px',
|
|
1617
1587
|
threshold: 0.0
|
|
1618
1588
|
});
|
|
1619
1589
|
// @ts-expect-error
|
|
@@ -1632,7 +1602,7 @@ class RCEWrapper extends React.Component {
|
|
|
1632
1602
|
this.focusCurrentView();
|
|
1633
1603
|
}
|
|
1634
1604
|
if (prevProps.readOnly !== this.props.readOnly) {
|
|
1635
|
-
this.mceInstance().mode.set(this.props.readOnly ?
|
|
1605
|
+
this.mceInstance().mode.set(this.props.readOnly ? 'readonly' : 'design');
|
|
1636
1606
|
}
|
|
1637
1607
|
}
|
|
1638
1608
|
}
|
|
@@ -1646,7 +1616,7 @@ class RCEWrapper extends React.Component {
|
|
|
1646
1616
|
this._tagTinymceAuxDiv();
|
|
1647
1617
|
this.registerTextareaChange();
|
|
1648
1618
|
// @ts-expect-error
|
|
1649
|
-
this._elementRef.current.addEventListener(
|
|
1619
|
+
this._elementRef.current.addEventListener('keydown', this.handleKey, true);
|
|
1650
1620
|
// give the textarea its initial size
|
|
1651
1621
|
this.onResize(null, {
|
|
1652
1622
|
deltaY: 0
|
|
@@ -1654,7 +1624,7 @@ class RCEWrapper extends React.Component {
|
|
|
1654
1624
|
// Preload the LTI Tools modal
|
|
1655
1625
|
// This helps with loading the favorited external tools
|
|
1656
1626
|
if (this.ltiToolFavorites.length > 0) {
|
|
1657
|
-
import(
|
|
1627
|
+
import('./plugins/instructure_rce_external_tools/components/ExternalToolSelectionDialog/ExternalToolSelectionDialog');
|
|
1658
1628
|
}
|
|
1659
1629
|
bridge.renderEditor(this);
|
|
1660
1630
|
}
|
|
@@ -1676,9 +1646,9 @@ class RCEWrapper extends React.Component {
|
|
|
1676
1646
|
fallback: /*#__PURE__*/React.createElement("div", {
|
|
1677
1647
|
style: {
|
|
1678
1648
|
height: this.state.height,
|
|
1679
|
-
display:
|
|
1680
|
-
justifyContent:
|
|
1681
|
-
alignItems:
|
|
1649
|
+
display: 'flex',
|
|
1650
|
+
justifyContent: 'center',
|
|
1651
|
+
alignItems: 'center'
|
|
1682
1652
|
}
|
|
1683
1653
|
}, /*#__PURE__*/React.createElement(Spinner, {
|
|
1684
1654
|
renderTitle: renderLoading,
|
|
@@ -1714,12 +1684,11 @@ class RCEWrapper extends React.Component {
|
|
|
1714
1684
|
ref: this._editorPlaceholderRef,
|
|
1715
1685
|
style: {
|
|
1716
1686
|
height: `${this.props.editorOptions.height}px`,
|
|
1717
|
-
border:
|
|
1687
|
+
border: '1px solid grey'
|
|
1718
1688
|
}
|
|
1719
1689
|
});
|
|
1720
1690
|
}
|
|
1721
1691
|
const statusBarOptions = {
|
|
1722
|
-
aiTextTools: this.props.ai_text_tools,
|
|
1723
1692
|
isDesktop: tinymce.Env.deviceType.isDesktop(),
|
|
1724
1693
|
a11yResizers: !!this.props.features?.rce_a11y_resize
|
|
1725
1694
|
};
|
|
@@ -1740,8 +1709,8 @@ class RCEWrapper extends React.Component {
|
|
|
1740
1709
|
// @ts-expect-error
|
|
1741
1710
|
,
|
|
1742
1711
|
ref: this._elementRef,
|
|
1743
|
-
style: this.variant ===
|
|
1744
|
-
marginBottom:
|
|
1712
|
+
style: this.variant === 'full' ? {
|
|
1713
|
+
marginBottom: '.5rem'
|
|
1745
1714
|
} : undefined,
|
|
1746
1715
|
onFocus: this.handleFocusRCE,
|
|
1747
1716
|
onBlur: this.handleBlurRCE
|
|
@@ -1749,7 +1718,7 @@ class RCEWrapper extends React.Component {
|
|
|
1749
1718
|
id: `show-on-focus-btn-${this.id}`,
|
|
1750
1719
|
onClick: this.openKBShortcutModal,
|
|
1751
1720
|
margin: "xx-small",
|
|
1752
|
-
screenReaderLabel: formatMessage(
|
|
1721
|
+
screenReaderLabel: formatMessage('View keyboard shortcuts')
|
|
1753
1722
|
// @ts-expect-error
|
|
1754
1723
|
,
|
|
1755
1724
|
ref: el => this._showOnFocusButton = el
|
|
@@ -1761,7 +1730,7 @@ class RCEWrapper extends React.Component {
|
|
|
1761
1730
|
afterDismiss: this.removeAlert
|
|
1762
1731
|
}), this.state.editorView === PRETTY_HTML_EDITOR_VIEW && this.renderHtmlEditor(), /*#__PURE__*/React.createElement("div", {
|
|
1763
1732
|
style: {
|
|
1764
|
-
display: this.state.editorView === PRETTY_HTML_EDITOR_VIEW ?
|
|
1733
|
+
display: this.state.editorView === PRETTY_HTML_EDITOR_VIEW ? 'none' : 'block'
|
|
1765
1734
|
}
|
|
1766
1735
|
}, /*#__PURE__*/React.createElement(Editor, {
|
|
1767
1736
|
id: mceProps.textareaId,
|
|
@@ -1809,8 +1778,7 @@ class RCEWrapper extends React.Component {
|
|
|
1809
1778
|
skipEditorFocus: true
|
|
1810
1779
|
}),
|
|
1811
1780
|
disabledPlugins: this.pluginsToExclude,
|
|
1812
|
-
features: statusBarFeatures
|
|
1813
|
-
onAI: this.handleAIClick
|
|
1781
|
+
features: statusBarFeatures
|
|
1814
1782
|
}), this._effectiveContainingContext && /*#__PURE__*/React.createElement(CanvasContentTray, Object.assign({
|
|
1815
1783
|
mountNode: instuiPopupMountNodeFn,
|
|
1816
1784
|
key: this.id,
|
|
@@ -1828,19 +1796,6 @@ class RCEWrapper extends React.Component {
|
|
|
1828
1796
|
onExited: this.KBShortcutModalExited,
|
|
1829
1797
|
onDismiss: this.closeKBShortcutModal,
|
|
1830
1798
|
open: this.state.KBShortcutModalOpen
|
|
1831
|
-
}), this.props.ai_text_tools && this.AIToolsTray &&
|
|
1832
|
-
/*#__PURE__*/
|
|
1833
|
-
// @ts-expect-error
|
|
1834
|
-
React.createElement(this.AIToolsTray, {
|
|
1835
|
-
open: this.state.AIToolsOpen,
|
|
1836
|
-
container: document.querySelector('[role="main"]'),
|
|
1837
|
-
mountNode: instuiPopupMountNodeFn,
|
|
1838
|
-
contextId: trayProps.contextId,
|
|
1839
|
-
contextType: trayProps.contextId,
|
|
1840
|
-
currentContent: this.getCurrentContentForAI(),
|
|
1841
|
-
onClose: this.closeAITools,
|
|
1842
|
-
onInsertContent: this.handleInsertAIContent,
|
|
1843
|
-
onReplaceContent: this.handleReplaceAIContent
|
|
1844
1799
|
}), this.state.confirmAutoSave ? /*#__PURE__*/React.createElement(Suspense, {
|
|
1845
1800
|
fallback: /*#__PURE__*/React.createElement(Spinner, {
|
|
1846
1801
|
renderTitle: renderLoading,
|
|
@@ -1859,7 +1814,6 @@ class RCEWrapper extends React.Component {
|
|
|
1859
1814
|
}
|
|
1860
1815
|
}
|
|
1861
1816
|
RCEWrapper.propTypes = {
|
|
1862
|
-
ai_text_tools: _pt.bool,
|
|
1863
1817
|
autosave: _pt.shape({
|
|
1864
1818
|
enabled: _pt.bool,
|
|
1865
1819
|
maxAge: _pt.number
|
|
@@ -1885,6 +1839,8 @@ RCEWrapper.propTypes = {
|
|
|
1885
1839
|
textareaId: _pt.string,
|
|
1886
1840
|
tinymce: _pt.any.isRequired,
|
|
1887
1841
|
use_rce_icon_maker: _pt.bool,
|
|
1842
|
+
useHighContrast: _pt.bool,
|
|
1843
|
+
fontFamily: _pt.string,
|
|
1888
1844
|
userCacheKey: _pt.string
|
|
1889
1845
|
};
|
|
1890
1846
|
RCEWrapper.propTypes = rceWrapperPropTypes;
|
|
@@ -1898,8 +1854,8 @@ RCEWrapper.defaultProps = {
|
|
|
1898
1854
|
maxInitRenderedRCEs: -1,
|
|
1899
1855
|
features: {},
|
|
1900
1856
|
timezone: Intl?.DateTimeFormat()?.resolvedOptions()?.timeZone,
|
|
1901
|
-
canvasOrigin:
|
|
1902
|
-
variant:
|
|
1857
|
+
canvasOrigin: '',
|
|
1858
|
+
variant: 'full'
|
|
1903
1859
|
};
|
|
1904
1860
|
RCEWrapper.skinCssInjected = false;
|
|
1905
1861
|
export default RCEWrapper;
|