Restructures + New Evironment Variables (#1230)

* Use document base uri for react router

Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>

* Rename getAndSetUser to fetchAndSetUser

Getter should be reserved for simple get functions.
Everything that does a bit more logic should use a more meaningful verb.

Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>

* Rename getFrontPageContent to fetchFrontPageContent

Getter should be reserved for simple get functions.
Everything that does a bit more logic should use a more meaningful verb.

Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>

* Reformat code

Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>

* Use PUBLIC_URL env var in index.html

Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>

* Introduce new environment variables

For better testing (especially if you have multiple endpoints) this commit introduces
REACT_APP_BACKEND_BASE_URL, REACT_APP_FRONTEND_ASSETS_URL and REACT_APP_CUSTOMIZE_ASSETS_URL

Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>

* Remove redundant license information

Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>

* Remove redundant license information

Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>

* Fix rebase issues

Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>

* Remove unused file

Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>

* Correct parameter

Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>

* Fix run tasks

Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>

* Force use of bash

Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>

* Fix link to cypress picture

Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>

* revert change

Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>

* fix url

Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>

* Remove license info

Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>

* Revert rebase issues

Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>

* Add missing banner code

Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>

* Fix test url

Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>

* Useless change to trigger github

Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>

* Don't set backend base url because this break the mock mode detection

Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>

Co-authored-by: Philip Molares <philip.molares@udo.edu>
This commit is contained in:
Tilman Vatteroth 2021-05-02 22:38:43 +02:00 committed by GitHub
parent 9cf7980334
commit 2c5a03b3ee
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
52 changed files with 193 additions and 162 deletions

View file

@ -40,4 +40,4 @@ jobs:
- name: Test Project
run: yarn test
- name: Build project
run: yarn build:production
run: yarn build:mock

View file

@ -3,7 +3,11 @@ Upstream-Name: react-client
Upstream-Contact: The HedgeDoc developers <license@hedgedoc.org>
Source: https://github.com/hedgedoc/react-client
Files: public/api/*
Files: public/mock-backend/public/*
Copyright: 2021 The HedgeDoc developers (see AUTHORS file)
License: CC0-1.0
Files: public/mock-backend/api/*
Copyright: 2021 The HedgeDoc developers (see AUTHORS file)
License: CC0-1.0
@ -15,11 +19,7 @@ Files: public/locales/*
Copyright: 2021 The HedgeDoc developers (see AUTHORS file)
License: CC-BY-SA-4.0
Files: public/img/*
Copyright: 2021 The HedgeDoc developers (see AUTHORS file)
License: CC0-1.0
Files: public/img/highres.jpg
Files: public/mock-backend/public/img/highres.jpg
Copyright: Vincent van Gogh
License: CC0-1.0
@ -27,10 +27,6 @@ Files: public/index.html
Copyright: 2021 The HedgeDoc developers (see AUTHORS file)
License: CC0-1.0
Files: public/intro.md
Copyright: 2021 The HedgeDoc developers (see AUTHORS file)
License: CC0-1.0
Files: public/robots.txt
Copyright: 2021 The HedgeDoc developers (see AUTHORS file)
License: CC0-1.0

View file

@ -65,4 +65,3 @@ You can inspect the generated production-bundle files to look for optimization i
This will build the app in production mode and save it into the `build` folder.
The production build is optimized for best performance, minimized
and the filenames include a hash value of the content. Don't edit them by hand!

View file

@ -31,7 +31,7 @@ describe('File upload', () => {
beforeEach(() => {
cy.intercept({
method: 'POST',
url: '/api/private/media/upload'
url: '/mock-backend/api/private/media/upload'
}, {
statusCode: 201,
body: {
@ -86,7 +86,7 @@ describe('File upload', () => {
it('upload fails', () => {
cy.intercept({
method: 'POST',
url: '/api/private/media/upload'
url: '/mock-backend/api/private/media/upload'
}, {
statusCode: 400
})

View file

@ -26,7 +26,7 @@ describe('History', () => {
describe('Pinning', () => {
describe('working', () => {
beforeEach(() => {
cy.intercept('PUT', '/api/private/me/history/features', (req) => {
cy.intercept('PUT', '/mock-backend/api/private/me/history/features', (req) => {
req.reply(200, req.body)
})
})
@ -60,7 +60,7 @@ describe('History', () => {
describe('failing', () => {
beforeEach(() => {
cy.intercept('PUT', '/api/private/me/history/features', {
cy.intercept('PUT', '/mock-backend/api/private/me/history/features', {
statusCode: 401
})
})

View file

@ -7,7 +7,7 @@
/* eslint-disable @typescript-eslint/no-unsafe-call */
describe('Intro page', () => {
beforeEach(() => {
cy.intercept('/intro.md', 'test content')
cy.intercept('/mock-backend/public/intro.md', 'test content')
cy.visit('/')
})

View file

@ -7,7 +7,7 @@
describe('profile page', () => {
beforeEach(() => {
cy.intercept({
url: '/api/private/tokens',
url: '/mock-backend/api/private/tokens',
method: 'GET'
}, {
body: [
@ -18,7 +18,7 @@ describe('profile page', () => {
]
})
cy.intercept({
url: '/api/private/tokens',
url: '/mock-backend/api/private/tokens',
method: 'POST'
}, {
body: {
@ -28,7 +28,7 @@ describe('profile page', () => {
}
})
cy.intercept({
url: '/api/private/tokens/1601991518',
url: '/mock-backend/api/private/tokens/1601991518',
method: 'DELETE'
}, {
body: []

View file

@ -83,7 +83,7 @@ describe('When logged-out ', () => {
cy.get('[data-cy=sign-in-button]')
.should('be.visible')
// The absolute URL is used because it is defined as API base URL absolute.
.should('have.attr', 'href', 'http://127.0.0.1:3001/api/private/auth/saml')
.should('have.attr', 'href', '/mock-backend/api/private/auth/saml')
})
})

View file

@ -17,7 +17,7 @@ export const banner = {
export const branding = {
name: 'DEMO Corp',
logo: '/img/demo.png'
logo: '/mock-backend/public/img/demo.png'
}
export const authProviders = {
@ -63,7 +63,7 @@ export const config = {
}
Cypress.Commands.add('loadConfig', (additionalConfig?: Partial<typeof config>) => {
return cy.intercept('/api/private/config', {
return cy.intercept('/mock-backend/api/private/config', {
statusCode: 200,
body: {
...config,

View file

@ -17,7 +17,7 @@ Cypress.Commands.add('visitTestEditor', (query?: string) => {
})
beforeEach(() => {
cy.intercept(`/api/private/notes/${ testNoteId }-get`, {
cy.intercept(`/mock-backend/api/private/notes/${ testNoteId }-get`, {
"content": "",
"metadata": {
"id": "ABC11",

View file

@ -109,12 +109,13 @@
},
"scripts": {
"start": "cross-env PORT=3001 craco start",
"start:test": "cross-env PORT=3001 REACT_APP_TEST_MODE=true craco start",
"start:for-real-backend": "cross-env REACT_APP_BACKEND=http://localhost:3000 yarn start",
"start:test": "cross-env REACT_APP_TEST_MODE=true yarn start",
"start:for-real-backend": "cross-env REACT_APP_BACKEND_BASE_URL=http://localhost:3000 yarn start",
"serve:build": "http-server build/ -s -p 3001 -P \"http://localhost:3001?\"",
"build:test": "cross-env REACT_APP_TEST_MODE=true craco build",
"build:production": "craco build",
"analyze": "cross-env ANALYZE=true craco build",
"build:mock": "cross-env craco build",
"build:production": "bash -c \"[[ -v REACT_APP_BACKEND_BASE_URL ]]\" && (cross-env craco build) || (echo -e '\n==============\nREACT_APP_BACKEND_BASE_URL not set.\nUse this task only if you want to create a production build with a real backend. Otherwise use build:mock\n==============\n'; exit 1)",
"analyze": "cross-env ANALYZE=true yarn build:mock",
"test": "craco test",
"lint": "eslint --max-warnings=0 --ext .ts,.tsx src",
"eject": "react-scripts eject",

File diff suppressed because one or more lines are too long

View file

@ -1,3 +0,0 @@
SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
SPDX-License-Identifier: CC0-1.0

View file

@ -3,19 +3,20 @@
<head>
<meta charset="utf-8"/>
<title>HedgeDoc</title>
<link rel="apple-touch-icon" sizes="180x180" href="/icons/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/icons/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/icons/favicon-16x16.png">
<link rel="manifest" href="/icons/site.webmanifest">
<link rel="mask-icon" href="/icons/safari-pinned-tab.svg" color="#b51f08">
<link rel="shortcut icon" href="/icons/favicon.ico">
<link href="%PUBLIC_URL%/icons/apple-touch-icon.png" rel="apple-touch-icon" sizes="180x180">
<link href="%PUBLIC_URL%/icons/favicon-32x32.png" rel="icon" sizes="32x32" type="image/png">
<link href="%PUBLIC_URL%/icons/favicon-16x16.png" rel="icon" sizes="16x16" type="image/png">
<link href="%PUBLIC_URL%/icons/site.webmanifest" rel="manifest">
<link color="#b51f08" href="%PUBLIC_URL%/icons/safari-pinned-tab.svg" rel="mask-icon">
<link href="%PUBLIC_URL%/icons/favicon.ico" rel="shortcut icon">
<meta name="apple-mobile-web-app-title" content="HedgeDoc">
<meta name="application-name" content="HedgeDoc">
<meta name="msapplication-TileColor" content="#b51f08">
<meta name="msapplication-config" content="/icons/browserconfig.xml">
<meta content="%PUBLIC_URL%/icons/browserconfig.xml" name="msapplication-config">
<meta name="theme-color" content="#b51f08">
<meta content="width=device-width, initial-scale=1" name="viewport"/>
<meta content="HedgeDoc - Collaborative markdown notes" name="description"/>
<base href="%PUBLIC_URL%/"/>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>

View file

@ -16,11 +16,11 @@
"allowRegister": true,
"branding": {
"name": "DEMO Corp",
"logo": "/img/demo.png"
"logo": "/mock-backend/public/img/demo.png"
},
"banner": {
"text": "This is the test banner text",
"timestamp": "2020-05-22T20:46:08.962Z"
"text": "This is the test banner text",
"timestamp": "2020-05-22T20:46:08.962Z"
},
"customAuthNames": {
"ldap": "FooBar",

View file

@ -1,6 +1,6 @@
{
"id": "mockUser",
"photo": "/img/avatar.png",
"photo": "/mock-backend/public/img/avatar.png",
"name": "Test",
"status": "ok",
"provider": "internal"

File diff suppressed because one or more lines are too long

View file

@ -1,6 +1,6 @@
{
"id": "dermolly",
"photo": "/img/avatar.png",
"photo": "/mock-backend/public/img/avatar.png",
"name": "Philip",
"status": "ok",
"provider": "internal"

View file

@ -1,6 +1,6 @@
{
"id": "emcrx",
"photo": "/img/avatar.png",
"photo": "/mock-backend/public/img/avatar.png",
"name": "Erik",
"status": "ok",
"provider": "internal"

View file

@ -1,6 +1,6 @@
{
"id": "mrdrogdrog",
"photo": "/img/avatar.png",
"photo": "/mock-backend/public/img/avatar.png",
"name": "Tilman",
"status": "ok",
"provider": "internal"

View file

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View file

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View file

Before

Width:  |  Height:  |  Size: 3.9 MiB

After

Width:  |  Height:  |  Size: 3.9 MiB

View file

@ -2,4 +2,4 @@
What you see is an UI-Test! It's filled with dummy data, not connected to a backend and no data will be saved.
:::
![HedgeDoc Screenshot](screenshot.png)
![HedgeDoc Screenshot](/mock-backend/public/screenshot.png)

View file

Before

Width:  |  Height:  |  Size: 250 KiB

After

Width:  |  Height:  |  Size: 250 KiB

View file

@ -1,3 +0,0 @@
SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
SPDX-License-Identifier: CC0-1.0

View file

@ -1,21 +1,24 @@
/*
SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
SPDX-License-Identifier: AGPL-3.0-only
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import React, { Suspense, useCallback, useEffect, useState } from 'react'
import { useFrontendBaseUrl } from '../../hooks/common/use-frontend-base-url'
import { useBackendBaseUrl } from '../../hooks/common/use-backend-base-url'
import './application-loader.scss'
import { createSetUpTaskList, InitTask } from './initializers'
import { LoadingScreen } from './loading-screen'
import { useCustomizeAssetsUrl } from '../../hooks/common/use-customize-assets-url'
import { useFrontendAssetsUrl } from '../../hooks/common/use-frontend-assets-url'
export const ApplicationLoader: React.FC = ({ children }) => {
const frontendUrl = useFrontendBaseUrl()
const frontendAssetsUrl = useFrontendAssetsUrl()
const backendBaseUrl = useBackendBaseUrl()
const customizeAssetsUrl = useCustomizeAssetsUrl()
const setUpTasks = useCallback(() => {
return createSetUpTaskList(frontendUrl)
}, [frontendUrl])
const setUpTasks = useCallback(() => createSetUpTaskList(frontendAssetsUrl, customizeAssetsUrl, backendBaseUrl),
[backendBaseUrl, customizeAssetsUrl, frontendAssetsUrl])
const [failedTitle, setFailedTitle] = useState<string>('')
const [doneTasks, setDoneTasks] = useState<number>(0)

View file

@ -5,16 +5,10 @@
*/
import { getConfig } from '../../../api/config'
import { setApiUrl } from '../../../redux/api-url/methods'
import { setBanner } from '../../../redux/banner/methods'
import { setConfig } from '../../../redux/config/methods'
import { getAndSetUser } from '../../login-page/auth/utils'
export const loadAllConfig: (baseUrl: string) => Promise<void> = async (baseUrl) => {
setApiUrl({
apiUrl: (process.env.REACT_APP_BACKEND || baseUrl) + '/api/private'
})
export const fetchFrontendConfig = async (): Promise<void> => {
const config = await getConfig()
if (!config) {
return Promise.reject(new Error('Config empty!'))
@ -29,6 +23,4 @@ export const loadAllConfig: (baseUrl: string) => Promise<void> = async (baseUrl)
show: banner.text !== '' && banner.timestamp !== lastAcknowledgedTimestamp
})
}
await getAndSetUser()
}

View file

@ -10,7 +10,7 @@ import Backend from 'i18next-http-backend'
import { Settings } from 'luxon'
import { initReactI18next } from 'react-i18next'
export const setUpI18n = async (): Promise<void> => {
export const setUpI18n = async (frontendAssetsUrl: string): Promise<void> => {
await i18n
.use(Backend)
.use(LanguageDetector)
@ -19,7 +19,7 @@ export const setUpI18n = async (): Promise<void> => {
fallbackLng: 'en',
debug: process.env.NODE_ENV !== 'production',
backend: {
loadPath: '/locales/{{lng}}.json'
loadPath: `${ frontendAssetsUrl }/locales/{{lng}}.json`
},
interpolation: {

View file

@ -4,9 +4,11 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { loadAllConfig } from './configLoader'
import { setUpI18n } from './i18n'
import { refreshHistoryState } from '../../../redux/history/methods'
import { setApiUrl } from '../../../redux/api-url/methods'
import { fetchAndSetUser } from '../../login-page/auth/utils'
import { fetchFrontendConfig } from './fetch-frontend-config'
const customDelay: () => Promise<void> = async () => {
if (window.localStorage.getItem('customDelay')) {
@ -21,13 +23,20 @@ export interface InitTask {
task: Promise<void>
}
export const createSetUpTaskList = (baseUrl: string): InitTask[] => {
export const createSetUpTaskList = (frontendAssetsUrl: string, customizeAssetsUrl: string, backendBaseUrl: string): InitTask[] => {
setApiUrl({
apiUrl: `${ backendBaseUrl }/api/private`
})
return [{
name: 'Load Translations',
task: setUpI18n()
task: setUpI18n(frontendAssetsUrl)
}, {
name: 'Load config',
task: loadAllConfig(baseUrl)
task: fetchFrontendConfig()
}, {
name: 'Fetch user information',
task: fetchAndSetUser()
}, {
name: 'Load history state',
task: refreshHistoryState()

View file

@ -6,8 +6,8 @@
import { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { getFrontPageContent } from '../requests'
import { useFrontendBaseUrl } from '../../../hooks/common/use-frontend-base-url'
import { fetchFrontPageContent } from '../requests'
import { useCustomizeAssetsUrl } from '../../../hooks/common/use-customize-assets-url'
const MARKDOWN_WHILE_LOADING = ':zzz: {message}'
const MARKDOWN_IF_ERROR = ':::danger\n' +
@ -17,13 +17,13 @@ const MARKDOWN_IF_ERROR = ':::danger\n' +
export const useIntroPageContent = (): string => {
const { t } = useTranslation()
const [content, setContent] = useState<string>(() => MARKDOWN_WHILE_LOADING.replace('{message}', t('landing.intro.markdownWhileLoading')))
const frontendBaseUrl = useFrontendBaseUrl()
const customizeAssetsUrl = useCustomizeAssetsUrl()
useEffect(() => {
getFrontPageContent(frontendBaseUrl)
fetchFrontPageContent(customizeAssetsUrl)
.then((content) => setContent(content))
.catch(() => setContent(MARKDOWN_IF_ERROR.replace('{message}', t('landing.intro.markdownLoadingError'))))
}, [frontendBaseUrl, t])
}, [customizeAssetsUrl, t])
return content
}

View file

@ -6,8 +6,8 @@
import { defaultFetchConfig, expectResponseCode } from '../../api/utils'
export const getFrontPageContent = async (baseUrl: string): Promise<string> => {
const response = await fetch(baseUrl + '/intro.md', {
export const fetchFrontPageContent = async (customizeAssetsUrl: string): Promise<string> => {
const response = await fetch(customizeAssetsUrl + '/intro.md', {
...defaultFetchConfig,
method: 'GET'
})

View file

@ -26,7 +26,7 @@ export const PoweredByLinks: React.FC = () => {
<ExternalLink href={ links.webpage } text="HedgeDoc"/>
</Trans>
&nbsp;|&nbsp;
<TranslatedInternalLink href='/n/release-notes' i18nKey='landing.footer.releases'/>
<TranslatedInternalLink href="/n/release-notes" i18nKey="landing.footer.releases"/>
{
specialUrls.map(([i18nKey, href]) =>
<Fragment key={ i18nKey }>

View file

@ -7,7 +7,7 @@
import { getMe } from '../../../api/me'
import { setUser } from '../../../redux/user/methods'
export const getAndSetUser: () => (Promise<void>) = async () => {
export const fetchAndSetUser: () => (Promise<void>) = async () => {
const me = await getMe()
setUser({
id: me.id,

View file

@ -12,7 +12,7 @@ import { Link } from 'react-router-dom'
import { doInternalLogin } from '../../../api/auth'
import { ApplicationState } from '../../../redux'
import { ShowIf } from '../../common/show-if/show-if'
import { getAndSetUser } from './utils'
import { fetchAndSetUser } from './utils'
export const ViaInternal: React.FC = () => {
const { t } = useTranslation()
@ -23,7 +23,7 @@ export const ViaInternal: React.FC = () => {
const onLoginSubmit = useCallback((event: FormEvent) => {
doInternalLogin(username, password)
.then(() => getAndSetUser())
.then(() => fetchAndSetUser())
.catch(() => setError(true))
event.preventDefault()
}, [username, password])

View file

@ -1,7 +1,7 @@
/*
SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
SPDX-License-Identifier: AGPL-3.0-only
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import React, { FormEvent, useCallback, useState } from 'react'
@ -11,7 +11,7 @@ import { Trans, useTranslation } from 'react-i18next'
import { useSelector } from 'react-redux'
import { doLdapLogin } from '../../../api/auth'
import { ApplicationState } from '../../../redux'
import { getAndSetUser } from './utils'
import { fetchAndSetUser } from './utils'
export const ViaLdap: React.FC = () => {
const { t } = useTranslation()
@ -25,7 +25,7 @@ export const ViaLdap: React.FC = () => {
const onLoginSubmit = useCallback((event: FormEvent) => {
doLdapLogin(username, password)
.then(() => getAndSetUser())
.then(() => fetchAndSetUser())
.catch(() => setError(true))
event.preventDefault()
}, [username, password])

View file

@ -1,14 +1,14 @@
/*
SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
SPDX-License-Identifier: AGPL-3.0-only
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import React, { FormEvent, useState } from 'react'
import { Alert, Button, Card, Form } from 'react-bootstrap'
import { Trans, useTranslation } from 'react-i18next'
import { doOpenIdLogin } from '../../../api/auth'
import { getAndSetUser } from './utils'
import { fetchAndSetUser } from './utils'
export const ViaOpenId: React.FC = () => {
useTranslation()
@ -16,7 +16,7 @@ export const ViaOpenId: React.FC = () => {
const [error, setError] = useState(false)
const doAsyncLogin: (() => Promise<void>) = async () => {
await doOpenIdLogin(openId)
await getAndSetUser()
await fetchAndSetUser()
}
const onFormSubmit = (event: FormEvent) => {

View file

@ -7,6 +7,7 @@
import React, { Fragment, useCallback, useEffect, useRef, useState } from 'react'
import { Alert } from 'react-bootstrap'
import { ShowIf } from '../../../common/show-if/show-if'
import { useFrontendBaseUrl } from '../../../../hooks/common/use-frontend-base-url'
export interface GraphvizFrameProps {
code: string
@ -26,6 +27,8 @@ export const GraphvizFrame: React.FC<GraphvizFrameProps> = ({ code }) => {
.forEach(child => child.remove())
}, [])
const frontendBaseUrl = useFrontendBaseUrl()
useEffect(() => {
if (!container.current) {
return
@ -34,7 +37,7 @@ export const GraphvizFrame: React.FC<GraphvizFrameProps> = ({ code }) => {
import(/* webpackChunkName: "d3-graphviz" */'@hpcc-js/wasm')
.then((wasmPlugin) => {
wasmPlugin.wasmFolder('/static/js')
wasmPlugin.wasmFolder(`${ frontendBaseUrl }/static/js`)
})
.then(() => import(/* webpackChunkName: "d3-graphviz" */ 'd3-graphviz'))
.then((graphvizImport) => {
@ -53,7 +56,7 @@ export const GraphvizFrame: React.FC<GraphvizFrameProps> = ({ code }) => {
.catch(() => {
console.error('error while loading graphviz')
})
}, [code, error, showError])
}, [code, error, frontendBaseUrl, showError])
return (
<Fragment>

View file

@ -1,7 +1,7 @@
/*
SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
SPDX-License-Identifier: AGPL-3.0-only
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import React, { ChangeEvent, FormEvent, useEffect, useState } from 'react'
@ -10,7 +10,7 @@ import { Trans, useTranslation } from 'react-i18next'
import { useSelector } from 'react-redux'
import { updateDisplayName } from '../../../api/me'
import { ApplicationState } from '../../../redux'
import { getAndSetUser } from '../../login-page/auth/utils'
import { fetchAndSetUser } from '../../login-page/auth/utils'
export const ProfileDisplayName: React.FC = () => {
const regexInvalidDisplayName = /^\s*$/
@ -37,7 +37,7 @@ export const ProfileDisplayName: React.FC = () => {
const doAsyncChange = async () => {
await updateDisplayName(displayName)
await getAndSetUser()
await fetchAndSetUser()
}
const changeNameSubmit = (event: FormEvent) => {

View file

@ -1,7 +1,7 @@
/*
SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
SPDX-License-Identifier: AGPL-3.0-only
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import React, { FormEvent, Fragment, useCallback, useEffect, useState } from 'react'
@ -13,8 +13,7 @@ import { doInternalRegister } from '../../api/auth'
import { ApplicationState } from '../../redux'
import { TranslatedExternalLink } from '../common/links/translated-external-link'
import { ShowIf } from '../common/show-if/show-if'
import { getAndSetUser } from '../login-page/auth/utils'
import { SpecialUrls } from '../../api/config/types'
import { fetchAndSetUser } from '../login-page/auth/utils'
export enum RegisterError {
NONE = 'none',
@ -25,7 +24,7 @@ export enum RegisterError {
export const RegisterPage: React.FC = () => {
const { t } = useTranslation()
const allowRegister = useSelector((state: ApplicationState) => state.config.allowRegister)
const specialUrls: SpecialUrls = useSelector((state: ApplicationState) => state.config.specialUrls)
const specialUrls = useSelector((state: ApplicationState) => state.config.specialUrls)
const userExists = useSelector((state: ApplicationState) => !!state.user)
const [username, setUsername] = useState('')
@ -36,7 +35,7 @@ export const RegisterPage: React.FC = () => {
const doRegisterSubmit = useCallback((event: FormEvent) => {
doInternalRegister(username, password)
.then(() => getAndSetUser())
.then(() => fetchAndSetUser())
.catch((err: Error) => {
console.error(err)
setError(err.message === RegisterError.USERNAME_EXISTING ? err.message : RegisterError.OTHER)
@ -61,83 +60,84 @@ export const RegisterPage: React.FC = () => {
}
return <Fragment>
<div className='my-3'>
<h1 className='mb-4'><Trans i18nKey='login.register.title'/></h1>
<Row className='h-100 d-flex justify-content-center'>
<div className="my-3">
<h1 className="mb-4"><Trans i18nKey="login.register.title"/></h1>
<Row className="h-100 d-flex justify-content-center">
<Col lg={ 6 }>
<Card className='bg-dark mb-4 text-start'>
<Card className="bg-dark mb-4 text-start">
<Card.Body>
<Form onSubmit={ doRegisterSubmit }>
<Form.Group controlId='username'>
<Form.Label><Trans i18nKey='login.auth.username'/></Form.Label>
<Form.Group controlId="username">
<Form.Label><Trans i18nKey="login.auth.username"/></Form.Label>
<Form.Control
type='text'
size='sm'
type="text"
size="sm"
value={ username }
isValid={ username !== '' }
onChange={ (event) => setUsername(event.target.value) }
placeholder={ t('login.auth.username') }
className='bg-dark text-light'
autoComplete='username'
className="bg-dark text-light"
autoComplete="username"
autoFocus={ true }
required
/>
<Form.Text><Trans i18nKey='login.register.usernameInfo'/></Form.Text>
<Form.Text><Trans i18nKey="login.register.usernameInfo"/></Form.Text>
</Form.Group>
<Form.Group controlId='password'>
<Form.Label><Trans i18nKey='login.auth.password'/></Form.Label>
<Form.Group controlId="password">
<Form.Label><Trans i18nKey="login.auth.password"/></Form.Label>
<Form.Control
type='password'
size='sm'
type="password"
size="sm"
isValid={ password !== '' && password.length >= 8 }
onChange={ (event) => setPassword(event.target.value) }
placeholder={ t('login.auth.password') }
className='bg-dark text-light'
className="bg-dark text-light"
minLength={ 8 }
autoComplete='new-password'
autoComplete="new-password"
required
/>
<Form.Text><Trans i18nKey='login.register.passwordInfo'/></Form.Text>
<Form.Text><Trans i18nKey="login.register.passwordInfo"/></Form.Text>
</Form.Group>
<Form.Group controlId='re-password'>
<Form.Label><Trans i18nKey='login.register.passwordAgain'/></Form.Label>
<Form.Group controlId="re-password">
<Form.Label><Trans i18nKey="login.register.passwordAgain"/></Form.Label>
<Form.Control
type='password'
size='sm'
type="password"
size="sm"
isInvalid={ passwordAgain !== '' && password !== passwordAgain }
isValid={ passwordAgain !== '' && password === passwordAgain }
onChange={ (event) => setPasswordAgain(event.target.value) }
placeholder={ t('login.register.passwordAgain') }
className='bg-dark text-light'
autoComplete='new-password'
className="bg-dark text-light"
autoComplete="new-password"
required
/>
</Form.Group>
<ShowIf condition={ !!specialUrls?.termsOfUse || !!specialUrls?.privacy }>
<Trans i18nKey='login.register.infoTermsPrivacy'/>
<ShowIf condition={ !!specialUrls.termsOfUse || !!specialUrls.privacy }>
<Trans i18nKey="login.register.infoTermsPrivacy"/>
<ul>
<ShowIf condition={ !!specialUrls?.termsOfUse }>
<ShowIf condition={ !!specialUrls.termsOfUse }>
<li>
<TranslatedExternalLink i18nKey='landing.footer.termsOfUse' href={ specialUrls.termsOfUse }/>
<TranslatedExternalLink i18nKey="landing.footer.termsOfUse"
href={ specialUrls.termsOfUse ?? '' }/>
</li>
</ShowIf>
<ShowIf condition={ !!specialUrls?.privacy }>
<ShowIf condition={ !!specialUrls.privacy }>
<li>
<TranslatedExternalLink i18nKey='landing.footer.privacy' href={ specialUrls.privacy }/>
<TranslatedExternalLink i18nKey="landing.footer.privacy" href={ specialUrls.privacy ?? '' }/>
</li>
</ShowIf>
</ul>
</ShowIf>
<Button
variant='primary'
type='submit'
variant="primary"
type="submit"
block={ true }
disabled={ !ready }>
<Trans i18nKey='login.register.title'/>
<Trans i18nKey="login.register.title"/>
</Button>
</Form>
<br/>
<Alert show={ error !== RegisterError.NONE } variant='danger'>
<Alert show={ error !== RegisterError.NONE } variant="danger">
<Trans i18nKey={ `login.register.error.${ error }` }/>
</Alert>
</Card.Body>

View file

@ -0,0 +1,9 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
export const useBackendBaseUrl = (): string => {
return process.env.REACT_APP_BACKEND_BASE_URL ?? '/mock-backend'
}

View file

@ -0,0 +1,12 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { useBackendBaseUrl } from './use-backend-base-url'
export const useCustomizeAssetsUrl = (): string => {
const backendBaseUrl = useBackendBaseUrl()
return (process.env.REACT_APP_CUSTOMIZE_ASSETS_URL || `${ backendBaseUrl }/public`)
}

View file

@ -0,0 +1,12 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { useFrontendBaseUrl } from './use-frontend-base-url'
export const useFrontendAssetsUrl = (): string => {
const frontendBaseUrl = useFrontendBaseUrl()
return (process.env.REACT_APP_FRONTEND_ASSETS_URL || frontendBaseUrl)
}

View file

@ -27,10 +27,11 @@ import { isTestMode } from './utils/test-modes'
const EditorPage = React.lazy(() => import(/* webpackPrefetch: true *//* webpackChunkName: "editor" */ './components/editor-page/editor-page'))
const RenderPage = React.lazy(() => import (/* webpackPrefetch: true *//* webpackChunkName: "renderPage" */ './components/render-page/render-page'))
const DocumentReadOnlyPage = React.lazy(() => import (/* webpackPrefetch: true *//* webpackChunkName: "documentReadOnly" */ './components/document-read-only-page/document-read-only-page'))
const baseUrl = new URL(document.head.baseURI).pathname
ReactDOM.render(
<Provider store={ store }>
<Router>
<Router basename={ baseUrl }>
<ApplicationLoader>
<ErrorBoundary>
<Switch>
@ -93,4 +94,3 @@ if (isTestMode()) {
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorkerRegistration.unregister()

View file

@ -9,5 +9,5 @@ export const isTestMode = (): boolean => {
}
export const isMockMode = (): boolean => {
return process.env.REACT_APP_BACKEND === undefined
return process.env.REACT_APP_BACKEND_BASE_URL === undefined
}