mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-20 18:25:21 -04:00
Add boolean state hook
Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
parent
311d37b16f
commit
50d2dee9d2
15 changed files with 114 additions and 118 deletions
|
@ -1,20 +1,20 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { Fragment, useCallback, useState } from 'react'
|
import React, { Fragment } from 'react'
|
||||||
import { Button } from 'react-bootstrap'
|
import { Button } from 'react-bootstrap'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { ForkAwesomeIcon } from '../../../common/fork-awesome/fork-awesome-icon'
|
import { ForkAwesomeIcon } from '../../../common/fork-awesome/fork-awesome-icon'
|
||||||
import { HelpModal } from './help-modal'
|
import { HelpModal } from './help-modal'
|
||||||
import { cypressId } from '../../../../utils/cypress-attribute'
|
import { cypressId } from '../../../../utils/cypress-attribute'
|
||||||
|
import { useBooleanState } from '../../../../hooks/common/use-boolean-state'
|
||||||
|
|
||||||
export const HelpButton: React.FC = () => {
|
export const HelpButton: React.FC = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [show, setShow] = useState(false)
|
const [modalVisibility, showModal, closeModal] = useBooleanState()
|
||||||
const onHide = useCallback(() => setShow(false), [])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
|
@ -24,10 +24,10 @@ export const HelpButton: React.FC = () => {
|
||||||
className='ml-2 text-secondary'
|
className='ml-2 text-secondary'
|
||||||
size='sm'
|
size='sm'
|
||||||
variant='outline-light'
|
variant='outline-light'
|
||||||
onClick={() => setShow(true)}>
|
onClick={showModal}>
|
||||||
<ForkAwesomeIcon icon='question-circle' />
|
<ForkAwesomeIcon icon='question-circle' />
|
||||||
</Button>
|
</Button>
|
||||||
<HelpModal show={show} onHide={onHide} />
|
<HelpModal show={modalVisibility} onHide={closeModal} />
|
||||||
</Fragment>
|
</Fragment>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,33 +1,33 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useCallback, useEffect, useRef, useState } from 'react'
|
import React, { useEffect, useRef } from 'react'
|
||||||
import { MaxLengthWarningModal } from './max-length-warning-modal'
|
import { MaxLengthWarningModal } from './max-length-warning-modal'
|
||||||
import { useNoteMarkdownContent } from '../../../../hooks/common/use-note-markdown-content'
|
import { useNoteMarkdownContent } from '../../../../hooks/common/use-note-markdown-content'
|
||||||
import { useApplicationState } from '../../../../hooks/common/use-application-state'
|
import { useApplicationState } from '../../../../hooks/common/use-application-state'
|
||||||
|
import { useBooleanState } from '../../../../hooks/common/use-boolean-state'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Watches the length of the document and shows a warning modal to the user if the document length exceeds the configured value.
|
* Watches the length of the document and shows a warning modal to the user if the document length exceeds the configured value.
|
||||||
*/
|
*/
|
||||||
export const MaxLengthWarning: React.FC = () => {
|
export const MaxLengthWarning: React.FC = () => {
|
||||||
const [showMaxLengthWarningModal, setShowMaxLengthWarningModal] = useState(false)
|
const [modalVisibility, showModal, closeModal] = useBooleanState()
|
||||||
const maxLengthWarningAlreadyShown = useRef(false)
|
const maxLengthWarningAlreadyShown = useRef(false)
|
||||||
const maxDocumentLength = useApplicationState((state) => state.config.maxDocumentLength)
|
const maxDocumentLength = useApplicationState((state) => state.config.maxDocumentLength)
|
||||||
const hideWarning = useCallback(() => setShowMaxLengthWarningModal(false), [])
|
|
||||||
const markdownContent = useNoteMarkdownContent()
|
const markdownContent = useNoteMarkdownContent()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (markdownContent.length > maxDocumentLength && !maxLengthWarningAlreadyShown.current) {
|
if (markdownContent.length > maxDocumentLength && !maxLengthWarningAlreadyShown.current) {
|
||||||
setShowMaxLengthWarningModal(true)
|
showModal()
|
||||||
maxLengthWarningAlreadyShown.current = true
|
maxLengthWarningAlreadyShown.current = true
|
||||||
}
|
}
|
||||||
if (markdownContent.length <= maxDocumentLength) {
|
if (markdownContent.length <= maxDocumentLength) {
|
||||||
maxLengthWarningAlreadyShown.current = false
|
maxLengthWarningAlreadyShown.current = false
|
||||||
}
|
}
|
||||||
}, [markdownContent, maxDocumentLength])
|
}, [markdownContent, maxDocumentLength, showModal])
|
||||||
|
|
||||||
return <MaxLengthWarningModal show={showMaxLengthWarningModal} onHide={hideWarning} />
|
return <MaxLengthWarningModal show={modalVisibility} onHide={closeModal} />
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
@ -12,30 +12,27 @@ import type {
|
||||||
} from '../../render-page/window-post-message-communicator/rendering-message'
|
} from '../../render-page/window-post-message-communicator/rendering-message'
|
||||||
import { CommunicationMessageType } from '../../render-page/window-post-message-communicator/rendering-message'
|
import { CommunicationMessageType } from '../../render-page/window-post-message-communicator/rendering-message'
|
||||||
import { useEditorReceiveHandler } from '../../render-page/window-post-message-communicator/hooks/use-editor-receive-handler'
|
import { useEditorReceiveHandler } from '../../render-page/window-post-message-communicator/hooks/use-editor-receive-handler'
|
||||||
|
import { useBooleanState } from '../../../hooks/common/use-boolean-state'
|
||||||
|
|
||||||
export const CommunicatorImageLightbox: React.FC = () => {
|
export const CommunicatorImageLightbox: React.FC = () => {
|
||||||
const [lightboxDetails, setLightboxDetails] = useState<ImageDetails | undefined>(undefined)
|
const [lightboxDetails, setLightboxDetails] = useState<ImageDetails | undefined>(undefined)
|
||||||
const [show, setShow] = useState<boolean>(false)
|
const [modalVisibility, showModal, closeModal] = useBooleanState()
|
||||||
|
|
||||||
useEditorReceiveHandler(
|
useEditorReceiveHandler(
|
||||||
CommunicationMessageType.IMAGE_CLICKED,
|
CommunicationMessageType.IMAGE_CLICKED,
|
||||||
useCallback(
|
useCallback(
|
||||||
(values: ImageClickedMessage) => {
|
(values: ImageClickedMessage) => {
|
||||||
setLightboxDetails?.(values.details)
|
setLightboxDetails?.(values.details)
|
||||||
setShow(true)
|
showModal()
|
||||||
},
|
},
|
||||||
[setLightboxDetails]
|
[showModal]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
const hideLightbox = useCallback(() => {
|
|
||||||
setShow(false)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ImageLightboxModal
|
<ImageLightboxModal
|
||||||
show={show}
|
show={modalVisibility}
|
||||||
onHide={hideLightbox}
|
onHide={closeModal}
|
||||||
src={lightboxDetails?.src}
|
src={lightboxDetails?.src}
|
||||||
alt={lightboxDetails?.alt}
|
alt={lightboxDetails?.alt}
|
||||||
title={lightboxDetails?.title}
|
title={lightboxDetails?.title}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { PropsWithChildren } from 'react'
|
import type { PropsWithChildren } from 'react'
|
||||||
import React, { Fragment, useCallback, useState } from 'react'
|
import React, { Fragment, useCallback } from 'react'
|
||||||
import { Trans, useTranslation } from 'react-i18next'
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
import { SidebarButton } from '../sidebar-button/sidebar-button'
|
import { SidebarButton } from '../sidebar-button/sidebar-button'
|
||||||
import type { SpecificSidebarEntryProps } from '../types'
|
import type { SpecificSidebarEntryProps } from '../types'
|
||||||
|
@ -14,7 +14,7 @@ import { cypressId } from '../../../../utils/cypress-attribute'
|
||||||
import { showErrorNotification } from '../../../../redux/ui-notifications/methods'
|
import { showErrorNotification } from '../../../../redux/ui-notifications/methods'
|
||||||
import { deleteNote } from '../../../../api/notes'
|
import { deleteNote } from '../../../../api/notes'
|
||||||
import { DeleteNoteModal } from './delete-note-modal'
|
import { DeleteNoteModal } from './delete-note-modal'
|
||||||
import { ShowIf } from '../../../common/show-if/show-if'
|
import { useBooleanState } from '../../../../hooks/common/use-boolean-state'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sidebar entry that can be used to delete the current note.
|
* Sidebar entry that can be used to delete the current note.
|
||||||
|
@ -24,15 +24,11 @@ import { ShowIf } from '../../../common/show-if/show-if'
|
||||||
*/
|
*/
|
||||||
export const DeleteNoteSidebarEntry: React.FC<PropsWithChildren<SpecificSidebarEntryProps>> = ({ hide, className }) => {
|
export const DeleteNoteSidebarEntry: React.FC<PropsWithChildren<SpecificSidebarEntryProps>> = ({ hide, className }) => {
|
||||||
useTranslation()
|
useTranslation()
|
||||||
const [showDialog, setShowDialog] = useState(false)
|
|
||||||
const noteId = useApplicationState((state) => state.noteDetails.id)
|
const noteId = useApplicationState((state) => state.noteDetails.id)
|
||||||
const openDialog = useCallback(() => setShowDialog(true), [])
|
const [modalVisibility, showModal, closeModal] = useBooleanState()
|
||||||
const closeDialog = useCallback(() => setShowDialog(false), [])
|
|
||||||
const deleteNoteAndCloseDialog = useCallback(() => {
|
const deleteNoteAndCloseDialog = useCallback(() => {
|
||||||
deleteNote(noteId)
|
deleteNote(noteId).catch(showErrorNotification('landing.history.error.deleteNote.text')).finally(closeModal)
|
||||||
.catch(showErrorNotification('landing.history.error.deleteNote.text'))
|
}, [closeModal, noteId])
|
||||||
.finally(() => setShowDialog(false))
|
|
||||||
}, [noteId])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
|
@ -41,12 +37,10 @@ export const DeleteNoteSidebarEntry: React.FC<PropsWithChildren<SpecificSidebarE
|
||||||
icon={'trash'}
|
icon={'trash'}
|
||||||
className={className}
|
className={className}
|
||||||
hide={hide}
|
hide={hide}
|
||||||
onClick={openDialog}>
|
onClick={showModal}>
|
||||||
<Trans i18nKey={'landing.history.menu.deleteNote'} />
|
<Trans i18nKey={'landing.history.menu.deleteNote'} />
|
||||||
</SidebarButton>
|
</SidebarButton>
|
||||||
<ShowIf condition={showDialog}>
|
<DeleteNoteModal onHide={closeModal} onConfirm={deleteNoteAndCloseDialog} show={modalVisibility} />
|
||||||
<DeleteNoteModal onHide={closeDialog} onConfirm={deleteNoteAndCloseDialog} show={showDialog} />
|
|
||||||
</ShowIf>
|
|
||||||
</Fragment>
|
</Fragment>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,19 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { Fragment, useState } from 'react'
|
import React, { Fragment } from 'react'
|
||||||
import { Trans, useTranslation } from 'react-i18next'
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
import { NoteInfoModal } from '../../document-bar/note-info/note-info-modal'
|
import { NoteInfoModal } from '../../document-bar/note-info/note-info-modal'
|
||||||
import { SidebarButton } from '../sidebar-button/sidebar-button'
|
import { SidebarButton } from '../sidebar-button/sidebar-button'
|
||||||
import type { SpecificSidebarEntryProps } from '../types'
|
import type { SpecificSidebarEntryProps } from '../types'
|
||||||
import { cypressId } from '../../../../utils/cypress-attribute'
|
import { cypressId } from '../../../../utils/cypress-attribute'
|
||||||
|
import { useBooleanState } from '../../../../hooks/common/use-boolean-state'
|
||||||
|
|
||||||
export const NoteInfoSidebarEntry: React.FC<SpecificSidebarEntryProps> = ({ className, hide }) => {
|
export const NoteInfoSidebarEntry: React.FC<SpecificSidebarEntryProps> = ({ className, hide }) => {
|
||||||
const [showModal, setShowModal] = useState(false)
|
const [modalVisibility, showModal, closeModal] = useBooleanState()
|
||||||
useTranslation()
|
useTranslation()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -21,11 +22,11 @@ export const NoteInfoSidebarEntry: React.FC<SpecificSidebarEntryProps> = ({ clas
|
||||||
hide={hide}
|
hide={hide}
|
||||||
className={className}
|
className={className}
|
||||||
icon={'line-chart'}
|
icon={'line-chart'}
|
||||||
onClick={() => setShowModal(true)}
|
onClick={showModal}
|
||||||
{...cypressId('sidebar-btn-document-info')}>
|
{...cypressId('sidebar-btn-document-info')}>
|
||||||
<Trans i18nKey={'editor.modal.documentInfo.title'} />
|
<Trans i18nKey={'editor.modal.documentInfo.title'} />
|
||||||
</SidebarButton>
|
</SidebarButton>
|
||||||
<NoteInfoModal show={showModal} onHide={() => setShowModal(false)} />
|
<NoteInfoModal show={modalVisibility} onHide={closeModal} />
|
||||||
</Fragment>
|
</Fragment>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,25 +1,26 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { Fragment, useState } from 'react'
|
import React, { Fragment } from 'react'
|
||||||
import { Trans, useTranslation } from 'react-i18next'
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
import { PermissionModal } from '../../document-bar/permissions/permission-modal'
|
import { PermissionModal } from '../../document-bar/permissions/permission-modal'
|
||||||
import { SidebarButton } from '../sidebar-button/sidebar-button'
|
import { SidebarButton } from '../sidebar-button/sidebar-button'
|
||||||
import type { SpecificSidebarEntryProps } from '../types'
|
import type { SpecificSidebarEntryProps } from '../types'
|
||||||
|
import { useBooleanState } from '../../../../hooks/common/use-boolean-state'
|
||||||
|
|
||||||
export const PermissionsSidebarEntry: React.FC<SpecificSidebarEntryProps> = ({ className, hide }) => {
|
export const PermissionsSidebarEntry: React.FC<SpecificSidebarEntryProps> = ({ className, hide }) => {
|
||||||
const [showModal, setShowModal] = useState(false)
|
const [modalVisibility, showModal, closeModal] = useBooleanState()
|
||||||
useTranslation()
|
useTranslation()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<SidebarButton hide={hide} className={className} icon={'lock'} onClick={() => setShowModal(true)}>
|
<SidebarButton hide={hide} className={className} icon={'lock'} onClick={showModal}>
|
||||||
<Trans i18nKey={'editor.modal.permissions.title'} />
|
<Trans i18nKey={'editor.modal.permissions.title'} />
|
||||||
</SidebarButton>
|
</SidebarButton>
|
||||||
<PermissionModal show={showModal} onHide={() => setShowModal(false)} />
|
<PermissionModal show={modalVisibility} onHide={closeModal} />
|
||||||
</Fragment>
|
</Fragment>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,30 +1,25 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { Fragment, useCallback, useState } from 'react'
|
import React, { Fragment } from 'react'
|
||||||
import { Trans } from 'react-i18next'
|
import { Trans } from 'react-i18next'
|
||||||
import { RevisionModal } from '../../document-bar/revisions/revision-modal'
|
import { RevisionModal } from '../../document-bar/revisions/revision-modal'
|
||||||
import { SidebarButton } from '../sidebar-button/sidebar-button'
|
import { SidebarButton } from '../sidebar-button/sidebar-button'
|
||||||
import type { SpecificSidebarEntryProps } from '../types'
|
import type { SpecificSidebarEntryProps } from '../types'
|
||||||
|
import { useBooleanState } from '../../../../hooks/common/use-boolean-state'
|
||||||
|
|
||||||
export const RevisionSidebarEntry: React.FC<SpecificSidebarEntryProps> = ({ className, hide }) => {
|
export const RevisionSidebarEntry: React.FC<SpecificSidebarEntryProps> = ({ className, hide }) => {
|
||||||
const [showModal, setShowModal] = useState(false)
|
const [modalVisibility, showModal, closeModal] = useBooleanState()
|
||||||
const onHide = useCallback(() => {
|
|
||||||
setShowModal(false)
|
|
||||||
}, [])
|
|
||||||
const onShow = useCallback(() => {
|
|
||||||
setShowModal(true)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<SidebarButton hide={hide} className={className} icon={'history'} onClick={onShow}>
|
<SidebarButton hide={hide} className={className} icon={'history'} onClick={showModal}>
|
||||||
<Trans i18nKey={'editor.modal.revision.title'} />
|
<Trans i18nKey={'editor.modal.revision.title'} />
|
||||||
</SidebarButton>
|
</SidebarButton>
|
||||||
<RevisionModal show={showModal} onHide={onHide} />
|
<RevisionModal show={modalVisibility} onHide={closeModal} />
|
||||||
</Fragment>
|
</Fragment>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,25 +1,26 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { Fragment, useState } from 'react'
|
import React, { Fragment } from 'react'
|
||||||
import { Trans, useTranslation } from 'react-i18next'
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
import { ShareModal } from '../../document-bar/share/share-modal'
|
import { ShareModal } from '../../document-bar/share/share-modal'
|
||||||
import { SidebarButton } from '../sidebar-button/sidebar-button'
|
import { SidebarButton } from '../sidebar-button/sidebar-button'
|
||||||
import type { SpecificSidebarEntryProps } from '../types'
|
import type { SpecificSidebarEntryProps } from '../types'
|
||||||
|
import { useBooleanState } from '../../../../hooks/common/use-boolean-state'
|
||||||
|
|
||||||
export const ShareSidebarEntry: React.FC<SpecificSidebarEntryProps> = ({ className, hide }) => {
|
export const ShareSidebarEntry: React.FC<SpecificSidebarEntryProps> = ({ className, hide }) => {
|
||||||
const [showModal, setShowModal] = useState(false)
|
const [modalVisibility, showModal, closeModal] = useBooleanState()
|
||||||
useTranslation()
|
useTranslation()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<SidebarButton hide={hide} className={className} icon={'share'} onClick={() => setShowModal(true)}>
|
<SidebarButton hide={hide} className={className} icon={'share'} onClick={showModal}>
|
||||||
<Trans i18nKey={'editor.modal.shareLink.title'} />
|
<Trans i18nKey={'editor.modal.shareLink.title'} />
|
||||||
</SidebarButton>
|
</SidebarButton>
|
||||||
<ShareModal show={showModal} onHide={() => setShowModal(false)} />
|
<ShareModal show={modalVisibility} onHide={closeModal} />
|
||||||
</Fragment>
|
</Fragment>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,17 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { Fragment, useCallback, useState } from 'react'
|
import React, { Fragment, useCallback } 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 { ForkAwesomeIcon } from '../../common/fork-awesome/fork-awesome-icon'
|
import { ForkAwesomeIcon } from '../../common/fork-awesome/fork-awesome-icon'
|
||||||
import type { IconName } from '../../common/fork-awesome/types'
|
import type { IconName } from '../../common/fork-awesome/types'
|
||||||
import type { DeleteHistoryNoteModalProps } from '../../editor-page/sidebar/delete-note-sidebar-entry/delete-note-modal'
|
import type { DeleteHistoryNoteModalProps } from '../../editor-page/sidebar/delete-note-sidebar-entry/delete-note-modal'
|
||||||
import { DeleteNoteModal } from '../../editor-page/sidebar/delete-note-sidebar-entry/delete-note-modal'
|
import { DeleteNoteModal } from '../../editor-page/sidebar/delete-note-sidebar-entry/delete-note-modal'
|
||||||
|
import { useBooleanState } from '../../../hooks/common/use-boolean-state'
|
||||||
|
|
||||||
export interface DropdownItemWithDeletionModalProps {
|
export interface DropdownItemWithDeletionModalProps {
|
||||||
onConfirm: () => void
|
onConfirm: () => void
|
||||||
|
@ -47,24 +48,24 @@ export const DropdownItemWithDeletionModal: React.FC<
|
||||||
className
|
className
|
||||||
}) => {
|
}) => {
|
||||||
useTranslation()
|
useTranslation()
|
||||||
const [showDialog, setShowDialog] = useState(false)
|
const [modalVisibility, showModal, closeModal] = useBooleanState()
|
||||||
|
|
||||||
const handleConfirm = useCallback(() => {
|
const handleConfirm = useCallback(() => {
|
||||||
setShowDialog(false)
|
closeModal()
|
||||||
onConfirm()
|
onConfirm()
|
||||||
}, [onConfirm])
|
}, [closeModal, onConfirm])
|
||||||
const onHide = useCallback(() => setShowDialog(false), [])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<Dropdown.Item onClick={() => setShowDialog(true)} className={className}>
|
<Dropdown.Item onClick={showModal} className={className}>
|
||||||
<ForkAwesomeIcon icon={modalIcon} fixedWidth={true} className='mx-2' />
|
<ForkAwesomeIcon icon={modalIcon} fixedWidth={true} className='mx-2' />
|
||||||
<Trans i18nKey={itemI18nKey} />
|
<Trans i18nKey={itemI18nKey} />
|
||||||
</Dropdown.Item>
|
</Dropdown.Item>
|
||||||
<DeleteNoteModal
|
<DeleteNoteModal
|
||||||
optionalNoteTitle={noteTitle}
|
optionalNoteTitle={noteTitle}
|
||||||
onConfirm={handleConfirm}
|
onConfirm={handleConfirm}
|
||||||
show={showDialog}
|
show={modalVisibility}
|
||||||
onHide={onHide}
|
onHide={closeModal}
|
||||||
modalTitleI18nKey={modalTitleI18nKey}
|
modalTitleI18nKey={modalTitleI18nKey}
|
||||||
modalButtonI18nKey={modalButtonI18nKey}
|
modalButtonI18nKey={modalButtonI18nKey}
|
||||||
modalQuestionI18nKey={modalQuestionI18nKey}
|
modalQuestionI18nKey={modalQuestionI18nKey}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { Fragment, useCallback, useState } from 'react'
|
import React, { Fragment, useCallback } 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 { ForkAwesomeIcon } from '../../common/fork-awesome/fork-awesome-icon'
|
import { ForkAwesomeIcon } from '../../common/fork-awesome/fork-awesome-icon'
|
||||||
|
@ -12,36 +12,34 @@ import { DeletionModal } from '../../common/modals/deletion-modal'
|
||||||
import { deleteAllHistoryEntries, safeRefreshHistoryState } from '../../../redux/history/methods'
|
import { deleteAllHistoryEntries, safeRefreshHistoryState } from '../../../redux/history/methods'
|
||||||
import { showErrorNotification } from '../../../redux/ui-notifications/methods'
|
import { showErrorNotification } from '../../../redux/ui-notifications/methods'
|
||||||
import { cypressId } from '../../../utils/cypress-attribute'
|
import { cypressId } from '../../../utils/cypress-attribute'
|
||||||
|
import { useBooleanState } from '../../../hooks/common/use-boolean-state'
|
||||||
|
|
||||||
export const ClearHistoryButton: React.FC = () => {
|
export const ClearHistoryButton: React.FC = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [show, setShow] = useState(false)
|
const [modalVisibility, showModal, closeModal] = useBooleanState()
|
||||||
|
|
||||||
const handleShow = () => setShow(true)
|
|
||||||
const handleClose = () => setShow(false)
|
|
||||||
|
|
||||||
const onConfirm = useCallback(() => {
|
const onConfirm = useCallback(() => {
|
||||||
deleteAllHistoryEntries().catch((error: Error) => {
|
deleteAllHistoryEntries().catch((error: Error) => {
|
||||||
showErrorNotification('landing.history.error.deleteEntry.text')(error)
|
showErrorNotification('landing.history.error.deleteEntry.text')(error)
|
||||||
safeRefreshHistoryState()
|
safeRefreshHistoryState()
|
||||||
})
|
})
|
||||||
handleClose()
|
closeModal()
|
||||||
}, [])
|
}, [closeModal])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<Button
|
<Button
|
||||||
variant={'light'}
|
variant={'light'}
|
||||||
title={t('landing.history.toolbar.clear')}
|
title={t('landing.history.toolbar.clear')}
|
||||||
onClick={handleShow}
|
onClick={showModal}
|
||||||
{...cypressId('history-clear-button')}>
|
{...cypressId('history-clear-button')}>
|
||||||
<ForkAwesomeIcon icon={'trash'} />
|
<ForkAwesomeIcon icon={'trash'} />
|
||||||
</Button>
|
</Button>
|
||||||
<DeletionModal
|
<DeletionModal
|
||||||
onConfirm={onConfirm}
|
onConfirm={onConfirm}
|
||||||
deletionButtonI18nKey={'landing.history.toolbar.clear'}
|
deletionButtonI18nKey={'landing.history.toolbar.clear'}
|
||||||
show={show}
|
show={modalVisibility}
|
||||||
onHide={handleClose}
|
onHide={closeModal}
|
||||||
title={'landing.history.modal.clearHistory.title'}>
|
title={'landing.history.modal.clearHistory.title'}>
|
||||||
<h5>
|
<h5>
|
||||||
<Trans i18nKey={'landing.history.modal.clearHistory.question'} />
|
<Trans i18nKey={'landing.history.modal.clearHistory.question'} />
|
||||||
|
|
|
@ -1,25 +1,24 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { Fragment, useCallback, useState } from 'react'
|
import React, { Fragment } from 'react'
|
||||||
import { Trans } from 'react-i18next'
|
import { Trans } from 'react-i18next'
|
||||||
import { VersionInfoModal } from './version-info-modal'
|
import { VersionInfoModal } from './version-info-modal'
|
||||||
import { cypressId } from '../../../../utils/cypress-attribute'
|
import { cypressId } from '../../../../utils/cypress-attribute'
|
||||||
|
import { useBooleanState } from '../../../../hooks/common/use-boolean-state'
|
||||||
|
|
||||||
export const VersionInfoLink: React.FC = () => {
|
export const VersionInfoLink: React.FC = () => {
|
||||||
const [show, setShow] = useState(false)
|
const [modalVisibility, showModal, closeModal] = useBooleanState()
|
||||||
const closeModal = useCallback(() => setShow(false), [])
|
|
||||||
const showModal = useCallback(() => setShow(true), [])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<a {...cypressId('show-version-modal')} href={'#'} className={'text-light'} onClick={showModal}>
|
<a {...cypressId('show-version-modal')} href={'#'} className={'text-light'} onClick={showModal}>
|
||||||
<Trans i18nKey={'landing.versionInfo.versionInfo'} />
|
<Trans i18nKey={'landing.versionInfo.versionInfo'} />
|
||||||
</a>
|
</a>
|
||||||
<VersionInfoModal onHide={closeModal} show={show} />
|
<VersionInfoModal onHide={closeModal} show={modalVisibility} />
|
||||||
</Fragment>
|
</Fragment>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,11 +37,7 @@ export const AccessTokenDeletionModal: React.FC<AccessTokenDeletionModalProps> =
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.catch(showErrorNotification('profile.modal.deleteAccessToken.failed'))
|
.catch(showErrorNotification('profile.modal.deleteAccessToken.failed'))
|
||||||
.finally(() => {
|
.finally(() => onHide?.())
|
||||||
if (onHide) {
|
|
||||||
onHide()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}, [token, onHide])
|
}, [token, onHide])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useCallback, useMemo, useState } from 'react'
|
import React, { useCallback, useMemo } from 'react'
|
||||||
import { Col, ListGroup, Row } from 'react-bootstrap'
|
import { Col, ListGroup, Row } from 'react-bootstrap'
|
||||||
import { cypressId } from '../../../utils/cypress-attribute'
|
import { cypressId } from '../../../utils/cypress-attribute'
|
||||||
import { Trans, useTranslation } from 'react-i18next'
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
|
@ -13,6 +13,7 @@ import { IconButton } from '../../common/icon-button/icon-button'
|
||||||
import type { AccessToken } from '../../../api/tokens/types'
|
import type { AccessToken } from '../../../api/tokens/types'
|
||||||
import { AccessTokenDeletionModal } from './access-token-deletion-modal'
|
import { AccessTokenDeletionModal } from './access-token-deletion-modal'
|
||||||
import type { AccessTokenUpdateProps } from './profile-access-tokens'
|
import type { AccessTokenUpdateProps } from './profile-access-tokens'
|
||||||
|
import { useBooleanState } from '../../../hooks/common/use-boolean-state'
|
||||||
|
|
||||||
export interface AccessTokenListEntryProps {
|
export interface AccessTokenListEntryProps {
|
||||||
token: AccessToken
|
token: AccessToken
|
||||||
|
@ -28,16 +29,12 @@ export const AccessTokenListEntry: React.FC<AccessTokenListEntryProps & AccessTo
|
||||||
onUpdateList
|
onUpdateList
|
||||||
}) => {
|
}) => {
|
||||||
useTranslation()
|
useTranslation()
|
||||||
const [showDeletionModal, setShowDeletionModal] = useState(false)
|
const [modalVisibility, showModal, closeModal] = useBooleanState()
|
||||||
|
|
||||||
const onShowDeletionModal = useCallback(() => {
|
|
||||||
setShowDeletionModal(true)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const onHideDeletionModal = useCallback(() => {
|
const onHideDeletionModal = useCallback(() => {
|
||||||
setShowDeletionModal(false)
|
closeModal()
|
||||||
onUpdateList()
|
onUpdateList()
|
||||||
}, [onUpdateList])
|
}, [closeModal, onUpdateList])
|
||||||
|
|
||||||
const lastUsed = useMemo(() => {
|
const lastUsed = useMemo(() => {
|
||||||
if (!token.lastUsedAt) {
|
if (!token.lastUsedAt) {
|
||||||
|
@ -66,12 +63,12 @@ export const AccessTokenListEntry: React.FC<AccessTokenListEntryProps & AccessTo
|
||||||
<IconButton
|
<IconButton
|
||||||
icon='trash-o'
|
icon='trash-o'
|
||||||
variant='danger'
|
variant='danger'
|
||||||
onClick={onShowDeletionModal}
|
onClick={showModal}
|
||||||
{...cypressId('access-token-delete-button')}
|
{...cypressId('access-token-delete-button')}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<AccessTokenDeletionModal token={token} show={showDeletionModal} onHide={onHideDeletionModal} />
|
<AccessTokenDeletionModal token={token} show={modalVisibility} onHide={onHideDeletionModal} />
|
||||||
</ListGroup.Item>
|
</ListGroup.Item>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,30 +1,23 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { Fragment, useCallback, useState } from 'react'
|
import React, { Fragment } from 'react'
|
||||||
import { Button, Card } from 'react-bootstrap'
|
import { Button, Card } from 'react-bootstrap'
|
||||||
import { Trans, useTranslation } from 'react-i18next'
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
import { ForkAwesomeIcon } from '../../common/fork-awesome/fork-awesome-icon'
|
import { ForkAwesomeIcon } from '../../common/fork-awesome/fork-awesome-icon'
|
||||||
import { AccountDeletionModal } from './account-deletion-modal'
|
import { AccountDeletionModal } from './account-deletion-modal'
|
||||||
import { apiUrl } from '../../../utils/api-url'
|
import { apiUrl } from '../../../utils/api-url'
|
||||||
|
import { useBooleanState } from '../../../hooks/common/use-boolean-state'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Profile page section that allows to export all data from the account or to delete the account.
|
* Profile page section that allows to export all data from the account or to delete the account.
|
||||||
*/
|
*/
|
||||||
export const ProfileAccountManagement: React.FC = () => {
|
export const ProfileAccountManagement: React.FC = () => {
|
||||||
useTranslation()
|
useTranslation()
|
||||||
const [showDeleteModal, setShowDeleteModal] = useState(false)
|
const [modalVisibility, showModal, closeModal] = useBooleanState()
|
||||||
|
|
||||||
const onShowDeletionModal = useCallback(() => {
|
|
||||||
setShowDeleteModal(true)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const onHideDeletionModal = useCallback(() => {
|
|
||||||
setShowDeleteModal(false)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
|
@ -37,13 +30,13 @@ export const ProfileAccountManagement: React.FC = () => {
|
||||||
<ForkAwesomeIcon icon='cloud-download' fixedWidth={true} className='mx-2' />
|
<ForkAwesomeIcon icon='cloud-download' fixedWidth={true} className='mx-2' />
|
||||||
<Trans i18nKey='profile.exportUserData' />
|
<Trans i18nKey='profile.exportUserData' />
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant='danger' block onClick={onShowDeletionModal}>
|
<Button variant='danger' block onClick={showModal}>
|
||||||
<ForkAwesomeIcon icon='trash' fixedWidth={true} className='mx-2' />
|
<ForkAwesomeIcon icon='trash' fixedWidth={true} className='mx-2' />
|
||||||
<Trans i18nKey='profile.deleteUser' />
|
<Trans i18nKey='profile.deleteUser' />
|
||||||
</Button>
|
</Button>
|
||||||
</Card.Body>
|
</Card.Body>
|
||||||
</Card>
|
</Card>
|
||||||
<AccountDeletionModal show={showDeleteModal} onHide={onHideDeletionModal} />
|
<AccountDeletionModal show={modalVisibility} onHide={closeModal} />
|
||||||
</Fragment>
|
</Fragment>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
23
src/hooks/common/use-boolean-state.ts
Normal file
23
src/hooks/common/use-boolean-state.ts
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useCallback, useState } from 'react'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a boolean state and two functions that set the boolean to true or false.
|
||||||
|
*
|
||||||
|
* @param initialState The initial value of the state
|
||||||
|
* @return An array containing the state, and two functions that set the state value to true or false.
|
||||||
|
*/
|
||||||
|
export const useBooleanState = (
|
||||||
|
initialState: boolean | (() => boolean) = false
|
||||||
|
): [state: boolean, setToTrue: () => void, setToFalse: () => void] => {
|
||||||
|
const [state, setState] = useState(initialState)
|
||||||
|
const setToFalse = useCallback(() => setState(false), [])
|
||||||
|
const setToTrue = useCallback(() => setState(true), [])
|
||||||
|
|
||||||
|
return [state, setToTrue, setToFalse]
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue