mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-24 20:14:35 -04:00
Refactor handling of environment variables (#2303)
* Refactor environment variables Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
parent
e412115a78
commit
39a4125cb0
85 changed files with 624 additions and 461 deletions
|
@ -6,7 +6,6 @@
|
|||
|
||||
import { setMotd } from '../../../redux/motd/methods'
|
||||
import { Logger } from '../../../utils/logger'
|
||||
import { customizeAssetsUrl } from '../../../utils/customize-assets-url'
|
||||
import { defaultConfig } from '../../../api/common/default-config'
|
||||
|
||||
export const MOTD_LOCAL_STORAGE_KEY = 'motd.lastModified'
|
||||
|
@ -21,7 +20,7 @@ const log = new Logger('Motd')
|
|||
*/
|
||||
export const fetchMotd = async (): Promise<void> => {
|
||||
const cachedLastModified = window.localStorage.getItem(MOTD_LOCAL_STORAGE_KEY)
|
||||
const motdUrl = `${customizeAssetsUrl}motd.md`
|
||||
const motdUrl = `public/motd.md`
|
||||
|
||||
if (cachedLastModified) {
|
||||
const response = await fetch(motdUrl, {
|
||||
|
|
38
src/components/common/base-url/base-url-context-provider.tsx
Normal file
38
src/components/common/base-url/base-url-context-provider.tsx
Normal file
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import React, { createContext, useState } from 'react'
|
||||
import type { PropsWithChildren } from 'react'
|
||||
|
||||
export interface BaseUrls {
|
||||
renderer: string
|
||||
editor: string
|
||||
}
|
||||
|
||||
interface BaseUrlContextProviderProps {
|
||||
baseUrls?: BaseUrls
|
||||
}
|
||||
|
||||
export const baseUrlContext = createContext<BaseUrls | undefined>(undefined)
|
||||
|
||||
/**
|
||||
* Provides the given base urls as context content and renders an error message if no base urls have been found.
|
||||
*
|
||||
* @param baseUrls The base urls that should be set in the context
|
||||
* @param children The child components that should receive the context value
|
||||
*/
|
||||
export const BaseUrlContextProvider: React.FC<PropsWithChildren<BaseUrlContextProviderProps>> = ({
|
||||
baseUrls,
|
||||
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>
|
||||
) : (
|
||||
<baseUrlContext.Provider value={baseUrlState}>{children}</baseUrlContext.Provider>
|
||||
)
|
||||
}
|
|
@ -6,7 +6,6 @@
|
|||
import React from 'react'
|
||||
import { useAsync } from 'react-use'
|
||||
import { getUser } from '../../../api/users'
|
||||
import { customizeAssetsUrl } from '../../../utils/customize-assets-url'
|
||||
import type { UserAvatarProps } from './user-avatar'
|
||||
import { UserAvatar } from './user-avatar'
|
||||
import type { UserInfo } from '../../../api/users/types'
|
||||
|
@ -34,7 +33,7 @@ export const UserAvatarForUsername: React.FC<UserAvatarForUsernameProps> = ({ us
|
|||
}
|
||||
return {
|
||||
displayName: t('common.guestUser'),
|
||||
photo: `${customizeAssetsUrl}img/avatar.png`,
|
||||
photo: `public/img/avatar.png`,
|
||||
username: ''
|
||||
}
|
||||
}, [username, t])
|
||||
|
|
|
@ -13,7 +13,7 @@ import { CommonModal } from '../../../common/modals/common-modal'
|
|||
import { ShowIf } from '../../../common/show-if/show-if'
|
||||
import { useApplicationState } from '../../../../hooks/common/use-application-state'
|
||||
import { NoteType } from '../../../../redux/note-details/types/note-details'
|
||||
import { useFrontendBaseUrl } from '../../../../hooks/common/use-frontend-base-url'
|
||||
import { useBaseUrl } from '../../../../hooks/common/use-base-url'
|
||||
|
||||
/**
|
||||
* Renders a modal which provides shareable URLs of this note.
|
||||
|
@ -25,7 +25,7 @@ export const ShareModal: React.FC<ModalVisibilityProps> = ({ show, onHide }) =>
|
|||
useTranslation()
|
||||
const noteFrontmatter = useApplicationState((state) => state.noteDetails.frontmatter)
|
||||
const editorMode = useApplicationState((state) => state.editorConfig.editorMode)
|
||||
const baseUrl = useFrontendBaseUrl()
|
||||
const baseUrl = useBaseUrl()
|
||||
const noteIdentifier = useApplicationState((state) => state.noteDetails.primaryAddress)
|
||||
|
||||
return (
|
||||
|
|
|
@ -5,9 +5,9 @@
|
|||
*/
|
||||
|
||||
import { useMemo } from 'react'
|
||||
import { backendUrl } from '../../../../../utils/backend-url'
|
||||
import { isMockMode } from '../../../../../utils/test-modes'
|
||||
import { useApplicationState } from '../../../../../hooks/common/use-application-state'
|
||||
import { useBaseUrl } from '../../../../../hooks/common/use-base-url'
|
||||
|
||||
const LOCAL_FALLBACK_URL = 'ws://localhost:8080/realtime/'
|
||||
|
||||
|
@ -16,13 +16,14 @@ const LOCAL_FALLBACK_URL = 'ws://localhost:8080/realtime/'
|
|||
*/
|
||||
export const useWebsocketUrl = (): URL => {
|
||||
const noteId = useApplicationState((state) => state.noteDetails.id)
|
||||
const baseUrl = useBaseUrl()
|
||||
|
||||
const baseUrl = useMemo(() => {
|
||||
const websocketUrl = useMemo(() => {
|
||||
if (isMockMode) {
|
||||
return process.env.NEXT_PUBLIC_REALTIME_URL ?? LOCAL_FALLBACK_URL
|
||||
return LOCAL_FALLBACK_URL
|
||||
}
|
||||
try {
|
||||
const backendBaseUrlParsed = new URL(backendUrl, window.location.toString())
|
||||
const backendBaseUrlParsed = new URL(baseUrl, window.location.toString())
|
||||
backendBaseUrlParsed.protocol = backendBaseUrlParsed.protocol === 'https:' ? 'wss:' : 'ws:'
|
||||
backendBaseUrlParsed.pathname += 'realtime'
|
||||
return backendBaseUrlParsed.toString()
|
||||
|
@ -30,11 +31,11 @@ export const useWebsocketUrl = (): URL => {
|
|||
console.error(e)
|
||||
return LOCAL_FALLBACK_URL
|
||||
}
|
||||
}, [])
|
||||
}, [baseUrl])
|
||||
|
||||
return useMemo(() => {
|
||||
const url = new URL(baseUrl)
|
||||
const url = new URL(websocketUrl)
|
||||
url.search = `?noteId=${noteId}`
|
||||
return url
|
||||
}, [baseUrl, noteId])
|
||||
}, [noteId, websocketUrl])
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ const customEmojis: CustomEmoji[] = ForkAwesomeIcons.map((name) => ({
|
|||
category: 'ForkAwesome'
|
||||
}))
|
||||
|
||||
const EMOJI_DATA_PATH = '/_next/static/js/emoji-data.json'
|
||||
const EMOJI_DATA_PATH = '_next/static/js/emoji-data.json'
|
||||
|
||||
const emojiPickerConfig = {
|
||||
customEmoji: customEmojis,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
@ -8,7 +8,7 @@ import type { PropsWithChildren } from 'react'
|
|||
import React, { createContext, useContext, useEffect, useMemo } from 'react'
|
||||
import { RendererToEditorCommunicator } from '../../render-page/window-post-message-communicator/renderer-to-editor-communicator'
|
||||
import { CommunicationMessageType } from '../../render-page/window-post-message-communicator/rendering-message'
|
||||
import { ORIGIN_TYPE, useOriginFromConfig } from './use-origin-from-config'
|
||||
import { ORIGIN, useBaseUrl } from '../../../hooks/common/use-base-url'
|
||||
|
||||
const RendererToEditorCommunicatorContext = createContext<RendererToEditorCommunicator | undefined>(undefined)
|
||||
|
||||
|
@ -27,12 +27,13 @@ export const useRendererToEditorCommunicator: () => RendererToEditorCommunicator
|
|||
}
|
||||
|
||||
export const RendererToEditorCommunicatorContextProvider: React.FC<PropsWithChildren<unknown>> = ({ children }) => {
|
||||
const editorOrigin = useOriginFromConfig(ORIGIN_TYPE.EDITOR)
|
||||
const editorOrigin = useBaseUrl(ORIGIN.EDITOR)
|
||||
const communicator = useMemo<RendererToEditorCommunicator>(() => new RendererToEditorCommunicator(), [])
|
||||
|
||||
useEffect(() => {
|
||||
const currentCommunicator = communicator
|
||||
currentCommunicator.setMessageTarget(window.parent, editorOrigin)
|
||||
|
||||
currentCommunicator.setMessageTarget(window.parent, new URL(editorOrigin).origin)
|
||||
currentCommunicator.registerEventListener()
|
||||
currentCommunicator.enableCommunication()
|
||||
currentCommunicator.sendMessageToOtherSide({
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { useApplicationState } from '../../../hooks/common/use-application-state'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
export enum ORIGIN_TYPE {
|
||||
EDITOR,
|
||||
RENDERER
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the url origin of the editor or the renderer.
|
||||
*/
|
||||
export const useOriginFromConfig = (originType: ORIGIN_TYPE): string => {
|
||||
const originFromConfig = useApplicationState((state) =>
|
||||
originType === ORIGIN_TYPE.EDITOR
|
||||
? state.config.iframeCommunication.editorOrigin
|
||||
: state.config.iframeCommunication.rendererOrigin
|
||||
)
|
||||
|
||||
return useMemo(() => {
|
||||
return process.env.NEXT_PUBLIC_IGNORE_IFRAME_ORIGIN_CONFIG !== undefined
|
||||
? window.location.origin + '/'
|
||||
: originFromConfig ?? ''
|
||||
}, [originFromConfig])
|
||||
}
|
|
@ -7,6 +7,7 @@
|
|||
import type { RefObject } from 'react'
|
||||
import { useCallback, useEffect, useMemo, useRef } from 'react'
|
||||
import { Logger } from '../../../../utils/logger'
|
||||
import { ORIGIN, useBaseUrl } from '../../../../hooks/common/use-base-url'
|
||||
|
||||
const log = new Logger('IframeLoader')
|
||||
|
||||
|
@ -14,15 +15,18 @@ const log = new Logger('IframeLoader')
|
|||
* Generates a callback for an iframe load handler, that enforces a given URL if frame navigates away.
|
||||
*
|
||||
* @param iFrameReference A reference to the iframe react dom element.
|
||||
* @param rendererOrigin The base url that should be enforced.
|
||||
* @param onNavigateAway An optional callback that is executed when the iframe leaves the enforced URL.
|
||||
*/
|
||||
export const useForceRenderPageUrlOnIframeLoadCallback = (
|
||||
iFrameReference: RefObject<HTMLIFrameElement>,
|
||||
rendererOrigin: string,
|
||||
onNavigateAway?: () => void
|
||||
): (() => void) => {
|
||||
const forcedUrl = useMemo(() => `${rendererOrigin}render`, [rendererOrigin])
|
||||
const rendererBaseUrl = useBaseUrl(ORIGIN.RENDERER)
|
||||
const forcedUrl = useMemo(() => {
|
||||
const renderUrl = new URL(rendererBaseUrl)
|
||||
renderUrl.pathname += 'render'
|
||||
return renderUrl.toString()
|
||||
}, [rendererBaseUrl])
|
||||
const redirectionInProgress = useRef<boolean>(false)
|
||||
|
||||
const loadCallback = useCallback(() => {
|
||||
|
|
|
@ -26,8 +26,8 @@ import { useSendScrollState } from './hooks/use-send-scroll-state'
|
|||
import { Logger } from '../../../utils/logger'
|
||||
import { useEffectOnRenderTypeChange } from './hooks/use-effect-on-render-type-change'
|
||||
import { cypressAttribute, cypressId } from '../../../utils/cypress-attribute'
|
||||
import { ORIGIN_TYPE, useOriginFromConfig } from '../render-context/use-origin-from-config'
|
||||
import { getGlobalState } from '../../../redux'
|
||||
import { ORIGIN, useBaseUrl } from '../../../hooks/common/use-base-url'
|
||||
|
||||
export interface RenderIframeProps extends RendererProps {
|
||||
rendererType: RendererType
|
||||
|
@ -64,14 +64,14 @@ export const RenderIframe: React.FC<RenderIframeProps> = ({
|
|||
forcedDarkMode
|
||||
}) => {
|
||||
const frameReference = useRef<HTMLIFrameElement>(null)
|
||||
const rendererOrigin = useOriginFromConfig(ORIGIN_TYPE.RENDERER)
|
||||
const rendererBaseUrl = useBaseUrl(ORIGIN.RENDERER)
|
||||
const iframeCommunicator = useEditorToRendererCommunicator()
|
||||
const resetRendererReady = useCallback(() => {
|
||||
log.debug('Reset render status')
|
||||
setRendererStatus(false)
|
||||
}, [])
|
||||
const rendererReady = useIsRendererReady()
|
||||
const onIframeLoad = useForceRenderPageUrlOnIframeLoadCallback(frameReference, rendererOrigin, resetRendererReady)
|
||||
const onIframeLoad = useForceRenderPageUrlOnIframeLoadCallback(frameReference, resetRendererReady)
|
||||
const [frameHeight, setFrameHeight] = useState<number>(0)
|
||||
|
||||
useEffect(() => () => setRendererStatus(false), [iframeCommunicator])
|
||||
|
@ -124,8 +124,9 @@ export const RenderIframe: React.FC<RenderIframeProps> = ({
|
|||
log.error('Load triggered without content window')
|
||||
return
|
||||
}
|
||||
log.debug(`Set iframecommunicator window with origin ${rendererOrigin ?? 'undefined'}`)
|
||||
iframeCommunicator.setMessageTarget(otherWindow, rendererOrigin)
|
||||
const origin = new URL(rendererBaseUrl).origin
|
||||
log.debug(`Set iframecommunicator window with origin ${origin ?? 'undefined'}`)
|
||||
iframeCommunicator.setMessageTarget(otherWindow, origin)
|
||||
iframeCommunicator.enableCommunication()
|
||||
iframeCommunicator.sendMessageToOtherSide({
|
||||
type: CommunicationMessageType.SET_BASE_CONFIGURATION,
|
||||
|
@ -135,7 +136,7 @@ export const RenderIframe: React.FC<RenderIframeProps> = ({
|
|||
}
|
||||
})
|
||||
setRendererStatus(true)
|
||||
}, [iframeCommunicator, rendererOrigin, rendererType])
|
||||
}, [iframeCommunicator, rendererBaseUrl, rendererType])
|
||||
)
|
||||
|
||||
useEffectOnRenderTypeChange(rendererType, onIframeLoad)
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { customizeAssetsUrl } from '../../utils/customize-assets-url'
|
||||
import { defaultConfig } from '../../api/common/default-config'
|
||||
|
||||
/**
|
||||
|
@ -14,7 +13,7 @@ import { defaultConfig } from '../../api/common/default-config'
|
|||
* @throws {Error} if the content can't be fetched
|
||||
*/
|
||||
export const fetchFrontPageContent = async (): Promise<string> => {
|
||||
const response = await fetch(customizeAssetsUrl + 'intro.md', {
|
||||
const response = await fetch('public/intro.md', {
|
||||
...defaultConfig,
|
||||
method: 'GET'
|
||||
})
|
||||
|
|
|
@ -9,6 +9,7 @@ import { Trans } from 'react-i18next'
|
|||
import { VersionInfoModal } from './version-info-modal'
|
||||
import { cypressId } from '../../../../utils/cypress-attribute'
|
||||
import { useBooleanState } from '../../../../hooks/common/use-boolean-state'
|
||||
import { Button } from 'react-bootstrap'
|
||||
|
||||
/**
|
||||
* Renders a link for the version info and the {@link VersionInfoModal}.
|
||||
|
@ -18,9 +19,14 @@ export const VersionInfoLink: React.FC = () => {
|
|||
|
||||
return (
|
||||
<Fragment>
|
||||
<a {...cypressId('show-version-modal')} href={'#'} className={'text-light'} onClick={showModal}>
|
||||
<Button
|
||||
size={'sm'}
|
||||
variant={'link'}
|
||||
{...cypressId('show-version-modal')}
|
||||
className={'text-light p-0'}
|
||||
onClick={showModal}>
|
||||
<Trans i18nKey={'landing.versionInfo.versionInfo'} />
|
||||
</a>
|
||||
</Button>
|
||||
<VersionInfoModal onHide={closeModal} show={modalVisibility} />
|
||||
</Fragment>
|
||||
)
|
||||
|
|
|
@ -34,7 +34,7 @@ export const SignInButton: React.FC<SignInButtonProps> = ({ variant, ...props })
|
|||
const metadata = getOneClickProviderMetadata(oneClickProviders[0])
|
||||
return metadata.url
|
||||
}
|
||||
return '/login'
|
||||
return 'login'
|
||||
}, [authProviders])
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
@ -8,17 +8,19 @@ import React from 'react'
|
|||
import Head from 'next/head'
|
||||
import { useAppTitle } from '../../hooks/common/use-app-title'
|
||||
import { FavIcon } from './fav-icon'
|
||||
import { useBaseUrl } from '../../hooks/common/use-base-url'
|
||||
|
||||
/**
|
||||
* Sets basic browser meta tags.
|
||||
*/
|
||||
export const BaseHead: React.FC = () => {
|
||||
const appTitle = useAppTitle()
|
||||
|
||||
const baseUrl = useBaseUrl()
|
||||
return (
|
||||
<Head>
|
||||
<title>{appTitle}</title>
|
||||
<FavIcon />
|
||||
<base href={baseUrl} />
|
||||
<meta content='width=device-width, initial-scale=1' name='viewport' />
|
||||
</Head>
|
||||
)
|
||||
|
|
|
@ -12,17 +12,17 @@ import React, { Fragment } from 'react'
|
|||
export const FavIcon: React.FC = () => {
|
||||
return (
|
||||
<Fragment>
|
||||
<link href='/icons/apple-touch-icon.png' rel='apple-touch-icon' sizes='180x180' />
|
||||
<link href='/icons/favicon-32x32.png' rel='icon' sizes='32x32' type='image/png' />
|
||||
<link href='/icons/favicon-16x16.png' rel='icon' sizes='16x16' type='image/png' />
|
||||
<link href='/icons/site.webmanifest' rel='manifest' />
|
||||
<link href='/icons/favicon.ico' rel='shortcut icon' />
|
||||
<link color='#b51f08' href='/icons/safari-pinned-tab.svg' rel='mask-icon' />
|
||||
<link href='icons/apple-touch-icon.png' rel='apple-touch-icon' sizes='180x180' />
|
||||
<link href='icons/favicon-32x32.png' rel='icon' sizes='32x32' type='image/png' />
|
||||
<link href='icons/favicon-16x16.png' rel='icon' sizes='16x16' type='image/png' />
|
||||
<link href='icons/site.webmanifest' rel='manifest' />
|
||||
<link href='icons/favicon.ico' rel='shortcut icon' />
|
||||
<link color='#b51f08' href='icons/safari-pinned-tab.svg' rel='mask-icon' />
|
||||
<meta name='apple-mobile-web-app-title' content='HedgeDoc' />
|
||||
<meta name='application-name' content='HedgeDoc' />
|
||||
<meta name='msapplication-TileColor' content='#b51f08' />
|
||||
<meta name='theme-color' content='#b51f08' />
|
||||
<meta content='/icons/browserconfig.xml' name='msapplication-config' />
|
||||
<meta content='icons/browserconfig.xml' name='msapplication-config' />
|
||||
<meta content='HedgeDoc - Collaborative markdown notes' name='description' />
|
||||
</Fragment>
|
||||
)
|
||||
|
|
|
@ -7,7 +7,6 @@ import type { AuthProvider } from '../../../../api/config/types'
|
|||
import { AuthProviderType } from '../../../../api/config/types'
|
||||
import type { IconName } from '../../../common/fork-awesome/types'
|
||||
import styles from '../via-one-click.module.scss'
|
||||
import { backendUrl } from '../../../../utils/backend-url'
|
||||
import { Logger } from '../../../../utils/logger'
|
||||
|
||||
export interface OneClickMetadata {
|
||||
|
@ -18,7 +17,7 @@ export interface OneClickMetadata {
|
|||
}
|
||||
|
||||
const getBackendAuthUrl = (providerIdentifer: string): string => {
|
||||
return `${backendUrl}auth/${providerIdentifer}`
|
||||
return `auth/${providerIdentifer}`
|
||||
}
|
||||
|
||||
const logger = new Logger('GetOneClickProviderMetadata')
|
||||
|
|
|
@ -9,7 +9,6 @@ import { Button, Card } from 'react-bootstrap'
|
|||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { ForkAwesomeIcon } from '../../common/fork-awesome/fork-awesome-icon'
|
||||
import { AccountDeletionModal } from './account-deletion-modal'
|
||||
import { apiUrl } from '../../../utils/api-url'
|
||||
import { useBooleanState } from '../../../hooks/common/use-boolean-state'
|
||||
|
||||
/**
|
||||
|
@ -26,7 +25,7 @@ export const ProfileAccountManagement: React.FC = () => {
|
|||
<Card.Title>
|
||||
<Trans i18nKey='profile.accountManagement' />
|
||||
</Card.Title>
|
||||
<Button variant='secondary' block href={apiUrl + 'me/export'} className='mb-2'>
|
||||
<Button variant='secondary' block href={'me/export'} className='mb-2'>
|
||||
<ForkAwesomeIcon icon='cloud-download' fixedWidth={true} className='mx-2' />
|
||||
<Trans i18nKey='profile.exportUserData' />
|
||||
</Button>
|
||||
|
|
|
@ -128,7 +128,9 @@ export const IframeMarkdownRenderer: React.FC = () => {
|
|||
)
|
||||
|
||||
if (!baseConfiguration) {
|
||||
return null
|
||||
return (
|
||||
<span>This is the render endpoint. If you can read this text then please check your HedgeDoc configuration.</span>
|
||||
)
|
||||
}
|
||||
|
||||
switch (baseConfiguration.rendererType) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue