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