mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2024-11-25 03:06:31 -05:00
feat: fetch frontend config in server side rendering
Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
parent
312d1adf6f
commit
24f1b2a361
41 changed files with 270 additions and 220 deletions
|
@ -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<Config> => {
|
||||
const response = await new GetApiRequestBuilder<Config>('config').sendRequest()
|
||||
export const getConfig = async (baseUrl?: string): Promise<FrontendConfig> => {
|
||||
const response = await new GetApiRequestBuilder<FrontendConfig>('config', baseUrl).sendRequest()
|
||||
return response.asParsedJsonObject()
|
||||
}
|
||||
|
|
|
@ -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[]
|
||||
|
|
|
@ -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<void> => {
|
||||
const config = await getConfig()
|
||||
if (!config) {
|
||||
return Promise.reject(new Error('Config empty!'))
|
||||
}
|
||||
setConfig(config)
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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<PropsWithChildren<BaseUrlContextPr
|
|||
children
|
||||
}) => {
|
||||
const [baseUrlState] = useState<undefined | BaseUrls>(() => baseUrls)
|
||||
|
||||
return baseUrlState === undefined ? (
|
||||
<div className={'text-white'}>HedgeDoc is not configured correctly! Please check the server log.</div>
|
||||
<span className={'text-white bg-dark'}>HedgeDoc is not configured correctly! Please check the server log.</span>
|
||||
) : (
|
||||
<baseUrlContext.Provider value={baseUrlState}>{children}</baseUrlContext.Provider>
|
||||
)
|
||||
|
|
|
@ -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<BrandingProps> = ({ inline = false, delimiter = true }) => {
|
||||
const branding = useApplicationState((state) => state.config.branding)
|
||||
const branding = useFrontendConfig().branding
|
||||
const showBranding = !!branding.name || !!branding.logo
|
||||
|
||||
const brandingDom = useMemo(() => {
|
||||
|
|
|
@ -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<FrontendConfig | undefined>(undefined)
|
|
@ -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<FrontendConfigContextProviderProps> = ({ config, children }) => {
|
||||
const [configState, setConfigState] = useState<undefined | FrontendConfig>(() => 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 ? (
|
||||
<span className={'text-white bg-dark'}>No frontend config received! Please check the server log.</span>
|
||||
) : (
|
||||
<frontendConfigContext.Provider value={configState}>{children}</frontendConfigContext.Provider>
|
||||
)
|
||||
}
|
|
@ -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?')
|
||||
)
|
||||
}
|
|
@ -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()
|
||||
})
|
||||
|
||||
|
|
|
@ -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<LinkFieldProps> = ({ 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 <CopyableField content={url} shareOriginUrl={url} />
|
||||
}
|
|
@ -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<ModalVisibilityProps> = ({ show, onHide }) => {
|
||||
useTranslation()
|
||||
const noteFrontmatter = useApplicationState((state) => state.noteDetails.frontmatter)
|
||||
const baseUrl = useBaseUrl()
|
||||
const noteIdentifier = useApplicationState((state) => state.noteDetails.primaryAddress)
|
||||
|
||||
return (
|
||||
<CommonModal show={show} onHide={onHide} showCloseButton={true} titleI18nKey={'editor.modal.shareLink.title'}>
|
||||
<Modal.Body>
|
||||
<Trans i18nKey={'editor.modal.shareLink.editorDescription'} />
|
||||
<CopyableField content={`${baseUrl}n/${noteIdentifier}`} shareOriginUrl={`${baseUrl}n/${noteIdentifier}`} />
|
||||
<NoteUrlField type={LinkType.EDITOR} />
|
||||
<ShowIf condition={noteFrontmatter.type === NoteType.SLIDE}>
|
||||
<Trans i18nKey={'editor.modal.shareLink.slidesDescription'} />
|
||||
<CopyableField content={`${baseUrl}p/${noteIdentifier}`} shareOriginUrl={`${baseUrl}p/${noteIdentifier}`} />
|
||||
<NoteUrlField type={LinkType.SLIDESHOW} />
|
||||
</ShowIf>
|
||||
<ShowIf condition={noteFrontmatter.type === NoteType.DOCUMENT}>
|
||||
<Trans i18nKey={'editor.modal.shareLink.viewOnlyDescription'} />
|
||||
<CopyableField content={`${baseUrl}s/${noteIdentifier}`} shareOriginUrl={`${baseUrl}s/${noteIdentifier}`} />
|
||||
<NoteUrlField type={LinkType.DOCUMENT} />
|
||||
</ShowIf>
|
||||
</Modal.Body>
|
||||
</CommonModal>
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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<ModalVisibilityProps> = ({ show, onHide }) => {
|
||||
useTranslation()
|
||||
const maxDocumentLength = useApplicationState((state) => state.config.maxDocumentLength)
|
||||
const maxDocumentLength = useFrontendConfig().maxDocumentLength
|
||||
|
||||
return (
|
||||
<CommonModal
|
||||
|
|
|
@ -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 { useApplicationState } from '../../../../hooks/common/use-application-state'
|
||||
import { useBooleanState } from '../../../../hooks/common/use-boolean-state'
|
||||
import { useNoteMarkdownContent } from '../../../../hooks/common/use-note-markdown-content'
|
||||
import { useFrontendConfig } from '../../../common/frontend-config-context/use-frontend-config'
|
||||
import { MaxLengthWarningModal } from './max-length-warning-modal'
|
||||
import React, { useEffect, useRef } from 'react'
|
||||
|
||||
|
@ -15,7 +15,7 @@ import React, { useEffect, useRef } from 'react'
|
|||
export const MaxLengthWarning: React.FC = () => {
|
||||
const [modalVisibility, showModal, closeModal] = useBooleanState()
|
||||
const maxLengthWarningAlreadyShown = useRef(false)
|
||||
const maxDocumentLength = useApplicationState((state) => state.config.maxDocumentLength)
|
||||
const maxDocumentLength = useFrontendConfig().maxDocumentLength
|
||||
const markdownContent = useNoteMarkdownContent()
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
@ -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])
|
||||
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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<CommonModalProps> = ({ 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}`
|
||||
|
||||
|
|
|
@ -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<ButtonProps, 'href'>
|
|||
*/
|
||||
export const SignInButton: React.FC<SignInButtonProps> = ({ variant, ...props }) => {
|
||||
const { t } = useTranslation()
|
||||
const authProviders = useApplicationState((state) => state.config.authProviders)
|
||||
const authProviders = useFrontendConfig().authProviders
|
||||
|
||||
const loginLink = useMemo(() => {
|
||||
const oneClickProviders = authProviders.filter(filterOneClickProviders)
|
||||
|
|
|
@ -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<string>()
|
||||
const allowRegister = useApplicationState((state) => state.config.allowRegister)
|
||||
const allowRegister = useFrontendConfig().allowRegister
|
||||
|
||||
const onLoginSubmit = useCallback(
|
||||
(event: FormEvent) => {
|
||||
|
|
|
@ -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<React.ImgHTMLAttributes<HTMLImageElement>> = ({ src, title, alt, ...props }) => {
|
||||
const [imageUrl, setImageUrl] = useState('')
|
||||
const imageProxyEnabled = useApplicationState((state) => state.config.useImageProxy)
|
||||
const imageProxyEnabled = useFrontendConfig().useImageProxy
|
||||
|
||||
useEffect(() => {
|
||||
if (!imageProxyEnabled || !src) {
|
||||
|
|
|
@ -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[] {
|
||||
|
|
|
@ -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])
|
||||
}
|
||||
|
|
|
@ -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 (
|
||||
<ShowIf condition={!!specialUrls.termsOfUse || !!specialUrls.privacy}>
|
||||
|
|
|
@ -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 []
|
||||
}
|
||||
|
||||
|
|
|
@ -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[] {
|
||||
|
|
|
@ -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<ApplicationState>({
|
||||
config: {
|
||||
plantumlServer: 'https://example.org'
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
const view = render(
|
||||
<TestMarkdownRenderer
|
||||
extensions={[new PlantumlMarkdownExtension()]}
|
||||
extensions={[new PlantumlMarkdownExtension('https://example.org')]}
|
||||
content={'```plantuml\nclass Example\n```'}
|
||||
/>
|
||||
)
|
||||
|
@ -36,17 +23,9 @@ describe('PlantUML markdown extensions', () => {
|
|||
})
|
||||
|
||||
it('renders an error if no server is defined', () => {
|
||||
jest.spyOn(reduxModule, 'getGlobalState').mockReturnValue(
|
||||
Mock.of<ApplicationState>({
|
||||
config: {
|
||||
plantumlServer: undefined
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
const view = render(
|
||||
<TestMarkdownRenderer
|
||||
extensions={[new PlantumlMarkdownExtension()]}
|
||||
extensions={[new PlantumlMarkdownExtension(undefined)]}
|
||||
content={'```plantuml\nclass Example\n```'}
|
||||
/>
|
||||
)
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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}` : '')
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<AppPageProps>) {
|
||||
return (
|
||||
<BaseUrlContextProvider baseUrls={pageProps.baseUrls}>
|
||||
<ExpectedOriginBoundary currentOrigin={pageProps.currentOrigin}>
|
||||
<StoreProvider>
|
||||
<BaseHead />
|
||||
<ApplicationLoader>
|
||||
<ErrorBoundary>
|
||||
<UiNotificationBoundary>
|
||||
<Component {...pageProps} />
|
||||
</UiNotificationBoundary>
|
||||
</ErrorBoundary>
|
||||
</ApplicationLoader>
|
||||
</StoreProvider>
|
||||
</ExpectedOriginBoundary>
|
||||
<FrontendConfigContextProvider config={pageProps.frontendConfig}>
|
||||
<ExpectedOriginBoundary currentOrigin={pageProps.currentOrigin}>
|
||||
<StoreProvider>
|
||||
<BaseHead />
|
||||
<ApplicationLoader>
|
||||
<ErrorBoundary>
|
||||
<UiNotificationBoundary>
|
||||
<Component {...pageProps} />
|
||||
</UiNotificationBoundary>
|
||||
</ErrorBoundary>
|
||||
</ApplicationLoader>
|
||||
</StoreProvider>
|
||||
</ExpectedOriginBoundary>
|
||||
</FrontendConfigContextProvider>
|
||||
</BaseUrlContextProvider>
|
||||
)
|
||||
}
|
||||
|
||||
const baseUrlFromEnvExtractor = new BaseUrlFromEnvExtractor()
|
||||
const frontendConfigFetcher = new FrontendConfigFetcher()
|
||||
|
||||
HedgeDocApp.getInitialProps = ({ ctx }: AppContext): AppInitialProps<AppPageProps> => {
|
||||
HedgeDocApp.getInitialProps = async ({ ctx }: AppContext): Promise<AppInitialProps<AppPageProps>> => {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Config>(HttpMethod.GET, req, res, {
|
||||
respondToMatchingRequest<FrontendConfig>(HttpMethod.GET, req, res, {
|
||||
allowAnonymous: true,
|
||||
allowRegister: true,
|
||||
authProviders: [
|
||||
|
|
|
@ -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(() => {
|
||||
|
|
|
@ -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('')
|
||||
|
|
2
frontend/src/redux/application-state.d.ts
vendored
2
frontend/src/redux/application-state.d.ts
vendored
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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<Config, ConfigActions> = (state: Config = initialState, action: ConfigActions) => {
|
||||
switch (action.type) {
|
||||
case ConfigActionType.SET_CONFIG:
|
||||
return action.state
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
|
@ -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<ConfigActionType> {
|
||||
type: ConfigActionType.SET_CONFIG
|
||||
state: Config
|
||||
}
|
|
@ -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<ApplicationState> = combineReducers<ApplicationState>({
|
||||
user: UserReducer,
|
||||
config: ConfigReducer,
|
||||
history: HistoryReducer,
|
||||
editorConfig: EditorConfigReducer,
|
||||
darkMode: DarkModeConfigReducer,
|
||||
|
|
35
frontend/src/utils/frontend-config-fetcher.ts
Normal file
35
frontend/src/utils/frontend-config-fetcher.ts
Normal file
|
@ -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<FrontendConfig | undefined> {
|
||||
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
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue