diff --git a/src/components/editor-page/app-bar/help-button/help-button.tsx b/src/components/editor-page/app-bar/help-button/help-button.tsx
index e72b92699..734f040ff 100644
--- a/src/components/editor-page/app-bar/help-button/help-button.tsx
+++ b/src/components/editor-page/app-bar/help-button/help-button.tsx
@@ -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
*/
-import React, { Fragment, useCallback, useState } from 'react'
+import React, { Fragment } from 'react'
import { Button } from 'react-bootstrap'
import { useTranslation } from 'react-i18next'
import { ForkAwesomeIcon } from '../../../common/fork-awesome/fork-awesome-icon'
import { HelpModal } from './help-modal'
import { cypressId } from '../../../../utils/cypress-attribute'
+import { useBooleanState } from '../../../../hooks/common/use-boolean-state'
export const HelpButton: React.FC = () => {
const { t } = useTranslation()
- const [show, setShow] = useState(false)
- const onHide = useCallback(() => setShow(false), [])
+ const [modalVisibility, showModal, closeModal] = useBooleanState()
return (
@@ -24,10 +24,10 @@ export const HelpButton: React.FC = () => {
className='ml-2 text-secondary'
size='sm'
variant='outline-light'
- onClick={() => setShow(true)}>
+ onClick={showModal}>
-
+
)
}
diff --git a/src/components/editor-page/editor-pane/max-length-warning/max-length-warning.tsx b/src/components/editor-page/editor-pane/max-length-warning/max-length-warning.tsx
index a13cdf436..f3d4437b0 100644
--- a/src/components/editor-page/editor-pane/max-length-warning/max-length-warning.tsx
+++ b/src/components/editor-page/editor-pane/max-length-warning/max-length-warning.tsx
@@ -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
*/
-import React, { useCallback, useEffect, useRef, useState } from 'react'
+import React, { useEffect, useRef } from 'react'
import { MaxLengthWarningModal } from './max-length-warning-modal'
import { useNoteMarkdownContent } from '../../../../hooks/common/use-note-markdown-content'
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.
*/
export const MaxLengthWarning: React.FC = () => {
- const [showMaxLengthWarningModal, setShowMaxLengthWarningModal] = useState(false)
+ const [modalVisibility, showModal, closeModal] = useBooleanState()
const maxLengthWarningAlreadyShown = useRef(false)
const maxDocumentLength = useApplicationState((state) => state.config.maxDocumentLength)
- const hideWarning = useCallback(() => setShowMaxLengthWarningModal(false), [])
const markdownContent = useNoteMarkdownContent()
useEffect(() => {
if (markdownContent.length > maxDocumentLength && !maxLengthWarningAlreadyShown.current) {
- setShowMaxLengthWarningModal(true)
+ showModal()
maxLengthWarningAlreadyShown.current = true
}
if (markdownContent.length <= maxDocumentLength) {
maxLengthWarningAlreadyShown.current = false
}
- }, [markdownContent, maxDocumentLength])
+ }, [markdownContent, maxDocumentLength, showModal])
- return
+ return
}
diff --git a/src/components/editor-page/renderer-pane/communicator-image-lightbox.tsx b/src/components/editor-page/renderer-pane/communicator-image-lightbox.tsx
index d0ec8a2e3..a0a082e26 100644
--- a/src/components/editor-page/renderer-pane/communicator-image-lightbox.tsx
+++ b/src/components/editor-page/renderer-pane/communicator-image-lightbox.tsx
@@ -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
*/
@@ -12,30 +12,27 @@ import type {
} 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 { useBooleanState } from '../../../hooks/common/use-boolean-state'
export const CommunicatorImageLightbox: React.FC = () => {
const [lightboxDetails, setLightboxDetails] = useState(undefined)
- const [show, setShow] = useState(false)
+ const [modalVisibility, showModal, closeModal] = useBooleanState()
useEditorReceiveHandler(
CommunicationMessageType.IMAGE_CLICKED,
useCallback(
(values: ImageClickedMessage) => {
setLightboxDetails?.(values.details)
- setShow(true)
+ showModal()
},
- [setLightboxDetails]
+ [showModal]
)
)
- const hideLightbox = useCallback(() => {
- setShow(false)
- }, [])
-
return (
> = ({ hide, className }) => {
useTranslation()
- const [showDialog, setShowDialog] = useState(false)
const noteId = useApplicationState((state) => state.noteDetails.id)
- const openDialog = useCallback(() => setShowDialog(true), [])
- const closeDialog = useCallback(() => setShowDialog(false), [])
+ const [modalVisibility, showModal, closeModal] = useBooleanState()
const deleteNoteAndCloseDialog = useCallback(() => {
- deleteNote(noteId)
- .catch(showErrorNotification('landing.history.error.deleteNote.text'))
- .finally(() => setShowDialog(false))
- }, [noteId])
+ deleteNote(noteId).catch(showErrorNotification('landing.history.error.deleteNote.text')).finally(closeModal)
+ }, [closeModal, noteId])
return (
@@ -41,12 +37,10 @@ export const DeleteNoteSidebarEntry: React.FC
+ onClick={showModal}>
-
-
-
+
)
}
diff --git a/src/components/editor-page/sidebar/specific-sidebar-entries/note-info-sidebar-entry.tsx b/src/components/editor-page/sidebar/specific-sidebar-entries/note-info-sidebar-entry.tsx
index c42005cf5..5f55faa8c 100644
--- a/src/components/editor-page/sidebar/specific-sidebar-entries/note-info-sidebar-entry.tsx
+++ b/src/components/editor-page/sidebar/specific-sidebar-entries/note-info-sidebar-entry.tsx
@@ -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
*/
-import React, { Fragment, useState } from 'react'
+import React, { Fragment } from 'react'
import { Trans, useTranslation } from 'react-i18next'
import { NoteInfoModal } from '../../document-bar/note-info/note-info-modal'
import { SidebarButton } from '../sidebar-button/sidebar-button'
import type { SpecificSidebarEntryProps } from '../types'
import { cypressId } from '../../../../utils/cypress-attribute'
+import { useBooleanState } from '../../../../hooks/common/use-boolean-state'
export const NoteInfoSidebarEntry: React.FC = ({ className, hide }) => {
- const [showModal, setShowModal] = useState(false)
+ const [modalVisibility, showModal, closeModal] = useBooleanState()
useTranslation()
return (
@@ -21,11 +22,11 @@ export const NoteInfoSidebarEntry: React.FC = ({ clas
hide={hide}
className={className}
icon={'line-chart'}
- onClick={() => setShowModal(true)}
+ onClick={showModal}
{...cypressId('sidebar-btn-document-info')}>
- setShowModal(false)} />
+
)
}
diff --git a/src/components/editor-page/sidebar/specific-sidebar-entries/permissions-sidebar-entry.tsx b/src/components/editor-page/sidebar/specific-sidebar-entries/permissions-sidebar-entry.tsx
index e6cbd43d7..a1a86f7bc 100644
--- a/src/components/editor-page/sidebar/specific-sidebar-entries/permissions-sidebar-entry.tsx
+++ b/src/components/editor-page/sidebar/specific-sidebar-entries/permissions-sidebar-entry.tsx
@@ -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
*/
-import React, { Fragment, useState } from 'react'
+import React, { Fragment } from 'react'
import { Trans, useTranslation } from 'react-i18next'
import { PermissionModal } from '../../document-bar/permissions/permission-modal'
import { SidebarButton } from '../sidebar-button/sidebar-button'
import type { SpecificSidebarEntryProps } from '../types'
+import { useBooleanState } from '../../../../hooks/common/use-boolean-state'
export const PermissionsSidebarEntry: React.FC = ({ className, hide }) => {
- const [showModal, setShowModal] = useState(false)
+ const [modalVisibility, showModal, closeModal] = useBooleanState()
useTranslation()
return (
- setShowModal(true)}>
+
- setShowModal(false)} />
+
)
}
diff --git a/src/components/editor-page/sidebar/specific-sidebar-entries/revision-sidebar-entry.tsx b/src/components/editor-page/sidebar/specific-sidebar-entries/revision-sidebar-entry.tsx
index 2d0b9b732..8f134fec1 100644
--- a/src/components/editor-page/sidebar/specific-sidebar-entries/revision-sidebar-entry.tsx
+++ b/src/components/editor-page/sidebar/specific-sidebar-entries/revision-sidebar-entry.tsx
@@ -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
*/
-import React, { Fragment, useCallback, useState } from 'react'
+import React, { Fragment } from 'react'
import { Trans } from 'react-i18next'
import { RevisionModal } from '../../document-bar/revisions/revision-modal'
import { SidebarButton } from '../sidebar-button/sidebar-button'
import type { SpecificSidebarEntryProps } from '../types'
+import { useBooleanState } from '../../../../hooks/common/use-boolean-state'
export const RevisionSidebarEntry: React.FC = ({ className, hide }) => {
- const [showModal, setShowModal] = useState(false)
- const onHide = useCallback(() => {
- setShowModal(false)
- }, [])
- const onShow = useCallback(() => {
- setShowModal(true)
- }, [])
+ const [modalVisibility, showModal, closeModal] = useBooleanState()
return (
-
+
-
+
)
}
diff --git a/src/components/editor-page/sidebar/specific-sidebar-entries/share-sidebar-entry.tsx b/src/components/editor-page/sidebar/specific-sidebar-entries/share-sidebar-entry.tsx
index 407a20dbe..fe8c528de 100644
--- a/src/components/editor-page/sidebar/specific-sidebar-entries/share-sidebar-entry.tsx
+++ b/src/components/editor-page/sidebar/specific-sidebar-entries/share-sidebar-entry.tsx
@@ -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
*/
-import React, { Fragment, useState } from 'react'
+import React, { Fragment } from 'react'
import { Trans, useTranslation } from 'react-i18next'
import { ShareModal } from '../../document-bar/share/share-modal'
import { SidebarButton } from '../sidebar-button/sidebar-button'
import type { SpecificSidebarEntryProps } from '../types'
+import { useBooleanState } from '../../../../hooks/common/use-boolean-state'
export const ShareSidebarEntry: React.FC = ({ className, hide }) => {
- const [showModal, setShowModal] = useState(false)
+ const [modalVisibility, showModal, closeModal] = useBooleanState()
useTranslation()
return (
- setShowModal(true)}>
+
- setShowModal(false)} />
+
)
}
diff --git a/src/components/history-page/entry-menu/dropdown-item-with-deletion-modal.tsx b/src/components/history-page/entry-menu/dropdown-item-with-deletion-modal.tsx
index 60b153198..117d47c9e 100644
--- a/src/components/history-page/entry-menu/dropdown-item-with-deletion-modal.tsx
+++ b/src/components/history-page/entry-menu/dropdown-item-with-deletion-modal.tsx
@@ -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
*/
-import React, { Fragment, useCallback, useState } from 'react'
+import React, { Fragment, useCallback } from 'react'
import { Dropdown } from 'react-bootstrap'
import { Trans, useTranslation } from 'react-i18next'
import { ForkAwesomeIcon } from '../../common/fork-awesome/fork-awesome-icon'
import type { IconName } from '../../common/fork-awesome/types'
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 { useBooleanState } from '../../../hooks/common/use-boolean-state'
export interface DropdownItemWithDeletionModalProps {
onConfirm: () => void
@@ -47,24 +48,24 @@ export const DropdownItemWithDeletionModal: React.FC<
className
}) => {
useTranslation()
- const [showDialog, setShowDialog] = useState(false)
+ const [modalVisibility, showModal, closeModal] = useBooleanState()
+
const handleConfirm = useCallback(() => {
- setShowDialog(false)
+ closeModal()
onConfirm()
- }, [onConfirm])
- const onHide = useCallback(() => setShowDialog(false), [])
+ }, [closeModal, onConfirm])
return (
- setShowDialog(true)} className={className}>
+
{
const { t } = useTranslation()
- const [show, setShow] = useState(false)
-
- const handleShow = () => setShow(true)
- const handleClose = () => setShow(false)
+ const [modalVisibility, showModal, closeModal] = useBooleanState()
const onConfirm = useCallback(() => {
deleteAllHistoryEntries().catch((error: Error) => {
showErrorNotification('landing.history.error.deleteEntry.text')(error)
safeRefreshHistoryState()
})
- handleClose()
- }, [])
+ closeModal()
+ }, [closeModal])
return (
diff --git a/src/components/landing-layout/footer/version-info/version-info-link.tsx b/src/components/landing-layout/footer/version-info/version-info-link.tsx
index 4bf8bf26f..86e9f8a63 100644
--- a/src/components/landing-layout/footer/version-info/version-info-link.tsx
+++ b/src/components/landing-layout/footer/version-info/version-info-link.tsx
@@ -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
*/
-import React, { Fragment, useCallback, useState } from 'react'
+import React, { Fragment } from 'react'
import { Trans } from 'react-i18next'
import { VersionInfoModal } from './version-info-modal'
import { cypressId } from '../../../../utils/cypress-attribute'
+import { useBooleanState } from '../../../../hooks/common/use-boolean-state'
export const VersionInfoLink: React.FC = () => {
- const [show, setShow] = useState(false)
- const closeModal = useCallback(() => setShow(false), [])
- const showModal = useCallback(() => setShow(true), [])
+ const [modalVisibility, showModal, closeModal] = useBooleanState()
return (
-
+
)
}
diff --git a/src/components/profile-page/access-tokens/access-token-deletion-modal.tsx b/src/components/profile-page/access-tokens/access-token-deletion-modal.tsx
index b317bc4ec..ee567167b 100644
--- a/src/components/profile-page/access-tokens/access-token-deletion-modal.tsx
+++ b/src/components/profile-page/access-tokens/access-token-deletion-modal.tsx
@@ -37,11 +37,7 @@ export const AccessTokenDeletionModal: React.FC =
)
})
.catch(showErrorNotification('profile.modal.deleteAccessToken.failed'))
- .finally(() => {
- if (onHide) {
- onHide()
- }
- })
+ .finally(() => onHide?.())
}, [token, onHide])
return (
diff --git a/src/components/profile-page/access-tokens/access-token-list-entry.tsx b/src/components/profile-page/access-tokens/access-token-list-entry.tsx
index b4a76e8ee..a1f2cf06d 100644
--- a/src/components/profile-page/access-tokens/access-token-list-entry.tsx
+++ b/src/components/profile-page/access-tokens/access-token-list-entry.tsx
@@ -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
*/
-import React, { useCallback, useMemo, useState } from 'react'
+import React, { useCallback, useMemo } from 'react'
import { Col, ListGroup, Row } from 'react-bootstrap'
import { cypressId } from '../../../utils/cypress-attribute'
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 { AccessTokenDeletionModal } from './access-token-deletion-modal'
import type { AccessTokenUpdateProps } from './profile-access-tokens'
+import { useBooleanState } from '../../../hooks/common/use-boolean-state'
export interface AccessTokenListEntryProps {
token: AccessToken
@@ -28,16 +29,12 @@ export const AccessTokenListEntry: React.FC {
useTranslation()
- const [showDeletionModal, setShowDeletionModal] = useState(false)
-
- const onShowDeletionModal = useCallback(() => {
- setShowDeletionModal(true)
- }, [])
+ const [modalVisibility, showModal, closeModal] = useBooleanState()
const onHideDeletionModal = useCallback(() => {
- setShowDeletionModal(false)
+ closeModal()
onUpdateList()
- }, [onUpdateList])
+ }, [closeModal, onUpdateList])
const lastUsed = useMemo(() => {
if (!token.lastUsedAt) {
@@ -66,12 +63,12 @@ export const AccessTokenListEntry: React.FC
-
+
)
}
diff --git a/src/components/profile-page/account-management/profile-account-management.tsx b/src/components/profile-page/account-management/profile-account-management.tsx
index c1d6ce70d..ecb23f08e 100644
--- a/src/components/profile-page/account-management/profile-account-management.tsx
+++ b/src/components/profile-page/account-management/profile-account-management.tsx
@@ -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
*/
-import React, { Fragment, useCallback, useState } from 'react'
+import React, { Fragment } from 'react'
import { Button, Card } from 'react-bootstrap'
import { Trans, useTranslation } from 'react-i18next'
import { ForkAwesomeIcon } from '../../common/fork-awesome/fork-awesome-icon'
import { AccountDeletionModal } from './account-deletion-modal'
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.
*/
export const ProfileAccountManagement: React.FC = () => {
useTranslation()
- const [showDeleteModal, setShowDeleteModal] = useState(false)
-
- const onShowDeletionModal = useCallback(() => {
- setShowDeleteModal(true)
- }, [])
-
- const onHideDeletionModal = useCallback(() => {
- setShowDeleteModal(false)
- }, [])
+ const [modalVisibility, showModal, closeModal] = useBooleanState()
return (
@@ -37,13 +30,13 @@ export const ProfileAccountManagement: React.FC = () => {
-
)
}
diff --git a/src/hooks/common/use-boolean-state.ts b/src/hooks/common/use-boolean-state.ts
new file mode 100644
index 000000000..c252b47f9
--- /dev/null
+++ b/src/hooks/common/use-boolean-state.ts
@@ -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]
+}