This commit is contained in:
Erik Michelson 2025-03-11 06:57:55 +01:00
parent 159a100b0d
commit efec25e4fb
No known key found for this signature in database
GPG key ID: DB99ADDDC5C0AF82
29 changed files with 25 additions and 1134 deletions

View file

@ -7,31 +7,10 @@
set -e set -e
cleanup () {
if [ -d ../tmp/src/pages/api ]; then
echo "🦔 > Moving Mock API files back"
mv ../tmp/src/pages/api src/pages
fi
}
trap cleanup EXIT
echo "🦔 Frontend Production Build" echo "🦔 Frontend Production Build"
echo "🦔 > Clearing existing builds" echo "🦔 > Clearing existing builds"
rm -rf dist/ rm -rf dist/
echo "🦔 > Preparing files"
if [ ! -z "${NEXT_PUBLIC_USE_MOCK_API}" ]; then
echo "🦔 > Keeping Mock API because NEXT_PUBLIC_USE_MOCK_API is set"
if [ ! -d src/pages/api ]; then
echo "🦔 > ⚠️ src/pages/api doesn't exist"
fi
else
echo "🦔 > Moving Mock API because NEXT_PUBLIC_USE_MOCK_API is unset"
mkdir -p ../tmp/src/pages
mv src/pages/api ../tmp/src/pages/
fi
echo "🦔 > Building" echo "🦔 > Building"
BUILD_TIME=true next build BUILD_TIME=true next build

View file

@ -1,6 +1,5 @@
/// <reference types="next" /> /// <reference types="next" />
/// <reference types="next/image-types/global" /> /// <reference types="next/image-types/global" />
/// <reference types="next/navigation-types/compat/navigation" />
// NOTE: This file should not be edited // NOTE: This file should not be edited
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information. // see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.

View file

@ -3,7 +3,7 @@
* *
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
const { isMockMode, isTestMode, isProfilingMode, isBuildTime } = require('./src/utils/test-modes') const { isTestMode, isProfilingMode, isBuildTime } = require('./src/utils/test-modes')
const path = require('path') const path = require('path')
const CopyWebpackPlugin = require('copy-webpack-plugin') const CopyWebpackPlugin = require('copy-webpack-plugin')
const withBundleAnalyzer = require('@next/bundle-analyzer')({ const withBundleAnalyzer = require('@next/bundle-analyzer')({
@ -21,14 +21,6 @@ if (isTestMode) {
`) `)
} }
if (isMockMode) {
console.warn(`This build runs in mock mode. This means:
- No real data. All API responses are mocked
- No persistent data
- No realtime editing
`)
}
if (isBuildTime) { if (isBuildTime) {
console.warn(`This process runs in build mode. During build time this means: console.warn(`This process runs in build mode. During build time this means:
- Editor and Renderer base urls are '' (empty string) - Editor and Renderer base urls are '' (empty string)

View file

@ -5,7 +5,6 @@
"license": "AGPL-3.0", "license": "AGPL-3.0",
"scripts": { "scripts": {
"build": "dotenv -v NODE_ENV=production -c production -- ./build.sh", "build": "dotenv -v NODE_ENV=production -c production -- ./build.sh",
"build:mock": "dotenv -v NEXT_PUBLIC_USE_MOCK_API=true -- ./build.sh",
"build:test": "dotenv -v NODE_ENV=test -v NEXT_PUBLIC_TEST_MODE=true -c test -- ./build.sh", "build:test": "dotenv -v NODE_ENV=test -v NEXT_PUBLIC_TEST_MODE=true -c test -- ./build.sh",
"analyze": "cross-env ANALYZE=true yarn build --profile", "analyze": "cross-env ANALYZE=true yarn build --profile",
"format": "prettier -c \"src/**/*.{ts,tsx,js}\" \"cypress/**/*.{ts,tsx}\"", "format": "prettier -c \"src/**/*.{ts,tsx,js}\" \"cypress/**/*.{ts,tsx}\"",
@ -14,7 +13,6 @@
"lint:fix": "eslint --fix --ext .ts,.tsx src", "lint:fix": "eslint --fix --ext .ts,.tsx src",
"start": "cross-env PORT=${HD_FRONTEND_PORT:-3001} node dist/frontend/server.js", "start": "cross-env PORT=${HD_FRONTEND_PORT:-3001} node dist/frontend/server.js",
"start:dev": "cross-env PORT=${HD_FRONTEND_PORT:-3001} next dev", "start:dev": "cross-env PORT=${HD_FRONTEND_PORT:-3001} next dev",
"start:dev:mock": "cross-env PORT=${HD_FRONTEND_PORT:-3001} NEXT_PUBLIC_USE_MOCK_API=true HD_BASE_URL=\"http://localhost:3001/\" HD_RENDERER_BASE_URL=\"http://127.0.0.1:3001/\" next dev",
"start:dev:test": "cross-env PORT=${HD_FRONTEND_PORT:-3001} NODE_ENV=test NEXT_PUBLIC_TEST_MODE=true next dev", "start:dev:test": "cross-env PORT=${HD_FRONTEND_PORT:-3001} NODE_ENV=test NEXT_PUBLIC_TEST_MODE=true next dev",
"test:e2e:open": "cypress open", "test:e2e:open": "cypress open",
"test:e2e": "cypress run --browser chrome", "test:e2e": "cypress run --browser chrome",

View file

@ -4,13 +4,11 @@
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import { useApplicationState } from '../../../../../hooks/common/use-application-state' import { useApplicationState } from '../../../../../hooks/common/use-application-state'
import { getGlobalState } from '../../../../../redux'
import { setRealtimeConnectionState } from '../../../../../redux/realtime/methods' import { setRealtimeConnectionState } from '../../../../../redux/realtime/methods'
import { Logger } from '../../../../../utils/logger' import { Logger } from '../../../../../utils/logger'
import { isMockMode } from '../../../../../utils/test-modes'
import { FrontendWebsocketAdapter } from './frontend-websocket-adapter' import { FrontendWebsocketAdapter } from './frontend-websocket-adapter'
import { useWebsocketUrl } from './use-websocket-url' import { useWebsocketUrl } from './use-websocket-url'
import { DisconnectReason, MessageTransporter, MockedBackendTransportAdapter } from '@hedgedoc/commons' import { DisconnectReason, MessageTransporter } from '@hedgedoc/commons'
import type { Listener } from 'eventemitter2' import type { Listener } from 'eventemitter2'
import { useCallback, useEffect, useMemo, useRef } from 'react' import { useCallback, useEffect, useMemo, useRef } from 'react'
@ -30,29 +28,25 @@ export const useRealtimeConnection = (): MessageTransporter => {
const reconnectCount = useRef(0) const reconnectCount = useRef(0)
const disconnectReason = useRef<DisconnectReason | undefined>(undefined) const disconnectReason = useRef<DisconnectReason | undefined>(undefined)
const establishWebsocketConnection = useCallback(() => { const establishWebsocketConnection = useCallback(() => {
if (isMockMode) { if (!websocketUrl) {
logger.debug('Creating Loopback connection...') return
messageTransporter.setAdapter(
new MockedBackendTransportAdapter(getGlobalState().noteDetails?.markdownContent.plain ?? '')
)
} else if (websocketUrl) {
logger.debug(`Connecting to ${websocketUrl.toString()}`)
const socket = new WebSocket(websocketUrl.toString())
socket.addEventListener('error', () => {
const timeout = WEBSOCKET_RECONNECT_INTERVAL + reconnectCount.current * 1000 + Math.random() * 1000
setTimeout(
() => {
reconnectCount.current += 1
establishWebsocketConnection()
},
Math.max(timeout, WEBSOCKET_RECONNECT_MAX_DURATION)
)
})
socket.addEventListener('open', () => {
messageTransporter.setAdapter(new FrontendWebsocketAdapter(socket))
})
} }
logger.debug(`Connecting to ${websocketUrl.toString()}`)
const socket = new WebSocket(websocketUrl.toString())
socket.addEventListener('error', () => {
const timeout = WEBSOCKET_RECONNECT_INTERVAL + reconnectCount.current * 1000 + Math.random() * 1000
setTimeout(
() => {
reconnectCount.current += 1
establishWebsocketConnection()
},
Math.max(timeout, WEBSOCKET_RECONNECT_MAX_DURATION)
)
})
socket.addEventListener('open', () => {
messageTransporter.setAdapter(new FrontendWebsocketAdapter(socket))
})
}, [messageTransporter, websocketUrl]) }, [messageTransporter, websocketUrl])
const isConnected = useApplicationState((state) => state.realtimeStatus.isConnected) const isConnected = useApplicationState((state) => state.realtimeStatus.isConnected)

View file

@ -5,11 +5,13 @@
*/ */
import { useApplicationState } from '../../../../../hooks/common/use-application-state' import { useApplicationState } from '../../../../../hooks/common/use-application-state'
import { useBaseUrl } from '../../../../../hooks/common/use-base-url' import { useBaseUrl } from '../../../../../hooks/common/use-base-url'
import { isMockMode } from '../../../../../utils/test-modes'
import { useMemo } from 'react' import { useMemo } from 'react'
import { Logger } from '../../../../../utils/logger'
const LOCAL_FALLBACK_URL = 'ws://localhost:8080/realtime/' const LOCAL_FALLBACK_URL = 'ws://localhost:8080/realtime/'
const logger = new Logger('WebsocketUrl')
/** /**
* Provides the URL for the realtime endpoint. * Provides the URL for the realtime endpoint.
*/ */
@ -18,16 +20,13 @@ export const useWebsocketUrl = (): URL | null => {
const baseUrl = useBaseUrl() const baseUrl = useBaseUrl()
const websocketUrl = useMemo(() => { const websocketUrl = useMemo(() => {
if (isMockMode) {
return LOCAL_FALLBACK_URL
}
try { try {
const backendBaseUrlParsed = new URL(baseUrl) const backendBaseUrlParsed = new URL(baseUrl)
backendBaseUrlParsed.protocol = backendBaseUrlParsed.protocol === 'https:' ? 'wss:' : 'ws:' backendBaseUrlParsed.protocol = backendBaseUrlParsed.protocol === 'https:' ? 'wss:' : 'ws:'
backendBaseUrlParsed.pathname += 'realtime' backendBaseUrlParsed.pathname += 'realtime'
return backendBaseUrlParsed.toString() return backendBaseUrlParsed.toString()
} catch (e) { } catch (e) {
console.error(e) logger.error(e)
return LOCAL_FALLBACK_URL return LOCAL_FALLBACK_URL
} }
}, [baseUrl]) }, [baseUrl])

View file

@ -1,74 +0,0 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { isMockMode, isTestMode } from '../utils/test-modes'
import type { NextApiRequest, NextApiResponse } from 'next'
export enum HttpMethod {
GET = 'GET',
POST = 'POST',
PUT = 'PUT',
DELETE = 'DELETE'
}
/**
* Intercepts a mock HTTP request, checks the used request method and responds with given response content or an error
* that the request method is not allowed.
*
* @param method The expected HTTP method.
* @param req The request object.
* @param res The response object.
* @param response The response data that will be returned when the HTTP method was the expected one.
* @param statusCode The status code with which the response will be sent.
* @param respondMethodNotAllowedOnMismatch If set and the method can't process the request then a 405 will be returned. Used for chaining multiple calls together.
* @return {@link true} if the HTTP method of the request is the expected one, {@link false} otherwise.
*/
export const respondToMatchingRequest = <T>(
method: HttpMethod,
req: NextApiRequest,
res: NextApiResponse,
response: T,
statusCode = 200,
respondMethodNotAllowedOnMismatch = true
): boolean => {
if (!isMockMode) {
res.status(404).send('Mock API is disabled')
return false
} else if (method === req.method) {
res.status(statusCode).json(response)
return true
} else if (respondMethodNotAllowedOnMismatch) {
res.status(405).send('Method not allowed')
return true
} else {
return false
}
}
/**
* Intercepts a mock HTTP request that is only allowed in test mode.
* Such requests can only be issued from localhost and only if mock API is activated.
*
* @param req The request object.
* @param res The response object.
* @param response The response data that will be returned when the HTTP method was the expected one.
*/
export const respondToTestRequest = <T>(req: NextApiRequest, res: NextApiResponse, response: () => T): boolean => {
if (!isMockMode) {
res.status(404).send('Mock API is disabled')
} else if (req.method !== HttpMethod.POST) {
res.status(405).send('Method not allowed')
} else if (!isTestMode) {
res.status(404).send('Route only available in test mode')
} else if (
req.socket.remoteAddress === undefined ||
!['127.0.0.1', '::1', '::ffff:127.0.0.1'].includes(req.socket.remoteAddress)
) {
res.status(403).send(`Request must come from localhost but was ${req.socket.remoteAddress}`)
} else {
res.status(200).json(response())
}
return true
}

View file

@ -1,13 +0,0 @@
/*
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { NextApiRequest, NextApiResponse } from 'next'
const handler = (req: NextApiRequest, res: NextApiResponse) => {
res.status(200).setHeader('Set-Cookie', ['mock-session=1; Path=/']).json({})
}
export default handler

View file

@ -1,15 +0,0 @@
/*
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { NextApiRequest, NextApiResponse } from 'next'
const handler = (req: NextApiRequest, res: NextApiResponse) => {
res.setHeader('Set-Cookie', 'mock-session=0; Path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT').status(200).json({
redirect: '/'
})
}
export default handler

View file

@ -1,79 +0,0 @@
/*
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { FrontendConfig } from '../../../api/config/types'
import { AuthProviderType, GuestAccessLevel } from '../../../api/config/types'
import {
HttpMethod,
respondToMatchingRequest,
respondToTestRequest
} from '../../../handler-utils/respond-to-matching-request'
import { isTestMode } from '../../../utils/test-modes'
import type { NextApiRequest, NextApiResponse } from 'next'
const initialConfig: FrontendConfig = {
allowRegister: true,
allowProfileEdits: true,
allowChooseUsername: true,
branding: {
name: 'DEMO Corp',
logo: '/public/img/demo.png'
},
guestAccess: GuestAccessLevel.WRITE,
useImageProxy: false,
specialUrls: {
privacy: 'https://example.com/privacy',
termsOfUse: 'https://example.com/termsOfUse',
imprint: 'https://example.com/imprint'
},
version: {
major: isTestMode ? 0 : 2,
minor: 0,
patch: 0,
preRelease: isTestMode ? undefined : '',
commit: 'mock'
},
plantumlServer: isTestMode ? 'http://mock-plantuml.local' : 'https://www.plantuml.com/plantuml',
maxDocumentLength: isTestMode ? 200 : 1000000,
authProviders: [
{
type: AuthProviderType.LOCAL
},
{
type: AuthProviderType.LDAP,
identifier: 'test-ldap',
providerName: 'Test LDAP'
},
{
type: AuthProviderType.OIDC,
identifier: 'test-oidc',
providerName: 'Test OIDC'
}
]
}
let currentConfig: FrontendConfig = initialConfig
const handler = (req: NextApiRequest, res: NextApiResponse) => {
const responseSuccessful = respondToMatchingRequest<FrontendConfig>(
HttpMethod.GET,
req,
res,
currentConfig,
200,
false
)
if (!responseSuccessful) {
respondToTestRequest<FrontendConfig>(req, res, () => {
currentConfig = {
...initialConfig,
...(req.body as FrontendConfig)
}
return currentConfig
})
}
}
export default handler

View file

@ -1,18 +0,0 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { GroupInfo } from '../../../../api/group/types'
import { HttpMethod, respondToMatchingRequest } from '../../../../handler-utils/respond-to-matching-request'
import type { NextApiRequest, NextApiResponse } from 'next'
const handler = (req: NextApiRequest, res: NextApiResponse) => {
respondToMatchingRequest<GroupInfo>(HttpMethod.GET, req, res, {
name: '_EVERYONE',
displayName: 'Everyone',
special: true
})
}
export default handler

View file

@ -1,18 +0,0 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { GroupInfo } from '../../../../api/group/types'
import { HttpMethod, respondToMatchingRequest } from '../../../../handler-utils/respond-to-matching-request'
import type { NextApiRequest, NextApiResponse } from 'next'
const handler = (req: NextApiRequest, res: NextApiResponse) => {
respondToMatchingRequest<GroupInfo>(HttpMethod.GET, req, res, {
name: '_LOGGED_IN',
displayName: 'All registered users',
special: true
})
}
export default handler

View file

@ -1,18 +0,0 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { GroupInfo } from '../../../../api/group/types'
import { HttpMethod, respondToMatchingRequest } from '../../../../handler-utils/respond-to-matching-request'
import type { NextApiRequest, NextApiResponse } from 'next'
const handler = (req: NextApiRequest, res: NextApiResponse) => {
respondToMatchingRequest<GroupInfo>(HttpMethod.GET, req, res, {
name: 'hedgedoc-devs',
displayName: 'HedgeDoc devs',
special: true
})
}
export default handler

View file

@ -1,47 +0,0 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { HistoryEntry } from '../../../../api/history/types'
import { HttpMethod, respondToMatchingRequest } from '../../../../handler-utils/respond-to-matching-request'
import type { NextApiRequest, NextApiResponse } from 'next'
const handler = (req: NextApiRequest, res: NextApiResponse) => {
respondToMatchingRequest<HistoryEntry[]>(HttpMethod.GET, req, res, [
{
identifier: 'slide-example',
title: 'Slide example',
lastVisitedAt: '2020-05-30T15:20:36.088Z',
pinStatus: true,
tags: ['features', 'cool', 'updated'],
owner: null
},
{
identifier: 'features',
title: 'Features',
lastVisitedAt: '2020-05-31T15:20:36.088Z',
pinStatus: true,
tags: ['features', 'cool', 'updated'],
owner: null
},
{
identifier: 'ODakLc2MQkyyFc_Xmb53sg',
title: 'Non existent',
lastVisitedAt: '2020-05-25T19:48:14.025Z',
pinStatus: false,
tags: [],
owner: null
},
{
identifier: 'l8JuWxApTR6Fqa0LCrpnLg',
title: 'Non existent',
lastVisitedAt: '2020-05-24T16:04:36.433Z',
pinStatus: false,
tags: ['agenda', 'HedgeDoc community', 'community call'],
owner: 'test'
}
])
}
export default handler

View file

@ -1,25 +0,0 @@
/*
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { LoginUserInfo } from '../../../../api/me/types'
import { HttpMethod, respondToMatchingRequest } from '../../../../handler-utils/respond-to-matching-request'
import type { NextApiRequest, NextApiResponse } from 'next'
const handler = (req: NextApiRequest, res: NextApiResponse): void => {
const cookieSet = req.headers?.['cookie']?.split(';').find((value) => value.trim() === 'mock-session=1') !== undefined
if (!cookieSet) {
res.status(403).json({})
return
}
respondToMatchingRequest<LoginUserInfo>(HttpMethod.GET, req, res, {
username: 'mock',
photoUrl: '/public/img/avatar.png',
displayName: 'Mock User',
authProvider: 'local',
email: 'mock@hedgedoc.test'
})
}
export default handler

View file

@ -1,29 +0,0 @@
/*
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { MediaUpload } from '../../../../api/media/types'
import { HttpMethod, respondToMatchingRequest } from '../../../../handler-utils/respond-to-matching-request'
import type { NextApiRequest, NextApiResponse } from 'next'
const handler = (req: NextApiRequest, res: NextApiResponse) => {
respondToMatchingRequest<MediaUpload[]>(HttpMethod.GET, req, res, [
{
username: 'tilman',
createdAt: '2022-03-20T20:36:32Z',
uuid: '5355ed83-7e12-4db0-95ed-837e124db08c',
fileName: 'dummy.png',
noteId: 'features'
},
{
username: 'tilman',
createdAt: '2022-03-20T20:36:57+0000',
uuid: '656745ab-fbf9-47f1-a745-abfbf9a7f10c',
fileName: 'dummy2.png',
noteId: null
}
])
}
export default handler

View file

@ -1,33 +0,0 @@
/*
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { MediaUpload } from '../../../api/media/types'
import { HttpMethod, respondToMatchingRequest } from '../../../handler-utils/respond-to-matching-request'
import { isMockMode, isTestMode } from '../../../utils/test-modes'
import type { NextApiRequest, NextApiResponse } from 'next'
const handler = async (req: NextApiRequest, res: NextApiResponse): Promise<void> => {
if (isMockMode && !isTestMode) {
await new Promise((resolve) => {
setTimeout(resolve, 3000)
})
}
respondToMatchingRequest<MediaUpload>(
HttpMethod.POST,
req,
res,
{
uuid: 'e81f57cd-5866-4253-9f57-cd5866a253ca',
fileName: 'avatar.png',
noteId: null,
username: 'test',
createdAt: '2022-02-27T21:54:23.856Z'
},
201
)
}
export default handler

File diff suppressed because one or more lines are too long

View file

@ -1,215 +0,0 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { RevisionDetails } from '../../../../../../api/revisions/types'
import { HttpMethod, respondToMatchingRequest } from '../../../../../../handler-utils/respond-to-matching-request'
import type { NextApiRequest, NextApiResponse } from 'next'
const handler = (req: NextApiRequest, res: NextApiResponse): void => {
respondToMatchingRequest<RevisionDetails>(HttpMethod.GET, req, res, {
id: 0,
createdAt: '2021-12-21T16:59:42.000Z',
title: 'Features',
description: 'Many features, such wow!',
tags: ['hedgedoc', 'demo', 'react'],
patch: `Index:
===================================================================
---
+++
@@ -0,0 +1,92 @@
+---
+title: Features
+description: Many features, such wow!
+robots: noindex
+tags: hedgedoc, demo, react
+opengraph:
+ title: Features
+---
+# Embedding demo
+[TOC]
+
+## some plain text
+
+Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
+
+## MathJax
+You can render *LaTeX* mathematical expressions using **MathJax**, as on [math.stackexchange.com](https://math.stackexchange.com/):
+
+The *Gamma function* satisfying $\\Gamma(n) = (n-1)!\\quad\\forall n\\in\\mathbb N$ is via the Euler integral
+
+$$
+x = {-b \\pm \\sqrt{b^2-4ac} \\over 2a}.
+$$
+
+$$
+\\Gamma(z) = \\int_0^\\infty t^{z-1}e^{-t}dt\\,.
+$$
+
+> More information about **LaTeX** mathematical expressions [here](https://meta.math.stackexchange.com/questions/5020/mathjax-basic-tutorial-and-quick-reference).
+
+## Blockquote
+> Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
+> Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
+> [color=red] [name=John Doe] [time=2020-06-21 22:50]
+
+## Slideshare
+{%slideshare mazlan1/internet-of-things-the-tip-of-an-iceberg %}
+
+## Gist
+https://gist.github.com/schacon/1
+
+## YouTube
+https://www.youtube.com/watch?v=KgMpKsp23yY
+
+## Vimeo
+https://vimeo.com/23237102
+
+## Asciinema
+https://asciinema.org/a/117928
+
+## PDF
+{%pdf https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf %}
+
+## Code highlighting
+\`\`\`javascript=
+
+let a = 1
+\`\`\`
+
+## PlantUML
+\`\`\`plantuml
+@startuml
+participant Alice
+participant "The **Famous** Bob" as Bob
+
+Alice -> Bob : hello --there--
+... Some ~~long delay~~ ...
+Bob -> Alice : ok
+note left
+ This is **bold**
+ This is //italics//
+ This is ""monospaced""
+ This is --stroked--
+ This is __underlined__
+ This is ~~waved~~
+end note
+
+Alice -> Bob : A //well formatted// message
+note right of Alice
+ This is <back:cadetblue><size:18>displayed</size></back>
+ __left of__ Alice.
+end note
+note left of Bob
+ <u:red>This</u> is <color #118888>displayed</color>
+ **<color purple>left of</color> <s:red>Alice</strike> Bob**.
+end note
+note over Alice, Bob
+ <w:#FF33FF>This is hosted</w> by <img sourceforge.jpg>
+end note
+@enduml
+\`\`\`
+
`,
edits: [],
length: 2782,
authorUsernames: [],
anonymousAuthorCount: 2,
content: `---
title: Features
description: Many features, such wow!
robots: noindex
tags: hedgedoc, demo, react
opengraph:
title: Features
---
# Embedding demo
[TOC]
## some plain text
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
## MathJax
You can render *LaTeX* mathematical expressions using **MathJax**, as on [math.stackexchange.com](https://math.stackexchange.com/):
The *Gamma function* satisfying $\\Gamma(n) = (n-1)!\\quad\\forall n\\in\\mathbb N$ is via the Euler integral
$$
x = {-b \\pm \\sqrt{b^2-4ac} \\over 2a}.
$$
$$
\\Gamma(z) = \\int_0^\\infty t^{z-1}e^{-t}dt\\,.
$$
> More information about **LaTeX** mathematical expressions [here](https://meta.math.stackexchange.com/questions/5020/mathjax-basic-tutorial-and-quick-reference).
## Blockquote
> Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
> Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
> [color=red] [name=John Doe] [time=2020-06-21 22:50]
## Slideshare
{%slideshare mazlan1/internet-of-things-the-tip-of-an-iceberg %}
## Gist
https://gist.github.com/schacon/1
## YouTube
https://www.youtube.com/watch?v=KgMpKsp23yY
## Vimeo
https://vimeo.com/23237102
## Asciinema
https://asciinema.org/a/117928
## PDF
{%pdf https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf %}
## Code highlighting
\`\`\`javascript=
let a = 1
\`\`\`
## PlantUML
\`\`\`plantuml
@startuml
participant Alice
participant "The **Famous** Bob" as Bob
Alice -> Bob : hello --there--
... Some ~~long delay~~ ...
Bob -> Alice : ok
note left
This is **bold**
This is //italics//
This is ""monospaced""
This is --stroked--
This is __underlined__
This is ~~waved~~
end note
Alice -> Bob : A //well formatted// message
note right of Alice
This is <back:cadetblue><size:18>displayed</size></back>
__left of__ Alice.
end note
note left of Bob
<u:red>This</u> is <color #118888>displayed</color>
**<color purple>left of</color> <s:red>Alice</strike> Bob**.
end note
note over Alice, Bob
<w:#FF33FF>This is hosted</w> by <img sourceforge.jpg>
end note
@enduml
\`\`\`
`
})
}
export default handler

View file

@ -1,163 +0,0 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { RevisionDetails } from '../../../../../../api/revisions/types'
import { HttpMethod, respondToMatchingRequest } from '../../../../../../handler-utils/respond-to-matching-request'
import type { NextApiRequest, NextApiResponse } from 'next'
const handler = (req: NextApiRequest, res: NextApiResponse): void => {
respondToMatchingRequest<RevisionDetails>(HttpMethod.GET, req, res, {
id: 1,
createdAt: '2021-12-29T17:54:11.000Z',
title: 'Features',
description: 'Many more features, such wow!',
tags: ['hedgedoc', 'demo', 'react'],
patch: `Index:
===================================================================
---
+++
@@ -1,7 +1,7 @@
---
title: Features
-description: Many features, such wow!
+description: Many more features, such wow!
robots: noindex
tags: hedgedoc, demo, react
opengraph:
title: Features
@@ -10,9 +10,9 @@
[TOC]
## some plain text
-Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
+Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magnus aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetezur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam _et_ justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
## MathJax
You can render *LaTeX* mathematical expressions using **MathJax**, as on [math.stackexchange.com](https://math.stackexchange.com/):
@@ -39,9 +39,9 @@
## Gist
https://gist.github.com/schacon/1
## YouTube
-https://www.youtube.com/watch?v=KgMpKsp23yY
+https://www.youtube.com/watch?v=zHAIuE5BQWk
## Vimeo
https://vimeo.com/23237102
@@ -62,9 +62,9 @@
@startuml
participant Alice
participant "The **Famous** Bob" as Bob
-Alice -> Bob : hello --there--
+Alice -> Bob : bye --there--
... Some ~~long delay~~ ...
Bob -> Alice : ok
note left
This is **bold**`,
edits: [],
length: 2788,
authorUsernames: [],
anonymousAuthorCount: 4,
content: `---
title: Features
description: Many more features, such wow!
robots: noindex
tags: hedgedoc, demo, react
opengraph:
title: Features
---
# Embedding demo
[TOC]
## some plain text
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magnus aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetezur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam _et_ justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
## MathJax
You can render *LaTeX* mathematical expressions using **MathJax**, as on [math.stackexchange.com](https://math.stackexchange.com/):
The *Gamma function* satisfying $\\Gamma(n) = (n-1)!\\quad\\forall n\\in\\mathbb N$ is via the Euler integral
$$
x = {-b \\pm \\sqrt{b^2-4ac} \\over 2a}.
$$
$$
\\Gamma(z) = \\int_0^\\infty t^{z-1}e^{-t}dt\\,.
$$
> More information about **LaTeX** mathematical expressions [here](https://meta.math.stackexchange.com/questions/5020/mathjax-basic-tutorial-and-quick-reference).
## Blockquote
> Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
> Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
> [color=red] [name=John Doe] [time=2020-06-21 22:50]
## Slideshare
{%slideshare mazlan1/internet-of-things-the-tip-of-an-iceberg %}
## Gist
https://gist.github.com/schacon/1
## YouTube
https://www.youtube.com/watch?v=zHAIuE5BQWk
## Vimeo
https://vimeo.com/23237102
## Asciinema
https://asciinema.org/a/117928
## PDF
{%pdf https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf %}
## Code highlighting
\`\`\`javascript=
let a = 1
\`\`\`
## PlantUML
\`\`\`plantuml
@startuml
participant Alice
participant "The **Famous** Bob" as Bob
Alice -> Bob : bye --there--
... Some ~~long delay~~ ...
Bob -> Alice : ok
note left
This is **bold**
This is //italics//
This is ""monospaced""
This is --stroked--
This is __underlined__
This is ~~waved~~
end note
Alice -> Bob : A //well formatted// message
note right of Alice
This is <back:cadetblue><size:18>displayed</size></back>
__left of__ Alice.
end note
note left of Bob
<u:red>This</u> is <color #118888>displayed</color>
**<color purple>left of</color> <s:red>Alice</strike> Bob**.
end note
note over Alice, Bob
<w:#FF33FF>This is hosted</w> by <img sourceforge.jpg>
end note
@enduml
\`\`\`
`
})
}
export default handler

View file

@ -1,35 +0,0 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { RevisionMetadata } from '../../../../../../api/revisions/types'
import { HttpMethod, respondToMatchingRequest } from '../../../../../../handler-utils/respond-to-matching-request'
import type { NextApiRequest, NextApiResponse } from 'next'
const handler = (req: NextApiRequest, res: NextApiResponse): void => {
respondToMatchingRequest<RevisionMetadata[]>(HttpMethod.GET, req, res, [
{
id: 1,
createdAt: '2021-12-29T17:54:11.000Z',
length: 2788,
authorUsernames: [],
anonymousAuthorCount: 4,
title: 'Features',
description: 'Many features, such wow!',
tags: ['hedgedoc', 'demo', 'react']
},
{
id: 0,
createdAt: '2021-12-21T16:59:42.000Z',
length: 2782,
authorUsernames: [],
anonymousAuthorCount: 2,
title: 'Features',
description: 'Many more features, such wow!',
tags: ['hedgedoc', 'demo', 'react']
}
])
}
export default handler

View file

@ -1,58 +0,0 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { Note } from '../../../../api/notes/types'
import { HttpMethod, respondToMatchingRequest } from '../../../../handler-utils/respond-to-matching-request'
import type { NextApiRequest, NextApiResponse } from 'next'
const handler = (req: NextApiRequest, res: NextApiResponse): void => {
respondToMatchingRequest<Note>(
HttpMethod.POST,
req,
res,
{
content: 'new note content',
metadata: {
id: 'featuresId',
version: 2,
viewCount: 0,
updatedAt: '2021-04-24T09:27:51.000Z',
createdAt: '2021-04-24T09:27:51.000Z',
updateUsername: null,
primaryAddress: 'features',
editedBy: [],
title: 'New note',
tags: ['hedgedoc', 'demo', 'react'],
description: 'Many features, such wow!',
aliases: [
{
name: 'features',
primaryAlias: true,
noteId: 'featuresId'
}
],
permissions: {
owner: 'tilman',
sharedToUsers: [
{
username: 'molly',
canEdit: true
}
],
sharedToGroups: [
{
groupName: '_LOGGED_IN',
canEdit: false
}
]
}
},
editedByAtPosition: []
},
201
)
}
export default handler

File diff suppressed because one or more lines are too long

View file

@ -1,29 +0,0 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { AccessToken } from '../../../api/tokens/types'
import { HttpMethod, respondToMatchingRequest } from '../../../handler-utils/respond-to-matching-request'
import type { NextApiRequest, NextApiResponse } from 'next'
const handler = (req: NextApiRequest, res: NextApiResponse) => {
respondToMatchingRequest<AccessToken[]>(HttpMethod.GET, req, res, [
{
label: 'Demo-App',
keyId: 'demo',
createdAt: '2021-11-20T23:54:13+01:00',
lastUsedAt: '2021-11-20T23:54:13+01:00',
validUntil: '2022-11-20'
},
{
label: 'CLI @ Test-PC',
keyId: 'cli',
createdAt: '2021-11-20T23:54:13+01:00',
lastUsedAt: '2021-11-20T23:54:13+01:00',
validUntil: '2021-11-20'
}
])
}
export default handler

View file

@ -1,18 +0,0 @@
/*
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { UserInfo } from '../../../../../api/users/types'
import { HttpMethod, respondToMatchingRequest } from '../../../../../handler-utils/respond-to-matching-request'
import type { NextApiRequest, NextApiResponse } from 'next'
const handler = (req: NextApiRequest, res: NextApiResponse): void => {
respondToMatchingRequest<UserInfo>(HttpMethod.GET, req, res, {
username: 'erik',
displayName: 'Erik',
photoUrl: '/public/img/avatar.png'
})
}
export default handler

View file

@ -1,18 +0,0 @@
/*
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { UserInfo } from '../../../../../api/users/types'
import { HttpMethod, respondToMatchingRequest } from '../../../../../handler-utils/respond-to-matching-request'
import type { NextApiRequest, NextApiResponse } from 'next'
const handler = (req: NextApiRequest, res: NextApiResponse): void => {
respondToMatchingRequest<UserInfo>(HttpMethod.GET, req, res, {
username: 'mock',
displayName: 'Mock User',
photoUrl: ''
})
}
export default handler

View file

@ -1,18 +0,0 @@
/*
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { UserInfo } from '../../../../../api/users/types'
import { HttpMethod, respondToMatchingRequest } from '../../../../../handler-utils/respond-to-matching-request'
import type { NextApiRequest, NextApiResponse } from 'next'
const handler = (req: NextApiRequest, res: NextApiResponse): void => {
respondToMatchingRequest<UserInfo>(HttpMethod.GET, req, res, {
username: 'molly',
displayName: 'Molly',
photoUrl: '/public/img/avatar.png'
})
}
export default handler

View file

@ -1,18 +0,0 @@
/*
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { UserInfo } from '../../../../../api/users/types'
import { HttpMethod, respondToMatchingRequest } from '../../../../../handler-utils/respond-to-matching-request'
import type { NextApiRequest, NextApiResponse } from 'next'
const handler = (req: NextApiRequest, res: NextApiResponse): void => {
respondToMatchingRequest<UserInfo>(HttpMethod.GET, req, res, {
username: 'tilman',
displayName: 'Tilman',
photoUrl: '/public/img/avatar.png'
})
}
export default handler

View file

@ -4,7 +4,7 @@
* SPDX-License-Identifier: AGPL-3.0-only * 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` * This file is intentionally a js and not a ts file because it is used in `next.config.js`
*/ */
@ -25,12 +25,6 @@ const isPositiveAnswer = (value) => {
*/ */
const isTestMode = !!process.env.NEXT_PUBLIC_TEST_MODE && isPositiveAnswer(process.env.NEXT_PUBLIC_TEST_MODE) 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. * Defines if the current runtime was built in development mode.
* @type boolean * @type boolean
@ -52,7 +46,6 @@ const isBuildTime = !!process.env.BUILD_TIME && isPositiveAnswer(process.env.BUI
module.exports = { module.exports = {
isTestMode, isTestMode,
isMockMode,
isDevMode, isDevMode,
isProfilingMode, isProfilingMode,
isBuildTime isBuildTime