@instructure/canvas-rce 5.14.1 → 5.15.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 +15 -0
- package/DEVELOPMENT.md +2 -2
- package/README.md +0 -8
- package/__tests__/common/indicate.test.js +84 -0
- package/__tests__/common/mimeClass.test.js +85 -0
- package/__tests__/module/contentInsertionUtils.test.js +52 -0
- package/__tests__/module/indicatorRegion.test.js +75 -0
- package/__tests__/module/normalizeLocale.test.js +46 -0
- package/__tests__/module/normalizeProps.test.js +51 -0
- package/__tests__/module/sanitizePlugins.test.js +48 -0
- package/__tests__/module/wrapInitCb.test.js +56 -0
- package/__tests__/rcs/api.test.js +819 -0
- package/{mocha-reporter-config.js → __tests__/sidebar/actions/all_files.test.js} +10 -9
- package/__tests__/sidebar/actions/data.test.js +196 -0
- package/__tests__/sidebar/actions/utils.js +44 -0
- package/{es/rce/__mocks__/_mockStudioPlayer.js → __tests__/sidebar/reducers/all_files.test.js} +12 -4
- package/babel.config.js +3 -1
- package/es/bridge/Bridge.js +18 -73
- package/es/bridge/index.js +1 -0
- package/es/canvasFileBrowser/FileBrowser.js +21 -77
- package/es/canvasFileBrowser/en-US.js +3 -6
- package/es/common/FlashAlert.js +15 -39
- package/es/common/browser.js +4 -2
- package/es/common/fileUrl.js +105 -64
- package/es/common/incremental-loading/LoadMoreButton.js +4 -4
- package/es/common/incremental-loading/LoadingIndicator.js +1 -2
- package/es/common/incremental-loading/LoadingStatus.js +5 -13
- package/es/common/incremental-loading/index.js +1 -0
- package/es/common/incremental-loading/useIncrementalLoading.js +1 -3
- package/es/common/indicate.js +16 -10
- package/es/common/mimeClass.js +3 -4
- package/es/common/natcompare.js +1 -4
- package/es/defaultTinymceConfig.js +5 -3
- package/es/elementDenylist.js +1 -0
- package/es/enhance-user-content/doc_previews.js +24 -35
- package/es/enhance-user-content/enhance_user_content.js +32 -67
- package/es/enhance-user-content/external_links.js +6 -9
- package/es/enhance-user-content/index.js +1 -0
- package/es/enhance-user-content/instructure_helper.js +22 -50
- package/es/enhance-user-content/jqueryish_funcs.js +8 -11
- package/es/enhance-user-content/mathml.js +48 -107
- package/es/enhance-user-content/media_comment_thumbnail.js +6 -25
- package/es/format-message.js +4 -5
- package/es/getThemeVars.js +8 -6
- package/es/getTranslations.js +1 -78
- package/es/index.d.ts +59 -0
- package/es/index.js +6 -6
- package/es/rce/AlertMessageArea.js +15 -16
- package/es/rce/DraggingBlocker.js +4 -2
- package/es/rce/KeyboardShortcutModal.js +3 -2
- package/es/rce/RCE.js +16 -17
- package/es/rce/RCEGlobals.js +12 -10
- package/es/rce/RCEVariants.js +29 -14
- package/es/rce/RCEWrapper.js +530 -641
- package/es/rce/RCEWrapper.utils.js +131 -0
- package/es/rce/RCEWrapperProps.js +9 -5
- package/es/rce/RceHtmlEditor.js +17 -19
- package/es/rce/ResizeHandle.js +4 -10
- package/es/rce/RestoreAutoSaveModal.js +1 -2
- package/es/rce/ShowOnFocusButton/index.js +2 -8
- package/es/rce/StatusBar.js +10 -44
- package/es/rce/alertHandler.js +1 -4
- package/es/rce/contentInsertion.js +36 -59
- package/es/rce/contentInsertionUtils.js +6 -8
- package/es/rce/contentRendering.js +13 -17
- package/es/rce/customEvents.js +1 -0
- package/es/rce/editorLanguage.js +23 -11
- package/es/rce/indicatorRegion.js +7 -7
- package/es/rce/normalizeLocale.js +5 -3
- package/es/rce/normalizeProps.js +7 -5
- package/es/rce/plugins/instructure-ui-icons/plugin.js +21 -3
- package/es/rce/plugins/instructure_color/clickCallback.js +82 -0
- package/es/rce/plugins/instructure_color/components/ColorPicker.js +294 -0
- package/es/rce/plugins/instructure_color/components/ColorPopup.js +67 -0
- package/es/rce/plugins/instructure_color/components/colorUtils.js +60 -0
- package/es/rce/plugins/instructure_color/plugin.js +40 -0
- package/es/rce/plugins/instructure_condensed_buttons/core/ListUtils.js +10 -3
- package/es/rce/plugins/instructure_condensed_buttons/plugin.js +1 -0
- package/es/rce/plugins/instructure_condensed_buttons/ui/alignment-button.js +1 -2
- package/es/rce/plugins/instructure_condensed_buttons/ui/directionality-button.js +3 -2
- package/es/rce/plugins/instructure_condensed_buttons/ui/indent-outdent-button.js +1 -0
- package/es/rce/plugins/instructure_condensed_buttons/ui/list-button.js +26 -25
- package/es/rce/plugins/instructure_condensed_buttons/ui/subscript-superscript-button.js +2 -3
- package/es/rce/plugins/instructure_documents/clickCallback.js +1 -0
- package/es/rce/plugins/instructure_documents/components/DocumentsPanel.js +1 -9
- package/es/rce/plugins/instructure_documents/components/Link.js +4 -20
- package/es/rce/plugins/instructure_documents/plugin.js +7 -14
- package/es/rce/plugins/instructure_equation/EquationEditorModal/advancedOnlySyntax.js +4 -2
- package/es/rce/plugins/instructure_equation/EquationEditorModal/advancedPreference.js +1 -2
- package/es/rce/plugins/instructure_equation/EquationEditorModal/index.js +17 -37
- package/es/rce/plugins/instructure_equation/EquationEditorModal/latexTextareaUtil.js +14 -15
- package/es/rce/plugins/instructure_equation/EquationEditorModal/parseLatex.js +6 -5
- package/es/rce/plugins/instructure_equation/EquationEditorModal/styles.js +4 -2
- package/es/rce/plugins/instructure_equation/EquationEditorToolbar/buttons.js +14 -8
- package/es/rce/plugins/instructure_equation/EquationEditorToolbar/index.js +13 -18
- package/es/rce/plugins/instructure_equation/MathIcon/index.js +4 -5
- package/es/rce/plugins/instructure_equation/MathIcon/svgs.js +1 -1
- package/es/rce/plugins/instructure_equation/clickCallback.js +2 -5
- package/es/rce/plugins/instructure_equation/mathlive/index.js +167 -16
- package/es/rce/plugins/instructure_equation/plugin.js +7 -10
- package/es/rce/plugins/instructure_fullscreen/plugin.js +1 -6
- package/es/rce/plugins/instructure_html_view/clickCallback.js +1 -0
- package/es/rce/plugins/instructure_html_view/plugin.js +5 -4
- package/es/rce/plugins/instructure_icon_maker/clickCallback.js +5 -8
- package/es/rce/plugins/instructure_icon_maker/components/CreateIconMakerForm/ColorSection.js +47 -51
- package/es/rce/plugins/instructure_icon_maker/components/CreateIconMakerForm/CreateIconMakerForm.js +10 -10
- package/es/rce/plugins/instructure_icon_maker/components/CreateIconMakerForm/Footer.js +11 -11
- package/es/rce/plugins/instructure_icon_maker/components/CreateIconMakerForm/Group.js +6 -6
- package/es/rce/plugins/instructure_icon_maker/components/CreateIconMakerForm/Header.js +8 -10
- package/es/rce/plugins/instructure_icon_maker/components/CreateIconMakerForm/ImageSection/Course.js +32 -31
- package/es/rce/plugins/instructure_icon_maker/components/CreateIconMakerForm/ImageSection/ImageOptions.js +24 -35
- package/es/rce/plugins/instructure_icon_maker/components/CreateIconMakerForm/ImageSection/ImageSection.js +32 -32
- package/es/rce/plugins/instructure_icon_maker/components/CreateIconMakerForm/ImageSection/ModeSelect.js +11 -11
- package/es/rce/plugins/instructure_icon_maker/components/CreateIconMakerForm/ImageSection/MultiColor/index.js +16 -15
- package/es/rce/plugins/instructure_icon_maker/components/CreateIconMakerForm/ImageSection/MultiColor/svg.js +1 -0
- package/es/rce/plugins/instructure_icon_maker/components/CreateIconMakerForm/ImageSection/SVGList.js +11 -11
- package/es/rce/plugins/instructure_icon_maker/components/CreateIconMakerForm/ImageSection/SVGThumbnail.js +9 -13
- package/es/rce/plugins/instructure_icon_maker/components/CreateIconMakerForm/ImageSection/SingleColor/index.js +12 -13
- package/es/rce/plugins/instructure_icon_maker/components/CreateIconMakerForm/ImageSection/SingleColor/svg.js +33 -80
- package/es/rce/plugins/instructure_icon_maker/components/CreateIconMakerForm/ImageSection/Upload.js +34 -28
- package/es/rce/plugins/instructure_icon_maker/components/CreateIconMakerForm/ImageSection/index.js +1 -0
- package/es/rce/plugins/instructure_icon_maker/components/CreateIconMakerForm/ImageSection/propTypes.js +1 -0
- package/es/rce/plugins/instructure_icon_maker/components/CreateIconMakerForm/ImageSection/utils.js +5 -5
- package/es/rce/plugins/instructure_icon_maker/components/CreateIconMakerForm/Preview.js +7 -8
- package/es/rce/plugins/instructure_icon_maker/components/CreateIconMakerForm/ShapeSection.js +5 -7
- package/es/rce/plugins/instructure_icon_maker/components/CreateIconMakerForm/TextSection.js +5 -10
- package/es/rce/plugins/instructure_icon_maker/components/CreateIconMakerForm/index.js +1 -0
- package/es/rce/plugins/instructure_icon_maker/components/IconMakerTray.js +38 -60
- package/es/rce/plugins/instructure_icon_maker/components/SavedIconMakerList.js +4 -4
- package/es/rce/plugins/instructure_icon_maker/plugin.js +10 -14
- package/es/rce/plugins/instructure_icon_maker/reducers/imageSection.js +37 -38
- package/es/rce/plugins/instructure_icon_maker/reducers/svgSettings.js +24 -24
- package/es/rce/plugins/instructure_icon_maker/registerEditToolbar.js +2 -4
- package/es/rce/plugins/instructure_icon_maker/svg/constants.js +4 -3
- package/es/rce/plugins/instructure_icon_maker/svg/font.js +3 -1
- package/es/rce/plugins/instructure_icon_maker/svg/image.js +74 -90
- package/es/rce/plugins/instructure_icon_maker/svg/index.js +17 -24
- package/es/rce/plugins/instructure_icon_maker/svg/metadata.js +1 -0
- package/es/rce/plugins/instructure_icon_maker/svg/settings.js +48 -58
- package/es/rce/plugins/instructure_icon_maker/svg/shape.js +5 -54
- package/es/rce/plugins/instructure_icon_maker/svg/text.js +35 -124
- package/es/rce/plugins/instructure_icon_maker/svg/utils.js +3 -11
- package/es/rce/plugins/instructure_icon_maker/utils/IconMakerClose.js +4 -9
- package/es/rce/plugins/instructure_icon_maker/utils/IconMakerFormHasChanges.js +1 -15
- package/es/rce/plugins/instructure_icon_maker/utils/addIconMakerAttributes.js +3 -4
- package/es/rce/plugins/instructure_icon_maker/utils/iconValidation.js +2 -3
- package/es/rce/plugins/instructure_icon_maker/utils/iconsLabels.js +1 -0
- package/es/rce/plugins/instructure_icon_maker/utils/useDebouncedValue.js +12 -13
- package/es/rce/plugins/instructure_image/ImageEmbedOptions.js +9 -31
- package/es/rce/plugins/instructure_image/ImageList/Image.js +8 -14
- package/es/rce/plugins/instructure_image/ImageList/index.js +8 -10
- package/es/rce/plugins/instructure_image/ImageOptionsTray/TrayController.js +9 -31
- package/es/rce/plugins/instructure_image/ImageOptionsTray/index.js +6 -19
- package/es/rce/plugins/instructure_image/Images/index.js +1 -3
- package/es/rce/plugins/instructure_image/clickCallback.js +1 -0
- package/es/rce/plugins/instructure_image/plugin.js +14 -20
- package/es/rce/plugins/instructure_links/clickCallback.js +1 -0
- package/es/rce/plugins/instructure_links/components/AccordionSection.js +8 -8
- package/es/rce/plugins/instructure_links/components/CollectionPanel.js +1 -3
- package/es/rce/plugins/instructure_links/components/Link.js +68 -84
- package/es/rce/plugins/instructure_links/components/LinkOptionsDialog/LinkOptionsDialogController.js +2 -23
- package/es/rce/plugins/instructure_links/components/LinkOptionsDialog/index.js +3 -6
- package/es/rce/plugins/instructure_links/components/LinkOptionsTray/LinkOptionsTrayController.js +3 -20
- package/es/rce/plugins/instructure_links/components/LinkOptionsTray/index.js +3 -14
- package/es/rce/plugins/instructure_links/components/LinkSet.js +32 -57
- package/es/rce/plugins/instructure_links/components/LinksPanel.js +22 -10
- package/es/rce/plugins/instructure_links/components/NavigationPanel.js +7 -9
- package/es/rce/plugins/instructure_links/components/NoResults.js +7 -14
- package/es/rce/plugins/instructure_links/plugin.js +23 -49
- package/es/rce/plugins/instructure_links/validateURL.js +81 -36
- package/es/rce/plugins/instructure_media_embed/clickCallback.js +5 -9
- package/es/rce/plugins/instructure_media_embed/components/Embed.js +7 -7
- package/es/rce/plugins/instructure_media_embed/plugin.js +7 -3
- package/es/rce/plugins/instructure_paste/pasteMenuCommand.js +1 -5
- package/es/rce/plugins/instructure_paste/plugin.js +29 -33
- package/es/rce/plugins/instructure_rce_external_tools/ExternalToolsEnv.js +31 -79
- package/es/rce/plugins/instructure_rce_external_tools/RceToolWrapper.js +24 -83
- package/es/rce/plugins/instructure_rce_external_tools/components/ExternalToolDialog/ExternalToolDialog.js +39 -69
- package/es/rce/plugins/instructure_rce_external_tools/components/ExternalToolDialog/ExternalToolDialogModal.js +1 -2
- package/es/rce/plugins/instructure_rce_external_tools/components/ExternalToolDialog/ExternalToolDialogTray.js +1 -1
- package/es/rce/plugins/instructure_rce_external_tools/components/ExternalToolSelectionDialog/ExternalToolSelectionDialog.js +5 -14
- package/es/rce/plugins/instructure_rce_external_tools/components/ExternalToolSelectionDialog/ExternalToolSelectionItem.js +1 -2
- package/es/rce/plugins/instructure_rce_external_tools/components/util/ExpandoText.js +1 -0
- package/es/rce/plugins/instructure_rce_external_tools/components/util/ToolLaunchIframe.js +2 -1
- package/es/rce/plugins/instructure_rce_external_tools/constants.js +28 -0
- package/es/rce/plugins/instructure_rce_external_tools/dialog-helper.js +20 -6
- package/es/rce/plugins/instructure_rce_external_tools/helpers/tags.js +0 -2
- package/es/rce/plugins/instructure_rce_external_tools/jquery/jquery.dropdownList.js +129 -136
- package/es/rce/plugins/instructure_rce_external_tools/lti11-content-items/RceLti11ContentItem.js +110 -112
- package/es/rce/plugins/instructure_rce_external_tools/lti13-content-items/Lti13ContentItemJson.js +1 -0
- package/es/rce/plugins/instructure_rce_external_tools/lti13-content-items/RceLti13ContentItem.js +4 -21
- package/es/rce/plugins/instructure_rce_external_tools/lti13-content-items/models/BaseLinkContentItem.js +5 -19
- package/es/rce/plugins/instructure_rce_external_tools/lti13-content-items/models/HtmlFragmentContentItem.js +1 -6
- package/es/rce/plugins/instructure_rce_external_tools/lti13-content-items/models/ImageContentItem.js +1 -9
- package/es/rce/plugins/instructure_rce_external_tools/lti13-content-items/models/LinkContentItem.js +1 -1
- package/es/rce/plugins/instructure_rce_external_tools/lti13-content-items/models/ResourceLinkContentItem.js +3 -5
- package/es/rce/plugins/instructure_rce_external_tools/lti13-content-items/processEditorContentItems.js +23 -16
- package/es/rce/plugins/instructure_rce_external_tools/lti13-content-items/rceLti13ContentItemFromJson.js +3 -4
- package/es/rce/plugins/instructure_rce_external_tools/plugin.js +11 -20
- package/es/rce/plugins/instructure_rce_external_tools/util/addParentFrameContextToUrl.js +1 -1
- package/es/rce/plugins/instructure_rce_external_tools/util/externalToolsForToolbar.js +42 -0
- package/es/rce/plugins/instructure_record/AudioOptionsTray/TrayController.js +6 -35
- package/es/rce/plugins/instructure_record/AudioOptionsTray/index.js +13 -17
- package/es/rce/plugins/instructure_record/MediaPanel/index.js +1 -9
- package/es/rce/plugins/instructure_record/VideoOptionsTray/TrayController.js +16 -66
- package/es/rce/plugins/instructure_record/VideoOptionsTray/index.js +21 -35
- package/es/rce/plugins/instructure_record/clickCallback.js +32 -44
- package/es/rce/plugins/instructure_record/mediaTranslations.js +1 -0
- package/es/rce/plugins/instructure_record/plugin.js +11 -18
- package/es/rce/plugins/instructure_search_and_replace/clickCallback.js +4 -8
- package/es/rce/plugins/instructure_search_and_replace/components/FindReplaceTray.js +34 -51
- package/es/rce/plugins/instructure_search_and_replace/components/FindReplaceTrayController.js +12 -30
- package/es/rce/plugins/instructure_search_and_replace/getSelectionContext.js +2 -9
- package/es/rce/plugins/instructure_search_and_replace/plugin.js +2 -5
- package/es/rce/plugins/instructure_studio_media_options/plugin.js +1 -1
- package/es/rce/plugins/instructure_wordcount/clickCallback.js +5 -9
- package/es/rce/plugins/instructure_wordcount/components/WordCountModal.js +27 -37
- package/es/rce/plugins/instructure_wordcount/plugin.js +1 -0
- package/es/rce/plugins/instructure_wordcount/utils/countContent.js +4 -11
- package/es/rce/plugins/instructure_wordcount/utils/tableContent.js +6 -8
- package/es/rce/plugins/shared/CanvasContentTray.js +29 -63
- package/es/rce/plugins/shared/CheckerboardStyling.js +1 -1
- package/es/rce/plugins/shared/ColorInput.js +27 -39
- package/es/rce/plugins/shared/ConditionalTooltip.js +6 -6
- package/es/rce/plugins/shared/ContentSelection.js +29 -78
- package/es/rce/plugins/shared/DimensionUtils.js +3 -12
- package/es/rce/plugins/shared/DimensionsInput/DimensionInput.js +6 -6
- package/es/rce/plugins/shared/DimensionsInput/index.js +37 -15
- package/es/rce/plugins/shared/DimensionsInput/useDimensionsState.js +5 -29
- package/es/rce/plugins/shared/ErrorBoundary.js +2 -5
- package/es/rce/plugins/shared/EventUtils.js +2 -4
- package/es/rce/plugins/shared/Filter.js +8 -38
- package/es/rce/plugins/shared/FixedContentTray.js +16 -17
- package/es/rce/plugins/shared/ImageCropper/DirectionRegion.js +4 -12
- package/es/rce/plugins/shared/ImageCropper/Modal.js +16 -20
- package/es/rce/plugins/shared/ImageCropper/Preview.js +18 -24
- package/es/rce/plugins/shared/ImageCropper/constants.js +1 -0
- package/es/rce/plugins/shared/ImageCropper/controls/CustomNumberInput.js +10 -14
- package/es/rce/plugins/shared/ImageCropper/controls/ResetControls.js +4 -4
- package/es/rce/plugins/shared/ImageCropper/controls/RotationControls.js +5 -15
- package/es/rce/plugins/shared/ImageCropper/controls/ShapeControls.js +8 -11
- package/es/rce/plugins/shared/ImageCropper/controls/ZoomControls.js +5 -16
- package/es/rce/plugins/shared/ImageCropper/controls/index.js +5 -5
- package/es/rce/plugins/shared/ImageCropper/controls/useDebouncedNumericValue.js +16 -31
- package/es/rce/plugins/shared/ImageCropper/controls/utils.js +1 -2
- package/es/rce/plugins/shared/ImageCropper/imageCropUtils.js +19 -31
- package/es/rce/plugins/shared/ImageCropper/index.js +1 -0
- package/es/rce/plugins/shared/ImageCropper/propTypes.js +1 -0
- package/es/rce/plugins/shared/ImageCropper/reducers/imageCropper.js +15 -14
- package/es/rce/plugins/shared/ImageCropper/shape.js +1 -0
- package/es/rce/plugins/shared/ImageCropper/svg/index.js +1 -2
- package/es/rce/plugins/shared/ImageCropper/svg/shape.js +5 -22
- package/es/rce/plugins/shared/ImageCropper/svg/utils.js +3 -4
- package/es/rce/plugins/shared/ImageCropper/useKeyMouseEvents.js +20 -50
- package/es/rce/plugins/shared/ImageCropper/useMouseWheel.js +8 -10
- package/es/rce/plugins/shared/ImageOptionsForm.js +18 -20
- package/es/rce/plugins/shared/LinkDisplay.js +9 -11
- package/es/rce/plugins/shared/PreviewIcon.js +9 -15
- package/es/rce/plugins/shared/Previewable.js +1 -0
- package/es/rce/plugins/shared/RceFileBrowser.js +7 -10
- package/es/rce/plugins/shared/StoreContext.js +9 -12
- package/es/rce/plugins/shared/StudioLtiSupportUtils.js +15 -12
- package/es/rce/plugins/shared/UnknownFileTypePanel.js +1 -0
- package/es/rce/plugins/shared/Upload/CanvasContentPanel.js +19 -25
- package/es/rce/plugins/shared/Upload/CategoryProcessor.js +2 -3
- package/es/rce/plugins/shared/Upload/ComputerPanel.js +19 -40
- package/es/rce/plugins/shared/Upload/PanelFilter.js +10 -20
- package/es/rce/plugins/shared/Upload/SvgCategoryProcessor.js +4 -3
- package/es/rce/plugins/shared/Upload/UploadFile.js +32 -38
- package/es/rce/plugins/shared/Upload/UploadFileModal.js +37 -59
- package/es/rce/plugins/shared/Upload/UrlPanel.js +5 -5
- package/es/rce/plugins/shared/Upload/UsageRightsSelectBox.js +25 -36
- package/es/rce/plugins/shared/Upload/doFileUpload.js +10 -13
- package/es/rce/plugins/shared/Upload/index.js +1 -0
- package/es/rce/plugins/shared/ai_tools/AIResponseModal.js +8 -11
- package/es/rce/plugins/shared/ai_tools/AIToolsTray.js +19 -40
- package/es/rce/plugins/shared/ai_tools/aiicons.js +3 -2
- package/es/rce/plugins/shared/ai_tools/index.js +1 -0
- package/es/rce/plugins/shared/buildDownloadUrl.js +0 -2
- package/es/rce/plugins/shared/canvasContentUtils.js +7 -11
- package/es/rce/plugins/shared/compressionUtils.js +18 -28
- package/es/rce/plugins/shared/dateUtils.js +1 -1
- package/es/rce/plugins/shared/do-fetch-api-effect/defaultFetchOptions.js +4 -2
- package/es/rce/plugins/shared/do-fetch-api-effect/doFetchApi.js +18 -24
- package/es/rce/plugins/shared/do-fetch-api-effect/get-cookie.js +1 -1
- package/es/rce/plugins/shared/do-fetch-api-effect/index.js +1 -0
- package/es/rce/plugins/shared/do-fetch-api-effect/parse-link-header.js +6 -20
- package/es/rce/plugins/shared/do-fetch-api-effect/query-string-encoding.js +5 -3
- package/es/rce/plugins/shared/fileShape.js +4 -9
- package/es/rce/plugins/shared/fileTypeUtils.js +34 -47
- package/es/rce/plugins/shared/fileUtils.js +1 -2
- package/es/rce/plugins/shared/linkUtils.js +1 -16
- package/es/rce/plugins/shared/round.js +2 -2
- package/es/rce/plugins/shared/trayUtils.js +7 -3
- package/es/rce/plugins/shared/useDataUrl.js +13 -14
- package/es/rce/plugins/shared/useFilterSettings.js +3 -3
- package/es/rce/plugins/tinymce-a11y-checker/components/ColorField.js +4 -8
- package/es/rce/plugins/tinymce-a11y-checker/components/checker.js +12 -72
- package/es/rce/plugins/tinymce-a11y-checker/components/color-picker.js +1 -2
- package/es/rce/plugins/tinymce-a11y-checker/components/placeholder-svg.js +1 -0
- package/es/rce/plugins/tinymce-a11y-checker/components/pointer.js +1 -0
- package/es/rce/plugins/tinymce-a11y-checker/node-checker.js +2 -9
- package/es/rce/plugins/tinymce-a11y-checker/plugin.js +18 -24
- package/es/rce/plugins/tinymce-a11y-checker/rules/adjacent-links.js +3 -26
- package/es/rce/plugins/tinymce-a11y-checker/rules/headings-sequence.js +9 -38
- package/es/rce/plugins/tinymce-a11y-checker/rules/headings-start-at-h2.js +2 -7
- package/es/rce/plugins/tinymce-a11y-checker/rules/img-alt-filename.js +1 -2
- package/es/rce/plugins/tinymce-a11y-checker/rules/img-alt-length.js +1 -1
- package/es/rce/plugins/tinymce-a11y-checker/rules/img-alt.js +1 -2
- package/es/rce/plugins/tinymce-a11y-checker/rules/index.js +1 -0
- package/es/rce/plugins/tinymce-a11y-checker/rules/large-text-contrast.js +2 -6
- package/es/rce/plugins/tinymce-a11y-checker/rules/list-structure.js +5 -24
- package/es/rce/plugins/tinymce-a11y-checker/rules/paragraphs-for-headings.js +1 -3
- package/es/rce/plugins/tinymce-a11y-checker/rules/small-text-contrast.js +2 -8
- package/es/rce/plugins/tinymce-a11y-checker/rules/table-caption.js +1 -3
- package/es/rce/plugins/tinymce-a11y-checker/rules/table-header-scope.js +1 -2
- package/es/rce/plugins/tinymce-a11y-checker/rules/table-header.js +1 -9
- package/es/rce/plugins/tinymce-a11y-checker/utils/colors.js +1 -0
- package/es/rce/plugins/tinymce-a11y-checker/utils/describe.js +1 -7
- package/es/rce/plugins/tinymce-a11y-checker/utils/dom.js +3 -30
- package/es/rce/plugins/tinymce-a11y-checker/utils/indicate.js +18 -18
- package/es/rce/plugins/tinymce-a11y-checker/utils/rgb-hex.js +6 -12
- package/es/rce/plugins/tinymce-a11y-checker/utils/strings.js +1 -4
- package/es/rce/root.js +17 -16
- package/es/rce/sanitizePlugins.js +1 -3
- package/es/rce/style.js +1 -4
- package/es/rce/tinyRCE.js +14 -9
- package/es/rce/tinymce.oxide.content.min.css.js +1 -0
- package/es/rce/tinymce.oxide.skin.min.css.js +1 -0
- package/es/rce/transformContent.js +9 -11
- package/es/rce/types.js +1 -0
- package/es/rce/userOS.js +1 -1
- package/es/rce/wrapInitCb.js +50 -43
- package/es/rcs/api.js +100 -171
- package/es/rcs/buildError.js +8 -20
- package/es/rcs/fake.js +9 -20
- package/es/sidebar/actions/all_files.js +2 -0
- package/es/sidebar/actions/data.js +4 -7
- package/es/sidebar/actions/documents.js +19 -18
- package/es/sidebar/actions/files.js +21 -28
- package/es/sidebar/actions/filter.js +5 -5
- package/es/sidebar/actions/flickr.js +1 -1
- package/es/sidebar/actions/images.js +32 -37
- package/es/sidebar/actions/links.js +1 -0
- package/es/sidebar/actions/media.js +27 -28
- package/es/sidebar/actions/session.js +2 -5
- package/es/sidebar/actions/ui.js +1 -0
- package/es/sidebar/actions/upload.js +38 -74
- package/es/sidebar/containers/Sidebar.js +1 -2
- package/es/sidebar/containers/sidebarHandlers.js +9 -13
- package/es/sidebar/dragHtml.js +11 -5
- package/es/sidebar/reducers/all_files.js +5 -6
- package/es/sidebar/reducers/collection.js +12 -15
- package/es/sidebar/reducers/collections.js +6 -8
- package/es/sidebar/reducers/documents.js +7 -16
- package/es/sidebar/reducers/files.js +4 -6
- package/es/sidebar/reducers/filter.js +8 -23
- package/es/sidebar/reducers/flickr.js +10 -12
- package/es/sidebar/reducers/folder.js +16 -18
- package/es/sidebar/reducers/folders.js +4 -6
- package/es/sidebar/reducers/images.js +4 -16
- package/es/sidebar/reducers/index.js +3 -1
- package/es/sidebar/reducers/media.js +7 -16
- package/es/sidebar/reducers/newPageLinkExpanded.js +2 -5
- package/es/sidebar/reducers/noop.js +2 -2
- package/es/sidebar/reducers/rootFolderId.js +2 -5
- package/es/sidebar/reducers/session.js +4 -6
- package/es/sidebar/reducers/ui.js +6 -25
- package/es/sidebar/reducers/upload.js +16 -64
- package/es/sidebar/store/configureStore.js +1 -0
- package/es/sidebar/store/initialState.js +14 -26
- package/es/translations/locales/ab.js +1 -0
- package/es/translations/locales/ar.js +72 -8
- package/es/translations/locales/ca.js +72 -8
- package/es/translations/locales/cs.js +1 -0
- package/es/translations/locales/cs_CZ.js +1 -0
- package/es/translations/locales/cy.js +72 -8
- package/es/translations/locales/da-x-k12.js +72 -8
- package/es/translations/locales/da.js +72 -8
- package/es/translations/locales/da_DK.js +1 -0
- package/es/translations/locales/de.js +72 -8
- package/es/translations/locales/el.js +4 -0
- package/es/translations/locales/en-AU-x-unimelb.js +72 -8
- package/es/translations/locales/en-GB-x-ukhe.js +72 -8
- package/es/translations/locales/en.js +72 -8
- package/es/translations/locales/en_AU.js +72 -8
- package/es/translations/locales/en_CA.js +72 -8
- package/es/translations/locales/en_CY.js +72 -8
- package/es/translations/locales/en_GB.js +72 -8
- package/es/translations/locales/en_NZ.js +1 -0
- package/es/translations/locales/en_SE.js +1 -0
- package/es/translations/locales/en_US.js +1 -0
- package/es/translations/locales/es.js +72 -8
- package/es/translations/locales/es_ES.js +72 -8
- package/es/translations/locales/es_GT.js +1 -0
- package/es/translations/locales/fa_IR.js +7 -0
- package/es/translations/locales/fi.js +72 -8
- package/es/translations/locales/fr.js +72 -8
- package/es/translations/locales/fr_CA.js +73 -9
- package/es/translations/locales/ga.js +5 -13
- package/es/translations/locales/he.js +7 -0
- package/es/translations/locales/hi.js +72 -8
- package/es/translations/locales/ht.js +72 -8
- package/es/translations/locales/hu.js +7 -6
- package/es/translations/locales/hu_HU.js +1 -0
- package/es/translations/locales/hy.js +1 -0
- package/es/translations/locales/id.js +72 -8
- package/es/translations/locales/id_ID.js +1 -0
- package/es/translations/locales/is.js +72 -8
- package/es/translations/locales/it.js +72 -8
- package/es/translations/locales/ja.js +72 -8
- package/es/translations/locales/ko.js +1 -0
- package/es/translations/locales/ko_KR.js +1 -0
- package/es/translations/locales/lt.js +1 -0
- package/es/translations/locales/lt_LT.js +1 -0
- package/es/translations/locales/mi.js +72 -8
- package/es/translations/locales/mn_MN.js +1 -0
- package/es/translations/locales/ms.js +72 -8
- package/es/translations/locales/nb-x-k12.js +72 -8
- package/es/translations/locales/nb.js +72 -8
- package/es/translations/locales/nl.js +72 -8
- package/es/translations/locales/nl_NL.js +1 -0
- package/es/translations/locales/nn.js +7 -6
- package/es/translations/locales/pl.js +72 -8
- package/es/translations/locales/pt.js +72 -8
- package/es/translations/locales/pt_BR.js +72 -8
- package/es/translations/locales/ro.js +1 -0
- package/es/translations/locales/ru.js +72 -8
- package/es/translations/locales/se.js +1 -0
- package/es/translations/locales/sl.js +72 -8
- package/es/translations/locales/sv-x-k12.js +72 -8
- package/es/translations/locales/sv.js +72 -8
- package/es/translations/locales/sv_SE.js +1 -0
- package/es/translations/locales/tg.js +1 -0
- package/es/translations/locales/th.js +72 -8
- package/es/translations/locales/th_TH.js +1 -0
- package/es/translations/locales/tl_PH.js +1 -0
- package/es/translations/locales/tr.js +7 -0
- package/es/translations/locales/uk_UA.js +7 -0
- package/es/translations/locales/vi.js +72 -8
- package/es/translations/locales/vi_VN.js +1 -0
- package/es/translations/locales/zh-Hans.js +72 -8
- package/es/translations/locales/zh-Hant.js +72 -8
- package/es/translations/locales/zh.js +72 -8
- package/es/translations/locales/zh_HK.js +72 -8
- package/es/translations/locales/zh_TW.Big5.js +1 -0
- package/es/translations/locales/zh_TW.js +1 -0
- package/es/translations/tinymce/ar_SA.js +1 -0
- package/es/translations/tinymce/fi.js +1 -0
- package/es/translations/tinymce/ga.js +1 -0
- package/es/translations/tinymce/id.js +1 -0
- package/es/translations/tinymce/ru.js +1 -0
- package/es/translations/tinymce/ru_RU.js +1 -0
- package/es/translations/tinymce/sl.js +1 -0
- package/es/translations/tinymce/sr.js +1 -0
- package/es/translations/tinymce/th.js +1 -0
- package/es/translations/tinymce/uk_UA.js +1 -0
- package/es/translations/tinymce/vi_VN.js +1 -0
- package/es/util/TypedDict.js +4 -2
- package/es/util/elem-util.js +1 -1
- package/es/util/encrypted-storage.js +3 -13
- package/es/util/file-url-util.js +2 -7
- package/es/util/fullscreenHelpers.js +9 -9
- package/es/util/instui-icon-helper.js +4 -3
- package/es/util/loadingPlaceholder.js +39 -41
- package/es/util/simpleCache.js +1 -5
- package/es/util/string-util.js +1 -1
- package/es/util/textarea-editing-util.js +3 -7
- package/es/util/tinymce-plugin-util.js +0 -5
- package/es/util/url-util.js +20 -29
- package/eslint.config.js +250 -0
- package/jest.config.js +1 -1
- package/locales/en.json +190 -10
- package/package.json +78 -82
- package/scripts/build-canvas +2 -1
- package/scripts/build.js +4 -4
- package/scripts/installTranslations.js +7 -8
- package/testcafe/RCEWrapper.test.js +0 -1
- package/testcafe/StatusBar.test.js +0 -1
- package/testcafe/axe.test.js +3 -4
- package/testcafe/enhanceUserContent.test.js +0 -1
- package/tsconfig.json +21 -16
- package/{es/rce/__mocks__/styleMock.js → types/format-message-generate-id.d.ts} +6 -2
- package/{es/rce/plugins/shared/__mocks__/screenfull.js → types/js-beautify.d.ts} +4 -7
- package/.eslintrc +0 -45
- package/.prettierignore +0 -6
- package/es/rce/__mocks__/_mockCryptoEs.js +0 -124
- package/es/rce/__mocks__/tinymceReact.js +0 -55
- package/es/rce/plugins/tinymce-a11y-checker/rules/__mocks__/index.js +0 -53
package/es/rce/RCEWrapper.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import _pt from "prop-types";
|
|
3
2
|
/*
|
|
4
3
|
* Copyright (C) 2018 - present Instructure, Inc.
|
|
5
4
|
*
|
|
@@ -17,6 +16,7 @@ var _Intl, _Intl$DateTimeFormat, _Intl$DateTimeFormat$;
|
|
|
17
16
|
* You should have received a copy of the GNU Affero General Public License along
|
|
18
17
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
19
18
|
*/
|
|
19
|
+
|
|
20
20
|
import React, { Suspense } from 'react';
|
|
21
21
|
import { Editor } from '@tinymce/tinymce-react';
|
|
22
22
|
import _ from 'lodash';
|
|
@@ -32,12 +32,12 @@ import getCookie from '../common/getCookie';
|
|
|
32
32
|
import formatMessage from '../format-message';
|
|
33
33
|
import * as contentInsertion from './contentInsertion';
|
|
34
34
|
import indicatorRegion from './indicatorRegion';
|
|
35
|
-
import editorLanguage from './editorLanguage';
|
|
35
|
+
import { editorLanguage } from './editorLanguage';
|
|
36
36
|
import normalizeLocale from './normalizeLocale';
|
|
37
37
|
import { sanitizePlugins } from './sanitizePlugins';
|
|
38
38
|
import RCEGlobals from './RCEGlobals';
|
|
39
39
|
import defaultTinymceConfig from '../defaultTinymceConfig';
|
|
40
|
-
import { FS_CHANGEEVENT, FS_ELEMENT, FS_ENABLED, FS_EXIT, FS_REQUEST,
|
|
40
|
+
import { FS_CHANGEEVENT, FS_ELEMENT, FS_ENABLED, FS_EXIT, FS_REQUEST, instuiPopupMountNodeFn } from '../util/fullscreenHelpers';
|
|
41
41
|
import indicate from '../common/indicate';
|
|
42
42
|
import bridge from '../bridge';
|
|
43
43
|
import CanvasContentTray from './plugins/shared/CanvasContentTray';
|
|
@@ -56,74 +56,50 @@ import contentCSS from './tinymce.oxide.content.min.css';
|
|
|
56
56
|
import { rceWrapperPropTypes } from './RCEWrapperProps';
|
|
57
57
|
import { insertPlaceholder, placeholderInfoFor, removePlaceholder } from '../util/loadingPlaceholder';
|
|
58
58
|
import { transformRceContentForEditing } from './transformContent';
|
|
59
|
+
// @ts-expect-error
|
|
59
60
|
import { IconMoreSolid } from '@instructure/ui-icons/es/svg';
|
|
60
61
|
import EncryptedStorage from '../util/encrypted-storage';
|
|
61
62
|
import buildStyle from './style';
|
|
62
|
-
import { externalToolsForToolbar } from './plugins/instructure_rce_external_tools/RceToolWrapper';
|
|
63
63
|
import { getMenubarForVariant, getMenuForVariant, getToolbarForVariant, getStatusBarFeaturesForVariant } from './RCEVariants';
|
|
64
|
+
import { focusFirstMenuButton, focusToolbar, isElementWithinTable, mergeMenu, mergeMenuItems, mergePlugins, mergeToolbar, parsePluginsToExclude, patchAutosavedContent } from './RCEWrapper.utils';
|
|
65
|
+
import { externalToolsForToolbar } from './plugins/instructure_rce_external_tools/util/externalToolsForToolbar';
|
|
64
66
|
const RestoreAutoSaveModal = /*#__PURE__*/React.lazy(() => import('./RestoreAutoSaveModal'));
|
|
65
67
|
const RceHtmlEditor = /*#__PURE__*/React.lazy(() => import('./RceHtmlEditor'));
|
|
66
68
|
const ASYNC_FOCUS_TIMEOUT = 250;
|
|
67
69
|
const DEFAULT_RCE_HEIGHT = '400px';
|
|
68
|
-
|
|
69
70
|
function addKebabIcon(editor) {
|
|
70
71
|
// This has to be done here instead of of in plugins/instructure-ui-icons/plugin.ts
|
|
71
72
|
// presumably because the toolbar gets created before that plugin is loaded?
|
|
72
73
|
editor.ui.registry.addIcon('more-drawer', IconMoreSolid.src);
|
|
73
|
-
}
|
|
74
|
-
|
|
74
|
+
}
|
|
75
75
|
|
|
76
|
+
// Get oxide the default skin injected into the DOM before the overrides loaded by themeable
|
|
76
77
|
let inserted = false;
|
|
77
|
-
|
|
78
78
|
function injectTinySkin() {
|
|
79
79
|
if (inserted) return;
|
|
80
80
|
inserted = true;
|
|
81
81
|
const style = document.createElement('style');
|
|
82
82
|
style.setAttribute('data-skin', 'tiny oxide skin');
|
|
83
|
-
style.appendChild(document.createTextNode(skinCSS));
|
|
83
|
+
style.appendChild(document.createTextNode(skinCSS));
|
|
84
|
+
// there's CSS from discussions that turns the instui Selectors bold
|
|
84
85
|
// and in classic quizzes that also mucks with padding
|
|
85
|
-
|
|
86
86
|
style.appendChild(document.createTextNode(`
|
|
87
87
|
#discussion-edit-view .rce-wrapper input[readonly] {font-weight: normal;}
|
|
88
88
|
#quiz_edit_wrapper .rce-wrapper input[readonly] {font-weight: normal; padding-left: .75rem;}
|
|
89
89
|
`));
|
|
90
|
-
const beforeMe = document.head.querySelector('style[data-glamor]') ||
|
|
91
|
-
|
|
90
|
+
const beforeMe = document.head.querySelector('style[data-glamor]') ||
|
|
91
|
+
// find instui's themeable stylesheet
|
|
92
|
+
document.head.querySelector('style') ||
|
|
93
|
+
// find any stylesheet
|
|
92
94
|
document.head.firstElementChild;
|
|
93
95
|
document.head.insertBefore(style, beforeMe);
|
|
94
96
|
}
|
|
95
|
-
|
|
96
97
|
const editorWrappers = new WeakMap();
|
|
97
98
|
|
|
98
|
-
|
|
99
|
-
const $firstToolbarButton = el.querySelector('.tox-tbtn');
|
|
100
|
-
$firstToolbarButton && $firstToolbarButton.focus();
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function focusFirstMenuButton(el) {
|
|
104
|
-
const $firstMenu = el.querySelector('.tox-mbtn');
|
|
105
|
-
$firstMenu && $firstMenu.focus();
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function isElementWithinTable(node) {
|
|
109
|
-
let elem = node;
|
|
110
|
-
|
|
111
|
-
while (elem) {
|
|
112
|
-
if (elem.tagName === 'TABLE' || elem.tagName === 'TD' || elem.tagName === 'TH') {
|
|
113
|
-
return true;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
elem = elem.parentElement;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
return false;
|
|
120
|
-
} // determines if localStorage is available for our use.
|
|
99
|
+
// determines if localStorage is available for our use.
|
|
121
100
|
// see https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API/Using_the_Web_Storage_API
|
|
122
|
-
|
|
123
|
-
|
|
124
101
|
export function storageAvailable() {
|
|
125
102
|
let storage;
|
|
126
|
-
|
|
127
103
|
try {
|
|
128
104
|
storage = window.localStorage;
|
|
129
105
|
const x = '__storage_test__';
|
|
@@ -131,163 +107,174 @@ export function storageAvailable() {
|
|
|
131
107
|
storage.removeItem(x);
|
|
132
108
|
return true;
|
|
133
109
|
} catch (e) {
|
|
134
|
-
return e instanceof DOMException && (
|
|
135
|
-
e.code === 22 || // Firefox
|
|
136
|
-
e.code === 1014 || // test name field too, because code might not be present
|
|
110
|
+
return e instanceof DOMException && (
|
|
137
111
|
// everything except Firefox
|
|
138
|
-
e.
|
|
139
|
-
|
|
112
|
+
e.code === 22 ||
|
|
113
|
+
// Firefox
|
|
114
|
+
e.code === 1014 ||
|
|
115
|
+
// test name field too, because code might not be present
|
|
116
|
+
// everything except Firefox
|
|
117
|
+
e.name === 'QuotaExceededError' ||
|
|
118
|
+
// Firefox
|
|
119
|
+
e.name === 'NS_ERROR_DOM_QUOTA_REACHED') &&
|
|
120
|
+
// acknowledge QuotaExceededError only if there's something already stored
|
|
140
121
|
storage && storage.length !== 0;
|
|
141
122
|
}
|
|
142
123
|
}
|
|
143
|
-
|
|
144
124
|
function renderLoading() {
|
|
145
125
|
return formatMessage('Loading');
|
|
146
126
|
}
|
|
147
|
-
|
|
148
127
|
let alertIdValue = 0;
|
|
149
|
-
|
|
150
128
|
class RCEWrapper extends React.Component {
|
|
151
129
|
static getByEditor(editor) {
|
|
152
130
|
return editorWrappers.get(editor);
|
|
153
131
|
}
|
|
154
|
-
|
|
155
132
|
constructor(props) {
|
|
156
|
-
var _this, _window, _window$location, _props$editorOptions, _props$editorOptions2;
|
|
157
|
-
|
|
158
133
|
super(props);
|
|
159
|
-
|
|
160
|
-
|
|
134
|
+
this._destroyCalled = false;
|
|
135
|
+
this._editorPlaceholderRef = void 0;
|
|
136
|
+
this._elementRef = void 0;
|
|
137
|
+
this._focusRegio = void 0;
|
|
138
|
+
this._focusRegion = void 0;
|
|
139
|
+
this._mceSerializedInitialHtmlCached = void 0;
|
|
140
|
+
this._showOnFocusButton = void 0;
|
|
141
|
+
this._statusBarId = void 0;
|
|
142
|
+
this._textareaEl = void 0;
|
|
143
|
+
this.AIToolsTray = void 0;
|
|
144
|
+
this.editor = void 0;
|
|
145
|
+
this.initialContent = void 0;
|
|
146
|
+
this.intersectionObserver = void 0;
|
|
147
|
+
this.language = void 0;
|
|
148
|
+
this.ltiToolFavorites = void 0;
|
|
149
|
+
this.mutationObserver = void 0;
|
|
150
|
+
this.pendingEventHandlers = void 0;
|
|
151
|
+
this.pluginsToExclude = void 0;
|
|
152
|
+
this.resizeObserver = void 0;
|
|
153
|
+
this.storage = void 0;
|
|
154
|
+
this.variant = void 0;
|
|
155
|
+
this.style = void 0;
|
|
156
|
+
this.insert_code = void 0;
|
|
157
|
+
this.get_code = void 0;
|
|
158
|
+
this.set_code = void 0;
|
|
161
159
|
this.onRemove = () => {
|
|
162
160
|
bridge.detachEditor(this);
|
|
163
|
-
|
|
161
|
+
if (this.props.onRemove) {
|
|
162
|
+
this.props.onRemove(this);
|
|
163
|
+
}
|
|
164
164
|
};
|
|
165
|
-
|
|
166
165
|
this.toggleView = newView => {
|
|
167
166
|
// coming from the menubar, we don't have a newView,
|
|
168
|
-
let newState;
|
|
169
167
|
|
|
168
|
+
let newState;
|
|
170
169
|
switch (this.state.editorView) {
|
|
171
170
|
case WYSIWYG_VIEW:
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
171
|
+
{
|
|
172
|
+
newState = {
|
|
173
|
+
editorView: newView || PRETTY_HTML_EDITOR_VIEW
|
|
174
|
+
};
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
177
|
case PRETTY_HTML_EDITOR_VIEW:
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
178
|
+
{
|
|
179
|
+
newState = {
|
|
180
|
+
editorView: newView || WYSIWYG_VIEW
|
|
181
|
+
};
|
|
182
|
+
break;
|
|
183
|
+
}
|
|
183
184
|
case RAW_HTML_EDITOR_VIEW:
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
185
|
+
{
|
|
186
|
+
newState = {
|
|
187
|
+
editorView: newView || WYSIWYG_VIEW
|
|
188
|
+
};
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
default:
|
|
192
|
+
return;
|
|
187
193
|
}
|
|
188
|
-
|
|
194
|
+
// @ts-expect-error
|
|
189
195
|
this.setState(newState);
|
|
190
196
|
this.checkAccessibility();
|
|
191
|
-
|
|
192
197
|
if (newView === PRETTY_HTML_EDITOR_VIEW || newView === RAW_HTML_EDITOR_VIEW) {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
(_this$storage = this.storage) === null || _this$storage === void 0 ? void 0 : (_this$storage$setItem = _this$storage.setItem) === null || _this$storage$setItem === void 0 ? void 0 : _this$storage$setItem.call(_this$storage, 'rce.htmleditor', newView);
|
|
196
|
-
} // Emit view change event
|
|
197
|
-
|
|
198
|
+
this.storage?.setItem?.('rce.htmleditor', newView);
|
|
199
|
+
}
|
|
198
200
|
|
|
201
|
+
// Emit view change event
|
|
199
202
|
this.mceInstance().fire(VIEW_CHANGE, {
|
|
200
203
|
target: this.editor,
|
|
201
204
|
newView: newState.editorView
|
|
202
205
|
});
|
|
203
206
|
};
|
|
204
|
-
|
|
205
207
|
this.toggleFullscreen = () => {
|
|
206
208
|
this.handleClickFullscreen();
|
|
207
209
|
};
|
|
208
|
-
|
|
209
210
|
this._onFullscreenChange = event => {
|
|
210
211
|
if (document[FS_ELEMENT]) {
|
|
211
|
-
|
|
212
|
-
|
|
212
|
+
// @ts-expect-error
|
|
213
213
|
this.resizeObserver.observe(document[FS_ELEMENT]);
|
|
214
|
-
|
|
215
|
-
|
|
214
|
+
window.visualViewport?.addEventListener('resize', this._handleFullscreenResize);
|
|
216
215
|
this._handleFullscreenResize();
|
|
217
|
-
|
|
216
|
+
// @ts-expect-error
|
|
218
217
|
this._focusRegion = FocusRegionManager.activateRegion(document[FS_ELEMENT], {
|
|
219
218
|
shouldContainFocus: true
|
|
220
219
|
});
|
|
221
220
|
} else {
|
|
222
|
-
var _window$visualViewpor2;
|
|
223
|
-
|
|
224
221
|
event.target.removeEventListener(FS_CHANGEEVENT, this._onFullscreenChange);
|
|
225
222
|
this.resizeObserver.unobserve(event.target);
|
|
226
|
-
|
|
227
|
-
|
|
223
|
+
window.visualViewport?.removeEventListener('resize', this._handleFullscreenResize);
|
|
228
224
|
this._setHeight(this.state.fullscreenState.prevHeight);
|
|
229
|
-
|
|
230
225
|
if (this._focusRegion) {
|
|
231
226
|
FocusRegionManager.blurRegion(event.target, this._focusRegion.id);
|
|
232
227
|
}
|
|
233
228
|
}
|
|
234
|
-
|
|
235
229
|
this.focusCurrentView();
|
|
236
230
|
};
|
|
237
|
-
|
|
238
231
|
this._handleFullscreenResize = () => {
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
const ht = ((_window$visualViewpor3 = window.visualViewport) === null || _window$visualViewpor3 === void 0 ? void 0 : _window$visualViewpor3.height) || ((_document$FS_ELEMENT = document[FS_ELEMENT]) === null || _document$FS_ELEMENT === void 0 ? void 0 : _document$FS_ELEMENT.offsetHeight);
|
|
242
|
-
|
|
243
|
-
this._setHeight(ht - this._getStatusBarHeight());
|
|
232
|
+
const ht = window.visualViewport?.height || document[FS_ELEMENT]?.offsetHeight;
|
|
233
|
+
this._setHeight((ht || 0) - this._getStatusBarHeight());
|
|
244
234
|
};
|
|
245
|
-
|
|
246
235
|
this.contentTrayClosing = false;
|
|
247
236
|
this.blurTimer = 0;
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
this.handleFocus(event);
|
|
237
|
+
this.handleFocusRCE = () => {
|
|
238
|
+
this.handleFocus();
|
|
251
239
|
};
|
|
252
|
-
|
|
253
240
|
this.handleBlurRCE = event => {
|
|
254
|
-
var _this$_elementRef$cur;
|
|
255
|
-
|
|
256
241
|
if (event.relatedTarget === null) {
|
|
257
242
|
// focus might be moving to tinymce
|
|
258
243
|
this.handleBlur(event);
|
|
259
244
|
}
|
|
260
|
-
|
|
261
|
-
if (!((_this$_elementRef$cur = this._elementRef.current) !== null && _this$_elementRef$cur !== void 0 && _this$_elementRef$cur.contains(event.relatedTarget))) {
|
|
245
|
+
if (!this._elementRef.current?.contains(event.relatedTarget)) {
|
|
262
246
|
this.handleBlur(event);
|
|
263
247
|
}
|
|
264
248
|
};
|
|
265
|
-
|
|
266
|
-
this.handleFocusEditor = (event, _editor) => {
|
|
249
|
+
this.handleFocusEditor = _event => {
|
|
267
250
|
// use .active to put a focus ring around the content area
|
|
268
251
|
// when the editor has focus. This isn't perfect, but it's
|
|
269
252
|
// what we've got for now.
|
|
270
253
|
const ifr = this.iframe;
|
|
271
|
-
|
|
272
|
-
|
|
254
|
+
if (ifr?.parentElement) {
|
|
255
|
+
ifr.parentElement.classList.add('active');
|
|
256
|
+
}
|
|
257
|
+
this.handleFocus();
|
|
273
258
|
};
|
|
274
|
-
|
|
275
|
-
this.handleBlurEditor = (event, _editor) => {
|
|
259
|
+
this.handleBlurEditor = event => {
|
|
276
260
|
const ifr = this.iframe;
|
|
277
|
-
|
|
261
|
+
if (ifr?.parentElement) {
|
|
262
|
+
ifr.parentElement.classList.remove('active');
|
|
263
|
+
}
|
|
278
264
|
this.handleBlur(event);
|
|
279
265
|
};
|
|
280
|
-
|
|
281
266
|
this.handleKey = event => {
|
|
282
267
|
if (event.code === 'F9' && event.altKey) {
|
|
283
268
|
event.preventDefault();
|
|
284
269
|
event.stopPropagation();
|
|
285
270
|
this.setFocusAbilityForHeader(true);
|
|
271
|
+
// @ts-expect-error
|
|
286
272
|
focusFirstMenuButton(this._elementRef.current);
|
|
287
273
|
} else if (event.code === 'F10' && event.altKey) {
|
|
288
274
|
event.preventDefault();
|
|
289
275
|
event.stopPropagation();
|
|
290
276
|
this.setFocusAbilityForHeader(true);
|
|
277
|
+
// @ts-expect-error
|
|
291
278
|
focusToolbar(this._elementRef.current);
|
|
292
279
|
} else if (event.code === 'F8' && event.altKey) {
|
|
293
280
|
event.preventDefault();
|
|
@@ -300,7 +287,6 @@ class RCEWrapper extends React.Component {
|
|
|
300
287
|
event.stopPropagation();
|
|
301
288
|
}
|
|
302
289
|
};
|
|
303
|
-
|
|
304
290
|
this.handleClickFullscreen = () => {
|
|
305
291
|
if (this._isFullscreen()) {
|
|
306
292
|
this._exitFullscreen();
|
|
@@ -308,127 +294,122 @@ class RCEWrapper extends React.Component {
|
|
|
308
294
|
this._enterFullscreen();
|
|
309
295
|
}
|
|
310
296
|
};
|
|
311
|
-
|
|
312
297
|
this.handleInputChange = () => {
|
|
313
298
|
this.checkAccessibility();
|
|
314
299
|
};
|
|
315
|
-
|
|
316
300
|
this.onInit = (_event, editor) => {
|
|
317
|
-
|
|
318
|
-
|
|
301
|
+
// @ts-expect-error
|
|
319
302
|
editor.rceWrapper = this;
|
|
320
303
|
this.editor = editor;
|
|
321
|
-
const textarea = this.editor.getElement();
|
|
304
|
+
const textarea = this.editor.getElement();
|
|
322
305
|
|
|
323
|
-
|
|
306
|
+
// expected by canvas
|
|
307
|
+
// @ts-expect-error
|
|
308
|
+
textarea.dataset.rich_text = true;
|
|
324
309
|
|
|
310
|
+
// start with the textarea and tinymce in sync
|
|
311
|
+
// @ts-expect-error
|
|
325
312
|
textarea.value = this.getCode();
|
|
326
313
|
textarea.style.height = this.state.height;
|
|
327
|
-
|
|
328
314
|
if (document.body.classList.contains('Underline-All-Links__enabled')) {
|
|
329
|
-
this.iframe
|
|
315
|
+
if (this.iframe?.contentDocument) {
|
|
316
|
+
this.iframe.contentDocument.body.classList.add('Underline-All-Links__enabled');
|
|
317
|
+
}
|
|
330
318
|
}
|
|
331
|
-
|
|
332
|
-
|
|
319
|
+
editor.on('wordCountUpdate', this.onWordCountUpdate);
|
|
320
|
+
// add an aria-label to the application div that wraps RCE
|
|
333
321
|
// and change role from "application" to "document" to ensure
|
|
334
322
|
// the editor gets properly picked up by screen readers
|
|
335
|
-
|
|
336
323
|
const tinyapp = document.querySelector('.tox-tinymce[role="application"]');
|
|
337
|
-
|
|
338
324
|
if (tinyapp) {
|
|
339
325
|
tinyapp.setAttribute('aria-label', formatMessage('Rich Content Editor'));
|
|
340
326
|
tinyapp.setAttribute('role', 'document');
|
|
341
327
|
tinyapp.setAttribute('tabIndex', '-1');
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
const header = this._elementRef.current.querySelector('.tox-editor-header');
|
|
328
|
+
}
|
|
346
329
|
|
|
330
|
+
// Adds a focusout event listener for handling screen reader navigation focus
|
|
331
|
+
const header = this._elementRef.current?.querySelector('.tox-editor-header');
|
|
347
332
|
if (header) {
|
|
333
|
+
// @ts-expect-error
|
|
348
334
|
header.addEventListener('focusout', e => {
|
|
335
|
+
// @ts-expect-error
|
|
349
336
|
const leavingHeader = !header.contains(e.relatedTarget);
|
|
350
|
-
|
|
351
337
|
if (leavingHeader) {
|
|
352
338
|
this.setFocusAbilityForHeader(false);
|
|
353
339
|
}
|
|
354
340
|
});
|
|
355
341
|
}
|
|
342
|
+
this.setFocusAbilityForHeader(false);
|
|
356
343
|
|
|
357
|
-
|
|
358
|
-
|
|
344
|
+
// Probably should do this in tinymce.scss, but we only want it in new rce
|
|
359
345
|
textarea.style.resize = 'none';
|
|
360
346
|
editor.on('keydown', this.handleKey);
|
|
361
|
-
editor.on('FullscreenStateChanged', this._onFullscreenChange);
|
|
347
|
+
editor.on('FullscreenStateChanged', this._onFullscreenChange);
|
|
348
|
+
// This propagates click events on the editor out of the iframe to the parent
|
|
362
349
|
// document. We need this so that click events get captured properly by instui
|
|
363
350
|
// focus-trapping components, so they properly ignore trapping focus on click.
|
|
364
|
-
|
|
365
351
|
editor.on('click', () => window.document.body.click(), true);
|
|
366
352
|
editor.on('Cut Change input Undo Redo', debounce(this.handleInputChange, 1000));
|
|
367
353
|
this.announceContextToolbars(editor);
|
|
368
|
-
|
|
369
354
|
if (this.isAutoSaving) {
|
|
370
355
|
this.initAutoSave(editor);
|
|
371
|
-
}
|
|
356
|
+
}
|
|
372
357
|
|
|
358
|
+
// first view
|
|
359
|
+
this.setEditorView(this.state.editorView);
|
|
373
360
|
|
|
374
|
-
|
|
361
|
+
// readonly should have been handled via the init property passed
|
|
375
362
|
// to <Editor>, but it's not.
|
|
363
|
+
editor.mode.set(this.props.readOnly ? 'readonly' : 'design');
|
|
376
364
|
|
|
377
|
-
|
|
365
|
+
// Not using iframe_aria_text because compatibility issues.
|
|
378
366
|
// Not using iframe_attrs because library overwriting.
|
|
379
|
-
|
|
380
367
|
if (this.iframe) {
|
|
381
368
|
this.iframe.setAttribute('title', formatMessage('Rich Text Area. Press {OSKey}+F8 for Rich Content Editor shortcuts.', {
|
|
382
369
|
OSKey: determineOSDependentKey()
|
|
383
370
|
}));
|
|
384
371
|
}
|
|
385
|
-
|
|
386
372
|
this._setupSelectionSaving(editor);
|
|
387
|
-
|
|
388
373
|
this.checkAccessibility();
|
|
389
374
|
this.fixToolbarKeyboardNavigation();
|
|
390
|
-
|
|
375
|
+
if (this.props.onInitted) {
|
|
376
|
+
this.props.onInitted(editor);
|
|
377
|
+
}
|
|
391
378
|
|
|
379
|
+
// cleans up highlight artifacts from findreplace plugin
|
|
392
380
|
if (this.getRequiredFeatureStatuses().rce_find_replace) {
|
|
393
|
-
editor.on('undo redo',
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
if ((editor === null || editor === void 0 ? void 0 : (_editor$dom = editor.dom) === null || _editor$dom === void 0 ? void 0 : (_editor$dom$doc = _editor$dom.doc) === null || _editor$dom$doc === void 0 ? void 0 : (_editor$dom$doc$getEl = _editor$dom$doc.getElementsByClassName) === null || _editor$dom$doc$getEl === void 0 ? void 0 : (_editor$dom$doc$getEl2 = _editor$dom$doc$getEl.call(_editor$dom$doc, 'mce-match-marker')) === null || _editor$dom$doc$getEl2 === void 0 ? void 0 : _editor$dom$doc$getEl2.length) > 0) {
|
|
397
|
-
var _editor$plugins, _editor$plugins$searc;
|
|
398
|
-
|
|
399
|
-
(_editor$plugins = editor.plugins) === null || _editor$plugins === void 0 ? void 0 : (_editor$plugins$searc = _editor$plugins.searchreplace) === null || _editor$plugins$searc === void 0 ? void 0 : _editor$plugins$searc.done();
|
|
381
|
+
editor.on('undo redo', _e => {
|
|
382
|
+
if (editor?.dom?.doc?.getElementsByClassName?.('mce-match-marker')?.length > 0) {
|
|
383
|
+
editor.plugins?.searchreplace?.done();
|
|
400
384
|
}
|
|
401
385
|
});
|
|
402
386
|
}
|
|
403
387
|
};
|
|
404
|
-
|
|
405
388
|
this.fixToolbarKeyboardNavigation = () => {
|
|
406
|
-
var _this$_elementRef$cur2;
|
|
407
|
-
|
|
408
389
|
// The keyboard navigation config in tinymce for the expanded toolbar is incorrectly configured,
|
|
409
390
|
// and stops at [data-alloy-tabstop] elements.
|
|
410
391
|
// It should be configured to stop on .tox-toolbar__group elements.
|
|
411
392
|
// This workaround removes attribute, thusly causing navigation to work correctly again.
|
|
412
393
|
// For the correct solution, Keying.config should have { selector: '.tox-toolbar__group' }
|
|
413
394
|
// in https://github.com/tinymce/tinymce/blob/develop/modules/alloy/src/main/ts/ephox/alloy/ui/schema/SplitSlidingToolbarSchema.ts
|
|
414
|
-
|
|
395
|
+
this._elementRef.current?.querySelectorAll('.tox-toolbar-overlord button[data-alloy-tabstop]').forEach(it => it.removeAttribute('data-alloy-tabstop'));
|
|
415
396
|
};
|
|
416
|
-
|
|
417
397
|
this._setupSelectionSaving = editor => {
|
|
398
|
+
// @ts-expect-error
|
|
418
399
|
let savedSelection = null;
|
|
419
400
|
let selectionWasReset = false;
|
|
420
401
|
let editorHasFocus = false;
|
|
421
|
-
|
|
422
402
|
const restoreSelectionIfNecessary = () => {
|
|
423
|
-
|
|
403
|
+
// @ts-expect-error
|
|
404
|
+
if (this.editor && savedSelection && selectionWasReset) {
|
|
424
405
|
this.editor.selection.setRng(savedSelection.range, savedSelection.isForward);
|
|
425
406
|
selectionWasReset = false;
|
|
426
407
|
}
|
|
427
408
|
};
|
|
428
|
-
|
|
429
409
|
editor.on('blur', () => {
|
|
430
410
|
editorHasFocus = false;
|
|
431
411
|
selectionWasReset = false;
|
|
412
|
+
if (!this.editor) return;
|
|
432
413
|
savedSelection = {
|
|
433
414
|
range: this.editor.selection.getRng().cloneRange(),
|
|
434
415
|
isForward: this.editor.selection.isForward()
|
|
@@ -444,24 +425,25 @@ class RCEWrapper extends React.Component {
|
|
|
444
425
|
selectionWasReset = false;
|
|
445
426
|
});
|
|
446
427
|
editor.on('SelectionChange', () => {
|
|
447
|
-
var _selection$startConta;
|
|
448
|
-
|
|
449
428
|
if (editorHasFocus) {
|
|
450
429
|
// We don't care if a selection reset occurs when the editor has focus, the user probably intended that
|
|
451
430
|
// At least they will see the effect
|
|
452
431
|
return;
|
|
453
432
|
}
|
|
433
|
+
if (!this.editor) return;
|
|
434
|
+
const selection = this.editor.selection.normalize();
|
|
454
435
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
if (((_selection$startConta = selection.startContainer) === null || _selection$startConta === void 0 ? void 0 : _selection$startConta.nodeName) === 'BODY' && selection.startContainer === selection.endContainer && selection.startOffset === 0 && selection.endOffset === 0) {
|
|
436
|
+
// Detect a browser-reset selection (e.g. From invoking the Find command)
|
|
437
|
+
if (selection.startContainer?.nodeName === 'BODY' && selection.startContainer === selection.endContainer && selection.startOffset === 0 && selection.endOffset === 0) {
|
|
458
438
|
selectionWasReset = true;
|
|
459
439
|
}
|
|
460
440
|
});
|
|
461
441
|
editor.on('BeforeExecCommand', () => {
|
|
462
442
|
restoreSelectionIfNecessary();
|
|
463
443
|
});
|
|
464
|
-
editor.on('ExecCommand', (
|
|
444
|
+
editor.on('ExecCommand', (/* event */
|
|
445
|
+
) => {
|
|
446
|
+
if (!this.editor) return;
|
|
465
447
|
// Commands may have modified the selection, we need to recapture it
|
|
466
448
|
savedSelection = {
|
|
467
449
|
range: this.editor.selection.getRng().cloneRange(),
|
|
@@ -469,35 +451,29 @@ class RCEWrapper extends React.Component {
|
|
|
469
451
|
};
|
|
470
452
|
});
|
|
471
453
|
};
|
|
472
|
-
|
|
473
454
|
this.announcing = 0;
|
|
474
|
-
|
|
475
455
|
this.initAutoSave = editor => {
|
|
476
456
|
var _this$props$userCache;
|
|
477
|
-
|
|
478
457
|
this.storage = new EncryptedStorage((_this$props$userCache = this.props.userCacheKey) !== null && _this$props$userCache !== void 0 ? _this$props$userCache : '');
|
|
479
|
-
|
|
480
458
|
if (this.storage) {
|
|
481
459
|
editor.on('change Undo Redo', this.doAutoSave);
|
|
482
460
|
editor.on('blur', this.doAutoSave);
|
|
483
461
|
this.cleanupAutoSave();
|
|
484
|
-
|
|
485
462
|
try {
|
|
486
463
|
const autosaved = this.getAutoSaved(this.autoSaveKey);
|
|
487
|
-
|
|
488
464
|
if (autosaved && autosaved.content) {
|
|
489
465
|
// We'll compare just the text of the autosave content, since
|
|
490
466
|
// Canvas is prone to swizzling images and iframes which will
|
|
491
467
|
// make the editor content and autosave content never match up
|
|
492
|
-
const editorContent =
|
|
468
|
+
const editorContent = patchAutosavedContent(editor.getContent({
|
|
493
469
|
no_events: true
|
|
494
470
|
}), true);
|
|
495
|
-
const autosavedContent =
|
|
496
|
-
|
|
471
|
+
const autosavedContent = patchAutosavedContent(autosaved.content, true);
|
|
497
472
|
if (autosavedContent !== editorContent) {
|
|
498
473
|
this.setState({
|
|
499
474
|
confirmAutoSave: true,
|
|
500
|
-
|
|
475
|
+
// @ts-expect-error
|
|
476
|
+
autoSavedContent: patchAutosavedContent(autosaved.content)
|
|
501
477
|
});
|
|
502
478
|
} else {
|
|
503
479
|
this.storage.removeItem(this.autoSaveKey);
|
|
@@ -505,80 +481,66 @@ class RCEWrapper extends React.Component {
|
|
|
505
481
|
}
|
|
506
482
|
} catch (ex) {
|
|
507
483
|
// log and ignore
|
|
508
|
-
|
|
484
|
+
|
|
509
485
|
console.error('Failed initializing rce autosave', ex);
|
|
510
486
|
}
|
|
511
487
|
}
|
|
512
488
|
};
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
if (_this.storage) {
|
|
518
|
-
const expiry = deleteAll ? Date.now() : Date.now() - _this.props.autosave.maxAge;
|
|
489
|
+
this.cleanupAutoSave = (deleteAll = false) => {
|
|
490
|
+
if (this.storage) {
|
|
491
|
+
const expiry = deleteAll ? Date.now() : Date.now() - (this.props.autosave?.maxAge || 0);
|
|
519
492
|
let i = 0;
|
|
520
493
|
let key;
|
|
521
|
-
|
|
522
|
-
while (key = _this.storage.key(i++)) {
|
|
494
|
+
while (key = this.storage.key(i++)) {
|
|
523
495
|
if (/^rceautosave:/.test(key)) {
|
|
524
|
-
const autosaved =
|
|
525
|
-
|
|
496
|
+
const autosaved = this.getAutoSaved(key);
|
|
526
497
|
if (autosaved && autosaved.autosaveTimestamp < expiry) {
|
|
527
|
-
|
|
498
|
+
this.storage.removeItem(key);
|
|
528
499
|
}
|
|
529
500
|
}
|
|
530
501
|
}
|
|
531
502
|
}
|
|
532
503
|
};
|
|
533
|
-
|
|
534
504
|
this.restoreAutoSave = ans => {
|
|
535
505
|
this.setState({
|
|
536
506
|
confirmAutoSave: false
|
|
537
507
|
}, () => {
|
|
538
508
|
const editor = this.mceInstance();
|
|
539
|
-
|
|
540
509
|
if (ans) {
|
|
541
510
|
editor.setContent(this.state.autoSavedContent, {});
|
|
542
511
|
}
|
|
543
|
-
|
|
512
|
+
// @ts-expect-error
|
|
544
513
|
this.storage.removeItem(this.autoSaveKey);
|
|
545
|
-
});
|
|
546
|
-
|
|
514
|
+
});
|
|
515
|
+
// let the content be restored
|
|
547
516
|
debounce(this.checkAccessibility, 1000)();
|
|
548
517
|
};
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
if (_this.storage) {
|
|
554
|
-
const editor = _this.mceInstance(); // if the editor is empty don't save
|
|
555
|
-
|
|
556
|
-
|
|
518
|
+
this.doAutoSave = (e, retry = false) => {
|
|
519
|
+
if (this.storage) {
|
|
520
|
+
const editor = this.mceInstance();
|
|
521
|
+
// if the editor is empty don't save
|
|
557
522
|
if (editor.dom.isEmpty(editor.getBody())) {
|
|
558
523
|
return;
|
|
559
524
|
}
|
|
560
|
-
|
|
561
525
|
const content = editor.getContent({
|
|
562
526
|
no_events: true
|
|
563
527
|
});
|
|
564
|
-
|
|
565
528
|
try {
|
|
566
|
-
|
|
529
|
+
this.storage.setItem(this.autoSaveKey, content);
|
|
567
530
|
} catch (ex) {
|
|
568
531
|
if (!retry) {
|
|
569
532
|
// probably failed because there's not enough space
|
|
570
533
|
// delete up all the other entries and try again
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
_this.doAutoSave(e, true);
|
|
534
|
+
this.cleanupAutoSave(true);
|
|
535
|
+
this.doAutoSave(e, true);
|
|
574
536
|
} else {
|
|
575
|
-
console.error('Autosave failed:', ex);
|
|
537
|
+
console.error('Autosave failed:', ex);
|
|
576
538
|
}
|
|
577
539
|
}
|
|
578
540
|
}
|
|
579
541
|
};
|
|
580
|
-
|
|
581
542
|
this.onWordCountUpdate = e => {
|
|
543
|
+
if (!this.editor) return;
|
|
582
544
|
const shouldIgnore = countShouldIgnore(this.editor, 'body', 'words');
|
|
583
545
|
const updatedCount = e.wordCount.words - shouldIgnore;
|
|
584
546
|
this.setState(state => {
|
|
@@ -589,51 +551,48 @@ class RCEWrapper extends React.Component {
|
|
|
589
551
|
} else return null;
|
|
590
552
|
});
|
|
591
553
|
};
|
|
592
|
-
|
|
593
554
|
this.onNodeChange = e => {
|
|
594
555
|
// This is basically copied out of the tinymce silver theme code for the status bar
|
|
595
|
-
const path = e.parents.filter(p => p.nodeName !== 'BR' && !p.getAttribute('data-mce-bogus') && p.getAttribute('data-mce-type') !== 'bookmark')
|
|
556
|
+
const path = e.parents.filter(p => p.nodeName !== 'BR' && !p.getAttribute('data-mce-bogus') && p.getAttribute('data-mce-type') !== 'bookmark')
|
|
557
|
+
// @ts-expect-error
|
|
558
|
+
.map(p => p.nodeName.toLowerCase()).reverse();
|
|
596
559
|
this.setState({
|
|
597
560
|
path
|
|
598
561
|
});
|
|
599
562
|
};
|
|
600
|
-
|
|
601
563
|
this.onEditorChange = (content, _editor) => {
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
(_this$props$onContent = (_this$props2 = this.props).onContentChange) === null || _this$props$onContent === void 0 ? void 0 : _this$props$onContent.call(_this$props2, content); // check accessibility when clearing the editor,
|
|
564
|
+
this.props.onContentChange?.(content);
|
|
565
|
+
// check accessibility when clearing the editor,
|
|
605
566
|
// all other times should be checked by handleInputChange
|
|
606
|
-
|
|
607
567
|
if (content === '') {
|
|
608
568
|
this.checkAccessibility();
|
|
609
569
|
}
|
|
610
570
|
};
|
|
611
|
-
|
|
612
571
|
this.onResize = (_e, coordinates) => {
|
|
613
572
|
const editor = this.mceInstance();
|
|
614
|
-
|
|
615
573
|
if (editor) {
|
|
616
574
|
const container = editor.getContainer();
|
|
617
575
|
if (!container) return;
|
|
618
576
|
const currentContainerHeight = Number.parseInt(container.style.height, 10);
|
|
619
|
-
if (isNaN(currentContainerHeight)) return;
|
|
620
|
-
|
|
577
|
+
if (isNaN(currentContainerHeight)) return;
|
|
621
578
|
const modifiedHeight = currentContainerHeight + coordinates.deltaY;
|
|
622
579
|
const newHeight = `${modifiedHeight}px`;
|
|
623
580
|
container.style.height = newHeight;
|
|
624
|
-
this.getTextarea()
|
|
581
|
+
const textarea = this.getTextarea();
|
|
582
|
+
if (textarea) {
|
|
583
|
+
textarea.style.height = newHeight;
|
|
584
|
+
}
|
|
625
585
|
this.setState({
|
|
626
586
|
height: newHeight
|
|
627
|
-
});
|
|
628
|
-
|
|
587
|
+
});
|
|
588
|
+
// play nice and send the same event that the silver theme would send
|
|
629
589
|
editor.fire('ResizeEditor');
|
|
630
590
|
}
|
|
631
591
|
};
|
|
632
|
-
|
|
633
592
|
this.onA11yChecker = triggerElementId => {
|
|
634
593
|
const editor = this.mceInstance();
|
|
635
594
|
editor.execCommand('openAccessibilityChecker', false, {
|
|
636
|
-
mountNode:
|
|
595
|
+
mountNode: instuiPopupMountNodeFn,
|
|
637
596
|
triggerElementId,
|
|
638
597
|
onFixError: errors => {
|
|
639
598
|
this.setState({
|
|
@@ -644,10 +603,10 @@ class RCEWrapper extends React.Component {
|
|
|
644
603
|
skip_focus: true
|
|
645
604
|
});
|
|
646
605
|
};
|
|
647
|
-
|
|
648
606
|
this.checkAccessibility = () => {
|
|
649
607
|
const editor = this.mceInstance();
|
|
650
608
|
editor.execCommand('checkAccessibility', false, {
|
|
609
|
+
// @ts-expect-error
|
|
651
610
|
done: errors => {
|
|
652
611
|
this.setState({
|
|
653
612
|
a11yErrorsCount: errors.length
|
|
@@ -657,86 +616,75 @@ class RCEWrapper extends React.Component {
|
|
|
657
616
|
skip_focus: true
|
|
658
617
|
});
|
|
659
618
|
};
|
|
660
|
-
|
|
661
619
|
this.openKBShortcutModal = () => {
|
|
662
620
|
this.setState({
|
|
663
621
|
KBShortcutModalOpen: true,
|
|
622
|
+
// @ts-expect-error
|
|
664
623
|
KBShortcutFocusReturn: document.activeElement
|
|
665
624
|
});
|
|
666
625
|
};
|
|
667
|
-
|
|
668
626
|
this.closeKBShortcutModal = () => {
|
|
669
627
|
this.setState({
|
|
670
628
|
KBShortcutModalOpen: false
|
|
671
629
|
});
|
|
672
630
|
};
|
|
673
|
-
|
|
674
631
|
this.KBShortcutModalExited = () => {
|
|
675
632
|
if (this.state.KBShortcutFocusReturn === this.iframe) {
|
|
676
633
|
// launched using a kb shortcut
|
|
677
634
|
// the iframe has focus so we need to forward it on to tinymce editor
|
|
678
|
-
this.editor
|
|
635
|
+
if (this.editor) {
|
|
636
|
+
this.editor.focus(false);
|
|
637
|
+
}
|
|
679
638
|
} else if (this.state.KBShortcutFocusReturn === document.getElementById(`show-on-focus-btn-${this.id}`)) {
|
|
680
|
-
var _this$_showOnFocusBut;
|
|
681
|
-
|
|
682
639
|
// launched from showOnFocus button
|
|
683
640
|
// edge case where focusing KBShortcutFocusReturn doesn't work
|
|
684
|
-
|
|
641
|
+
this._showOnFocusButton?.focus();
|
|
685
642
|
} else {
|
|
686
|
-
var _this$state$KBShortcu;
|
|
687
|
-
|
|
688
643
|
// launched from kb shortcut button on status bar
|
|
689
|
-
|
|
644
|
+
this.state.KBShortcutFocusReturn?.focus();
|
|
690
645
|
}
|
|
691
646
|
};
|
|
692
|
-
|
|
693
647
|
this.handleAIClick = () => {
|
|
694
648
|
import('./plugins/shared/ai_tools').then(module => {
|
|
649
|
+
// @ts-expect-error
|
|
695
650
|
this.AIToolsTray = module.AIToolsTray;
|
|
696
651
|
this.setState({
|
|
697
652
|
AIToolsOpen: true,
|
|
698
653
|
AITToolsFocusReturn: document.activeElement
|
|
699
654
|
});
|
|
700
655
|
}).catch(ex => {
|
|
701
|
-
// eslint-disable-next-line no-console
|
|
702
656
|
console.error('Failed loading the AIToolsTray', ex);
|
|
703
657
|
});
|
|
704
658
|
};
|
|
705
|
-
|
|
706
659
|
this.closeAITools = () => {
|
|
707
660
|
this.setState({
|
|
708
661
|
AIToolsOpen: false
|
|
709
662
|
});
|
|
710
663
|
};
|
|
711
|
-
|
|
712
664
|
this.AIToolsExited = () => {
|
|
713
665
|
if (this.state.AITToolsFocusReturn === this.iframe) {
|
|
714
666
|
// launched using a kb shortcut
|
|
715
667
|
// the iframe has focus so we need to forward it on to tinymce editor
|
|
716
|
-
this.editor
|
|
668
|
+
if (this.editor) {
|
|
669
|
+
this.editor.focus(false);
|
|
670
|
+
}
|
|
717
671
|
} else if (this.state.AITToolsFocusReturn === document.getElementById(`show-on-focus-btn-${this.id}`)) {
|
|
718
|
-
var _this$_showOnFocusBut2;
|
|
719
|
-
|
|
720
672
|
// launched from showOnFocus button
|
|
721
673
|
// edge case where focusing KBShortcutFocusReturn doesn't work
|
|
722
|
-
|
|
674
|
+
this._showOnFocusButton?.focus();
|
|
723
675
|
} else {
|
|
724
|
-
var _this$state$AITToolsF;
|
|
725
|
-
|
|
726
676
|
// launched from kb shortcut button on status bar
|
|
727
|
-
|
|
677
|
+
// @ts-expect-error
|
|
678
|
+
this.state.AITToolsFocusReturn?.focus();
|
|
728
679
|
}
|
|
729
680
|
};
|
|
730
|
-
|
|
731
681
|
this.handleInsertAIContent = content => {
|
|
732
682
|
const editor = this.mceInstance();
|
|
733
683
|
contentInsertion.insertContent(editor, content);
|
|
734
684
|
};
|
|
735
|
-
|
|
736
685
|
this.handleReplaceAIContent = content => {
|
|
737
686
|
const ed = this.mceInstance();
|
|
738
687
|
const selection = ed.selection;
|
|
739
|
-
|
|
740
688
|
if (selection.getContent().length > 0) {
|
|
741
689
|
selection.setContent(content);
|
|
742
690
|
} else {
|
|
@@ -744,7 +692,6 @@ class RCEWrapper extends React.Component {
|
|
|
744
692
|
selection.setContent(content);
|
|
745
693
|
}
|
|
746
694
|
};
|
|
747
|
-
|
|
748
695
|
this.getCurrentContentForAI = () => {
|
|
749
696
|
const selected = this.mceInstance().selection.getContent();
|
|
750
697
|
return selected ? {
|
|
@@ -755,35 +702,30 @@ class RCEWrapper extends React.Component {
|
|
|
755
702
|
content: this.mceInstance().getContent()
|
|
756
703
|
};
|
|
757
704
|
};
|
|
758
|
-
|
|
759
705
|
this.setFocusAbilityForHeader = focusable => {
|
|
760
706
|
// Sets aria-hidden to prevent screen readers focus in RCE menus and toolbar
|
|
761
|
-
const header = this._elementRef.current
|
|
762
|
-
|
|
707
|
+
const header = this._elementRef.current?.querySelector('.tox-editor-header');
|
|
763
708
|
if (header) {
|
|
764
709
|
header.setAttribute('aria-hidden', focusable ? 'false' : 'true');
|
|
765
710
|
}
|
|
766
711
|
};
|
|
767
|
-
|
|
768
712
|
this.handleTextareaChange = () => {
|
|
769
713
|
if (this.isHidden()) {
|
|
770
714
|
this.setCode(this.textareaValue());
|
|
715
|
+
// @ts-expect-error
|
|
771
716
|
this.doAutoSave();
|
|
772
717
|
}
|
|
773
718
|
};
|
|
774
|
-
|
|
775
719
|
this.addAlert = alert => {
|
|
776
720
|
alert.id = alertIdValue++;
|
|
777
721
|
this.setState(state => {
|
|
778
722
|
let messages = state.messages.concat(alert);
|
|
779
723
|
messages = _.uniqBy(messages, 'text'); // Don't show the same message twice
|
|
780
|
-
|
|
781
724
|
return {
|
|
782
725
|
messages
|
|
783
726
|
};
|
|
784
727
|
});
|
|
785
728
|
};
|
|
786
|
-
|
|
787
729
|
this.removeAlert = messageId => {
|
|
788
730
|
this.setState(state => {
|
|
789
731
|
const messages = state.messages.filter(message => message.id !== messageId);
|
|
@@ -792,47 +734,50 @@ class RCEWrapper extends React.Component {
|
|
|
792
734
|
};
|
|
793
735
|
});
|
|
794
736
|
};
|
|
795
|
-
|
|
796
737
|
this.resetAlertId = () => {
|
|
797
738
|
if (this.state.messages.length > 0) {
|
|
798
739
|
throw new Error('There are messages currently, you cannot reset when they are non-zero');
|
|
799
740
|
}
|
|
800
|
-
|
|
801
741
|
alertIdValue = 0;
|
|
802
742
|
};
|
|
743
|
+
this.style = buildStyle();
|
|
803
744
|
|
|
804
|
-
|
|
745
|
+
// Set up some limited global state that can be referenced
|
|
805
746
|
// as needed in RCE's components and function / plugin definitions
|
|
806
747
|
// Not intended to be dynamically changed!
|
|
807
|
-
|
|
808
748
|
RCEGlobals.setFeatures(this.getRequiredFeatureStatuses());
|
|
809
749
|
RCEGlobals.setConfig(this.getRequiredConfigValues());
|
|
810
750
|
this.editor = null; // my tinymce editor instance
|
|
751
|
+
this.language = normalizeLocale(this.props.language);
|
|
811
752
|
|
|
812
|
-
|
|
813
|
-
|
|
753
|
+
// interface consistent with editorBox
|
|
814
754
|
this.get_code = this.getCode;
|
|
815
755
|
this.set_code = this.setCode;
|
|
816
|
-
this.insert_code = this.insertCode;
|
|
756
|
+
this.insert_code = this.insertCode;
|
|
817
757
|
|
|
758
|
+
// test override points
|
|
759
|
+
// @ts-expect-error
|
|
818
760
|
this.indicator = false;
|
|
819
761
|
this._elementRef = /*#__PURE__*/React.createRef();
|
|
820
762
|
this._editorPlaceholderRef = /*#__PURE__*/React.createRef();
|
|
763
|
+
// @ts-expect-error
|
|
821
764
|
this._prettyHtmlEditorRef = /*#__PURE__*/React.createRef();
|
|
822
|
-
|
|
765
|
+
// @ts-expect-error
|
|
766
|
+
this._showOnFocusButton = null;
|
|
823
767
|
|
|
768
|
+
// Process initial content
|
|
769
|
+
// @ts-expect-error
|
|
824
770
|
this.initialContent = this.getRequiredFeatureStatuses().rce_transform_loaded_content ? transformRceContentForEditing(this.props.defaultContent, {
|
|
825
|
-
origin: this.props.canvasOrigin ||
|
|
771
|
+
origin: this.props.canvasOrigin || window?.location?.origin
|
|
826
772
|
}) : this.props.defaultContent;
|
|
827
|
-
injectTinySkin();
|
|
828
|
-
// height of the status bar (which used to be tinymce's)
|
|
829
|
-
|
|
830
|
-
let _ht = ((_props$editorOptions = props.editorOptions) === null || _props$editorOptions === void 0 ? void 0 : _props$editorOptions.height) || DEFAULT_RCE_HEIGHT;
|
|
773
|
+
injectTinySkin();
|
|
831
774
|
|
|
775
|
+
// FWIW, for historic reaasons, the height does not include the
|
|
776
|
+
// height of the status bar (which used to be tinymce's)
|
|
777
|
+
let _ht = props.editorOptions?.height || DEFAULT_RCE_HEIGHT;
|
|
832
778
|
if (!Number.isNaN(_ht)) {
|
|
833
779
|
_ht = `${_ht}px`;
|
|
834
780
|
}
|
|
835
|
-
|
|
836
781
|
const currentRCECount = document.querySelectorAll('.rce-wrapper').length;
|
|
837
782
|
const maxInitRenderedRCEs = Number.isNaN(props.maxInitRenderedRCEs) ? RCEWrapper.defaultProps.maxInitRenderedRCEs : props.maxInitRenderedRCEs;
|
|
838
783
|
this.state = {
|
|
@@ -845,9 +790,12 @@ class RCEWrapper extends React.Component {
|
|
|
845
790
|
announcement: null,
|
|
846
791
|
confirmAutoSave: false,
|
|
847
792
|
autoSavedContent: '',
|
|
793
|
+
// @ts-expect-error
|
|
848
794
|
id: this.props.id || this.props.textareaId || `${uid('rce', 2)}`,
|
|
795
|
+
// @ts-expect-error
|
|
849
796
|
height: _ht,
|
|
850
797
|
fullscreenState: {
|
|
798
|
+
// @ts-expect-error
|
|
851
799
|
prevHeight: _ht
|
|
852
800
|
},
|
|
853
801
|
a11yErrorsCount: 0,
|
|
@@ -856,45 +804,47 @@ class RCEWrapper extends React.Component {
|
|
|
856
804
|
};
|
|
857
805
|
this._statusBarId = `${this.state.id}_statusbar`;
|
|
858
806
|
this.pendingEventHandlers = [];
|
|
807
|
+
|
|
808
|
+
// @ts-expect-error
|
|
859
809
|
this.ltiToolFavorites = externalToolsForToolbar(this.props.ltiTools).map(e => `instructure_external_button_${e.id}`);
|
|
860
|
-
this.pluginsToExclude = parsePluginsToExclude(
|
|
810
|
+
this.pluginsToExclude = parsePluginsToExclude(props.editorOptions?.plugins || []);
|
|
811
|
+
|
|
812
|
+
// @ts-expect-error
|
|
861
813
|
this.resourceType = props.resourceType;
|
|
814
|
+
// @ts-expect-error
|
|
862
815
|
this.resourceId = props.resourceId;
|
|
816
|
+
|
|
817
|
+
// @ts-expect-error
|
|
863
818
|
this.variant = window.RCE_VARIANT || props.variant; // to facilitate testing
|
|
864
819
|
|
|
820
|
+
// @ts-expect-error
|
|
865
821
|
this.tinymceInitOptions = this.wrapOptions(props.editorOptions);
|
|
866
822
|
alertHandler.alertFunc = this.addAlert;
|
|
867
823
|
this.handleContentTrayClosing = this.handleContentTrayClosing.bind(this);
|
|
868
|
-
this.resizeObserver = new ResizeObserver(
|
|
824
|
+
this.resizeObserver = new ResizeObserver(() => {
|
|
869
825
|
this._handleFullscreenResize();
|
|
870
826
|
});
|
|
871
827
|
this.AIToolsTray = undefined;
|
|
872
|
-
}
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
// when the RCE is put into fullscreen we need to move the div
|
|
873
831
|
// tinymce mounts popup menus into from the body to the rce-wrapper
|
|
874
832
|
// or the menus wind up behind the RCE. I can't find a way to
|
|
875
833
|
// configure tinymce to say where that div is mounted, do this
|
|
876
834
|
// is a bit of a hack to tag the div that is this RCE's
|
|
877
|
-
|
|
878
|
-
|
|
879
835
|
_tagTinymceAuxDiv() {
|
|
880
836
|
const tinyauxlist = document.querySelectorAll('.tox-tinymce-aux');
|
|
881
|
-
|
|
882
837
|
if (tinyauxlist.length) {
|
|
883
838
|
const myaux = tinyauxlist[tinyauxlist.length - 1];
|
|
884
|
-
|
|
885
839
|
if (myaux.id) {
|
|
886
|
-
// eslint-disable-next-line no-console
|
|
887
840
|
console.error('Unexpected ID on my tox-tinymce-aux element');
|
|
888
841
|
}
|
|
889
|
-
|
|
890
842
|
myaux.id = `tinyaux-${this.id}`;
|
|
891
843
|
}
|
|
892
844
|
}
|
|
893
|
-
|
|
894
845
|
_myTinymceAuxDiv() {
|
|
895
846
|
return document.getElementById(`tinyaux-${this.id}`);
|
|
896
847
|
}
|
|
897
|
-
|
|
898
848
|
getRequiredFeatureStatuses() {
|
|
899
849
|
const {
|
|
900
850
|
new_math_equation_handling = false,
|
|
@@ -915,57 +865,54 @@ class RCEWrapper extends React.Component {
|
|
|
915
865
|
consolidated_media_player
|
|
916
866
|
};
|
|
917
867
|
}
|
|
918
|
-
|
|
919
868
|
getRequiredConfigValues() {
|
|
920
869
|
return {
|
|
921
870
|
locale: normalizeLocale(this.props.language),
|
|
871
|
+
// @ts-expect-error
|
|
922
872
|
flashAlertTimeout: this.props.flashAlertTimeout,
|
|
873
|
+
// @ts-expect-error
|
|
923
874
|
timezone: this.props.timezone
|
|
924
875
|
};
|
|
925
876
|
}
|
|
926
|
-
|
|
927
877
|
getCanvasUrl() {
|
|
928
878
|
return this.props.canvasOrigin;
|
|
929
879
|
}
|
|
930
|
-
|
|
931
880
|
getResourceIdentifiers() {
|
|
932
881
|
return {
|
|
882
|
+
// @ts-expect-error
|
|
933
883
|
resourceType: this.resourceType,
|
|
884
|
+
// @ts-expect-error
|
|
934
885
|
resourceId: this.resourceId
|
|
935
886
|
};
|
|
936
|
-
}
|
|
937
|
-
// kind of strange but want to be consistent
|
|
938
|
-
|
|
887
|
+
}
|
|
939
888
|
|
|
889
|
+
// getCode and setCode naming comes from tinyMCE
|
|
890
|
+
// kind of strange but want to be consistent
|
|
940
891
|
getCode() {
|
|
941
892
|
return this.isHidden() ? this.textareaValue() : this.mceInstance().getContent();
|
|
942
893
|
}
|
|
943
894
|
|
|
895
|
+
// @ts-expect-error
|
|
944
896
|
checkReadyToGetCode(promptFunc) {
|
|
945
|
-
let status = true;
|
|
946
|
-
|
|
897
|
+
let status = true;
|
|
898
|
+
// Check for remaining placeholders
|
|
947
899
|
if (this.mceInstance().dom.doc.querySelector(`[data-placeholder-for]`)) {
|
|
948
900
|
status = promptFunc(formatMessage('Content is still being uploaded, if you continue it will not be embedded properly.'));
|
|
949
901
|
}
|
|
950
|
-
|
|
951
902
|
return status;
|
|
952
903
|
}
|
|
953
|
-
|
|
954
904
|
setCode(newContent) {
|
|
955
|
-
|
|
905
|
+
this.mceInstance()?.setContent(newContent);
|
|
906
|
+
}
|
|
956
907
|
|
|
957
|
-
|
|
958
|
-
} // This function is called imperatively by the page that renders the RCE.
|
|
908
|
+
// This function is called imperatively by the page that renders the RCE.
|
|
959
909
|
// It should be called when the RCE content is done being edited.
|
|
960
|
-
|
|
961
|
-
|
|
962
910
|
RCEClosed() {
|
|
963
911
|
// We want to clear the autosaved content, since the page was legitimately closed.
|
|
964
912
|
if (this.storage) {
|
|
965
913
|
this.storage.removeItem(this.autoSaveKey);
|
|
966
914
|
}
|
|
967
915
|
}
|
|
968
|
-
|
|
969
916
|
indicateEditor(element) {
|
|
970
917
|
if (document.querySelector('[role="dialog"][data-mce-component]')) {
|
|
971
918
|
// there is a modal open, which zeros out the vertical scroll
|
|
@@ -975,42 +922,46 @@ class RCEWrapper extends React.Component {
|
|
|
975
922
|
}, 100);
|
|
976
923
|
return;
|
|
977
924
|
}
|
|
978
|
-
|
|
979
925
|
const editor = this.mceInstance();
|
|
980
|
-
|
|
926
|
+
// @ts-expect-error
|
|
981
927
|
if (this.indicator) {
|
|
928
|
+
// @ts-expect-error
|
|
982
929
|
this.indicator(editor, element);
|
|
983
930
|
} else if (!this.isHidden()) {
|
|
984
931
|
indicate(indicatorRegion(editor, element));
|
|
985
932
|
}
|
|
986
933
|
}
|
|
987
|
-
|
|
988
934
|
contentInserted(element) {
|
|
989
935
|
this.indicateEditor(element);
|
|
990
936
|
this.checkImageLoadError(element);
|
|
991
937
|
this.sizeEditorForContent(element);
|
|
992
|
-
}
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
// make a attempt at sizing the editor so that the new content fits.
|
|
993
941
|
// works under the assumptions the body's box-sizing is not content-box
|
|
994
942
|
// and that the content is w/in a <p> whose margin is 12px top and bottom
|
|
995
943
|
// (which, in canvas, is set in app/stylesheets/components/_ic-typography.scss)
|
|
996
|
-
|
|
997
|
-
|
|
998
944
|
sizeEditorForContent(elem) {
|
|
999
945
|
let height;
|
|
1000
|
-
|
|
1001
946
|
if (elem && elem.nodeType === 1) {
|
|
1002
947
|
height = elem.clientHeight;
|
|
1003
948
|
}
|
|
1004
|
-
|
|
1005
949
|
if (height) {
|
|
1006
950
|
const ifr = this.iframe;
|
|
1007
|
-
|
|
1008
951
|
if (ifr) {
|
|
1009
|
-
|
|
1010
|
-
const
|
|
952
|
+
// @ts-expect-error
|
|
953
|
+
const editor_body_style = ifr.contentWindow.getComputedStyle(
|
|
954
|
+
// @ts-expect-error
|
|
955
|
+
this.iframe.contentDocument.body);
|
|
956
|
+
const editor_ht =
|
|
957
|
+
// @ts-expect-error
|
|
958
|
+
ifr.contentDocument.body.clientHeight -
|
|
959
|
+
// @ts-expect-error
|
|
960
|
+
parseInt(editor_body_style['padding-top'], 10) -
|
|
961
|
+
// @ts-expect-error
|
|
962
|
+
parseInt(editor_body_style['padding-bottom'], 10);
|
|
1011
963
|
const para_margin_ht = 24;
|
|
1012
964
|
const reserve_ht = Math.ceil(height + para_margin_ht);
|
|
1013
|
-
|
|
1014
965
|
if (reserve_ht > editor_ht) {
|
|
1015
966
|
this.onResize(null, {
|
|
1016
967
|
deltaY: reserve_ht - editor_ht
|
|
@@ -1019,90 +970,82 @@ class RCEWrapper extends React.Component {
|
|
|
1019
970
|
}
|
|
1020
971
|
}
|
|
1021
972
|
}
|
|
1022
|
-
|
|
1023
973
|
checkImageLoadError(element) {
|
|
1024
974
|
if (!element || element.tagName !== 'IMG') {
|
|
1025
975
|
return;
|
|
1026
976
|
}
|
|
1027
|
-
|
|
977
|
+
// @ts-expect-error
|
|
1028
978
|
if (!element.complete) {
|
|
979
|
+
// @ts-expect-error
|
|
1029
980
|
element.onload = () => this.checkImageLoadError(element);
|
|
1030
|
-
|
|
1031
981
|
return;
|
|
1032
|
-
}
|
|
982
|
+
}
|
|
983
|
+
// checking naturalWidth in a future event loop run prevents a race
|
|
1033
984
|
// condition between the onload callback and naturalWidth being set.
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
985
|
setTimeout(() => {
|
|
986
|
+
// @ts-expect-error
|
|
1037
987
|
if (element.naturalWidth === 0) {
|
|
988
|
+
// @ts-expect-error
|
|
1038
989
|
element.style.border = '1px solid #000';
|
|
990
|
+
// @ts-expect-error
|
|
1039
991
|
element.style.padding = '2px';
|
|
1040
992
|
}
|
|
1041
993
|
}, 0);
|
|
1042
994
|
}
|
|
1043
|
-
|
|
1044
995
|
insertCode(code) {
|
|
1045
996
|
const editor = this.mceInstance();
|
|
1046
997
|
const element = contentInsertion.insertContent(editor, code);
|
|
1047
998
|
this.contentInserted(element);
|
|
1048
999
|
}
|
|
1049
|
-
|
|
1050
1000
|
replaceCode(code) {
|
|
1051
1001
|
if (code !== '' && window.confirm(formatMessage('Content in the editor will be changed. Press Cancel to keep the original content.'))) {
|
|
1052
1002
|
this.mceInstance().setContent(code);
|
|
1053
1003
|
}
|
|
1054
1004
|
}
|
|
1055
|
-
|
|
1056
1005
|
insertEmbedCode(code) {
|
|
1057
|
-
const editor = this.mceInstance();
|
|
1006
|
+
const editor = this.mceInstance();
|
|
1007
|
+
|
|
1008
|
+
// don't replace selected text, but embed after
|
|
1009
|
+
editor.selection.collapse();
|
|
1058
1010
|
|
|
1059
|
-
|
|
1011
|
+
// tinymce treats iframes uniquely, and doesn't like adding attributes
|
|
1060
1012
|
// once it's in the editor, and I'd rather not parse the incomming html
|
|
1061
1013
|
// string with a regex, so let's create a temp copy, then add a title
|
|
1062
1014
|
// attribute if one doesn't exist. This will let screenreaders announce
|
|
1063
1015
|
// that there's some embedded content helper
|
|
1064
1016
|
// From what I've read, "title" is more reliable than "aria-label" for
|
|
1065
1017
|
// elements like iframes and embeds.
|
|
1066
|
-
|
|
1067
1018
|
const temp = document.createElement('div');
|
|
1068
1019
|
temp.innerHTML = code;
|
|
1069
1020
|
const code_elem = temp.firstElementChild;
|
|
1070
|
-
|
|
1071
1021
|
if (code_elem) {
|
|
1072
1022
|
if (!code_elem.hasAttribute('title') && !code_elem.hasAttribute('aria-label')) {
|
|
1073
1023
|
code_elem.setAttribute('title', formatMessage('embedded content'));
|
|
1074
1024
|
}
|
|
1075
|
-
|
|
1076
1025
|
code = code_elem.outerHTML;
|
|
1077
|
-
}
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
// inserting an iframe in tinymce (as is often the case with
|
|
1078
1029
|
// embedded content) causes it to wrap it in a span
|
|
1079
1030
|
// and it's often inserted into a <p> on top of that. Find the
|
|
1080
1031
|
// iframe and use it to flash the indicator.
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
1032
|
const element = contentInsertion.insertContent(editor, code);
|
|
1084
1033
|
const ifr = element && element.querySelector && element.querySelector('iframe');
|
|
1085
|
-
|
|
1086
1034
|
if (ifr) {
|
|
1087
1035
|
this.contentInserted(ifr);
|
|
1088
1036
|
} else {
|
|
1089
1037
|
this.contentInserted(element);
|
|
1090
1038
|
}
|
|
1091
1039
|
}
|
|
1092
|
-
|
|
1093
1040
|
insertImage(image) {
|
|
1094
|
-
var _element$nextSibling, _element$nextSibling$;
|
|
1095
|
-
|
|
1096
1041
|
const editor = this.mceInstance();
|
|
1097
|
-
const element = contentInsertion.insertImage(editor, image, this.getCanvasUrl());
|
|
1042
|
+
const element = contentInsertion.insertImage(editor, image, this.getCanvasUrl());
|
|
1098
1043
|
|
|
1099
|
-
|
|
1100
|
-
/* nbsp */
|
|
1101
|
-
)) {
|
|
1044
|
+
// Removes TinyMCE's caret text if exists.
|
|
1045
|
+
if (element?.nextSibling?.data?.startsWith('\xA0' /* nbsp */)) {
|
|
1102
1046
|
element.nextSibling.splitText(1);
|
|
1103
1047
|
element.nextSibling.remove();
|
|
1104
1048
|
}
|
|
1105
|
-
|
|
1106
1049
|
return {
|
|
1107
1050
|
imageElem: element,
|
|
1108
1051
|
loadingPromise: new Promise((resolve, reject) => {
|
|
@@ -1114,7 +1057,6 @@ class RCEWrapper extends React.Component {
|
|
|
1114
1057
|
this.contentInserted(element);
|
|
1115
1058
|
resolve();
|
|
1116
1059
|
};
|
|
1117
|
-
|
|
1118
1060
|
element.onerror = e => {
|
|
1119
1061
|
this.checkImageLoadError(element);
|
|
1120
1062
|
reject(e);
|
|
@@ -1123,330 +1065,320 @@ class RCEWrapper extends React.Component {
|
|
|
1123
1065
|
})
|
|
1124
1066
|
};
|
|
1125
1067
|
}
|
|
1126
|
-
|
|
1127
1068
|
insertImagePlaceholder(fileMetaProps) {
|
|
1128
1069
|
return insertPlaceholder(this.mceInstance(), fileMetaProps.name, placeholderInfoFor(fileMetaProps));
|
|
1129
1070
|
}
|
|
1130
|
-
|
|
1131
1071
|
insertVideo(video) {
|
|
1132
1072
|
const editor = this.mceInstance();
|
|
1133
1073
|
const element = contentInsertion.insertVideo(editor, video, this.getCanvasUrl());
|
|
1134
1074
|
this.contentInserted(element);
|
|
1135
1075
|
}
|
|
1136
|
-
|
|
1137
1076
|
insertAudio(audio) {
|
|
1138
1077
|
const editor = this.mceInstance();
|
|
1139
1078
|
const element = contentInsertion.insertAudio(editor, audio, this.getCanvasUrl());
|
|
1140
1079
|
this.contentInserted(element);
|
|
1141
1080
|
}
|
|
1142
|
-
|
|
1143
1081
|
insertMathEquation(tex) {
|
|
1144
1082
|
const editor = this.mceInstance();
|
|
1145
1083
|
contentInsertion.insertEquation(editor, tex);
|
|
1146
1084
|
}
|
|
1147
|
-
|
|
1148
1085
|
removePlaceholders(name) {
|
|
1149
1086
|
removePlaceholder(this.mceInstance(), name);
|
|
1150
1087
|
}
|
|
1151
|
-
|
|
1152
1088
|
insertLink(link) {
|
|
1153
1089
|
const editor = this.mceInstance();
|
|
1154
1090
|
const element = contentInsertion.insertLink(editor, link, this.getCanvasUrl());
|
|
1155
1091
|
this.contentInserted(element);
|
|
1156
1092
|
}
|
|
1157
|
-
|
|
1158
1093
|
existingContentToLink() {
|
|
1159
1094
|
const editor = this.mceInstance();
|
|
1160
1095
|
return contentInsertion.existingContentToLink(editor);
|
|
1161
1096
|
}
|
|
1162
|
-
|
|
1163
1097
|
existingContentToLinkIsImg() {
|
|
1164
1098
|
const editor = this.mceInstance();
|
|
1165
1099
|
return contentInsertion.existingContentToLinkIsImg(editor);
|
|
1166
|
-
}
|
|
1167
|
-
|
|
1100
|
+
}
|
|
1168
1101
|
|
|
1102
|
+
// since we may defer rendering tinymce, queue up any tinymce event handlers
|
|
1103
|
+
// @ts-expect-error
|
|
1169
1104
|
tinymceOn(tinymceEventName, handler) {
|
|
1170
1105
|
if (this.state.shouldShowEditor) {
|
|
1171
1106
|
this.mceInstance().on(tinymceEventName, handler);
|
|
1172
1107
|
} else {
|
|
1108
|
+
// @ts-expect-error
|
|
1173
1109
|
this.pendingEventHandlers.push({
|
|
1174
1110
|
name: tinymceEventName,
|
|
1175
1111
|
handler
|
|
1176
1112
|
});
|
|
1177
1113
|
}
|
|
1178
1114
|
}
|
|
1179
|
-
|
|
1180
1115
|
mceInstance() {
|
|
1181
1116
|
if (this.editor) {
|
|
1182
1117
|
return this.editor;
|
|
1183
1118
|
}
|
|
1184
|
-
|
|
1185
1119
|
const editors = this.props.tinymce.editors || [];
|
|
1186
1120
|
return editors.filter(ed => ed.id === this.props.textareaId)[0];
|
|
1187
1121
|
}
|
|
1188
1122
|
|
|
1189
|
-
|
|
1123
|
+
// @ts-expect-error
|
|
1124
|
+
onTinyMCEInstance(command, ...args) {
|
|
1190
1125
|
const editor = this.mceInstance();
|
|
1191
|
-
|
|
1192
1126
|
if (editor) {
|
|
1193
1127
|
if (command === 'mceRemoveEditor') {
|
|
1194
1128
|
editor.execCommand('mceNewDocument');
|
|
1195
1129
|
} // makes sure content can't persist past removal
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
|
|
1199
|
-
args[_key - 1] = arguments[_key];
|
|
1200
|
-
}
|
|
1201
|
-
|
|
1202
1130
|
editor.execCommand(command, false, ...args);
|
|
1203
1131
|
}
|
|
1204
1132
|
}
|
|
1205
|
-
|
|
1206
1133
|
destroy() {
|
|
1207
1134
|
this._destroyCalled = true;
|
|
1208
1135
|
this.unhandleTextareaChange();
|
|
1209
|
-
|
|
1136
|
+
if (this.props.handleUnmount) {
|
|
1137
|
+
this.props.handleUnmount();
|
|
1138
|
+
}
|
|
1210
1139
|
}
|
|
1211
|
-
|
|
1212
1140
|
getTextarea() {
|
|
1213
|
-
|
|
1141
|
+
const node = this.props.textareaId && document.getElementById(this.props.textareaId);
|
|
1142
|
+
if (node instanceof HTMLTextAreaElement) {
|
|
1143
|
+
return node;
|
|
1144
|
+
}
|
|
1145
|
+
return null;
|
|
1214
1146
|
}
|
|
1215
|
-
|
|
1216
1147
|
textareaValue() {
|
|
1217
|
-
return this.getTextarea()
|
|
1148
|
+
return this.getTextarea()?.value || '';
|
|
1218
1149
|
}
|
|
1219
|
-
|
|
1220
1150
|
get id() {
|
|
1221
1151
|
return this.state.id;
|
|
1222
1152
|
}
|
|
1223
|
-
|
|
1224
1153
|
getHtmlEditorStorage() {
|
|
1225
|
-
var _this$storage2, _this$storage2$getIte, _this$storage2$getIte2;
|
|
1226
|
-
|
|
1227
1154
|
const cookieValue = getCookie('rce.htmleditor');
|
|
1228
|
-
|
|
1229
1155
|
if (cookieValue) {
|
|
1230
1156
|
document.cookie = `rce.htmleditor=${cookieValue};path=/;max-age=0`;
|
|
1231
1157
|
}
|
|
1232
|
-
|
|
1233
|
-
const value = cookieValue || ((_this$storage2 = this.storage) === null || _this$storage2 === void 0 ? void 0 : (_this$storage2$getIte = _this$storage2.getItem) === null || _this$storage2$getIte === void 0 ? void 0 : (_this$storage2$getIte2 = _this$storage2$getIte.call(_this$storage2, 'rce.htmleditor')) === null || _this$storage2$getIte2 === void 0 ? void 0 : _this$storage2$getIte2.content);
|
|
1158
|
+
const value = cookieValue || this.storage?.getItem?.('rce.htmleditor')?.content;
|
|
1234
1159
|
return value === RAW_HTML_EDITOR_VIEW || value === PRETTY_HTML_EDITOR_VIEW ? value : PRETTY_HTML_EDITOR_VIEW;
|
|
1235
1160
|
}
|
|
1236
|
-
|
|
1237
1161
|
_isFullscreen() {
|
|
1238
1162
|
return !!(this.state.fullscreenState.isTinyFullscreen || document[FS_ELEMENT]);
|
|
1239
1163
|
}
|
|
1240
|
-
|
|
1241
1164
|
_enterFullscreen() {
|
|
1242
1165
|
// tinymce mounts its menus and toolbars in this element, which is in the DOM
|
|
1243
1166
|
// at the bottom of the body. When we're fullscreen the menus need to be mounted
|
|
1244
1167
|
// in the fullscreen element or they won't show up. Let's move tinymce's mount point
|
|
1245
1168
|
// when we go into fullscreen, then put it back when we're finished.
|
|
1246
1169
|
const tinymenuhost = this._myTinymceAuxDiv();
|
|
1247
|
-
|
|
1248
1170
|
if (tinymenuhost) {
|
|
1249
1171
|
tinymenuhost.remove();
|
|
1250
|
-
|
|
1251
|
-
this._elementRef.current.appendChild(tinymenuhost);
|
|
1172
|
+
this._elementRef.current?.appendChild(tinymenuhost);
|
|
1252
1173
|
}
|
|
1253
|
-
|
|
1254
|
-
this._elementRef.current
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
}
|
|
1260
|
-
}
|
|
1261
|
-
|
|
1174
|
+
this._elementRef.current?.addEventListener(FS_CHANGEEVENT, this._onFullscreenChange);
|
|
1175
|
+
if (typeof this._elementRef.current?.offsetHeight === 'number') {
|
|
1176
|
+
this.setState({
|
|
1177
|
+
fullscreenState: {
|
|
1178
|
+
prevHeight: this._elementRef.current.offsetHeight - this._getStatusBarHeight()
|
|
1179
|
+
}
|
|
1180
|
+
});
|
|
1181
|
+
}
|
|
1182
|
+
// @ts-expect-error
|
|
1262
1183
|
this._elementRef.current[FS_REQUEST]();
|
|
1263
1184
|
}
|
|
1264
|
-
|
|
1265
1185
|
_exitFullscreen() {
|
|
1266
1186
|
if (document[FS_ELEMENT]) {
|
|
1267
1187
|
const tinymenuhost = this._myTinymceAuxDiv();
|
|
1268
|
-
|
|
1269
1188
|
if (tinymenuhost) {
|
|
1270
1189
|
tinymenuhost.remove();
|
|
1271
1190
|
document.body.appendChild(tinymenuhost);
|
|
1272
1191
|
}
|
|
1273
|
-
|
|
1274
1192
|
document[FS_EXIT]();
|
|
1275
1193
|
}
|
|
1276
1194
|
}
|
|
1277
1195
|
|
|
1196
|
+
// @ts-expect-error
|
|
1197
|
+
|
|
1278
1198
|
_getStatusBarHeight() {
|
|
1279
1199
|
// the height prop is the height of the editor and does not include
|
|
1280
1200
|
// the status bar. we'll need this later.
|
|
1281
|
-
|
|
1201
|
+
const node = document.getElementById(this._statusBarId);
|
|
1202
|
+
return node?.offsetHeight || 0;
|
|
1282
1203
|
}
|
|
1283
|
-
|
|
1284
1204
|
_setHeight(newHeight) {
|
|
1285
1205
|
const cssHeight = `${newHeight}px`;
|
|
1286
1206
|
const ed = this.mceInstance();
|
|
1287
1207
|
const container = ed.getContainer();
|
|
1288
|
-
|
|
1289
1208
|
if (container) {
|
|
1290
1209
|
container.style.height = cssHeight;
|
|
1291
1210
|
ed.fire('ResizeEditor');
|
|
1292
1211
|
}
|
|
1293
|
-
|
|
1294
|
-
|
|
1212
|
+
const textarea = this.getTextarea();
|
|
1213
|
+
if (textarea) {
|
|
1214
|
+
textarea.style.height = cssHeight;
|
|
1215
|
+
}
|
|
1295
1216
|
this.setState({
|
|
1296
1217
|
height: cssHeight
|
|
1297
1218
|
});
|
|
1298
1219
|
}
|
|
1299
|
-
|
|
1300
1220
|
focus() {
|
|
1301
|
-
this.onTinyMCEInstance('mceFocus');
|
|
1302
|
-
|
|
1221
|
+
this.onTinyMCEInstance('mceFocus');
|
|
1222
|
+
// tinymce doesn't always call the focus handler.
|
|
1223
|
+
// @ts-expect-error
|
|
1303
1224
|
this.handleFocusEditor(new Event('focus', {
|
|
1304
1225
|
target: this.mceInstance()
|
|
1305
1226
|
}));
|
|
1306
1227
|
}
|
|
1307
|
-
|
|
1308
1228
|
focusCurrentView() {
|
|
1309
1229
|
switch (this.state.editorView) {
|
|
1310
1230
|
case WYSIWYG_VIEW:
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1231
|
+
{
|
|
1232
|
+
this.mceInstance().focus();
|
|
1233
|
+
break;
|
|
1234
|
+
}
|
|
1314
1235
|
case PRETTY_HTML_EDITOR_VIEW:
|
|
1315
|
-
|
|
1316
|
-
|
|
1236
|
+
{
|
|
1237
|
+
break;
|
|
1238
|
+
}
|
|
1317
1239
|
case RAW_HTML_EDITOR_VIEW:
|
|
1318
|
-
|
|
1319
|
-
|
|
1240
|
+
{
|
|
1241
|
+
const textarea = this.getTextarea();
|
|
1242
|
+
if (textarea) {
|
|
1243
|
+
textarea.focus();
|
|
1244
|
+
}
|
|
1245
|
+
break;
|
|
1246
|
+
}
|
|
1320
1247
|
}
|
|
1321
1248
|
}
|
|
1322
|
-
|
|
1323
1249
|
is_dirty() {
|
|
1324
|
-
var _this$mceInstance2;
|
|
1325
|
-
|
|
1326
1250
|
if (this.mceInstance().isDirty()) {
|
|
1327
1251
|
return true;
|
|
1328
1252
|
}
|
|
1329
|
-
|
|
1330
|
-
const currentHtml = this.isHidden() ? this.textareaValue() : (_this$mceInstance2 = this.mceInstance()) === null || _this$mceInstance2 === void 0 ? void 0 : _this$mceInstance2.getContent();
|
|
1253
|
+
const currentHtml = this.isHidden() ? this.textareaValue() : this.mceInstance()?.getContent();
|
|
1331
1254
|
return currentHtml !== this._mceSerializedInitialHtml;
|
|
1332
1255
|
}
|
|
1256
|
+
|
|
1333
1257
|
/**
|
|
1334
1258
|
* Holds a copy of the initial content of the editor as serialized by tinyMCE to normalize it.
|
|
1335
1259
|
*/
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
1260
|
get _mceSerializedInitialHtml() {
|
|
1339
1261
|
if (!this._mceSerializedInitialHtmlCached) {
|
|
1340
1262
|
const el = window.document.createElement('div');
|
|
1263
|
+
// @ts-expect-error
|
|
1341
1264
|
el.innerHTML = this.initialContent;
|
|
1342
1265
|
const serializer = this.mceInstance().serializer;
|
|
1343
1266
|
this._mceSerializedInitialHtmlCached = serializer.serialize(el, {
|
|
1344
1267
|
getInner: true
|
|
1345
1268
|
});
|
|
1346
1269
|
}
|
|
1347
|
-
|
|
1348
1270
|
return this._mceSerializedInitialHtmlCached;
|
|
1349
1271
|
}
|
|
1350
|
-
|
|
1351
1272
|
isHtmlView() {
|
|
1352
1273
|
return this.state.editorView !== WYSIWYG_VIEW;
|
|
1353
1274
|
}
|
|
1354
|
-
|
|
1355
1275
|
isHidden() {
|
|
1356
1276
|
return this.mceInstance().isHidden();
|
|
1357
1277
|
}
|
|
1358
|
-
|
|
1359
1278
|
get iframe() {
|
|
1360
1279
|
return document.getElementById(`${this.props.textareaId}_ifr`);
|
|
1361
|
-
}
|
|
1362
|
-
// can report focus and blur events from the RCE at-large
|
|
1363
|
-
|
|
1280
|
+
}
|
|
1364
1281
|
|
|
1282
|
+
// these focus and blur event handlers work together so that RCEWrapper
|
|
1283
|
+
// can report focus and blur events from the RCE at-large
|
|
1365
1284
|
get focused() {
|
|
1366
1285
|
return this === bridge.getEditor();
|
|
1367
1286
|
}
|
|
1368
|
-
|
|
1369
|
-
handleFocus(_event) {
|
|
1287
|
+
handleFocus() {
|
|
1370
1288
|
if (!this.focused) {
|
|
1371
1289
|
bridge.focusEditor(this);
|
|
1372
|
-
|
|
1290
|
+
if (this.props.onFocus) {
|
|
1291
|
+
this.props.onFocus(this);
|
|
1292
|
+
}
|
|
1373
1293
|
}
|
|
1374
1294
|
}
|
|
1375
|
-
|
|
1376
1295
|
handleContentTrayClosing(isClosing) {
|
|
1377
1296
|
this.contentTrayClosing = isClosing;
|
|
1378
1297
|
}
|
|
1379
|
-
|
|
1380
1298
|
handleBlur(event) {
|
|
1381
1299
|
if (this.blurTimer) return;
|
|
1382
|
-
|
|
1383
1300
|
if (this.focused) {
|
|
1384
1301
|
// because the old active element fires blur before the next element gets focus
|
|
1385
1302
|
// we often need a moment to see if focus comes back
|
|
1303
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
1386
1304
|
event && event.persist && event.persist();
|
|
1387
1305
|
this.blurTimer = window.setTimeout(() => {
|
|
1388
|
-
var _this$_elementRef$cur3, _event$focusedEditor, _event$relatedTarget, _event$relatedTarget$;
|
|
1389
|
-
|
|
1390
1306
|
this.blurTimer = 0;
|
|
1391
|
-
|
|
1392
1307
|
if (this.contentTrayClosing) {
|
|
1393
1308
|
// the CanvasContentTray is in the process of closing
|
|
1394
1309
|
// wait until it finishes
|
|
1395
1310
|
return;
|
|
1396
1311
|
}
|
|
1397
|
-
|
|
1398
|
-
if ((_this$_elementRef$cur3 = this._elementRef.current) !== null && _this$_elementRef$cur3 !== void 0 && _this$_elementRef$cur3.contains(document.activeElement)) {
|
|
1312
|
+
if (this._elementRef.current?.contains(document.activeElement)) {
|
|
1399
1313
|
// focus is still somewhere w/in me
|
|
1400
1314
|
return;
|
|
1401
1315
|
}
|
|
1402
|
-
|
|
1403
1316
|
const activeClass = document.activeElement && document.activeElement.getAttribute('class');
|
|
1404
|
-
|
|
1405
|
-
|
|
1317
|
+
if (
|
|
1318
|
+
// @ts-expect-error
|
|
1319
|
+
(event.focusedEditor === undefined || event.target.id === event.focusedEditor?.id) && activeClass?.includes('tox-')) {
|
|
1406
1320
|
// if a toolbar button has focus, then the user clicks on the "more" button
|
|
1407
1321
|
// focus jumps to the body, then eventually to the popped up toolbar. This
|
|
1408
1322
|
// catches that case.
|
|
1409
1323
|
return;
|
|
1410
1324
|
}
|
|
1411
|
-
|
|
1412
|
-
if (event !== null && event !== void 0 && (_event$relatedTarget = event.relatedTarget) !== null && _event$relatedTarget !== void 0 && (_event$relatedTarget$ = _event$relatedTarget.getAttribute('class')) !== null && _event$relatedTarget$ !== void 0 && _event$relatedTarget$.includes('tox-')) {
|
|
1325
|
+
if (event?.relatedTarget?.getAttribute('class')?.includes('tox-')) {
|
|
1413
1326
|
// a tinymce popup has focus
|
|
1414
1327
|
return;
|
|
1415
1328
|
}
|
|
1416
|
-
|
|
1417
1329
|
const popups = document.querySelectorAll('[data-mce-component]');
|
|
1418
|
-
|
|
1419
1330
|
for (const popup of popups) {
|
|
1420
1331
|
if (popup.contains(document.activeElement)) {
|
|
1421
1332
|
// one of our popups has focus
|
|
1422
1333
|
return;
|
|
1423
1334
|
}
|
|
1424
1335
|
}
|
|
1425
|
-
|
|
1426
1336
|
bridge.blurEditor(this);
|
|
1427
|
-
|
|
1337
|
+
if (this.props.onBlur) {
|
|
1338
|
+
this.props.onBlur(event);
|
|
1339
|
+
}
|
|
1428
1340
|
}, ASYNC_FOCUS_TIMEOUT);
|
|
1429
1341
|
}
|
|
1430
1342
|
}
|
|
1431
1343
|
|
|
1432
|
-
|
|
1344
|
+
// @ts-expect-error
|
|
1345
|
+
|
|
1346
|
+
// @ts-expect-error
|
|
1347
|
+
call(methodName, ...args) {
|
|
1433
1348
|
// since exists? has a ? and cant be a regular function just return true
|
|
1434
1349
|
// rather than calling as a fn on the editor
|
|
1435
1350
|
if (methodName === 'exists?') {
|
|
1436
1351
|
return true;
|
|
1437
1352
|
}
|
|
1438
|
-
|
|
1439
|
-
for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
|
|
1440
|
-
args[_key2 - 1] = arguments[_key2];
|
|
1441
|
-
}
|
|
1442
|
-
|
|
1353
|
+
// @ts-expect-error
|
|
1443
1354
|
return this[methodName](...args);
|
|
1444
1355
|
}
|
|
1445
1356
|
|
|
1357
|
+
/**
|
|
1358
|
+
* Fix keyboard navigation in the expanded toolbar
|
|
1359
|
+
*
|
|
1360
|
+
* NOTE: This is a workaround for https://github.com/tinymce/tinymce/issues/8618
|
|
1361
|
+
* and should be removed once that issue is resolved and the tinymce dependency is updated to include it.
|
|
1362
|
+
*/
|
|
1363
|
+
|
|
1364
|
+
/**
|
|
1365
|
+
* Sets up selection saving and restoration logic.
|
|
1366
|
+
*
|
|
1367
|
+
* There are certain actions a user can take when the RCE is not focused that clear the selection inside the
|
|
1368
|
+
* editor, such as invoking the Find feature of the browser. If the user then tries to insert content without
|
|
1369
|
+
* going back to the editor, the content would be inserted at the top of the RCE, instead of where their cursor
|
|
1370
|
+
* was.
|
|
1371
|
+
*
|
|
1372
|
+
* This method adds logic that saves and restores the selection to work around the issue.
|
|
1373
|
+
*
|
|
1374
|
+
* @private
|
|
1375
|
+
*/
|
|
1376
|
+
// @ts-expect-error
|
|
1377
|
+
|
|
1446
1378
|
announceContextToolbars(editor) {
|
|
1447
1379
|
editor.on('NodeChange', () => {
|
|
1448
1380
|
const node = editor.selection.getNode();
|
|
1449
|
-
|
|
1381
|
+
// @ts-expect-error
|
|
1450
1382
|
if (isImageEmbed(node, editor)) {
|
|
1451
1383
|
if (this.announcing !== 1) {
|
|
1452
1384
|
this.setState({
|
|
@@ -1465,7 +1397,7 @@ class RCEWrapper extends React.Component {
|
|
|
1465
1397
|
});
|
|
1466
1398
|
this.announcing = 2;
|
|
1467
1399
|
}
|
|
1468
|
-
} else if (isElementWithinTable(node
|
|
1400
|
+
} else if (isElementWithinTable(node)) {
|
|
1469
1401
|
if (this.announcing !== 3) {
|
|
1470
1402
|
this.setState({
|
|
1471
1403
|
announcement: formatMessage('type Control F9 to access table options. {text}', {
|
|
@@ -1482,121 +1414,109 @@ class RCEWrapper extends React.Component {
|
|
|
1482
1414
|
}
|
|
1483
1415
|
});
|
|
1484
1416
|
}
|
|
1417
|
+
|
|
1485
1418
|
/* ********** autosave support *************** */
|
|
1486
1419
|
|
|
1420
|
+
// remove any autosaved value that's too old
|
|
1487
1421
|
|
|
1488
|
-
//
|
|
1489
|
-
// because the data url gets converted to a blob, which is not valid when restored.
|
|
1490
|
-
// besides, the placeholder is intended to be temporary while the file
|
|
1491
|
-
// is being uploaded
|
|
1492
|
-
patchAutosavedContent(content, asText) {
|
|
1493
|
-
const temp = document.createElement('div');
|
|
1494
|
-
temp.innerHTML = content;
|
|
1495
|
-
temp.querySelectorAll('[data-placeholder-for]').forEach(placeholder => {
|
|
1496
|
-
placeholder.parentElement.removeChild(placeholder);
|
|
1497
|
-
});
|
|
1498
|
-
if (asText) return temp.textContent;
|
|
1499
|
-
return temp.innerHTML;
|
|
1500
|
-
}
|
|
1422
|
+
// @ts-expect-error
|
|
1501
1423
|
|
|
1502
1424
|
getAutoSaved(key) {
|
|
1503
1425
|
let autosaved = null;
|
|
1504
|
-
|
|
1505
1426
|
try {
|
|
1506
1427
|
autosaved = this.storage && this.storage.getItem(key);
|
|
1507
1428
|
} catch (_ex) {
|
|
1429
|
+
// @ts-expect-error
|
|
1508
1430
|
this.storage.removeItem(this.autoSaveKey);
|
|
1509
1431
|
}
|
|
1510
|
-
|
|
1511
1432
|
return autosaved;
|
|
1512
|
-
}
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
// only autosave if the feature flag is set, and there is only 1 RCE on the page
|
|
1513
1436
|
// the latter condition is necessary because the popup RestoreAutoSaveModal
|
|
1514
1437
|
// is lousey UX when there are >1
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
1438
|
get isAutoSaving() {
|
|
1439
|
+
if (!this.editor) return false;
|
|
1440
|
+
|
|
1518
1441
|
// If the editor is invisible for some reason, don't show the autosave modal
|
|
1519
1442
|
// This doesn't apply if the editor is off-screen or has visibility:hidden;
|
|
1520
1443
|
// only if it isn't rendered or has display:none;
|
|
1521
1444
|
const editorVisible = this.editor.getContainer().offsetParent;
|
|
1522
|
-
return this.props.autosave
|
|
1445
|
+
return this.props.autosave?.enabled && editorVisible && document.querySelectorAll('.rce-wrapper').length === 1 && storageAvailable();
|
|
1523
1446
|
}
|
|
1524
|
-
|
|
1525
1447
|
get autoSaveKey() {
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
const userId = (_this$props$trayProps = this.props.trayProps) === null || _this$props$trayProps === void 0 ? void 0 : _this$props$trayProps.containingContext.userId;
|
|
1448
|
+
// @ts-expect-error
|
|
1449
|
+
const userId = this.props.trayProps?.containingContext.userId;
|
|
1529
1450
|
return `rceautosave:${userId}${window.location.href}:${this.props.textareaId}`;
|
|
1530
1451
|
}
|
|
1531
1452
|
|
|
1453
|
+
// @ts-expect-error
|
|
1454
|
+
|
|
1455
|
+
/* *********** end autosave support *************** */
|
|
1456
|
+
|
|
1457
|
+
// @ts-expect-error
|
|
1458
|
+
|
|
1532
1459
|
componentWillUnmount() {
|
|
1533
1460
|
if (this.state.shouldShowEditor) {
|
|
1534
|
-
var _this$mutationObserve, _this$intersectionObs;
|
|
1535
|
-
|
|
1536
1461
|
window.clearTimeout(this.blurTimer);
|
|
1537
|
-
|
|
1538
1462
|
if (!this._destroyCalled) {
|
|
1539
1463
|
this.destroy();
|
|
1540
1464
|
}
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1465
|
+
if (this._elementRef.current) {
|
|
1466
|
+
this._elementRef.current.removeEventListener('keydown', this.handleKey, true);
|
|
1467
|
+
}
|
|
1468
|
+
this.mutationObserver?.disconnect();
|
|
1469
|
+
this.intersectionObserver?.disconnect();
|
|
1546
1470
|
}
|
|
1547
1471
|
}
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
var _this$props$trayProps2, _this$props$trayProps3, _this$props$trayProps4, _sanitizePlugins;
|
|
1551
|
-
|
|
1552
|
-
let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
1553
|
-
const rcsExists = !!((_this$props$trayProps2 = this.props.trayProps) !== null && _this$props$trayProps2 !== void 0 && _this$props$trayProps2.host && (_this$props$trayProps3 = this.props.trayProps) !== null && _this$props$trayProps3 !== void 0 && _this$props$trayProps3.jwt);
|
|
1472
|
+
wrapOptions(options = {}) {
|
|
1473
|
+
const rcsExists = !!(this.props.trayProps?.host && this.props.trayProps?.jwt);
|
|
1554
1474
|
const userLocale = editorLanguage(this.language);
|
|
1475
|
+
|
|
1476
|
+
// @ts-expect-error
|
|
1555
1477
|
const setupCallback = options.setup;
|
|
1556
1478
|
const canvasPlugins = rcsExists ? ['instructure_image', 'instructure_documents', 'instructure_equation'] : [];
|
|
1557
|
-
|
|
1558
1479
|
if (rcsExists && !this.props.instRecordDisabled) {
|
|
1559
1480
|
canvasPlugins.splice(2, 0, 'instructure_record');
|
|
1560
1481
|
}
|
|
1561
|
-
|
|
1562
1482
|
const pastePlugins = rcsExists ? ['instructure_paste', 'paste'] : ['paste'];
|
|
1563
|
-
|
|
1564
|
-
if (rcsExists && this.props.use_rce_icon_maker && ((_this$props$trayProps4 = this.props.trayProps) === null || _this$props$trayProps4 === void 0 ? void 0 : _this$props$trayProps4.contextType) === 'course') {
|
|
1483
|
+
if (rcsExists && this.props.use_rce_icon_maker && this.props.trayProps?.contextType === 'course') {
|
|
1565
1484
|
canvasPlugins.push('instructure_icon_maker');
|
|
1566
1485
|
}
|
|
1567
|
-
|
|
1568
1486
|
if (document[FS_ENABLED]) {
|
|
1569
1487
|
canvasPlugins.push('instructure_fullscreen');
|
|
1570
1488
|
}
|
|
1571
|
-
|
|
1572
1489
|
if (this.getRequiredFeatureStatuses().rce_find_replace) {
|
|
1573
1490
|
canvasPlugins.push('searchreplace');
|
|
1574
1491
|
canvasPlugins.push('instructure_search_and_replace');
|
|
1575
1492
|
}
|
|
1576
|
-
|
|
1577
1493
|
const possibleNewMenubarItems = this.props.editorOptions.menu ? Object.keys(this.props.editorOptions.menu).join(' ') : undefined;
|
|
1578
|
-
const wrappedOpts = {
|
|
1494
|
+
const wrappedOpts = {
|
|
1495
|
+
...defaultTinymceConfig,
|
|
1579
1496
|
...options,
|
|
1580
1497
|
readonly: this.props.readOnly,
|
|
1581
1498
|
theme: 'silver',
|
|
1582
1499
|
// some older code specified 'modern', which doesn't exist any more
|
|
1500
|
+
|
|
1501
|
+
// @ts-expect-error
|
|
1583
1502
|
height: options.height || DEFAULT_RCE_HEIGHT,
|
|
1584
1503
|
language: userLocale,
|
|
1585
1504
|
document_base_url: this.props.canvasOrigin,
|
|
1586
|
-
block_formats:
|
|
1505
|
+
block_formats:
|
|
1506
|
+
// @ts-expect-error
|
|
1507
|
+
options.block_formats || [`${formatMessage('Heading 2')}=h2`, `${formatMessage('Heading 3')}=h3`, `${formatMessage('Heading 4')}=h4`, `${formatMessage('Preformatted')}=pre`, `${formatMessage('Paragraph')}=p`].join('; '),
|
|
1587
1508
|
setup: editor => {
|
|
1588
|
-
var _bridge$trayProps;
|
|
1589
|
-
|
|
1590
1509
|
addKebabIcon(editor);
|
|
1591
1510
|
editorWrappers.set(editor, this);
|
|
1592
1511
|
const trayPropsWithColor = {
|
|
1512
|
+
// @ts-expect-error
|
|
1593
1513
|
brandColor: this.style.theme.canvasBrandColor,
|
|
1594
1514
|
...this.props.trayProps
|
|
1595
1515
|
};
|
|
1596
|
-
|
|
1516
|
+
bridge.trayProps?.set(editor, trayPropsWithColor);
|
|
1517
|
+
// @ts-expect-error
|
|
1597
1518
|
bridge.userLocale = userLocale;
|
|
1598
1519
|
bridge.canvasOrigin = this.props.canvasOrigin;
|
|
1599
|
-
|
|
1600
1520
|
if (typeof setupCallback === 'function') {
|
|
1601
1521
|
setupCallback(editor);
|
|
1602
1522
|
}
|
|
@@ -1605,8 +1525,10 @@ class RCEWrapper extends React.Component {
|
|
|
1605
1525
|
// in the editor matches the styles of the app it will be displayed in when saved.
|
|
1606
1526
|
// This is just so we inject the helper class names that tinyMCE uses for
|
|
1607
1527
|
// things like table resizing and stuff.
|
|
1528
|
+
// @ts-expect-error
|
|
1608
1529
|
content_css: options.content_css || [],
|
|
1609
|
-
|
|
1530
|
+
// @ts-expect-error
|
|
1531
|
+
content_style: contentCSS + (options.content_style || ''),
|
|
1610
1532
|
menubar: mergeMenuItems(getMenubarForVariant(this.variant), possibleNewMenubarItems),
|
|
1611
1533
|
// default menu options listed at https://www.tiny.cloud/docs/configure/editor-appearance/#menu
|
|
1612
1534
|
// tinymce's default edit and table menus are fine
|
|
@@ -1614,10 +1536,16 @@ class RCEWrapper extends React.Component {
|
|
|
1614
1536
|
// since we currently can't effectively paste using the clipboard api anyway.
|
|
1615
1537
|
// we include all the canvas specific items in the menu and toolbar
|
|
1616
1538
|
// and rely on tinymce only showing them if the plugin is provided.
|
|
1539
|
+
// @ts-expect-error
|
|
1617
1540
|
menu: mergeMenu(getMenuForVariant(this.variant), options.menu),
|
|
1618
|
-
toolbar: mergeToolbar(
|
|
1541
|
+
toolbar: mergeToolbar(
|
|
1542
|
+
// @ts-expect-error
|
|
1543
|
+
getToolbarForVariant(this.variant, this.ltiToolFavorites),
|
|
1544
|
+
// @ts-expect-error
|
|
1545
|
+
options.toolbar),
|
|
1619
1546
|
contextmenu: '',
|
|
1620
1547
|
// show the browser's native context menu
|
|
1548
|
+
|
|
1621
1549
|
toolbar_mode: 'sliding',
|
|
1622
1550
|
toolbar_sticky: true,
|
|
1623
1551
|
// In regards to the ability to disable plugins:
|
|
@@ -1627,8 +1555,10 @@ class RCEWrapper extends React.Component {
|
|
|
1627
1555
|
// handles all of that complexity. It that ever changes in the
|
|
1628
1556
|
// future in an upgraded version, we will have to update the
|
|
1629
1557
|
// logic in those other places as well.
|
|
1630
|
-
plugins: mergePlugins(['autolink', 'media', 'table', 'link', 'directionality', 'lists', 'textpattern', 'hr', 'instructure-ui-icons', 'instructure_condensed_buttons', 'instructure_links', 'instructure_html_view', 'instructure_media_embed', 'a11y_checker', 'wordcount', 'instructure_wordcount', 'instructure_studio_media_options', 'instructure_rce_external_tools', ...pastePlugins, ...canvasPlugins],
|
|
1631
|
-
|
|
1558
|
+
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_studio_media_options', 'instructure_rce_external_tools', ...pastePlugins, ...canvasPlugins],
|
|
1559
|
+
// filter out the plugins designated for removal
|
|
1560
|
+
// @ts-expect-error
|
|
1561
|
+
sanitizePlugins(options.plugins)?.filter(p => p.length > 0 && p[0] !== '-'), this.pluginsToExclude),
|
|
1632
1562
|
textpattern_patterns: [{
|
|
1633
1563
|
start: '* ',
|
|
1634
1564
|
cmd: 'InsertUnorderedList'
|
|
@@ -1637,128 +1567,120 @@ class RCEWrapper extends React.Component {
|
|
|
1637
1567
|
cmd: 'InsertUnorderedList'
|
|
1638
1568
|
}]
|
|
1639
1569
|
};
|
|
1640
|
-
|
|
1641
1570
|
if (this.props.trayProps) {
|
|
1571
|
+
// @ts-expect-error
|
|
1642
1572
|
wrappedOpts.canvas_rce_user_context = {
|
|
1643
1573
|
type: this.props.trayProps.contextType,
|
|
1644
1574
|
id: this.props.trayProps.contextId
|
|
1645
1575
|
};
|
|
1576
|
+
|
|
1577
|
+
// @ts-expect-error
|
|
1646
1578
|
wrappedOpts.canvas_rce_containing_context = {
|
|
1579
|
+
// @ts-expect-error
|
|
1647
1580
|
type: this.props.trayProps.containingContext.contextType,
|
|
1581
|
+
// @ts-expect-error
|
|
1648
1582
|
id: this.props.trayProps.containingContext.contextId
|
|
1649
1583
|
};
|
|
1650
1584
|
}
|
|
1651
|
-
|
|
1652
1585
|
return wrappedOpts;
|
|
1653
1586
|
}
|
|
1654
|
-
|
|
1655
1587
|
unhandleTextareaChange() {
|
|
1656
1588
|
if (this._textareaEl) {
|
|
1657
1589
|
this._textareaEl.removeEventListener('input', this.handleTextareaChange);
|
|
1658
1590
|
}
|
|
1659
1591
|
}
|
|
1660
|
-
|
|
1661
1592
|
registerTextareaChange() {
|
|
1662
1593
|
const el = this.getTextarea();
|
|
1663
|
-
|
|
1664
1594
|
if (this._textareaEl !== el) {
|
|
1665
1595
|
this.unhandleTextareaChange();
|
|
1666
|
-
|
|
1667
1596
|
if (el) {
|
|
1668
1597
|
el.addEventListener('input', this.handleTextareaChange);
|
|
1669
|
-
|
|
1670
1598
|
if (this.props.textareaClassName) {
|
|
1671
1599
|
// split the string on whitespace because classList doesn't let you add multiple
|
|
1672
1600
|
// space seperated classes at a time but does let you add an array of them
|
|
1673
1601
|
el.classList.add(...this.props.textareaClassName.split(/\s+/));
|
|
1674
1602
|
}
|
|
1675
|
-
|
|
1676
1603
|
this._textareaEl = el;
|
|
1677
1604
|
}
|
|
1678
1605
|
}
|
|
1679
1606
|
}
|
|
1680
|
-
|
|
1681
1607
|
componentDidMount() {
|
|
1682
1608
|
if (this.state.shouldShowEditor) {
|
|
1683
1609
|
this.editorReallyDidMount();
|
|
1684
1610
|
} else {
|
|
1685
1611
|
this.intersectionObserver = new IntersectionObserver(entries => {
|
|
1686
1612
|
const entry = entries[0];
|
|
1687
|
-
|
|
1688
1613
|
if (entry.isIntersecting || entry.intersectionRatio > 0) {
|
|
1689
1614
|
this.setState({
|
|
1690
1615
|
shouldShowEditor: true
|
|
1691
1616
|
});
|
|
1692
1617
|
}
|
|
1693
|
-
},
|
|
1618
|
+
},
|
|
1619
|
+
// initialize the RCE when it gets close to entering the viewport
|
|
1694
1620
|
{
|
|
1695
1621
|
root: null,
|
|
1696
1622
|
rootMargin: '200px 0px',
|
|
1697
1623
|
threshold: 0.0
|
|
1698
1624
|
});
|
|
1625
|
+
// @ts-expect-error
|
|
1699
1626
|
this.intersectionObserver.observe(this._editorPlaceholderRef.current);
|
|
1700
1627
|
}
|
|
1701
1628
|
}
|
|
1702
|
-
|
|
1703
1629
|
componentDidUpdate(prevProps, prevState) {
|
|
1704
1630
|
if (this.state.shouldShowEditor) {
|
|
1705
1631
|
if (!prevState.shouldShowEditor) {
|
|
1706
|
-
var _this$intersectionObs2;
|
|
1707
|
-
|
|
1708
1632
|
this.editorReallyDidMount();
|
|
1709
|
-
|
|
1633
|
+
this.intersectionObserver?.disconnect();
|
|
1710
1634
|
} else {
|
|
1711
1635
|
this.registerTextareaChange();
|
|
1712
|
-
|
|
1713
1636
|
if (prevState.editorView !== this.state.editorView) {
|
|
1714
1637
|
this.setEditorView(this.state.editorView);
|
|
1715
1638
|
this.focusCurrentView();
|
|
1716
1639
|
}
|
|
1717
|
-
|
|
1718
1640
|
if (prevProps.readOnly !== this.props.readOnly) {
|
|
1719
1641
|
this.mceInstance().mode.set(this.props.readOnly ? 'readonly' : 'design');
|
|
1720
1642
|
}
|
|
1721
1643
|
}
|
|
1722
1644
|
}
|
|
1723
1645
|
}
|
|
1724
|
-
|
|
1725
1646
|
editorReallyDidMount() {
|
|
1726
1647
|
const myTiny = this.mceInstance();
|
|
1727
1648
|
this.pendingEventHandlers.forEach(e => {
|
|
1649
|
+
// @ts-expect-error
|
|
1728
1650
|
myTiny.on(e.name, e.handler);
|
|
1729
1651
|
});
|
|
1730
|
-
|
|
1731
1652
|
this._tagTinymceAuxDiv();
|
|
1732
|
-
|
|
1733
1653
|
this.registerTextareaChange();
|
|
1734
|
-
|
|
1735
|
-
this._elementRef.current.addEventListener('keydown', this.handleKey, true);
|
|
1736
|
-
|
|
1737
|
-
|
|
1654
|
+
// @ts-expect-error
|
|
1655
|
+
this._elementRef.current.addEventListener('keydown', this.handleKey, true);
|
|
1656
|
+
// give the textarea its initial size
|
|
1738
1657
|
this.onResize(null, {
|
|
1739
1658
|
deltaY: 0
|
|
1740
|
-
});
|
|
1659
|
+
});
|
|
1660
|
+
// Preload the LTI Tools modal
|
|
1741
1661
|
// This helps with loading the favorited external tools
|
|
1742
|
-
|
|
1743
1662
|
if (this.ltiToolFavorites.length > 0) {
|
|
1744
1663
|
import('./plugins/instructure_rce_external_tools/components/ExternalToolSelectionDialog/ExternalToolSelectionDialog');
|
|
1745
1664
|
}
|
|
1746
|
-
|
|
1747
1665
|
bridge.renderEditor(this);
|
|
1748
1666
|
}
|
|
1749
1667
|
|
|
1668
|
+
// @ts-expect-error
|
|
1750
1669
|
setEditorView(view) {
|
|
1751
1670
|
switch (view) {
|
|
1752
1671
|
case WYSIWYG_VIEW:
|
|
1753
1672
|
this.setCode(this.textareaValue());
|
|
1754
1673
|
this.mceInstance().show();
|
|
1755
1674
|
break;
|
|
1756
|
-
|
|
1757
1675
|
default:
|
|
1758
1676
|
this.mceInstance().hide();
|
|
1759
1677
|
}
|
|
1760
1678
|
}
|
|
1761
1679
|
|
|
1680
|
+
/**
|
|
1681
|
+
* Used for reseting the value during tests
|
|
1682
|
+
*/
|
|
1683
|
+
|
|
1762
1684
|
renderHtmlEditor() {
|
|
1763
1685
|
// the div keeps the editor from collapsing while the code editor is downloaded
|
|
1764
1686
|
return /*#__PURE__*/React.createElement(Suspense, {
|
|
@@ -1777,27 +1699,29 @@ class RCEWrapper extends React.Component {
|
|
|
1777
1699
|
as: "div",
|
|
1778
1700
|
borderRadius: "medium",
|
|
1779
1701
|
borderWidth: "small"
|
|
1780
|
-
}, /*#__PURE__*/React.createElement(RceHtmlEditor
|
|
1702
|
+
}, /*#__PURE__*/React.createElement(RceHtmlEditor
|
|
1703
|
+
// @ts-expect-error
|
|
1704
|
+
, {
|
|
1781
1705
|
ref: this._prettyHtmlEditorRef,
|
|
1782
1706
|
height: this.state.height,
|
|
1783
1707
|
code: this.getCode(),
|
|
1784
1708
|
onChange: value => {
|
|
1785
|
-
this.getTextarea()
|
|
1709
|
+
const node = this.getTextarea();
|
|
1710
|
+
if (node) {
|
|
1711
|
+
node.value = value;
|
|
1712
|
+
}
|
|
1786
1713
|
this.handleTextareaChange();
|
|
1787
1714
|
}
|
|
1788
1715
|
})));
|
|
1789
1716
|
}
|
|
1790
|
-
|
|
1791
1717
|
render() {
|
|
1792
|
-
var _this$props$trayProps5, _this$props$trayProps6, _this$props$trayProps7, _this$props$trayProps8, _this$props$trayProps9;
|
|
1793
|
-
|
|
1794
1718
|
const {
|
|
1795
1719
|
trayProps,
|
|
1796
1720
|
...mceProps
|
|
1797
1721
|
} = this.props;
|
|
1798
|
-
|
|
1799
1722
|
if (!this.state.shouldShowEditor) {
|
|
1800
1723
|
return /*#__PURE__*/React.createElement("div", {
|
|
1724
|
+
// @ts-expect-error
|
|
1801
1725
|
ref: this._editorPlaceholderRef,
|
|
1802
1726
|
style: {
|
|
1803
1727
|
height: `${this.props.editorOptions.height}px`,
|
|
@@ -1805,21 +1729,22 @@ class RCEWrapper extends React.Component {
|
|
|
1805
1729
|
}
|
|
1806
1730
|
});
|
|
1807
1731
|
}
|
|
1808
|
-
|
|
1809
1732
|
const statusBarFeatures = getStatusBarFeaturesForVariant(this.variant, this.props.ai_text_tools);
|
|
1810
1733
|
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("style", null, this.style.css), /*#__PURE__*/React.createElement(StoreProvider, {
|
|
1811
|
-
jwt:
|
|
1812
|
-
refreshToken:
|
|
1813
|
-
host:
|
|
1814
|
-
contextType:
|
|
1815
|
-
contextId:
|
|
1734
|
+
jwt: this.props.trayProps?.jwt,
|
|
1735
|
+
refreshToken: this.props.trayProps?.refreshToken,
|
|
1736
|
+
host: this.props.trayProps?.host,
|
|
1737
|
+
contextType: this.props.trayProps?.contextType,
|
|
1738
|
+
contextId: this.props.trayProps?.contextId,
|
|
1816
1739
|
canvasOrigin: this.props.canvasOrigin
|
|
1817
1740
|
}, storeProps => {
|
|
1818
|
-
var _this$props$trayProps10;
|
|
1819
|
-
|
|
1820
1741
|
return /*#__PURE__*/React.createElement("div", {
|
|
1821
|
-
key: this.id
|
|
1822
|
-
|
|
1742
|
+
key: this.id
|
|
1743
|
+
// @ts-expect-error
|
|
1744
|
+
,
|
|
1745
|
+
className: `${this.style.classNames.root} rce-wrapper`
|
|
1746
|
+
// @ts-expect-error
|
|
1747
|
+
,
|
|
1823
1748
|
ref: this._elementRef,
|
|
1824
1749
|
style: this.variant === 'full' ? {
|
|
1825
1750
|
marginBottom: '.5rem'
|
|
@@ -1830,10 +1755,14 @@ class RCEWrapper extends React.Component {
|
|
|
1830
1755
|
id: `show-on-focus-btn-${this.id}`,
|
|
1831
1756
|
onClick: this.openKBShortcutModal,
|
|
1832
1757
|
margin: "xx-small",
|
|
1833
|
-
screenReaderLabel: formatMessage('View keyboard shortcuts')
|
|
1758
|
+
screenReaderLabel: formatMessage('View keyboard shortcuts')
|
|
1759
|
+
// @ts-expect-error
|
|
1760
|
+
,
|
|
1834
1761
|
ref: el => this._showOnFocusButton = el
|
|
1835
1762
|
}, /*#__PURE__*/React.createElement(IconKeyboardShortcutsLine, null)), /*#__PURE__*/React.createElement(AlertMessageArea, {
|
|
1836
|
-
messages: this.state.messages
|
|
1763
|
+
messages: this.state.messages
|
|
1764
|
+
// @ts-expect-error
|
|
1765
|
+
,
|
|
1837
1766
|
liveRegion: this.props.liveRegion,
|
|
1838
1767
|
afterDismiss: this.removeAlert
|
|
1839
1768
|
}), this.state.editorView === PRETTY_HTML_EDITOR_VIEW && this.renderHtmlEditor(), /*#__PURE__*/React.createElement("div", {
|
|
@@ -1842,15 +1771,25 @@ class RCEWrapper extends React.Component {
|
|
|
1842
1771
|
}
|
|
1843
1772
|
}, /*#__PURE__*/React.createElement(Editor, {
|
|
1844
1773
|
id: mceProps.textareaId,
|
|
1845
|
-
textareaName: mceProps.name
|
|
1774
|
+
textareaName: mceProps.name
|
|
1775
|
+
// @ts-expect-error
|
|
1776
|
+
,
|
|
1846
1777
|
init: this.tinymceInitOptions,
|
|
1847
|
-
initialValue: this.initialContent
|
|
1778
|
+
initialValue: this.initialContent
|
|
1779
|
+
// @ts-expect-error
|
|
1780
|
+
,
|
|
1848
1781
|
onInit: this.onInit,
|
|
1849
1782
|
onClick: this.handleFocusEditor,
|
|
1850
|
-
onKeypress: this.handleFocusEditor
|
|
1783
|
+
onKeypress: this.handleFocusEditor
|
|
1784
|
+
// @ts-expect-error
|
|
1785
|
+
,
|
|
1851
1786
|
onActivate: this.handleFocusEditor,
|
|
1852
|
-
onRemove: this.onRemove
|
|
1853
|
-
|
|
1787
|
+
onRemove: this.onRemove
|
|
1788
|
+
// @ts-expect-error
|
|
1789
|
+
,
|
|
1790
|
+
onFocus: this.handleFocusEditor
|
|
1791
|
+
// @ts-expect-error
|
|
1792
|
+
,
|
|
1854
1793
|
onBlur: this.handleBlurEditor,
|
|
1855
1794
|
onNodeChange: this.onNodeChange,
|
|
1856
1795
|
onEditorChange: this.onEditorChange,
|
|
@@ -1867,7 +1806,9 @@ class RCEWrapper extends React.Component {
|
|
|
1867
1806
|
onResize: this.onResize,
|
|
1868
1807
|
onKBShortcutModalOpen: this.openKBShortcutModal,
|
|
1869
1808
|
onA11yChecker: this.onA11yChecker,
|
|
1870
|
-
onFullscreen: this.handleClickFullscreen
|
|
1809
|
+
onFullscreen: this.handleClickFullscreen
|
|
1810
|
+
// @ts-expect-error
|
|
1811
|
+
,
|
|
1871
1812
|
a11yBadgeColor: this.style.theme.canvasBadgeBackgroundColor,
|
|
1872
1813
|
a11yErrorsCount: this.state.a11yErrorsCount,
|
|
1873
1814
|
onWordcountModalOpen: () => launchWordcountModal(this.mceInstance(), document, {
|
|
@@ -1876,8 +1817,8 @@ class RCEWrapper extends React.Component {
|
|
|
1876
1817
|
disabledPlugins: this.pluginsToExclude,
|
|
1877
1818
|
features: statusBarFeatures,
|
|
1878
1819
|
onAI: this.handleAIClick
|
|
1879
|
-
}),
|
|
1880
|
-
mountNode:
|
|
1820
|
+
}), this.props.trayProps?.containingContext && /*#__PURE__*/React.createElement(CanvasContentTray, Object.assign({
|
|
1821
|
+
mountNode: instuiPopupMountNodeFn,
|
|
1881
1822
|
key: this.id,
|
|
1882
1823
|
canvasOrigin: this.getCanvasUrl(),
|
|
1883
1824
|
bridge: bridge,
|
|
@@ -1885,15 +1826,19 @@ class RCEWrapper extends React.Component {
|
|
|
1885
1826
|
onTrayClosing: this.handleContentTrayClosing,
|
|
1886
1827
|
use_rce_icon_maker: this.props.use_rce_icon_maker
|
|
1887
1828
|
}, trayProps, {
|
|
1829
|
+
// @ts-expect-error
|
|
1888
1830
|
storeProps: storeProps
|
|
1889
1831
|
})), /*#__PURE__*/React.createElement(KeyboardShortcutModal, {
|
|
1890
1832
|
onExited: this.KBShortcutModalExited,
|
|
1891
1833
|
onDismiss: this.closeKBShortcutModal,
|
|
1892
1834
|
open: this.state.KBShortcutModalOpen
|
|
1893
|
-
}), this.props.ai_text_tools && this.AIToolsTray &&
|
|
1835
|
+
}), this.props.ai_text_tools && this.AIToolsTray &&
|
|
1836
|
+
/*#__PURE__*/
|
|
1837
|
+
// @ts-expect-error
|
|
1838
|
+
React.createElement(this.AIToolsTray, {
|
|
1894
1839
|
open: this.state.AIToolsOpen,
|
|
1895
1840
|
container: document.querySelector('[role="main"]'),
|
|
1896
|
-
mountNode:
|
|
1841
|
+
mountNode: instuiPopupMountNodeFn,
|
|
1897
1842
|
contextId: trayProps.contextId,
|
|
1898
1843
|
contextType: trayProps.contextId,
|
|
1899
1844
|
currentContent: this.getCurrentContentForAI(),
|
|
@@ -1916,13 +1861,37 @@ class RCEWrapper extends React.Component {
|
|
|
1916
1861
|
}, this.state.announcement));
|
|
1917
1862
|
}));
|
|
1918
1863
|
}
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1864
|
+
}
|
|
1865
|
+
RCEWrapper.propTypes = {
|
|
1866
|
+
ai_text_tools: _pt.bool,
|
|
1867
|
+
autosave: _pt.shape({
|
|
1868
|
+
enabled: _pt.bool,
|
|
1869
|
+
maxAge: _pt.number,
|
|
1870
|
+
interval: _pt.number
|
|
1871
|
+
}),
|
|
1872
|
+
canvasOrigin: _pt.string,
|
|
1873
|
+
defaultContent: _pt.string,
|
|
1874
|
+
editorView: _pt.string,
|
|
1875
|
+
features: _pt.objectOf(_pt.any),
|
|
1876
|
+
handleUnmount: _pt.func,
|
|
1877
|
+
instRecordDisabled: _pt.bool,
|
|
1878
|
+
language: _pt.string,
|
|
1879
|
+
ltiToolFavorites: _pt.arrayOf(_pt.string),
|
|
1880
|
+
maxInitRenderedRCEs: _pt.number,
|
|
1881
|
+
name: _pt.string,
|
|
1882
|
+
onBlur: _pt.func,
|
|
1883
|
+
onContentChange: _pt.func,
|
|
1884
|
+
onFocus: _pt.func,
|
|
1885
|
+
onInitted: _pt.func,
|
|
1886
|
+
onRemove: _pt.func,
|
|
1887
|
+
readOnly: _pt.bool,
|
|
1888
|
+
renderKBShortcutModal: _pt.bool,
|
|
1889
|
+
textareaClassName: _pt.string,
|
|
1890
|
+
textareaId: _pt.string,
|
|
1891
|
+
tinymce: _pt.any.isRequired,
|
|
1892
|
+
use_rce_icon_maker: _pt.bool,
|
|
1893
|
+
userCacheKey: _pt.string
|
|
1894
|
+
};
|
|
1926
1895
|
RCEWrapper.propTypes = rceWrapperPropTypes;
|
|
1927
1896
|
RCEWrapper.defaultProps = {
|
|
1928
1897
|
trayProps: null,
|
|
@@ -1933,90 +1902,10 @@ RCEWrapper.defaultProps = {
|
|
|
1933
1902
|
ltiTools: [],
|
|
1934
1903
|
maxInitRenderedRCEs: -1,
|
|
1935
1904
|
features: {},
|
|
1936
|
-
timezone:
|
|
1905
|
+
timezone: Intl?.DateTimeFormat()?.resolvedOptions()?.timeZone,
|
|
1937
1906
|
canvasOrigin: '',
|
|
1938
1907
|
variant: 'full'
|
|
1939
1908
|
};
|
|
1940
1909
|
RCEWrapper.skinCssInjected = false;
|
|
1941
|
-
|
|
1942
|
-
function mergeMenuItems(standard, custom) {
|
|
1943
|
-
var _custom$trim;
|
|
1944
|
-
|
|
1945
|
-
let c = custom === null || custom === void 0 ? void 0 : (_custom$trim = custom.trim) === null || _custom$trim === void 0 ? void 0 : _custom$trim.call(custom);
|
|
1946
|
-
if (!c) return standard;
|
|
1947
|
-
const s = new Set(standard.split(/[\s|]+/)); // remove any duplicates
|
|
1948
|
-
|
|
1949
|
-
c = c.split(/\s+/).filter(m => !s.has(m));
|
|
1950
|
-
c = c.join(' ').replace(/^\s*\|\s*/, '').replace(/\s*\|\s*$/, '');
|
|
1951
|
-
return `${standard} | ${c}`;
|
|
1952
|
-
} // standard: the incoming tinymce menu object
|
|
1953
|
-
// custom: tinymce menu object to merge into standard
|
|
1954
|
-
// returns: the merged result by mutating incoming standard arg.
|
|
1955
|
-
// It will add commands to existing menus, or add a new menu
|
|
1956
|
-
// if the custom one does not exist
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
function mergeMenu(standard, custom) {
|
|
1960
|
-
if (!custom) return standard;
|
|
1961
|
-
Object.keys(custom).forEach(k => {
|
|
1962
|
-
const curr_m = standard[k];
|
|
1963
|
-
|
|
1964
|
-
if (curr_m) {
|
|
1965
|
-
curr_m.items = mergeMenuItems(curr_m.items, custom[k].items);
|
|
1966
|
-
} else {
|
|
1967
|
-
standard[k] = { ...custom[k]
|
|
1968
|
-
};
|
|
1969
|
-
}
|
|
1970
|
-
});
|
|
1971
|
-
return standard;
|
|
1972
|
-
} // standard: incoming tinymce toolbar array
|
|
1973
|
-
// custom: tinymce toolbar array to merge into standard
|
|
1974
|
-
// returns: the merged result by mutating the incoming standard arg.
|
|
1975
|
-
// It will add commands to existing toolbars, or add a new toolbar
|
|
1976
|
-
// if the custom one does not exist
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
function mergeToolbar(standard, custom) {
|
|
1980
|
-
if (!custom) return standard; // merge given toolbar data into the default toolbar
|
|
1981
|
-
|
|
1982
|
-
custom.forEach(tb => {
|
|
1983
|
-
const curr_tb = standard.find(t => tb.name && formatMessage(tb.name) === t.name);
|
|
1984
|
-
|
|
1985
|
-
if (curr_tb) {
|
|
1986
|
-
curr_tb.items.splice(curr_tb.items.length, 0, ...tb.items);
|
|
1987
|
-
} else {
|
|
1988
|
-
standard.push(tb);
|
|
1989
|
-
}
|
|
1990
|
-
});
|
|
1991
|
-
return standard;
|
|
1992
|
-
} // standard: incoming array of plugin names
|
|
1993
|
-
// custom: array of plugin names to merge
|
|
1994
|
-
// exclusions: array of plugins to remove
|
|
1995
|
-
// returns: the merged result, duplicates and exclusions removed
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
function mergePlugins(standard) {
|
|
1999
|
-
let custom = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
|
|
2000
|
-
let exclusions = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : [];
|
|
2001
|
-
const union = new Set(standard);
|
|
2002
|
-
|
|
2003
|
-
for (const c of custom) {
|
|
2004
|
-
union.add(c);
|
|
2005
|
-
}
|
|
2006
|
-
|
|
2007
|
-
for (const e of exclusions) {
|
|
2008
|
-
union.delete(e);
|
|
2009
|
-
}
|
|
2010
|
-
|
|
2011
|
-
return [...union];
|
|
2012
|
-
} // plugins is an array of strings
|
|
2013
|
-
// the convention is that plugins starting with '-',
|
|
2014
|
-
// i.e. a hyphen, are to be disabled in the RCE instance
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
function parsePluginsToExclude(plugins) {
|
|
2018
|
-
return plugins.filter(plugin => plugin.length > 0 && plugin[0] === '-').map(pluginToIgnore => pluginToIgnore.slice(1));
|
|
2019
|
-
}
|
|
2020
|
-
|
|
2021
1910
|
export default RCEWrapper;
|
|
2022
1911
|
export { mergeMenuItems, mergeMenu, mergeToolbar, mergePlugins, parsePluginsToExclude };
|