mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-06-08 10:22:47 -04:00
refactor: move help entries into new global app bar
Co-authored-by: Tilman Vatteroth <git@tilmanvatteroth.de> Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de> Signed-off-by: Erik Michelson <github@erik.michelson.eu>
This commit is contained in:
parent
74b92f2bbb
commit
d10c6d3290
43 changed files with 749 additions and 397 deletions
|
@ -0,0 +1,68 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`app bar contains alert when editor is not synced 1`] = `
|
||||
<div>
|
||||
<div>
|
||||
<span>
|
||||
first part
|
||||
</span>
|
||||
<div>
|
||||
<div
|
||||
class="fade w-100 m-0 px-2 py-1 border-top-0 border-bottom-0 d-flex align-items-center alert alert-warning show"
|
||||
role="alert"
|
||||
>
|
||||
realtime.connecting
|
||||
BootstrapIconMock_ArrowRepeat
|
||||
</div>
|
||||
</div>
|
||||
<span>
|
||||
last part
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`app bar contains note title and read-only marker when having only read permissions 1`] = `
|
||||
<div>
|
||||
<div>
|
||||
<span>
|
||||
first part
|
||||
</span>
|
||||
<div>
|
||||
<span
|
||||
class="text-secondary me-2"
|
||||
>
|
||||
BootstrapIconMock_Lock
|
||||
</span>
|
||||
<span
|
||||
class="text-truncate mw-100"
|
||||
>
|
||||
Note Title Test
|
||||
</span>
|
||||
</div>
|
||||
<span>
|
||||
last part
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`app bar contains note title when editor is synced 1`] = `
|
||||
<div>
|
||||
<div>
|
||||
<span>
|
||||
first part
|
||||
</span>
|
||||
<div>
|
||||
<span
|
||||
class="text-truncate mw-100"
|
||||
>
|
||||
Note Title Test
|
||||
</span>
|
||||
</div>
|
||||
<span>
|
||||
last part
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
.custom {
|
||||
font-size: 18px;
|
||||
line-height: 1.0;
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { useDarkModeState } from '../../../../hooks/dark-mode/use-dark-mode-state'
|
||||
import { BrandingSeparatorDash } from '../../../common/custom-branding/branding-separator-dash'
|
||||
import { CustomBranding } from '../../../common/custom-branding/custom-branding'
|
||||
import { HedgeDocLogoHorizontalGrey } from '../../../common/hedge-doc-logo/hedge-doc-logo-horizontal-grey'
|
||||
import { LogoSize } from '../../../common/hedge-doc-logo/logo-size'
|
||||
import Link from 'next/link'
|
||||
import React from 'react'
|
||||
|
||||
/**
|
||||
* Renders the HedgeDoc branding and branding customizations for the app bar.
|
||||
*/
|
||||
export const BrandingElement: React.FC = () => {
|
||||
const darkModeActivated = useDarkModeState()
|
||||
|
||||
return (
|
||||
<Link
|
||||
href='/intro'
|
||||
className={'text-secondary text-decoration-none d-flex align-items-center justify-content-start gap-1'}>
|
||||
<div>
|
||||
<HedgeDocLogoHorizontalGrey color={darkModeActivated ? 'dark' : 'light'} size={LogoSize.SMALL} />
|
||||
</div>
|
||||
<BrandingSeparatorDash />
|
||||
<CustomBranding inline={true} />
|
||||
</Link>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import React from 'react'
|
||||
import { Dropdown } from 'react-bootstrap'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
|
||||
interface DropdownHeaderProps {
|
||||
i18nKey: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a dropdown header with a translation.
|
||||
* @param i18nKey The i18n key for the header
|
||||
*/
|
||||
export const DropdownHeader: React.FC<DropdownHeaderProps> = ({ i18nKey }) => {
|
||||
useTranslation()
|
||||
|
||||
return (
|
||||
<Dropdown.Header>
|
||||
<Trans i18nKey={i18nKey} />
|
||||
</Dropdown.Header>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { UiIcon } from '../../../../common/icons/ui-icon'
|
||||
import { HelpSubmenu } from './submenues/help-submenu'
|
||||
import { InstanceSubmenu } from './submenues/instance-submenu'
|
||||
import { LegalSubmenu } from './submenues/legal-submenu'
|
||||
import { ProjectLinksSubmenu } from './submenues/project-links-submenu'
|
||||
import { SocialLinksSubmenu } from './submenues/social-links-submenu'
|
||||
import React from 'react'
|
||||
import { Dropdown } from 'react-bootstrap'
|
||||
import { QuestionLg as IconQuestion } from 'react-bootstrap-icons'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
/**
|
||||
* Renders the help dropdown in the app bar.
|
||||
*/
|
||||
export const HelpDropdown: React.FC = () => {
|
||||
useTranslation()
|
||||
|
||||
return (
|
||||
<Dropdown>
|
||||
<Dropdown.Toggle size={'sm'}>
|
||||
<UiIcon icon={IconQuestion} />
|
||||
</Dropdown.Toggle>
|
||||
<Dropdown.Menu>
|
||||
<HelpSubmenu />
|
||||
<Dropdown.Divider />
|
||||
<InstanceSubmenu />
|
||||
<LegalSubmenu />
|
||||
<Dropdown.Divider />
|
||||
<ProjectLinksSubmenu />
|
||||
<Dropdown.Divider />
|
||||
<SocialLinksSubmenu />
|
||||
</Dropdown.Menu>
|
||||
</Dropdown>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { DropdownHeader } from '../dropdown-header'
|
||||
import { CheatsheetHelpMenuEntry } from './help/cheatsheet-help-menu-entry'
|
||||
import { ShortcutsHelpMenuEntry } from './help/shortcuts-help-menu-entry'
|
||||
import React, { Fragment } from 'react'
|
||||
|
||||
/**
|
||||
* Renders the help submenu for the help dropdown.
|
||||
*/
|
||||
export const HelpSubmenu: React.FC = () => {
|
||||
return (
|
||||
<Fragment>
|
||||
<DropdownHeader i18nKey={'appbar.help.help.header'} />
|
||||
<ShortcutsHelpMenuEntry />
|
||||
<CheatsheetHelpMenuEntry />
|
||||
</Fragment>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* 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 { CheatsheetContent } from '../../../../../../cheatsheet/cheatsheet-content'
|
||||
import { CheatsheetInNewTabButton } from '../../../../../../cheatsheet/cheatsheet-in-new-tab-button'
|
||||
import { CommonModal } from '../../../../../../common/modals/common-modal'
|
||||
import { TranslatedDropdownItem } from '../../translated-dropdown-item'
|
||||
import React, { Fragment } from 'react'
|
||||
import { Modal } from 'react-bootstrap'
|
||||
import { Search as IconSearch } from 'react-bootstrap-icons'
|
||||
|
||||
/**
|
||||
* Renders the cheatsheet menu entry for the help dropdown.
|
||||
*/
|
||||
export const CheatsheetHelpMenuEntry: React.FC = () => {
|
||||
const [modalVisibility, showModal, closeModal] = useBooleanState()
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<TranslatedDropdownItem
|
||||
i18nKey={'appbar.help.help.cheatsheet'}
|
||||
icon={IconSearch}
|
||||
onClick={showModal}
|
||||
{...cypressId('open.cheatsheet-button')}
|
||||
/>
|
||||
<CommonModal
|
||||
modalSize={'xl'}
|
||||
show={modalVisibility}
|
||||
onHide={closeModal}
|
||||
showCloseButton={true}
|
||||
titleI18nKey={'cheatsheet.modal.title'}
|
||||
additionalTitleElement={
|
||||
<div className={'d-flex flex-row-reverse w-100 mx-2'}>
|
||||
<CheatsheetInNewTabButton onClick={closeModal} />
|
||||
</div>
|
||||
}>
|
||||
<Modal.Body>
|
||||
<CheatsheetContent />
|
||||
</Modal.Body>
|
||||
</CommonModal>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* 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 { ShortcutsModal } from '../../../../../../global-dialogs/shortcuts-modal/shortcuts-modal'
|
||||
import { TranslatedDropdownItem } from '../../translated-dropdown-item'
|
||||
import React, { Fragment } from 'react'
|
||||
import { KeyboardFill as IconKeyboardFill } from 'react-bootstrap-icons'
|
||||
|
||||
/**
|
||||
* Renders the shortcuts menu entry for the help dropdown.
|
||||
*/
|
||||
export const ShortcutsHelpMenuEntry: React.FC = () => {
|
||||
const [modalVisibility, showModal, closeModal] = useBooleanState()
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<TranslatedDropdownItem icon={IconKeyboardFill} i18nKey={'appbar.help.help.shortcuts'} onClick={showModal} />
|
||||
<ShortcutsModal show={modalVisibility} onHide={closeModal} />
|
||||
</Fragment>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { DropdownHeader } from '../dropdown-header'
|
||||
import { VersionInfoHelpMenuEntry } from './instance/version-info-help-menu-entry'
|
||||
import React, { Fragment } from 'react'
|
||||
|
||||
/**
|
||||
* Renders the instance submenu for the help dropdown.
|
||||
*/
|
||||
export const InstanceSubmenu: React.FC = () => {
|
||||
return (
|
||||
<Fragment>
|
||||
<DropdownHeader i18nKey={'appbar.help.instance.header'} />
|
||||
<VersionInfoHelpMenuEntry />
|
||||
</Fragment>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* 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 { VersionInfoModal } from '../../../../../../global-dialogs/version-info-modal/version-info-modal'
|
||||
import { TranslatedDropdownItem } from '../../translated-dropdown-item'
|
||||
import React, { Fragment } from 'react'
|
||||
import { Server as IconServer } from 'react-bootstrap-icons'
|
||||
|
||||
/**
|
||||
* Renders the version info menu entry for the help dropdown.
|
||||
*/
|
||||
export const VersionInfoHelpMenuEntry: React.FC = () => {
|
||||
const [modalVisibility, showModal, closeModal] = useBooleanState()
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<TranslatedDropdownItem icon={IconServer} i18nKey={'appbar.help.instance.versionInfo'} onClick={showModal} />
|
||||
<VersionInfoModal show={modalVisibility} onHide={closeModal} />
|
||||
</Fragment>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { useFrontendConfig } from '../../../../../common/frontend-config-context/use-frontend-config'
|
||||
import { ShowIf } from '../../../../../common/show-if/show-if'
|
||||
import { DropdownHeader } from '../dropdown-header'
|
||||
import { TranslatedDropdownItem } from '../translated-dropdown-item'
|
||||
import React, { Fragment, useMemo } from 'react'
|
||||
import { Dropdown } from 'react-bootstrap'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
/**
|
||||
* Renders the legal submenu for the help dropdown.
|
||||
*/
|
||||
export const LegalSubmenu: React.FC = () => {
|
||||
useTranslation()
|
||||
const specialUrls = useFrontendConfig().specialUrls
|
||||
const linksConfigured = useMemo(
|
||||
() => specialUrls.privacy || specialUrls.termsOfUse || specialUrls.imprint,
|
||||
[specialUrls]
|
||||
)
|
||||
|
||||
if (!linksConfigured) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Dropdown.Divider />
|
||||
<DropdownHeader i18nKey={'appbar.help.legal.header'} />
|
||||
<ShowIf condition={!!specialUrls.privacy}>
|
||||
<TranslatedDropdownItem href={specialUrls.privacy} i18nKey={'appbar.help.legal.privacy'} />
|
||||
</ShowIf>
|
||||
<ShowIf condition={!!specialUrls.termsOfUse}>
|
||||
<TranslatedDropdownItem href={specialUrls.termsOfUse} i18nKey={'appbar.help.legal.termsOfUse'} />
|
||||
</ShowIf>
|
||||
<ShowIf condition={!!specialUrls.imprint}>
|
||||
<TranslatedDropdownItem href={specialUrls.imprint} i18nKey={'appbar.help.legal.imprint'} />
|
||||
</ShowIf>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import links from '../../../../../../links.json'
|
||||
import { DropdownHeader } from '../dropdown-header'
|
||||
import { TranslatedDropdownItem } from '../translated-dropdown-item'
|
||||
import React, { Fragment } from 'react'
|
||||
import { Flag as IconFlag, Tag as IconTag, Github as IconGithub } from 'react-bootstrap-icons'
|
||||
|
||||
/**
|
||||
* Renders the project links submenu for the help dropdown.
|
||||
*/
|
||||
export const ProjectLinksSubmenu: React.FC = () => {
|
||||
return (
|
||||
<Fragment>
|
||||
<DropdownHeader i18nKey={'appbar.help.project.header'} />
|
||||
<TranslatedDropdownItem
|
||||
i18nKey={'appbar.help.project.github'}
|
||||
icon={IconGithub}
|
||||
href={links.githubOrg}
|
||||
target={'_blank'}
|
||||
/>
|
||||
<TranslatedDropdownItem
|
||||
i18nKey={'appbar.help.project.reportIssue'}
|
||||
icon={IconTag}
|
||||
href={links.issues}
|
||||
target={'_blank'}
|
||||
/>
|
||||
<TranslatedDropdownItem
|
||||
i18nKey={'appbar.help.project.helpTranslating'}
|
||||
icon={IconFlag}
|
||||
href={links.translate}
|
||||
target={'_blank'}
|
||||
/>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import links from '../../../../../../links.json'
|
||||
import { IconDiscourse } from '../../../../../common/icons/additional/icon-discourse'
|
||||
import { IconMatrixOrg } from '../../../../../common/icons/additional/icon-matrix-org'
|
||||
import { DropdownHeader } from '../dropdown-header'
|
||||
import { TranslatedDropdownItem } from '../translated-dropdown-item'
|
||||
import React, { Fragment } from 'react'
|
||||
import { Mastodon as IconMastodon } from 'react-bootstrap-icons'
|
||||
|
||||
/**
|
||||
* Renders the social links submenu for the help dropdown.
|
||||
*/
|
||||
export const SocialLinksSubmenu: React.FC = () => {
|
||||
return (
|
||||
<Fragment>
|
||||
<DropdownHeader i18nKey={'appbar.help.social.header'} />
|
||||
<TranslatedDropdownItem
|
||||
i18nKey={'appbar.help.social.discourse'}
|
||||
icon={IconDiscourse}
|
||||
href={links.community}
|
||||
target={'_blank'}
|
||||
/>
|
||||
<TranslatedDropdownItem
|
||||
i18nKey={'appbar.help.social.matrix'}
|
||||
icon={IconMatrixOrg}
|
||||
href={links.chat}
|
||||
target={'_blank'}
|
||||
/>
|
||||
<TranslatedDropdownItem
|
||||
i18nKey={'appbar.help.social.mastodon'}
|
||||
icon={IconMastodon}
|
||||
href={links.mastodon}
|
||||
target={'_blank'}
|
||||
/>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { useTranslatedText } from '../../../../../hooks/common/use-translated-text'
|
||||
import { UiIcon } from '../../../../common/icons/ui-icon'
|
||||
import { ShowIf } from '../../../../common/show-if/show-if'
|
||||
import type { TOptions } from 'i18next'
|
||||
import React from 'react'
|
||||
import { Dropdown } from 'react-bootstrap'
|
||||
import type { Icon } from 'react-bootstrap-icons'
|
||||
import type { DropdownItemProps } from 'react-bootstrap/DropdownItem'
|
||||
|
||||
interface TranslatedDropdownItemProps extends DropdownItemProps {
|
||||
i18nKey: string
|
||||
i18nKeyOptions?: TOptions
|
||||
icon?: Icon
|
||||
target?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a dropdown item with translated title.
|
||||
* @param i18nKey The i18n key for the title
|
||||
* @param i18nKeyOptions The options for the i18n key
|
||||
* @param icon The icon that should be rendered before the title
|
||||
* @param props Other props for the dropdown item
|
||||
*/
|
||||
export const TranslatedDropdownItem: React.FC<TranslatedDropdownItemProps> = ({
|
||||
i18nKey,
|
||||
i18nKeyOptions,
|
||||
icon,
|
||||
...props
|
||||
}) => {
|
||||
const title = useTranslatedText(i18nKey, i18nKeyOptions)
|
||||
|
||||
return (
|
||||
<Dropdown.Item {...props} title={title} className={'d-flex align-items-center'}>
|
||||
<ShowIf condition={!!icon}>
|
||||
<UiIcon icon={icon} className={'me-2'} />
|
||||
</ShowIf>
|
||||
<span>{title}</span>
|
||||
</Dropdown.Item>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
.read-only-marker {
|
||||
font-size: 0.8em;
|
||||
margin-top: -0.3em;
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { useMayEdit } from '../../../../../hooks/common/use-may-edit'
|
||||
import { useNoteTitle } from '../../../../../hooks/common/use-note-title'
|
||||
import { useTranslatedText } from '../../../../../hooks/common/use-translated-text'
|
||||
import { UiIcon } from '../../../../common/icons/ui-icon'
|
||||
import { ShowIf } from '../../../../common/show-if/show-if'
|
||||
import React, { Fragment } from 'react'
|
||||
import { Lock as IconLock } from 'react-bootstrap-icons'
|
||||
|
||||
/**
|
||||
* Renders the title of the current note and an optional read-only marker.
|
||||
*/
|
||||
export const NoteTitleElement: React.FC = () => {
|
||||
const isWriteable = useMayEdit()
|
||||
const noteTitle = useNoteTitle()
|
||||
const readOnlyLabel = useTranslatedText('appbar.editor.readOnly')
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<ShowIf condition={!isWriteable}>
|
||||
<span className={'text-secondary me-2'}>
|
||||
<UiIcon icon={IconLock} title={readOnlyLabel} />
|
||||
</span>
|
||||
</ShowIf>
|
||||
<span className={'text-truncate mw-100'}>{noteTitle}</span>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* 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 { SignInButton } from '../../../landing-layout/navigation/sign-in-button'
|
||||
import { UserDropdown } from '../../../landing-layout/navigation/user-dropdown'
|
||||
import React from 'react'
|
||||
|
||||
/**
|
||||
* Renders either the user dropdown or the sign-in button depending on the user state.
|
||||
*/
|
||||
export const UserElement: React.FC = () => {
|
||||
const userExists = useApplicationState((state) => !!state.user)
|
||||
return userExists ? <UserDropdown /> : <SignInButton size={'sm'} />
|
||||
}
|
37
frontend/src/components/layout/app-bar/base-app-bar.tsx
Normal file
37
frontend/src/components/layout/app-bar/base-app-bar.tsx
Normal file
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { NewNoteButton } from '../../common/new-note-button/new-note-button'
|
||||
import { SettingsButton } from '../../global-dialogs/settings-dialog/settings-button'
|
||||
import { BrandingElement } from './app-bar-elements/branding-element'
|
||||
import { HelpDropdown } from './app-bar-elements/help-dropdown/help-dropdown'
|
||||
import { UserElement } from './app-bar-elements/user-element'
|
||||
import type { PropsWithChildren } from 'react'
|
||||
import React from 'react'
|
||||
import { Col, Nav, Navbar } from 'react-bootstrap'
|
||||
|
||||
/**
|
||||
* Renders the base app bar with branding, help, settings user elements.
|
||||
*/
|
||||
export const BaseAppBar: React.FC<PropsWithChildren> = ({ children }) => {
|
||||
return (
|
||||
<Navbar expand={true} className={'px-2 py-2 shadow-sm'}>
|
||||
<Col>
|
||||
<BrandingElement />
|
||||
</Col>
|
||||
<Col md={6} className={'h-100'}>
|
||||
<Nav className={'d-flex align-items-center justify-content-center h-100'}>{children}</Nav>
|
||||
</Col>
|
||||
<Col>
|
||||
<Nav className={'d-flex align-items-center justify-content-end gap-2'}>
|
||||
<HelpDropdown />
|
||||
<SettingsButton />
|
||||
<NewNoteButton />
|
||||
<UserElement />
|
||||
</Nav>
|
||||
</Col>
|
||||
</Navbar>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import * as UseApplicationStateModule from '../../../hooks/common/use-application-state'
|
||||
import type { ApplicationState } from '../../../redux/application-state'
|
||||
import { mockI18n } from '../../../test-utils/mock-i18n'
|
||||
import { EditorAppBar } from './editor-app-bar'
|
||||
import type { NoteGroupPermissionEntry, NoteUserPermissionEntry } from '@hedgedoc/commons'
|
||||
import { render } from '@testing-library/react'
|
||||
import type { PropsWithChildren } from 'react'
|
||||
import React from 'react'
|
||||
|
||||
jest.mock('./base-app-bar', () => ({
|
||||
__esModule: true,
|
||||
BaseAppBar: ({ children }: PropsWithChildren) => (
|
||||
<div>
|
||||
<span>first part</span>
|
||||
<div>{children}</div>
|
||||
<span>last part</span>
|
||||
</div>
|
||||
)
|
||||
}))
|
||||
jest.mock('../../../hooks/common/use-application-state')
|
||||
|
||||
const mockedCommonAppState = {
|
||||
noteDetails: {
|
||||
title: 'Note Title Test',
|
||||
permissions: {
|
||||
owner: 'test',
|
||||
sharedToGroups: [
|
||||
{
|
||||
groupName: '_EVERYONE',
|
||||
canEdit: false
|
||||
}
|
||||
] as NoteGroupPermissionEntry[],
|
||||
sharedToUsers: [] as NoteUserPermissionEntry[]
|
||||
}
|
||||
},
|
||||
user: {
|
||||
username: 'test'
|
||||
}
|
||||
}
|
||||
|
||||
describe('app bar', () => {
|
||||
beforeAll(mockI18n)
|
||||
afterAll(() => jest.restoreAllMocks())
|
||||
|
||||
it('contains note title when editor is synced', () => {
|
||||
jest.spyOn(UseApplicationStateModule, 'useApplicationState').mockImplementation((fn) => {
|
||||
return fn({
|
||||
...mockedCommonAppState,
|
||||
realtimeStatus: {
|
||||
isSynced: true
|
||||
}
|
||||
} as ApplicationState)
|
||||
})
|
||||
const view = render(<EditorAppBar />)
|
||||
expect(view.container).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('contains alert when editor is not synced', () => {
|
||||
jest.spyOn(UseApplicationStateModule, 'useApplicationState').mockImplementation((fn) => {
|
||||
return fn({
|
||||
...mockedCommonAppState,
|
||||
realtimeStatus: {
|
||||
isSynced: false
|
||||
}
|
||||
} as ApplicationState)
|
||||
})
|
||||
const view = render(<EditorAppBar />)
|
||||
expect(view.container).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('contains note title and read-only marker when having only read permissions', () => {
|
||||
jest.spyOn(UseApplicationStateModule, 'useApplicationState').mockImplementation((fn) => {
|
||||
return fn({
|
||||
...mockedCommonAppState,
|
||||
realtimeStatus: {
|
||||
isSynced: true
|
||||
},
|
||||
user: null
|
||||
} as ApplicationState)
|
||||
})
|
||||
const view = render(<EditorAppBar />)
|
||||
expect(view.container).toMatchSnapshot()
|
||||
})
|
||||
})
|
19
frontend/src/components/layout/app-bar/editor-app-bar.tsx
Normal file
19
frontend/src/components/layout/app-bar/editor-app-bar.tsx
Normal file
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* 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 { RealtimeConnectionAlert } from '../../editor-page/realtime-connection-alert/realtime-connection-alert'
|
||||
import { NoteTitleElement } from './app-bar-elements/note-title-element/note-title-element'
|
||||
import { BaseAppBar } from './base-app-bar'
|
||||
import React from 'react'
|
||||
|
||||
/**
|
||||
* Renders the EditorAppBar that extends the {@link BaseAppBar} with the note title or realtime connection alert.
|
||||
*/
|
||||
export const EditorAppBar: React.FC = () => {
|
||||
const isSynced = useApplicationState((state) => state.realtimeStatus.isSynced)
|
||||
|
||||
return <BaseAppBar>{isSynced ? <NoteTitleElement /> : <RealtimeConnectionAlert />}</BaseAppBar>
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue