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 * 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 { InternalLink } from '../common/links/internal-link'
import { ShowIf } from '../common/show-if/show-if' import { ShowIf } from '../common/show-if/show-if'
import { NoteInfoLineCreated } from '../editor-page/document-bar/note-info/note-info-line-created' 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 = () => { export const DocumentInfobar: React.FC = () => {
const { t } = useTranslation() 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. // TODO Check permissions ("writability") of note and show edit link depending on that.
return ( return (

View file

@ -4,6 +4,7 @@
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import { useApplicationState } from '../../../hooks/common/use-application-state' 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 { NewNoteButton } from '../../common/new-note-button/new-note-button'
import { ShowIf } from '../../common/show-if/show-if' import { ShowIf } from '../../common/show-if/show-if'
import { SignInButton } from '../../landing-layout/navigation/sign-in-button' import { SignInButton } from '../../landing-layout/navigation/sign-in-button'
@ -34,17 +35,17 @@ export interface AppBarProps {
*/ */
export const AppBar: React.FC<AppBarProps> = ({ mode }) => { export const AppBar: React.FC<AppBarProps> = ({ mode }) => {
const userExists = useApplicationState((state) => !!state.user) const userExists = useApplicationState((state) => !!state.user)
const noteFrontmatter = useApplicationState((state) => state.noteDetails.frontmatter) const noteFrontmatter = useNoteDetails()
return ( return (
<Navbar expand={true} className={'bg-light px-3'}> <Navbar expand={true} className={'bg-light px-3'}>
<Nav className='me-auto d-flex align-items-center'> <Nav className='me-auto d-flex align-items-center'>
<NavbarBranding /> <NavbarBranding />
<ShowIf condition={mode === AppBarMode.EDITOR}> <ShowIf condition={mode === AppBarMode.EDITOR}>
<ShowIf condition={noteFrontmatter.type === NoteType.SLIDE}> <ShowIf condition={noteFrontmatter.frontmatter.type === NoteType.SLIDE}>
<SlideModeButton /> <SlideModeButton />
</ShowIf> </ShowIf>
<ShowIf condition={noteFrontmatter.type !== NoteType.SLIDE}> <ShowIf condition={noteFrontmatter.frontmatter.type !== NoteType.SLIDE}>
<ReadOnlyModeButton /> <ReadOnlyModeButton />
</ShowIf> </ShowIf>
<HelpButton /> <HelpButton />

View file

@ -3,7 +3,7 @@
* *
* SPDX-License-Identifier: AGPL-3.0-only * 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 { UiIcon } from '../../common/icons/ui-icon'
import Link from 'next/link' import Link from 'next/link'
import React from 'react' import React from 'react'
@ -16,7 +16,7 @@ import { useTranslation } from 'react-i18next'
*/ */
export const ReadOnlyModeButton: React.FC = () => { export const ReadOnlyModeButton: React.FC = () => {
const { t } = useTranslation() const { t } = useTranslation()
const noteIdentifier = useApplicationState((state) => state.noteDetails.primaryAddress) const noteIdentifier = useNoteDetails().primaryAddress
return ( return (
<Link href={`/s/${noteIdentifier}`} target='_blank'> <Link href={`/s/${noteIdentifier}`} target='_blank'>

View file

@ -3,7 +3,7 @@
* *
* SPDX-License-Identifier: AGPL-3.0-only * 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 { UiIcon } from '../../common/icons/ui-icon'
import Link from 'next/link' import Link from 'next/link'
import React from 'react' import React from 'react'
@ -16,7 +16,7 @@ import { useTranslation } from 'react-i18next'
*/ */
export const SlideModeButton: React.FC = () => { export const SlideModeButton: React.FC = () => {
const { t } = useTranslation() const { t } = useTranslation()
const noteIdentifier = useApplicationState((state) => state.noteDetails.primaryAddress) const noteIdentifier = useNoteDetails().primaryAddress
return ( return (
<Link href={`/p/${noteIdentifier}`} target='_blank'> <Link href={`/p/${noteIdentifier}`} target='_blank'>

View file

@ -4,8 +4,8 @@
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import { addAlias } from '../../../../api/alias' import { addAlias } from '../../../../api/alias'
import { useApplicationState } from '../../../../hooks/common/use-application-state'
import { useIsOwner } from '../../../../hooks/common/use-is-owner' 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 { useOnInputChange } from '../../../../hooks/common/use-on-input-change'
import { updateMetadata } from '../../../../redux/note-details/methods' import { updateMetadata } from '../../../../redux/note-details/methods'
import { testId } from '../../../../utils/test-id' import { testId } from '../../../../utils/test-id'
@ -25,7 +25,7 @@ const validAliasRegex = /^[a-z0-9_-]*$/
export const AliasesAddForm: React.FC = () => { export const AliasesAddForm: React.FC = () => {
const { t } = useTranslation() const { t } = useTranslation()
const { showErrorNotification } = useUiNotifications() const { showErrorNotification } = useUiNotifications()
const noteId = useApplicationState((state) => state.noteDetails.id) const noteId = useNoteDetails().id
const isOwner = useIsOwner() const isOwner = useIsOwner()
const [newAlias, setNewAlias] = useState('') const [newAlias, setNewAlias] = useState('')

View file

@ -3,8 +3,7 @@
* *
* SPDX-License-Identifier: AGPL-3.0-only * 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 { ApplicationState } from '../../../../redux/application-state'
import { AliasesListEntry } from './aliases-list-entry' import { AliasesListEntry } from './aliases-list-entry'
import React, { Fragment, useMemo } from 'react' import React, { Fragment, useMemo } from 'react'
@ -12,7 +11,7 @@ import React, { Fragment, useMemo } from 'react'
* Renders the list of aliases. * Renders the list of aliases.
*/ */
export const AliasesList: React.FC = () => { export const AliasesList: React.FC = () => {
const aliases = useApplicationState((state: ApplicationState) => state.noteDetails.aliases) const aliases = useNoteDetails().aliases
const aliasesDom = useMemo(() => { const aliasesDom = useMemo(() => {
return aliases return aliases

View file

@ -3,7 +3,7 @@
* *
* SPDX-License-Identifier: AGPL-3.0-only * 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 { NoteInfoLine } from './note-info-line'
import { UnitalicBoldContent } from './unitalic-bold-content' import { UnitalicBoldContent } from './unitalic-bold-content'
import React from 'react' 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. * Renders an info line about the number of contributors for the note.
*/ */
export const NoteInfoLineContributors: React.FC = () => { export const NoteInfoLineContributors: React.FC = () => {
const contributors = useApplicationState((state) => state.noteDetails.editedBy.length) const contributors = useNoteDetails().editedBy.length
return ( return (
<NoteInfoLine icon={IconPeople} size={2}> <NoteInfoLine icon={IconPeople} size={2}>

View file

@ -3,7 +3,7 @@
* *
* SPDX-License-Identifier: AGPL-3.0-only * 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 { NoteInfoLine } from './note-info-line'
import type { NoteInfoTimeLineProps } from './note-info-time-line' import type { NoteInfoTimeLineProps } from './note-info-time-line'
import { UnitalicBoldTimeFromNow } from './utils/unitalic-bold-time-from-now' 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. * @param size The size in which the line should be displayed.
*/ */
export const NoteInfoLineCreated: React.FC<NoteInfoTimeLineProps> = ({ size }) => { 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]) const noteCreateDateTime = useMemo(() => DateTime.fromSeconds(noteCreateTime), [noteCreateTime])
return ( return (

View file

@ -3,7 +3,7 @@
* *
* SPDX-License-Identifier: AGPL-3.0-only * 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 { UserAvatarForUsername } from '../../../common/user-avatar/user-avatar-for-username'
import { NoteInfoLine } from './note-info-line' import { NoteInfoLine } from './note-info-line'
import type { NoteInfoTimeLineProps } from './note-info-time-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 }) => { export const NoteInfoLineUpdated: React.FC<NoteInfoTimeLineProps> = ({ size }) => {
useTranslation() useTranslation()
const noteUpdateTime = useApplicationState((state) => state.noteDetails.updatedAt) const noteUpdateTime = useNoteDetails().updatedAt
const noteUpdateDateTime = useMemo(() => DateTime.fromSeconds(noteUpdateTime), [noteUpdateTime]) const noteUpdateDateTime = useMemo(() => DateTime.fromSeconds(noteUpdateTime), [noteUpdateTime])
const noteUpdateUser = useApplicationState((state) => state.noteDetails.updateUsername) const noteUpdateUser = useNoteDetails().updateUsername
const userBlock = useMemo(() => { const userBlock = useMemo(() => {
if (!noteUpdateUser) { if (!noteUpdateUser) {

View file

@ -4,7 +4,7 @@
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import { removeGroupPermission, setGroupPermission } from '../../../../api/permissions' 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 { setNotePermissionsFromServer } from '../../../../redux/note-details/methods'
import { IconButton } from '../../../common/icon-button/icon-button' import { IconButton } from '../../../common/icon-button/icon-button'
import { useUiNotifications } from '../../../notifications/ui-notification-boundary' 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 { AccessLevel, SpecialGroup } from '@hedgedoc/commons'
import React, { useCallback, useMemo } from 'react' import React, { useCallback, useMemo } from 'react'
import { ToggleButtonGroup } from 'react-bootstrap' import { ToggleButtonGroup } from 'react-bootstrap'
import { Eye as IconEye } from 'react-bootstrap-icons' import { Eye as IconEye, Pencil as IconPencil, SlashCircle as IconSlashCircle } from 'react-bootstrap-icons'
import { Pencil as IconPencil } from 'react-bootstrap-icons'
import { SlashCircle as IconSlashCircle } from 'react-bootstrap-icons'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
export interface PermissionEntrySpecialGroupProps { export interface PermissionEntrySpecialGroupProps {
@ -34,7 +32,7 @@ export const PermissionEntrySpecialGroup: React.FC<PermissionEntrySpecialGroupPr
type, type,
disabled disabled
}) => { }) => {
const noteId = useApplicationState((state) => state.noteDetails.primaryAddress) const noteId = useNoteDetails().primaryAddress
const { t } = useTranslation() const { t } = useTranslation()
const { showErrorNotification } = useUiNotifications() const { showErrorNotification } = useUiNotifications()

View file

@ -5,7 +5,7 @@
*/ */
import { removeUserPermission, setUserPermission } from '../../../../api/permissions' import { removeUserPermission, setUserPermission } from '../../../../api/permissions'
import { getUser } from '../../../../api/users' 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 { setNotePermissionsFromServer } from '../../../../redux/note-details/methods'
import { ShowIf } from '../../../common/show-if/show-if' import { ShowIf } from '../../../common/show-if/show-if'
import { UserAvatarForUser } from '../../../common/user-avatar/user-avatar-for-user' import { UserAvatarForUser } from '../../../common/user-avatar/user-avatar-for-user'
@ -31,7 +31,7 @@ export const PermissionEntryUser: React.FC<PermissionEntryUserProps & Permission
entry, entry,
disabled disabled
}) => { }) => {
const noteId = useApplicationState((state) => state.noteDetails.primaryAddress) const noteId = useNoteDetails().primaryAddress
const { showErrorNotification } = useUiNotifications() const { showErrorNotification } = useUiNotifications()
const onRemoveEntry = useCallback(() => { const onRemoveEntry = useCallback(() => {

View file

@ -3,7 +3,7 @@
* *
* SPDX-License-Identifier: AGPL-3.0-only * 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 { UiIcon } from '../../../common/icons/ui-icon'
import { UserAvatarForUsername } from '../../../common/user-avatar/user-avatar-for-username' import { UserAvatarForUsername } from '../../../common/user-avatar/user-avatar-for-username'
import type { PermissionDisabledProps } from './permission-disabled.prop' import type { PermissionDisabledProps } from './permission-disabled.prop'
@ -27,7 +27,7 @@ export const PermissionOwnerInfo: React.FC<PermissionOwnerInfoProps & Permission
disabled disabled
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
const noteOwner = useApplicationState((state) => state.noteDetails.permissions.owner) const noteOwner = useNoteDetails().permissions.owner
return ( return (
<Fragment> <Fragment>

View file

@ -4,7 +4,7 @@
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import { setNoteOwner } from '../../../../api/permissions' 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 { setNotePermissionsFromServer } from '../../../../redux/note-details/methods'
import { useUiNotifications } from '../../../notifications/ui-notification-boundary' import { useUiNotifications } from '../../../notifications/ui-notification-boundary'
import type { PermissionDisabledProps } from './permission-disabled.prop' 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. * @param disabled If the user is not the owner, functionality is disabled.
*/ */
export const PermissionSectionOwner: React.FC<PermissionDisabledProps> = ({ 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 [changeOwner, setChangeOwner] = useState(false)
const { showErrorNotification } = useUiNotifications() const { showErrorNotification } = useUiNotifications()

View file

@ -3,8 +3,8 @@
* *
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import { useApplicationState } from '../../../../hooks/common/use-application-state'
import { useIsOwner } from '../../../../hooks/common/use-is-owner' import { useIsOwner } from '../../../../hooks/common/use-is-owner'
import { useNoteDetails } from '../../../../hooks/common/use-note-details'
import type { PermissionDisabledProps } from './permission-disabled.prop' import type { PermissionDisabledProps } from './permission-disabled.prop'
import { PermissionEntrySpecialGroup } from './permission-entry-special-group' import { PermissionEntrySpecialGroup } from './permission-entry-special-group'
import { AccessLevel, SpecialGroup } from '@hedgedoc/commons' import { AccessLevel, SpecialGroup } from '@hedgedoc/commons'
@ -18,7 +18,7 @@ import { Trans, useTranslation } from 'react-i18next'
*/ */
export const PermissionSectionSpecialGroups: React.FC<PermissionDisabledProps> = ({ disabled }) => { export const PermissionSectionSpecialGroups: React.FC<PermissionDisabledProps> = ({ disabled }) => {
useTranslation() useTranslation()
const groupPermissions = useApplicationState((state) => state.noteDetails.permissions.sharedToGroups) const groupPermissions = useNoteDetails().permissions.sharedToGroups
const isOwner = useIsOwner() const isOwner = useIsOwner()
const specialGroupEntries = useMemo(() => { const specialGroupEntries = useMemo(() => {

View file

@ -4,7 +4,7 @@
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import { setUserPermission } from '../../../../api/permissions' 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 { setNotePermissionsFromServer } from '../../../../redux/note-details/methods'
import { useUiNotifications } from '../../../notifications/ui-notification-boundary' import { useUiNotifications } from '../../../notifications/ui-notification-boundary'
import { PermissionAddEntryField } from './permission-add-entry-field' import { PermissionAddEntryField } from './permission-add-entry-field'
@ -20,8 +20,8 @@ import { Trans, useTranslation } from 'react-i18next'
*/ */
export const PermissionSectionUsers: React.FC<PermissionDisabledProps> = ({ disabled }) => { export const PermissionSectionUsers: React.FC<PermissionDisabledProps> = ({ disabled }) => {
useTranslation() useTranslation()
const userPermissions = useApplicationState((state) => state.noteDetails.permissions.sharedToUsers) const userPermissions = useNoteDetails().permissions.sharedToUsers
const noteId = useApplicationState((state) => state.noteDetails.primaryAddress) const noteId = useNoteDetails().primaryAddress
const { showErrorNotification } = useUiNotifications() const { showErrorNotification } = useUiNotifications()
const userEntries = useMemo(() => { const userEntries = useMemo(() => {

View file

@ -4,7 +4,7 @@
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import { getAllRevisions } from '../../../../api/revisions' 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 { AsyncLoadingBoundary } from '../../../common/async-loading-boundary/async-loading-boundary'
import { RevisionListEntry } from './revision-list-entry' import { RevisionListEntry } from './revision-list-entry'
import { DateTime } from 'luxon' import { DateTime } from 'luxon'
@ -24,7 +24,7 @@ export interface RevisionListProps {
* @param onRevisionSelect Callback that is executed when a list entry is selected * @param onRevisionSelect Callback that is executed when a list entry is selected
*/ */
export const RevisionList: React.FC<RevisionListProps> = ({ selectedRevisionId, onRevisionSelect }) => { export const RevisionList: React.FC<RevisionListProps> = ({ selectedRevisionId, onRevisionSelect }) => {
const noteIdentifier = useApplicationState((state) => state.noteDetails.primaryAddress) const noteIdentifier = useNoteDetails().primaryAddress
const { const {
value: revisions, value: revisions,

View file

@ -4,7 +4,7 @@
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import { getRevision } from '../../../../api/revisions' 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 type { ModalVisibilityProps } from '../../../common/modals/common-modal'
import { useUiNotifications } from '../../../notifications/ui-notification-boundary' import { useUiNotifications } from '../../../notifications/ui-notification-boundary'
import { downloadRevision } from './utils' import { downloadRevision } from './utils'
@ -28,7 +28,7 @@ export const RevisionModalFooter: React.FC<RevisionModalFooterProps & Pick<Modal
onHide onHide
}) => { }) => {
useTranslation() useTranslation()
const noteIdentifier = useApplicationState((state) => state.noteDetails.primaryAddress) const noteIdentifier = useNoteDetails().primaryAddress
const { showErrorNotification } = useUiNotifications() const { showErrorNotification } = useUiNotifications()
const onRevertToRevision = useCallback(() => { const onRevertToRevision = useCallback(() => {

View file

@ -4,8 +4,8 @@
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import { getRevision } from '../../../../api/revisions' import { getRevision } from '../../../../api/revisions'
import { useApplicationState } from '../../../../hooks/common/use-application-state'
import { useDarkModeState } from '../../../../hooks/common/use-dark-mode-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 { AsyncLoadingBoundary } from '../../../common/async-loading-boundary/async-loading-boundary'
import { invertUnifiedPatch } from './invert-unified-patch' import { invertUnifiedPatch } from './invert-unified-patch'
import { Optional } from '@mrdrogdrog/optional' import { Optional } from '@mrdrogdrog/optional'
@ -25,7 +25,7 @@ export interface RevisionViewerProps {
* @param allRevisions List of metadata for all available revisions. * @param allRevisions List of metadata for all available revisions.
*/ */
export const RevisionViewer: React.FC<RevisionViewerProps> = ({ selectedRevisionId }) => { export const RevisionViewer: React.FC<RevisionViewerProps> = ({ selectedRevisionId }) => {
const noteIdentifier = useApplicationState((state) => state.noteDetails.primaryAddress) const noteIdentifier = useNoteDetails().primaryAddress
const darkModeEnabled = useDarkModeState() const darkModeEnabled = useDarkModeState()
const { value, error, loading } = useAsync(async () => { const { value, error, loading } = useAsync(async () => {

View file

@ -3,8 +3,8 @@
* *
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import { useApplicationState } from '../../../../hooks/common/use-application-state'
import { useBaseUrl } from '../../../../hooks/common/use-base-url' 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 { CopyableField } from '../../../common/copyable/copyable-field/copyable-field'
import React, { useMemo } from 'react' import React, { useMemo } from 'react'
@ -24,7 +24,7 @@ export interface LinkFieldProps {
*/ */
export const NoteUrlField: React.FC<LinkFieldProps> = ({ type }) => { export const NoteUrlField: React.FC<LinkFieldProps> = ({ type }) => {
const baseUrl = useBaseUrl() const baseUrl = useBaseUrl()
const noteIdentifier = useApplicationState((state) => state.noteDetails.primaryAddress) const noteIdentifier = useNoteDetails().primaryAddress
const url = useMemo(() => { const url = useMemo(() => {
const url = new URL(baseUrl) const url = new URL(baseUrl)

View file

@ -3,7 +3,7 @@
* *
* SPDX-License-Identifier: AGPL-3.0-only * 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 type { ModalVisibilityProps } from '../../../common/modals/common-modal'
import { CommonModal } from '../../../common/modals/common-modal' import { CommonModal } from '../../../common/modals/common-modal'
import { ShowIf } from '../../../common/show-if/show-if' import { ShowIf } from '../../../common/show-if/show-if'
@ -21,7 +21,7 @@ import { Trans, useTranslation } from 'react-i18next'
*/ */
export const ShareModal: React.FC<ModalVisibilityProps> = ({ show, onHide }) => { export const ShareModal: React.FC<ModalVisibilityProps> = ({ show, onHide }) => {
useTranslation() useTranslation()
const noteFrontmatter = useApplicationState((state) => state.noteDetails.frontmatter) const noteFrontmatter = useNoteDetails().frontmatter
return ( return (
<CommonModal show={show} onHide={onHide} showCloseButton={true} titleI18nKey={'editor.modal.shareLink.title'}> <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 * 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 { useTrimmedNoteMarkdownContentWithoutFrontmatter } from '../../../hooks/common/use-trimmed-note-markdown-content-without-frontmatter'
import { setRendererStatus } from '../../../redux/renderer-status/methods' import { setRendererStatus } from '../../../redux/renderer-status/methods'
import { RendererType } from '../../render-page/window-post-message-communicator/rendering-message' 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 }) => { export const EditorDocumentRenderer: React.FC<EditorDocumentRendererProps> = ({ scrollState, onScroll, ...props }) => {
const trimmedContentLines = useTrimmedNoteMarkdownContentWithoutFrontmatter() const trimmedContentLines = useTrimmedNoteMarkdownContentWithoutFrontmatter()
const noteType: NoteType = useApplicationState((state) => state.noteDetails.frontmatter.type) const noteType: NoteType = useNoteDetails().frontmatter.type
const adjustedOnScroll = useOnScrollWithLineOffset(onScroll) const adjustedOnScroll = useOnScrollWithLineOffset(onScroll)
const adjustedScrollState = useScrollStateWithoutLineOffset(scrollState) const adjustedScrollState = useScrollStateWithoutLineOffset(scrollState)

View file

@ -4,8 +4,7 @@
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import { getGlobalState } from '../../../../redux' import { getGlobalState } from '../../../../redux'
import type { ScrollState } from '../../synced-scroll/scroll-props' import type { ScrollCallback, ScrollState } from '../../synced-scroll/scroll-props'
import type { ScrollCallback } from '../../synced-scroll/scroll-props'
import { useMemo } from 'react' import { useMemo } from 'react'
/** /**
@ -22,7 +21,7 @@ export const useOnScrollWithLineOffset = (onScroll: ScrollCallback | undefined):
return (scrollState: ScrollState) => { return (scrollState: ScrollState) => {
onScroll({ onScroll({
firstLineInView: firstLineInView:
scrollState.firstLineInView + getGlobalState().noteDetails.frontmatterRendererInfo.lineOffset, scrollState.firstLineInView + (getGlobalState().noteDetails?.frontmatterRendererInfo.lineOffset ?? 0),
scrolledPercentage: scrollState.scrolledPercentage scrolledPercentage: scrollState.scrolledPercentage
}) })
} }

View file

@ -3,7 +3,7 @@
* *
* SPDX-License-Identifier: AGPL-3.0-only * 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 type { ScrollState } from '../../synced-scroll/scroll-props'
import { useMemo } from 'react' import { useMemo } from 'react'
@ -14,7 +14,7 @@ import { useMemo } from 'react'
* @return the adjusted scroll state without the line offset * @return the adjusted scroll state without the line offset
*/ */
export const useScrollStateWithoutLineOffset = (scrollState: ScrollState | undefined): ScrollState | undefined => { export const useScrollStateWithoutLineOffset = (scrollState: ScrollState | undefined): ScrollState | undefined => {
const lineOffset = useApplicationState((state) => state.noteDetails.frontmatterRendererInfo.lineOffset) const lineOffset = useNoteDetails().frontmatterRendererInfo.lineOffset
return useMemo(() => { return useMemo(() => {
return scrollState === undefined return scrollState === undefined
? undefined ? undefined

View file

@ -45,7 +45,7 @@ export const useOnImageUploadFromRenderer = (): void => {
const file = new File([blob], fileName, { type: blob.type }) const file = new File([blob], fileName, { type: blob.type })
const { cursorSelection, alt, title } = Optional.ofNullable(lineIndex) const { cursorSelection, alt, title } = Optional.ofNullable(lineIndex)
.flatMap((actualLineIndex) => { .flatMap((actualLineIndex) => {
const lineOffset = getGlobalState().noteDetails.frontmatterRendererInfo.lineOffset const lineOffset = getGlobalState().noteDetails?.frontmatterRendererInfo.lineOffset ?? 0
return findPlaceholderInMarkdownContent(actualLineIndex + lineOffset, placeholderIndexInLine) return findPlaceholderInMarkdownContent(actualLineIndex + lineOffset, placeholderIndexInLine)
}) })
.orElse({} as ExtractResult) .orElse({} as ExtractResult)
@ -73,6 +73,9 @@ export interface ExtractResult {
*/ */
const findPlaceholderInMarkdownContent = (lineIndex: number, replacementIndexInLine = 0): Optional<ExtractResult> => { const findPlaceholderInMarkdownContent = (lineIndex: number, replacementIndexInLine = 0): Optional<ExtractResult> => {
const noteDetails = getGlobalState().noteDetails const noteDetails = getGlobalState().noteDetails
if (noteDetails === null) {
throw new Error('no note details')
}
const currentMarkdownContentLines = noteDetails.markdownContent.lines const currentMarkdownContentLines = noteDetails.markdownContent.lines
return Optional.ofNullable(noteDetails.markdownContent.lineStartIndexes[lineIndex]).map((startIndexOfLine) => return Optional.ofNullable(noteDetails.markdownContent.lineStartIndexes[lineIndex]).map((startIndexOfLine) =>
findImagePlaceholderInLine(currentMarkdownContentLines[lineIndex], startIndexOfLine, replacementIndexInLine) findImagePlaceholderInLine(currentMarkdownContentLines[lineIndex], startIndexOfLine, replacementIndexInLine)

View file

@ -50,11 +50,14 @@ export const useHandleUpload = (): handleUploadSignature => {
: t('editor.upload.uploadFile.withoutDescription', { fileName: file.name }) : t('editor.upload.uploadFile.withoutDescription', { fileName: file.name })
const uploadPlaceholder = `![${uploadFileInfo}](upload-${randomId}${additionalUrlText ?? ''})` 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 }) => { changeContent(({ currentSelection }) => {
return replaceSelection(cursorSelection ?? currentSelection, uploadPlaceholder, false) return replaceSelection(cursorSelection ?? currentSelection, uploadPlaceholder, false)
}) })
uploadFile(noteId, file) uploadFile(noteDetails.id, file)
.then(({ url }) => { .then(({ url }) => {
const replacement = `![${description ?? file.name ?? ''}](${url}${additionalUrlText ?? ''})` const replacement = `![${description ?? file.name ?? ''}](${url}${additionalUrlText ?? ''})`
changeContent(({ markdownContent }) => [ changeContent(({ markdownContent }) => [

View file

@ -3,7 +3,7 @@
* *
* SPDX-License-Identifier: AGPL-3.0-only * 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' import { useMemo } from 'react'
export interface LineBasedPosition { export interface LineBasedPosition {
@ -24,8 +24,8 @@ const calculateLineBasedPosition = (absolutePosition: number, lineStartIndexes:
* Returns the line+character based position of the to-cursor, if available. * Returns the line+character based position of the to-cursor, if available.
*/ */
export const useLineBasedToPosition = (): LineBasedPosition | undefined => { export const useLineBasedToPosition = (): LineBasedPosition | undefined => {
const lineStartIndexes = useApplicationState((state) => state.noteDetails.markdownContent.lineStartIndexes) const lineStartIndexes = useNoteDetails().markdownContent.lineStartIndexes
const selection = useApplicationState((state) => state.noteDetails.selection) const selection = useNoteDetails().selection
return useMemo(() => { return useMemo(() => {
const to = selection.to const to = selection.to
@ -40,8 +40,8 @@ export const useLineBasedToPosition = (): LineBasedPosition | undefined => {
* Returns the line+character based position of the from-cursor. * Returns the line+character based position of the from-cursor.
*/ */
export const useLineBasedFromPosition = (): LineBasedPosition => { export const useLineBasedFromPosition = (): LineBasedPosition => {
const lineStartIndexes = useApplicationState((state) => state.noteDetails.markdownContent.lineStartIndexes) const lineStartIndexes = useNoteDetails().markdownContent.lineStartIndexes
const selection = useApplicationState((state) => state.noteDetails.selection) const selection = useNoteDetails().selection
return useMemo(() => { return useMemo(() => {
return calculateLineBasedPosition(selection.from, lineStartIndexes) return calculateLineBasedPosition(selection.from, lineStartIndexes)

View file

@ -3,7 +3,7 @@
* *
* SPDX-License-Identifier: AGPL-3.0-only * 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 { Logger } from '../../../../../utils/logger'
import { useUiNotifications } from '../../../../notifications/ui-notification-boundary' import { useUiNotifications } from '../../../../notifications/ui-notification-boundary'
import type { MessageTransporter } from '@hedgedoc/commons' import type { MessageTransporter } from '@hedgedoc/commons'
@ -21,7 +21,7 @@ const logger = new Logger('UseOnNoteDeleted')
*/ */
export const useOnNoteDeleted = (websocketConnection: MessageTransporter): void => { export const useOnNoteDeleted = (websocketConnection: MessageTransporter): void => {
const router = useRouter() const router = useRouter()
const noteTitle = useApplicationState((state) => state.noteDetails.title) const noteTitle = useNoteDetails().title
const { dispatchUiNotification } = useUiNotifications() const { dispatchUiNotification } = useUiNotifications()
const noteDeletedHandler = useCallback(() => { const noteDeletedHandler = useCallback(() => {

View file

@ -28,7 +28,7 @@ export const useRealtimeConnection = (): MessageTransporter => {
const messageTransporter = useMemo(() => { const messageTransporter = useMemo(() => {
if (isMockMode) { if (isMockMode) {
logger.debug('Creating Loopback connection...') logger.debug('Creating Loopback connection...')
return new MockedBackendMessageTransporter(getGlobalState().noteDetails.markdownContent.plain) return new MockedBackendMessageTransporter(getGlobalState().noteDetails?.markdownContent.plain ?? '')
} else { } else {
logger.debug('Creating Websocket connection...') logger.debug('Creating Websocket connection...')
return new WebsocketTransporter() return new WebsocketTransporter()

View file

@ -3,8 +3,8 @@
* *
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import { useApplicationState } from '../../../../../hooks/common/use-application-state'
import { useBaseUrl } from '../../../../../hooks/common/use-base-url' import { useBaseUrl } from '../../../../../hooks/common/use-base-url'
import { useNoteDetails } from '../../../../../hooks/common/use-note-details'
import { isMockMode } from '../../../../../utils/test-modes' import { isMockMode } from '../../../../../utils/test-modes'
import { useMemo } from 'react' import { useMemo } from 'react'
@ -14,7 +14,7 @@ const LOCAL_FALLBACK_URL = 'ws://localhost:8080/realtime/'
* Provides the URL for the realtime endpoint. * Provides the URL for the realtime endpoint.
*/ */
export const useWebsocketUrl = (): URL | undefined => { export const useWebsocketUrl = (): URL | undefined => {
const noteId = useApplicationState((state) => state.noteDetails.id) const noteId = useNoteDetails().id
const baseUrl = useBaseUrl() const baseUrl = useBaseUrl()
const websocketUrl = useMemo(() => { const websocketUrl = useMemo(() => {

View file

@ -3,7 +3,7 @@
* *
* SPDX-License-Identifier: AGPL-3.0-only * 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 React, { useMemo } from 'react'
import { Trans, useTranslation } from 'react-i18next' import { Trans, useTranslation } from 'react-i18next'
@ -13,7 +13,7 @@ import { Trans, useTranslation } from 'react-i18next'
export const NumberOfLinesInDocumentInfo: React.FC = () => { export const NumberOfLinesInDocumentInfo: React.FC = () => {
useTranslation() useTranslation()
const linesInDocument = useApplicationState((state) => state.noteDetails.markdownContent.lines.length) const linesInDocument = useNoteDetails().markdownContent.lines.length
const translationOptions = useMemo(() => ({ lines: linesInDocument }), [linesInDocument]) const translationOptions = useMemo(() => ({ lines: linesInDocument }), [linesInDocument])
return ( return (

View file

@ -3,7 +3,7 @@
* *
* SPDX-License-Identifier: AGPL-3.0-only * 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 { cypressId } from '../../../../utils/cypress-attribute'
import { useFrontendConfig } from '../../../common/frontend-config-context/use-frontend-config' import { useFrontendConfig } from '../../../common/frontend-config-context/use-frontend-config'
import React, { useMemo } from 'react' import React, { useMemo } from 'react'
@ -16,7 +16,7 @@ export const RemainingCharactersInfo: React.FC = () => {
const { t } = useTranslation() const { t } = useTranslation()
const maxDocumentLength = useFrontendConfig().maxDocumentLength 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 remainingCharacters = useMemo(() => maxDocumentLength - contentLength, [contentLength, maxDocumentLength])
const remainingCharactersClass = useMemo(() => { const remainingCharactersClass = useMemo(() => {

View file

@ -3,7 +3,7 @@
* *
* SPDX-License-Identifier: AGPL-3.0-only * 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 { SeparatorDash } from './separator-dash'
import React, { Fragment, useMemo } from 'react' import React, { Fragment, useMemo } from 'react'
import { Trans, useTranslation } from 'react-i18next' import { Trans, useTranslation } from 'react-i18next'
@ -14,7 +14,7 @@ import { Trans, useTranslation } from 'react-i18next'
export const SelectedCharacters: React.FC = () => { export const SelectedCharacters: React.FC = () => {
useTranslation() useTranslation()
const selection = useApplicationState((state) => state.noteDetails.selection) const selection = useNoteDetails().selection
const count = useMemo( const count = useMemo(
() => (selection.to === undefined ? undefined : selection.to - selection.from), () => (selection.to === undefined ? undefined : selection.to - selection.from),
[selection.from, selection.to] [selection.from, selection.to]

View file

@ -3,7 +3,7 @@
* *
* SPDX-License-Identifier: AGPL-3.0-only * 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 Head from 'next/head'
import React, { useMemo } from 'react' 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. * Renders the license link tag if a license is set in the frontmatter.
*/ */
export const LicenseLinkHead: React.FC = () => { export const LicenseLinkHead: React.FC = () => {
const license = useApplicationState((state) => state.noteDetails.frontmatter.license) const license = useNoteDetails().frontmatter.license
const optionalLinkElement = useMemo(() => { const optionalLinkElement = useMemo(() => {
if (!license || license.trim() === '') { if (!license || license.trim() === '') {

View file

@ -3,7 +3,7 @@
* *
* SPDX-License-Identifier: AGPL-3.0-only * 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 { useNoteTitle } from '../../../hooks/common/use-note-title'
import Head from 'next/head' import Head from 'next/head'
import React, { useMemo } from 'react' import React, { useMemo } from 'react'
@ -13,7 +13,7 @@ import React, { useMemo } from 'react'
*/ */
export const OpengraphHead: React.FC = () => { export const OpengraphHead: React.FC = () => {
const noteTitle = useNoteTitle() const noteTitle = useNoteTitle()
const openGraphData = useApplicationState((state) => state.noteDetails.frontmatter.opengraph) const openGraphData = useNoteDetails().frontmatter.opengraph
const openGraphMetaElements = useMemo(() => { const openGraphMetaElements = useMemo(() => {
const elements = Object.entries(openGraphData) const elements = Object.entries(openGraphData)
.filter(([, value]) => value && String(value).trim() !== '') .filter(([, value]) => value && String(value).trim() !== '')

View file

@ -6,6 +6,7 @@
import type { HistoryEntryWithOrigin } from '../../../api/history/types' import type { HistoryEntryWithOrigin } from '../../../api/history/types'
import { HistoryEntryOrigin } from '../../../api/history/types' import { HistoryEntryOrigin } from '../../../api/history/types'
import { useApplicationState } from '../../../hooks/common/use-application-state' import { useApplicationState } from '../../../hooks/common/use-application-state'
import { useNoteDetails } from '../../../hooks/common/use-note-details'
import { getGlobalState } from '../../../redux' import { getGlobalState } from '../../../redux'
import { updateLocalHistoryEntry } from '../../../redux/history/methods' import { updateLocalHistoryEntry } from '../../../redux/history/methods'
import equal from 'fast-deep-equal' 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. * The entry is updated when the title or tags of the note change.
*/ */
export const useUpdateLocalHistoryEntry = (): void => { export const useUpdateLocalHistoryEntry = (): void => {
const id = useApplicationState((state) => state.noteDetails.id) const id = useNoteDetails().id
const userExists = useApplicationState((state) => !!state.user) const userExists = useApplicationState((state) => !!state.user)
const currentNoteTitle = useApplicationState((state) => state.noteDetails.title) const currentNoteTitle = useNoteDetails().title
const currentNoteTags = useApplicationState((state) => state.noteDetails.frontmatter.tags) const currentNoteTags = useNoteDetails().frontmatter.tags
const lastNoteTitle = useRef('') const lastNoteTitle = useRef('')
const lastNoteTags = useRef<string[]>([]) const lastNoteTags = useRef<string[]>([])

View file

@ -4,6 +4,7 @@
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import { useApplicationState } from '../../../../hooks/common/use-application-state' import { useApplicationState } from '../../../../hooks/common/use-application-state'
import { useNoteDetails } from '../../../../hooks/common/use-note-details'
import { DarkModePreference } from '../../../../redux/dark-mode/types' import { DarkModePreference } from '../../../../redux/dark-mode/types'
import { useSendToRenderer } from '../../../render-page/window-post-message-communicator/hooks/use-send-to-renderer' 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' import { CommunicationMessageType } from '../../../render-page/window-post-message-communicator/rendering-message'
@ -20,7 +21,7 @@ export const useSendAdditionalConfigurationToRenderer = (
forcedDarkMode: DarkModePreference = DarkModePreference.AUTO forcedDarkMode: DarkModePreference = DarkModePreference.AUTO
): void => { ): void => {
const darkModePreference = useApplicationState((state) => state.darkMode.darkModePreference) const darkModePreference = useApplicationState((state) => state.darkMode.darkModePreference)
const newlinesAreBreaks = useApplicationState((state) => state.noteDetails.frontmatter.newlinesAreBreaks) const newlinesAreBreaks = useNoteDetails().frontmatter.newlinesAreBreaks
const darkMode = useMemo(() => { const darkMode = useMemo(() => {
return forcedDarkMode === DarkModePreference.AUTO ? darkModePreference : forcedDarkMode return forcedDarkMode === DarkModePreference.AUTO ? darkModePreference : forcedDarkMode

View file

@ -3,7 +3,7 @@
* *
* SPDX-License-Identifier: AGPL-3.0-only * 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 { cypressId } from '../../../../utils/cypress-attribute'
import type { ModalVisibilityProps } from '../../../common/modals/common-modal' import type { ModalVisibilityProps } from '../../../common/modals/common-modal'
import { DeletionModal } from '../../../common/modals/deletion-modal' import { DeletionModal } from '../../../common/modals/deletion-modal'
@ -44,7 +44,7 @@ export const DeleteNoteModal: React.FC<DeleteNoteModalProps & DeleteHistoryNoteM
modalWarningI18nKey, modalWarningI18nKey,
modalButtonI18nKey modalButtonI18nKey
}) => { }) => {
const noteTitle = useApplicationState((state) => state.noteDetails.title) const noteTitle = useNoteDetails().title
return ( return (
<DeletionModal <DeletionModal

View file

@ -4,8 +4,8 @@
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import { deleteNote } from '../../../../api/notes' import { deleteNote } from '../../../../api/notes'
import { useApplicationState } from '../../../../hooks/common/use-application-state'
import { useBooleanState } from '../../../../hooks/common/use-boolean-state' import { useBooleanState } from '../../../../hooks/common/use-boolean-state'
import { useNoteDetails } from '../../../../hooks/common/use-note-details'
import { cypressId } from '../../../../utils/cypress-attribute' import { cypressId } from '../../../../utils/cypress-attribute'
import { Logger } from '../../../../utils/logger' import { Logger } from '../../../../utils/logger'
import { useUiNotifications } from '../../../notifications/ui-notification-boundary' 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 }) => { export const DeleteNoteSidebarEntry: React.FC<PropsWithChildren<SpecificSidebarEntryProps>> = ({ hide, className }) => {
useTranslation() useTranslation()
const router = useRouter() const router = useRouter()
const noteId = useApplicationState((state) => state.noteDetails.id) const noteId = useNoteDetails().id
const [modalVisibility, showModal, closeModal] = useBooleanState() const [modalVisibility, showModal, closeModal] = useBooleanState()
const { showErrorNotification } = useUiNotifications() const { showErrorNotification } = useUiNotifications()

View file

@ -20,7 +20,11 @@ export const ExportMarkdownSidebarEntry: React.FC = () => {
const { t } = useTranslation() const { t } = useTranslation()
const markdownContent = useNoteMarkdownContent() const markdownContent = useNoteMarkdownContent()
const onClick = useCallback(() => { 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') download(markdownContent, `${sanitized !== '' ? sanitized : t('editor.untitledNote')}.md`, 'text/markdown')
}, [markdownContent, t]) }, [markdownContent, t])

View file

@ -4,6 +4,7 @@
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import { useApplicationState } from '../../../../hooks/common/use-application-state' import { useApplicationState } from '../../../../hooks/common/use-application-state'
import { useNoteDetails } from '../../../../hooks/common/use-note-details'
import { toggleHistoryEntryPinning } from '../../../../redux/history/methods' import { toggleHistoryEntryPinning } from '../../../../redux/history/methods'
import { useUiNotifications } from '../../../notifications/ui-notification-boundary' import { useUiNotifications } from '../../../notifications/ui-notification-boundary'
import { SidebarButton } from '../sidebar-button/sidebar-button' 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 }) => { export const PinNoteSidebarEntry: React.FC<SpecificSidebarEntryProps> = ({ className, hide }) => {
useTranslation() useTranslation()
const id = useApplicationState((state) => state.noteDetails.id) const id = useNoteDetails().id
const history = useApplicationState((state) => state.history) const history = useApplicationState((state) => state.history)
const { showErrorNotification } = useUiNotifications() const { showErrorNotification } = useUiNotifications()

View file

@ -4,6 +4,7 @@
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import { useApplicationState } from '../../hooks/common/use-application-state' 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 { useTrimmedNoteMarkdownContentWithoutFrontmatter } from '../../hooks/common/use-trimmed-note-markdown-content-without-frontmatter'
import { setRendererStatus } from '../../redux/renderer-status/methods' import { setRendererStatus } from '../../redux/renderer-status/methods'
import { RenderIframe } from '../editor-page/renderer-pane/render-iframe' import { RenderIframe } from '../editor-page/renderer-pane/render-iframe'
@ -22,7 +23,7 @@ export const SlideShowPageContent: React.FC = () => {
const markdownContentLines = useTrimmedNoteMarkdownContentWithoutFrontmatter() const markdownContentLines = useTrimmedNoteMarkdownContentWithoutFrontmatter()
useTranslation() useTranslation()
const slideOptions = useApplicationState((state) => state.noteDetails.frontmatter.slideOptions) const slideOptions = useNoteDetails().frontmatter.slideOptions
const rendererReady = useApplicationState((state) => state.rendererStatus.rendererReady) const rendererReady = useApplicationState((state) => state.rendererStatus.rendererReady)
useSendToRenderer( useSendToRenderer(
useMemo( useMemo(

View file

@ -6,7 +6,7 @@
import { useChangeEditorContentCallback } from '../../../components/editor-page/change-content-context/use-change-editor-content-callback' 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 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 { 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 { createCheckboxContent } from './create-checkbox-content'
import type { TaskCheckedEventPayload } from './event-emitting-task-list-checkbox' import type { TaskCheckedEventPayload } from './event-emitting-task-list-checkbox'
import { findCheckBox } from './find-check-box' import { findCheckBox } from './find-check-box'
@ -32,7 +32,11 @@ export const useSetCheckboxInEditor = () => {
return useCallback( return useCallback(
({ lineInMarkdown, newCheckedState }: TaskCheckedEventPayload): void => { ({ lineInMarkdown, newCheckedState }: TaskCheckedEventPayload): void => {
changeEditorContent?.(({ markdownContent }) => { 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) const edits = findCheckBox(markdownContent, correctedLineIndex)
.map(([startIndex, endIndex]) => createCheckboxContentEdit(startIndex, endIndex, newCheckedState)) .map(([startIndex, endIndex]) => createCheckboxContentEdit(startIndex, endIndex, newCheckedState))
.orElse([]) .orElse([])

View file

@ -4,6 +4,7 @@
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import { useApplicationState } from './use-application-state' import { useApplicationState } from './use-application-state'
import { useNoteDetails } from './use-note-details'
import type { NotePermissions } from '@hedgedoc/commons' import type { NotePermissions } from '@hedgedoc/commons'
import { userIsOwner } from '@hedgedoc/commons' import { userIsOwner } from '@hedgedoc/commons'
import { useMemo } from 'react' import { useMemo } from 'react'
@ -15,7 +16,7 @@ import { useMemo } from 'react'
*/ */
export const useIsOwner = (): boolean => { export const useIsOwner = (): boolean => {
const me: string | undefined = useApplicationState((state) => state.user?.username) 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]) return useMemo(() => userIsOwner(permissions, me), [permissions, me])
} }

View file

@ -4,6 +4,7 @@
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import { useApplicationState } from './use-application-state' import { useApplicationState } from './use-application-state'
import { useNoteDetails } from './use-note-details'
import type { NotePermissions } from '@hedgedoc/commons' import type { NotePermissions } from '@hedgedoc/commons'
import { userCanEdit } from '@hedgedoc/commons' import { userCanEdit } from '@hedgedoc/commons'
import { useMemo } from 'react' import { useMemo } from 'react'
@ -15,7 +16,7 @@ import { useMemo } from 'react'
*/ */
export const useMayEdit = (): boolean => { export const useMayEdit = (): boolean => {
const me: string | undefined = useApplicationState((state) => state.user?.username) 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]) 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 * 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. * 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 * @return The markdown content of the note
*/ */
export const useNoteMarkdownContent = (): string => { 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 * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import { useApplicationState } from './use-application-state' import { useNoteDetails } from './use-note-details'
import { useMemo } from 'react' import { useMemo } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@ -15,7 +15,7 @@ import { useTranslation } from 'react-i18next'
export const useNoteTitle = (): string => { export const useNoteTitle = (): string => {
const { t } = useTranslation() const { t } = useTranslation()
const untitledNote = useMemo(() => t('editor.untitledNote'), [t]) 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]) return useMemo(() => (noteTitle === '' ? untitledNote : noteTitle), [noteTitle, untitledNote])
} }

View file

@ -4,7 +4,7 @@
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import { useFrontendConfig } from '../../components/common/frontend-config-context/use-frontend-config' 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' import { useMemo } from 'react'
/** /**
@ -14,19 +14,17 @@ import { useMemo } from 'react'
*/ */
export const useTrimmedNoteMarkdownContentWithoutFrontmatter = (): string[] => { export const useTrimmedNoteMarkdownContentWithoutFrontmatter = (): string[] => {
const maxLength = useFrontendConfig().maxDocumentLength const maxLength = useFrontendConfig().maxDocumentLength
const markdownContent = useApplicationState((state) => ({ const lines = useNoteDetails().markdownContent.lines
lines: state.noteDetails.markdownContent.lines, const content = useNoteDetails().markdownContent.plain
content: state.noteDetails.markdownContent.plain const lineOffset = useNoteDetails().frontmatterRendererInfo.lineOffset
}))
const lineOffset = useApplicationState((state) => state.noteDetails.frontmatterRendererInfo.lineOffset)
const trimmedLines = useMemo(() => { const trimmedLines = useMemo(() => {
if (markdownContent.content.length > maxLength) { if (content.length > maxLength) {
return markdownContent.content.slice(0, maxLength).split('\n') return content.slice(0, maxLength).split('\n')
} else { } else {
return markdownContent.lines return lines
} }
}, [markdownContent, maxLength]) }, [content, lines, maxLength])
return useMemo(() => { return useMemo(() => {
return trimmedLines.slice(lineOffset) return trimmedLines.slice(lineOffset)

View file

@ -6,7 +6,7 @@
import type { HistoryEntryWithOrigin } from '../api/history/types' import type { HistoryEntryWithOrigin } from '../api/history/types'
import type { DarkModeConfig } from './dark-mode/types' import type { DarkModeConfig } from './dark-mode/types'
import type { EditorConfig } from './editor/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 { RealtimeStatus } from './realtime/types'
import type { RendererStatus } from './renderer-status/types' import type { RendererStatus } from './renderer-status/types'
import type { OptionalUserState } from './user/types' import type { OptionalUserState } from './user/types'
@ -16,7 +16,7 @@ export interface ApplicationState {
history: HistoryEntryWithOrigin[] history: HistoryEntryWithOrigin[]
editorConfig: EditorConfig editorConfig: EditorConfig
darkMode: DarkModeConfig darkMode: DarkModeConfig
noteDetails: NoteDetails noteDetails: OptionalNoteDetails
rendererStatus: RendererStatus rendererStatus: RendererStatus
realtimeStatus: RealtimeStatus realtimeStatus: RealtimeStatus
} }

View file

@ -6,6 +6,7 @@
import { calculateLineStartIndexes } from './calculate-line-start-indexes' import { calculateLineStartIndexes } from './calculate-line-start-indexes'
import { initialState } from './initial-state' import { initialState } from './initial-state'
import { createNoteFrontmatterFromYaml } from './raw-note-frontmatter-parser/parser' import { createNoteFrontmatterFromYaml } from './raw-note-frontmatter-parser/parser'
import type { OptionalNoteDetails } from './types'
import type { NoteDetails } from './types/note-details' import type { NoteDetails } from './types/note-details'
import { extractFrontmatter, generateNoteTitle } from '@hedgedoc/commons' import { extractFrontmatter, generateNoteTitle } from '@hedgedoc/commons'
import type { PresentFrontmatterExtractionResult } 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. * @param markdownContent The new note markdown content consisting of the frontmatter and markdown part.
* @return An updated {@link NoteDetails} state. * @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')) 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. * Updates the current note's metadata from the server.
*/ */
export const updateMetadata = async (): Promise<void> => { 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({ store.dispatch({
type: NoteDetailsActionType.UPDATE_METADATA, type: NoteDetailsActionType.UPDATE_METADATA,
updatedMetadata updatedMetadata

View file

@ -4,19 +4,17 @@
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import { buildStateFromUpdatedMarkdownContent } from './build-state-from-updated-markdown-content' 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 { buildStateFromFirstHeadingUpdate } from './reducers/build-state-from-first-heading-update'
import { buildStateFromMetadataUpdate } from './reducers/build-state-from-metadata-update' import { buildStateFromMetadataUpdate } from './reducers/build-state-from-metadata-update'
import { buildStateFromServerPermissions } from './reducers/build-state-from-server-permissions' import { buildStateFromServerPermissions } from './reducers/build-state-from-server-permissions'
import { buildStateFromServerDto } from './reducers/build-state-from-set-note-data-from-server' import { buildStateFromServerDto } from './reducers/build-state-from-set-note-data-from-server'
import { buildStateFromUpdateCursorPosition } from './reducers/build-state-from-update-cursor-position' 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 { NoteDetailsActionType } from './types'
import type { NoteDetails } from './types/note-details'
import type { Reducer } from 'redux' import type { Reducer } from 'redux'
export const NoteDetailsReducer: Reducer<NoteDetails, NoteDetailsActions> = ( export const NoteDetailsReducer: Reducer<OptionalNoteDetails, NoteDetailsActions> = (
state: NoteDetails = initialState, state: OptionalNoteDetails = null,
action: NoteDetailsActions action: NoteDetailsActions
) => { ) => {
switch (action.type) { switch (action.type) {

View file

@ -3,7 +3,7 @@
* *
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import type { NoteDetails } from '../types/note-details' import type { OptionalNoteDetails } from '../types'
import { generateNoteTitle } from '@hedgedoc/commons' 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. * @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. * @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 { return {
...state, ...state,
firstHeading: firstHeading, firstHeading: firstHeading,

View file

@ -4,7 +4,7 @@
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import type { NoteMetadata } from '../../../api/notes/types' import type { NoteMetadata } from '../../../api/notes/types'
import type { NoteDetails } from '../types/note-details' import type { OptionalNoteDetails } from '../types'
import { DateTime } from 'luxon' import { DateTime } from 'luxon'
/** /**
@ -13,7 +13,13 @@ import { DateTime } from 'luxon'
* @param noteMetadata The updated metadata from the API. * @param noteMetadata The updated metadata from the API.
* @return An updated {@link NoteDetails} redux state. * @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 { return {
...state, ...state,
updateUsername: noteMetadata.updateUsername, updateUsername: noteMetadata.updateUsername,

View file

@ -3,7 +3,7 @@
* *
* SPDX-License-Identifier: AGPL-3.0-only * 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' import type { NotePermissions } from '@hedgedoc/commons'
/** /**
@ -12,9 +12,12 @@ import type { NotePermissions } from '@hedgedoc/commons'
* @param serverPermissions The updated NotePermissions data. * @param serverPermissions The updated NotePermissions data.
*/ */
export const buildStateFromServerPermissions = ( export const buildStateFromServerPermissions = (
state: NoteDetails, state: OptionalNoteDetails,
serverPermissions: NotePermissions serverPermissions: NotePermissions
): NoteDetails => { ): OptionalNoteDetails => {
if (state === null) {
return null
}
return { return {
...state, ...state,
permissions: serverPermissions 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 { buildStateFromUpdatedMarkdownContent } from '../build-state-from-updated-markdown-content'
import { calculateLineStartIndexes } from '../calculate-line-start-indexes' import { calculateLineStartIndexes } from '../calculate-line-start-indexes'
import { initialState } from '../initial-state' import { initialState } from '../initial-state'
import type { OptionalNoteDetails } from '../types'
import type { NoteDetails } from '../types/note-details' import type { NoteDetails } from '../types/note-details'
import { buildStateFromMetadataUpdate } from './build-state-from-metadata-update' 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. * @param dto The first DTO received from the API containing the relevant information about the note.
* @return An updated {@link NoteDetails} redux state. * @return An updated {@link NoteDetails} redux state.
*/ */
export const buildStateFromServerDto = (dto: Note): NoteDetails => { export const buildStateFromServerDto = (dto: Note): OptionalNoteDetails => {
const newState = convertNoteDtoToNoteDetails(dto) const newState = convertNoteDtoToNoteDetails(dto)
return buildStateFromUpdatedMarkdownContent(newState, newState.markdownContent.plain) return buildStateFromUpdatedMarkdownContent(newState, newState.markdownContent.plain)
} }
@ -28,6 +29,9 @@ export const buildStateFromServerDto = (dto: Note): NoteDetails => {
*/ */
const convertNoteDtoToNoteDetails = (note: Note): NoteDetails => { const convertNoteDtoToNoteDetails = (note: Note): NoteDetails => {
const stateWithMetadata = buildStateFromMetadataUpdate(initialState, note.metadata) const stateWithMetadata = buildStateFromMetadataUpdate(initialState, note.metadata)
if (stateWithMetadata === null) {
throw new Error('no note details!')
}
const newLines = note.content.split('\n') const newLines = note.content.split('\n')
return { return {
...stateWithMetadata, ...stateWithMetadata,

View file

@ -4,9 +4,15 @@
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import type { CursorSelection } from '../../../components/editor-page/editor-pane/tool-bar/formatters/types/cursor-selection' 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) const correctedSelection = isFromAfterTo(selection)
? { ? {
to: selection.from, to: selection.from,

View file

@ -5,6 +5,7 @@
*/ */
import type { Note, NoteMetadata } from '../../api/notes/types' 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 { 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 { NotePermissions } from '@hedgedoc/commons'
import type { Action } from 'redux' import type { Action } from 'redux'
@ -69,3 +70,5 @@ export interface UpdateMetadataAction extends Action<NoteDetailsActionType> {
type: NoteDetailsActionType.UPDATE_METADATA type: NoteDetailsActionType.UPDATE_METADATA
updatedMetadata: NoteMetadata updatedMetadata: NoteMetadata
} }
export type OptionalNoteDetails = NoteDetails | null