enhancement(sidebar): move note info modal into sidebar

Signed-off-by: Erik Michelson <github@erik.michelson.eu>
This commit is contained in:
Erik Michelson 2023-04-16 22:21:29 +02:00 committed by Tilman Vatteroth
parent e3a9f70965
commit b454e3be03
23 changed files with 258 additions and 327 deletions

View file

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
@ -11,32 +11,32 @@ describe('Test word count with', () => {
it('empty note', () => {
cy.setCodemirrorContent('')
cy.getByCypressId('sidebar-btn-document-info').click()
cy.getByCypressId('document-info-modal').should('be.visible')
cy.getByCypressId('sidebar-menu-info').click()
cy.getByCypressId('document-info-word-count').should('be.visible')
cy.getByCypressId('document-info-word-count').contains('0')
})
it('simple words', () => {
cy.setCodemirrorContent('five words should be enough')
cy.getMarkdownBody().contains('five words should be enough')
cy.getByCypressId('sidebar-btn-document-info').click()
cy.getByCypressId('document-info-modal').should('be.visible')
cy.getByCypressId('sidebar-menu-info').click()
cy.getByCypressId('document-info-word-count').should('be.visible')
cy.getByCypressId('document-info-word-count').contains('5')
})
it('excluded codeblocks', () => {
cy.setCodemirrorContent('```\nthis is should be ignored\n```\n\ntwo `words`')
cy.getMarkdownBody().contains('two words')
cy.getByCypressId('sidebar-btn-document-info').click()
cy.getByCypressId('document-info-modal').should('be.visible')
cy.getByCypressId('sidebar-menu-info').click()
cy.getByCypressId('document-info-word-count').should('be.visible')
cy.getByCypressId('document-info-word-count').contains('2')
})
it('excluded images', () => {
cy.setCodemirrorContent('![ignored alt text](https://dummyimage.com/48) not ignored text')
cy.getMarkdownBody().contains('not ignored text')
cy.getByCypressId('sidebar-btn-document-info').click()
cy.getByCypressId('document-info-modal').should('be.visible')
cy.getByCypressId('sidebar-menu-info').click()
cy.getByCypressId('document-info-word-count').should('be.visible')
cy.getByCypressId('document-info-word-count').contains('3')
})
})

View file

