mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2024-11-21 09:16:30 -05:00
nullable note details
Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
parent
4fbe813af0
commit
a05b387ee1
58 changed files with 194 additions and 125 deletions
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { useApplicationState } from '../../hooks/common/use-application-state'
|
||||
import { useNoteDetails } from '../../hooks/common/use-note-details'
|
||||
import { InternalLink } from '../common/links/internal-link'
|
||||
import { ShowIf } from '../common/show-if/show-if'
|
||||
import { NoteInfoLineCreated } from '../editor-page/document-bar/note-info/note-info-line-created'
|
||||
|
@ -18,7 +18,7 @@ import { Trans, useTranslation } from 'react-i18next'
|
|||
*/
|
||||
export const DocumentInfobar: React.FC = () => {
|
||||
const { t } = useTranslation()
|
||||
const noteDetails = useApplicationState((state) => state.noteDetails)
|
||||
const noteDetails = useNoteDetails()
|
||||
|
||||
// TODO Check permissions ("writability") of note and show edit link depending on that.
|
||||
return (
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { useApplicationState } from '../../../hooks/common/use-application-state'
|
||||
import { useNoteDetails } from '../../../hooks/common/use-note-details'
|
||||
import { NewNoteButton } from '../../common/new-note-button/new-note-button'
|
||||
import { ShowIf } from '../../common/show-if/show-if'
|
||||
import { SignInButton } from '../../landing-layout/navigation/sign-in-button'
|
||||
|
@ -34,17 +35,17 @@ export interface AppBarProps {
|
|||
*/
|
||||
export const AppBar: React.FC<AppBarProps> = ({ mode }) => {
|
||||
const userExists = useApplicationState((state) => !!state.user)
|
||||
const noteFrontmatter = useApplicationState((state) => state.noteDetails.frontmatter)
|
||||
const noteFrontmatter = useNoteDetails()
|
||||
|
||||
return (
|
||||
<Navbar expand={true} className={'bg-light px-3'}>
|
||||
<Nav className='me-auto d-flex align-items-center'>
|
||||
<NavbarBranding />
|
||||
<ShowIf condition={mode === AppBarMode.EDITOR}>
|
||||
<ShowIf condition={noteFrontmatter.type === NoteType.SLIDE}>
|
||||
<ShowIf condition={noteFrontmatter.frontmatter.type === NoteType.SLIDE}>
|
||||
<SlideModeButton />
|
||||
</ShowIf>
|
||||
<ShowIf condition={noteFrontmatter.type !== NoteType.SLIDE}>
|
||||
<ShowIf condition={noteFrontmatter.frontmatter.type !== NoteType.SLIDE}>
|
||||
<ReadOnlyModeButton />
|
||||
</ShowIf>
|
||||
<HelpButton />
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { useApplicationState } from '../../../hooks/common/use-application-state'
|
||||
import { useNoteDetails } from '../../../hooks/common/use-note-details'
|
||||
import { UiIcon } from '../../common/icons/ui-icon'
|
||||
import Link from 'next/link'
|
||||
import React from 'react'
|
||||
|
@ -16,7 +16,7 @@ import { useTranslation } from 'react-i18next'
|
|||
*/
|
||||
export const ReadOnlyModeButton: React.FC = () => {
|
||||
const { t } = useTranslation()
|
||||
const noteIdentifier = useApplicationState((state) => state.noteDetails.primaryAddress)
|
||||
const noteIdentifier = useNoteDetails().primaryAddress
|
||||
|
||||
return (
|
||||
<Link href={`/s/${noteIdentifier}`} target='_blank'>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { useApplicationState } from '../../../hooks/common/use-application-state'
|
||||
import { useNoteDetails } from '../../../hooks/common/use-note-details'
|
||||
import { UiIcon } from '../../common/icons/ui-icon'
|
||||
import Link from 'next/link'
|
||||
import React from 'react'
|
||||
|
@ -16,7 +16,7 @@ import { useTranslation } from 'react-i18next'
|
|||
*/
|
||||
export const SlideModeButton: React.FC = () => {
|
||||
const { t } = useTranslation()
|
||||
const noteIdentifier = useApplicationState((state) => state.noteDetails.primaryAddress)
|
||||
const noteIdentifier = useNoteDetails().primaryAddress
|
||||
|
||||
return (
|
||||
<Link href={`/p/${noteIdentifier}`} target='_blank'>
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { addAlias } from '../../../../api/alias'
|
||||
import { useApplicationState } from '../../../../hooks/common/use-application-state'
|
||||
import { useIsOwner } from '../../../../hooks/common/use-is-owner'
|
||||
import { useNoteDetails } from '../../../../hooks/common/use-note-details'
|
||||
import { useOnInputChange } from '../../../../hooks/common/use-on-input-change'
|
||||
import { updateMetadata } from '../../../../redux/note-details/methods'
|
||||
import { testId } from '../../../../utils/test-id'
|
||||
|
@ -25,7 +25,7 @@ const validAliasRegex = /^[a-z0-9_-]*$/
|
|||
export const AliasesAddForm: React.FC = () => {
|
||||
const { t } = useTranslation()
|
||||
const { showErrorNotification } = useUiNotifications()
|
||||
const noteId = useApplicationState((state) => state.noteDetails.id)
|
||||
const noteId = useNoteDetails().id
|
||||
const isOwner = useIsOwner()
|
||||
const [newAlias, setNewAlias] = useState('')
|
||||
|
||||
|
|
|
@ -3,8 +3,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { useApplicationState } from '../../../../hooks/common/use-application-state'
|
||||
import type { ApplicationState } from '../../../../redux/application-state'
|
||||
import { useNoteDetails } from '../../../../hooks/common/use-note-details'
|
||||
import { AliasesListEntry } from './aliases-list-entry'
|
||||
import React, { Fragment, useMemo } from 'react'
|
||||
|
||||
|
@ -12,7 +11,7 @@ import React, { Fragment, useMemo } from 'react'
|
|||
* Renders the list of aliases.
|
||||
*/
|
||||
export const AliasesList: React.FC = () => {
|
||||
const aliases = useApplicationState((state: ApplicationState) => state.noteDetails.aliases)
|
||||
const aliases = useNoteDetails().aliases
|
||||
|
||||
const aliasesDom = useMemo(() => {
|
||||
return aliases
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { useApplicationState } from '../../../../hooks/common/use-application-state'
|
||||
import { useNoteDetails } from '../../../../hooks/common/use-note-details'
|
||||
import { NoteInfoLine } from './note-info-line'
|
||||
import { UnitalicBoldContent } from './unitalic-bold-content'
|
||||
import React from 'react'
|
||||
|
@ -14,7 +14,7 @@ import { Trans } from 'react-i18next'
|
|||
* Renders an info line about the number of contributors for the note.
|
||||
*/
|
||||
export const NoteInfoLineContributors: React.FC = () => {
|
||||
const contributors = useApplicationState((state) => state.noteDetails.editedBy.length)
|
||||
const contributors = useNoteDetails().editedBy.length
|
||||
|
||||
return (
|
||||
<NoteInfoLine icon={IconPeople} size={2}>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { useApplicationState } from '../../../../hooks/common/use-application-state'
|
||||
import { useNoteDetails } from '../../../../hooks/common/use-note-details'
|
||||
import { NoteInfoLine } from './note-info-line'
|
||||
import type { NoteInfoTimeLineProps } from './note-info-time-line'
|
||||
import { UnitalicBoldTimeFromNow } from './utils/unitalic-bold-time-from-now'
|
||||
|
@ -18,7 +18,7 @@ import { Trans } from 'react-i18next'
|
|||
* @param size The size in which the line should be displayed.
|
||||
*/
|
||||
export const NoteInfoLineCreated: React.FC<NoteInfoTimeLineProps> = ({ size }) => {
|
||||
const noteCreateTime = useApplicationState((state) => state.noteDetails.createdAt)
|
||||
const noteCreateTime = useNoteDetails().createdAt
|
||||
const noteCreateDateTime = useMemo(() => DateTime.fromSeconds(noteCreateTime), [noteCreateTime])
|
||||
|
||||
return (
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { useApplicationState } from '../../../../hooks/common/use-application-state'
|
||||
import { useNoteDetails } from '../../../../hooks/common/use-note-details'
|
||||
import { UserAvatarForUsername } from '../../../common/user-avatar/user-avatar-for-username'
|
||||
import { NoteInfoLine } from './note-info-line'
|
||||
import type { NoteInfoTimeLineProps } from './note-info-time-line'
|
||||
|
@ -21,9 +21,9 @@ import { Trans, useTranslation } from 'react-i18next'
|
|||
*/
|
||||
export const NoteInfoLineUpdated: React.FC<NoteInfoTimeLineProps> = ({ size }) => {
|
||||
useTranslation()
|
||||
const noteUpdateTime = useApplicationState((state) => state.noteDetails.updatedAt)
|
||||
const noteUpdateTime = useNoteDetails().updatedAt
|
||||
const noteUpdateDateTime = useMemo(() => DateTime.fromSeconds(noteUpdateTime), [noteUpdateTime])
|
||||
const noteUpdateUser = useApplicationState((state) => state.noteDetails.updateUsername)
|
||||
const noteUpdateUser = useNoteDetails().updateUsername
|
||||
|
||||
const userBlock = useMemo(() => {
|
||||
if (!noteUpdateUser) {
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { removeGroupPermission, setGroupPermission } from '../../../../api/permissions'
|
||||
import { useApplicationState } from '../../../../hooks/common/use-application-state'
|
||||
import { useNoteDetails } from '../../../../hooks/common/use-note-details'
|
||||
import { setNotePermissionsFromServer } from '../../../../redux/note-details/methods'
|
||||
import { IconButton } from '../../../common/icon-button/icon-button'
|
||||
import { useUiNotifications } from '../../../notifications/ui-notification-boundary'
|
||||
|
@ -12,9 +12,7 @@ import type { PermissionDisabledProps } from './permission-disabled.prop'
|
|||
import { AccessLevel, SpecialGroup } from '@hedgedoc/commons'
|
||||
import React, { useCallback, useMemo } from 'react'
|
||||
import { ToggleButtonGroup } from 'react-bootstrap'
|
||||
import { Eye as IconEye } from 'react-bootstrap-icons'
|
||||
import { Pencil as IconPencil } from 'react-bootstrap-icons'
|
||||
import { SlashCircle as IconSlashCircle } from 'react-bootstrap-icons'
|
||||
import { Eye as IconEye, Pencil as IconPencil, SlashCircle as IconSlashCircle } from 'react-bootstrap-icons'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
export interface PermissionEntrySpecialGroupProps {
|
||||
|
@ -34,7 +32,7 @@ export const PermissionEntrySpecialGroup: React.FC<PermissionEntrySpecialGroupPr
|
|||
type,
|
||||
disabled
|
||||
}) => {
|
||||
const noteId = useApplicationState((state) => state.noteDetails.primaryAddress)
|
||||
const noteId = useNoteDetails().primaryAddress
|
||||
const { t } = useTranslation()
|
||||
const { showErrorNotification } = useUiNotifications()
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
import { removeUserPermission, setUserPermission } from '../../../../api/permissions'
|
||||
import { getUser } from '../../../../api/users'
|
||||
import { useApplicationState } from '../../../../hooks/common/use-application-state'
|
||||
import { useNoteDetails } from '../../../../hooks/common/use-note-details'
|
||||
import { setNotePermissionsFromServer } from '../../../../redux/note-details/methods'
|
||||
import { ShowIf } from '../../../common/show-if/show-if'
|
||||
import { UserAvatarForUser } from '../../../common/user-avatar/user-avatar-for-user'
|
||||
|
@ -31,7 +31,7 @@ export const PermissionEntryUser: React.FC<PermissionEntryUserProps & Permission
|
|||
entry,
|
||||
disabled
|
||||
}) => {
|
||||
const noteId = useApplicationState((state) => state.noteDetails.primaryAddress)
|
||||
const noteId = useNoteDetails().primaryAddress
|
||||
const { showErrorNotification } = useUiNotifications()
|
||||
|
||||
const onRemoveEntry = useCallback(() => {
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { useApplicationState } from '../../../../hooks/common/use-application-state'
|
||||
import { useNoteDetails } from '../../../../hooks/common/use-note-details'
|
||||
import { UiIcon } from '../../../common/icons/ui-icon'
|
||||
import { UserAvatarForUsername } from '../../../common/user-avatar/user-avatar-for-username'
|
||||
import type { PermissionDisabledProps } from './permission-disabled.prop'
|
||||
|
@ -27,7 +27,7 @@ export const PermissionOwnerInfo: React.FC<PermissionOwnerInfoProps & Permission
|
|||
disabled
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const noteOwner = useApplicationState((state) => state.noteDetails.permissions.owner)
|
||||
const noteOwner = useNoteDetails().permissions.owner
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { setNoteOwner } from '../../../../api/permissions'
|
||||
import { useApplicationState } from '../../../../hooks/common/use-application-state'
|
||||
import { useNoteDetails } from '../../../../hooks/common/use-note-details'
|
||||
import { setNotePermissionsFromServer } from '../../../../redux/note-details/methods'
|
||||
import { useUiNotifications } from '../../../notifications/ui-notification-boundary'
|
||||
import type { PermissionDisabledProps } from './permission-disabled.prop'
|
||||
|
@ -19,7 +19,7 @@ import { Trans } from 'react-i18next'
|
|||
* @param disabled If the user is not the owner, functionality is disabled.
|
||||
*/
|
||||
export const PermissionSectionOwner: React.FC<PermissionDisabledProps> = ({ disabled }) => {
|
||||
const noteId = useApplicationState((state) => state.noteDetails.primaryAddress)
|
||||
const noteId = useNoteDetails().primaryAddress
|
||||
const [changeOwner, setChangeOwner] = useState(false)
|
||||
const { showErrorNotification } = useUiNotifications()
|
||||
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { useApplicationState } from '../../../../hooks/common/use-application-state'
|
||||
import { useIsOwner } from '../../../../hooks/common/use-is-owner'
|
||||
import { useNoteDetails } from '../../../../hooks/common/use-note-details'
|
||||
import type { PermissionDisabledProps } from './permission-disabled.prop'
|
||||
import { PermissionEntrySpecialGroup } from './permission-entry-special-group'
|
||||
import { AccessLevel, SpecialGroup } from '@hedgedoc/commons'
|
||||
|
@ -18,7 +18,7 @@ import { Trans, useTranslation } from 'react-i18next'
|
|||
*/
|
||||
export const PermissionSectionSpecialGroups: React.FC<PermissionDisabledProps> = ({ disabled }) => {
|
||||
useTranslation()
|
||||
const groupPermissions = useApplicationState((state) => state.noteDetails.permissions.sharedToGroups)
|
||||
const groupPermissions = useNoteDetails().permissions.sharedToGroups
|
||||
const isOwner = useIsOwner()
|
||||
|
||||
const specialGroupEntries = useMemo(() => {
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { setUserPermission } from '../../../../api/permissions'
|
||||
import { useApplicationState } from '../../../../hooks/common/use-application-state'
|
||||
import { useNoteDetails } from '../../../../hooks/common/use-note-details'
|
||||
import { setNotePermissionsFromServer } from '../../../../redux/note-details/methods'
|
||||
import { useUiNotifications } from '../../../notifications/ui-notification-boundary'
|
||||
import { PermissionAddEntryField } from './permission-add-entry-field'
|
||||
|
@ -20,8 +20,8 @@ import { Trans, useTranslation } from 'react-i18next'
|
|||
*/
|
||||
export const PermissionSectionUsers: React.FC<PermissionDisabledProps> = ({ disabled }) => {
|
||||
useTranslation()
|
||||
const userPermissions = useApplicationState((state) => state.noteDetails.permissions.sharedToUsers)
|
||||
const noteId = useApplicationState((state) => state.noteDetails.primaryAddress)
|
||||
const userPermissions = useNoteDetails().permissions.sharedToUsers
|
||||
const noteId = useNoteDetails().primaryAddress
|
||||
const { showErrorNotification } = useUiNotifications()
|
||||
|
||||
const userEntries = useMemo(() => {
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { getAllRevisions } from '../../../../api/revisions'
|
||||
import { useApplicationState } from '../../../../hooks/common/use-application-state'
|
||||
import { useNoteDetails } from '../../../../hooks/common/use-note-details'
|
||||
import { AsyncLoadingBoundary } from '../../../common/async-loading-boundary/async-loading-boundary'
|
||||
import { RevisionListEntry } from './revision-list-entry'
|
||||
import { DateTime } from 'luxon'
|
||||
|
@ -24,7 +24,7 @@ export interface RevisionListProps {
|
|||
* @param onRevisionSelect Callback that is executed when a list entry is selected
|
||||
*/
|
||||
export const RevisionList: React.FC<RevisionListProps> = ({ selectedRevisionId, onRevisionSelect }) => {
|
||||
const noteIdentifier = useApplicationState((state) => state.noteDetails.primaryAddress)
|
||||
const noteIdentifier = useNoteDetails().primaryAddress
|
||||
|
||||
const {
|
||||
value: revisions,
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { getRevision } from '../../../../api/revisions'
|
||||
import { useApplicationState } from '../../../../hooks/common/use-application-state'
|
||||
import { useNoteDetails } from '../../../../hooks/common/use-note-details'
|
||||
import type { ModalVisibilityProps } from '../../../common/modals/common-modal'
|
||||
import { useUiNotifications } from '../../../notifications/ui-notification-boundary'
|
||||
import { downloadRevision } from './utils'
|
||||
|
@ -28,7 +28,7 @@ export const RevisionModalFooter: React.FC<RevisionModalFooterProps & Pick<Modal
|
|||
onHide
|
||||
}) => {
|
||||
useTranslation()
|
||||
const noteIdentifier = useApplicationState((state) => state.noteDetails.primaryAddress)
|
||||
const noteIdentifier = useNoteDetails().primaryAddress
|
||||
const { showErrorNotification } = useUiNotifications()
|
||||
|
||||
const onRevertToRevision = useCallback(() => {
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { getRevision } from '../../../../api/revisions'
|
||||
import { useApplicationState } from '../../../../hooks/common/use-application-state'
|
||||
import { useDarkModeState } from '../../../../hooks/common/use-dark-mode-state'
|
||||
import { useNoteDetails } from '../../../../hooks/common/use-note-details'
|
||||
import { AsyncLoadingBoundary } from '../../../common/async-loading-boundary/async-loading-boundary'
|
||||
import { invertUnifiedPatch } from './invert-unified-patch'
|
||||
import { Optional } from '@mrdrogdrog/optional'
|
||||
|
@ -25,7 +25,7 @@ export interface RevisionViewerProps {
|
|||
* @param allRevisions List of metadata for all available revisions.
|
||||
*/
|
||||
export const RevisionViewer: React.FC<RevisionViewerProps> = ({ selectedRevisionId }) => {
|
||||
const noteIdentifier = useApplicationState((state) => state.noteDetails.primaryAddress)
|
||||
const noteIdentifier = useNoteDetails().primaryAddress
|
||||
const darkModeEnabled = useDarkModeState()
|
||||
|
||||
const { value, error, loading } = useAsync(async () => {
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { useApplicationState } from '../../../../hooks/common/use-application-state'
|
||||
import { useBaseUrl } from '../../../../hooks/common/use-base-url'
|
||||
import { useNoteDetails } from '../../../../hooks/common/use-note-details'
|
||||
import { CopyableField } from '../../../common/copyable/copyable-field/copyable-field'
|
||||
import React, { useMemo } from 'react'
|
||||
|
||||
|
@ -24,7 +24,7 @@ export interface LinkFieldProps {
|
|||
*/
|
||||
export const NoteUrlField: React.FC<LinkFieldProps> = ({ type }) => {
|
||||
const baseUrl = useBaseUrl()
|
||||
const noteIdentifier = useApplicationState((state) => state.noteDetails.primaryAddress)
|
||||
const noteIdentifier = useNoteDetails().primaryAddress
|
||||
|
||||
const url = useMemo(() => {
|
||||
const url = new URL(baseUrl)
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { useApplicationState } from '../../../../hooks/common/use-application-state'
|
||||
import { useNoteDetails } from '../../../../hooks/common/use-note-details'
|
||||
import type { ModalVisibilityProps } from '../../../common/modals/common-modal'
|
||||
import { CommonModal } from '../../../common/modals/common-modal'
|
||||
import { ShowIf } from '../../../common/show-if/show-if'
|
||||
|
@ -21,7 +21,7 @@ import { Trans, useTranslation } from 'react-i18next'
|
|||
*/
|
||||
export const ShareModal: React.FC<ModalVisibilityProps> = ({ show, onHide }) => {
|
||||
useTranslation()
|
||||
const noteFrontmatter = useApplicationState((state) => state.noteDetails.frontmatter)
|
||||
const noteFrontmatter = useNoteDetails().frontmatter
|
||||
|
||||
return (
|
||||
<CommonModal show={show} onHide={onHide} showCloseButton={true} titleI18nKey={'editor.modal.shareLink.title'}>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { useApplicationState } from '../../../hooks/common/use-application-state'
|
||||
import { useNoteDetails } from '../../../hooks/common/use-note-details'
|
||||
import { useTrimmedNoteMarkdownContentWithoutFrontmatter } from '../../../hooks/common/use-trimmed-note-markdown-content-without-frontmatter'
|
||||
import { setRendererStatus } from '../../../redux/renderer-status/methods'
|
||||
import { RendererType } from '../../render-page/window-post-message-communicator/rendering-message'
|
||||
|
@ -28,7 +28,7 @@ export type EditorDocumentRendererProps = Omit<
|
|||
*/
|
||||
export const EditorDocumentRenderer: React.FC<EditorDocumentRendererProps> = ({ scrollState, onScroll, ...props }) => {
|
||||
const trimmedContentLines = useTrimmedNoteMarkdownContentWithoutFrontmatter()
|
||||
const noteType: NoteType = useApplicationState((state) => state.noteDetails.frontmatter.type)
|
||||
const noteType: NoteType = useNoteDetails().frontmatter.type
|
||||
const adjustedOnScroll = useOnScrollWithLineOffset(onScroll)
|
||||
const adjustedScrollState = useScrollStateWithoutLineOffset(scrollState)
|
||||
|
||||
|
|
|
@ -4,8 +4,7 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { getGlobalState } from '../../../../redux'
|
||||
import type { ScrollState } from '../../synced-scroll/scroll-props'
|
||||
import type { ScrollCallback } from '../../synced-scroll/scroll-props'
|
||||
import type { ScrollCallback, ScrollState } from '../../synced-scroll/scroll-props'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
/**
|
||||
|
@ -22,7 +21,7 @@ export const useOnScrollWithLineOffset = (onScroll: ScrollCallback | undefined):
|
|||
return (scrollState: ScrollState) => {
|
||||
onScroll({
|
||||
firstLineInView:
|
||||
scrollState.firstLineInView + getGlobalState().noteDetails.frontmatterRendererInfo.lineOffset,
|
||||
scrollState.firstLineInView + (getGlobalState().noteDetails?.frontmatterRendererInfo.lineOffset ?? 0),
|
||||
scrolledPercentage: scrollState.scrolledPercentage
|
||||
})
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { useApplicationState } from '../../../../hooks/common/use-application-state'
|
||||
import { useNoteDetails } from '../../../../hooks/common/use-note-details'
|
||||
import type { ScrollState } from '../../synced-scroll/scroll-props'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
|
@ -14,7 +14,7 @@ import { useMemo } from 'react'
|
|||
* @return the adjusted scroll state without the line offset
|
||||
*/
|
||||
export const useScrollStateWithoutLineOffset = (scrollState: ScrollState | undefined): ScrollState | undefined => {
|
||||
const lineOffset = useApplicationState((state) => state.noteDetails.frontmatterRendererInfo.lineOffset)
|
||||
const lineOffset = useNoteDetails().frontmatterRendererInfo.lineOffset
|
||||
return useMemo(() => {
|
||||
return scrollState === undefined
|
||||
? undefined
|
||||
|
|
|
@ -45,7 +45,7 @@ export const useOnImageUploadFromRenderer = (): void => {
|
|||
const file = new File([blob], fileName, { type: blob.type })
|
||||
const { cursorSelection, alt, title } = Optional.ofNullable(lineIndex)
|
||||
.flatMap((actualLineIndex) => {
|
||||
const lineOffset = getGlobalState().noteDetails.frontmatterRendererInfo.lineOffset
|
||||
const lineOffset = getGlobalState().noteDetails?.frontmatterRendererInfo.lineOffset ?? 0
|
||||
return findPlaceholderInMarkdownContent(actualLineIndex + lineOffset, placeholderIndexInLine)
|
||||
})
|
||||
.orElse({} as ExtractResult)
|
||||
|
@ -73,6 +73,9 @@ export interface ExtractResult {
|
|||
*/
|
||||
const findPlaceholderInMarkdownContent = (lineIndex: number, replacementIndexInLine = 0): Optional<ExtractResult> => {
|
||||
const noteDetails = getGlobalState().noteDetails
|
||||
if (noteDetails === null) {
|
||||
throw new Error('no note details')
|
||||
}
|
||||
const currentMarkdownContentLines = noteDetails.markdownContent.lines
|
||||
return Optional.ofNullable(noteDetails.markdownContent.lineStartIndexes[lineIndex]).map((startIndexOfLine) =>
|
||||
findImagePlaceholderInLine(currentMarkdownContentLines[lineIndex], startIndexOfLine, replacementIndexInLine)
|
||||
|
|
|
@ -50,11 +50,14 @@ export const useHandleUpload = (): handleUploadSignature => {
|
|||
: t('editor.upload.uploadFile.withoutDescription', { fileName: file.name })
|
||||
|
||||
const uploadPlaceholder = `![${uploadFileInfo}](upload-${randomId}${additionalUrlText ?? ''})`
|
||||
const noteId = getGlobalState().noteDetails.id
|
||||
const noteDetails = getGlobalState().noteDetails
|
||||
if (noteDetails === null) {
|
||||
throw new Error('no note details')
|
||||
}
|
||||
changeContent(({ currentSelection }) => {
|
||||
return replaceSelection(cursorSelection ?? currentSelection, uploadPlaceholder, false)
|
||||
})
|
||||
uploadFile(noteId, file)
|
||||
uploadFile(noteDetails.id, file)
|
||||
.then(({ url }) => {
|
||||
const replacement = `![${description ?? file.name ?? ''}](${url}${additionalUrlText ?? ''})`
|
||||
changeContent(({ markdownContent }) => [
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { useApplicationState } from '../../../../hooks/common/use-application-state'
|
||||
import { useNoteDetails } from '../../../../hooks/common/use-note-details'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
export interface LineBasedPosition {
|
||||
|
@ -24,8 +24,8 @@ const calculateLineBasedPosition = (absolutePosition: number, lineStartIndexes:
|
|||
* Returns the line+character based position of the to-cursor, if available.
|
||||
*/
|
||||
export const useLineBasedToPosition = (): LineBasedPosition | undefined => {
|
||||
const lineStartIndexes = useApplicationState((state) => state.noteDetails.markdownContent.lineStartIndexes)
|
||||
const selection = useApplicationState((state) => state.noteDetails.selection)
|
||||
const lineStartIndexes = useNoteDetails().markdownContent.lineStartIndexes
|
||||
const selection = useNoteDetails().selection
|
||||
|
||||
return useMemo(() => {
|
||||
const to = selection.to
|
||||
|
@ -40,8 +40,8 @@ export const useLineBasedToPosition = (): LineBasedPosition | undefined => {
|
|||
* Returns the line+character based position of the from-cursor.
|
||||
*/
|
||||
export const useLineBasedFromPosition = (): LineBasedPosition => {
|
||||
const lineStartIndexes = useApplicationState((state) => state.noteDetails.markdownContent.lineStartIndexes)
|
||||
const selection = useApplicationState((state) => state.noteDetails.selection)
|
||||
const lineStartIndexes = useNoteDetails().markdownContent.lineStartIndexes
|
||||
const selection = useNoteDetails().selection
|
||||
|
||||
return useMemo(() => {
|
||||
return calculateLineBasedPosition(selection.from, lineStartIndexes)
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { useApplicationState } from '../../../../../hooks/common/use-application-state'
|
||||
import { useNoteDetails } from '../../../../../hooks/common/use-note-details'
|
||||
import { Logger } from '../../../../../utils/logger'
|
||||
import { useUiNotifications } from '../../../../notifications/ui-notification-boundary'
|
||||
import type { MessageTransporter } from '@hedgedoc/commons'
|
||||
|
@ -21,7 +21,7 @@ const logger = new Logger('UseOnNoteDeleted')
|
|||
*/
|
||||
export const useOnNoteDeleted = (websocketConnection: MessageTransporter): void => {
|
||||
const router = useRouter()
|
||||
const noteTitle = useApplicationState((state) => state.noteDetails.title)
|
||||
const noteTitle = useNoteDetails().title
|
||||
const { dispatchUiNotification } = useUiNotifications()
|
||||
|
||||
const noteDeletedHandler = useCallback(() => {
|
||||
|
|
|
@ -28,7 +28,7 @@ export const useRealtimeConnection = (): MessageTransporter => {
|
|||
const messageTransporter = useMemo(() => {
|
||||
if (isMockMode) {
|
||||
logger.debug('Creating Loopback connection...')
|
||||
return new MockedBackendMessageTransporter(getGlobalState().noteDetails.markdownContent.plain)
|
||||
return new MockedBackendMessageTransporter(getGlobalState().noteDetails?.markdownContent.plain ?? '')
|
||||
} else {
|
||||
logger.debug('Creating Websocket connection...')
|
||||
return new WebsocketTransporter()
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { useApplicationState } from '../../../../../hooks/common/use-application-state'
|
||||
import { useBaseUrl } from '../../../../../hooks/common/use-base-url'
|
||||
import { useNoteDetails } from '../../../../../hooks/common/use-note-details'
|
||||
import { isMockMode } from '../../../../../utils/test-modes'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
|
@ -14,7 +14,7 @@ const LOCAL_FALLBACK_URL = 'ws://localhost:8080/realtime/'
|
|||
* Provides the URL for the realtime endpoint.
|
||||
*/
|
||||
export const useWebsocketUrl = (): URL | undefined => {
|
||||
const noteId = useApplicationState((state) => state.noteDetails.id)
|
||||
const noteId = useNoteDetails().id
|
||||
const baseUrl = useBaseUrl()
|
||||
|
||||
const websocketUrl = useMemo(() => {
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { useApplicationState } from '../../../../hooks/common/use-application-state'
|
||||
import { useNoteDetails } from '../../../../hooks/common/use-note-details'
|
||||
import React, { useMemo } from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
|
||||
|
@ -13,7 +13,7 @@ import { Trans, useTranslation } from 'react-i18next'
|
|||
export const NumberOfLinesInDocumentInfo: React.FC = () => {
|
||||
useTranslation()
|
||||
|
||||
const linesInDocument = useApplicationState((state) => state.noteDetails.markdownContent.lines.length)
|
||||
const linesInDocument = useNoteDetails().markdownContent.lines.length
|
||||
const translationOptions = useMemo(() => ({ lines: linesInDocument }), [linesInDocument])
|
||||
|
||||
return (
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { useApplicationState } from '../../../../hooks/common/use-application-state'
|
||||
import { useNoteDetails } from '../../../../hooks/common/use-note-details'
|
||||
import { cypressId } from '../../../../utils/cypress-attribute'
|
||||
import { useFrontendConfig } from '../../../common/frontend-config-context/use-frontend-config'
|
||||
import React, { useMemo } from 'react'
|
||||
|
@ -16,7 +16,7 @@ export const RemainingCharactersInfo: React.FC = () => {
|
|||
const { t } = useTranslation()
|
||||
|
||||
const maxDocumentLength = useFrontendConfig().maxDocumentLength
|
||||
const contentLength = useApplicationState((state) => state.noteDetails.markdownContent.plain.length)
|
||||
const contentLength = useNoteDetails().markdownContent.plain.length
|
||||
const remainingCharacters = useMemo(() => maxDocumentLength - contentLength, [contentLength, maxDocumentLength])
|
||||
|
||||
const remainingCharactersClass = useMemo(() => {
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { useApplicationState } from '../../../../hooks/common/use-application-state'
|
||||
import { useNoteDetails } from '../../../../hooks/common/use-note-details'
|
||||
import { SeparatorDash } from './separator-dash'
|
||||
import React, { Fragment, useMemo } from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
|
@ -14,7 +14,7 @@ import { Trans, useTranslation } from 'react-i18next'
|
|||
export const SelectedCharacters: React.FC = () => {
|
||||
useTranslation()
|
||||
|
||||
const selection = useApplicationState((state) => state.noteDetails.selection)
|
||||
const selection = useNoteDetails().selection
|
||||
const count = useMemo(
|
||||
() => (selection.to === undefined ? undefined : selection.to - selection.from),
|
||||
[selection.from, selection.to]
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { useApplicationState } from '../../../hooks/common/use-application-state'
|
||||
import { useNoteDetails } from '../../../hooks/common/use-note-details'
|
||||
import Head from 'next/head'
|
||||
import React, { useMemo } from 'react'
|
||||
|
||||
|
@ -11,7 +11,7 @@ import React, { useMemo } from 'react'
|
|||
* Renders the license link tag if a license is set in the frontmatter.
|
||||
*/
|
||||
export const LicenseLinkHead: React.FC = () => {
|
||||
const license = useApplicationState((state) => state.noteDetails.frontmatter.license)
|
||||
const license = useNoteDetails().frontmatter.license
|
||||
|
||||
const optionalLinkElement = useMemo(() => {
|
||||
if (!license || license.trim() === '') {
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { useApplicationState } from '../../../hooks/common/use-application-state'
|
||||
import { useNoteDetails } from '../../../hooks/common/use-note-details'
|
||||
import { useNoteTitle } from '../../../hooks/common/use-note-title'
|
||||
import Head from 'next/head'
|
||||
import React, { useMemo } from 'react'
|
||||
|
@ -13,7 +13,7 @@ import React, { useMemo } from 'react'
|
|||
*/
|
||||
export const OpengraphHead: React.FC = () => {
|
||||
const noteTitle = useNoteTitle()
|
||||
const openGraphData = useApplicationState((state) => state.noteDetails.frontmatter.opengraph)
|
||||
const openGraphData = useNoteDetails().frontmatter.opengraph
|
||||
const openGraphMetaElements = useMemo(() => {
|
||||
const elements = Object.entries(openGraphData)
|
||||
.filter(([, value]) => value && String(value).trim() !== '')
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
import type { HistoryEntryWithOrigin } from '../../../api/history/types'
|
||||
import { HistoryEntryOrigin } from '../../../api/history/types'
|
||||
import { useApplicationState } from '../../../hooks/common/use-application-state'
|
||||
import { useNoteDetails } from '../../../hooks/common/use-note-details'
|
||||
import { getGlobalState } from '../../../redux'
|
||||
import { updateLocalHistoryEntry } from '../../../redux/history/methods'
|
||||
import equal from 'fast-deep-equal'
|
||||
|
@ -16,10 +17,10 @@ import { useEffect, useRef } from 'react'
|
|||
* The entry is updated when the title or tags of the note change.
|
||||
*/
|
||||
export const useUpdateLocalHistoryEntry = (): void => {
|
||||
const id = useApplicationState((state) => state.noteDetails.id)
|
||||
const id = useNoteDetails().id
|
||||
const userExists = useApplicationState((state) => !!state.user)
|
||||
const currentNoteTitle = useApplicationState((state) => state.noteDetails.title)
|
||||
const currentNoteTags = useApplicationState((state) => state.noteDetails.frontmatter.tags)
|
||||
const currentNoteTitle = useNoteDetails().title
|
||||
const currentNoteTags = useNoteDetails().frontmatter.tags
|
||||
|
||||
const lastNoteTitle = useRef('')
|
||||
const lastNoteTags = useRef<string[]>([])
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { useApplicationState } from '../../../../hooks/common/use-application-state'
|
||||
import { useNoteDetails } from '../../../../hooks/common/use-note-details'
|
||||
import { DarkModePreference } from '../../../../redux/dark-mode/types'
|
||||
import { useSendToRenderer } from '../../../render-page/window-post-message-communicator/hooks/use-send-to-renderer'
|
||||
import { CommunicationMessageType } from '../../../render-page/window-post-message-communicator/rendering-message'
|
||||
|
@ -20,7 +21,7 @@ export const useSendAdditionalConfigurationToRenderer = (
|
|||
forcedDarkMode: DarkModePreference = DarkModePreference.AUTO
|
||||
): void => {
|
||||
const darkModePreference = useApplicationState((state) => state.darkMode.darkModePreference)
|
||||
const newlinesAreBreaks = useApplicationState((state) => state.noteDetails.frontmatter.newlinesAreBreaks)
|
||||
const newlinesAreBreaks = useNoteDetails().frontmatter.newlinesAreBreaks
|
||||
|
||||
const darkMode = useMemo(() => {
|
||||
return forcedDarkMode === DarkModePreference.AUTO ? darkModePreference : forcedDarkMode
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { useApplicationState } from '../../../../hooks/common/use-application-state'
|
||||
import { useNoteDetails } from '../../../../hooks/common/use-note-details'
|
||||
import { cypressId } from '../../../../utils/cypress-attribute'
|
||||
import type { ModalVisibilityProps } from '../../../common/modals/common-modal'
|
||||
import { DeletionModal } from '../../../common/modals/deletion-modal'
|
||||
|
@ -44,7 +44,7 @@ export const DeleteNoteModal: React.FC<DeleteNoteModalProps & DeleteHistoryNoteM
|
|||
modalWarningI18nKey,
|
||||
modalButtonI18nKey
|
||||
}) => {
|
||||
const noteTitle = useApplicationState((state) => state.noteDetails.title)
|
||||
const noteTitle = useNoteDetails().title
|
||||
|
||||
return (
|
||||
<DeletionModal
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { deleteNote } from '../../../../api/notes'
|
||||
import { useApplicationState } from '../../../../hooks/common/use-application-state'
|
||||
import { useBooleanState } from '../../../../hooks/common/use-boolean-state'
|
||||
import { useNoteDetails } from '../../../../hooks/common/use-note-details'
|
||||
import { cypressId } from '../../../../utils/cypress-attribute'
|
||||
import { Logger } from '../../../../utils/logger'
|
||||
import { useUiNotifications } from '../../../notifications/ui-notification-boundary'
|
||||
|
@ -29,7 +29,7 @@ const logger = new Logger('note-deletion')
|
|||
export const DeleteNoteSidebarEntry: React.FC<PropsWithChildren<SpecificSidebarEntryProps>> = ({ hide, className }) => {
|
||||
useTranslation()
|
||||
const router = useRouter()
|
||||
const noteId = useApplicationState((state) => state.noteDetails.id)
|
||||
const noteId = useNoteDetails().id
|
||||
const [modalVisibility, showModal, closeModal] = useBooleanState()
|
||||
const { showErrorNotification } = useUiNotifications()
|
||||
|
||||
|
|
|
@ -20,7 +20,11 @@ export const ExportMarkdownSidebarEntry: React.FC = () => {
|
|||
const { t } = useTranslation()
|
||||
const markdownContent = useNoteMarkdownContent()
|
||||
const onClick = useCallback(() => {
|
||||
const sanitized = sanitize(getGlobalState().noteDetails.title)
|
||||
const noteDetails = getGlobalState().noteDetails
|
||||
if (noteDetails === null) {
|
||||
throw new Error('no note details!')
|
||||
}
|
||||
const sanitized = sanitize(noteDetails.title)
|
||||
download(markdownContent, `${sanitized !== '' ? sanitized : t('editor.untitledNote')}.md`, 'text/markdown')
|
||||
}, [markdownContent, t])
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { useApplicationState } from '../../../../hooks/common/use-application-state'
|
||||
import { useNoteDetails } from '../../../../hooks/common/use-note-details'
|
||||
import { toggleHistoryEntryPinning } from '../../../../redux/history/methods'
|
||||
import { useUiNotifications } from '../../../notifications/ui-notification-boundary'
|
||||
import { SidebarButton } from '../sidebar-button/sidebar-button'
|
||||
|
@ -21,7 +22,7 @@ import { Trans, useTranslation } from 'react-i18next'
|
|||
*/
|
||||
export const PinNoteSidebarEntry: React.FC<SpecificSidebarEntryProps> = ({ className, hide }) => {
|
||||
useTranslation()
|
||||
const id = useApplicationState((state) => state.noteDetails.id)
|
||||
const id = useNoteDetails().id
|
||||
const history = useApplicationState((state) => state.history)
|
||||
const { showErrorNotification } = useUiNotifications()
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { useApplicationState } from '../../hooks/common/use-application-state'
|
||||
import { useNoteDetails } from '../../hooks/common/use-note-details'
|
||||
import { useTrimmedNoteMarkdownContentWithoutFrontmatter } from '../../hooks/common/use-trimmed-note-markdown-content-without-frontmatter'
|
||||
import { setRendererStatus } from '../../redux/renderer-status/methods'
|
||||
import { RenderIframe } from '../editor-page/renderer-pane/render-iframe'
|
||||
|
@ -22,7 +23,7 @@ export const SlideShowPageContent: React.FC = () => {
|
|||
const markdownContentLines = useTrimmedNoteMarkdownContentWithoutFrontmatter()
|
||||
useTranslation()
|
||||
|
||||
const slideOptions = useApplicationState((state) => state.noteDetails.frontmatter.slideOptions)
|
||||
const slideOptions = useNoteDetails().frontmatter.slideOptions
|
||||
const rendererReady = useApplicationState((state) => state.rendererStatus.rendererReady)
|
||||
useSendToRenderer(
|
||||
useMemo(
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
import { useChangeEditorContentCallback } from '../../../components/editor-page/change-content-context/use-change-editor-content-callback'
|
||||
import type { ContentEdits } from '../../../components/editor-page/editor-pane/tool-bar/formatters/types/changes'
|
||||
import { useExtensionEventEmitterHandler } from '../../../components/markdown-renderer/hooks/use-extension-event-emitter'
|
||||
import { store } from '../../../redux'
|
||||
import { getGlobalState } from '../../../redux'
|
||||
import { createCheckboxContent } from './create-checkbox-content'
|
||||
import type { TaskCheckedEventPayload } from './event-emitting-task-list-checkbox'
|
||||
import { findCheckBox } from './find-check-box'
|
||||
|
@ -32,7 +32,11 @@ export const useSetCheckboxInEditor = () => {
|
|||
return useCallback(
|
||||
({ lineInMarkdown, newCheckedState }: TaskCheckedEventPayload): void => {
|
||||
changeEditorContent?.(({ markdownContent }) => {
|
||||
const correctedLineIndex = lineInMarkdown + store.getState().noteDetails.frontmatterRendererInfo.lineOffset
|
||||
const noteDetails = getGlobalState().noteDetails
|
||||
if (noteDetails === null) {
|
||||
throw new Error('no note details!')
|
||||
}
|
||||
const correctedLineIndex = lineInMarkdown + noteDetails.frontmatterRendererInfo.lineOffset
|
||||
const edits = findCheckBox(markdownContent, correctedLineIndex)
|
||||
.map(([startIndex, endIndex]) => createCheckboxContentEdit(startIndex, endIndex, newCheckedState))
|
||||
.orElse([])
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { useApplicationState } from './use-application-state'
|
||||
import { useNoteDetails } from './use-note-details'
|
||||
import type { NotePermissions } from '@hedgedoc/commons'
|
||||
import { userIsOwner } from '@hedgedoc/commons'
|
||||
import { useMemo } from 'react'
|
||||
|
@ -15,7 +16,7 @@ import { useMemo } from 'react'
|
|||
*/
|
||||
export const useIsOwner = (): boolean => {
|
||||
const me: string | undefined = useApplicationState((state) => state.user?.username)
|
||||
const permissions: NotePermissions = useApplicationState((state) => state.noteDetails.permissions)
|
||||
const permissions: NotePermissions = useNoteDetails().permissions
|
||||
|
||||
return useMemo(() => userIsOwner(permissions, me), [permissions, me])
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { useApplicationState } from './use-application-state'
|
||||
import { useNoteDetails } from './use-note-details'
|
||||
import type { NotePermissions } from '@hedgedoc/commons'
|
||||
import { userCanEdit } from '@hedgedoc/commons'
|
||||
import { useMemo } from 'react'
|
||||
|
@ -15,7 +16,7 @@ import { useMemo } from 'react'
|
|||
*/
|
||||
export const useMayEdit = (): boolean => {
|
||||
const me: string | undefined = useApplicationState((state) => state.user?.username)
|
||||
const permissions: NotePermissions = useApplicationState((state) => state.noteDetails.permissions)
|
||||
const permissions: NotePermissions = useNoteDetails().permissions
|
||||
|
||||
return useMemo(() => userCanEdit(permissions, me), [permissions, me])
|
||||
}
|
||||
|
|
17
frontend/src/hooks/common/use-note-details.ts
Normal file
17
frontend/src/hooks/common/use-note-details.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { NoteDetails } from '../../redux/note-details/types/note-details'
|
||||
import { useApplicationState } from './use-application-state'
|
||||
|
||||
export const useNoteDetails = (): NoteDetails => {
|
||||
const noteDetails = useApplicationState((state) => state.noteDetails)
|
||||
|
||||
if (noteDetails === null) {
|
||||
throw new Error('No note details in global application state!')
|
||||
}
|
||||
|
||||
return noteDetails
|
||||
}
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { useApplicationState } from './use-application-state'
|
||||
import { useNoteDetails } from './use-note-details'
|
||||
|
||||
/**
|
||||
* Extracts the markdown content of the current note from the global application state.
|
||||
|
@ -11,5 +11,5 @@ import { useApplicationState } from './use-application-state'
|
|||
* @return The markdown content of the note
|
||||
*/
|
||||
export const useNoteMarkdownContent = (): string => {
|
||||
return useApplicationState((state) => state.noteDetails.markdownContent.plain)
|
||||
return useNoteDetails().markdownContent.plain
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { useApplicationState } from './use-application-state'
|
||||
import { useNoteDetails } from './use-note-details'
|
||||
import { useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
|
@ -15,7 +15,7 @@ import { useTranslation } from 'react-i18next'
|
|||
export const useNoteTitle = (): string => {
|
||||
const { t } = useTranslation()
|
||||
const untitledNote = useMemo(() => t('editor.untitledNote'), [t])
|
||||
const noteTitle = useApplicationState((state) => state.noteDetails.title)
|
||||
const noteTitle = useNoteDetails().title
|
||||
|
||||
return useMemo(() => (noteTitle === '' ? untitledNote : noteTitle), [noteTitle, untitledNote])
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { useFrontendConfig } from '../../components/common/frontend-config-context/use-frontend-config'
|
||||
import { useApplicationState } from './use-application-state'
|
||||
import { useNoteDetails } from './use-note-details'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
/**
|
||||
|
@ -14,19 +14,17 @@ import { useMemo } from 'react'
|
|||
*/
|
||||
export const useTrimmedNoteMarkdownContentWithoutFrontmatter = (): string[] => {
|
||||
const maxLength = useFrontendConfig().maxDocumentLength
|
||||
const markdownContent = useApplicationState((state) => ({
|
||||
lines: state.noteDetails.markdownContent.lines,
|
||||
content: state.noteDetails.markdownContent.plain
|
||||
}))
|
||||
const lineOffset = useApplicationState((state) => state.noteDetails.frontmatterRendererInfo.lineOffset)
|
||||
const lines = useNoteDetails().markdownContent.lines
|
||||
const content = useNoteDetails().markdownContent.plain
|
||||
const lineOffset = useNoteDetails().frontmatterRendererInfo.lineOffset
|
||||
|
||||
const trimmedLines = useMemo(() => {
|
||||
if (markdownContent.content.length > maxLength) {
|
||||
return markdownContent.content.slice(0, maxLength).split('\n')
|
||||
if (content.length > maxLength) {
|
||||
return content.slice(0, maxLength).split('\n')
|
||||
} else {
|
||||
return markdownContent.lines
|
||||
return lines
|
||||
}
|
||||
}, [markdownContent, maxLength])
|
||||
}, [content, lines, maxLength])
|
||||
|
||||
return useMemo(() => {
|
||||
return trimmedLines.slice(lineOffset)
|
||||
|
|
4
frontend/src/redux/application-state.d.ts
vendored
4
frontend/src/redux/application-state.d.ts
vendored
|
@ -6,7 +6,7 @@
|
|||
import type { HistoryEntryWithOrigin } from '../api/history/types'
|
||||
import type { DarkModeConfig } from './dark-mode/types'
|
||||
import type { EditorConfig } from './editor/types'
|
||||
import type { NoteDetails } from './note-details/types/note-details'
|
||||
import type { OptionalNoteDetails } from './note-details/types'
|
||||
import type { RealtimeStatus } from './realtime/types'
|
||||
import type { RendererStatus } from './renderer-status/types'
|
||||
import type { OptionalUserState } from './user/types'
|
||||
|
@ -16,7 +16,7 @@ export interface ApplicationState {
|
|||
history: HistoryEntryWithOrigin[]
|
||||
editorConfig: EditorConfig
|
||||
darkMode: DarkModeConfig
|
||||
noteDetails: NoteDetails
|
||||
noteDetails: OptionalNoteDetails
|
||||
rendererStatus: RendererStatus
|
||||
realtimeStatus: RealtimeStatus
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
import { calculateLineStartIndexes } from './calculate-line-start-indexes'
|
||||
import { initialState } from './initial-state'
|
||||
import { createNoteFrontmatterFromYaml } from './raw-note-frontmatter-parser/parser'
|
||||
import type { OptionalNoteDetails } from './types'
|
||||
import type { NoteDetails } from './types/note-details'
|
||||
import { extractFrontmatter, generateNoteTitle } from '@hedgedoc/commons'
|
||||
import type { PresentFrontmatterExtractionResult } from '@hedgedoc/commons'
|
||||
|
@ -16,7 +17,13 @@ import type { PresentFrontmatterExtractionResult } from '@hedgedoc/commons'
|
|||
* @param markdownContent The new note markdown content consisting of the frontmatter and markdown part.
|
||||
* @return An updated {@link NoteDetails} state.
|
||||
*/
|
||||
export const buildStateFromUpdatedMarkdownContent = (state: NoteDetails, markdownContent: string): NoteDetails => {
|
||||
export const buildStateFromUpdatedMarkdownContent = (
|
||||
state: OptionalNoteDetails,
|
||||
markdownContent: string
|
||||
): OptionalNoteDetails => {
|
||||
if (state === null) {
|
||||
return state
|
||||
}
|
||||
return buildStateFromMarkdownContentAndLines(state, markdownContent, markdownContent.split('\n'))
|
||||
}
|
||||
|
||||
|
|
|
@ -73,7 +73,11 @@ export const updateCursorPositions = (selection: CursorSelection): void => {
|
|||
* Updates the current note's metadata from the server.
|
||||
*/
|
||||
export const updateMetadata = async (): Promise<void> => {
|
||||
const updatedMetadata = await getNoteMetadata(store.getState().noteDetails.id)
|
||||
const noteDetails = store.getState().noteDetails
|
||||
if (!noteDetails) {
|
||||
throw new Error('no note details loaded')
|
||||
}
|
||||
const updatedMetadata = await getNoteMetadata(noteDetails.id)
|
||||
store.dispatch({
|
||||
type: NoteDetailsActionType.UPDATE_METADATA,
|
||||
updatedMetadata
|
||||
|
|
|
@ -4,19 +4,17 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { buildStateFromUpdatedMarkdownContent } from './build-state-from-updated-markdown-content'
|
||||
import { initialState } from './initial-state'
|
||||
import { buildStateFromFirstHeadingUpdate } from './reducers/build-state-from-first-heading-update'
|
||||
import { buildStateFromMetadataUpdate } from './reducers/build-state-from-metadata-update'
|
||||
import { buildStateFromServerPermissions } from './reducers/build-state-from-server-permissions'
|
||||
import { buildStateFromServerDto } from './reducers/build-state-from-set-note-data-from-server'
|
||||
import { buildStateFromUpdateCursorPosition } from './reducers/build-state-from-update-cursor-position'
|
||||
import type { NoteDetailsActions } from './types'
|
||||
import type { NoteDetailsActions, OptionalNoteDetails } from './types'
|
||||
import { NoteDetailsActionType } from './types'
|
||||
import type { NoteDetails } from './types/note-details'
|
||||
import type { Reducer } from 'redux'
|
||||
|
||||
export const NoteDetailsReducer: Reducer<NoteDetails, NoteDetailsActions> = (
|
||||
state: NoteDetails = initialState,
|
||||
export const NoteDetailsReducer: Reducer<OptionalNoteDetails, NoteDetailsActions> = (
|
||||
state: OptionalNoteDetails = null,
|
||||
action: NoteDetailsActions
|
||||
) => {
|
||||
switch (action.type) {
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { NoteDetails } from '../types/note-details'
|
||||
import type { OptionalNoteDetails } from '../types'
|
||||
import { generateNoteTitle } from '@hedgedoc/commons'
|
||||
|
||||
/**
|
||||
|
@ -12,7 +12,13 @@ import { generateNoteTitle } from '@hedgedoc/commons'
|
|||
* @param firstHeading The first heading of the document. Should be {@link undefined} if there is no such heading.
|
||||
* @return An updated {@link NoteDetails} redux state.
|
||||
*/
|
||||
export const buildStateFromFirstHeadingUpdate = (state: NoteDetails, firstHeading?: string): NoteDetails => {
|
||||
export const buildStateFromFirstHeadingUpdate = (
|
||||
state: OptionalNoteDetails,
|
||||
firstHeading?: string
|
||||
): OptionalNoteDetails => {
|
||||
if (state === null) {
|
||||
return null
|
||||
}
|
||||
return {
|
||||
...state,
|
||||
firstHeading: firstHeading,
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { NoteMetadata } from '../../../api/notes/types'
|
||||
import type { NoteDetails } from '../types/note-details'
|
||||
import type { OptionalNoteDetails } from '../types'
|
||||
import { DateTime } from 'luxon'
|
||||
|
||||
/**
|
||||
|
@ -13,7 +13,13 @@ import { DateTime } from 'luxon'
|
|||
* @param noteMetadata The updated metadata from the API.
|
||||
* @return An updated {@link NoteDetails} redux state.
|
||||
*/
|
||||
export const buildStateFromMetadataUpdate = (state: NoteDetails, noteMetadata: NoteMetadata): NoteDetails => {
|
||||
export const buildStateFromMetadataUpdate = (
|
||||
state: OptionalNoteDetails,
|
||||
noteMetadata: NoteMetadata
|
||||
): OptionalNoteDetails => {
|
||||
if (state === null) {
|
||||
return null
|
||||
}
|
||||
return {
|
||||
...state,
|
||||
updateUsername: noteMetadata.updateUsername,
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { NoteDetails } from '../types/note-details'
|
||||
import type { OptionalNoteDetails } from '../types'
|
||||
import type { NotePermissions } from '@hedgedoc/commons'
|
||||
|
||||
/**
|
||||
|
@ -12,9 +12,12 @@ import type { NotePermissions } from '@hedgedoc/commons'
|
|||
* @param serverPermissions The updated NotePermissions data.
|
||||
*/
|
||||
export const buildStateFromServerPermissions = (
|
||||
state: NoteDetails,
|
||||
state: OptionalNoteDetails,
|
||||
serverPermissions: NotePermissions
|
||||
): NoteDetails => {
|
||||
): OptionalNoteDetails => {
|
||||
if (state === null) {
|
||||
return null
|
||||
}
|
||||
return {
|
||||
...state,
|
||||
permissions: serverPermissions
|
||||
|
|
|
@ -7,6 +7,7 @@ import type { Note } from '../../../api/notes/types'
|
|||
import { buildStateFromUpdatedMarkdownContent } from '../build-state-from-updated-markdown-content'
|
||||
import { calculateLineStartIndexes } from '../calculate-line-start-indexes'
|
||||
import { initialState } from '../initial-state'
|
||||
import type { OptionalNoteDetails } from '../types'
|
||||
import type { NoteDetails } from '../types/note-details'
|
||||
import { buildStateFromMetadataUpdate } from './build-state-from-metadata-update'
|
||||
|
||||
|
@ -15,7 +16,7 @@ import { buildStateFromMetadataUpdate } from './build-state-from-metadata-update
|
|||
* @param dto The first DTO received from the API containing the relevant information about the note.
|
||||
* @return An updated {@link NoteDetails} redux state.
|
||||
*/
|
||||
export const buildStateFromServerDto = (dto: Note): NoteDetails => {
|
||||
export const buildStateFromServerDto = (dto: Note): OptionalNoteDetails => {
|
||||
const newState = convertNoteDtoToNoteDetails(dto)
|
||||
return buildStateFromUpdatedMarkdownContent(newState, newState.markdownContent.plain)
|
||||
}
|
||||
|
@ -28,6 +29,9 @@ export const buildStateFromServerDto = (dto: Note): NoteDetails => {
|
|||
*/
|
||||
const convertNoteDtoToNoteDetails = (note: Note): NoteDetails => {
|
||||
const stateWithMetadata = buildStateFromMetadataUpdate(initialState, note.metadata)
|
||||
if (stateWithMetadata === null) {
|
||||
throw new Error('no note details!')
|
||||
}
|
||||
const newLines = note.content.split('\n')
|
||||
return {
|
||||
...stateWithMetadata,
|
||||
|
|
|
@ -4,9 +4,15 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { CursorSelection } from '../../../components/editor-page/editor-pane/tool-bar/formatters/types/cursor-selection'
|
||||
import type { NoteDetails } from '../types/note-details'
|
||||
import type { OptionalNoteDetails } from '../types'
|
||||
|
||||
export const buildStateFromUpdateCursorPosition = (state: NoteDetails, selection: CursorSelection): NoteDetails => {
|
||||
export const buildStateFromUpdateCursorPosition = (
|
||||
state: OptionalNoteDetails,
|
||||
selection: CursorSelection
|
||||
): OptionalNoteDetails => {
|
||||
if (state === null) {
|
||||
return null
|
||||
}
|
||||
const correctedSelection = isFromAfterTo(selection)
|
||||
? {
|
||||
to: selection.from,
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
import type { Note, NoteMetadata } from '../../api/notes/types'
|
||||
import type { CursorSelection } from '../../components/editor-page/editor-pane/tool-bar/formatters/types/cursor-selection'
|
||||
import type { NoteDetails } from './types/note-details'
|
||||
import type { NotePermissions } from '@hedgedoc/commons'
|
||||
import type { Action } from 'redux'
|
||||
|
||||
|
@ -69,3 +70,5 @@ export interface UpdateMetadataAction extends Action<NoteDetailsActionType> {
|
|||
type: NoteDetailsActionType.UPDATE_METADATA
|
||||
updatedMetadata: NoteMetadata
|
||||
}
|
||||
|
||||
export type OptionalNoteDetails = NoteDetails | null
|
||||
|
|
Loading…
Reference in a new issue