mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-14 07:04:45 -04:00
Add application state hook (#1308)
* Add application state hook Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de> * Add docs Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
parent
4720f2d36b
commit
829cc2fe48
47 changed files with 149 additions and 195 deletions
|
@ -4,12 +4,10 @@
|
||||||
SPDX-License-Identifier: AGPL-3.0-only
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import equal from 'fast-deep-equal'
|
import React, { useMemo } from 'react'
|
||||||
import React from 'react'
|
|
||||||
import { useSelector } from 'react-redux'
|
|
||||||
import { ApplicationState } from '../../../redux'
|
|
||||||
import { ShowIf } from '../show-if/show-if'
|
import { ShowIf } from '../show-if/show-if'
|
||||||
import './branding.scss'
|
import './branding.scss'
|
||||||
|
import { useApplicationState } from '../../../hooks/common/use-application-state'
|
||||||
|
|
||||||
export interface BrandingProps {
|
export interface BrandingProps {
|
||||||
inline?: boolean
|
inline?: boolean
|
||||||
|
@ -17,24 +15,30 @@ export interface BrandingProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Branding: React.FC<BrandingProps> = ({ inline = false, delimiter = true }) => {
|
export const Branding: React.FC<BrandingProps> = ({ 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
|
const showBranding = !!branding.name || !!branding.logo
|
||||||
|
|
||||||
return (
|
const brandingDom = useMemo(() => {
|
||||||
<ShowIf condition={showBranding}>
|
if (branding.logo) {
|
||||||
<ShowIf condition={delimiter}>
|
return (
|
||||||
<strong className={`mx-1 ${inline ? 'inline-size' : 'regular-size'}`}>@</strong>
|
|
||||||
</ShowIf>
|
|
||||||
{branding.logo ? (
|
|
||||||
<img
|
<img
|
||||||
src={branding.logo}
|
src={branding.logo}
|
||||||
alt={branding.name}
|
alt={branding.name}
|
||||||
title={branding.name}
|
title={branding.name}
|
||||||
className={inline ? 'inline-size' : 'regular-size'}
|
className={inline ? 'inline-size' : 'regular-size'}
|
||||||
/>
|
/>
|
||||||
) : (
|
)
|
||||||
branding.name
|
} else {
|
||||||
)}
|
return branding.name
|
||||||
|
}
|
||||||
|
}, [branding.logo, branding.name, inline])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ShowIf condition={showBranding}>
|
||||||
|
<ShowIf condition={delimiter}>
|
||||||
|
<strong className={`mx-1 ${inline ? 'inline-size' : 'regular-size'}`}>@</strong>
|
||||||
|
</ShowIf>
|
||||||
|
{brandingDom}
|
||||||
</ShowIf>
|
</ShowIf>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,17 +4,15 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import equal from 'fast-deep-equal'
|
|
||||||
import React, { useCallback } from 'react'
|
import React, { useCallback } from 'react'
|
||||||
import { Alert, Button } from 'react-bootstrap'
|
import { Alert, Button } from 'react-bootstrap'
|
||||||
import { useSelector } from 'react-redux'
|
|
||||||
import { ApplicationState } from '../../../redux'
|
|
||||||
import { setBanner } from '../../../redux/banner/methods'
|
import { setBanner } from '../../../redux/banner/methods'
|
||||||
import { ForkAwesomeIcon } from '../fork-awesome/fork-awesome-icon'
|
import { ForkAwesomeIcon } from '../fork-awesome/fork-awesome-icon'
|
||||||
import { BANNER_LOCAL_STORAGE_KEY } from '../../application-loader/initializers/fetch-and-set-banner'
|
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 = () => {
|
export const MotdBanner: React.FC = () => {
|
||||||
const bannerState = useSelector((state: ApplicationState) => state.banner, equal)
|
const bannerState = useApplicationState((state) => state.banner)
|
||||||
|
|
||||||
const dismissBanner = useCallback(() => {
|
const dismissBanner = useCallback(() => {
|
||||||
if (bannerState.lastModified) {
|
if (bannerState.lastModified) {
|
||||||
|
|
|
@ -6,12 +6,10 @@
|
||||||
|
|
||||||
import React, { useCallback } from 'react'
|
import React, { useCallback } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useSelector } from 'react-redux'
|
|
||||||
import { useParams } from 'react-router'
|
import { useParams } from 'react-router'
|
||||||
import { useApplyDarkMode } from '../../hooks/common/use-apply-dark-mode'
|
import { useApplyDarkMode } from '../../hooks/common/use-apply-dark-mode'
|
||||||
import { useDocumentTitleWithNoteTitle } from '../../hooks/common/use-document-title-with-note-title'
|
import { useDocumentTitleWithNoteTitle } from '../../hooks/common/use-document-title-with-note-title'
|
||||||
import { useNoteMarkdownContent } from '../../hooks/common/use-note-markdown-content'
|
import { useNoteMarkdownContent } from '../../hooks/common/use-note-markdown-content'
|
||||||
import { ApplicationState } from '../../redux'
|
|
||||||
import { setNoteFrontmatter, updateNoteTitleByFirstHeading } from '../../redux/note-details/methods'
|
import { setNoteFrontmatter, updateNoteTitleByFirstHeading } from '../../redux/note-details/methods'
|
||||||
import { MotdBanner } from '../common/motd-banner/motd-banner'
|
import { MotdBanner } from '../common/motd-banner/motd-banner'
|
||||||
import { ShowIf } from '../common/show-if/show-if'
|
import { ShowIf } from '../common/show-if/show-if'
|
||||||
|
@ -23,6 +21,7 @@ import { DocumentInfobar } from './document-infobar'
|
||||||
import { ErrorWhileLoadingNoteAlert } from './ErrorWhileLoadingNoteAlert'
|
import { ErrorWhileLoadingNoteAlert } from './ErrorWhileLoadingNoteAlert'
|
||||||
import { LoadingNoteAlert } from './LoadingNoteAlert'
|
import { LoadingNoteAlert } from './LoadingNoteAlert'
|
||||||
import { RendererType } from '../render-page/rendering-message'
|
import { RendererType } from '../render-page/rendering-message'
|
||||||
|
import { useApplicationState } from '../../hooks/common/use-application-state'
|
||||||
|
|
||||||
export const DocumentReadOnlyPage: React.FC = () => {
|
export const DocumentReadOnlyPage: React.FC = () => {
|
||||||
useTranslation()
|
useTranslation()
|
||||||
|
@ -35,7 +34,7 @@ export const DocumentReadOnlyPage: React.FC = () => {
|
||||||
const onFrontmatterChange = useCallback(setNoteFrontmatter, [])
|
const onFrontmatterChange = useCallback(setNoteFrontmatter, [])
|
||||||
const [error, loading] = useLoadNoteFromServer()
|
const [error, loading] = useLoadNoteFromServer()
|
||||||
const markdownContent = useNoteMarkdownContent()
|
const markdownContent = useNoteMarkdownContent()
|
||||||
const noteDetails = useSelector((state: ApplicationState) => state.noteDetails)
|
const noteDetails = useApplicationState((state) => state.noteDetails)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={'d-flex flex-column mvh-100 bg-light'}>
|
<div className={'d-flex flex-column mvh-100 bg-light'}>
|
||||||
|
|
|
@ -5,10 +5,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import equal from 'fast-deep-equal'
|
|
||||||
import { Nav, Navbar } from 'react-bootstrap'
|
import { Nav, Navbar } from 'react-bootstrap'
|
||||||
import { useSelector } from 'react-redux'
|
|
||||||
import { ApplicationState } from '../../../redux'
|
|
||||||
import { ShowIf } from '../../common/show-if/show-if'
|
import { ShowIf } from '../../common/show-if/show-if'
|
||||||
import { SignInButton } from '../../landing-layout/navigation/sign-in-button'
|
import { SignInButton } from '../../landing-layout/navigation/sign-in-button'
|
||||||
import { UserDropdown } from '../../landing-layout/navigation/user-dropdown'
|
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 { SlideModeButton } from './slide-mode-button'
|
||||||
import { ReadOnlyModeButton } from './read-only-mode-button'
|
import { ReadOnlyModeButton } from './read-only-mode-button'
|
||||||
import { NewNoteButton } from './new-note-button'
|
import { NewNoteButton } from './new-note-button'
|
||||||
|
import { useApplicationState } from '../../../hooks/common/use-application-state'
|
||||||
|
|
||||||
export enum AppBarMode {
|
export enum AppBarMode {
|
||||||
BASIC,
|
BASIC,
|
||||||
|
@ -32,8 +30,8 @@ export interface AppBarProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AppBar: React.FC<AppBarProps> = ({ mode }) => {
|
export const AppBar: React.FC<AppBarProps> = ({ mode }) => {
|
||||||
const userExists = useSelector((state: ApplicationState) => !!state.user)
|
const userExists = useApplicationState((state) => !!state.user)
|
||||||
const noteFrontmatter = useSelector((state: ApplicationState) => state.noteDetails.frontmatter, equal)
|
const noteFrontmatter = useApplicationState((state) => state.noteDetails.frontmatter)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Navbar bg={'light'}>
|
<Navbar bg={'light'}>
|
||||||
|
|
|
@ -7,10 +7,9 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { ToggleButton, ToggleButtonGroup } from 'react-bootstrap'
|
import { ToggleButton, ToggleButtonGroup } from 'react-bootstrap'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useSelector } from 'react-redux'
|
|
||||||
import { ApplicationState } from '../../../redux'
|
|
||||||
import { setEditorMode } from '../../../redux/editor/methods'
|
import { setEditorMode } from '../../../redux/editor/methods'
|
||||||
import { ForkAwesomeIcon } from '../../common/fork-awesome/fork-awesome-icon'
|
import { ForkAwesomeIcon } from '../../common/fork-awesome/fork-awesome-icon'
|
||||||
|
import { useApplicationState } from '../../../hooks/common/use-application-state'
|
||||||
|
|
||||||
export enum EditorMode {
|
export enum EditorMode {
|
||||||
PREVIEW = 'view',
|
PREVIEW = 'view',
|
||||||
|
@ -20,7 +19,8 @@ export enum EditorMode {
|
||||||
|
|
||||||
export const EditorViewMode: React.FC = () => {
|
export const EditorViewMode: React.FC = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const editorMode = useSelector((state: ApplicationState) => state.editorConfig.editorMode)
|
const editorMode = useApplicationState((state) => state.editorConfig.editorMode)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ToggleButtonGroup
|
<ToggleButtonGroup
|
||||||
type='radio'
|
type='radio'
|
||||||
|
|
|
@ -7,12 +7,11 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { ToggleButton, ToggleButtonGroup } from 'react-bootstrap'
|
import { ToggleButton, ToggleButtonGroup } from 'react-bootstrap'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useSelector } from 'react-redux'
|
|
||||||
import { ApplicationState } from '../../../../redux'
|
|
||||||
import { setEditorSyncScroll } from '../../../../redux/editor/methods'
|
import { setEditorSyncScroll } from '../../../../redux/editor/methods'
|
||||||
import { ReactComponent as DisabledScrollIcon } from './disabledScroll.svg'
|
import { ReactComponent as DisabledScrollIcon } from './disabledScroll.svg'
|
||||||
import { ReactComponent as EnabledScrollIcon } from './enabledScroll.svg'
|
import { ReactComponent as EnabledScrollIcon } from './enabledScroll.svg'
|
||||||
import './sync-scroll-buttons.scss'
|
import './sync-scroll-buttons.scss'
|
||||||
|
import { useApplicationState } from '../../../../hooks/common/use-application-state'
|
||||||
|
|
||||||
enum SyncScrollState {
|
enum SyncScrollState {
|
||||||
SYNCED,
|
SYNCED,
|
||||||
|
@ -20,7 +19,7 @@ enum SyncScrollState {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SyncScrollButtons: React.FC = () => {
|
export const SyncScrollButtons: React.FC = () => {
|
||||||
const syncScrollEnabled = useSelector((state: ApplicationState) => state.editorConfig.syncScroll)
|
const syncScrollEnabled = useApplicationState((state) => state.editorConfig.syncScroll)
|
||||||
? SyncScrollState.SYNCED
|
? SyncScrollState.SYNCED
|
||||||
: SyncScrollState.UNSYNCED
|
: SyncScrollState.UNSYNCED
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
|
@ -4,19 +4,17 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import equal from 'fast-deep-equal'
|
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Modal } from 'react-bootstrap'
|
import { Modal } from 'react-bootstrap'
|
||||||
import { Trans, useTranslation } from 'react-i18next'
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
import { useSelector } from 'react-redux'
|
|
||||||
import { useParams } from 'react-router-dom'
|
import { useParams } from 'react-router-dom'
|
||||||
import { useFrontendBaseUrl } from '../../../../hooks/common/use-frontend-base-url'
|
import { useFrontendBaseUrl } from '../../../../hooks/common/use-frontend-base-url'
|
||||||
import { ApplicationState } from '../../../../redux'
|
|
||||||
import { CopyableField } from '../../../common/copyable/copyable-field/copyable-field'
|
import { CopyableField } from '../../../common/copyable/copyable-field/copyable-field'
|
||||||
import { CommonModal } from '../../../common/modals/common-modal'
|
import { CommonModal } from '../../../common/modals/common-modal'
|
||||||
import { ShowIf } from '../../../common/show-if/show-if'
|
import { ShowIf } from '../../../common/show-if/show-if'
|
||||||
import { EditorPagePathParams } from '../../editor-page'
|
import { EditorPagePathParams } from '../../editor-page'
|
||||||
import { NoteType } from '../../note-frontmatter/note-frontmatter'
|
import { NoteType } from '../../note-frontmatter/note-frontmatter'
|
||||||
|
import { useApplicationState } from '../../../../hooks/common/use-application-state'
|
||||||
|
|
||||||
export interface ShareModalProps {
|
export interface ShareModalProps {
|
||||||
show: boolean
|
show: boolean
|
||||||
|
@ -25,8 +23,8 @@ export interface ShareModalProps {
|
||||||
|
|
||||||
export const ShareModal: React.FC<ShareModalProps> = ({ show, onHide }) => {
|
export const ShareModal: React.FC<ShareModalProps> = ({ show, onHide }) => {
|
||||||
useTranslation()
|
useTranslation()
|
||||||
const noteFrontmatter = useSelector((state: ApplicationState) => state.noteDetails.frontmatter, equal)
|
const noteFrontmatter = useApplicationState((state) => state.noteDetails.frontmatter)
|
||||||
const editorMode = useSelector((state: ApplicationState) => state.editorConfig.editorMode)
|
const editorMode = useApplicationState((state) => state.editorConfig.editorMode)
|
||||||
const baseUrl = useFrontendBaseUrl()
|
const baseUrl = useFrontendBaseUrl()
|
||||||
const { id } = useParams<EditorPagePathParams>()
|
const { id } = useParams<EditorPagePathParams>()
|
||||||
|
|
||||||
|
|
|
@ -6,11 +6,9 @@
|
||||||
|
|
||||||
import React, { useCallback, useMemo, useRef, useState } from 'react'
|
import React, { useCallback, useMemo, useRef, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useSelector } from 'react-redux'
|
|
||||||
import { useApplyDarkMode } from '../../hooks/common/use-apply-dark-mode'
|
import { useApplyDarkMode } from '../../hooks/common/use-apply-dark-mode'
|
||||||
import { useDocumentTitleWithNoteTitle } from '../../hooks/common/use-document-title-with-note-title'
|
import { useDocumentTitleWithNoteTitle } from '../../hooks/common/use-document-title-with-note-title'
|
||||||
import { useNoteMarkdownContent } from '../../hooks/common/use-note-markdown-content'
|
import { useNoteMarkdownContent } from '../../hooks/common/use-note-markdown-content'
|
||||||
import { ApplicationState } from '../../redux'
|
|
||||||
import {
|
import {
|
||||||
SetCheckboxInMarkdownContent,
|
SetCheckboxInMarkdownContent,
|
||||||
setNoteFrontmatter,
|
setNoteFrontmatter,
|
||||||
|
@ -36,6 +34,7 @@ import { UiNotifications } from '../notifications/ui-notifications'
|
||||||
import { useNotificationTest } from './use-notification-test'
|
import { useNotificationTest } from './use-notification-test'
|
||||||
import { IframeCommunicatorContextProvider } from './render-context/iframe-communicator-context-provider'
|
import { IframeCommunicatorContextProvider } from './render-context/iframe-communicator-context-provider'
|
||||||
import { useUpdateLocalHistoryEntry } from './hooks/useUpdateLocalHistoryEntry'
|
import { useUpdateLocalHistoryEntry } from './hooks/useUpdateLocalHistoryEntry'
|
||||||
|
import { useApplicationState } from '../../hooks/common/use-application-state'
|
||||||
|
|
||||||
export interface EditorPagePathParams {
|
export interface EditorPagePathParams {
|
||||||
id: string
|
id: string
|
||||||
|
@ -51,8 +50,8 @@ export const EditorPage: React.FC = () => {
|
||||||
const markdownContent = useNoteMarkdownContent()
|
const markdownContent = useNoteMarkdownContent()
|
||||||
const scrollSource = useRef<ScrollSource>(ScrollSource.EDITOR)
|
const scrollSource = useRef<ScrollSource>(ScrollSource.EDITOR)
|
||||||
|
|
||||||
const editorMode: EditorMode = useSelector((state: ApplicationState) => state.editorConfig.editorMode)
|
const editorMode: EditorMode = useApplicationState((state) => state.editorConfig.editorMode)
|
||||||
const editorSyncScroll: boolean = useSelector((state: ApplicationState) => state.editorConfig.syncScroll)
|
const editorSyncScroll: boolean = useApplicationState((state) => state.editorConfig.syncScroll)
|
||||||
|
|
||||||
const [scrollState, setScrollState] = useState<DualScrollState>(() => ({
|
const [scrollState, setScrollState] = useState<DualScrollState>(() => ({
|
||||||
editorScrollState: { firstLineInView: 1, scrolledPercentage: 0 },
|
editorScrollState: { firstLineInView: 1, scrolledPercentage: 0 },
|
||||||
|
|
|
@ -27,12 +27,9 @@ import 'codemirror/keymap/emacs'
|
||||||
import 'codemirror/keymap/sublime'
|
import 'codemirror/keymap/sublime'
|
||||||
import 'codemirror/keymap/vim'
|
import 'codemirror/keymap/vim'
|
||||||
import 'codemirror/mode/gfm/gfm'
|
import 'codemirror/mode/gfm/gfm'
|
||||||
import equal from 'fast-deep-equal'
|
|
||||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||||
import { Controlled as ControlledCodeMirror } from 'react-codemirror2'
|
import { Controlled as ControlledCodeMirror } from 'react-codemirror2'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useSelector } from 'react-redux'
|
|
||||||
import { ApplicationState } from '../../../redux'
|
|
||||||
import { MaxLengthWarningModal } from '../editor-modals/max-length-warning-modal'
|
import { MaxLengthWarningModal } from '../editor-modals/max-length-warning-modal'
|
||||||
import { ScrollProps, ScrollState } from '../synced-scroll/scroll-props'
|
import { ScrollProps, ScrollState } from '../synced-scroll/scroll-props'
|
||||||
import { allHinters, findWordAtCursor } from './autocompletion'
|
import { allHinters, findWordAtCursor } from './autocompletion'
|
||||||
|
@ -42,6 +39,7 @@ import { createStatusInfo, defaultState, StatusBar, StatusBarInfo } from './stat
|
||||||
import { ToolBar } from './tool-bar/tool-bar'
|
import { ToolBar } from './tool-bar/tool-bar'
|
||||||
import { handleUpload } from './upload-handler'
|
import { handleUpload } from './upload-handler'
|
||||||
import { handleFilePaste, handleTablePaste, PasteEvent } from './tool-bar/utils/pasteHandlers'
|
import { handleFilePaste, handleTablePaste, PasteEvent } from './tool-bar/utils/pasteHandlers'
|
||||||
|
import { useApplicationState } from '../../../hooks/common/use-application-state'
|
||||||
|
|
||||||
export interface EditorPaneProps {
|
export interface EditorPaneProps {
|
||||||
onContentChange: (content: string) => void
|
onContentChange: (content: string) => void
|
||||||
|
@ -81,14 +79,14 @@ export const EditorPane: React.FC<EditorPaneProps & ScrollProps> = ({
|
||||||
onMakeScrollSource
|
onMakeScrollSource
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const maxLength = useSelector((state: ApplicationState) => state.config.maxDocumentLength)
|
const maxLength = useApplicationState((state) => state.config.maxDocumentLength)
|
||||||
const smartPasteEnabled = useSelector((state: ApplicationState) => state.editorConfig.smartPaste)
|
const smartPasteEnabled = useApplicationState((state) => state.editorConfig.smartPaste)
|
||||||
const [showMaxLengthWarning, setShowMaxLengthWarning] = useState(false)
|
const [showMaxLengthWarning, setShowMaxLengthWarning] = useState(false)
|
||||||
const maxLengthWarningAlreadyShown = useRef(false)
|
const maxLengthWarningAlreadyShown = useRef(false)
|
||||||
const [editor, setEditor] = useState<Editor>()
|
const [editor, setEditor] = useState<Editor>()
|
||||||
const [statusBarInfo, setStatusBarInfo] = useState<StatusBarInfo>(defaultState)
|
const [statusBarInfo, setStatusBarInfo] = useState<StatusBarInfo>(defaultState)
|
||||||
const editorPreferences = useSelector((state: ApplicationState) => state.editorConfig.preferences, equal)
|
const editorPreferences = useApplicationState((state) => state.editorConfig.preferences)
|
||||||
const ligaturesEnabled = useSelector((state: ApplicationState) => state.editorConfig.ligatures, equal)
|
const ligaturesEnabled = useApplicationState((state) => state.editorConfig.ligatures)
|
||||||
|
|
||||||
const lastScrollPosition = useRef<number>()
|
const lastScrollPosition = useRef<number>()
|
||||||
const [editorScroll, setEditorScroll] = useState<ScrollInfo>()
|
const [editorScroll, setEditorScroll] = useState<ScrollInfo>()
|
||||||
|
|
|
@ -5,24 +5,19 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { EditorConfiguration } from 'codemirror'
|
import { EditorConfiguration } from 'codemirror'
|
||||||
import equal from 'fast-deep-equal'
|
|
||||||
import React, { ChangeEvent, useCallback } from 'react'
|
import React, { ChangeEvent, useCallback } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useSelector } from 'react-redux'
|
|
||||||
import { ApplicationState } from '../../../../../redux'
|
|
||||||
import { mergeEditorPreferences } from '../../../../../redux/editor/methods'
|
import { mergeEditorPreferences } from '../../../../../redux/editor/methods'
|
||||||
import { EditorPreferenceInput, EditorPreferenceInputType } from './editor-preference-input'
|
import { EditorPreferenceInput, EditorPreferenceInputType } from './editor-preference-input'
|
||||||
import { EditorPreferenceProperty } from './editor-preference-property'
|
import { EditorPreferenceProperty } from './editor-preference-property'
|
||||||
|
import { useApplicationState } from '../../../../../hooks/common/use-application-state'
|
||||||
|
|
||||||
export interface EditorPreferenceBooleanProps {
|
export interface EditorPreferenceBooleanProps {
|
||||||
property: EditorPreferenceProperty
|
property: EditorPreferenceProperty
|
||||||
}
|
}
|
||||||
|
|
||||||
export const EditorPreferenceBooleanProperty: React.FC<EditorPreferenceBooleanProps> = ({ property }) => {
|
export const EditorPreferenceBooleanProperty: React.FC<EditorPreferenceBooleanProps> = ({ property }) => {
|
||||||
const preference = useSelector(
|
const preference = useApplicationState((state) => state.editorConfig.preferences[property]?.toString() ?? '')
|
||||||
(state: ApplicationState) => state.editorConfig.preferences[property]?.toString() || '',
|
|
||||||
equal
|
|
||||||
)
|
|
||||||
|
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const selectItem = useCallback(
|
const selectItem = useCallback(
|
||||||
|
|
|
@ -5,13 +5,12 @@
|
||||||
*/
|
*/
|
||||||
import React, { ChangeEvent, useCallback } from 'react'
|
import React, { ChangeEvent, useCallback } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useSelector } from 'react-redux'
|
|
||||||
import { ApplicationState } from '../../../../../redux'
|
|
||||||
import { setEditorLigatures } from '../../../../../redux/editor/methods'
|
import { setEditorLigatures } from '../../../../../redux/editor/methods'
|
||||||
import { EditorPreferenceInput, EditorPreferenceInputType } from './editor-preference-input'
|
import { EditorPreferenceInput, EditorPreferenceInputType } from './editor-preference-input'
|
||||||
|
import { useApplicationState } from '../../../../../hooks/common/use-application-state'
|
||||||
|
|
||||||
export const EditorPreferenceLigaturesSelect: React.FC = () => {
|
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<HTMLSelectElement>) => {
|
const saveLigatures = useCallback((event: ChangeEvent<HTMLSelectElement>) => {
|
||||||
const ligaturesActivated: boolean = event.target.value === 'true'
|
const ligaturesActivated: boolean = event.target.value === 'true'
|
||||||
setEditorLigatures(ligaturesActivated)
|
setEditorLigatures(ligaturesActivated)
|
||||||
|
|
|
@ -5,23 +5,18 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { EditorConfiguration } from 'codemirror'
|
import { EditorConfiguration } from 'codemirror'
|
||||||
import equal from 'fast-deep-equal'
|
|
||||||
import React, { ChangeEvent, useCallback } from 'react'
|
import React, { ChangeEvent, useCallback } from 'react'
|
||||||
import { useSelector } from 'react-redux'
|
|
||||||
import { ApplicationState } from '../../../../../redux'
|
|
||||||
import { mergeEditorPreferences } from '../../../../../redux/editor/methods'
|
import { mergeEditorPreferences } from '../../../../../redux/editor/methods'
|
||||||
import { EditorPreferenceInput, EditorPreferenceInputType } from './editor-preference-input'
|
import { EditorPreferenceInput, EditorPreferenceInputType } from './editor-preference-input'
|
||||||
import { EditorPreferenceProperty } from './editor-preference-property'
|
import { EditorPreferenceProperty } from './editor-preference-property'
|
||||||
|
import { useApplicationState } from '../../../../../hooks/common/use-application-state'
|
||||||
|
|
||||||
export interface EditorPreferenceNumberProps {
|
export interface EditorPreferenceNumberProps {
|
||||||
property: EditorPreferenceProperty
|
property: EditorPreferenceProperty
|
||||||
}
|
}
|
||||||
|
|
||||||
export const EditorPreferenceNumberProperty: React.FC<EditorPreferenceNumberProps> = ({ property }) => {
|
export const EditorPreferenceNumberProperty: React.FC<EditorPreferenceNumberProps> = ({ property }) => {
|
||||||
const preference = useSelector(
|
const preference = useApplicationState((state) => state.editorConfig.preferences[property]?.toString() ?? '')
|
||||||
(state: ApplicationState) => state.editorConfig.preferences[property]?.toString() || '',
|
|
||||||
equal
|
|
||||||
)
|
|
||||||
|
|
||||||
const selectItem = useCallback(
|
const selectItem = useCallback(
|
||||||
(event: ChangeEvent<HTMLSelectElement>) => {
|
(event: ChangeEvent<HTMLSelectElement>) => {
|
||||||
|
|
|
@ -5,14 +5,12 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { EditorConfiguration } from 'codemirror'
|
import { EditorConfiguration } from 'codemirror'
|
||||||
import equal from 'fast-deep-equal'
|
|
||||||
import React, { ChangeEvent, useCallback } from 'react'
|
import React, { ChangeEvent, useCallback } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useSelector } from 'react-redux'
|
|
||||||
import { ApplicationState } from '../../../../../redux'
|
|
||||||
import { mergeEditorPreferences } from '../../../../../redux/editor/methods'
|
import { mergeEditorPreferences } from '../../../../../redux/editor/methods'
|
||||||
import { EditorPreferenceInput, EditorPreferenceInputType } from './editor-preference-input'
|
import { EditorPreferenceInput, EditorPreferenceInputType } from './editor-preference-input'
|
||||||
import { EditorPreferenceProperty } from './editor-preference-property'
|
import { EditorPreferenceProperty } from './editor-preference-property'
|
||||||
|
import { useApplicationState } from '../../../../../hooks/common/use-application-state'
|
||||||
|
|
||||||
export interface EditorPreferenceSelectPropertyProps {
|
export interface EditorPreferenceSelectPropertyProps {
|
||||||
property: EditorPreferenceProperty
|
property: EditorPreferenceProperty
|
||||||
|
@ -23,10 +21,7 @@ export const EditorPreferenceSelectProperty: React.FC<EditorPreferenceSelectProp
|
||||||
property,
|
property,
|
||||||
selections
|
selections
|
||||||
}) => {
|
}) => {
|
||||||
const preference = useSelector(
|
const preference = useApplicationState((state) => state.editorConfig.preferences[property]?.toString() ?? '')
|
||||||
(state: ApplicationState) => state.editorConfig.preferences[property]?.toString() || '',
|
|
||||||
equal
|
|
||||||
)
|
|
||||||
|
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
|
|
@ -5,13 +5,12 @@
|
||||||
*/
|
*/
|
||||||
import React, { ChangeEvent, useCallback } from 'react'
|
import React, { ChangeEvent, useCallback } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useSelector } from 'react-redux'
|
import { useApplicationState } from '../../../../../hooks/common/use-application-state'
|
||||||
import { ApplicationState } from '../../../../../redux'
|
|
||||||
import { setEditorSmartPaste } from '../../../../../redux/editor/methods'
|
import { setEditorSmartPaste } from '../../../../../redux/editor/methods'
|
||||||
import { EditorPreferenceInput, EditorPreferenceInputType } from './editor-preference-input'
|
import { EditorPreferenceInput, EditorPreferenceInputType } from './editor-preference-input'
|
||||||
|
|
||||||
export const EditorPreferenceSmartPasteSelect: React.FC = () => {
|
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<HTMLSelectElement>) => {
|
const saveSmartPaste = useCallback((event: ChangeEvent<HTMLSelectElement>) => {
|
||||||
const smartPasteActivated: boolean = event.target.value === 'true'
|
const smartPasteActivated: boolean = event.target.value === 'true'
|
||||||
setEditorSmartPaste(smartPasteActivated)
|
setEditorSmartPaste(smartPasteActivated)
|
||||||
|
|
|
@ -4,12 +4,9 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import equal from 'fast-deep-equal'
|
|
||||||
import React, { Fragment, useState } from 'react'
|
import React, { Fragment, useState } from 'react'
|
||||||
import { Button, Form, ListGroup } from 'react-bootstrap'
|
import { Button, Form, ListGroup } from 'react-bootstrap'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useSelector } from 'react-redux'
|
|
||||||
import { ApplicationState } from '../../../../../redux'
|
|
||||||
import { ForkAwesomeIcon } from '../../../../common/fork-awesome/fork-awesome-icon'
|
import { ForkAwesomeIcon } from '../../../../common/fork-awesome/fork-awesome-icon'
|
||||||
import { CommonModal } from '../../../../common/modals/common-modal'
|
import { CommonModal } from '../../../../common/modals/common-modal'
|
||||||
import { ShowIf } from '../../../../common/show-if/show-if'
|
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 { EditorPreferenceProperty } from './editor-preference-property'
|
||||||
import { EditorPreferenceSelectProperty } from './editor-preference-select-property'
|
import { EditorPreferenceSelectProperty } from './editor-preference-select-property'
|
||||||
import { EditorPreferenceSmartPasteSelect } from './editor-preference-smart-paste-select'
|
import { EditorPreferenceSmartPasteSelect } from './editor-preference-smart-paste-select'
|
||||||
|
import { useApplicationState } from '../../../../../hooks/common/use-application-state'
|
||||||
|
|
||||||
export const EditorPreferences: React.FC = () => {
|
export const EditorPreferences: React.FC = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [showModal, setShowModal] = useState(false)
|
const [showModal, setShowModal] = useState(false)
|
||||||
const indentWithTabs = useSelector(
|
const indentWithTabs = useApplicationState((state) => state.editorConfig.preferences.indentWithTabs ?? false)
|
||||||
(state: ApplicationState) => state.editorConfig.preferences.indentWithTabs ?? false,
|
|
||||||
equal
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
|
|
|
@ -6,18 +6,18 @@
|
||||||
|
|
||||||
import equal from 'fast-deep-equal'
|
import equal from 'fast-deep-equal'
|
||||||
import { useEffect, useRef } from 'react'
|
import { useEffect, useRef } from 'react'
|
||||||
import { useSelector } from 'react-redux'
|
import { store } from '../../../redux'
|
||||||
import { ApplicationState, store } from '../../../redux'
|
|
||||||
import { useParams } from 'react-router-dom'
|
import { useParams } from 'react-router-dom'
|
||||||
import { EditorPagePathParams } from '../editor-page'
|
import { EditorPagePathParams } from '../editor-page'
|
||||||
import { HistoryEntry, HistoryEntryOrigin } from '../../../redux/history/types'
|
import { HistoryEntry, HistoryEntryOrigin } from '../../../redux/history/types'
|
||||||
import { updateLocalHistoryEntry } from '../../../redux/history/methods'
|
import { updateLocalHistoryEntry } from '../../../redux/history/methods'
|
||||||
|
import { useApplicationState } from '../../../hooks/common/use-application-state'
|
||||||
|
|
||||||
export const useUpdateLocalHistoryEntry = (updateReady: boolean): void => {
|
export const useUpdateLocalHistoryEntry = (updateReady: boolean): void => {
|
||||||
const { id } = useParams<EditorPagePathParams>()
|
const { id } = useParams<EditorPagePathParams>()
|
||||||
const userExists = useSelector((state: ApplicationState) => !!state.user)
|
const userExists = useApplicationState((state) => !!state.user)
|
||||||
const currentNoteTitle = useSelector((state: ApplicationState) => state.noteDetails.noteTitle)
|
const currentNoteTitle = useApplicationState((state) => state.noteDetails.noteTitle)
|
||||||
const currentNoteTags = useSelector((state: ApplicationState) => state.noteDetails.frontmatter.tags)
|
const currentNoteTags = useApplicationState((state) => state.noteDetails.frontmatter.tags)
|
||||||
|
|
||||||
const lastNoteTitle = useRef('')
|
const lastNoteTitle = useRef('')
|
||||||
const lastNoteTags = useRef<string[]>([])
|
const lastNoteTags = useRef<string[]>([])
|
||||||
|
|
|
@ -5,9 +5,8 @@
|
||||||
*/
|
*/
|
||||||
import equal from 'fast-deep-equal'
|
import equal from 'fast-deep-equal'
|
||||||
import React, { Fragment, useCallback, useEffect, useRef, useState } from 'react'
|
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 { useIsDarkModeActivated } from '../../../hooks/common/use-is-dark-mode-activated'
|
||||||
import { ApplicationState } from '../../../redux'
|
|
||||||
import { isTestMode } from '../../../utils/test-modes'
|
import { isTestMode } from '../../../utils/test-modes'
|
||||||
import { RendererProps } from '../../render-page/markdown-document'
|
import { RendererProps } from '../../render-page/markdown-document'
|
||||||
import { ImageDetails, RendererType } from '../../render-page/rendering-message'
|
import { ImageDetails, RendererType } from '../../render-page/rendering-message'
|
||||||
|
@ -42,7 +41,7 @@ export const RenderIframe: React.FC<RenderIframeProps> = ({
|
||||||
const [lightboxDetails, setLightboxDetails] = useState<ImageDetails | undefined>(undefined)
|
const [lightboxDetails, setLightboxDetails] = useState<ImageDetails | undefined>(undefined)
|
||||||
|
|
||||||
const frameReference = useRef<HTMLIFrameElement>(null)
|
const frameReference = useRef<HTMLIFrameElement>(null)
|
||||||
const rendererOrigin = useSelector((state: ApplicationState) => state.config.iframeCommunication.rendererOrigin)
|
const rendererOrigin = useApplicationState((state) => state.config.iframeCommunication.rendererOrigin)
|
||||||
const renderPageUrl = `${rendererOrigin}render`
|
const renderPageUrl = `${rendererOrigin}render`
|
||||||
const resetRendererReady = useCallback(() => setRendererReady(false), [])
|
const resetRendererReady = useCallback(() => setRendererReady(false), [])
|
||||||
const iframeCommunicator = useContextOrStandaloneIframeCommunicator()
|
const iframeCommunicator = useContextOrStandaloneIframeCommunicator()
|
||||||
|
|
|
@ -7,17 +7,14 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Alert } from 'react-bootstrap'
|
import { Alert } from 'react-bootstrap'
|
||||||
import { Trans, useTranslation } from 'react-i18next'
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
import { useSelector } from 'react-redux'
|
|
||||||
import links from '../../../links.json'
|
import links from '../../../links.json'
|
||||||
import { ApplicationState } from '../../../redux'
|
|
||||||
import { TranslatedExternalLink } from '../../common/links/translated-external-link'
|
import { TranslatedExternalLink } from '../../common/links/translated-external-link'
|
||||||
import { ShowIf } from '../../common/show-if/show-if'
|
import { ShowIf } from '../../common/show-if/show-if'
|
||||||
|
import { useApplicationState } from '../../../hooks/common/use-application-state'
|
||||||
|
|
||||||
export const YamlArrayDeprecationAlert: React.FC = () => {
|
export const YamlArrayDeprecationAlert: React.FC = () => {
|
||||||
useTranslation()
|
useTranslation()
|
||||||
const yamlDeprecatedTags = useSelector(
|
const yamlDeprecatedTags = useApplicationState((state) => state.noteDetails.frontmatter.deprecatedTagsSyntax)
|
||||||
(state: ApplicationState) => state.noteDetails.frontmatter.deprecatedTagsSyntax
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ShowIf condition={yamlDeprecatedTags}>
|
<ShowIf condition={yamlDeprecatedTags}>
|
||||||
|
|
|
@ -10,15 +10,14 @@ import { SidebarButton } from './sidebar-button'
|
||||||
import { SpecificSidebarEntryProps } from './types'
|
import { SpecificSidebarEntryProps } from './types'
|
||||||
import { useParams } from 'react-router-dom'
|
import { useParams } from 'react-router-dom'
|
||||||
import { EditorPagePathParams } from '../editor-page'
|
import { EditorPagePathParams } from '../editor-page'
|
||||||
import { useSelector } from 'react-redux'
|
|
||||||
import { ApplicationState } from '../../../redux'
|
|
||||||
import { toggleHistoryEntryPinning } from '../../../redux/history/methods'
|
import { toggleHistoryEntryPinning } from '../../../redux/history/methods'
|
||||||
import { showErrorNotification } from '../../../redux/ui-notifications/methods'
|
import { showErrorNotification } from '../../../redux/ui-notifications/methods'
|
||||||
|
import { useApplicationState } from '../../../hooks/common/use-application-state'
|
||||||
|
|
||||||
export const PinNoteSidebarEntry: React.FC<SpecificSidebarEntryProps> = ({ className, hide }) => {
|
export const PinNoteSidebarEntry: React.FC<SpecificSidebarEntryProps> = ({ className, hide }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { id } = useParams<EditorPagePathParams>()
|
const { id } = useParams<EditorPagePathParams>()
|
||||||
const history = useSelector((state: ApplicationState) => state.history)
|
const history = useApplicationState((state) => state.history)
|
||||||
|
|
||||||
const isPinned = useMemo(() => {
|
const isPinned = useMemo(() => {
|
||||||
const entry = history.find((entry) => entry.identifier === id)
|
const entry = history.find((entry) => entry.identifier === id)
|
||||||
|
|
|
@ -13,8 +13,7 @@ import { DeleteNoteItem } from './delete-note-item'
|
||||||
import './entry-menu.scss'
|
import './entry-menu.scss'
|
||||||
import { RemoveNoteEntryItem } from './remove-note-entry-item'
|
import { RemoveNoteEntryItem } from './remove-note-entry-item'
|
||||||
import { HistoryEntryOrigin } from '../../../redux/history/types'
|
import { HistoryEntryOrigin } from '../../../redux/history/types'
|
||||||
import { useSelector } from 'react-redux'
|
import { useApplicationState } from '../../../hooks/common/use-application-state'
|
||||||
import { ApplicationState } from '../../../redux'
|
|
||||||
|
|
||||||
export interface EntryMenuProps {
|
export interface EntryMenuProps {
|
||||||
id: string
|
id: string
|
||||||
|
@ -29,7 +28,7 @@ export interface EntryMenuProps {
|
||||||
export const EntryMenu: React.FC<EntryMenuProps> = ({ id, title, origin, isDark, onRemove, onDelete, className }) => {
|
export const EntryMenu: React.FC<EntryMenuProps> = ({ id, title, origin, isDark, onRemove, onDelete, className }) => {
|
||||||
useTranslation()
|
useTranslation()
|
||||||
|
|
||||||
const userExists = useSelector((state: ApplicationState) => !!state.user)
|
const userExists = useApplicationState((state) => !!state.user)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dropdown className={`d-inline-flex ${className || ''}`}>
|
<Dropdown className={`d-inline-flex ${className || ''}`}>
|
||||||
|
|
|
@ -7,19 +7,18 @@
|
||||||
import React, { Fragment, useEffect, useMemo, useState } from 'react'
|
import React, { Fragment, useEffect, useMemo, useState } from 'react'
|
||||||
import { Row } from 'react-bootstrap'
|
import { Row } from 'react-bootstrap'
|
||||||
import { Trans, useTranslation } from 'react-i18next'
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
import { useSelector } from 'react-redux'
|
|
||||||
import { ApplicationState } from '../../redux'
|
|
||||||
import { HistoryContent } from './history-content/history-content'
|
import { HistoryContent } from './history-content/history-content'
|
||||||
import { HistoryToolbar, HistoryToolbarState, initToolbarState } from './history-toolbar/history-toolbar'
|
import { HistoryToolbar, HistoryToolbarState, initToolbarState } from './history-toolbar/history-toolbar'
|
||||||
import { sortAndFilterEntries } from './utils'
|
import { sortAndFilterEntries } from './utils'
|
||||||
import { refreshHistoryState } from '../../redux/history/methods'
|
import { refreshHistoryState } from '../../redux/history/methods'
|
||||||
import { HistoryEntry } from '../../redux/history/types'
|
import { HistoryEntry } from '../../redux/history/types'
|
||||||
import { showErrorNotification } from '../../redux/ui-notifications/methods'
|
import { showErrorNotification } from '../../redux/ui-notifications/methods'
|
||||||
|
import { useApplicationState } from '../../hooks/common/use-application-state'
|
||||||
|
|
||||||
export const HistoryPage: React.FC = () => {
|
export const HistoryPage: React.FC = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const allEntries = useSelector((state: ApplicationState) => state.history)
|
const allEntries = useApplicationState((state) => state.history)
|
||||||
const [toolbarState, setToolbarState] = useState<HistoryToolbarState>(initToolbarState)
|
const [toolbarState, setToolbarState] = useState<HistoryToolbarState>(initToolbarState)
|
||||||
|
|
||||||
const entriesToShow = useMemo<HistoryEntry[]>(
|
const entriesToShow = useMemo<HistoryEntry[]>(
|
||||||
|
|
|
@ -9,9 +9,7 @@ import React, { ChangeEvent, useCallback, useEffect, useMemo, useRef, useState }
|
||||||
import { Button, Form, FormControl, InputGroup, ToggleButton, ToggleButtonGroup } from 'react-bootstrap'
|
import { Button, Form, FormControl, InputGroup, ToggleButton, ToggleButtonGroup } from 'react-bootstrap'
|
||||||
import { Typeahead } from 'react-bootstrap-typeahead'
|
import { Typeahead } from 'react-bootstrap-typeahead'
|
||||||
import { Trans, useTranslation } from 'react-i18next'
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
import { useSelector } from 'react-redux'
|
|
||||||
import { useQueryState } from 'react-router-use-location-state'
|
import { useQueryState } from 'react-router-use-location-state'
|
||||||
import { ApplicationState } from '../../../redux'
|
|
||||||
import { ForkAwesomeIcon } from '../../common/fork-awesome/fork-awesome-icon'
|
import { ForkAwesomeIcon } from '../../common/fork-awesome/fork-awesome-icon'
|
||||||
import { ShowIf } from '../../common/show-if/show-if'
|
import { ShowIf } from '../../common/show-if/show-if'
|
||||||
import { SortButton, SortModeEnum } from '../sort-button/sort-button'
|
import { SortButton, SortModeEnum } from '../sort-button/sort-button'
|
||||||
|
@ -22,6 +20,7 @@ import './typeahead-hacks.scss'
|
||||||
import { HistoryEntryOrigin } from '../../../redux/history/types'
|
import { HistoryEntryOrigin } from '../../../redux/history/types'
|
||||||
import { importHistoryEntries, refreshHistoryState, setHistoryEntries } from '../../../redux/history/methods'
|
import { importHistoryEntries, refreshHistoryState, setHistoryEntries } from '../../../redux/history/methods'
|
||||||
import { showErrorNotification } from '../../../redux/ui-notifications/methods'
|
import { showErrorNotification } from '../../../redux/ui-notifications/methods'
|
||||||
|
import { useApplicationState } from '../../../hooks/common/use-application-state'
|
||||||
|
|
||||||
export type HistoryToolbarChange = (newState: HistoryToolbarState) => void
|
export type HistoryToolbarChange = (newState: HistoryToolbarState) => void
|
||||||
|
|
||||||
|
@ -61,8 +60,8 @@ export const initToolbarState: HistoryToolbarState = {
|
||||||
|
|
||||||
export const HistoryToolbar: React.FC<HistoryToolbarProps> = ({ onSettingsChange }) => {
|
export const HistoryToolbar: React.FC<HistoryToolbarProps> = ({ onSettingsChange }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const historyEntries = useSelector((state: ApplicationState) => state.history)
|
const historyEntries = useApplicationState((state) => state.history)
|
||||||
const userExists = useSelector((state: ApplicationState) => !!state.user)
|
const userExists = useApplicationState((state) => !!state.user)
|
||||||
|
|
||||||
const tags = useMemo<string[]>(() => {
|
const tags = useMemo<string[]>(() => {
|
||||||
const allTags = historyEntries.map((entry) => entry.tags).flat()
|
const allTags = historyEntries.map((entry) => entry.tags).flat()
|
||||||
|
|
|
@ -16,14 +16,13 @@ import {
|
||||||
mergeHistoryEntries,
|
mergeHistoryEntries,
|
||||||
refreshHistoryState
|
refreshHistoryState
|
||||||
} from '../../../redux/history/methods'
|
} from '../../../redux/history/methods'
|
||||||
import { ApplicationState } from '../../../redux'
|
|
||||||
import { useSelector } from 'react-redux'
|
|
||||||
import { showErrorNotification } from '../../../redux/ui-notifications/methods'
|
import { showErrorNotification } from '../../../redux/ui-notifications/methods'
|
||||||
|
import { useApplicationState } from '../../../hooks/common/use-application-state'
|
||||||
|
|
||||||
export const ImportHistoryButton: React.FC = () => {
|
export const ImportHistoryButton: React.FC = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const userExists = useSelector((state: ApplicationState) => !!state.user)
|
const userExists = useApplicationState((state) => !!state.user)
|
||||||
const historyState = useSelector((state: ApplicationState) => state.history)
|
const historyState = useApplicationState((state) => state.history)
|
||||||
const uploadInput = useRef<HTMLInputElement>(null)
|
const uploadInput = useRef<HTMLInputElement>(null)
|
||||||
const [show, setShow] = useState(false)
|
const [show, setShow] = useState(false)
|
||||||
const [fileName, setFilename] = useState('')
|
const [fileName, setFilename] = useState('')
|
||||||
|
|
|
@ -7,17 +7,16 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Button } from 'react-bootstrap'
|
import { Button } from 'react-bootstrap'
|
||||||
import { Trans, useTranslation } from 'react-i18next'
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
import { useSelector } from 'react-redux'
|
|
||||||
import { Link } from 'react-router-dom'
|
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 { ShowIf } from '../../common/show-if/show-if'
|
||||||
import { SignInButton } from '../../landing-layout/navigation/sign-in-button'
|
import { SignInButton } from '../../landing-layout/navigation/sign-in-button'
|
||||||
import './cover-buttons.scss'
|
import './cover-buttons.scss'
|
||||||
|
|
||||||
export const CoverButtons: React.FC = () => {
|
export const CoverButtons: React.FC = () => {
|
||||||
useTranslation()
|
useTranslation()
|
||||||
const userExists = useSelector((state: ApplicationState) => !!state.user)
|
const userExists = useApplicationState((state) => !!state.user)
|
||||||
const anyAuthProviderActivated = useSelector((state: ApplicationState) =>
|
const anyAuthProviderActivated = useApplicationState((state) =>
|
||||||
Object.values(state.config.authProviders).includes(true)
|
Object.values(state.config.authProviders).includes(true)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -6,21 +6,18 @@
|
||||||
|
|
||||||
import React, { Fragment } from 'react'
|
import React, { Fragment } from 'react'
|
||||||
import { Trans, useTranslation } from 'react-i18next'
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
import { useSelector } from 'react-redux'
|
|
||||||
import links from '../../../links.json'
|
import links from '../../../links.json'
|
||||||
import { ApplicationState } from '../../../redux'
|
|
||||||
import { ExternalLink } from '../../common/links/external-link'
|
import { ExternalLink } from '../../common/links/external-link'
|
||||||
import { TranslatedExternalLink } from '../../common/links/translated-external-link'
|
import { TranslatedExternalLink } from '../../common/links/translated-external-link'
|
||||||
import { TranslatedInternalLink } from '../../common/links/translated-internal-link'
|
import { TranslatedInternalLink } from '../../common/links/translated-internal-link'
|
||||||
import { VersionInfoLink } from './version-info/version-info-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 = () => {
|
export const PoweredByLinks: React.FC = () => {
|
||||||
useTranslation()
|
useTranslation()
|
||||||
|
|
||||||
const specialUrls: [string, string][] = useSelector(
|
const specialUrls: [string, string][] = useApplicationState((state) =>
|
||||||
(state: ApplicationState) => Object.entries(state.config.specialUrls) as [string, string][],
|
Object.entries(state.config.specialUrls).map(([i18nkey, url]) => [i18nkey, String(url)])
|
||||||
equal
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -10,13 +10,11 @@ import { Modal, Row } from 'react-bootstrap'
|
||||||
import { VersionInfoModalColumn } from './version-info-modal-column'
|
import { VersionInfoModalColumn } from './version-info-modal-column'
|
||||||
import frontendVersion from '../../../../version.json'
|
import frontendVersion from '../../../../version.json'
|
||||||
import links from '../../../../links.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 { BackendVersion } from '../../../../api/config/types'
|
||||||
|
import { useApplicationState } from '../../../../hooks/common/use-application-state'
|
||||||
|
|
||||||
export const VersionInfoModal: React.FC<CommonModalProps> = ({ onHide, show }) => {
|
export const VersionInfoModal: React.FC<CommonModalProps> = ({ onHide, show }) => {
|
||||||
const serverVersion: BackendVersion = useSelector((state: ApplicationState) => state.config.version, equal)
|
const serverVersion: BackendVersion = useApplicationState((state) => state.config.version)
|
||||||
const backendVersion = useMemo(() => {
|
const backendVersion = useMemo(() => {
|
||||||
const version = `${serverVersion.major}.${serverVersion.minor}.${serverVersion.patch}`
|
const version = `${serverVersion.major}.${serverVersion.minor}.${serverVersion.patch}`
|
||||||
|
|
||||||
|
|
|
@ -7,8 +7,7 @@
|
||||||
import React, { Fragment } from 'react'
|
import React, { Fragment } from 'react'
|
||||||
import { Navbar } from 'react-bootstrap'
|
import { Navbar } from 'react-bootstrap'
|
||||||
import { Trans, useTranslation } from 'react-i18next'
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
import { useSelector } from 'react-redux'
|
import { useApplicationState } from '../../../../hooks/common/use-application-state'
|
||||||
import { ApplicationState } from '../../../../redux'
|
|
||||||
import { HeaderNavLink } from '../header-nav-link'
|
import { HeaderNavLink } from '../header-nav-link'
|
||||||
import { NewGuestNoteButton } from '../new-guest-note-button'
|
import { NewGuestNoteButton } from '../new-guest-note-button'
|
||||||
import { NewUserNoteButton } from '../new-user-note-button'
|
import { NewUserNoteButton } from '../new-user-note-button'
|
||||||
|
@ -18,7 +17,7 @@ import './header-bar.scss'
|
||||||
|
|
||||||
const HeaderBar: React.FC = () => {
|
const HeaderBar: React.FC = () => {
|
||||||
useTranslation()
|
useTranslation()
|
||||||
const userExists = useSelector((state: ApplicationState) => !!state.user)
|
const userExists = useApplicationState((state) => !!state.user)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Navbar className='justify-content-between'>
|
<Navbar className='justify-content-between'>
|
||||||
|
|
|
@ -4,23 +4,21 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import equal from 'fast-deep-equal'
|
|
||||||
import React, { useMemo } from 'react'
|
import React, { useMemo } from 'react'
|
||||||
import { Button } from 'react-bootstrap'
|
import { Button } from 'react-bootstrap'
|
||||||
import { ButtonProps } from 'react-bootstrap/Button'
|
import { ButtonProps } from 'react-bootstrap/Button'
|
||||||
import { Trans, useTranslation } from 'react-i18next'
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
import { useSelector } from 'react-redux'
|
|
||||||
import { LinkContainer } from 'react-router-bootstrap'
|
import { LinkContainer } from 'react-router-bootstrap'
|
||||||
import { ApplicationState } from '../../../redux'
|
|
||||||
import { ShowIf } from '../../common/show-if/show-if'
|
import { ShowIf } from '../../common/show-if/show-if'
|
||||||
import { getApiUrl } from '../../../api/utils'
|
import { getApiUrl } from '../../../api/utils'
|
||||||
import { INTERACTIVE_LOGIN_METHODS } from '../../../api/auth'
|
import { INTERACTIVE_LOGIN_METHODS } from '../../../api/auth'
|
||||||
|
import { useApplicationState } from '../../../hooks/common/use-application-state'
|
||||||
|
|
||||||
export type SignInButtonProps = Omit<ButtonProps, 'href'>
|
export type SignInButtonProps = Omit<ButtonProps, 'href'>
|
||||||
|
|
||||||
export const SignInButton: React.FC<SignInButtonProps> = ({ variant, ...props }) => {
|
export const SignInButton: React.FC<SignInButtonProps> = ({ variant, ...props }) => {
|
||||||
const { t } = useTranslation()
|
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 authEnabled = useMemo(() => Object.values(authProviders).includes(true), [authProviders])
|
||||||
|
|
||||||
const loginLink = useMemo(() => {
|
const loginLink = useMemo(() => {
|
||||||
|
|
|
@ -4,20 +4,18 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import equal from 'fast-deep-equal'
|
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Dropdown } from 'react-bootstrap'
|
import { Dropdown } from 'react-bootstrap'
|
||||||
import { Trans, useTranslation } from 'react-i18next'
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
import { useSelector } from 'react-redux'
|
|
||||||
import { LinkContainer } from 'react-router-bootstrap'
|
import { LinkContainer } from 'react-router-bootstrap'
|
||||||
import { ApplicationState } from '../../../redux'
|
|
||||||
import { clearUser } from '../../../redux/user/methods'
|
import { clearUser } from '../../../redux/user/methods'
|
||||||
import { ForkAwesomeIcon } from '../../common/fork-awesome/fork-awesome-icon'
|
import { ForkAwesomeIcon } from '../../common/fork-awesome/fork-awesome-icon'
|
||||||
import { UserAvatar } from '../../common/user-avatar/user-avatar'
|
import { UserAvatar } from '../../common/user-avatar/user-avatar'
|
||||||
|
import { useApplicationState } from '../../../hooks/common/use-application-state'
|
||||||
|
|
||||||
export const UserDropdown: React.FC = () => {
|
export const UserDropdown: React.FC = () => {
|
||||||
useTranslation()
|
useTranslation()
|
||||||
const user = useSelector((state: ApplicationState) => state.user, equal)
|
const user = useApplicationState((state) => state.user)
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return null
|
return null
|
||||||
|
|
|
@ -7,19 +7,18 @@
|
||||||
import React, { FormEvent, useCallback, useState } from 'react'
|
import React, { FormEvent, useCallback, useState } from 'react'
|
||||||
import { Alert, Button, Card, Form } from 'react-bootstrap'
|
import { Alert, Button, Card, Form } from 'react-bootstrap'
|
||||||
import { Trans, useTranslation } from 'react-i18next'
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
import { useSelector } from 'react-redux'
|
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
import { doInternalLogin } from '../../../api/auth'
|
import { doInternalLogin } from '../../../api/auth'
|
||||||
import { ApplicationState } from '../../../redux'
|
|
||||||
import { ShowIf } from '../../common/show-if/show-if'
|
import { ShowIf } from '../../common/show-if/show-if'
|
||||||
import { fetchAndSetUser } from './utils'
|
import { fetchAndSetUser } from './utils'
|
||||||
|
import { useApplicationState } from '../../../hooks/common/use-application-state'
|
||||||
|
|
||||||
export const ViaInternal: React.FC = () => {
|
export const ViaInternal: React.FC = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [username, setUsername] = useState('')
|
const [username, setUsername] = useState('')
|
||||||
const [password, setPassword] = useState('')
|
const [password, setPassword] = useState('')
|
||||||
const [error, setError] = useState(false)
|
const [error, setError] = useState(false)
|
||||||
const allowRegister = useSelector((state: ApplicationState) => state.config.allowRegister)
|
const allowRegister = useApplicationState((state) => state.config.allowRegister)
|
||||||
|
|
||||||
const onLoginSubmit = useCallback(
|
const onLoginSubmit = useCallback(
|
||||||
(event: FormEvent) => {
|
(event: FormEvent) => {
|
||||||
|
|
|
@ -8,14 +8,13 @@ import React, { FormEvent, useCallback, useState } from 'react'
|
||||||
import { Alert, Button, Card, Form } from 'react-bootstrap'
|
import { Alert, Button, Card, Form } from 'react-bootstrap'
|
||||||
|
|
||||||
import { Trans, useTranslation } from 'react-i18next'
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
import { useSelector } from 'react-redux'
|
|
||||||
import { doLdapLogin } from '../../../api/auth'
|
import { doLdapLogin } from '../../../api/auth'
|
||||||
import { ApplicationState } from '../../../redux'
|
|
||||||
import { fetchAndSetUser } from './utils'
|
import { fetchAndSetUser } from './utils'
|
||||||
|
import { useApplicationState } from '../../../hooks/common/use-application-state'
|
||||||
|
|
||||||
export const ViaLdap: React.FC = () => {
|
export const ViaLdap: React.FC = () => {
|
||||||
const { t } = useTranslation()
|
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 [username, setUsername] = useState('')
|
||||||
const [password, setPassword] = useState('')
|
const [password, setPassword] = useState('')
|
||||||
|
|
|
@ -5,8 +5,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { useSelector } from 'react-redux'
|
import { useApplicationState } from '../../../hooks/common/use-application-state'
|
||||||
import { ApplicationState } from '../../../redux'
|
|
||||||
import { IconName } from '../../common/fork-awesome/types'
|
import { IconName } from '../../common/fork-awesome/types'
|
||||||
import { SocialLinkButton } from './social-link-button/social-link-button'
|
import { SocialLinkButton } from './social-link-button/social-link-button'
|
||||||
|
|
||||||
|
@ -108,7 +107,7 @@ export interface ViaOneClickProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ViaOneClick: React.FC<ViaOneClickProps> = ({ oneClickType, optionalName }) => {
|
export const ViaOneClick: React.FC<ViaOneClickProps> = ({ 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 { name, icon, className, url } = getMetadata(backendUrl, oneClickType)
|
||||||
const text = optionalName || name
|
const text = optionalName || name
|
||||||
|
|
||||||
|
|
|
@ -4,25 +4,23 @@
|
||||||
SPDX-License-Identifier: AGPL-3.0-only
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import equal from 'fast-deep-equal'
|
|
||||||
import React, { Fragment } from 'react'
|
import React, { Fragment } from 'react'
|
||||||
import { Card, Col, Row } from 'react-bootstrap'
|
import { Card, Col, Row } from 'react-bootstrap'
|
||||||
import { Trans, useTranslation } from 'react-i18next'
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
import { useSelector } from 'react-redux'
|
|
||||||
import { Redirect } from 'react-router'
|
import { Redirect } from 'react-router'
|
||||||
import { ApplicationState } from '../../redux'
|
|
||||||
import { ShowIf } from '../common/show-if/show-if'
|
import { ShowIf } from '../common/show-if/show-if'
|
||||||
import { ViaInternal } from './auth/via-internal'
|
import { ViaInternal } from './auth/via-internal'
|
||||||
import { ViaLdap } from './auth/via-ldap'
|
import { ViaLdap } from './auth/via-ldap'
|
||||||
import { OneClickType, ViaOneClick } from './auth/via-one-click'
|
import { OneClickType, ViaOneClick } from './auth/via-one-click'
|
||||||
import { ViaOpenId } from './auth/via-openid'
|
import { ViaOpenId } from './auth/via-openid'
|
||||||
|
import { useApplicationState } from '../../hooks/common/use-application-state'
|
||||||
|
|
||||||
export const LoginPage: React.FC = () => {
|
export const LoginPage: React.FC = () => {
|
||||||
useTranslation()
|
useTranslation()
|
||||||
const authProviders = useSelector((state: ApplicationState) => state.config.authProviders, equal)
|
const authProviders = useApplicationState((state) => state.config.authProviders)
|
||||||
const customSamlAuthName = useSelector((state: ApplicationState) => state.config.customAuthNames.saml)
|
const customSamlAuthName = useApplicationState((state) => state.config.customAuthNames.saml)
|
||||||
const customOauthAuthName = useSelector((state: ApplicationState) => state.config.customAuthNames.oauth2)
|
const customOauthAuthName = useApplicationState((state) => state.config.customAuthNames.oauth2)
|
||||||
const userLoggedIn = useSelector((state: ApplicationState) => !!state.user)
|
const userLoggedIn = useApplicationState((state) => !!state.user)
|
||||||
|
|
||||||
const oneClickProviders = [
|
const oneClickProviders = [
|
||||||
authProviders.dropbox,
|
authProviders.dropbox,
|
||||||
|
|
|
@ -7,15 +7,14 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Alert } from 'react-bootstrap'
|
import { Alert } from 'react-bootstrap'
|
||||||
import { Trans, useTranslation } from 'react-i18next'
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
import { useSelector } from 'react-redux'
|
import { useApplicationState } from '../../hooks/common/use-application-state'
|
||||||
import { ApplicationState } from '../../redux'
|
|
||||||
import { ShowIf } from '../common/show-if/show-if'
|
import { ShowIf } from '../common/show-if/show-if'
|
||||||
import { SimpleAlertProps } from '../common/simple-alert/simple-alert-props'
|
import { SimpleAlertProps } from '../common/simple-alert/simple-alert-props'
|
||||||
|
|
||||||
export const DocumentLengthLimitReachedAlert: React.FC<SimpleAlertProps> = ({ show }) => {
|
export const DocumentLengthLimitReachedAlert: React.FC<SimpleAlertProps> = ({ show }) => {
|
||||||
useTranslation()
|
useTranslation()
|
||||||
|
|
||||||
const maxLength = useSelector((state: ApplicationState) => state.config.maxDocumentLength)
|
const maxLength = useApplicationState((state) => state.config.maxDocumentLength)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ShowIf condition={show}>
|
<ShowIf condition={show}>
|
||||||
|
|
|
@ -4,12 +4,11 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useSelector } from 'react-redux'
|
|
||||||
import { ApplicationState } from '../../../redux'
|
|
||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
|
import { useApplicationState } from '../../../hooks/common/use-application-state'
|
||||||
|
|
||||||
export const useTrimmedContent = (content: string): [trimmedContent: string, contentExceedsLimit: boolean] => {
|
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 contentExceedsLimit = content.length > maxLength
|
||||||
|
|
||||||
const trimmedContent = useMemo(
|
const trimmedContent = useMemo(
|
||||||
|
|
|
@ -5,13 +5,12 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { useSelector } from 'react-redux'
|
|
||||||
import { getProxiedUrl } from '../../../../api/media'
|
import { getProxiedUrl } from '../../../../api/media'
|
||||||
import { ApplicationState } from '../../../../redux'
|
import { useApplicationState } from '../../../../hooks/common/use-application-state'
|
||||||
|
|
||||||
export const ProxyImageFrame: React.FC<React.ImgHTMLAttributes<HTMLImageElement>> = ({ src, title, alt, ...props }) => {
|
export const ProxyImageFrame: React.FC<React.ImgHTMLAttributes<HTMLImageElement>> = ({ src, title, alt, ...props }) => {
|
||||||
const [imageUrl, setImageUrl] = useState('')
|
const [imageUrl, setImageUrl] = useState('')
|
||||||
const imageProxyEnabled = useSelector((state: ApplicationState) => state.config.useImageProxy)
|
const imageProxyEnabled = useApplicationState((state) => state.config.useImageProxy)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!imageProxyEnabled || !src) {
|
if (!imageProxyEnabled || !src) {
|
||||||
|
|
|
@ -7,12 +7,10 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { UiNotificationToast } from './ui-notification-toast'
|
import { UiNotificationToast } from './ui-notification-toast'
|
||||||
import './notifications.scss'
|
import './notifications.scss'
|
||||||
import { useSelector } from 'react-redux'
|
import { useApplicationState } from '../../hooks/common/use-application-state'
|
||||||
import { ApplicationState } from '../../redux'
|
|
||||||
import equal from 'fast-deep-equal'
|
|
||||||
|
|
||||||
export const UiNotifications: React.FC = () => {
|
export const UiNotifications: React.FC = () => {
|
||||||
const notifications = useSelector((state: ApplicationState) => state.uiNotifications, equal)
|
const notifications = useApplicationState((state) => state.uiNotifications)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={'notifications-area'} aria-live='polite' aria-atomic='true'>
|
<div className={'notifications-area'} aria-live='polite' aria-atomic='true'>
|
||||||
|
|
|
@ -6,9 +6,8 @@
|
||||||
|
|
||||||
import React, { Fragment } from 'react'
|
import React, { Fragment } from 'react'
|
||||||
import { Col, Row } from 'react-bootstrap'
|
import { Col, Row } from 'react-bootstrap'
|
||||||
import { useSelector } from 'react-redux'
|
|
||||||
import { Redirect } from 'react-router'
|
import { Redirect } from 'react-router'
|
||||||
import { ApplicationState } from '../../redux'
|
import { useApplicationState } from '../../hooks/common/use-application-state'
|
||||||
import { LoginProvider } from '../../redux/user/types'
|
import { LoginProvider } from '../../redux/user/types'
|
||||||
import { ShowIf } from '../common/show-if/show-if'
|
import { ShowIf } from '../common/show-if/show-if'
|
||||||
import { ProfileAccessTokens } from './access-tokens/profile-access-tokens'
|
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'
|
import { ProfileDisplayName } from './settings/profile-display-name'
|
||||||
|
|
||||||
export const ProfilePage: React.FC = () => {
|
export const ProfilePage: React.FC = () => {
|
||||||
const userProvider = useSelector((state: ApplicationState) => state.user?.provider)
|
const userProvider = useApplicationState((state) => state.user?.provider)
|
||||||
|
|
||||||
if (!userProvider) {
|
if (!userProvider) {
|
||||||
return <Redirect to={'/login'} />
|
return <Redirect to={'/login'} />
|
||||||
|
|
|
@ -7,15 +7,14 @@
|
||||||
import React, { ChangeEvent, FormEvent, useEffect, useState } from 'react'
|
import React, { ChangeEvent, FormEvent, useEffect, useState } from 'react'
|
||||||
import { Alert, Button, Card, Form } from 'react-bootstrap'
|
import { Alert, Button, Card, Form } from 'react-bootstrap'
|
||||||
import { Trans, useTranslation } from 'react-i18next'
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
import { useSelector } from 'react-redux'
|
|
||||||
import { updateDisplayName } from '../../../api/me'
|
import { updateDisplayName } from '../../../api/me'
|
||||||
import { ApplicationState } from '../../../redux'
|
|
||||||
import { fetchAndSetUser } from '../../login-page/auth/utils'
|
import { fetchAndSetUser } from '../../login-page/auth/utils'
|
||||||
|
import { useApplicationState } from '../../../hooks/common/use-application-state'
|
||||||
|
|
||||||
export const ProfileDisplayName: React.FC = () => {
|
export const ProfileDisplayName: React.FC = () => {
|
||||||
const regexInvalidDisplayName = /^\s*$/
|
const regexInvalidDisplayName = /^\s*$/
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const userName = useSelector((state: ApplicationState) => state.user?.name)
|
const userName = useApplicationState((state) => state.user?.name)
|
||||||
const [submittable, setSubmittable] = useState(false)
|
const [submittable, setSubmittable] = useState(false)
|
||||||
const [error, setError] = useState(false)
|
const [error, setError] = useState(false)
|
||||||
const [displayName, setDisplayName] = useState('')
|
const [displayName, setDisplayName] = useState('')
|
||||||
|
|
|
@ -7,10 +7,9 @@
|
||||||
import React, { FormEvent, Fragment, useCallback, useEffect, useState } from 'react'
|
import React, { FormEvent, Fragment, useCallback, useEffect, useState } from 'react'
|
||||||
import { Alert, Button, Card, Col, Form, Row } from 'react-bootstrap'
|
import { Alert, Button, Card, Col, Form, Row } from 'react-bootstrap'
|
||||||
import { Trans, useTranslation } from 'react-i18next'
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
import { useSelector } from 'react-redux'
|
|
||||||
import { Redirect } from 'react-router'
|
import { Redirect } from 'react-router'
|
||||||
import { doInternalRegister } from '../../api/auth'
|
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 { TranslatedExternalLink } from '../common/links/translated-external-link'
|
||||||
import { ShowIf } from '../common/show-if/show-if'
|
import { ShowIf } from '../common/show-if/show-if'
|
||||||
import { fetchAndSetUser } from '../login-page/auth/utils'
|
import { fetchAndSetUser } from '../login-page/auth/utils'
|
||||||
|
@ -23,9 +22,9 @@ export enum RegisterError {
|
||||||
|
|
||||||
export const RegisterPage: React.FC = () => {
|
export const RegisterPage: React.FC = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const allowRegister = useSelector((state: ApplicationState) => state.config.allowRegister)
|
const allowRegister = useApplicationState((state) => state.config.allowRegister)
|
||||||
const specialUrls = useSelector((state: ApplicationState) => state.config.specialUrls)
|
const specialUrls = useApplicationState((state) => state.config.specialUrls)
|
||||||
const userExists = useSelector((state: ApplicationState) => !!state.user)
|
const userExists = useApplicationState((state) => !!state.user)
|
||||||
|
|
||||||
const [username, setUsername] = useState('')
|
const [username, setUsername] = useState('')
|
||||||
const [password, setPassword] = useState('')
|
const [password, setPassword] = useState('')
|
||||||
|
|
|
@ -14,10 +14,9 @@ import { ScrollProps } from '../editor-page/synced-scroll/scroll-props'
|
||||||
import { BasicMarkdownRenderer } from '../markdown-renderer/basic-markdown-renderer'
|
import { BasicMarkdownRenderer } from '../markdown-renderer/basic-markdown-renderer'
|
||||||
import { ImageClickHandler } from '../markdown-renderer/replace-components/image/image-replacer'
|
import { ImageClickHandler } from '../markdown-renderer/replace-components/image/image-replacer'
|
||||||
import './markdown-document.scss'
|
import './markdown-document.scss'
|
||||||
import { useSelector } from 'react-redux'
|
|
||||||
import { ApplicationState } from '../../redux'
|
|
||||||
import { WidthBasedTableOfContents } from './width-based-table-of-contents'
|
import { WidthBasedTableOfContents } from './width-based-table-of-contents'
|
||||||
import { ShowIf } from '../common/show-if/show-if'
|
import { ShowIf } from '../common/show-if/show-if'
|
||||||
|
import { useApplicationState } from '../../hooks/common/use-application-state'
|
||||||
|
|
||||||
export interface RendererProps extends ScrollProps {
|
export interface RendererProps extends ScrollProps {
|
||||||
onFirstHeadingChange?: (firstHeading: string | undefined) => void
|
onFirstHeadingChange?: (firstHeading: string | undefined) => void
|
||||||
|
@ -60,7 +59,7 @@ export const MarkdownDocument: React.FC<MarkdownDocumentProps> = ({
|
||||||
|
|
||||||
const [tocAst, setTocAst] = useState<TocAst>()
|
const [tocAst, setTocAst] = useState<TocAst>()
|
||||||
|
|
||||||
const useAlternativeBreaks = useSelector((state: ApplicationState) => state.noteDetails.frontmatter.breaks)
|
const useAlternativeBreaks = useApplicationState((state) => state.noteDetails.frontmatter.breaks)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!onHeightChange) {
|
if (!onHeightChange) {
|
||||||
|
|
|
@ -4,9 +4,8 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
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 { useApplyDarkMode } from '../../hooks/common/use-apply-dark-mode'
|
||||||
import { ApplicationState } from '../../redux'
|
|
||||||
import { setDarkMode } from '../../redux/dark-mode/methods'
|
import { setDarkMode } from '../../redux/dark-mode/methods'
|
||||||
import { setNoteFrontmatter } from '../../redux/note-details/methods'
|
import { setNoteFrontmatter } from '../../redux/note-details/methods'
|
||||||
import { NoteFrontmatter } from '../editor-page/note-frontmatter/note-frontmatter'
|
import { NoteFrontmatter } from '../editor-page/note-frontmatter/note-frontmatter'
|
||||||
|
@ -24,7 +23,7 @@ export const RenderPage: React.FC = () => {
|
||||||
const [scrollState, setScrollState] = useState<ScrollState>({ firstLineInView: 1, scrolledPercentage: 0 })
|
const [scrollState, setScrollState] = useState<ScrollState>({ firstLineInView: 1, scrolledPercentage: 0 })
|
||||||
const [baseConfiguration, setBaseConfiguration] = useState<BaseConfiguration | undefined>(undefined)
|
const [baseConfiguration, setBaseConfiguration] = useState<BaseConfiguration | undefined>(undefined)
|
||||||
|
|
||||||
const editorOrigin = useSelector((state: ApplicationState) => state.config.iframeCommunication.editorOrigin)
|
const editorOrigin = useApplicationState((state) => state.config.iframeCommunication.editorOrigin)
|
||||||
|
|
||||||
const iframeCommunicator = useMemo(() => {
|
const iframeCommunicator = useMemo(() => {
|
||||||
const newCommunicator = new IframeRendererToEditorCommunicator()
|
const newCommunicator = new IframeRendererToEditorCommunicator()
|
||||||
|
|
22
src/hooks/common/use-application-state.ts
Normal file
22
src/hooks/common/use-application-state.ts
Normal file
|
@ -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 = <TSelected>(
|
||||||
|
selector: (state: ApplicationState) => TSelected,
|
||||||
|
checkForEquality?: (a: TSelected, b: TSelected) => boolean
|
||||||
|
): TSelected => {
|
||||||
|
return useSelector<ApplicationState, TSelected>(selector, checkForEquality ? checkForEquality : equal)
|
||||||
|
}
|
|
@ -5,14 +5,13 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useSelector } from 'react-redux'
|
import { useApplicationState } from './use-application-state'
|
||||||
import { ApplicationState } from '../../redux'
|
|
||||||
import { useDocumentTitle } from './use-document-title'
|
import { useDocumentTitle } from './use-document-title'
|
||||||
|
|
||||||
export const useDocumentTitleWithNoteTitle = (): void => {
|
export const useDocumentTitleWithNoteTitle = (): void => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const untitledNote = t('editor.untitledNote')
|
const untitledNote = t('editor.untitledNote')
|
||||||
const noteTitle = useSelector((state: ApplicationState) => state.noteDetails.noteTitle)
|
const noteTitle = useApplicationState((state) => state.noteDetails.noteTitle)
|
||||||
|
|
||||||
useDocumentTitle(noteTitle === '' ? untitledNote : noteTitle)
|
useDocumentTitle(noteTitle === '' ? untitledNote : noteTitle)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,11 +5,10 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import { useSelector } from 'react-redux'
|
import { useApplicationState } from './use-application-state'
|
||||||
import { ApplicationState } from '../../redux'
|
|
||||||
|
|
||||||
export const useDocumentTitle = (title?: string): void => {
|
export const useDocumentTitle = (title?: string): void => {
|
||||||
const brandingName = useSelector((state: ApplicationState) => state.config.branding.name)
|
const brandingName = useApplicationState((state) => state.config.branding.name)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.title = `${title ? title + ' - ' : ''}HedgeDoc ${brandingName ? ` @ ${brandingName}` : ''}`
|
document.title = `${title ? title + ' - ' : ''}HedgeDoc ${brandingName ? ` @ ${brandingName}` : ''}`
|
||||||
|
|
|
@ -4,9 +4,8 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useSelector } from 'react-redux'
|
import { useApplicationState } from './use-application-state'
|
||||||
import { ApplicationState } from '../../redux'
|
|
||||||
|
|
||||||
export const useIsDarkModeActivated = (): boolean => {
|
export const useIsDarkModeActivated = (): boolean => {
|
||||||
return useSelector((state: ApplicationState) => state.darkMode.darkMode)
|
return useApplicationState((state) => state.darkMode.darkMode)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,8 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useSelector } from 'react-redux'
|
import { useApplicationState } from './use-application-state'
|
||||||
import { ApplicationState } from '../../redux'
|
|
||||||
|
|
||||||
export const useNoteMarkdownContent = (): string => {
|
export const useNoteMarkdownContent = (): string => {
|
||||||
return useSelector((state: ApplicationState) => state.noteDetails.markdownContent)
|
return useApplicationState((state) => state.noteDetails.markdownContent)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue