mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-22 03:05:19 -04:00
fix: Move content into to frontend directory
Doing this BEFORE the merge prevents a lot of merge conflicts. Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
parent
4e18ce38f3
commit
762a0a850e
1051 changed files with 0 additions and 35 deletions
68
frontend/src/utils/base-url-from-env-extractor.test.ts
Normal file
68
frontend/src/utils/base-url-from-env-extractor.test.ts
Normal file
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { BaseUrlFromEnvExtractor } from './base-url-from-env-extractor'
|
||||
|
||||
describe('BaseUrlFromEnvExtractor', () => {
|
||||
it('should return the base urls if both are valid urls', () => {
|
||||
process.env.HD_EDITOR_BASE_URL = 'https://editor.example.org/'
|
||||
process.env.HD_RENDERER_BASE_URL = 'https://renderer.example.org/'
|
||||
const baseUrlFromEnvExtractor = new BaseUrlFromEnvExtractor()
|
||||
const result = baseUrlFromEnvExtractor.extractBaseUrls()
|
||||
expect(result.isPresent()).toBeTruthy()
|
||||
expect(result.get()).toStrictEqual({
|
||||
renderer: 'https://renderer.example.org/',
|
||||
editor: 'https://editor.example.org/'
|
||||
})
|
||||
})
|
||||
|
||||
it('should return an empty optional if no var is set', () => {
|
||||
process.env.HD_EDITOR_BASE_URL = undefined
|
||||
process.env.HD_RENDERER_BASE_URL = undefined
|
||||
const baseUrlFromEnvExtractor = new BaseUrlFromEnvExtractor()
|
||||
expect(baseUrlFromEnvExtractor.extractBaseUrls().isEmpty()).toBeTruthy()
|
||||
})
|
||||
|
||||
it("should return an empty optional if editor base url isn't an URL", () => {
|
||||
process.env.HD_EDITOR_BASE_URL = 'bibedibabedibu'
|
||||
process.env.HD_RENDERER_BASE_URL = 'https://renderer.example.org/'
|
||||
const baseUrlFromEnvExtractor = new BaseUrlFromEnvExtractor()
|
||||
expect(baseUrlFromEnvExtractor.extractBaseUrls().isEmpty()).toBeTruthy()
|
||||
})
|
||||
|
||||
it("should return an empty optional if renderer base url isn't an URL", () => {
|
||||
process.env.HD_EDITOR_BASE_URL = 'https://editor.example.org/'
|
||||
process.env.HD_RENDERER_BASE_URL = 'bibedibabedibu'
|
||||
const baseUrlFromEnvExtractor = new BaseUrlFromEnvExtractor()
|
||||
expect(baseUrlFromEnvExtractor.extractBaseUrls().isEmpty()).toBeTruthy()
|
||||
})
|
||||
|
||||
it("should return an empty optional if editor base url isn't ending with a slash", () => {
|
||||
process.env.HD_EDITOR_BASE_URL = 'https://editor.example.org'
|
||||
process.env.HD_RENDERER_BASE_URL = 'https://renderer.example.org/'
|
||||
const baseUrlFromEnvExtractor = new BaseUrlFromEnvExtractor()
|
||||
expect(baseUrlFromEnvExtractor.extractBaseUrls().isEmpty()).toBeTruthy()
|
||||
})
|
||||
|
||||
it("should return an empty optional if renderer base url isn't ending with a slash", () => {
|
||||
process.env.HD_EDITOR_BASE_URL = 'https://editor.example.org/'
|
||||
process.env.HD_RENDERER_BASE_URL = 'https://renderer.example.org'
|
||||
const baseUrlFromEnvExtractor = new BaseUrlFromEnvExtractor()
|
||||
expect(baseUrlFromEnvExtractor.extractBaseUrls().isEmpty()).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should copy editor base url to renderer base url if renderer base url is omitted', () => {
|
||||
process.env.HD_EDITOR_BASE_URL = 'https://editor.example.org/'
|
||||
delete process.env.HD_RENDERER_BASE_URL
|
||||
const baseUrlFromEnvExtractor = new BaseUrlFromEnvExtractor()
|
||||
const result = baseUrlFromEnvExtractor.extractBaseUrls()
|
||||
expect(result.isPresent()).toBeTruthy()
|
||||
expect(result.get()).toStrictEqual({
|
||||
renderer: 'https://editor.example.org/',
|
||||
editor: 'https://editor.example.org/'
|
||||
})
|
||||
})
|
||||
})
|
89
frontend/src/utils/base-url-from-env-extractor.ts
Normal file
89
frontend/src/utils/base-url-from-env-extractor.ts
Normal file
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Optional } from '@mrdrogdrog/optional'
|
||||
import type { BaseUrls } from '../components/common/base-url/base-url-context-provider'
|
||||
import { Logger } from './logger'
|
||||
import { isTestMode } from './test-modes'
|
||||
|
||||
/**
|
||||
* Extracts the editor and renderer base urls from the environment variables.
|
||||
*/
|
||||
export class BaseUrlFromEnvExtractor {
|
||||
private baseUrls: Optional<BaseUrls> | undefined
|
||||
private logger = new Logger('Base URL Configuration')
|
||||
|
||||
private extractUrlFromEnvVar(envVarName: string, envVarValue: string | undefined): Optional<URL> {
|
||||
return Optional.ofNullable(envVarValue)
|
||||
.filter((value) => {
|
||||
const endsWithSlash = value.endsWith('/')
|
||||
if (!endsWithSlash) {
|
||||
this.logger.error(`${envVarName} must end with an '/'`)
|
||||
}
|
||||
return endsWithSlash
|
||||
})
|
||||
.map((value) => {
|
||||
try {
|
||||
return new URL(value)
|
||||
} catch (error) {
|
||||
return null
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private extractEditorBaseUrlFromEnv(): Optional<URL> {
|
||||
const envValue = this.extractUrlFromEnvVar('HD_EDITOR_BASE_URL', process.env.HD_EDITOR_BASE_URL)
|
||||
if (envValue.isEmpty()) {
|
||||
this.logger.error("HD_EDITOR_BASE_URL isn't a valid URL!")
|
||||
}
|
||||
return envValue
|
||||
}
|
||||
|
||||
private extractRendererBaseUrlFromEnv(editorBaseUrl: URL): Optional<URL> {
|
||||
if (isTestMode) {
|
||||
this.logger.info('Test mode activated. Using editor base url for renderer.')
|
||||
return Optional.of(editorBaseUrl)
|
||||
}
|
||||
|
||||
if (!process.env.HD_RENDERER_BASE_URL) {
|
||||
this.logger.info('HD_RENDERER_BASE_URL is unset. Using editor base url for renderer.')
|
||||
return Optional.of(editorBaseUrl)
|
||||
}
|
||||
|
||||
return this.extractUrlFromEnvVar('HD_RENDERER_BASE_URL', process.env.HD_RENDERER_BASE_URL)
|
||||
}
|
||||
|
||||
private renewBaseUrls(): void {
|
||||
this.baseUrls = this.extractEditorBaseUrlFromEnv().flatMap((editorBaseUrl) =>
|
||||
this.extractRendererBaseUrlFromEnv(editorBaseUrl).map((rendererBaseUrl) => {
|
||||
return {
|
||||
editor: editorBaseUrl.toString(),
|
||||
renderer: rendererBaseUrl.toString()
|
||||
}
|
||||
})
|
||||
)
|
||||
this.baseUrls.ifPresent((urls) => {
|
||||
this.logger.info('Editor base URL', urls.editor.toString())
|
||||
this.logger.info('Renderer base URL', urls.renderer.toString())
|
||||
})
|
||||
}
|
||||
|
||||
private isEnvironmentExtractDone(): boolean {
|
||||
return this.baseUrls !== undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the editor and renderer base urls from the environment variables.
|
||||
*
|
||||
* @return An {@link Optional} with the base urls.
|
||||
*/
|
||||
public extractBaseUrls(): Optional<BaseUrls> {
|
||||
if (!this.isEnvironmentExtractDone()) {
|
||||
this.renewBaseUrls()
|
||||
}
|
||||
return Optional.ofNullable(this.baseUrls).flatMap((value) => value)
|
||||
}
|
||||
}
|
51
frontend/src/utils/cypress-attribute.ts
Normal file
51
frontend/src/utils/cypress-attribute.ts
Normal file
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { isTestMode } from './test-modes'
|
||||
|
||||
export interface PropsWithDataCypressId {
|
||||
'data-cypress-id'?: string | undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an object with the "data-cypress-id" attribute that is used to find
|
||||
* elements for integration tests.
|
||||
* This works only if the runtime is built in test mode.
|
||||
*
|
||||
* @param identifier The identifier that is used to find the element
|
||||
* @return An object if in test mode, {@link undefined} otherwise.
|
||||
*/
|
||||
export const cypressId = (identifier: string | undefined | PropsWithDataCypressId): PropsWithDataCypressId => {
|
||||
if (!isTestMode || !identifier) {
|
||||
return {}
|
||||
}
|
||||
|
||||
const attributeContent = typeof identifier === 'string' ? identifier : identifier['data-cypress-id']
|
||||
|
||||
return attributeContent !== undefined ? { 'data-cypress-id': attributeContent } : {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an object with an attribute that starts with "data-cypress-" and the given attribute name.
|
||||
* It is used to check additional data during integration tests.
|
||||
* This works only if the runtime is built in test mode.
|
||||
*
|
||||
* @param attribute The attribute name
|
||||
* @param value The attribute content
|
||||
* @return An object if in test mode, undefined otherwise.
|
||||
*/
|
||||
export const cypressAttribute = (
|
||||
attribute: string,
|
||||
value: string | undefined
|
||||
): Record<string, string | undefined> | undefined => {
|
||||
if (!isTestMode) {
|
||||
return
|
||||
}
|
||||
|
||||
return {
|
||||
[`data-cypress-${attribute}`]: value
|
||||
}
|
||||
}
|
12
frontend/src/utils/is-client-side-rendering.ts
Normal file
12
frontend/src/utils/is-client-side-rendering.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
/**
|
||||
* Detects if the application is running on client side.
|
||||
*/
|
||||
export const isClientSideRendering = (): boolean => {
|
||||
return typeof window !== 'undefined' && typeof window.navigator !== 'undefined'
|
||||
}
|
71
frontend/src/utils/logger.test.ts
Normal file
71
frontend/src/utils/logger.test.ts
Normal file
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Logger, LogLevel } from './logger'
|
||||
import { Settings } from 'luxon'
|
||||
|
||||
describe('Logger', () => {
|
||||
let consoleMock: jest.SpyInstance
|
||||
let originalNow: () => number
|
||||
let dateShift = 0
|
||||
|
||||
function mockConsole(methodToMock: LogLevel, onResult: (result: string) => void) {
|
||||
consoleMock = jest.spyOn(console, methodToMock).mockImplementation((...data: string[]) => {
|
||||
const result = data.reduce((state, current) => state + ' ' + current)
|
||||
onResult(result)
|
||||
})
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
originalNow = Settings.now
|
||||
Settings.now = () => new Date(2021, 9, 25, dateShift, 1 + dateShift, 2 + dateShift, 3 + dateShift).valueOf()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
Settings.now = originalNow
|
||||
consoleMock.mockReset()
|
||||
})
|
||||
|
||||
it('logs a debug message into the console', (done) => {
|
||||
dateShift = 0
|
||||
mockConsole(LogLevel.DEBUG, (result) => {
|
||||
expect(consoleMock).toBeCalled()
|
||||
expect(result).toEqual('%c[2021-10-25 00:01:02] %c(prefix) color: yellow color: orange beans')
|
||||
done()
|
||||
})
|
||||
new Logger('prefix').debug('beans')
|
||||
})
|
||||
|
||||
it('logs a info message into the console', (done) => {
|
||||
dateShift = 1
|
||||
mockConsole(LogLevel.INFO, (result) => {
|
||||
expect(consoleMock).toBeCalled()
|
||||
expect(result).toEqual('%c[2021-10-25 01:02:03] %c(prefix) color: yellow color: orange toast')
|
||||
done()
|
||||
})
|
||||
new Logger('prefix').info('toast')
|
||||
})
|
||||
|
||||
it('logs a warn message into the console', (done) => {
|
||||
dateShift = 2
|
||||
mockConsole(LogLevel.WARN, (result) => {
|
||||
expect(consoleMock).toBeCalled()
|
||||
expect(result).toEqual('%c[2021-10-25 02:03:04] %c(prefix) color: yellow color: orange eggs')
|
||||
done()
|
||||
})
|
||||
new Logger('prefix').warn('eggs')
|
||||
})
|
||||
|
||||
it('logs a error message into the console', (done) => {
|
||||
dateShift = 3
|
||||
mockConsole(LogLevel.ERROR, (result) => {
|
||||
expect(consoleMock).toBeCalled()
|
||||
expect(result).toEqual('%c[2021-10-25 03:04:05] %c(prefix) color: yellow color: orange bacon')
|
||||
done()
|
||||
})
|
||||
new Logger('prefix').error('bacon')
|
||||
})
|
||||
})
|
87
frontend/src/utils/logger.ts
Normal file
87
frontend/src/utils/logger.ts
Normal file
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { DateTime } from 'luxon'
|
||||
|
||||
export enum LogLevel {
|
||||
DEBUG = 'debug',
|
||||
INFO = 'info',
|
||||
WARN = 'warn',
|
||||
ERROR = 'error'
|
||||
}
|
||||
|
||||
type OutputFunction = (...data: unknown[]) => void
|
||||
|
||||
/**
|
||||
* Simple logger that prefixes messages with a timestamp and a name.
|
||||
*/
|
||||
export class Logger {
|
||||
private readonly scope: string
|
||||
|
||||
constructor(scope: string) {
|
||||
this.scope = scope
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs a debug message.
|
||||
*
|
||||
* @param data data to log
|
||||
*/
|
||||
debug(...data: unknown[]): void {
|
||||
this.log(LogLevel.DEBUG, ...data)
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs a normal informative message.
|
||||
*
|
||||
* @param data data to log
|
||||
*/
|
||||
info(...data: unknown[]): void {
|
||||
this.log(LogLevel.INFO, ...data)
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs a warning.
|
||||
*
|
||||
* @param data data to log
|
||||
*/
|
||||
warn(...data: unknown[]): void {
|
||||
this.log(LogLevel.WARN, ...data)
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs an error.
|
||||
*
|
||||
* @param data data to log
|
||||
*/
|
||||
error(...data: unknown[]): void {
|
||||
this.log(LogLevel.ERROR, ...data)
|
||||
}
|
||||
|
||||
private log(loglevel: LogLevel, ...data: unknown[]) {
|
||||
const preparedData = [...this.prefix(), ...data]
|
||||
const logOutput = Logger.getLogOutput(loglevel)
|
||||
logOutput(...preparedData)
|
||||
}
|
||||
|
||||
private static getLogOutput(logLevel: LogLevel): OutputFunction {
|
||||
switch (logLevel) {
|
||||
case LogLevel.INFO:
|
||||
return console.info
|
||||
case LogLevel.DEBUG:
|
||||
return console.debug
|
||||
case LogLevel.ERROR:
|
||||
return console.error
|
||||
case LogLevel.WARN:
|
||||
return console.warn
|
||||
}
|
||||
}
|
||||
|
||||
private prefix(): string[] {
|
||||
const timestamp = DateTime.now().toFormat('yyyy-MM-dd HH:mm:ss')
|
||||
return [`%c[${timestamp}] %c(${this.scope})`, 'color: yellow', 'color: orange']
|
||||
}
|
||||
}
|
18
frontend/src/utils/read-file.test.ts
Normal file
18
frontend/src/utils/read-file.test.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { FileContentFormat, readFile } from './read-file'
|
||||
|
||||
describe('read file', () => {
|
||||
it('reads files as text', async () => {
|
||||
const a = await readFile(new Blob(['Kinderriegel'], { type: 'text/plain' }), FileContentFormat.TEXT)
|
||||
expect(a).toBe('Kinderriegel')
|
||||
})
|
||||
it('reads files as data url', async () => {
|
||||
const a = await readFile(new Blob(['Kinderriegel'], { type: 'text/plain' }), FileContentFormat.DATA_URL)
|
||||
expect(a).toBe('data:text/plain;base64,S2luZGVycmllZ2Vs')
|
||||
})
|
||||
})
|
40
frontend/src/utils/read-file.ts
Normal file
40
frontend/src/utils/read-file.ts
Normal file
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export enum FileContentFormat {
|
||||
TEXT,
|
||||
DATA_URL
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the given {@link File}.
|
||||
*
|
||||
* @param file The file to read
|
||||
* @param fileReaderMode Defines as what the file content should be formatted.
|
||||
* @throws {Error} if an invalid read mode was given or if the file couldn't be read.
|
||||
* @return the file content
|
||||
*/
|
||||
export const readFile = async (file: Blob, fileReaderMode: FileContentFormat): Promise<string> => {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
const fileReader = new FileReader()
|
||||
fileReader.addEventListener('load', () => {
|
||||
resolve(fileReader.result as string)
|
||||
})
|
||||
fileReader.addEventListener('error', (error) => {
|
||||
reject(error)
|
||||
})
|
||||
switch (fileReaderMode) {
|
||||
case FileContentFormat.DATA_URL:
|
||||
fileReader.readAsDataURL(file)
|
||||
break
|
||||
case FileContentFormat.TEXT:
|
||||
fileReader.readAsText(file)
|
||||
break
|
||||
default:
|
||||
throw new Error('Unknown file reader mode')
|
||||
}
|
||||
})
|
||||
}
|
21
frontend/src/utils/test-id.ts
Normal file
21
frontend/src/utils/test-id.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export interface PropsWithDataTestId {
|
||||
'data-testid'?: string | undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an object with the "data-testid" attribute that is used to find
|
||||
* elements in unit tests.
|
||||
* This works only if the runtime is built in test mode.
|
||||
*
|
||||
* @param identifier The identifier that is used to find the element
|
||||
* @return An object if in test mode, undefined otherwise.
|
||||
*/
|
||||
export const testId = (identifier: string): PropsWithDataTestId => {
|
||||
return process.env.NODE_ENV === 'test' ? { 'data-testid': identifier } : {}
|
||||
}
|
44
frontend/src/utils/test-modes.js
Normal file
44
frontend/src/utils/test-modes.js
Normal file
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
/**
|
||||
* This file is intentionally a js and not a ts file because it is used in `next.config.js`
|
||||
*/
|
||||
|
||||
/**
|
||||
* Checks if the given string is a positive answer (yes, true or 1).
|
||||
*
|
||||
* @param {string} value The value to check
|
||||
* @return {boolean} {@code true} if the value describes a positive answer string
|
||||
*/
|
||||
const isPositiveAnswer = (value) => {
|
||||
const lowerValue = value.toLowerCase()
|
||||
return lowerValue === 'yes' || lowerValue === '1' || lowerValue === 'true'
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines if the current runtime is built in e2e test mode.
|
||||
* @type boolean
|
||||
*/
|
||||
const isTestMode = !!process.env.NEXT_PUBLIC_TEST_MODE && isPositiveAnswer(process.env.NEXT_PUBLIC_TEST_MODE)
|
||||
|
||||
/**
|
||||
* Defines if the current runtime should use the mocked backend.
|
||||
* @type boolean
|
||||
*/
|
||||
const isMockMode = !!process.env.NEXT_PUBLIC_USE_MOCK_API && isPositiveAnswer(process.env.NEXT_PUBLIC_USE_MOCK_API)
|
||||
|
||||
/**
|
||||
* Defines if the current runtime was built in development mode.
|
||||
* @type boolean
|
||||
*/
|
||||
const isDevMode = process.env.NODE_ENV === 'development'
|
||||
|
||||
module.exports = {
|
||||
isTestMode,
|
||||
isMockMode,
|
||||
isDevMode
|
||||
}
|
27
frontend/src/utils/uri-origin-boundary.tsx
Normal file
27
frontend/src/utils/uri-origin-boundary.tsx
Normal file
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import React, { Fragment, useMemo } from 'react'
|
||||
import type { PropsWithChildren } from 'react'
|
||||
import { isClientSideRendering } from './is-client-side-rendering'
|
||||
import { useBaseUrl } from '../hooks/common/use-base-url'
|
||||
|
||||
/**
|
||||
* Checks if the url of the current browser window matches the expected origin.
|
||||
* This is necessary to ensure that the render endpoint is only opened from the rendering origin.
|
||||
*
|
||||
* @param children The children react element that should be rendered if the origin is correct
|
||||
*/
|
||||
export const ExpectedOriginBoundary: React.FC<PropsWithChildren> = ({ children }) => {
|
||||
const baseUrl = useBaseUrl()
|
||||
const expectedOrigin = useMemo(() => new URL(baseUrl).origin, [baseUrl])
|
||||
|
||||
if (isClientSideRendering() && window.location.origin !== expectedOrigin) {
|
||||
return <span>{`You can't open this page using this URL. For this endpoint "${expectedOrigin}" is expected.`}</span>
|
||||
} else {
|
||||
return <Fragment>{children}</Fragment>
|
||||
}
|
||||
}
|
17
frontend/src/utils/wait-for-other-promises-to-finish.ts
Normal file
17
frontend/src/utils/wait-for-other-promises-to-finish.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
/**
|
||||
* Waits until all other pending promises are processed.
|
||||
*
|
||||
* NodeJS has a queue for async code that waits for being processed. This method adds a promise to the very end of this queue.
|
||||
* If the promise is resolved then this means that all other promises before it have been processed as well.
|
||||
*
|
||||
* @return A promise which resolves when all other promises have been processed
|
||||
*/
|
||||
export function waitForOtherPromisesToFinish(): Promise<void> {
|
||||
return new Promise((resolve) => process.nextTick(resolve))
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue