diff --git a/src/components/common/branding/branding.tsx b/src/components/common/branding/branding.tsx index bda523b27..531cac588 100644 --- a/src/components/common/branding/branding.tsx +++ b/src/components/common/branding/branding.tsx @@ -4,12 +4,10 @@ SPDX-License-Identifier: AGPL-3.0-only */ -import equal from 'fast-deep-equal' -import React from 'react' -import { useSelector } from 'react-redux' -import { ApplicationState } from '../../../redux' +import React, { useMemo } from 'react' import { ShowIf } from '../show-if/show-if' import './branding.scss' +import { useApplicationState } from '../../../hooks/common/use-application-state' export interface BrandingProps { inline?: boolean @@ -17,24 +15,30 @@ export interface BrandingProps { } export const Branding: React.FC = ({ inline = false, delimiter = true }) => { - const branding = useSelector((state: ApplicationState) => state.config.branding, equal) + const branding = useApplicationState((state) => state.config.branding) const showBranding = !!branding.name || !!branding.logo - return ( - - - @ - - {branding.logo ? ( + const brandingDom = useMemo(() => { + if (branding.logo) { + return ( {branding.name} - ) : ( - branding.name - )} + ) + } else { + return branding.name + } + }, [branding.logo, branding.name, inline]) + + return ( + + + @ + + {brandingDom} ) } diff --git a/src/components/common/motd-banner/motd-banner.tsx b/src/components/common/motd-banner/motd-banner.tsx index b2c592da7..52db86f27 100644 --- a/src/components/common/motd-banner/motd-banner.tsx +++ b/src/components/common/motd-banner/motd-banner.tsx @@ -4,17 +4,15 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import equal from 'fast-deep-equal' import React, { useCallback } from 'react' import { Alert, Button } from 'react-bootstrap' -import { useSelector } from 'react-redux' -import { ApplicationState } from '../../../redux' import { setBanner } from '../../../redux/banner/methods' import { ForkAwesomeIcon } from '../fork-awesome/fork-awesome-icon' import { BANNER_LOCAL_STORAGE_KEY } from '../../application-loader/initializers/fetch-and-set-banner' +import { useApplicationState } from '../../../hooks/common/use-application-state' export const MotdBanner: React.FC = () => { - const bannerState = useSelector((state: ApplicationState) => state.banner, equal) + const bannerState = useApplicationState((state) => state.banner) const dismissBanner = useCallback(() => { if (bannerState.lastModified) { diff --git a/src/components/document-read-only-page/document-read-only-page.tsx b/src/components/document-read-only-page/document-read-only-page.tsx index 273094cce..a035ded67 100644 --- a/src/components/document-read-only-page/document-read-only-page.tsx +++ b/src/components/document-read-only-page/document-read-only-page.tsx @@ -6,12 +6,10 @@ import React, { useCallback } from 'react' import { useTranslation } from 'react-i18next' -import { useSelector } from 'react-redux' import { useParams } from 'react-router' import { useApplyDarkMode } from '../../hooks/common/use-apply-dark-mode' import { useDocumentTitleWithNoteTitle } from '../../hooks/common/use-document-title-with-note-title' import { useNoteMarkdownContent } from '../../hooks/common/use-note-markdown-content' -import { ApplicationState } from '../../redux' import { setNoteFrontmatter, updateNoteTitleByFirstHeading } from '../../redux/note-details/methods' import { MotdBanner } from '../common/motd-banner/motd-banner' import { ShowIf } from '../common/show-if/show-if' @@ -23,6 +21,7 @@ import { DocumentInfobar } from './document-infobar' import { ErrorWhileLoadingNoteAlert } from './ErrorWhileLoadingNoteAlert' import { LoadingNoteAlert } from './LoadingNoteAlert' import { RendererType } from '../render-page/rendering-message' +import { useApplicationState } from '../../hooks/common/use-application-state' export const DocumentReadOnlyPage: React.FC = () => { useTranslation() @@ -35,7 +34,7 @@ export const DocumentReadOnlyPage: React.FC = () => { const onFrontmatterChange = useCallback(setNoteFrontmatter, []) const [error, loading] = useLoadNoteFromServer() const markdownContent = useNoteMarkdownContent() - const noteDetails = useSelector((state: ApplicationState) => state.noteDetails) + const noteDetails = useApplicationState((state) => state.noteDetails) return (
diff --git a/src/components/editor-page/app-bar/app-bar.tsx b/src/components/editor-page/app-bar/app-bar.tsx index b050f2bed..1375da7b1 100644 --- a/src/components/editor-page/app-bar/app-bar.tsx +++ b/src/components/editor-page/app-bar/app-bar.tsx @@ -5,10 +5,7 @@ */ import React from 'react' -import equal from 'fast-deep-equal' import { Nav, Navbar } from 'react-bootstrap' -import { useSelector } from 'react-redux' -import { ApplicationState } from '../../../redux' import { ShowIf } from '../../common/show-if/show-if' import { SignInButton } from '../../landing-layout/navigation/sign-in-button' import { UserDropdown } from '../../landing-layout/navigation/user-dropdown' @@ -21,6 +18,7 @@ import { NoteType } from '../note-frontmatter/note-frontmatter' import { SlideModeButton } from './slide-mode-button' import { ReadOnlyModeButton } from './read-only-mode-button' import { NewNoteButton } from './new-note-button' +import { useApplicationState } from '../../../hooks/common/use-application-state' export enum AppBarMode { BASIC, @@ -32,8 +30,8 @@ export interface AppBarProps { } export const AppBar: React.FC = ({ mode }) => { - const userExists = useSelector((state: ApplicationState) => !!state.user) - const noteFrontmatter = useSelector((state: ApplicationState) => state.noteDetails.frontmatter, equal) + const userExists = useApplicationState((state) => !!state.user) + const noteFrontmatter = useApplicationState((state) => state.noteDetails.frontmatter) return ( diff --git a/src/components/editor-page/app-bar/editor-view-mode.tsx b/src/components/editor-page/app-bar/editor-view-mode.tsx index e813186ed..485c934c1 100644 --- a/src/components/editor-page/app-bar/editor-view-mode.tsx +++ b/src/components/editor-page/app-bar/editor-view-mode.tsx @@ -7,10 +7,9 @@ import React from 'react' import { ToggleButton, ToggleButtonGroup } from 'react-bootstrap' import { useTranslation } from 'react-i18next' -import { useSelector } from 'react-redux' -import { ApplicationState } from '../../../redux' import { setEditorMode } from '../../../redux/editor/methods' import { ForkAwesomeIcon } from '../../common/fork-awesome/fork-awesome-icon' +import { useApplicationState } from '../../../hooks/common/use-application-state' export enum EditorMode { PREVIEW = 'view', @@ -20,7 +19,8 @@ export enum EditorMode { export const EditorViewMode: React.FC = () => { const { t } = useTranslation() - const editorMode = useSelector((state: ApplicationState) => state.editorConfig.editorMode) + const editorMode = useApplicationState((state) => state.editorConfig.editorMode) + return ( { - const syncScrollEnabled = useSelector((state: ApplicationState) => state.editorConfig.syncScroll) + const syncScrollEnabled = useApplicationState((state) => state.editorConfig.syncScroll) ? SyncScrollState.SYNCED : SyncScrollState.UNSYNCED const { t } = useTranslation() diff --git a/src/components/editor-page/document-bar/share/share-modal.tsx b/src/components/editor-page/document-bar/share/share-modal.tsx index 560731599..cf7b3fb25 100644 --- a/src/components/editor-page/document-bar/share/share-modal.tsx +++ b/src/components/editor-page/document-bar/share/share-modal.tsx @@ -4,19 +4,17 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import equal from 'fast-deep-equal' import React from 'react' import { Modal } from 'react-bootstrap' import { Trans, useTranslation } from 'react-i18next' -import { useSelector } from 'react-redux' import { useParams } from 'react-router-dom' import { useFrontendBaseUrl } from '../../../../hooks/common/use-frontend-base-url' -import { ApplicationState } from '../../../../redux' import { CopyableField } from '../../../common/copyable/copyable-field/copyable-field' import { CommonModal } from '../../../common/modals/common-modal' import { ShowIf } from '../../../common/show-if/show-if' import { EditorPagePathParams } from '../../editor-page' import { NoteType } from '../../note-frontmatter/note-frontmatter' +import { useApplicationState } from '../../../../hooks/common/use-application-state' export interface ShareModalProps { show: boolean @@ -25,8 +23,8 @@ export interface ShareModalProps { export const ShareModal: React.FC = ({ show, onHide }) => { useTranslation() - const noteFrontmatter = useSelector((state: ApplicationState) => state.noteDetails.frontmatter, equal) - const editorMode = useSelector((state: ApplicationState) => state.editorConfig.editorMode) + const noteFrontmatter = useApplicationState((state) => state.noteDetails.frontmatter) + const editorMode = useApplicationState((state) => state.editorConfig.editorMode) const baseUrl = useFrontendBaseUrl() const { id } = useParams() diff --git a/src/components/editor-page/editor-page.tsx b/src/components/editor-page/editor-page.tsx index 7d1cd43d9..50e784235 100644 --- a/src/components/editor-page/editor-page.tsx +++ b/src/components/editor-page/editor-page.tsx @@ -6,11 +6,9 @@ import React, { useCallback, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' -import { useSelector } from 'react-redux' import { useApplyDarkMode } from '../../hooks/common/use-apply-dark-mode' import { useDocumentTitleWithNoteTitle } from '../../hooks/common/use-document-title-with-note-title' import { useNoteMarkdownContent } from '../../hooks/common/use-note-markdown-content' -import { ApplicationState } from '../../redux' import { SetCheckboxInMarkdownContent, setNoteFrontmatter, @@ -36,6 +34,7 @@ import { UiNotifications } from '../notifications/ui-notifications' import { useNotificationTest } from './use-notification-test' import { IframeCommunicatorContextProvider } from './render-context/iframe-communicator-context-provider' import { useUpdateLocalHistoryEntry } from './hooks/useUpdateLocalHistoryEntry' +import { useApplicationState } from '../../hooks/common/use-application-state' export interface EditorPagePathParams { id: string @@ -51,8 +50,8 @@ export const EditorPage: React.FC = () => { const markdownContent = useNoteMarkdownContent() const scrollSource = useRef(ScrollSource.EDITOR) - const editorMode: EditorMode = useSelector((state: ApplicationState) => state.editorConfig.editorMode) - const editorSyncScroll: boolean = useSelector((state: ApplicationState) => state.editorConfig.syncScroll) + const editorMode: EditorMode = useApplicationState((state) => state.editorConfig.editorMode) + const editorSyncScroll: boolean = useApplicationState((state) => state.editorConfig.syncScroll) const [scrollState, setScrollState] = useState(() => ({ editorScrollState: { firstLineInView: 1, scrolledPercentage: 0 }, diff --git a/src/components/editor-page/editor-pane/editor-pane.tsx b/src/components/editor-page/editor-pane/editor-pane.tsx index 21b97835a..8f238a50b 100644 --- a/src/components/editor-page/editor-pane/editor-pane.tsx +++ b/src/components/editor-page/editor-pane/editor-pane.tsx @@ -27,12 +27,9 @@ import 'codemirror/keymap/emacs' import 'codemirror/keymap/sublime' import 'codemirror/keymap/vim' import 'codemirror/mode/gfm/gfm' -import equal from 'fast-deep-equal' import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { Controlled as ControlledCodeMirror } from 'react-codemirror2' import { useTranslation } from 'react-i18next' -import { useSelector } from 'react-redux' -import { ApplicationState } from '../../../redux' import { MaxLengthWarningModal } from '../editor-modals/max-length-warning-modal' import { ScrollProps, ScrollState } from '../synced-scroll/scroll-props' import { allHinters, findWordAtCursor } from './autocompletion' @@ -42,6 +39,7 @@ import { createStatusInfo, defaultState, StatusBar, StatusBarInfo } from './stat import { ToolBar } from './tool-bar/tool-bar' import { handleUpload } from './upload-handler' import { handleFilePaste, handleTablePaste, PasteEvent } from './tool-bar/utils/pasteHandlers' +import { useApplicationState } from '../../../hooks/common/use-application-state' export interface EditorPaneProps { onContentChange: (content: string) => void @@ -81,14 +79,14 @@ export const EditorPane: React.FC = ({ onMakeScrollSource }) => { const { t } = useTranslation() - const maxLength = useSelector((state: ApplicationState) => state.config.maxDocumentLength) - const smartPasteEnabled = useSelector((state: ApplicationState) => state.editorConfig.smartPaste) + const maxLength = useApplicationState((state) => state.config.maxDocumentLength) + const smartPasteEnabled = useApplicationState((state) => state.editorConfig.smartPaste) const [showMaxLengthWarning, setShowMaxLengthWarning] = useState(false) const maxLengthWarningAlreadyShown = useRef(false) const [editor, setEditor] = useState() const [statusBarInfo, setStatusBarInfo] = useState(defaultState) - const editorPreferences = useSelector((state: ApplicationState) => state.editorConfig.preferences, equal) - const ligaturesEnabled = useSelector((state: ApplicationState) => state.editorConfig.ligatures, equal) + const editorPreferences = useApplicationState((state) => state.editorConfig.preferences) + const ligaturesEnabled = useApplicationState((state) => state.editorConfig.ligatures) const lastScrollPosition = useRef() const [editorScroll, setEditorScroll] = useState() diff --git a/src/components/editor-page/editor-pane/tool-bar/editor-preferences/editor-preference-boolean-property.tsx b/src/components/editor-page/editor-pane/tool-bar/editor-preferences/editor-preference-boolean-property.tsx index ee3809857..a8edc083c 100644 --- a/src/components/editor-page/editor-pane/tool-bar/editor-preferences/editor-preference-boolean-property.tsx +++ b/src/components/editor-page/editor-pane/tool-bar/editor-preferences/editor-preference-boolean-property.tsx @@ -5,24 +5,19 @@ */ import { EditorConfiguration } from 'codemirror' -import equal from 'fast-deep-equal' import React, { ChangeEvent, useCallback } from 'react' import { useTranslation } from 'react-i18next' -import { useSelector } from 'react-redux' -import { ApplicationState } from '../../../../../redux' import { mergeEditorPreferences } from '../../../../../redux/editor/methods' import { EditorPreferenceInput, EditorPreferenceInputType } from './editor-preference-input' import { EditorPreferenceProperty } from './editor-preference-property' +import { useApplicationState } from '../../../../../hooks/common/use-application-state' export interface EditorPreferenceBooleanProps { property: EditorPreferenceProperty } export const EditorPreferenceBooleanProperty: React.FC = ({ property }) => { - const preference = useSelector( - (state: ApplicationState) => state.editorConfig.preferences[property]?.toString() || '', - equal - ) + const preference = useApplicationState((state) => state.editorConfig.preferences[property]?.toString() ?? '') const { t } = useTranslation() const selectItem = useCallback( diff --git a/src/components/editor-page/editor-pane/tool-bar/editor-preferences/editor-preference-ligatures-select.tsx b/src/components/editor-page/editor-pane/tool-bar/editor-preferences/editor-preference-ligatures-select.tsx index d1afd7101..1825e0993 100644 --- a/src/components/editor-page/editor-pane/tool-bar/editor-preferences/editor-preference-ligatures-select.tsx +++ b/src/components/editor-page/editor-pane/tool-bar/editor-preferences/editor-preference-ligatures-select.tsx @@ -5,13 +5,12 @@ */ import React, { ChangeEvent, useCallback } from 'react' import { useTranslation } from 'react-i18next' -import { useSelector } from 'react-redux' -import { ApplicationState } from '../../../../../redux' import { setEditorLigatures } from '../../../../../redux/editor/methods' import { EditorPreferenceInput, EditorPreferenceInputType } from './editor-preference-input' +import { useApplicationState } from '../../../../../hooks/common/use-application-state' export const EditorPreferenceLigaturesSelect: React.FC = () => { - const ligaturesEnabled = useSelector((state: ApplicationState) => Boolean(state.editorConfig.ligatures).toString()) + const ligaturesEnabled = useApplicationState((state) => Boolean(state.editorConfig.ligatures).toString()) const saveLigatures = useCallback((event: ChangeEvent) => { const ligaturesActivated: boolean = event.target.value === 'true' setEditorLigatures(ligaturesActivated) diff --git a/src/components/editor-page/editor-pane/tool-bar/editor-preferences/editor-preference-number-property.tsx b/src/components/editor-page/editor-pane/tool-bar/editor-preferences/editor-preference-number-property.tsx index 71e831e01..49b612d6d 100644 --- a/src/components/editor-page/editor-pane/tool-bar/editor-preferences/editor-preference-number-property.tsx +++ b/src/components/editor-page/editor-pane/tool-bar/editor-preferences/editor-preference-number-property.tsx @@ -5,23 +5,18 @@ */ import { EditorConfiguration } from 'codemirror' -import equal from 'fast-deep-equal' import React, { ChangeEvent, useCallback } from 'react' -import { useSelector } from 'react-redux' -import { ApplicationState } from '../../../../../redux' import { mergeEditorPreferences } from '../../../../../redux/editor/methods' import { EditorPreferenceInput, EditorPreferenceInputType } from './editor-preference-input' import { EditorPreferenceProperty } from './editor-preference-property' +import { useApplicationState } from '../../../../../hooks/common/use-application-state' export interface EditorPreferenceNumberProps { property: EditorPreferenceProperty } export const EditorPreferenceNumberProperty: React.FC = ({ property }) => { - const preference = useSelector( - (state: ApplicationState) => state.editorConfig.preferences[property]?.toString() || '', - equal - ) + const preference = useApplicationState((state) => state.editorConfig.preferences[property]?.toString() ?? '') const selectItem = useCallback( (event: ChangeEvent) => { diff --git a/src/components/editor-page/editor-pane/tool-bar/editor-preferences/editor-preference-select-property.tsx b/src/components/editor-page/editor-pane/tool-bar/editor-preferences/editor-preference-select-property.tsx index 089bbd17e..58e3c23a5 100644 --- a/src/components/editor-page/editor-pane/tool-bar/editor-preferences/editor-preference-select-property.tsx +++ b/src/components/editor-page/editor-pane/tool-bar/editor-preferences/editor-preference-select-property.tsx @@ -5,14 +5,12 @@ */ import { EditorConfiguration } from 'codemirror' -import equal from 'fast-deep-equal' import React, { ChangeEvent, useCallback } from 'react' import { useTranslation } from 'react-i18next' -import { useSelector } from 'react-redux' -import { ApplicationState } from '../../../../../redux' import { mergeEditorPreferences } from '../../../../../redux/editor/methods' import { EditorPreferenceInput, EditorPreferenceInputType } from './editor-preference-input' import { EditorPreferenceProperty } from './editor-preference-property' +import { useApplicationState } from '../../../../../hooks/common/use-application-state' export interface EditorPreferenceSelectPropertyProps { property: EditorPreferenceProperty @@ -23,10 +21,7 @@ export const EditorPreferenceSelectProperty: React.FC { - const preference = useSelector( - (state: ApplicationState) => state.editorConfig.preferences[property]?.toString() || '', - equal - ) + const preference = useApplicationState((state) => state.editorConfig.preferences[property]?.toString() ?? '') const { t } = useTranslation() diff --git a/src/components/editor-page/editor-pane/tool-bar/editor-preferences/editor-preference-smart-paste-select.tsx b/src/components/editor-page/editor-pane/tool-bar/editor-preferences/editor-preference-smart-paste-select.tsx index 450156189..06cf387d5 100644 --- a/src/components/editor-page/editor-pane/tool-bar/editor-preferences/editor-preference-smart-paste-select.tsx +++ b/src/components/editor-page/editor-pane/tool-bar/editor-preferences/editor-preference-smart-paste-select.tsx @@ -5,13 +5,12 @@ */ import React, { ChangeEvent, useCallback } from 'react' import { useTranslation } from 'react-i18next' -import { useSelector } from 'react-redux' -import { ApplicationState } from '../../../../../redux' +import { useApplicationState } from '../../../../../hooks/common/use-application-state' import { setEditorSmartPaste } from '../../../../../redux/editor/methods' import { EditorPreferenceInput, EditorPreferenceInputType } from './editor-preference-input' export const EditorPreferenceSmartPasteSelect: React.FC = () => { - const smartPasteEnabled = useSelector((state: ApplicationState) => Boolean(state.editorConfig.smartPaste).toString()) + const smartPasteEnabled = useApplicationState((state) => Boolean(state.editorConfig.smartPaste).toString()) const saveSmartPaste = useCallback((event: ChangeEvent) => { const smartPasteActivated: boolean = event.target.value === 'true' setEditorSmartPaste(smartPasteActivated) diff --git a/src/components/editor-page/editor-pane/tool-bar/editor-preferences/editor-preferences.tsx b/src/components/editor-page/editor-pane/tool-bar/editor-preferences/editor-preferences.tsx index ca98eedbd..309d90003 100644 --- a/src/components/editor-page/editor-pane/tool-bar/editor-preferences/editor-preferences.tsx +++ b/src/components/editor-page/editor-pane/tool-bar/editor-preferences/editor-preferences.tsx @@ -4,12 +4,9 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import equal from 'fast-deep-equal' import React, { Fragment, useState } from 'react' import { Button, Form, ListGroup } from 'react-bootstrap' import { useTranslation } from 'react-i18next' -import { useSelector } from 'react-redux' -import { ApplicationState } from '../../../../../redux' import { ForkAwesomeIcon } from '../../../../common/fork-awesome/fork-awesome-icon' import { CommonModal } from '../../../../common/modals/common-modal' import { ShowIf } from '../../../../common/show-if/show-if' @@ -20,14 +17,12 @@ import { EditorPreferenceNumberProperty } from './editor-preference-number-prope import { EditorPreferenceProperty } from './editor-preference-property' import { EditorPreferenceSelectProperty } from './editor-preference-select-property' import { EditorPreferenceSmartPasteSelect } from './editor-preference-smart-paste-select' +import { useApplicationState } from '../../../../../hooks/common/use-application-state' export const EditorPreferences: React.FC = () => { const { t } = useTranslation() const [showModal, setShowModal] = useState(false) - const indentWithTabs = useSelector( - (state: ApplicationState) => state.editorConfig.preferences.indentWithTabs ?? false, - equal - ) + const indentWithTabs = useApplicationState((state) => state.editorConfig.preferences.indentWithTabs ?? false) return ( diff --git a/src/components/editor-page/hooks/useUpdateLocalHistoryEntry.ts b/src/components/editor-page/hooks/useUpdateLocalHistoryEntry.ts index af4422e90..a545a06de 100644 --- a/src/components/editor-page/hooks/useUpdateLocalHistoryEntry.ts +++ b/src/components/editor-page/hooks/useUpdateLocalHistoryEntry.ts @@ -6,18 +6,18 @@ import equal from 'fast-deep-equal' import { useEffect, useRef } from 'react' -import { useSelector } from 'react-redux' -import { ApplicationState, store } from '../../../redux' +import { store } from '../../../redux' import { useParams } from 'react-router-dom' import { EditorPagePathParams } from '../editor-page' import { HistoryEntry, HistoryEntryOrigin } from '../../../redux/history/types' import { updateLocalHistoryEntry } from '../../../redux/history/methods' +import { useApplicationState } from '../../../hooks/common/use-application-state' export const useUpdateLocalHistoryEntry = (updateReady: boolean): void => { const { id } = useParams() - const userExists = useSelector((state: ApplicationState) => !!state.user) - const currentNoteTitle = useSelector((state: ApplicationState) => state.noteDetails.noteTitle) - const currentNoteTags = useSelector((state: ApplicationState) => state.noteDetails.frontmatter.tags) + const userExists = useApplicationState((state) => !!state.user) + const currentNoteTitle = useApplicationState((state) => state.noteDetails.noteTitle) + const currentNoteTags = useApplicationState((state) => state.noteDetails.frontmatter.tags) const lastNoteTitle = useRef('') const lastNoteTags = useRef([]) diff --git a/src/components/editor-page/renderer-pane/render-iframe.tsx b/src/components/editor-page/renderer-pane/render-iframe.tsx index de0313089..ab7a56b9b 100644 --- a/src/components/editor-page/renderer-pane/render-iframe.tsx +++ b/src/components/editor-page/renderer-pane/render-iframe.tsx @@ -5,9 +5,8 @@ */ import equal from 'fast-deep-equal' import React, { Fragment, useCallback, useEffect, useRef, useState } from 'react' -import { useSelector } from 'react-redux' +import { useApplicationState } from '../../../hooks/common/use-application-state' import { useIsDarkModeActivated } from '../../../hooks/common/use-is-dark-mode-activated' -import { ApplicationState } from '../../../redux' import { isTestMode } from '../../../utils/test-modes' import { RendererProps } from '../../render-page/markdown-document' import { ImageDetails, RendererType } from '../../render-page/rendering-message' @@ -42,7 +41,7 @@ export const RenderIframe: React.FC = ({ const [lightboxDetails, setLightboxDetails] = useState(undefined) const frameReference = useRef(null) - const rendererOrigin = useSelector((state: ApplicationState) => state.config.iframeCommunication.rendererOrigin) + const rendererOrigin = useApplicationState((state) => state.config.iframeCommunication.rendererOrigin) const renderPageUrl = `${rendererOrigin}render` const resetRendererReady = useCallback(() => setRendererReady(false), []) const iframeCommunicator = useContextOrStandaloneIframeCommunicator() diff --git a/src/components/editor-page/renderer-pane/yaml-array-deprecation-alert.tsx b/src/components/editor-page/renderer-pane/yaml-array-deprecation-alert.tsx index eda66a186..a0bc08697 100644 --- a/src/components/editor-page/renderer-pane/yaml-array-deprecation-alert.tsx +++ b/src/components/editor-page/renderer-pane/yaml-array-deprecation-alert.tsx @@ -7,17 +7,14 @@ import React from 'react' import { Alert } from 'react-bootstrap' import { Trans, useTranslation } from 'react-i18next' -import { useSelector } from 'react-redux' import links from '../../../links.json' -import { ApplicationState } from '../../../redux' import { TranslatedExternalLink } from '../../common/links/translated-external-link' import { ShowIf } from '../../common/show-if/show-if' +import { useApplicationState } from '../../../hooks/common/use-application-state' export const YamlArrayDeprecationAlert: React.FC = () => { useTranslation() - const yamlDeprecatedTags = useSelector( - (state: ApplicationState) => state.noteDetails.frontmatter.deprecatedTagsSyntax - ) + const yamlDeprecatedTags = useApplicationState((state) => state.noteDetails.frontmatter.deprecatedTagsSyntax) return ( diff --git a/src/components/editor-page/sidebar/pin-note-sidebar-entry.tsx b/src/components/editor-page/sidebar/pin-note-sidebar-entry.tsx index d49082258..bd2f0f0ef 100644 --- a/src/components/editor-page/sidebar/pin-note-sidebar-entry.tsx +++ b/src/components/editor-page/sidebar/pin-note-sidebar-entry.tsx @@ -10,15 +10,14 @@ import { SidebarButton } from './sidebar-button' import { SpecificSidebarEntryProps } from './types' import { useParams } from 'react-router-dom' import { EditorPagePathParams } from '../editor-page' -import { useSelector } from 'react-redux' -import { ApplicationState } from '../../../redux' import { toggleHistoryEntryPinning } from '../../../redux/history/methods' import { showErrorNotification } from '../../../redux/ui-notifications/methods' +import { useApplicationState } from '../../../hooks/common/use-application-state' export const PinNoteSidebarEntry: React.FC = ({ className, hide }) => { const { t } = useTranslation() const { id } = useParams() - const history = useSelector((state: ApplicationState) => state.history) + const history = useApplicationState((state) => state.history) const isPinned = useMemo(() => { const entry = history.find((entry) => entry.identifier === id) diff --git a/src/components/history-page/entry-menu/entry-menu.tsx b/src/components/history-page/entry-menu/entry-menu.tsx index 86b87eb43..7f28ff498 100644 --- a/src/components/history-page/entry-menu/entry-menu.tsx +++ b/src/components/history-page/entry-menu/entry-menu.tsx @@ -13,8 +13,7 @@ import { DeleteNoteItem } from './delete-note-item' import './entry-menu.scss' import { RemoveNoteEntryItem } from './remove-note-entry-item' import { HistoryEntryOrigin } from '../../../redux/history/types' -import { useSelector } from 'react-redux' -import { ApplicationState } from '../../../redux' +import { useApplicationState } from '../../../hooks/common/use-application-state' export interface EntryMenuProps { id: string @@ -29,7 +28,7 @@ export interface EntryMenuProps { export const EntryMenu: React.FC = ({ id, title, origin, isDark, onRemove, onDelete, className }) => { useTranslation() - const userExists = useSelector((state: ApplicationState) => !!state.user) + const userExists = useApplicationState((state) => !!state.user) return ( diff --git a/src/components/history-page/history-page.tsx b/src/components/history-page/history-page.tsx index b1088e0eb..41a680bde 100644 --- a/src/components/history-page/history-page.tsx +++ b/src/components/history-page/history-page.tsx @@ -7,19 +7,18 @@ import React, { Fragment, useEffect, useMemo, useState } from 'react' import { Row } from 'react-bootstrap' import { Trans, useTranslation } from 'react-i18next' -import { useSelector } from 'react-redux' -import { ApplicationState } from '../../redux' import { HistoryContent } from './history-content/history-content' import { HistoryToolbar, HistoryToolbarState, initToolbarState } from './history-toolbar/history-toolbar' import { sortAndFilterEntries } from './utils' import { refreshHistoryState } from '../../redux/history/methods' import { HistoryEntry } from '../../redux/history/types' import { showErrorNotification } from '../../redux/ui-notifications/methods' +import { useApplicationState } from '../../hooks/common/use-application-state' export const HistoryPage: React.FC = () => { const { t } = useTranslation() - const allEntries = useSelector((state: ApplicationState) => state.history) + const allEntries = useApplicationState((state) => state.history) const [toolbarState, setToolbarState] = useState(initToolbarState) const entriesToShow = useMemo( diff --git a/src/components/history-page/history-toolbar/history-toolbar.tsx b/src/components/history-page/history-toolbar/history-toolbar.tsx index 4ed7e88ff..3630e6761 100644 --- a/src/components/history-page/history-toolbar/history-toolbar.tsx +++ b/src/components/history-page/history-toolbar/history-toolbar.tsx @@ -9,9 +9,7 @@ import React, { ChangeEvent, useCallback, useEffect, useMemo, useRef, useState } import { Button, Form, FormControl, InputGroup, ToggleButton, ToggleButtonGroup } from 'react-bootstrap' import { Typeahead } from 'react-bootstrap-typeahead' import { Trans, useTranslation } from 'react-i18next' -import { useSelector } from 'react-redux' import { useQueryState } from 'react-router-use-location-state' -import { ApplicationState } from '../../../redux' import { ForkAwesomeIcon } from '../../common/fork-awesome/fork-awesome-icon' import { ShowIf } from '../../common/show-if/show-if' import { SortButton, SortModeEnum } from '../sort-button/sort-button' @@ -22,6 +20,7 @@ import './typeahead-hacks.scss' import { HistoryEntryOrigin } from '../../../redux/history/types' import { importHistoryEntries, refreshHistoryState, setHistoryEntries } from '../../../redux/history/methods' import { showErrorNotification } from '../../../redux/ui-notifications/methods' +import { useApplicationState } from '../../../hooks/common/use-application-state' export type HistoryToolbarChange = (newState: HistoryToolbarState) => void @@ -61,8 +60,8 @@ export const initToolbarState: HistoryToolbarState = { export const HistoryToolbar: React.FC = ({ onSettingsChange }) => { const { t } = useTranslation() - const historyEntries = useSelector((state: ApplicationState) => state.history) - const userExists = useSelector((state: ApplicationState) => !!state.user) + const historyEntries = useApplicationState((state) => state.history) + const userExists = useApplicationState((state) => !!state.user) const tags = useMemo(() => { const allTags = historyEntries.map((entry) => entry.tags).flat() diff --git a/src/components/history-page/history-toolbar/import-history-button.tsx b/src/components/history-page/history-toolbar/import-history-button.tsx index a467dd5cb..e561d34ae 100644 --- a/src/components/history-page/history-toolbar/import-history-button.tsx +++ b/src/components/history-page/history-toolbar/import-history-button.tsx @@ -16,14 +16,13 @@ import { mergeHistoryEntries, refreshHistoryState } from '../../../redux/history/methods' -import { ApplicationState } from '../../../redux' -import { useSelector } from 'react-redux' import { showErrorNotification } from '../../../redux/ui-notifications/methods' +import { useApplicationState } from '../../../hooks/common/use-application-state' export const ImportHistoryButton: React.FC = () => { const { t } = useTranslation() - const userExists = useSelector((state: ApplicationState) => !!state.user) - const historyState = useSelector((state: ApplicationState) => state.history) + const userExists = useApplicationState((state) => !!state.user) + const historyState = useApplicationState((state) => state.history) const uploadInput = useRef(null) const [show, setShow] = useState(false) const [fileName, setFilename] = useState('') diff --git a/src/components/intro-page/cover-buttons/cover-buttons.tsx b/src/components/intro-page/cover-buttons/cover-buttons.tsx index 4dda9fe80..f3e50137f 100644 --- a/src/components/intro-page/cover-buttons/cover-buttons.tsx +++ b/src/components/intro-page/cover-buttons/cover-buttons.tsx @@ -7,17 +7,16 @@ import React from 'react' import { Button } from 'react-bootstrap' import { Trans, useTranslation } from 'react-i18next' -import { useSelector } from 'react-redux' import { Link } from 'react-router-dom' -import { ApplicationState } from '../../../redux' +import { useApplicationState } from '../../../hooks/common/use-application-state' import { ShowIf } from '../../common/show-if/show-if' import { SignInButton } from '../../landing-layout/navigation/sign-in-button' import './cover-buttons.scss' export const CoverButtons: React.FC = () => { useTranslation() - const userExists = useSelector((state: ApplicationState) => !!state.user) - const anyAuthProviderActivated = useSelector((state: ApplicationState) => + const userExists = useApplicationState((state) => !!state.user) + const anyAuthProviderActivated = useApplicationState((state) => Object.values(state.config.authProviders).includes(true) ) diff --git a/src/components/landing-layout/footer/powered-by-links.tsx b/src/components/landing-layout/footer/powered-by-links.tsx index 369ad318f..653c8d09d 100644 --- a/src/components/landing-layout/footer/powered-by-links.tsx +++ b/src/components/landing-layout/footer/powered-by-links.tsx @@ -6,21 +6,18 @@ import React, { Fragment } from 'react' import { Trans, useTranslation } from 'react-i18next' -import { useSelector } from 'react-redux' import links from '../../../links.json' -import { ApplicationState } from '../../../redux' import { ExternalLink } from '../../common/links/external-link' import { TranslatedExternalLink } from '../../common/links/translated-external-link' import { TranslatedInternalLink } from '../../common/links/translated-internal-link' import { VersionInfoLink } from './version-info/version-info-link' -import equal from 'fast-deep-equal' +import { useApplicationState } from '../../../hooks/common/use-application-state' export const PoweredByLinks: React.FC = () => { useTranslation() - const specialUrls: [string, string][] = useSelector( - (state: ApplicationState) => Object.entries(state.config.specialUrls) as [string, string][], - equal + const specialUrls: [string, string][] = useApplicationState((state) => + Object.entries(state.config.specialUrls).map(([i18nkey, url]) => [i18nkey, String(url)]) ) return ( diff --git a/src/components/landing-layout/footer/version-info/version-info-modal.tsx b/src/components/landing-layout/footer/version-info/version-info-modal.tsx index 80dd0635c..11a14419b 100644 --- a/src/components/landing-layout/footer/version-info/version-info-modal.tsx +++ b/src/components/landing-layout/footer/version-info/version-info-modal.tsx @@ -10,13 +10,11 @@ import { Modal, Row } from 'react-bootstrap' import { VersionInfoModalColumn } from './version-info-modal-column' import frontendVersion from '../../../../version.json' import links from '../../../../links.json' -import { useSelector } from 'react-redux' -import { ApplicationState } from '../../../../redux' -import equal from 'fast-deep-equal' import { BackendVersion } from '../../../../api/config/types' +import { useApplicationState } from '../../../../hooks/common/use-application-state' export const VersionInfoModal: React.FC = ({ onHide, show }) => { - const serverVersion: BackendVersion = useSelector((state: ApplicationState) => state.config.version, equal) + const serverVersion: BackendVersion = useApplicationState((state) => state.config.version) const backendVersion = useMemo(() => { const version = `${serverVersion.major}.${serverVersion.minor}.${serverVersion.patch}` diff --git a/src/components/landing-layout/navigation/header-bar/header-bar.tsx b/src/components/landing-layout/navigation/header-bar/header-bar.tsx index 573dd0c60..ea23d7bfe 100644 --- a/src/components/landing-layout/navigation/header-bar/header-bar.tsx +++ b/src/components/landing-layout/navigation/header-bar/header-bar.tsx @@ -7,8 +7,7 @@ import React, { Fragment } from 'react' import { Navbar } from 'react-bootstrap' import { Trans, useTranslation } from 'react-i18next' -import { useSelector } from 'react-redux' -import { ApplicationState } from '../../../../redux' +import { useApplicationState } from '../../../../hooks/common/use-application-state' import { HeaderNavLink } from '../header-nav-link' import { NewGuestNoteButton } from '../new-guest-note-button' import { NewUserNoteButton } from '../new-user-note-button' @@ -18,7 +17,7 @@ import './header-bar.scss' const HeaderBar: React.FC = () => { useTranslation() - const userExists = useSelector((state: ApplicationState) => !!state.user) + const userExists = useApplicationState((state) => !!state.user) return ( diff --git a/src/components/landing-layout/navigation/sign-in-button.tsx b/src/components/landing-layout/navigation/sign-in-button.tsx index ce0f260cc..206e371ae 100644 --- a/src/components/landing-layout/navigation/sign-in-button.tsx +++ b/src/components/landing-layout/navigation/sign-in-button.tsx @@ -4,23 +4,21 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import equal from 'fast-deep-equal' import React, { useMemo } from 'react' import { Button } from 'react-bootstrap' import { ButtonProps } from 'react-bootstrap/Button' import { Trans, useTranslation } from 'react-i18next' -import { useSelector } from 'react-redux' import { LinkContainer } from 'react-router-bootstrap' -import { ApplicationState } from '../../../redux' import { ShowIf } from '../../common/show-if/show-if' import { getApiUrl } from '../../../api/utils' import { INTERACTIVE_LOGIN_METHODS } from '../../../api/auth' +import { useApplicationState } from '../../../hooks/common/use-application-state' export type SignInButtonProps = Omit export const SignInButton: React.FC = ({ variant, ...props }) => { const { t } = useTranslation() - const authProviders = useSelector((state: ApplicationState) => state.config.authProviders, equal) + const authProviders = useApplicationState((state) => state.config.authProviders) const authEnabled = useMemo(() => Object.values(authProviders).includes(true), [authProviders]) const loginLink = useMemo(() => { diff --git a/src/components/landing-layout/navigation/user-dropdown.tsx b/src/components/landing-layout/navigation/user-dropdown.tsx index 6d193be96..866b8c0b0 100644 --- a/src/components/landing-layout/navigation/user-dropdown.tsx +++ b/src/components/landing-layout/navigation/user-dropdown.tsx @@ -4,20 +4,18 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import equal from 'fast-deep-equal' import React from 'react' import { Dropdown } from 'react-bootstrap' import { Trans, useTranslation } from 'react-i18next' -import { useSelector } from 'react-redux' import { LinkContainer } from 'react-router-bootstrap' -import { ApplicationState } from '../../../redux' import { clearUser } from '../../../redux/user/methods' import { ForkAwesomeIcon } from '../../common/fork-awesome/fork-awesome-icon' import { UserAvatar } from '../../common/user-avatar/user-avatar' +import { useApplicationState } from '../../../hooks/common/use-application-state' export const UserDropdown: React.FC = () => { useTranslation() - const user = useSelector((state: ApplicationState) => state.user, equal) + const user = useApplicationState((state) => state.user) if (!user) { return null diff --git a/src/components/login-page/auth/via-internal.tsx b/src/components/login-page/auth/via-internal.tsx index 2d1d8a075..4e5f50832 100644 --- a/src/components/login-page/auth/via-internal.tsx +++ b/src/components/login-page/auth/via-internal.tsx @@ -7,19 +7,18 @@ import React, { FormEvent, useCallback, useState } from 'react' import { Alert, Button, Card, Form } from 'react-bootstrap' import { Trans, useTranslation } from 'react-i18next' -import { useSelector } from 'react-redux' import { Link } from 'react-router-dom' import { doInternalLogin } from '../../../api/auth' -import { ApplicationState } from '../../../redux' import { ShowIf } from '../../common/show-if/show-if' import { fetchAndSetUser } from './utils' +import { useApplicationState } from '../../../hooks/common/use-application-state' export const ViaInternal: React.FC = () => { const { t } = useTranslation() const [username, setUsername] = useState('') const [password, setPassword] = useState('') const [error, setError] = useState(false) - const allowRegister = useSelector((state: ApplicationState) => state.config.allowRegister) + const allowRegister = useApplicationState((state) => state.config.allowRegister) const onLoginSubmit = useCallback( (event: FormEvent) => { diff --git a/src/components/login-page/auth/via-ldap.tsx b/src/components/login-page/auth/via-ldap.tsx index 72853d739..f9f28d520 100644 --- a/src/components/login-page/auth/via-ldap.tsx +++ b/src/components/login-page/auth/via-ldap.tsx @@ -8,14 +8,13 @@ import React, { FormEvent, useCallback, useState } from 'react' import { Alert, Button, Card, Form } from 'react-bootstrap' import { Trans, useTranslation } from 'react-i18next' -import { useSelector } from 'react-redux' import { doLdapLogin } from '../../../api/auth' -import { ApplicationState } from '../../../redux' import { fetchAndSetUser } from './utils' +import { useApplicationState } from '../../../hooks/common/use-application-state' export const ViaLdap: React.FC = () => { const { t } = useTranslation() - const ldapCustomName = useSelector((state: ApplicationState) => state.config.customAuthNames.ldap) + const ldapCustomName = useApplicationState((state) => state.config.customAuthNames.ldap) const [username, setUsername] = useState('') const [password, setPassword] = useState('') diff --git a/src/components/login-page/auth/via-one-click.tsx b/src/components/login-page/auth/via-one-click.tsx index 58c76c350..ef5d1856d 100644 --- a/src/components/login-page/auth/via-one-click.tsx +++ b/src/components/login-page/auth/via-one-click.tsx @@ -5,8 +5,7 @@ */ import React from 'react' -import { useSelector } from 'react-redux' -import { ApplicationState } from '../../../redux' +import { useApplicationState } from '../../../hooks/common/use-application-state' import { IconName } from '../../common/fork-awesome/types' import { SocialLinkButton } from './social-link-button/social-link-button' @@ -108,7 +107,7 @@ export interface ViaOneClickProps { } export const ViaOneClick: React.FC = ({ oneClickType, optionalName }) => { - const backendUrl = useSelector((state: ApplicationState) => state.apiUrl.apiUrl) + const backendUrl = useApplicationState((state) => state.apiUrl.apiUrl) const { name, icon, className, url } = getMetadata(backendUrl, oneClickType) const text = optionalName || name diff --git a/src/components/login-page/login-page.tsx b/src/components/login-page/login-page.tsx index 045b51a2c..d57b0aea6 100644 --- a/src/components/login-page/login-page.tsx +++ b/src/components/login-page/login-page.tsx @@ -4,25 +4,23 @@ SPDX-License-Identifier: AGPL-3.0-only */ -import equal from 'fast-deep-equal' import React, { Fragment } from 'react' import { Card, Col, Row } from 'react-bootstrap' import { Trans, useTranslation } from 'react-i18next' -import { useSelector } from 'react-redux' import { Redirect } from 'react-router' -import { ApplicationState } from '../../redux' import { ShowIf } from '../common/show-if/show-if' import { ViaInternal } from './auth/via-internal' import { ViaLdap } from './auth/via-ldap' import { OneClickType, ViaOneClick } from './auth/via-one-click' import { ViaOpenId } from './auth/via-openid' +import { useApplicationState } from '../../hooks/common/use-application-state' export const LoginPage: React.FC = () => { useTranslation() - const authProviders = useSelector((state: ApplicationState) => state.config.authProviders, equal) - const customSamlAuthName = useSelector((state: ApplicationState) => state.config.customAuthNames.saml) - const customOauthAuthName = useSelector((state: ApplicationState) => state.config.customAuthNames.oauth2) - const userLoggedIn = useSelector((state: ApplicationState) => !!state.user) + const authProviders = useApplicationState((state) => state.config.authProviders) + const customSamlAuthName = useApplicationState((state) => state.config.customAuthNames.saml) + const customOauthAuthName = useApplicationState((state) => state.config.customAuthNames.oauth2) + const userLoggedIn = useApplicationState((state) => !!state.user) const oneClickProviders = [ authProviders.dropbox, diff --git a/src/components/markdown-renderer/document-length-limit-reached-alert.tsx b/src/components/markdown-renderer/document-length-limit-reached-alert.tsx index f9f8d7122..7c15c91a3 100644 --- a/src/components/markdown-renderer/document-length-limit-reached-alert.tsx +++ b/src/components/markdown-renderer/document-length-limit-reached-alert.tsx @@ -7,15 +7,14 @@ import React from 'react' import { Alert } from 'react-bootstrap' import { Trans, useTranslation } from 'react-i18next' -import { useSelector } from 'react-redux' -import { ApplicationState } from '../../redux' +import { useApplicationState } from '../../hooks/common/use-application-state' import { ShowIf } from '../common/show-if/show-if' import { SimpleAlertProps } from '../common/simple-alert/simple-alert-props' export const DocumentLengthLimitReachedAlert: React.FC = ({ show }) => { useTranslation() - const maxLength = useSelector((state: ApplicationState) => state.config.maxDocumentLength) + const maxLength = useApplicationState((state) => state.config.maxDocumentLength) return ( diff --git a/src/components/markdown-renderer/hooks/use-trimmed-content.ts b/src/components/markdown-renderer/hooks/use-trimmed-content.ts index 0504cd451..595077e62 100644 --- a/src/components/markdown-renderer/hooks/use-trimmed-content.ts +++ b/src/components/markdown-renderer/hooks/use-trimmed-content.ts @@ -4,12 +4,11 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { useSelector } from 'react-redux' -import { ApplicationState } from '../../../redux' import { useMemo } from 'react' +import { useApplicationState } from '../../../hooks/common/use-application-state' export const useTrimmedContent = (content: string): [trimmedContent: string, contentExceedsLimit: boolean] => { - const maxLength = useSelector((state: ApplicationState) => state.config.maxDocumentLength) + const maxLength = useApplicationState((state) => state.config.maxDocumentLength) const contentExceedsLimit = content.length > maxLength const trimmedContent = useMemo( diff --git a/src/components/markdown-renderer/replace-components/image/proxy-image-frame.tsx b/src/components/markdown-renderer/replace-components/image/proxy-image-frame.tsx index b40ad8be5..e30ad40ef 100644 --- a/src/components/markdown-renderer/replace-components/image/proxy-image-frame.tsx +++ b/src/components/markdown-renderer/replace-components/image/proxy-image-frame.tsx @@ -5,13 +5,12 @@ */ import React, { useEffect, useState } from 'react' -import { useSelector } from 'react-redux' import { getProxiedUrl } from '../../../../api/media' -import { ApplicationState } from '../../../../redux' +import { useApplicationState } from '../../../../hooks/common/use-application-state' export const ProxyImageFrame: React.FC> = ({ src, title, alt, ...props }) => { const [imageUrl, setImageUrl] = useState('') - const imageProxyEnabled = useSelector((state: ApplicationState) => state.config.useImageProxy) + const imageProxyEnabled = useApplicationState((state) => state.config.useImageProxy) useEffect(() => { if (!imageProxyEnabled || !src) { diff --git a/src/components/notifications/ui-notifications.tsx b/src/components/notifications/ui-notifications.tsx index ab482d84a..a01dbe4b0 100644 --- a/src/components/notifications/ui-notifications.tsx +++ b/src/components/notifications/ui-notifications.tsx @@ -7,12 +7,10 @@ import React from 'react' import { UiNotificationToast } from './ui-notification-toast' import './notifications.scss' -import { useSelector } from 'react-redux' -import { ApplicationState } from '../../redux' -import equal from 'fast-deep-equal' +import { useApplicationState } from '../../hooks/common/use-application-state' export const UiNotifications: React.FC = () => { - const notifications = useSelector((state: ApplicationState) => state.uiNotifications, equal) + const notifications = useApplicationState((state) => state.uiNotifications) return (
diff --git a/src/components/profile-page/profile-page.tsx b/src/components/profile-page/profile-page.tsx index 471d2ea57..96da2c85d 100644 --- a/src/components/profile-page/profile-page.tsx +++ b/src/components/profile-page/profile-page.tsx @@ -6,9 +6,8 @@ import React, { Fragment } from 'react' import { Col, Row } from 'react-bootstrap' -import { useSelector } from 'react-redux' import { Redirect } from 'react-router' -import { ApplicationState } from '../../redux' +import { useApplicationState } from '../../hooks/common/use-application-state' import { LoginProvider } from '../../redux/user/types' import { ShowIf } from '../common/show-if/show-if' import { ProfileAccessTokens } from './access-tokens/profile-access-tokens' @@ -17,7 +16,7 @@ import { ProfileChangePassword } from './settings/profile-change-password' import { ProfileDisplayName } from './settings/profile-display-name' export const ProfilePage: React.FC = () => { - const userProvider = useSelector((state: ApplicationState) => state.user?.provider) + const userProvider = useApplicationState((state) => state.user?.provider) if (!userProvider) { return diff --git a/src/components/profile-page/settings/profile-display-name.tsx b/src/components/profile-page/settings/profile-display-name.tsx index 992164893..86b99ff54 100644 --- a/src/components/profile-page/settings/profile-display-name.tsx +++ b/src/components/profile-page/settings/profile-display-name.tsx @@ -7,15 +7,14 @@ import React, { ChangeEvent, FormEvent, useEffect, useState } from 'react' import { Alert, Button, Card, Form } from 'react-bootstrap' import { Trans, useTranslation } from 'react-i18next' -import { useSelector } from 'react-redux' import { updateDisplayName } from '../../../api/me' -import { ApplicationState } from '../../../redux' import { fetchAndSetUser } from '../../login-page/auth/utils' +import { useApplicationState } from '../../../hooks/common/use-application-state' export const ProfileDisplayName: React.FC = () => { const regexInvalidDisplayName = /^\s*$/ const { t } = useTranslation() - const userName = useSelector((state: ApplicationState) => state.user?.name) + const userName = useApplicationState((state) => state.user?.name) const [submittable, setSubmittable] = useState(false) const [error, setError] = useState(false) const [displayName, setDisplayName] = useState('') diff --git a/src/components/register-page/register-page.tsx b/src/components/register-page/register-page.tsx index 8f753f3a5..19ea24741 100644 --- a/src/components/register-page/register-page.tsx +++ b/src/components/register-page/register-page.tsx @@ -7,10 +7,9 @@ import React, { FormEvent, Fragment, useCallback, useEffect, useState } from 'react' import { Alert, Button, Card, Col, Form, Row } from 'react-bootstrap' import { Trans, useTranslation } from 'react-i18next' -import { useSelector } from 'react-redux' import { Redirect } from 'react-router' import { doInternalRegister } from '../../api/auth' -import { ApplicationState } from '../../redux' +import { useApplicationState } from '../../hooks/common/use-application-state' import { TranslatedExternalLink } from '../common/links/translated-external-link' import { ShowIf } from '../common/show-if/show-if' import { fetchAndSetUser } from '../login-page/auth/utils' @@ -23,9 +22,9 @@ export enum RegisterError { export const RegisterPage: React.FC = () => { const { t } = useTranslation() - const allowRegister = useSelector((state: ApplicationState) => state.config.allowRegister) - const specialUrls = useSelector((state: ApplicationState) => state.config.specialUrls) - const userExists = useSelector((state: ApplicationState) => !!state.user) + const allowRegister = useApplicationState((state) => state.config.allowRegister) + const specialUrls = useApplicationState((state) => state.config.specialUrls) + const userExists = useApplicationState((state) => !!state.user) const [username, setUsername] = useState('') const [password, setPassword] = useState('') diff --git a/src/components/render-page/markdown-document.tsx b/src/components/render-page/markdown-document.tsx index 7a51b1707..c80e6f2af 100644 --- a/src/components/render-page/markdown-document.tsx +++ b/src/components/render-page/markdown-document.tsx @@ -14,10 +14,9 @@ import { ScrollProps } from '../editor-page/synced-scroll/scroll-props' import { BasicMarkdownRenderer } from '../markdown-renderer/basic-markdown-renderer' import { ImageClickHandler } from '../markdown-renderer/replace-components/image/image-replacer' import './markdown-document.scss' -import { useSelector } from 'react-redux' -import { ApplicationState } from '../../redux' import { WidthBasedTableOfContents } from './width-based-table-of-contents' import { ShowIf } from '../common/show-if/show-if' +import { useApplicationState } from '../../hooks/common/use-application-state' export interface RendererProps extends ScrollProps { onFirstHeadingChange?: (firstHeading: string | undefined) => void @@ -60,7 +59,7 @@ export const MarkdownDocument: React.FC = ({ const [tocAst, setTocAst] = useState() - const useAlternativeBreaks = useSelector((state: ApplicationState) => state.noteDetails.frontmatter.breaks) + const useAlternativeBreaks = useApplicationState((state) => state.noteDetails.frontmatter.breaks) useEffect(() => { if (!onHeightChange) { diff --git a/src/components/render-page/render-page.tsx b/src/components/render-page/render-page.tsx index 66b839521..1147d291c 100644 --- a/src/components/render-page/render-page.tsx +++ b/src/components/render-page/render-page.tsx @@ -4,9 +4,8 @@ * SPDX-License-Identifier: AGPL-3.0-only */ import React, { useCallback, useEffect, useMemo, useState } from 'react' -import { useSelector } from 'react-redux' +import { useApplicationState } from '../../hooks/common/use-application-state' import { useApplyDarkMode } from '../../hooks/common/use-apply-dark-mode' -import { ApplicationState } from '../../redux' import { setDarkMode } from '../../redux/dark-mode/methods' import { setNoteFrontmatter } from '../../redux/note-details/methods' import { NoteFrontmatter } from '../editor-page/note-frontmatter/note-frontmatter' @@ -24,7 +23,7 @@ export const RenderPage: React.FC = () => { const [scrollState, setScrollState] = useState({ firstLineInView: 1, scrolledPercentage: 0 }) const [baseConfiguration, setBaseConfiguration] = useState(undefined) - const editorOrigin = useSelector((state: ApplicationState) => state.config.iframeCommunication.editorOrigin) + const editorOrigin = useApplicationState((state) => state.config.iframeCommunication.editorOrigin) const iframeCommunicator = useMemo(() => { const newCommunicator = new IframeRendererToEditorCommunicator() diff --git a/src/hooks/common/use-application-state.ts b/src/hooks/common/use-application-state.ts new file mode 100644 index 000000000..7601ff640 --- /dev/null +++ b/src/hooks/common/use-application-state.ts @@ -0,0 +1,22 @@ +/* + * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { useSelector } from 'react-redux' +import { ApplicationState } from '../../redux' +import equal from 'fast-deep-equal' + +/** + * Accesses the global application state to retrieve information. + * + * @param selector A selector function that extracts the needed information from the state. + * @param checkForEquality An optional custom equality function. If not provided then {@link equal equal from fast-deep-equal} will be used. + */ +export const useApplicationState = ( + selector: (state: ApplicationState) => TSelected, + checkForEquality?: (a: TSelected, b: TSelected) => boolean +): TSelected => { + return useSelector(selector, checkForEquality ? checkForEquality : equal) +} diff --git a/src/hooks/common/use-document-title-with-note-title.ts b/src/hooks/common/use-document-title-with-note-title.ts index 68597e7a4..5e5652947 100644 --- a/src/hooks/common/use-document-title-with-note-title.ts +++ b/src/hooks/common/use-document-title-with-note-title.ts @@ -5,14 +5,13 @@ */ import { useTranslation } from 'react-i18next' -import { useSelector } from 'react-redux' -import { ApplicationState } from '../../redux' +import { useApplicationState } from './use-application-state' import { useDocumentTitle } from './use-document-title' export const useDocumentTitleWithNoteTitle = (): void => { const { t } = useTranslation() const untitledNote = t('editor.untitledNote') - const noteTitle = useSelector((state: ApplicationState) => state.noteDetails.noteTitle) + const noteTitle = useApplicationState((state) => state.noteDetails.noteTitle) useDocumentTitle(noteTitle === '' ? untitledNote : noteTitle) } diff --git a/src/hooks/common/use-document-title.ts b/src/hooks/common/use-document-title.ts index f4efe6cb9..8f759c843 100644 --- a/src/hooks/common/use-document-title.ts +++ b/src/hooks/common/use-document-title.ts @@ -5,11 +5,10 @@ */ import { useEffect } from 'react' -import { useSelector } from 'react-redux' -import { ApplicationState } from '../../redux' +import { useApplicationState } from './use-application-state' export const useDocumentTitle = (title?: string): void => { - const brandingName = useSelector((state: ApplicationState) => state.config.branding.name) + const brandingName = useApplicationState((state) => state.config.branding.name) useEffect(() => { document.title = `${title ? title + ' - ' : ''}HedgeDoc ${brandingName ? ` @ ${brandingName}` : ''}` diff --git a/src/hooks/common/use-is-dark-mode-activated.ts b/src/hooks/common/use-is-dark-mode-activated.ts index 5511bd709..1a3c233a3 100644 --- a/src/hooks/common/use-is-dark-mode-activated.ts +++ b/src/hooks/common/use-is-dark-mode-activated.ts @@ -4,9 +4,8 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { useSelector } from 'react-redux' -import { ApplicationState } from '../../redux' +import { useApplicationState } from './use-application-state' export const useIsDarkModeActivated = (): boolean => { - return useSelector((state: ApplicationState) => state.darkMode.darkMode) + return useApplicationState((state) => state.darkMode.darkMode) } diff --git a/src/hooks/common/use-note-markdown-content.ts b/src/hooks/common/use-note-markdown-content.ts index d272a108a..2c33f140a 100644 --- a/src/hooks/common/use-note-markdown-content.ts +++ b/src/hooks/common/use-note-markdown-content.ts @@ -4,9 +4,8 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { useSelector } from 'react-redux' -import { ApplicationState } from '../../redux' +import { useApplicationState } from './use-application-state' export const useNoteMarkdownContent = (): string => { - return useSelector((state: ApplicationState) => state.noteDetails.markdownContent) + return useApplicationState((state) => state.noteDetails.markdownContent) }