From d10c6d3290165f50e1dac47864952e1c20720a0a Mon Sep 17 00:00:00 2001 From: Erik Michelson Date: Thu, 29 Jun 2023 22:17:03 +0200 Subject: [PATCH] refactor: move help entries into new global app bar Co-authored-by: Tilman Vatteroth Signed-off-by: Tilman Vatteroth Signed-off-by: Erik Michelson --- frontend/cypress/e2e/intro.spec.ts | 12 +-- frontend/locales/en.json | 32 +++++-- .../custom-branding.spec.tsx.snap | 6 +- .../branding-separator-dash.tsx | 2 +- .../custom-branding/branding.module.scss | 11 ++- .../custom-branding/custom-branding.tsx | 14 ++- .../editor-page/app-bar/app-bar.tsx | 68 -------------- .../app-bar/help-button/help-button.tsx | 39 -------- .../editor-page/app-bar/navbar-branding.tsx | 30 ------- .../app-bar/read-only-mode-button.tsx | 30 ------- .../editor-page/app-bar/slide-mode-button.tsx | 30 ------- .../editor-page/editor-page-content.tsx | 6 +- .../realtime-connection-alert.spec.tsx.snap | 6 +- .../realtime-connection-alert.spec.tsx | 14 +-- .../realtime-connection-alert.tsx | 12 +-- .../landing-layout/footer/footer.tsx | 20 ----- .../footer/powered-by-links.tsx | 45 ---------- .../landing-layout/footer/social-links.tsx | 33 ------- .../footer/version-info/version-info-link.tsx | 27 ------ .../landing-layout/landing-layout.tsx | 6 +- .../navigation/header-bar/header-bar.tsx | 11 --- .../editor-app-bar.spec.tsx.snap | 68 ++++++++++++++ .../branding-element.module.css | 10 +++ .../app-bar-elements/branding-element.tsx | 31 +++++++ .../help-dropdown/dropdown-header.tsx | 26 ++++++ .../help-dropdown/help-dropdown.tsx | 40 +++++++++ .../help-dropdown/submenues/help-submenu.tsx | 22 +++++ .../help/cheatsheet-help-menu-entry.tsx | 47 ++++++++++ .../help/shortcuts-help-menu-entry.tsx | 24 +++++ .../submenues/instance-submenu.tsx | 20 +++++ .../instance/version-info-help-menu-entry.tsx | 24 +++++ .../help-dropdown/submenues/legal-submenu.tsx | 44 +++++++++ .../submenues/project-links-submenu.tsx | 39 ++++++++ .../submenues/social-links-submenu.tsx | 41 +++++++++ .../translated-dropdown-item.tsx | 45 ++++++++++ .../note-title-element.module.css | 10 +++ .../note-title-element/note-title-element.tsx | 32 +++++++ .../app-bar/app-bar-elements/user-element.tsx | 17 ++++ .../layout/app-bar/base-app-bar.tsx | 37 ++++++++ .../layout/app-bar/editor-app-bar.spec.tsx | 89 +++++++++++++++++++ .../layout/app-bar/editor-app-bar.tsx | 19 ++++ frontend/src/pages/intro.tsx | 1 - frontend/src/pages/s/[noteId].tsx | 6 +- 43 files changed, 749 insertions(+), 397 deletions(-) rename frontend/src/components/{editor-page/app-bar => common/custom-branding}/branding-separator-dash.tsx (81%) delete mode 100644 frontend/src/components/editor-page/app-bar/app-bar.tsx delete mode 100644 frontend/src/components/editor-page/app-bar/help-button/help-button.tsx delete mode 100644 frontend/src/components/editor-page/app-bar/navbar-branding.tsx delete mode 100644 frontend/src/components/editor-page/app-bar/read-only-mode-button.tsx delete mode 100644 frontend/src/components/editor-page/app-bar/slide-mode-button.tsx delete mode 100644 frontend/src/components/landing-layout/footer/footer.tsx delete mode 100644 frontend/src/components/landing-layout/footer/powered-by-links.tsx delete mode 100644 frontend/src/components/landing-layout/footer/social-links.tsx delete mode 100644 frontend/src/components/landing-layout/footer/version-info/version-info-link.tsx create mode 100644 frontend/src/components/layout/app-bar/__snapshots__/editor-app-bar.spec.tsx.snap create mode 100644 frontend/src/components/layout/app-bar/app-bar-elements/branding-element.module.css create mode 100644 frontend/src/components/layout/app-bar/app-bar-elements/branding-element.tsx create mode 100644 frontend/src/components/layout/app-bar/app-bar-elements/help-dropdown/dropdown-header.tsx create mode 100644 frontend/src/components/layout/app-bar/app-bar-elements/help-dropdown/help-dropdown.tsx create mode 100644 frontend/src/components/layout/app-bar/app-bar-elements/help-dropdown/submenues/help-submenu.tsx create mode 100644 frontend/src/components/layout/app-bar/app-bar-elements/help-dropdown/submenues/help/cheatsheet-help-menu-entry.tsx create mode 100644 frontend/src/components/layout/app-bar/app-bar-elements/help-dropdown/submenues/help/shortcuts-help-menu-entry.tsx create mode 100644 frontend/src/components/layout/app-bar/app-bar-elements/help-dropdown/submenues/instance-submenu.tsx create mode 100644 frontend/src/components/layout/app-bar/app-bar-elements/help-dropdown/submenues/instance/version-info-help-menu-entry.tsx create mode 100644 frontend/src/components/layout/app-bar/app-bar-elements/help-dropdown/submenues/legal-submenu.tsx create mode 100644 frontend/src/components/layout/app-bar/app-bar-elements/help-dropdown/submenues/project-links-submenu.tsx create mode 100644 frontend/src/components/layout/app-bar/app-bar-elements/help-dropdown/submenues/social-links-submenu.tsx create mode 100644 frontend/src/components/layout/app-bar/app-bar-elements/help-dropdown/translated-dropdown-item.tsx create mode 100644 frontend/src/components/layout/app-bar/app-bar-elements/note-title-element/note-title-element.module.css create mode 100644 frontend/src/components/layout/app-bar/app-bar-elements/note-title-element/note-title-element.tsx create mode 100644 frontend/src/components/layout/app-bar/app-bar-elements/user-element.tsx create mode 100644 frontend/src/components/layout/app-bar/base-app-bar.tsx create mode 100644 frontend/src/components/layout/app-bar/editor-app-bar.spec.tsx create mode 100644 frontend/src/components/layout/app-bar/editor-app-bar.tsx diff --git a/frontend/cypress/e2e/intro.spec.ts b/frontend/cypress/e2e/intro.spec.ts index 2e3ac7e52..c63905845 100644 --- a/frontend/cypress/e2e/intro.spec.ts +++ b/frontend/cypress/e2e/intro.spec.ts @@ -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 */ @@ -36,14 +36,4 @@ describe('Intro page', () => { cy.getByCypressId('sign-in-button').should('exist') }) }) - - describe('version dialog', () => { - it('can be opened and closed', () => { - cy.getByCypressId('version-modal').should('not.exist') - cy.getByCypressId('show-version-modal').click() - cy.getByCypressId('version-modal').should('be.visible') - cy.getByCypressId('version-modal').find('.modal-header .btn-close').click() - cy.getByCypressId('version-modal').should('not.exist') - }) - }) }) diff --git a/frontend/locales/en.json b/frontend/locales/en.json index 22ccb8d19..e758e986d 100644 --- a/frontend/locales/en.json +++ b/frontend/locales/en.json @@ -147,11 +147,7 @@ }, "footer": { "releases": "Releases", - "poweredBy": "Powered by <0>", - "imprint": "Imprint", - "followUs": "Follow us on <0>, <1>, <2>, <3>, and <4>.", - "privacy": "Privacy", - "termsOfUse": "Terms of Use" + "poweredBy": "Powered by <0>" }, "versionInfo": { "issueTracker": "Found a bug? Fill an issue!", @@ -485,11 +481,36 @@ } }, "appbar": { + "editor": { + "readOnly": "read-only" + }, "help": { "help": { "header": "Help", "shortcuts": "Shortcuts", "cheatsheet": "Cheatsheet" + }, + "instance": { + "header": "About this instance", + "versionInfo": "Running version" + }, + "legal": { + "header": "Legal", + "privacy": "Privacy Policy", + "termsOfUse": "Terms of use", + "imprint": "Imprint" + }, + "project": { + "header": "Project", + "github": "Project on GitHub", + "reportIssue": "Report an issue", + "helpTranslating": "Help us translating" + }, + "social": { + "header": "Social", + "discourse": "Join the community", + "matrix": "Chat with us on Matrix", + "mastodon": "Follow us on Mastodon" } } }, @@ -642,7 +663,6 @@ } }, "cheatsheet": { - "button": "Open Cheatsheet", "search": "Search for Cheatsheets", "modal": { "popup": "Open in new tab", diff --git a/frontend/src/components/common/custom-branding/__snapshots__/custom-branding.spec.tsx.snap b/frontend/src/components/common/custom-branding/__snapshots__/custom-branding.spec.tsx.snap index 3fd60b783..aba5f84f6 100644 --- a/frontend/src/components/common/custom-branding/__snapshots__/custom-branding.spec.tsx.snap +++ b/frontend/src/components/common/custom-branding/__snapshots__/custom-branding.spec.tsx.snap @@ -35,7 +35,7 @@ exports[`custom branding with inline=false will prefer the logo over the text 1` exports[`custom branding with inline=true shows an image if branding logo is defined 1`] = `
@@ -44,7 +44,7 @@ exports[`custom branding with inline=true shows an image if branding logo is def exports[`custom branding with inline=true shows an text if branding text is defined 1`] = `
mockedBranding @@ -55,7 +55,7 @@ exports[`custom branding with inline=true will prefer the logo over the text 1`]
mockedBranding diff --git a/frontend/src/components/editor-page/app-bar/branding-separator-dash.tsx b/frontend/src/components/common/custom-branding/branding-separator-dash.tsx similarity index 81% rename from frontend/src/components/editor-page/app-bar/branding-separator-dash.tsx rename to frontend/src/components/common/custom-branding/branding-separator-dash.tsx index 83855ced4..2327b55cd 100644 --- a/frontend/src/components/editor-page/app-bar/branding-separator-dash.tsx +++ b/frontend/src/components/common/custom-branding/branding-separator-dash.tsx @@ -3,7 +3,7 @@ * * SPDX-License-Identifier: AGPL-3.0-only */ -import { useBrandingDetails } from '../../common/custom-branding/use-branding-details' +import { useBrandingDetails } from './use-branding-details' import React from 'react' /** diff --git a/frontend/src/components/common/custom-branding/branding.module.scss b/frontend/src/components/common/custom-branding/branding.module.scss index f4823f262..c3702dc12 100644 --- a/frontend/src/components/common/custom-branding/branding.module.scss +++ b/frontend/src/components/common/custom-branding/branding.module.scss @@ -1,4 +1,4 @@ -/* +/*! * SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file) * * SPDX-License-Identifier: AGPL-3.0-only @@ -8,6 +8,11 @@ height: 50px; } -.inline-size { - height: 30px; +.inline-logo { + height: 32px; +} + +.inline-text { + font-size: 18px; + line-height: 1.0; } diff --git a/frontend/src/components/common/custom-branding/custom-branding.tsx b/frontend/src/components/common/custom-branding/custom-branding.tsx index d2e6fe5e9..e0f79dd44 100644 --- a/frontend/src/components/common/custom-branding/custom-branding.tsx +++ b/frontend/src/components/common/custom-branding/custom-branding.tsx @@ -3,9 +3,10 @@ * * SPDX-License-Identifier: AGPL-3.0-only */ +import { concatCssClasses } from '../../../utils/concat-css-classes' import styles from './branding.module.scss' import { useBrandingDetails } from './use-branding-details' -import React from 'react' +import React, { useMemo } from 'react' export interface BrandingProps { inline?: boolean @@ -16,12 +17,19 @@ export interface BrandingProps { * This branding can either be a text, a logo or both (in that case the text is used as the title and alt of the image). * * @param inline If the logo should be using the inline-size or the regular-size css class. - * @param delimiter If the delimiter between the HedgeDoc logo and the branding should be shown. */ export const CustomBranding: React.FC = ({ inline = false }) => { const branding = useBrandingDetails() - const className = inline ? styles['inline-size'] : styles['regular-size'] + const className = useMemo( + () => + concatCssClasses({ + [styles['regular-size']]: !inline, + [styles['inline-logo']]: inline && branding && !!branding.logo, + [styles['inline-text']]: inline && branding && !branding.logo + }), + [inline, branding] + ) if (!branding) { return null diff --git a/frontend/src/components/editor-page/app-bar/app-bar.tsx b/frontend/src/components/editor-page/app-bar/app-bar.tsx deleted file mode 100644 index 491943c61..000000000 --- a/frontend/src/components/editor-page/app-bar/app-bar.tsx +++ /dev/null @@ -1,68 +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 { useOutlineButtonVariant } from '../../../hooks/dark-mode/use-outline-button-variant' -import { CheatsheetButton } from '../../cheatsheet/cheatsheet-button' -import { NewNoteButton } from '../../common/new-note-button/new-note-button' -import { ShowIf } from '../../common/show-if/show-if' -import { SettingsButton } from '../../global-dialogs/settings-dialog/settings-button' -import { SignInButton } from '../../landing-layout/navigation/sign-in-button' -import { UserDropdown } from '../../landing-layout/navigation/user-dropdown' -import { HelpButton } from './help-button/help-button' -import { NavbarBranding } from './navbar-branding' -import { ReadOnlyModeButton } from './read-only-mode-button' -import { SlideModeButton } from './slide-mode-button' -import { NoteType } from '@hedgedoc/commons' -import React from 'react' -import { Nav, Navbar } from 'react-bootstrap' - -export enum AppBarMode { - BASIC, - EDITOR -} - -export interface AppBarProps { - mode: AppBarMode -} - -/** - * Renders the app bar. - * - * @param mode Which mode the app bar should be rendered in. This mainly adds / removes buttons for the editor. - */ -export const AppBar: React.FC = ({ mode }) => { - const userExists = useApplicationState((state) => !!state.user) - const noteFrontmatter = useApplicationState((state) => state.noteDetails.frontmatter) - const buttonVariant = useOutlineButtonVariant() - - return ( - - - - - ) -} diff --git a/frontend/src/components/editor-page/app-bar/help-button/help-button.tsx b/frontend/src/components/editor-page/app-bar/help-button/help-button.tsx deleted file mode 100644 index 2c00ee0ee..000000000 --- a/frontend/src/components/editor-page/app-bar/help-button/help-button.tsx +++ /dev/null @@ -1,39 +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 { useTranslatedText } from '../../../../hooks/common/use-translated-text' -import { useOutlineButtonVariant } from '../../../../hooks/dark-mode/use-outline-button-variant' -import { cypressId } from '../../../../utils/cypress-attribute' -import { IconButton } from '../../../common/icon-button/icon-button' -import { ShortcutsModal } from '../../../global-dialogs/shortcuts-modal/shortcuts-modal' -import React, { Fragment } from 'react' -import { QuestionCircle as IconQuestionCircle } from 'react-bootstrap-icons' -import { Trans } from 'react-i18next' - -/** - * Renders the button to open the shortcuts modal. - */ -export const HelpButton: React.FC = () => { - const [modalVisibility, showModal, closeModal] = useBooleanState() - const buttonVariant = useOutlineButtonVariant() - const buttonTitle = useTranslatedText('editor.documentBar.help') - - return ( - - - - - - - ) -} diff --git a/frontend/src/components/editor-page/app-bar/navbar-branding.tsx b/frontend/src/components/editor-page/app-bar/navbar-branding.tsx deleted file mode 100644 index f518455c8..000000000 --- a/frontend/src/components/editor-page/app-bar/navbar-branding.tsx +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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 { 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 { BrandingSeparatorDash } from './branding-separator-dash' -import Link from 'next/link' -import React from 'react' -import { Navbar } from 'react-bootstrap' - -/** - * Renders the branding for the {@link AppBar} - */ -export const NavbarBranding: React.FC = () => { - const darkModeActivated = useDarkModeState() - - return ( - - - - - - - - ) -} diff --git a/frontend/src/components/editor-page/app-bar/read-only-mode-button.tsx b/frontend/src/components/editor-page/app-bar/read-only-mode-button.tsx deleted file mode 100644 index 5ba8509ed..000000000 --- a/frontend/src/components/editor-page/app-bar/read-only-mode-button.tsx +++ /dev/null @@ -1,30 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ -import { useApplicationState } from '../../../hooks/common/use-application-state' -import { useTranslatedText } from '../../../hooks/common/use-translated-text' -import { useOutlineButtonVariant } from '../../../hooks/dark-mode/use-outline-button-variant' -import { UiIcon } from '../../common/icons/ui-icon' -import Link from 'next/link' -import React from 'react' -import { Button } from 'react-bootstrap' -import { FileEarmarkTextFill as IconFileEarmarkTextFill } from 'react-bootstrap-icons' - -/** - * Button that links to the read-only version of a note. - */ -export const ReadOnlyModeButton: React.FC = () => { - const noteIdentifier = useApplicationState((state) => state.noteDetails.primaryAddress) - const buttonVariant = useOutlineButtonVariant() - const buttonTitle = useTranslatedText('editor.documentBar.readOnlyMode') - - return ( - - - - ) -} diff --git a/frontend/src/components/editor-page/app-bar/slide-mode-button.tsx b/frontend/src/components/editor-page/app-bar/slide-mode-button.tsx deleted file mode 100644 index 2c31173e8..000000000 --- a/frontend/src/components/editor-page/app-bar/slide-mode-button.tsx +++ /dev/null @@ -1,30 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ -import { useApplicationState } from '../../../hooks/common/use-application-state' -import { useTranslatedText } from '../../../hooks/common/use-translated-text' -import { useOutlineButtonVariant } from '../../../hooks/dark-mode/use-outline-button-variant' -import { UiIcon } from '../../common/icons/ui-icon' -import Link from 'next/link' -import React from 'react' -import { Button } from 'react-bootstrap' -import { Tv as IconTv } from 'react-bootstrap-icons' - -/** - * Button that links to the slide-show presentation of the current note. - */ -export const SlideModeButton: React.FC = () => { - const noteIdentifier = useApplicationState((state) => state.noteDetails.primaryAddress) - const buttonVariant = useOutlineButtonVariant() - const buttonTitle = useTranslatedText('editor.documentBar.slideMode') - - return ( - - - - ) -} diff --git a/frontend/src/components/editor-page/editor-page-content.tsx b/frontend/src/components/editor-page/editor-page-content.tsx index 30eff1c2d..3803a775a 100644 --- a/frontend/src/components/editor-page/editor-page-content.tsx +++ b/frontend/src/components/editor-page/editor-page-content.tsx @@ -6,9 +6,9 @@ import { useApplyDarkModeStyle } from '../../hooks/dark-mode/use-apply-dark-mode-style' import { useSaveDarkModePreferenceToLocalStorage } from '../../hooks/dark-mode/use-save-dark-mode-preference-to-local-storage' import { MotdModal } from '../global-dialogs/motd-modal/motd-modal' +import { EditorAppBar } from '../layout/app-bar/editor-app-bar' import { CommunicatorImageLightbox } from '../markdown-renderer/extensions/image/communicator-image-lightbox' import { ExtensionEventEmitterProvider } from '../markdown-renderer/hooks/use-extension-event-emitter' -import { AppBar, AppBarMode } from './app-bar/app-bar' import { ChangeEditorContentContextProvider } from './change-content-context/codemirror-reference-context' import { EditorPane } from './editor-pane/editor-pane' import { useComponentsFromAppExtensions } from './editor-pane/hooks/use-components-from-app-extensions' @@ -16,7 +16,6 @@ import { HeadMetaProperties } from './head-meta-properties/head-meta-properties' import { useScrollState } from './hooks/use-scroll-state' import { useSetScrollSource } from './hooks/use-set-scroll-source' import { useUpdateLocalHistoryEntry } from './hooks/use-update-local-history-entry' -import { RealtimeConnectionAlert } from './realtime-connection-alert/realtime-connection-alert' import { RendererPane } from './renderer-pane/renderer-pane' import { Sidebar } from './sidebar/sidebar' import { Splitter } from './splitter/splitter' @@ -77,8 +76,7 @@ export const EditorPageContent: React.FC = () => {
- - +
`; - -exports[`realtime connection alert won't show if synced 1`] = `
`; diff --git a/frontend/src/components/editor-page/realtime-connection-alert/realtime-connection-alert.spec.tsx b/frontend/src/components/editor-page/realtime-connection-alert/realtime-connection-alert.spec.tsx index fee16a247..af6d85dba 100644 --- a/frontend/src/components/editor-page/realtime-connection-alert/realtime-connection-alert.spec.tsx +++ b/frontend/src/components/editor-page/realtime-connection-alert/realtime-connection-alert.spec.tsx @@ -3,26 +3,14 @@ * * SPDX-License-Identifier: AGPL-3.0-only */ -import * as UseApplicationStateModule from '../../../hooks/common/use-application-state' import { mockI18n } from '../../../test-utils/mock-i18n' import { RealtimeConnectionAlert } from './realtime-connection-alert' import { render } from '@testing-library/react' -jest.mock('../../../hooks/common/use-application-state') - describe('realtime connection alert', () => { beforeAll(mockI18n) - it("won't show if synced", () => { - jest.spyOn(UseApplicationStateModule, 'useApplicationState').mockImplementation(() => true) - - const view = render() - expect(view.container).toMatchSnapshot() - }) - - it('will show if not synced', () => { - jest.spyOn(UseApplicationStateModule, 'useApplicationState').mockImplementation(() => false) - + it('will render correctly', () => { const view = render() expect(view.container).toMatchSnapshot() }) diff --git a/frontend/src/components/editor-page/realtime-connection-alert/realtime-connection-alert.tsx b/frontend/src/components/editor-page/realtime-connection-alert/realtime-connection-alert.tsx index bf1525f36..45a81dcd5 100644 --- a/frontend/src/components/editor-page/realtime-connection-alert/realtime-connection-alert.tsx +++ b/frontend/src/components/editor-page/realtime-connection-alert/realtime-connection-alert.tsx @@ -1,11 +1,10 @@ /* - * 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 */ -import { useApplicationState } from '../../../hooks/common/use-application-state' import { UiIcon } from '../../common/icons/ui-icon' -import React, { Fragment } from 'react' +import React from 'react' import { Alert } from 'react-bootstrap' import { ArrowRepeat as IconArrowRepeat } from 'react-bootstrap-icons' import { Trans, useTranslation } from 'react-i18next' @@ -14,15 +13,10 @@ import { Trans, useTranslation } from 'react-i18next' * Modal with a spinner that is only shown while reconnecting to the realtime backend */ export const RealtimeConnectionAlert: React.FC = () => { - const isSynced = useApplicationState((state) => state.realtimeStatus.isSynced) useTranslation() - if (isSynced) { - return - } - return ( - + diff --git a/frontend/src/components/landing-layout/footer/footer.tsx b/frontend/src/components/landing-layout/footer/footer.tsx deleted file mode 100644 index 193d23c20..000000000 --- a/frontend/src/components/landing-layout/footer/footer.tsx +++ /dev/null @@ -1,20 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ -import { PoweredByLinks } from './powered-by-links' -import { SocialLink } from './social-links' -import React from 'react' - -/** - * Renders the footer. - */ -export const Footer: React.FC = () => { - return ( -
- - -
- ) -} diff --git a/frontend/src/components/landing-layout/footer/powered-by-links.tsx b/frontend/src/components/landing-layout/footer/powered-by-links.tsx deleted file mode 100644 index 8fd436884..000000000 --- a/frontend/src/components/landing-layout/footer/powered-by-links.tsx +++ /dev/null @@ -1,45 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ -import links from '../../../links.json' -import { useFrontendConfig } from '../../common/frontend-config-context/use-frontend-config' -import { ExternalLink } from '../../common/links/external-link' -import { TranslatedExternalLink } from '../../common/links/translated-external-link' -import { TranslatedInternalLink } from '../../common/links/translated-internal-link' -import { VersionInfoLink } from './version-info/version-info-link' -import React, { Fragment, useMemo } from 'react' -import { Trans, useTranslation } from 'react-i18next' - -/** - * Renders a powered-by link. - */ -export const PoweredByLinks: React.FC = () => { - useTranslation() - - const rawSpecialUrls = useFrontendConfig().specialUrls - - const specialUrls = useMemo( - () => Object.entries(rawSpecialUrls).map(([i18nkey, url]) => [i18nkey, String(url)]), - [rawSpecialUrls] - ) - - return ( -

- - - -  |  - - {specialUrls.map(([i18nKey, href]) => ( - -  |  - - - ))} -  |  - -

- ) -} diff --git a/frontend/src/components/landing-layout/footer/social-links.tsx b/frontend/src/components/landing-layout/footer/social-links.tsx deleted file mode 100644 index afc5319ed..000000000 --- a/frontend/src/components/landing-layout/footer/social-links.tsx +++ /dev/null @@ -1,33 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2022 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 { ExternalLink } from '../../common/links/external-link' -import React from 'react' -import { Github as IconGithub, Globe as IconGlobe, Mastodon as IconMastodon } from 'react-bootstrap-icons' -import { Trans, useTranslation } from 'react-i18next' - -/** - * Renders links to the social networks. - */ -export const SocialLink: React.FC = () => { - useTranslation() - return ( -

- , - , - , - , - - ]} - /> -

- ) -} diff --git a/frontend/src/components/landing-layout/footer/version-info/version-info-link.tsx b/frontend/src/components/landing-layout/footer/version-info/version-info-link.tsx deleted file mode 100644 index e8c2b4107..000000000 --- a/frontend/src/components/landing-layout/footer/version-info/version-info-link.tsx +++ /dev/null @@ -1,27 +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 { VersionInfoModal } from '../../../global-dialogs/version-info-modal/version-info-modal' -import React, { Fragment } from 'react' -import { Button } from 'react-bootstrap' -import { Trans } from 'react-i18next' - -/** - * Renders a link for the version info and the {@link VersionInfoModal}. - */ -export const VersionInfoLink: React.FC = () => { - const [modalVisibility, showModal, closeModal] = useBooleanState() - - return ( - - - - - ) -} diff --git a/frontend/src/components/landing-layout/landing-layout.tsx b/frontend/src/components/landing-layout/landing-layout.tsx index e534f3b97..921f8fb55 100644 --- a/frontend/src/components/landing-layout/landing-layout.tsx +++ b/frontend/src/components/landing-layout/landing-layout.tsx @@ -6,7 +6,7 @@ import { useApplyDarkModeStyle } from '../../hooks/dark-mode/use-apply-dark-mode-style' import { useSaveDarkModePreferenceToLocalStorage } from '../../hooks/dark-mode/use-save-dark-mode-preference-to-local-storage' import { MotdModal } from '../global-dialogs/motd-modal/motd-modal' -import { Footer } from './footer/footer' +import { BaseAppBar } from '../layout/app-bar/base-app-bar' import { HeaderBar } from './navigation/header-bar/header-bar' import type { PropsWithChildren } from 'react' import React from 'react' @@ -23,12 +23,12 @@ export const LandingLayout: React.FC = ({ children }) => { return (
+ - +
{children}
-
diff --git a/frontend/src/components/landing-layout/navigation/header-bar/header-bar.tsx b/frontend/src/components/landing-layout/navigation/header-bar/header-bar.tsx index 49120af42..d4561a1e3 100644 --- a/frontend/src/components/landing-layout/navigation/header-bar/header-bar.tsx +++ b/frontend/src/components/landing-layout/navigation/header-bar/header-bar.tsx @@ -3,12 +3,7 @@ * * SPDX-License-Identifier: AGPL-3.0-only */ -import { useApplicationState } from '../../../../hooks/common/use-application-state' import { cypressId } from '../../../../utils/cypress-attribute' -import { NewNoteButton } from '../../../common/new-note-button/new-note-button' -import { SettingsButton } from '../../../global-dialogs/settings-dialog/settings-button' -import { SignInButton } from '../sign-in-button' -import { UserDropdown } from '../user-dropdown' import { HeaderNavLink } from './header-nav-link' import React from 'react' import { Navbar } from 'react-bootstrap' @@ -19,7 +14,6 @@ import { Trans, useTranslation } from 'react-i18next' */ const HeaderBar: React.FC = () => { useTranslation() - const userExists = useApplicationState((state) => !!state.user) return ( @@ -31,11 +25,6 @@ const HeaderBar: React.FC = () => {
-
- - - {!userExists ? : } -
) } diff --git a/frontend/src/components/layout/app-bar/__snapshots__/editor-app-bar.spec.tsx.snap b/frontend/src/components/layout/app-bar/__snapshots__/editor-app-bar.spec.tsx.snap new file mode 100644 index 000000000..b9416557c --- /dev/null +++ b/frontend/src/components/layout/app-bar/__snapshots__/editor-app-bar.spec.tsx.snap @@ -0,0 +1,68 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`app bar contains alert when editor is not synced 1`] = ` +
+
+ + first part + +
+ +
+ + last part + +
+
+`; + +exports[`app bar contains note title and read-only marker when having only read permissions 1`] = ` +
+
+ + first part + +
+ + BootstrapIconMock_Lock + + + Note Title Test + +
+ + last part + +
+
+`; + +exports[`app bar contains note title when editor is synced 1`] = ` +
+
+ + first part + +
+ + Note Title Test + +
+ + last part + +
+
+`; diff --git a/frontend/src/components/layout/app-bar/app-bar-elements/branding-element.module.css b/frontend/src/components/layout/app-bar/app-bar-elements/branding-element.module.css new file mode 100644 index 000000000..ee3614095 --- /dev/null +++ b/frontend/src/components/layout/app-bar/app-bar-elements/branding-element.module.css @@ -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; +} diff --git a/frontend/src/components/layout/app-bar/app-bar-elements/branding-element.tsx b/frontend/src/components/layout/app-bar/app-bar-elements/branding-element.tsx new file mode 100644 index 000000000..47ffc431b --- /dev/null +++ b/frontend/src/components/layout/app-bar/app-bar-elements/branding-element.tsx @@ -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 ( + +
+ +
+ + + + ) +} diff --git a/frontend/src/components/layout/app-bar/app-bar-elements/help-dropdown/dropdown-header.tsx b/frontend/src/components/layout/app-bar/app-bar-elements/help-dropdown/dropdown-header.tsx new file mode 100644 index 000000000..f6ed9dc10 --- /dev/null +++ b/frontend/src/components/layout/app-bar/app-bar-elements/help-dropdown/dropdown-header.tsx @@ -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 = ({ i18nKey }) => { + useTranslation() + + return ( + + + + ) +} diff --git a/frontend/src/components/layout/app-bar/app-bar-elements/help-dropdown/help-dropdown.tsx b/frontend/src/components/layout/app-bar/app-bar-elements/help-dropdown/help-dropdown.tsx new file mode 100644 index 000000000..4b0b8780f --- /dev/null +++ b/frontend/src/components/layout/app-bar/app-bar-elements/help-dropdown/help-dropdown.tsx @@ -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 ( + + + + + + + + + + + + + + + + ) +} diff --git a/frontend/src/components/layout/app-bar/app-bar-elements/help-dropdown/submenues/help-submenu.tsx b/frontend/src/components/layout/app-bar/app-bar-elements/help-dropdown/submenues/help-submenu.tsx new file mode 100644 index 000000000..d96ef6a59 --- /dev/null +++ b/frontend/src/components/layout/app-bar/app-bar-elements/help-dropdown/submenues/help-submenu.tsx @@ -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 ( + + + + + + ) +} diff --git a/frontend/src/components/layout/app-bar/app-bar-elements/help-dropdown/submenues/help/cheatsheet-help-menu-entry.tsx b/frontend/src/components/layout/app-bar/app-bar-elements/help-dropdown/submenues/help/cheatsheet-help-menu-entry.tsx new file mode 100644 index 000000000..e6e8895e0 --- /dev/null +++ b/frontend/src/components/layout/app-bar/app-bar-elements/help-dropdown/submenues/help/cheatsheet-help-menu-entry.tsx @@ -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 ( + + + + +
+ }> + + + + + + ) +} diff --git a/frontend/src/components/layout/app-bar/app-bar-elements/help-dropdown/submenues/help/shortcuts-help-menu-entry.tsx b/frontend/src/components/layout/app-bar/app-bar-elements/help-dropdown/submenues/help/shortcuts-help-menu-entry.tsx new file mode 100644 index 000000000..9e816bc51 --- /dev/null +++ b/frontend/src/components/layout/app-bar/app-bar-elements/help-dropdown/submenues/help/shortcuts-help-menu-entry.tsx @@ -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 ( + + + + + ) +} diff --git a/frontend/src/components/layout/app-bar/app-bar-elements/help-dropdown/submenues/instance-submenu.tsx b/frontend/src/components/layout/app-bar/app-bar-elements/help-dropdown/submenues/instance-submenu.tsx new file mode 100644 index 000000000..3e011cd29 --- /dev/null +++ b/frontend/src/components/layout/app-bar/app-bar-elements/help-dropdown/submenues/instance-submenu.tsx @@ -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 ( + + + + + ) +} diff --git a/frontend/src/components/layout/app-bar/app-bar-elements/help-dropdown/submenues/instance/version-info-help-menu-entry.tsx b/frontend/src/components/layout/app-bar/app-bar-elements/help-dropdown/submenues/instance/version-info-help-menu-entry.tsx new file mode 100644 index 000000000..f9fb27ef7 --- /dev/null +++ b/frontend/src/components/layout/app-bar/app-bar-elements/help-dropdown/submenues/instance/version-info-help-menu-entry.tsx @@ -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 ( + + + + + ) +} diff --git a/frontend/src/components/layout/app-bar/app-bar-elements/help-dropdown/submenues/legal-submenu.tsx b/frontend/src/components/layout/app-bar/app-bar-elements/help-dropdown/submenues/legal-submenu.tsx new file mode 100644 index 000000000..3d5b2de41 --- /dev/null +++ b/frontend/src/components/layout/app-bar/app-bar-elements/help-dropdown/submenues/legal-submenu.tsx @@ -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 ( + + + + + + + + + + + + + + ) +} diff --git a/frontend/src/components/layout/app-bar/app-bar-elements/help-dropdown/submenues/project-links-submenu.tsx b/frontend/src/components/layout/app-bar/app-bar-elements/help-dropdown/submenues/project-links-submenu.tsx new file mode 100644 index 000000000..051909d59 --- /dev/null +++ b/frontend/src/components/layout/app-bar/app-bar-elements/help-dropdown/submenues/project-links-submenu.tsx @@ -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 ( + + + + + + + ) +} diff --git a/frontend/src/components/layout/app-bar/app-bar-elements/help-dropdown/submenues/social-links-submenu.tsx b/frontend/src/components/layout/app-bar/app-bar-elements/help-dropdown/submenues/social-links-submenu.tsx new file mode 100644 index 000000000..32ad134ab --- /dev/null +++ b/frontend/src/components/layout/app-bar/app-bar-elements/help-dropdown/submenues/social-links-submenu.tsx @@ -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 ( + + + + + + + ) +} diff --git a/frontend/src/components/layout/app-bar/app-bar-elements/help-dropdown/translated-dropdown-item.tsx b/frontend/src/components/layout/app-bar/app-bar-elements/help-dropdown/translated-dropdown-item.tsx new file mode 100644 index 000000000..fdf0e26ec --- /dev/null +++ b/frontend/src/components/layout/app-bar/app-bar-elements/help-dropdown/translated-dropdown-item.tsx @@ -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 = ({ + i18nKey, + i18nKeyOptions, + icon, + ...props +}) => { + const title = useTranslatedText(i18nKey, i18nKeyOptions) + + return ( + + + + + {title} + + ) +} diff --git a/frontend/src/components/layout/app-bar/app-bar-elements/note-title-element/note-title-element.module.css b/frontend/src/components/layout/app-bar/app-bar-elements/note-title-element/note-title-element.module.css new file mode 100644 index 000000000..08386b63a --- /dev/null +++ b/frontend/src/components/layout/app-bar/app-bar-elements/note-title-element/note-title-element.module.css @@ -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; +} diff --git a/frontend/src/components/layout/app-bar/app-bar-elements/note-title-element/note-title-element.tsx b/frontend/src/components/layout/app-bar/app-bar-elements/note-title-element/note-title-element.tsx new file mode 100644 index 000000000..845e5d937 --- /dev/null +++ b/frontend/src/components/layout/app-bar/app-bar-elements/note-title-element/note-title-element.tsx @@ -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 ( + + + + + + + {noteTitle} + + ) +} diff --git a/frontend/src/components/layout/app-bar/app-bar-elements/user-element.tsx b/frontend/src/components/layout/app-bar/app-bar-elements/user-element.tsx new file mode 100644 index 000000000..1b66280d7 --- /dev/null +++ b/frontend/src/components/layout/app-bar/app-bar-elements/user-element.tsx @@ -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 ? : +} diff --git a/frontend/src/components/layout/app-bar/base-app-bar.tsx b/frontend/src/components/layout/app-bar/base-app-bar.tsx new file mode 100644 index 000000000..9a1db3f5e --- /dev/null +++ b/frontend/src/components/layout/app-bar/base-app-bar.tsx @@ -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 = ({ children }) => { + return ( + + + + + + + + + + + + ) +} diff --git a/frontend/src/components/layout/app-bar/editor-app-bar.spec.tsx b/frontend/src/components/layout/app-bar/editor-app-bar.spec.tsx new file mode 100644 index 000000000..e0b49a004 --- /dev/null +++ b/frontend/src/components/layout/app-bar/editor-app-bar.spec.tsx @@ -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) => ( +
+ first part +
{children}
+ last part +
+ ) +})) +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() + 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() + 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() + expect(view.container).toMatchSnapshot() + }) +}) diff --git a/frontend/src/components/layout/app-bar/editor-app-bar.tsx b/frontend/src/components/layout/app-bar/editor-app-bar.tsx new file mode 100644 index 000000000..8237e1f62 --- /dev/null +++ b/frontend/src/components/layout/app-bar/editor-app-bar.tsx @@ -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 {isSynced ? : } +} diff --git a/frontend/src/pages/intro.tsx b/frontend/src/pages/intro.tsx index ac4a26d54..a8c0957e5 100644 --- a/frontend/src/pages/intro.tsx +++ b/frontend/src/pages/intro.tsx @@ -33,7 +33,6 @@ const IntroPage: NextPage = () => {
-
diff --git a/frontend/src/pages/s/[noteId].tsx b/frontend/src/pages/s/[noteId].tsx index 4d4f3bc00..819505e8c 100644 --- a/frontend/src/pages/s/[noteId].tsx +++ b/frontend/src/pages/s/[noteId].tsx @@ -3,12 +3,12 @@ * * SPDX-License-Identifier: AGPL-3.0-only */ -import { MotdModal } from '../../components/common/motd-modal/motd-modal' import { NoteLoadingBoundary } from '../../components/common/note-loading-boundary/note-loading-boundary' import { DocumentReadOnlyPageContent } from '../../components/document-read-only-page/document-read-only-page-content' -import { AppBar, AppBarMode } from '../../components/editor-page/app-bar/app-bar' import { HeadMetaProperties } from '../../components/editor-page/head-meta-properties/head-meta-properties' import { EditorToRendererCommunicatorContextProvider } from '../../components/editor-page/render-context/editor-to-renderer-communicator-context-provider' +import { MotdModal } from '../../components/global-dialogs/motd-modal/motd-modal' +import { BaseAppBar } from '../../components/layout/app-bar/base-app-bar' import { useApplyDarkModeStyle } from '../../hooks/dark-mode/use-apply-dark-mode-style' import { useSaveDarkModePreferenceToLocalStorage } from '../../hooks/dark-mode/use-save-dark-mode-preference-to-local-storage' import React from 'react' @@ -26,7 +26,7 @@ export const DocumentReadOnlyPage: React.FC = () => {
- +