nullable note details

Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
Tilman Vatteroth 2023-03-26 10:27:57 +02:00
parent 4fbe813af0
commit a05b387ee1
No known key found for this signature in database
GPG key ID: 42498463316F048B
58 changed files with 194 additions and 125 deletions

View file

@ -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 (

View file

@ -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 />

View file

@ -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'>

View file

@ -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'>

View file

@ -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('')

View file

@ -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

View file

@ -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}>

View file

@ -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 (

View file

@ -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) {

View file

@ -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()

View file

@ -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(() => {

View file

@ -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>

View file

@ -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()

View file

@ -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(() => {

View file

@ -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(() => {

View file

@ -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,

View file

@ -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(() => {

View file

@ -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 () => {

View file

@ -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)

View file

@ -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'}>

View file

@ -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)

View file

@ -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
})
}

View file

@ -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

View file

@ -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)

View file

@ -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 }) => [

View file

@ -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)

View file

@ -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(() => {

View file

@ -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()

View file

@ -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(() => {

View file

@ -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 (

View file

@ -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(() => {

View file

@ -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]

View file

@ -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() === '') {

View file

@ -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() !== '')

View file

@ -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[]>([])

View file

@ -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

View file

@ -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

View file

@ -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()

View file

@ -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])

View file

@ -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()

View file

@ -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(

View file

@ -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([])

View file

@ -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])
}

View file

@ -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])
}

View 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
}

View file

@ -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
}

View file

@ -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])
}

View file

@ -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)

View file

@ -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
}

View file

@ -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'))
}

View file

@ -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

View file

@ -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) {

View file

@ -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,

View file

@ -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,

View file

@ -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

View file

@ -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,

View file

@ -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,

View file

@ -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