@ -383,20 +383,20 @@
"tagTime": "Time tag",
"spoiler": "Spoiler"
},
"noteInfo": {
"title": "Note info",
"created": "Note created",
"lastUpdated": "Last updated",
"lastUpdatedBy": "Last updated by",
"contributors": "Count of contributors",
"wordCount": "Count of words"
},
"modal": {
"snippetImport": {
"title": "Import from Snippet",
"selectProject": "Select From Available Projects",
"selectSnippet": "Select From Available Snippets"
},
"documentInfo": {
"title": "Document info",
"created": "This note was created <0></0>",
"edited": "<0></0> was the last editor <1></1>",
"usersContributed": "<0></0> users contributed to this document",
"revisions": "<0></0> revisions are saved",
"words": "<0></0> words in document"
},
"gistImport": {
"title": "Import from Gist",
"insertGistUrl": "Paste your gist url here…"
@ -645,7 +645,7 @@
},
"cheatsheet": {
"button": "Open Cheatsheet",
"modal":{
"modal": {
"popup": "Open in new tab",
"title": "Cheatsheet",
"headlines": {
@ -778,11 +778,11 @@
"example": "I :heart: :hedgehog:"
},
"csv": {
"title" : "CSV",
"table" : {
"title": "CSV",
"table": {
"title": "Table",
"description" : "You can render a CSV text as table by using a code block with `csv` as language. You must specify the delimiter.",
"example" : "```csv delimiter=;\nUsername; Identifier;First name;Last name\n\"booker12; rbooker\";9012;Rachel;Booker\ngrey07;2070;Laura;Grey\njohnson81;4081;Craig;Johnson\njenkins46;9346;Mary;Jenkins\nsmith79;5079;Jamie;Smith\n```"
"description": "You can render a CSV text as table by using a code block with `csv` as language. You must specify the delimiter.",
"example": "```csv delimiter=;\nUsername; Identifier;First name;Last name\n\"booker12; rbooker\";9012;Rachel;Booker\ngrey07;2070;Laura;Grey\njohnson81;4081;Craig;Johnson\njenkins46;9346;Mary;Jenkins\nsmith79;5079;Jamie;Smith\n```"
},
"header": {
"title": "Header",

View file

@ -17,7 +17,7 @@ export interface TimeFromNowProps {
*/
export const TimeFromNow: React.FC<TimeFromNowProps> = ({ time }) => {
return (
<time className={'mx-1'} title={time.toFormat('DDDD T')} dateTime={time.toString()}>
<time title={time.toFormat('DDDD T')} dateTime={time.toString()}>
{time.toRelative()}
</time>
)

View file

@ -6,8 +6,8 @@
import { useApplicationState } from '../../hooks/common/use-application-state'
import { InternalLink } from '../common/links/internal-link'
import { ShowIf } from '../common/show-if/show-if'
import { NoteInfoLineCreated } from '../editor-page/sidebar/specific-sidebar-entries/note-info-sidebar-entry/note-info-modal/note-info-line-created'
import { NoteInfoLineUpdated } from '../editor-page/sidebar/specific-sidebar-entries/note-info-sidebar-entry/note-info-modal/note-info-line-updated'
import { NoteInfoLineCreatedAt } from '../editor-page/sidebar/specific-sidebar-entries/note-info-sidebar-menu/note-info-line/note-info-line-created-at'
import { NoteInfoLineUpdatedBy } from '../editor-page/sidebar/specific-sidebar-entries/note-info-sidebar-menu/note-info-line/note-info-line-updated-by'
import styles from './document-infobar.module.scss'
import React from 'react'
import { Pencil as IconPencil } from 'react-bootstrap-icons'
@ -26,8 +26,8 @@ export const DocumentInfobar: React.FC = () => {
<div className={'col-md'}>&nbsp;</div>
<div className={'d-flex flex-fill'}>
<div className={'d-flex flex-column'}>
<NoteInfoLineCreated />
<NoteInfoLineUpdated />
<NoteInfoLineCreatedAt />
<NoteInfoLineUpdatedBy />
<hr />
</div>
<span className={'ms-auto'}>

View file

@ -0,0 +1,14 @@
/*
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
.entry {
border: solid 1px rgba(0, 0, 0, 0.15);
}
.title {
text-transform: uppercase;
font-size: 0.75em;
color: var(--bs-secondary);
}

View file

@ -0,0 +1,46 @@
/*
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { UiIcon } from '../../../common/icons/ui-icon'
import { ShowIf } from '../../../common/show-if/show-if'
import styles from './sidebar-menu-info-entry.module.css'
import type { PropsWithChildren } from 'react'
import React from 'react'
import type { Icon } from 'react-bootstrap-icons'
import { Trans, useTranslation } from 'react-i18next'
export interface SidebarMenuInfoEntryProps {
titleI18nKey: string
icon?: Icon
}
/**
* Renders an info entry for a sidebar menu.
*
* @param children The content of the entry
* @param titleI18nKey The i18n key for the title
* @param icon An optional icon as prefix
*/
export const SidebarMenuInfoEntry: React.FC<PropsWithChildren<SidebarMenuInfoEntryProps>> = ({
children,
titleI18nKey,
icon
}) => {
useTranslation()
return (
<div className={`d-flex flex-row align-items-center p-1 ${styles['entry']}`}>
<ShowIf condition={icon !== undefined}>
<UiIcon icon={icon} className={'mx-2'} size={1.25} />
</ShowIf>
<div className={'d-flex flex-column px-1'}>
<span className={styles['title']}>
<Trans i18nKey={titleI18nKey} />
</span>
{children}
</div>
</div>
)
}

View file

@ -7,7 +7,7 @@ import { AliasesSidebarEntry } from './specific-sidebar-entries/aliases-sidebar-
import { DeleteNoteSidebarEntry } from './specific-sidebar-entries/delete-note-sidebar-entry/delete-note-sidebar-entry'
import { ExportMenuSidebarMenu } from './specific-sidebar-entries/export-menu-sidebar-menu'
import { ImportMenuSidebarMenu } from './specific-sidebar-entries/import-menu-sidebar-menu'
import { NoteInfoSidebarEntry } from './specific-sidebar-entries/note-info-sidebar-entry/note-info-sidebar-entry'
import { NoteInfoSidebarMenu } from './specific-sidebar-entries/note-info-sidebar-menu/note-info-sidebar-menu'
import { PermissionsSidebarEntry } from './specific-sidebar-entries/permissions-sidebar-entry/permissions-sidebar-entry'
import { PinNoteSidebarEntry } from './specific-sidebar-entries/pin-note-sidebar-entry/pin-note-sidebar-entry'
import { RevisionSidebarEntry } from './specific-sidebar-entries/revisions-sidebar-entry/revision-sidebar-entry'
@ -47,7 +47,11 @@ export const Sidebar: React.FC = () => {
selectedMenuId={selectedMenu}
onClick={toggleValue}
/>
<NoteInfoSidebarEntry hide={selectionIsNotNone} />
<NoteInfoSidebarMenu
menuId={DocumentSidebarMenuSelection.NOTE_INFO}
selectedMenuId={selectedMenu}
onClick={toggleValue}
/>
<RevisionSidebarEntry hide={selectionIsNotNone} />
<PermissionsSidebarEntry hide={selectionIsNotNone} />
<AliasesSidebarEntry hide={selectionIsNotNone} />

View file

@ -1,31 +0,0 @@
/*
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { useApplicationState } from '../../../../../../hooks/common/use-application-state'
import { NoteInfoLine } from './note-info-line'
import type { NoteInfoTimeLineProps } from './note-info-time-line'
import { UnitalicBoldTimeFromNow } from './utils/unitalic-bold-time-from-now'
import { DateTime } from 'luxon'
import React, { useMemo } from 'react'
import { Plus as IconPlus } from 'react-bootstrap-icons'
import { Trans } from 'react-i18next'
/**
* Renders an info line about the creation of the current note.
*
* @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 noteCreateDateTime = useMemo(() => DateTime.fromSeconds(noteCreateTime), [noteCreateTime])
return (
<NoteInfoLine icon={IconPlus} size={size}>
<Trans i18nKey={'editor.modal.documentInfo.created'}>
<UnitalicBoldTimeFromNow time={noteCreateDateTime} />
</Trans>
</NoteInfoLine>
)
}

View file

@ -1,49 +0,0 @@
/*
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { useApplicationState } from '../../../../../../hooks/common/use-application-state'
import { UserAvatarForUsername } from '../../../../../common/user-avatar/user-avatar-for-username'
import { NoteInfoLine } from './note-info-line'
import type { NoteInfoTimeLineProps } from './note-info-time-line'
import { UnitalicBoldTimeFromNow } from './utils/unitalic-bold-time-from-now'
import { UnitalicBoldTrans } from './utils/unitalic-bold-trans'
import { DateTime } from 'luxon'
import React, { useMemo } from 'react'
import { Pencil as IconPencil } from 'react-bootstrap-icons'
import { Trans, useTranslation } from 'react-i18next'
/**
* Renders an info line about the last update of the current note.
*
* @param size The size in which line and user avatar should be displayed.
*/
export const NoteInfoLineUpdated: React.FC<NoteInfoTimeLineProps> = ({ size }) => {
useTranslation()
const noteUpdateTime = useApplicationState((state) => state.noteDetails.updatedAt)
const noteUpdateDateTime = useMemo(() => DateTime.fromSeconds(noteUpdateTime), [noteUpdateTime])
const noteUpdateUser = useApplicationState((state) => state.noteDetails.updateUsername)
const userBlock = useMemo(() => {
if (!noteUpdateUser) {
return <UnitalicBoldTrans i18nKey={'common.guestUser'} />
}
return (
<UserAvatarForUsername
username={noteUpdateUser}
additionalClasses={'font-style-normal bold font-weight-bold'}
size={size ? 'lg' : undefined}
/>
)
}, [noteUpdateUser, size])
return (
<NoteInfoLine icon={IconPencil} size={size}>
<Trans i18nKey={'editor.modal.documentInfo.edited'}>
{userBlock}
<UnitalicBoldTimeFromNow time={noteUpdateDateTime} />
</Trans>
</NoteInfoLine>
)
}

View file

@ -1,31 +0,0 @@
/*
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { UiIcon } from '../../../../../common/icons/ui-icon'
import type { PropsWithChildren } from 'react'
import React from 'react'
import type { Icon } from 'react-bootstrap-icons'
export interface NoteInfoLineProps {
icon: Icon
size?: 2 | 3 | 4 | 5 | undefined
}
/**
* This is the base component for all note info lines.
* It renders an icon and some children in italic.
*
* @param icon The icon be shown
* @param size Which size the icon should be
* @param children The children to render
*/
export const NoteInfoLine: React.FC<PropsWithChildren<NoteInfoLineProps>> = ({ icon, size, children }) => {
return (
<span className={'d-flex align-items-center'}>
<UiIcon icon={icon} size={size} className={'mx-2'} />
<i className={'d-flex align-items-center'}>{children}</i>
</span>
)
}

View file

@ -1,51 +0,0 @@
/*
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { cypressId } from '../../../../../../utils/cypress-attribute'
import type { ModalVisibilityProps } from '../../../../../common/modals/common-modal'
import { CommonModal } from '../../../../../common/modals/common-modal'
import { NoteInfoLineContributors } from './note-info-line-contributors'
import { NoteInfoLineCreated } from './note-info-line-created'
import { NoteInfoLineUpdated } from './note-info-line-updated'
import { NoteInfoLineWordCount } from './note-info-line-word-count'
import React from 'react'
import { ListGroup, Modal } from 'react-bootstrap'
import { useTranslation } from 'react-i18next'
/**
* Modal that shows informational data about the current note.
*
* @param show true when the modal should be visible, false otherwise.
* @param onHide Callback that is fired when the modal is going to be closed.
*/
export const NoteInfoModal: React.FC<ModalVisibilityProps> = ({ show, onHide }) => {
useTranslation()
return (
<CommonModal
show={show}
onHide={onHide}
showCloseButton={true}
titleI18nKey={'editor.modal.documentInfo.title'}
{...cypressId('document-info-modal')}>
<Modal.Body>
<ListGroup>
<ListGroup.Item>
<NoteInfoLineCreated size={2} />
</ListGroup.Item>
<ListGroup.Item>
<NoteInfoLineUpdated size={2} />
</ListGroup.Item>
<ListGroup.Item>
<NoteInfoLineContributors />
</ListGroup.Item>
<ListGroup.Item>
<NoteInfoLineWordCount />
</ListGroup.Item>
</ListGroup>
</Modal.Body>
</CommonModal>
)
}

View file

@ -1,8 +0,0 @@
/*
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
export interface NoteInfoTimeLineProps {
size?: 2 | 3 | 4 | 5
}

View file

@ -1,33 +0,0 @@
/*
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { PropsWithDataCypressId } from '../../../../../../utils/cypress-attribute'
import { cypressId } from '../../../../../../utils/cypress-attribute'
import type { PropsWithChildren } from 'react'
import React from 'react'
export interface UnitalicBoldContentProps extends PropsWithDataCypressId {
text?: string | number
}
/**
* Renders the children elements in a non-italic but bold style.
*
* @param text Optional text content that should be rendered.
* @param children Children that may be rendered.
* @param props Additional props for cypressId
*/
export const UnitalicBoldContent: React.FC<PropsWithChildren<UnitalicBoldContentProps>> = ({
text,
children,
...props
}) => {
return (
<strong className={'font-style-normal me-1'} {...cypressId(props)}>
{text}
{children}
</strong>
)
}

View file

@ -1,17 +0,0 @@
/*
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { TimeFromNowProps } from '../time-from-now'
import { TimeFromNow } from '../time-from-now'
import { UnitalicBoldContent } from '../unitalic-bold-content'
import React from 'react'
export const UnitalicBoldTimeFromNow: React.FC<TimeFromNowProps> = ({ time }) => {
return (
<UnitalicBoldContent>
<TimeFromNow time={time} />
</UnitalicBoldContent>
)
}

View file

@ -1,20 +0,0 @@
/*
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { UnitalicBoldContent } from '../unitalic-bold-content'
import React from 'react'
import { Trans } from 'react-i18next'
export interface UnitalicBoldTransProps {
i18nKey?: string
}
export const UnitalicBoldTrans: React.FC<UnitalicBoldTransProps> = ({ i18nKey }) => {
return (
<UnitalicBoldContent>
<Trans i18nKey={i18nKey} />
</UnitalicBoldContent>
)
}

View file

@ -1,38 +0,0 @@
/*
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { useBooleanState } from '../../../../../hooks/common/use-boolean-state'
import { cypressId } from '../../../../../utils/cypress-attribute'
import { SidebarButton } from '../../sidebar-button/sidebar-button'
import type { SpecificSidebarEntryProps } from '../../types'
import { NoteInfoModal } from './note-info-modal/note-info-modal'
import React, { Fragment } from 'react'
import { GraphUp as IconGraphUp } from 'react-bootstrap-icons'
import { Trans, useTranslation } from 'react-i18next'
/**
* Sidebar entry that allows to open the {@link NoteInfoModal} containing information about the current note.
*
* @param className CSS classes to add to the sidebar button
* @param hide true when the sidebar button should be hidden, false otherwise
*/
export const NoteInfoSidebarEntry: React.FC<SpecificSidebarEntryProps> = ({ className, hide }) => {
const [modalVisibility, showModal, closeModal] = useBooleanState()
useTranslation()
return (
<Fragment>
<SidebarButton
hide={hide}
className={className}
icon={IconGraphUp}
onClick={showModal}
{...cypressId('sidebar-btn-document-info')}>
<Trans i18nKey={'editor.modal.documentInfo.title'} />
</SidebarButton>
<NoteInfoModal show={modalVisibility} onHide={closeModal} />
</Fragment>
)
}

View file

@ -4,11 +4,9 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { useApplicationState } from '../../../../../../hooks/common/use-application-state'
import { NoteInfoLine } from './note-info-line'
import { UnitalicBoldContent } from './unitalic-bold-content'
import { SidebarMenuInfoEntry } from '../../../sidebar-menu-info-entry/sidebar-menu-info-entry'
import React from 'react'
import { People as IconPeople } from 'react-bootstrap-icons'
import { Trans } from 'react-i18next'
/**
* Renders an info line about the number of contributors for the note.
@ -17,10 +15,8 @@ export const NoteInfoLineContributors: React.FC = () => {
const contributors = useApplicationState((state) => state.noteDetails.editedBy.length)
return (
<NoteInfoLine icon={IconPeople} size={2}>
<Trans i18nKey={'editor.modal.documentInfo.usersContributed'}>
<UnitalicBoldContent text={contributors} />
</Trans>
</NoteInfoLine>
<SidebarMenuInfoEntry titleI18nKey={'editor.noteInfo.contributors'} icon={IconPeople}>
{contributors}
</SidebarMenuInfoEntry>
)
}

View file

@ -0,0 +1,27 @@
/*
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { useApplicationState } from '../../../../../../hooks/common/use-application-state'
import { TimeFromNow } from '../../../../../common/time-from-now'
import { SidebarMenuInfoEntry } from '../../../sidebar-menu-info-entry/sidebar-menu-info-entry'
import { DateTime } from 'luxon'
import React, { useMemo } from 'react'
import { Plus as IconPlus } from 'react-bootstrap-icons'
import { useTranslation } from 'react-i18next'
/**
* Renders an info line about the creation time of the current note.
*/
export const NoteInfoLineCreatedAt: React.FC = () => {
useTranslation()
const noteCreateTime = useApplicationState((state) => state.noteDetails.createdAt)
const noteCreateDateTime = useMemo(() => DateTime.fromSeconds(noteCreateTime), [noteCreateTime])
return (
<SidebarMenuInfoEntry titleI18nKey={'editor.noteInfo.created'} icon={IconPlus}>
<TimeFromNow time={noteCreateDateTime} />
</SidebarMenuInfoEntry>
)
}

View file

@ -0,0 +1,27 @@
/*
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { useApplicationState } from '../../../../../../hooks/common/use-application-state'
import { TimeFromNow } from '../../../../../common/time-from-now'
import { SidebarMenuInfoEntry } from '../../../sidebar-menu-info-entry/sidebar-menu-info-entry'
import { DateTime } from 'luxon'
import React, { useMemo } from 'react'
import { Pencil as IconPencil } from 'react-bootstrap-icons'
import { useTranslation } from 'react-i18next'
/**
* Renders an info line about the time that the note content was last updated.
*/
export const NoteInfoLineUpdatedAt: React.FC = () => {
useTranslation()
const noteUpdateTime = useApplicationState((state) => state.noteDetails.updatedAt)
const noteUpdateDateTime = useMemo(() => DateTime.fromSeconds(noteUpdateTime), [noteUpdateTime])
return (
<SidebarMenuInfoEntry titleI18nKey={'editor.noteInfo.lastUpdated'} icon={IconPencil}>
<TimeFromNow time={noteUpdateDateTime} />
</SidebarMenuInfoEntry>
)
}

View file

@ -0,0 +1,34 @@
/*
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { useApplicationState } from '../../../../../../hooks/common/use-application-state'
import { UserAvatarForUsername } from '../../../../../common/user-avatar/user-avatar-for-username'
import { SidebarMenuInfoEntry } from '../../../sidebar-menu-info-entry/sidebar-menu-info-entry'
import React, { useMemo } from 'react'
import { Pencil as IconPencil } from 'react-bootstrap-icons'
import { Trans, useTranslation } from 'react-i18next'
/**
* Renders an info line about the user that last updated the note content.
*/
export const NoteInfoLineUpdatedBy: React.FC = () => {
useTranslation()
const noteUpdateUser = useApplicationState((state) => state.noteDetails.updateUsername)
const userBlock = useMemo(() => {
if (!noteUpdateUser) {
return <Trans i18nKey={'common.guestUser'} />
}
return (
<UserAvatarForUsername username={noteUpdateUser} additionalClasses={'font-style-normal bold font-weight-bold'} />
)
}, [noteUpdateUser])
return (
<SidebarMenuInfoEntry titleI18nKey={'editor.noteInfo.lastUpdatedBy'} icon={IconPencil}>
{userBlock}
</SidebarMenuInfoEntry>
)
}

View file

@ -10,18 +10,20 @@ import { useEditorReceiveHandler } from '../../../../../render-page/window-post-
import type { OnWordCountCalculatedMessage } from '../../../../../render-page/window-post-message-communicator/rendering-message'
import { CommunicationMessageType } from '../../../../../render-page/window-post-message-communicator/rendering-message'
import { useEditorToRendererCommunicator } from '../../../../render-context/editor-to-renderer-communicator-context-provider'
import { NoteInfoLine } from './note-info-line'
import { UnitalicBoldContent } from './unitalic-bold-content'
import type { PropsWithChildren } from 'react'
import { SidebarMenuInfoEntry } from '../../../sidebar-menu-info-entry/sidebar-menu-info-entry'
import React, { useCallback, useEffect, useState } from 'react'
import { AlignStart as IconAlignStart } from 'react-bootstrap-icons'
import { Trans, useTranslation } from 'react-i18next'
interface NoteInfoLineWordCountProps {
visible: boolean
}
/**
* Creates a new info line for the document information dialog that holds the
* word count of the note, based on counting in the rendered output.
*/
export const NoteInfoLineWordCount: React.FC<PropsWithChildren<unknown>> = () => {
export const NoteInfoLineWordCount: React.FC<NoteInfoLineWordCountProps> = ({ visible }) => {
useTranslation()
const editorToRendererCommunicator = useEditorToRendererCommunicator()
const [wordCount, setWordCount] = useState<number | null>(null)
@ -33,21 +35,19 @@ export const NoteInfoLineWordCount: React.FC<PropsWithChildren<unknown>> = () =>
const rendererReady = useApplicationState((state) => state.rendererStatus.rendererReady)
useEffect(() => {
if (rendererReady) {
if (rendererReady && visible) {
editorToRendererCommunicator.sendMessageToOtherSide({ type: CommunicationMessageType.GET_WORD_COUNT })
}
}, [editorToRendererCommunicator, rendererReady])
}, [editorToRendererCommunicator, rendererReady, visible])
return (
<NoteInfoLine icon={IconAlignStart} size={2}>
<SidebarMenuInfoEntry titleI18nKey={'editor.noteInfo.wordCount'} icon={IconAlignStart}>
<ShowIf condition={wordCount === null}>
<Trans i18nKey={'common.loading'} />
</ShowIf>
<ShowIf condition={wordCount !== null}>
<Trans i18nKey={'editor.modal.documentInfo.words'}>
<UnitalicBoldContent text={wordCount ?? ''} {...cypressId('document-info-word-count')} />
</Trans>
<span {...cypressId('document-info-word-count')}>{wordCount}</span>
</ShowIf>
</NoteInfoLine>
</SidebarMenuInfoEntry>
)
}

View file

@ -0,0 +1,60 @@
/*
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { cypressId } from '../../../../../utils/cypress-attribute'
import { SidebarButton } from '../../sidebar-button/sidebar-button'
import { SidebarMenu } from '../../sidebar-menu/sidebar-menu'
import type { SpecificSidebarMenuProps } from '../../types'
import { DocumentSidebarMenuSelection } from '../../types'
import { NoteInfoLineContributors } from './note-info-line/note-info-line-contributors'
import { NoteInfoLineCreatedAt } from './note-info-line/note-info-line-created-at'
import { NoteInfoLineUpdatedAt } from './note-info-line/note-info-line-updated-at'
import { NoteInfoLineUpdatedBy } from './note-info-line/note-info-line-updated-by'
import { NoteInfoLineWordCount } from './note-info-line/note-info-line-word-count'
import React, { Fragment, useCallback } from 'react'
import { ArrowLeft as IconArrowLeft, GraphUp as IconGraphUp } from 'react-bootstrap-icons'
import { Trans, useTranslation } from 'react-i18next'
/**
* Renders the note info menu for the sidebar.
*
* @param className Additional class names given to the menu button
* @param menuId The id of the menu
* @param onClick The callback, that should be called when the menu button is pressed
* @param selectedMenuId The currently selected menu id
*/
export const NoteInfoSidebarMenu: React.FC<SpecificSidebarMenuProps> = ({
className,
menuId,
onClick,
selectedMenuId
}) => {
useTranslation()
const hide = selectedMenuId !== DocumentSidebarMenuSelection.NONE && selectedMenuId !== menuId
const expand = selectedMenuId === menuId
const onClickHandler = useCallback(() => {
onClick(menuId)
}, [menuId, onClick])
return (
<Fragment>
<SidebarButton
{...cypressId('sidebar-menu-info')}
hide={hide}
icon={expand ? IconArrowLeft : IconGraphUp}
className={className}
onClick={onClickHandler}>
<Trans i18nKey={'editor.noteInfo.title'} />
</SidebarButton>
<SidebarMenu expand={expand}>
<NoteInfoLineCreatedAt />
<NoteInfoLineUpdatedAt />
<NoteInfoLineUpdatedBy />
<NoteInfoLineContributors />
<NoteInfoLineWordCount visible={expand} />
</SidebarMenu>
</Fragment>
)
}

View file

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
@ -29,6 +29,7 @@ export interface SidebarMenuProps {
export enum DocumentSidebarMenuSelection {
NONE,
USERS_ONLINE,
NOTE_INFO,
IMPORT,
EXPORT
}