diff --git a/frontend/src/api/config/index.ts b/frontend/src/api/config/index.ts index a93e5f81f..32df13eef 100644 --- a/frontend/src/api/config/index.ts +++ b/frontend/src/api/config/index.ts @@ -1,10 +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 { GetApiRequestBuilder } from '../common/api-request-builder/get-api-request-builder' -import type { Config } from './types' +import type { FrontendConfig } from './types' /** * Fetches the frontend config from the backend. @@ -12,7 +12,7 @@ import type { Config } from './types' * @return The frontend config. * @throws {Error} when the api request wasn't successful. */ -export const getConfig = async (): Promise => { - const response = await new GetApiRequestBuilder('config').sendRequest() +export const getConfig = async (baseUrl?: string): Promise => { + const response = await new GetApiRequestBuilder('config', baseUrl).sendRequest() return response.asParsedJsonObject() } diff --git a/frontend/src/api/config/types.ts b/frontend/src/api/config/types.ts index a323cf47c..e8f06a6aa 100644 --- a/frontend/src/api/config/types.ts +++ b/frontend/src/api/config/types.ts @@ -1,10 +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 */ -export interface Config { +export interface FrontendConfig { allowAnonymous: boolean allowRegister: boolean authProviders: AuthProvider[] diff --git a/frontend/src/components/application-loader/initializers/fetch-frontend-config.ts b/frontend/src/components/application-loader/initializers/fetch-frontend-config.ts deleted file mode 100644 index ffdc6cf71..000000000 --- a/frontend/src/components/application-loader/initializers/fetch-frontend-config.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ -import { getConfig } from '../../../api/config' -import { setConfig } from '../../../redux/config/methods' - -/** - * Get the {@link Config frontend config} and save it in the global application state. - */ -export const fetchFrontendConfig = async (): Promise => { - const config = await getConfig() - if (!config) { - return Promise.reject(new Error('Config empty!')) - } - setConfig(config) -} diff --git a/frontend/src/components/application-loader/initializers/index.ts b/frontend/src/components/application-loader/initializers/index.ts index f2c7f55a1..95e398875 100644 --- a/frontend/src/components/application-loader/initializers/index.ts +++ b/frontend/src/components/application-loader/initializers/index.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 */ @@ -7,7 +7,6 @@ import { refreshHistoryState } from '../../../redux/history/methods' import { Logger } from '../../../utils/logger' import { isDevMode, isTestMode } from '../../../utils/test-modes' import { fetchAndSetUser } from '../../login-page/auth/utils' -import { fetchFrontendConfig } from './fetch-frontend-config' import { loadDarkMode } from './load-dark-mode' import { setUpI18n } from './setupI18n' @@ -55,10 +54,6 @@ export const createSetUpTaskList = (): InitTask[] => { name: 'Load Translations', task: setUpI18n }, - { - name: 'Load config', - task: fetchFrontendConfig - }, { name: 'Fetch user information', task: fetchUserInformation diff --git a/frontend/src/components/common/base-url/base-url-context-provider.tsx b/frontend/src/components/common/base-url/base-url-context-provider.tsx index a8d9eb228..609e78eb7 100644 --- a/frontend/src/components/common/base-url/base-url-context-provider.tsx +++ b/frontend/src/components/common/base-url/base-url-context-provider.tsx @@ -1,10 +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 React, { createContext, useState } from 'react' import type { PropsWithChildren } from 'react' +import React, { createContext, useState } from 'react' export interface BaseUrls { renderer: string @@ -28,9 +28,8 @@ export const BaseUrlContextProvider: React.FC { const [baseUrlState] = useState(() => baseUrls) - return baseUrlState === undefined ? ( -
HedgeDoc is not configured correctly! Please check the server log.
+ HedgeDoc is not configured correctly! Please check the server log. ) : ( {children} ) diff --git a/frontend/src/components/common/branding/branding.tsx b/frontend/src/components/common/branding/branding.tsx index a4dadb513..32073648f 100644 --- a/frontend/src/components/common/branding/branding.tsx +++ b/frontend/src/components/common/branding/branding.tsx @@ -1,9 +1,9 @@ /* - * 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 { useFrontendConfig } from '../frontend-config-context/use-frontend-config' import { ShowIf } from '../show-if/show-if' import styles from './branding.module.scss' import React, { useMemo } from 'react' @@ -21,7 +21,7 @@ export interface BrandingProps { * @param delimiter If the delimiter between the HedgeDoc logo and the branding should be shown. */ export const Branding: React.FC = ({ inline = false, delimiter = true }) => { - const branding = useApplicationState((state) => state.config.branding) + const branding = useFrontendConfig().branding const showBranding = !!branding.name || !!branding.logo const brandingDom = useMemo(() => { diff --git a/frontend/src/components/common/frontend-config-context/context.ts b/frontend/src/components/common/frontend-config-context/context.ts new file mode 100644 index 000000000..2b1a19a42 --- /dev/null +++ b/frontend/src/components/common/frontend-config-context/context.ts @@ -0,0 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ +import type { FrontendConfig } from '../../../api/config/types' +import { createContext } from 'react' + +export const frontendConfigContext = createContext(undefined) diff --git a/frontend/src/components/common/frontend-config-context/frontend-config-context-provider.tsx b/frontend/src/components/common/frontend-config-context/frontend-config-context-provider.tsx new file mode 100644 index 000000000..09b7de918 --- /dev/null +++ b/frontend/src/components/common/frontend-config-context/frontend-config-context-provider.tsx @@ -0,0 +1,45 @@ +/* + * SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ +import { getConfig } from '../../../api/config' +import type { FrontendConfig } from '../../../api/config/types' +import { useBaseUrl } from '../../../hooks/common/use-base-url' +import { Logger } from '../../../utils/logger' +import { frontendConfigContext } from './context' +import type { PropsWithChildren } from 'react' +import React, { useEffect, useState } from 'react' + +const logger = new Logger('FrontendConfigContextProvider') + +interface FrontendConfigContextProviderProps extends PropsWithChildren { + config?: FrontendConfig +} + +/** + * Provides the given frontend configuration in a context or renders an error message otherwise. + * + * @param config the frontend config to provoide + * @param children the react elements to show if the config is valid + */ +export const FrontendConfigContextProvider: React.FC = ({ config, children }) => { + const [configState, setConfigState] = useState(() => config) + + const baseUrl = useBaseUrl() + + useEffect(() => { + if (config === undefined && configState === undefined) { + logger.debug('Fetching Config client side') + getConfig(baseUrl) + .then((config) => setConfigState(config)) + .catch((error) => logger.error(error)) + } + }, [baseUrl, config, configState]) + + return configState === undefined ? ( + No frontend config received! Please check the server log. + ) : ( + {children} + ) +} diff --git a/frontend/src/components/common/frontend-config-context/use-frontend-config.ts b/frontend/src/components/common/frontend-config-context/use-frontend-config.ts new file mode 100644 index 000000000..ab8862220 --- /dev/null +++ b/frontend/src/components/common/frontend-config-context/use-frontend-config.ts @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ +import type { FrontendConfig } from '../../../api/config/types' +import { frontendConfigContext } from './context' +import { Optional } from '@mrdrogdrog/optional' +import { useContext } from 'react' + +/** + * Retrieves the current frontend config from the next react context. + */ +export const useFrontendConfig = (): FrontendConfig => { + return Optional.ofNullable(useContext(frontendConfigContext)).orElseThrow( + () => new Error('No frontend config context found. Did you forget to use the provider component?') + ) +} diff --git a/frontend/src/components/common/motd-modal/motd-modal.spec.tsx b/frontend/src/components/common/motd-modal/motd-modal.spec.tsx index f137db656..bede7f878 100644 --- a/frontend/src/components/common/motd-modal/motd-modal.spec.tsx +++ b/frontend/src/components/common/motd-modal/motd-modal.spec.tsx @@ -22,7 +22,7 @@ jest.mock('../../../hooks/common/use-base-url') describe('motd modal', () => { beforeAll(async () => { - jest.spyOn(UseBaseUrlModule, 'useBaseUrl').mockImplementation(() => 'https://example.org') + jest.spyOn(UseBaseUrlModule, 'useBaseUrl').mockImplementation(() => new URL('https://example.org')) await mockI18n() }) diff --git a/frontend/src/components/editor-page/document-bar/share/note-url-field.tsx b/frontend/src/components/editor-page/document-bar/share/note-url-field.tsx new file mode 100644 index 000000000..01e6efb04 --- /dev/null +++ b/frontend/src/components/editor-page/document-bar/share/note-url-field.tsx @@ -0,0 +1,36 @@ +/* + * 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 { useBaseUrl } from '../../../../hooks/common/use-base-url' +import { CopyableField } from '../../../common/copyable/copyable-field/copyable-field' +import React, { useMemo } from 'react' + +export enum LinkType { + EDITOR = 'n', + SLIDESHOW = 'p', + DOCUMENT = 's' +} + +export interface LinkFieldProps { + type: LinkType +} + +/** + * Renders a specific URL to the current note. + * @param type defines the URL type. (editor, read only document, slideshow, etc.) + */ +export const NoteUrlField: React.FC = ({ type }) => { + const baseUrl = useBaseUrl() + const noteIdentifier = useApplicationState((state) => state.noteDetails.primaryAddress) + + const url = useMemo(() => { + const url = new URL(baseUrl) + url.pathname += `${type}/${noteIdentifier}` + return url.toString() + }, [baseUrl, noteIdentifier, type]) + + return +} diff --git a/frontend/src/components/editor-page/document-bar/share/share-modal.tsx b/frontend/src/components/editor-page/document-bar/share/share-modal.tsx index 9117b5c41..5912cf482 100644 --- a/frontend/src/components/editor-page/document-bar/share/share-modal.tsx +++ b/frontend/src/components/editor-page/document-bar/share/share-modal.tsx @@ -4,11 +4,10 @@ * SPDX-License-Identifier: AGPL-3.0-only */ import { useApplicationState } from '../../../../hooks/common/use-application-state' -import { useBaseUrl } from '../../../../hooks/common/use-base-url' -import { CopyableField } from '../../../common/copyable/copyable-field/copyable-field' import type { ModalVisibilityProps } from '../../../common/modals/common-modal' import { CommonModal } from '../../../common/modals/common-modal' import { ShowIf } from '../../../common/show-if/show-if' +import { LinkType, NoteUrlField } from './note-url-field' import { NoteType } from '@hedgedoc/commons' import React from 'react' import { Modal } from 'react-bootstrap' @@ -23,21 +22,19 @@ import { Trans, useTranslation } from 'react-i18next' export const ShareModal: React.FC = ({ show, onHide }) => { useTranslation() const noteFrontmatter = useApplicationState((state) => state.noteDetails.frontmatter) - const baseUrl = useBaseUrl() - const noteIdentifier = useApplicationState((state) => state.noteDetails.primaryAddress) return ( - + - + - + diff --git a/frontend/src/components/editor-page/editor-pane/hooks/yjs/use-websocket-url.ts b/frontend/src/components/editor-page/editor-pane/hooks/yjs/use-websocket-url.ts index 1eec7c444..3083a85c2 100644 --- a/frontend/src/components/editor-page/editor-pane/hooks/yjs/use-websocket-url.ts +++ b/frontend/src/components/editor-page/editor-pane/hooks/yjs/use-websocket-url.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 */ @@ -22,7 +22,7 @@ export const useWebsocketUrl = (): URL | undefined => { return LOCAL_FALLBACK_URL } try { - const backendBaseUrlParsed = new URL(baseUrl, window.location.toString()) + const backendBaseUrlParsed = new URL(baseUrl) backendBaseUrlParsed.protocol = backendBaseUrlParsed.protocol === 'https:' ? 'wss:' : 'ws:' backendBaseUrlParsed.pathname += 'realtime' return backendBaseUrlParsed.toString() diff --git a/frontend/src/components/editor-page/editor-pane/max-length-warning/max-length-warning-modal.tsx b/frontend/src/components/editor-page/editor-pane/max-length-warning/max-length-warning-modal.tsx index a04b1f74c..1c889561d 100644 --- a/frontend/src/components/editor-page/editor-pane/max-length-warning/max-length-warning-modal.tsx +++ b/frontend/src/components/editor-page/editor-pane/max-length-warning/max-length-warning-modal.tsx @@ -3,8 +3,8 @@ * * SPDX-License-Identifier: AGPL-3.0-only */ -import { useApplicationState } from '../../../../hooks/common/use-application-state' import { cypressId } from '../../../../utils/cypress-attribute' +import { useFrontendConfig } from '../../../common/frontend-config-context/use-frontend-config' import type { ModalVisibilityProps } from '../../../common/modals/common-modal' import { CommonModal } from '../../../common/modals/common-modal' import React from 'react' @@ -19,7 +19,7 @@ import { Trans, useTranslation } from 'react-i18next' */ export const MaxLengthWarningModal: React.FC = ({ show, onHide }) => { useTranslation() - const maxDocumentLength = useApplicationState((state) => state.config.maxDocumentLength) + const maxDocumentLength = useFrontendConfig().maxDocumentLength return ( { const [modalVisibility, showModal, closeModal] = useBooleanState() const maxLengthWarningAlreadyShown = useRef(false) - const maxDocumentLength = useApplicationState((state) => state.config.maxDocumentLength) + const maxDocumentLength = useFrontendConfig().maxDocumentLength const markdownContent = useNoteMarkdownContent() useEffect(() => { diff --git a/frontend/src/components/editor-page/editor-pane/status-bar/remaining-characters-info.tsx b/frontend/src/components/editor-page/editor-pane/status-bar/remaining-characters-info.tsx index e34f55f4f..7aff674a5 100644 --- a/frontend/src/components/editor-page/editor-pane/status-bar/remaining-characters-info.tsx +++ b/frontend/src/components/editor-page/editor-pane/status-bar/remaining-characters-info.tsx @@ -1,10 +1,11 @@ /* - * 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 { cypressId } from '../../../../utils/cypress-attribute' +import { useFrontendConfig } from '../../../common/frontend-config-context/use-frontend-config' import React, { useMemo } from 'react' import { Trans, useTranslation } from 'react-i18next' @@ -14,7 +15,7 @@ import { Trans, useTranslation } from 'react-i18next' export const RemainingCharactersInfo: React.FC = () => { const { t } = useTranslation() - const maxDocumentLength = useApplicationState((state) => state.config.maxDocumentLength) + const maxDocumentLength = useFrontendConfig().maxDocumentLength const contentLength = useApplicationState((state) => state.noteDetails.markdownContent.plain.length) const remainingCharacters = useMemo(() => maxDocumentLength - contentLength, [contentLength, maxDocumentLength]) diff --git a/frontend/src/components/landing-layout/footer/powered-by-links.tsx b/frontend/src/components/landing-layout/footer/powered-by-links.tsx index 2f72545b5..8fd436884 100644 --- a/frontend/src/components/landing-layout/footer/powered-by-links.tsx +++ b/frontend/src/components/landing-layout/footer/powered-by-links.tsx @@ -1,15 +1,15 @@ /* - * 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 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 } from 'react' +import React, { Fragment, useMemo } from 'react' import { Trans, useTranslation } from 'react-i18next' /** @@ -18,8 +18,11 @@ import { Trans, useTranslation } from 'react-i18next' export const PoweredByLinks: React.FC = () => { useTranslation() - const specialUrls: [string, string][] = useApplicationState((state) => - Object.entries(state.config.specialUrls).map(([i18nkey, url]) => [i18nkey, String(url)]) + const rawSpecialUrls = useFrontendConfig().specialUrls + + const specialUrls = useMemo( + () => Object.entries(rawSpecialUrls).map(([i18nkey, url]) => [i18nkey, String(url)]), + [rawSpecialUrls] ) return ( diff --git a/frontend/src/components/landing-layout/footer/version-info/version-info-modal.tsx b/frontend/src/components/landing-layout/footer/version-info/version-info-modal.tsx index ebcd2e79c..de824c53f 100644 --- a/frontend/src/components/landing-layout/footer/version-info/version-info-modal.tsx +++ b/frontend/src/components/landing-layout/footer/version-info/version-info-modal.tsx @@ -4,10 +4,10 @@ * SPDX-License-Identifier: AGPL-3.0-only */ import type { BackendVersion } from '../../../../api/config/types' -import { useApplicationState } from '../../../../hooks/common/use-application-state' import links from '../../../../links.json' import { cypressId } from '../../../../utils/cypress-attribute' import { CopyableField } from '../../../common/copyable/copyable-field/copyable-field' +import { useFrontendConfig } from '../../../common/frontend-config-context/use-frontend-config' import { TranslatedExternalLink } from '../../../common/links/translated-external-link' import type { CommonModalProps } from '../../../common/modals/common-modal' import { CommonModal } from '../../../common/modals/common-modal' @@ -22,7 +22,7 @@ import { Modal } from 'react-bootstrap' * @param show If the modal should be shown. */ export const VersionInfoModal: React.FC = ({ onHide, show }) => { - const serverVersion: BackendVersion = useApplicationState((state) => state.config.version) + const serverVersion: BackendVersion = useFrontendConfig().version const backendVersion = useMemo(() => { const version = `${serverVersion.major}.${serverVersion.minor}.${serverVersion.patch}` diff --git a/frontend/src/components/landing-layout/navigation/sign-in-button.tsx b/frontend/src/components/landing-layout/navigation/sign-in-button.tsx index 1f943ed36..4474c94a0 100644 --- a/frontend/src/components/landing-layout/navigation/sign-in-button.tsx +++ b/frontend/src/components/landing-layout/navigation/sign-in-button.tsx @@ -1,10 +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 { cypressId } from '../../../utils/cypress-attribute' +import { useFrontendConfig } from '../../common/frontend-config-context/use-frontend-config' import { ShowIf } from '../../common/show-if/show-if' import { filterOneClickProviders } from '../../login-page/auth/utils' import { getOneClickProviderMetadata } from '../../login-page/auth/utils/get-one-click-provider-metadata' @@ -25,7 +25,7 @@ export type SignInButtonProps = Omit */ export const SignInButton: React.FC = ({ variant, ...props }) => { const { t } = useTranslation() - const authProviders = useApplicationState((state) => state.config.authProviders) + const authProviders = useFrontendConfig().authProviders const loginLink = useMemo(() => { const oneClickProviders = authProviders.filter(filterOneClickProviders) diff --git a/frontend/src/components/login-page/auth/via-local.tsx b/frontend/src/components/login-page/auth/via-local.tsx index 3e74b6549..0cc8c8dfc 100644 --- a/frontend/src/components/login-page/auth/via-local.tsx +++ b/frontend/src/components/login-page/auth/via-local.tsx @@ -1,12 +1,12 @@ /* - * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) + * SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file) * * SPDX-License-Identifier: AGPL-3.0-only */ import { doLocalLogin } from '../../../api/auth/local' import { ErrorToI18nKeyMapper } from '../../../api/common/error-to-i18n-key-mapper' -import { useApplicationState } from '../../../hooks/common/use-application-state' import { useOnInputChange } from '../../../hooks/common/use-on-input-change' +import { useFrontendConfig } from '../../common/frontend-config-context/use-frontend-config' import { ShowIf } from '../../common/show-if/show-if' import { PasswordField } from './fields/password-field' import { UsernameField } from './fields/username-field' @@ -25,7 +25,7 @@ export const ViaLocal: React.FC = () => { const [username, setUsername] = useState('') const [password, setPassword] = useState('') const [error, setError] = useState() - const allowRegister = useApplicationState((state) => state.config.allowRegister) + const allowRegister = useFrontendConfig().allowRegister const onLoginSubmit = useCallback( (event: FormEvent) => { diff --git a/frontend/src/components/markdown-renderer/extensions/image/proxy-image-frame.tsx b/frontend/src/components/markdown-renderer/extensions/image/proxy-image-frame.tsx index 302132a45..37b6ed1bd 100644 --- a/frontend/src/components/markdown-renderer/extensions/image/proxy-image-frame.tsx +++ b/frontend/src/components/markdown-renderer/extensions/image/proxy-image-frame.tsx @@ -1,11 +1,11 @@ /* - * 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 { getProxiedUrl } from '../../../../api/media' -import { useApplicationState } from '../../../../hooks/common/use-application-state' import { Logger } from '../../../../utils/logger' +import { useFrontendConfig } from '../../../common/frontend-config-context/use-frontend-config' import React, { useEffect, useState } from 'react' const log = new Logger('ProxyImageFrame') @@ -20,7 +20,7 @@ const log = new Logger('ProxyImageFrame') */ export const ProxyImageFrame: React.FC> = ({ src, title, alt, ...props }) => { const [imageUrl, setImageUrl] = useState('') - const imageProxyEnabled = useApplicationState((state) => state.config.useImageProxy) + const imageProxyEnabled = useFrontendConfig().useImageProxy useEffect(() => { if (!imageProxyEnabled || !src) { diff --git a/frontend/src/components/markdown-renderer/extensions/table-of-contents/table-of-contents-app-extension.ts b/frontend/src/components/markdown-renderer/extensions/table-of-contents/table-of-contents-app-extension.ts index b4a33c51b..197786dc5 100644 --- a/frontend/src/components/markdown-renderer/extensions/table-of-contents/table-of-contents-app-extension.ts +++ b/frontend/src/components/markdown-renderer/extensions/table-of-contents/table-of-contents-app-extension.ts @@ -3,18 +3,18 @@ * * SPDX-License-Identifier: AGPL-3.0-only */ +import type { MarkdownRendererExtensionOptions } from '../../../../extensions/base/app-extension' import { AppExtension } from '../../../../extensions/base/app-extension' import type { CheatsheetExtension } from '../../../editor-page/cheatsheet/cheatsheet-extension' import { basicCompletion } from '../../../editor-page/editor-pane/autocompletions/basic-completion' import type { MarkdownRendererExtension } from '../base/markdown-renderer-extension' import { TableOfContentsMarkdownExtension } from './table-of-contents-markdown-extension' import type { CompletionSource } from '@codemirror/autocomplete' -import type EventEmitter2 from 'eventemitter2' import { t } from 'i18next' export class TableOfContentsAppExtension extends AppExtension { - buildMarkdownRendererExtensions(eventEmitter?: EventEmitter2): MarkdownRendererExtension[] { - return [new TableOfContentsMarkdownExtension(eventEmitter)] + buildMarkdownRendererExtensions(options: MarkdownRendererExtensionOptions): MarkdownRendererExtension[] { + return [new TableOfContentsMarkdownExtension(options.eventEmitter)] } buildCheatsheetExtensions(): CheatsheetExtension[] { diff --git a/frontend/src/components/markdown-renderer/hooks/use-markdown-extensions.ts b/frontend/src/components/markdown-renderer/hooks/use-markdown-extensions.ts index 5008353f4..fdb98dd42 100644 --- a/frontend/src/components/markdown-renderer/hooks/use-markdown-extensions.ts +++ b/frontend/src/components/markdown-renderer/hooks/use-markdown-extensions.ts @@ -4,6 +4,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ import { optionalAppExtensions } from '../../../extensions/extra-integrations/optional-app-extensions' +import { useFrontendConfig } from '../../common/frontend-config-context/use-frontend-config' import type { MarkdownRendererExtension } from '../extensions/base/markdown-renderer-extension' import { DebuggerMarkdownExtension } from '../extensions/debugger-markdown-extension' import { ProxyImageMarkdownExtension } from '../extensions/image/proxy-image-markdown-extension' @@ -25,9 +26,15 @@ export const useMarkdownExtensions = ( additionalExtensions: MarkdownRendererExtension[] ): MarkdownRendererExtension[] => { const extensionEventEmitter = useExtensionEventEmitter() + const frontendConfig = useFrontendConfig() return useMemo(() => { return [ - ...optionalAppExtensions.flatMap((extension) => extension.buildMarkdownRendererExtensions(extensionEventEmitter)), + ...optionalAppExtensions.flatMap((extension) => + extension.buildMarkdownRendererExtensions({ + frontendConfig: frontendConfig, + eventEmitter: extensionEventEmitter + }) + ), ...additionalExtensions, new UploadIndicatingImageFrameMarkdownExtension(), new LinkAdjustmentMarkdownExtension(baseUrl), @@ -35,5 +42,5 @@ export const useMarkdownExtensions = ( new DebuggerMarkdownExtension(), new ProxyImageMarkdownExtension() ] - }, [additionalExtensions, baseUrl, extensionEventEmitter]) + }, [additionalExtensions, baseUrl, extensionEventEmitter, frontendConfig]) } diff --git a/frontend/src/components/register-page/register-infos.tsx b/frontend/src/components/register-page/register-infos.tsx index 6f11cb1f2..4b645321f 100644 --- a/frontend/src/components/register-page/register-infos.tsx +++ b/frontend/src/components/register-page/register-infos.tsx @@ -3,7 +3,7 @@ * * SPDX-License-Identifier: AGPL-3.0-only */ -import { useApplicationState } from '../../hooks/common/use-application-state' +import { useFrontendConfig } from '../common/frontend-config-context/use-frontend-config' import { TranslatedExternalLink } from '../common/links/translated-external-link' import { ShowIf } from '../common/show-if/show-if' import React from 'react' @@ -14,7 +14,7 @@ import { Trans, useTranslation } from 'react-i18next' */ export const RegisterInfos: React.FC = () => { useTranslation() - const specialUrls = useApplicationState((state) => state.config.specialUrls) + const specialUrls = useFrontendConfig().specialUrls return ( diff --git a/frontend/src/extensions/base/app-extension.ts b/frontend/src/extensions/base/app-extension.ts index 93ec1ff9e..0d1952279 100644 --- a/frontend/src/extensions/base/app-extension.ts +++ b/frontend/src/extensions/base/app-extension.ts @@ -1,8 +1,9 @@ /* - * 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 type { FrontendConfig } from '../../api/config/types' import type { CheatsheetExtension } from '../../components/editor-page/cheatsheet/cheatsheet-extension' import type { Linter } from '../../components/editor-page/editor-pane/linter/linter' import type { MarkdownRendererExtension } from '../../components/markdown-renderer/extensions/base/markdown-renderer-extension' @@ -11,9 +12,14 @@ import type { EventEmitter2 } from 'eventemitter2' import type React from 'react' import { Fragment } from 'react' +export interface MarkdownRendererExtensionOptions { + frontendConfig: FrontendConfig + eventEmitter?: EventEmitter2 +} + export abstract class AppExtension { // eslint-disable-next-line @typescript-eslint/no-unused-vars - public buildMarkdownRendererExtensions(eventEmitter?: EventEmitter2): MarkdownRendererExtension[] { + public buildMarkdownRendererExtensions(options: MarkdownRendererExtensionOptions): MarkdownRendererExtension[] { return [] } diff --git a/frontend/src/extensions/extra-integrations/plantuml/plantuml-app-extension.ts b/frontend/src/extensions/extra-integrations/plantuml/plantuml-app-extension.ts index b0ee8aa21..8130f0b74 100644 --- a/frontend/src/extensions/extra-integrations/plantuml/plantuml-app-extension.ts +++ b/frontend/src/extensions/extra-integrations/plantuml/plantuml-app-extension.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 */ @@ -9,6 +9,7 @@ import { codeFenceRegex } from '../../../components/editor-page/editor-pane/autocompletions/basic-completion' import type { MarkdownRendererExtension } from '../../../components/markdown-renderer/extensions/base/markdown-renderer-extension' +import type { MarkdownRendererExtensionOptions } from '../../base/app-extension' import { AppExtension } from '../../base/app-extension' import { PlantumlMarkdownExtension } from './plantuml-markdown-extension' import type { CompletionSource } from '@codemirror/autocomplete' @@ -19,8 +20,8 @@ import type { CompletionSource } from '@codemirror/autocomplete' * @see https://plantuml.com */ export class PlantumlAppExtension extends AppExtension { - buildMarkdownRendererExtensions(): MarkdownRendererExtension[] { - return [new PlantumlMarkdownExtension()] + buildMarkdownRendererExtensions(options: MarkdownRendererExtensionOptions): MarkdownRendererExtension[] { + return [new PlantumlMarkdownExtension(options.frontendConfig.plantumlServer)] } buildCheatsheetExtensions(): CheatsheetExtension[] { diff --git a/frontend/src/extensions/extra-integrations/plantuml/plantuml-markdown-extension.spec.tsx b/frontend/src/extensions/extra-integrations/plantuml/plantuml-markdown-extension.spec.tsx index d8051f3d3..71f6370bd 100644 --- a/frontend/src/extensions/extra-integrations/plantuml/plantuml-markdown-extension.spec.tsx +++ b/frontend/src/extensions/extra-integrations/plantuml/plantuml-markdown-extension.spec.tsx @@ -5,30 +5,17 @@ */ import { mockI18n } from '../../../components/markdown-renderer/test-utils/mock-i18n' import { TestMarkdownRenderer } from '../../../components/markdown-renderer/test-utils/test-markdown-renderer' -import * as reduxModule from '../../../redux' -import type { ApplicationState } from '../../../redux/application-state' import { PlantumlMarkdownExtension } from './plantuml-markdown-extension' import { render } from '@testing-library/react' import React from 'react' -import { Mock } from 'ts-mockery' - -jest.mock('../../../redux') describe('PlantUML markdown extensions', () => { beforeAll(() => mockI18n()) it('renders a plantuml codeblock', () => { - jest.spyOn(reduxModule, 'getGlobalState').mockReturnValue( - Mock.of({ - config: { - plantumlServer: 'https://example.org' - } - }) - ) - const view = render( ) @@ -36,17 +23,9 @@ describe('PlantUML markdown extensions', () => { }) it('renders an error if no server is defined', () => { - jest.spyOn(reduxModule, 'getGlobalState').mockReturnValue( - Mock.of({ - config: { - plantumlServer: undefined - } - }) - ) - const view = render( ) diff --git a/frontend/src/extensions/extra-integrations/plantuml/plantuml-markdown-extension.ts b/frontend/src/extensions/extra-integrations/plantuml/plantuml-markdown-extension.ts index 0d3c61721..40bd25dd4 100644 --- a/frontend/src/extensions/extra-integrations/plantuml/plantuml-markdown-extension.ts +++ b/frontend/src/extensions/extra-integrations/plantuml/plantuml-markdown-extension.ts @@ -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 { MarkdownRendererExtension } from '../../../components/markdown-renderer/extensions/base/markdown-renderer-extension' import type { ComponentReplacer } from '../../../components/markdown-renderer/replace-components/component-replacer' -import { getGlobalState } from '../../../redux' import { PlantumlNotConfiguredComponentReplacer } from './plantuml-not-configured-component-replacer' import { Optional } from '@mrdrogdrog/optional' import type MarkdownIt from 'markdown-it' @@ -20,6 +19,10 @@ import type Token from 'markdown-it/lib/token' * @see https://plantuml.com */ export class PlantumlMarkdownExtension extends MarkdownRendererExtension { + constructor(private plantumlServerUrl: string | undefined) { + super() + } + private plantumlError(markdownIt: MarkdownIt): void { const defaultRenderer: Renderer.RenderRule = markdownIt.renderer.rules.fence || (() => '') markdownIt.renderer.rules.fence = (tokens: Token[], idx: number, options: Options, env, slf: Renderer) => { @@ -30,7 +33,7 @@ export class PlantumlMarkdownExtension extends MarkdownRendererExtension { } public configureMarkdownIt(markdownIt: MarkdownIt): void { - Optional.ofNullable(getGlobalState().config.plantumlServer) + Optional.ofNullable(this.plantumlServerUrl) .map((plantumlServer) => plantuml(markdownIt, { openMarker: '```plantuml', diff --git a/frontend/src/extensions/extra-integrations/task-list/task-list-checkbox-app-extension.ts b/frontend/src/extensions/extra-integrations/task-list/task-list-checkbox-app-extension.ts index f46227dd8..ea9767612 100644 --- a/frontend/src/extensions/extra-integrations/task-list/task-list-checkbox-app-extension.ts +++ b/frontend/src/extensions/extra-integrations/task-list/task-list-checkbox-app-extension.ts @@ -1,14 +1,14 @@ /* - * 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 type { CheatsheetExtension } from '../../../components/editor-page/cheatsheet/cheatsheet-extension' +import type { MarkdownRendererExtensionOptions } from '../../base/app-extension' import { AppExtension } from '../../base/app-extension' import { SetCheckboxInCheatsheet } from './set-checkbox-in-cheatsheet' import { SetCheckboxInEditor } from './set-checkbox-in-editor' import { TaskListMarkdownExtension } from './task-list-markdown-extension' -import type { EventEmitter2 } from 'eventemitter2' import type React from 'react' /** @@ -17,8 +17,8 @@ import type React from 'react' export class TaskListCheckboxAppExtension extends AppExtension { public static readonly EVENT_NAME = 'TaskListCheckbox' - buildMarkdownRendererExtensions(eventEmitter: EventEmitter2): TaskListMarkdownExtension[] { - return [new TaskListMarkdownExtension(eventEmitter)] + buildMarkdownRendererExtensions(options: MarkdownRendererExtensionOptions): TaskListMarkdownExtension[] { + return [new TaskListMarkdownExtension(options.eventEmitter)] } buildEditorExtensionComponent(): React.FC { diff --git a/frontend/src/hooks/common/use-app-title.ts b/frontend/src/hooks/common/use-app-title.ts index c7b443eae..26fdf11e1 100644 --- a/frontend/src/hooks/common/use-app-title.ts +++ b/frontend/src/hooks/common/use-app-title.ts @@ -1,9 +1,9 @@ /* - * 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 './use-application-state' +import { useFrontendConfig } from '../../components/common/frontend-config-context/use-frontend-config' import { useMemo } from 'react' /** @@ -12,7 +12,7 @@ import { useMemo } from 'react' * @return The app title with branding. */ export const useAppTitle = (): string => { - const brandingName = useApplicationState((state) => state.config.branding.name) + const brandingName = useFrontendConfig().branding.name return useMemo(() => { return 'HedgeDoc' + (brandingName ? ` @ ${brandingName}` : '') diff --git a/frontend/src/hooks/common/use-trimmed-note-markdown-content-without-frontmatter.ts b/frontend/src/hooks/common/use-trimmed-note-markdown-content-without-frontmatter.ts index 1215186a7..b38566126 100644 --- a/frontend/src/hooks/common/use-trimmed-note-markdown-content-without-frontmatter.ts +++ b/frontend/src/hooks/common/use-trimmed-note-markdown-content-without-frontmatter.ts @@ -1,8 +1,9 @@ /* - * 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 { useFrontendConfig } from '../../components/common/frontend-config-context/use-frontend-config' import { useApplicationState } from './use-application-state' import { useMemo } from 'react' @@ -12,7 +13,7 @@ import { useMemo } from 'react' * @return The array of markdown content lines */ export const useTrimmedNoteMarkdownContentWithoutFrontmatter = (): string[] => { - const maxLength = useApplicationState((state) => state.config.maxDocumentLength) + const maxLength = useFrontendConfig().maxDocumentLength const markdownContent = useApplicationState((state) => ({ lines: state.noteDetails.markdownContent.lines, content: state.noteDetails.markdownContent.plain diff --git a/frontend/src/pages/_app.tsx b/frontend/src/pages/_app.tsx index 470f9feb5..d5a3e208f 100644 --- a/frontend/src/pages/_app.tsx +++ b/frontend/src/pages/_app.tsx @@ -5,9 +5,11 @@ */ import '../../global-styles/dark.scss' import '../../global-styles/index.scss' +import type { FrontendConfig } from '../api/config/types' import { ApplicationLoader } from '../components/application-loader/application-loader' -import { BaseUrlContextProvider } from '../components/common/base-url/base-url-context-provider' import type { BaseUrls } from '../components/common/base-url/base-url-context-provider' +import { BaseUrlContextProvider } from '../components/common/base-url/base-url-context-provider' +import { FrontendConfigContextProvider } from '../components/common/frontend-config-context/frontend-config-context-provider' import { ErrorBoundary } from '../components/error-boundary/error-boundary' import { BaseHead } from '../components/layout/base-head' import { UiNotificationBoundary } from '../components/notifications/ui-notification-boundary' @@ -16,6 +18,8 @@ import { BaseUrlFromEnvExtractor } from '../utils/base-url-from-env-extractor' import { configureLuxon } from '../utils/configure-luxon' import { determineCurrentOrigin } from '../utils/determine-current-origin' import { ExpectedOriginBoundary } from '../utils/expected-origin-boundary' +import { FrontendConfigFetcher } from '../utils/frontend-config-fetcher' +import { isTestMode } from '../utils/test-modes' import type { AppContext, AppInitialProps, AppProps } from 'next/app' import React from 'react' @@ -23,6 +27,7 @@ configureLuxon() interface AppPageProps { baseUrls: BaseUrls | undefined + frontendConfig: FrontendConfig | undefined currentOrigin: string | undefined } @@ -33,31 +38,36 @@ interface AppPageProps { function HedgeDocApp({ Component, pageProps }: AppProps) { return ( - - - - - - - - - - - - + + + + + + + + + + + + + + ) } const baseUrlFromEnvExtractor = new BaseUrlFromEnvExtractor() +const frontendConfigFetcher = new FrontendConfigFetcher() -HedgeDocApp.getInitialProps = ({ ctx }: AppContext): AppInitialProps => { +HedgeDocApp.getInitialProps = async ({ ctx }: AppContext): Promise> => { const baseUrls = baseUrlFromEnvExtractor.extractBaseUrls().orElse(undefined) + const frontendConfig = isTestMode ? undefined : await frontendConfigFetcher.fetch(baseUrls) //some tests mock the frontend config. Therefore it needs to be fetched in the browser. const currentOrigin = determineCurrentOrigin(ctx) return { pageProps: { baseUrls, + frontendConfig, currentOrigin } } diff --git a/frontend/src/pages/api/private/config.ts b/frontend/src/pages/api/private/config.ts index c41ab8cc6..a1e0d4b57 100644 --- a/frontend/src/pages/api/private/config.ts +++ b/frontend/src/pages/api/private/config.ts @@ -1,15 +1,15 @@ /* - * 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 type { Config } from '../../../api/config/types' +import type { FrontendConfig } from '../../../api/config/types' import { AuthProviderType } from '../../../api/config/types' import { HttpMethod, respondToMatchingRequest } from '../../../handler-utils/respond-to-matching-request' import type { NextApiRequest, NextApiResponse } from 'next' const handler = (req: NextApiRequest, res: NextApiResponse) => { - respondToMatchingRequest(HttpMethod.GET, req, res, { + respondToMatchingRequest(HttpMethod.GET, req, res, { allowAnonymous: true, allowRegister: true, authProviders: [ diff --git a/frontend/src/pages/login.tsx b/frontend/src/pages/login.tsx index 3539b3170..1f035f4c3 100644 --- a/frontend/src/pages/login.tsx +++ b/frontend/src/pages/login.tsx @@ -1,10 +1,11 @@ /* - SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) - - SPDX-License-Identifier: AGPL-3.0-only + * SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only */ import type { AuthProviderWithCustomName } from '../api/config/types' import { AuthProviderType } from '../api/config/types' +import { useFrontendConfig } from '../components/common/frontend-config-context/use-frontend-config' import { RedirectBack } from '../components/common/redirect-back' import { ShowIf } from '../components/common/show-if/show-if' import { LandingLayout } from '../components/landing-layout/landing-layout' @@ -23,7 +24,7 @@ import { Trans, useTranslation } from 'react-i18next' */ export const LoginPage: React.FC = () => { useTranslation() - const authProviders = useApplicationState((state) => state.config.authProviders) + const authProviders = useFrontendConfig().authProviders const userLoggedIn = useApplicationState((state) => !!state.user) const ldapProviders = useMemo(() => { diff --git a/frontend/src/pages/register.tsx b/frontend/src/pages/register.tsx index bb999d893..89e1d8538 100644 --- a/frontend/src/pages/register.tsx +++ b/frontend/src/pages/register.tsx @@ -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 */ @@ -9,6 +9,7 @@ import { DisplayNameField } from '../components/common/fields/display-name-field import { NewPasswordField } from '../components/common/fields/new-password-field' import { PasswordAgainField } from '../components/common/fields/password-again-field' import { UsernameField } from '../components/common/fields/username-field' +import { useFrontendConfig } from '../components/common/frontend-config-context/use-frontend-config' import { Redirect } from '../components/common/redirect' import { LandingLayout } from '../components/landing-layout/landing-layout' import { fetchAndSetUser } from '../components/login-page/auth/utils' @@ -30,7 +31,7 @@ import { Trans, useTranslation } from 'react-i18next' export const RegisterPage: NextPage = () => { useTranslation() const router = useRouter() - const allowRegister = useApplicationState((state) => state.config.allowRegister) + const allowRegister = useFrontendConfig().allowRegister const userExists = useApplicationState((state) => !!state.user) const [username, setUsername] = useState('') diff --git a/frontend/src/redux/application-state.d.ts b/frontend/src/redux/application-state.d.ts index 3d20406ac..1861375a5 100644 --- a/frontend/src/redux/application-state.d.ts +++ b/frontend/src/redux/application-state.d.ts @@ -3,7 +3,6 @@ * * SPDX-License-Identifier: AGPL-3.0-only */ -import type { Config } from '../api/config/types' import type { HistoryEntryWithOrigin } from '../api/history/types' import type { DarkModeConfig } from './dark-mode/types' import type { EditorConfig } from './editor/types' @@ -14,7 +13,6 @@ import type { OptionalUserState } from './user/types' export interface ApplicationState { user: OptionalUserState - config: Config history: HistoryEntryWithOrigin[] editorConfig: EditorConfig darkMode: DarkModeConfig diff --git a/frontend/src/redux/config/methods.ts b/frontend/src/redux/config/methods.ts deleted file mode 100644 index d62f585d8..000000000 --- a/frontend/src/redux/config/methods.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ -import { store } from '..' -import type { Config } from '../../api/config/types' -import type { SetConfigAction } from './types' -import { ConfigActionType } from './types' - -export const setConfig = (state: Config): void => { - store.dispatch({ - type: ConfigActionType.SET_CONFIG, - state: state - } as SetConfigAction) -} diff --git a/frontend/src/redux/config/reducers.ts b/frontend/src/redux/config/reducers.ts deleted file mode 100644 index 31e9fd1e8..000000000 --- a/frontend/src/redux/config/reducers.ts +++ /dev/null @@ -1,41 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ -import type { Config } from '../../api/config/types' -import type { ConfigActions } from './types' -import { ConfigActionType } from './types' -import type { Reducer } from 'redux' - -export const initialState: Config = { - allowAnonymous: true, - allowRegister: true, - authProviders: [], - branding: { - name: '', - logo: '' - }, - useImageProxy: false, - specialUrls: { - privacy: undefined, - termsOfUse: undefined, - imprint: undefined - }, - version: { - major: 0, - minor: 0, - patch: 0 - }, - plantumlServer: undefined, - maxDocumentLength: 0 -} - -export const ConfigReducer: Reducer = (state: Config = initialState, action: ConfigActions) => { - switch (action.type) { - case ConfigActionType.SET_CONFIG: - return action.state - default: - return state - } -} diff --git a/frontend/src/redux/config/types.ts b/frontend/src/redux/config/types.ts deleted file mode 100644 index 5e9c728a9..000000000 --- a/frontend/src/redux/config/types.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ -import type { Config } from '../../api/config/types' -import type { Action } from 'redux' - -export enum ConfigActionType { - SET_CONFIG = 'config/set' -} - -export type ConfigActions = SetConfigAction - -export interface SetConfigAction extends Action { - type: ConfigActionType.SET_CONFIG - state: Config -} diff --git a/frontend/src/redux/reducers.ts b/frontend/src/redux/reducers.ts index 9c55ec892..38a00e5c0 100644 --- a/frontend/src/redux/reducers.ts +++ b/frontend/src/redux/reducers.ts @@ -1,10 +1,9 @@ /* - * 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 type { ApplicationState } from './application-state' -import { ConfigReducer } from './config/reducers' import { DarkModeConfigReducer } from './dark-mode/reducers' import { EditorConfigReducer } from './editor/reducers' import { HistoryReducer } from './history/reducers' @@ -17,7 +16,6 @@ import { combineReducers } from 'redux' export const allReducers: Reducer = combineReducers({ user: UserReducer, - config: ConfigReducer, history: HistoryReducer, editorConfig: EditorConfigReducer, darkMode: DarkModeConfigReducer, diff --git a/frontend/src/utils/frontend-config-fetcher.ts b/frontend/src/utils/frontend-config-fetcher.ts new file mode 100644 index 000000000..2dccf6497 --- /dev/null +++ b/frontend/src/utils/frontend-config-fetcher.ts @@ -0,0 +1,35 @@ +/* + * SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ +import { getConfig } from '../api/config' +import type { FrontendConfig } from '../api/config/types' +import type { BaseUrls } from '../components/common/base-url/base-url-context-provider' +import { Logger } from './logger' + +/** + * Fetches and caches the {@link FrontendConfig frontend config} from the backend. + */ +export class FrontendConfigFetcher { + private readonly logger = new Logger('Frontend config fetcher') + + private frontendConfig: FrontendConfig | undefined = undefined + + public async fetch(baseUrls: BaseUrls | undefined): Promise { + if (!this.frontendConfig) { + if (baseUrls === undefined) { + return undefined + } + const baseUrl = baseUrls.editor.toString() + try { + this.frontendConfig = await getConfig(baseUrl) + } catch (error) { + this.logger.error(`Couldn't fetch frontend configuration from ${baseUrl}`, error) + return undefined + } + this.logger.info(`Fetched frontend config from ${baseUrl}`) + } + return this.frontendConfig + } +}