mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2024-11-22 09:46:30 -05:00
Feature/lazy load components (#590)
This commit is contained in:
parent
9c38655a92
commit
101292da92
46 changed files with 261 additions and 248 deletions
|
@ -1,5 +1,5 @@
|
|||
import { RegisterError } from '../../components/register-page/register-page'
|
||||
import { expectResponseCode, getApiUrl, defaultFetchConfig } from '../utils'
|
||||
import { defaultFetchConfig, expectResponseCode, getApiUrl } from '../utils'
|
||||
|
||||
export const doInternalLogin = async (username: string, password: string): Promise<void> => {
|
||||
const response = await fetch(getApiUrl() + '/auth/internal', {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { expectResponseCode, getApiUrl, defaultFetchConfig } from '../utils'
|
||||
import { defaultFetchConfig, expectResponseCode, getApiUrl } from '../utils'
|
||||
import { Config } from './types'
|
||||
|
||||
export const getConfig = async (): Promise<Config> => {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
|
||||
import { expectResponseCode, getApiUrl, defaultFetchConfig } from '../utils'
|
||||
import { HistoryEntry } from '../../components/history-page/history-page'
|
||||
import { defaultFetchConfig, expectResponseCode, getApiUrl } from '../utils'
|
||||
|
||||
export const getHistory = async (): Promise<HistoryEntry[]> => {
|
||||
const response = await fetch(getApiUrl() + '/history')
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { UserResponse } from '../users/types'
|
||||
import { expectResponseCode, getApiUrl, defaultFetchConfig } from '../utils'
|
||||
import { defaultFetchConfig, expectResponseCode, getApiUrl } from '../utils'
|
||||
|
||||
export const getMe = async (): Promise<UserResponse> => {
|
||||
const response = await fetch(getApiUrl() + '/me', {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { ImageProxyResponse } from '../../components/markdown-renderer/replace-components/image/types'
|
||||
import { expectResponseCode, getApiUrl, defaultFetchConfig } from '../utils'
|
||||
import { defaultFetchConfig, expectResponseCode, getApiUrl } from '../utils'
|
||||
|
||||
export const getProxiedUrl = async (imageUrl: string): Promise<ImageProxyResponse> => {
|
||||
const response = await fetch(getApiUrl() + '/media/proxy', {
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import React, { Fragment, useCallback, useEffect, useState } from 'react'
|
||||
import React, { Suspense, useCallback, useEffect, useState } from 'react'
|
||||
import { useLocation } from 'react-router'
|
||||
import './application-loader.scss'
|
||||
import { createSetUpTaskList, InitTask } from './initializers'
|
||||
|
||||
import { LoadingScreen } from './loading-screen'
|
||||
|
||||
export const ApplicationLoader: React.FC = ({ children }) => {
|
||||
|
@ -33,9 +32,13 @@ export const ApplicationLoader: React.FC = ({ children }) => {
|
|||
}
|
||||
}, [initTasks, runTask])
|
||||
|
||||
return (
|
||||
doneTasks < initTasks.length || initTasks.length === 0
|
||||
? <LoadingScreen failedTitle={failedTitle}/>
|
||||
: <Fragment>{children}</Fragment>
|
||||
)
|
||||
const tasksAreRunning = doneTasks < initTasks.length || initTasks.length === 0
|
||||
|
||||
if (tasksAreRunning) {
|
||||
return <LoadingScreen failedTitle={failedTitle}/>
|
||||
} else {
|
||||
return <Suspense fallback={(<LoadingScreen/>)}>
|
||||
{children}
|
||||
</Suspense>
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import { ForkAwesomeIcon } from '../common/fork-awesome/fork-awesome-icon'
|
|||
import { ShowIf } from '../common/show-if/show-if'
|
||||
|
||||
export interface LoadingScreenProps {
|
||||
failedTitle: string
|
||||
failedTitle?: string
|
||||
}
|
||||
|
||||
export const LoadingScreen: React.FC<LoadingScreenProps> = ({ failedTitle }) => {
|
||||
|
@ -14,7 +14,7 @@ export const LoadingScreen: React.FC<LoadingScreenProps> = ({ failedTitle }) =>
|
|||
<ForkAwesomeIcon icon="file-text" size="5x"
|
||||
className={failedTitle ? 'animation-shake' : 'animation-pulse'}/>
|
||||
</div>
|
||||
<ShowIf condition={ failedTitle !== ''}>
|
||||
<ShowIf condition={ !!failedTitle}>
|
||||
<Alert variant={'danger'}>
|
||||
The task '{failedTitle}' failed.<br/>
|
||||
For further information look into the browser console.
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
import equal from 'fast-deep-equal'
|
||||
import React from 'react'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import 'fork-awesome/css/fork-awesome.min.css'
|
||||
import React from 'react'
|
||||
import { IconName, IconSize } from './types'
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import React from 'react'
|
||||
import { Button, ButtonProps } from 'react-bootstrap'
|
||||
import { ForkAwesomeIcon } from '../fork-awesome/fork-awesome-icon'
|
||||
import './icon-button.scss'
|
||||
import { IconName } from '../fork-awesome/types'
|
||||
import './icon-button.scss'
|
||||
|
||||
export interface IconButtonProps extends ButtonProps {
|
||||
icon: IconName
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import React from 'react'
|
||||
import { Trans } from 'react-i18next'
|
||||
import './icon-button.scss'
|
||||
import { IconButton, IconButtonProps } from './icon-button'
|
||||
|
||||
export interface TranslatedIconButton extends IconButtonProps {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React from 'react'
|
||||
import { Modal, Button } from 'react-bootstrap'
|
||||
import { Button, Modal } from 'react-bootstrap'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { CommonModal, CommonModalProps } from './common-modal'
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import equal from 'fast-deep-equal'
|
||||
import React from 'react'
|
||||
import { Alert, Button } from 'react-bootstrap'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { ApplicationState } from '../../../redux'
|
||||
import { Alert, Button } from 'react-bootstrap'
|
||||
import { setBanner } from '../../../redux/banner/methods'
|
||||
import { ForkAwesomeIcon } from '../fork-awesome/fork-awesome-icon'
|
||||
import { ShowIf } from '../show-if/show-if'
|
||||
|
|
|
@ -9,12 +9,12 @@ import { ForkAwesomeIcon } from '../../common/fork-awesome/fork-awesome-icon'
|
|||
import { ShowIf } from '../../common/show-if/show-if'
|
||||
import { SignInButton } from '../../landing-layout/navigation/sign-in-button'
|
||||
import { UserDropdown } from '../../landing-layout/navigation/user-dropdown'
|
||||
import { SyncScrollButtons } from './sync-scroll-buttons/sync-scroll-buttons'
|
||||
import { EditorPathParams } from '../editor'
|
||||
import { DarkModeButton } from './dark-mode-button'
|
||||
import { EditorViewMode } from './editor-view-mode'
|
||||
import { HelpButton } from './help-button/help-button'
|
||||
import { NavbarBranding } from './navbar-branding'
|
||||
import { SyncScrollButtons } from './sync-scroll-buttons/sync-scroll-buttons'
|
||||
|
||||
export const AppBar: React.FC = () => {
|
||||
const { t } = useTranslation()
|
||||
|
|
|
@ -2,10 +2,10 @@ import React from 'react'
|
|||
import { Col, Row } from 'react-bootstrap'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { useSelector } from 'react-redux'
|
||||
import links from '../../../../links.json'
|
||||
import { ApplicationState } from '../../../../redux'
|
||||
import { TranslatedExternalLink } from '../../../common/links/translated-external-link'
|
||||
import { TranslatedInternalLink } from '../../../common/links/translated-internal-link'
|
||||
import links from '../../../../links.json'
|
||||
|
||||
export const Links: React.FC = () => {
|
||||
useTranslation()
|
||||
|
|
|
@ -1,19 +1,18 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="512"
|
||||
height="512"
|
||||
viewBox="0 0 135.46666 135.46666"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
|
||||
sodipodi:docname="buttonIcon.svg">
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="512"
|
||||
height="512"
|
||||
viewBox="0 0 135.46666 135.46666"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
|
||||
sodipodi:docname="buttonIcon.svg">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<sodipodi:namedview
|
||||
|
|
Before Width: | Height: | Size: 8.9 KiB After Width: | Height: | Size: 8.9 KiB |
|
@ -1,8 +1,8 @@
|
|||
import React from 'react'
|
||||
import { Dropdown } from 'react-bootstrap'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { ForkAwesomeIcon } from '../../../common/fork-awesome/fork-awesome-icon'
|
||||
import links from '../../../../links.json'
|
||||
import { ForkAwesomeIcon } from '../../../common/fork-awesome/fork-awesome-icon'
|
||||
|
||||
const ExportMenu: React.FC = () => {
|
||||
useTranslation()
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import { Alert, Col, ListGroup, Modal, Row, Button } from 'react-bootstrap'
|
||||
import { Alert, Button, Col, ListGroup, Modal, Row } from 'react-bootstrap'
|
||||
import ReactDiffViewer, { DiffMethod } from 'react-diff-viewer'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
|
|
@ -1,35 +1,38 @@
|
|||
import { Editor, Hint, Hints, Pos } from 'codemirror'
|
||||
import hljs from 'highlight.js'
|
||||
import { findWordAtCursor, Hinter, search } from './index'
|
||||
|
||||
const allowedChars = /[`\w-_+]/
|
||||
const wordRegExp = /^```((\w|-|_|\+)*)$/
|
||||
const allSupportedLanguages = hljs.listLanguages().concat('csv', 'flow', 'html')
|
||||
let allSupportedLanguages: string[] = []
|
||||
|
||||
const codeBlockHint = (editor: Editor): Promise< Hints| null > => {
|
||||
return new Promise((resolve) => {
|
||||
const searchTerm = findWordAtCursor(editor, allowedChars)
|
||||
const searchResult = wordRegExp.exec(searchTerm.text)
|
||||
if (searchResult === null) {
|
||||
resolve(null)
|
||||
return
|
||||
}
|
||||
const term = searchResult[1]
|
||||
const suggestions = search(term, allSupportedLanguages)
|
||||
const cursor = editor.getCursor()
|
||||
if (!suggestions) {
|
||||
resolve(null)
|
||||
} else {
|
||||
resolve({
|
||||
list: suggestions.map((suggestion: string): Hint => ({
|
||||
text: '```' + suggestion + '\n\n```\n',
|
||||
displayText: suggestion
|
||||
})),
|
||||
from: Pos(cursor.line, searchTerm.start),
|
||||
to: Pos(cursor.line, searchTerm.end)
|
||||
})
|
||||
}
|
||||
})
|
||||
return import(/* webpackChunkName: "highlight.js" */ 'highlight.js').then(hljs =>
|
||||
new Promise((resolve) => {
|
||||
const searchTerm = findWordAtCursor(editor, allowedChars)
|
||||
const searchResult = wordRegExp.exec(searchTerm.text)
|
||||
if (searchResult === null) {
|
||||
resolve(null)
|
||||
return
|
||||
}
|
||||
const term = searchResult[1]
|
||||
if (allSupportedLanguages.length === 0) {
|
||||
allSupportedLanguages = hljs.listLanguages().concat('csv', 'flow', 'html')
|
||||
}
|
||||
const suggestions = search(term, allSupportedLanguages)
|
||||
const cursor = editor.getCursor()
|
||||
if (!suggestions) {
|
||||
resolve(null)
|
||||
} else {
|
||||
resolve({
|
||||
list: suggestions.map((suggestion: string): Hint => ({
|
||||
text: '```' + suggestion + '\n\n```\n',
|
||||
displayText: suggestion
|
||||
})),
|
||||
from: Pos(cursor.line, searchTerm.start),
|
||||
to: Pos(cursor.line, searchTerm.end)
|
||||
})
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
export const CodeBlockHinter: Hinter = {
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
@import '../../../../node_modules/codemirror/lib/codemirror.css';
|
||||
@import '../../../../node_modules/codemirror/addon/display/fullscreen.css';
|
||||
@import '../../../../node_modules/codemirror/addon/dialog/dialog.css';
|
||||
@import '../../../../node_modules/codemirror/theme/neat.css';
|
||||
@import './one-dark.css';
|
||||
@import '../../../../node_modules/codemirror/lib/codemirror';
|
||||
@import '../../../../node_modules/codemirror/addon/display/fullscreen';
|
||||
@import '../../../../node_modules/codemirror/addon/dialog/dialog';
|
||||
@import '../../../../node_modules/codemirror/theme/neat';
|
||||
@import './one-dark';
|
||||
@import 'hints';
|
||||
|
||||
.CodeMirror {
|
||||
|
|
|
@ -3,8 +3,8 @@ import React, { Fragment, useState } from 'react'
|
|||
import { Button } from 'react-bootstrap'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { ForkAwesomeIcon } from '../../../../common/fork-awesome/fork-awesome-icon'
|
||||
import { EmojiPicker } from './emoji-picker'
|
||||
import { addEmoji } from '../utils/toolbarButtonUtils'
|
||||
import { EmojiPicker } from './emoji-picker'
|
||||
|
||||
export interface EmojiPickerButtonProps {
|
||||
editor: CodeMirror.Editor
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
@import '../../../../../../node_modules/emoji-mart/css/emoji-mart';
|
||||
|
||||
.emoji-mart {
|
||||
position: absolute;
|
||||
z-index: 10000;
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
import { CustomEmoji, Data, EmojiData, NimblePicker } from 'emoji-mart'
|
||||
import 'emoji-mart/css/emoji-mart.css'
|
||||
import emojiData from 'emoji-mart/data/twitter.json'
|
||||
import React, { useRef } from 'react'
|
||||
import { useClickAway } from 'react-use'
|
||||
import { ShowIf } from '../../../../common/show-if/show-if'
|
||||
import './emoji-picker.scss'
|
||||
import { ForkAwesomeIcons } from './icon-names'
|
||||
import forkawesomeIcon from './forkawesome.png'
|
||||
import { ForkAwesomeIcons } from './icon-names'
|
||||
|
||||
export interface EmojiPickerProps {
|
||||
show: boolean
|
||||
|
|
|
@ -145,3 +145,5 @@ export const Editor: React.FC = () => {
|
|||
</Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
export default Editor
|
||||
|
|
|
@ -1,20 +1,5 @@
|
|||
.history-menu {
|
||||
|
||||
.fa, &::after {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
&:hover .fa, &:hover::after {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&.btn {
|
||||
padding: 0.6rem 0.65rem;
|
||||
}
|
||||
}
|
||||
|
||||
.dropup .dropdown-toggle, .dropdown-toggle {
|
||||
&.no-arrow::after {
|
||||
content: initial;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ export interface EntryMenuProps {
|
|||
className?: string
|
||||
}
|
||||
|
||||
const EntryMenu: React.FC<EntryMenuProps> = ({ id, title, location, isDark, onRemove, onDelete, className }) => {
|
||||
export const EntryMenu: React.FC<EntryMenuProps> = ({ id, title, location, isDark, onRemove, onDelete, className }) => {
|
||||
useTranslation()
|
||||
|
||||
return (
|
||||
|
@ -54,5 +54,3 @@ const EntryMenu: React.FC<EntryMenuProps> = ({ id, title, location, isDark, onRe
|
|||
</Dropdown>
|
||||
)
|
||||
}
|
||||
|
||||
export { EntryMenu }
|
||||
|
|
|
@ -189,33 +189,33 @@ export const HistoryPage: React.FC = () => {
|
|||
sortAndFilterEntries(allEntries, toolbarState),
|
||||
[allEntries, toolbarState])
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<ErrorModal show={error !== ''} onHide={resetError}
|
||||
titleI18nKey={error !== '' ? `landing.history.error.${error}.title` : ''}>
|
||||
<h5>
|
||||
<Trans i18nKey={error !== '' ? `landing.history.error.${error}.text` : ''}/>
|
||||
</h5>
|
||||
</ErrorModal>
|
||||
<h1 className="mb-4"><Trans i18nKey="landing.navigation.history"/></h1>
|
||||
<Row className={'justify-content-center mt-5 mb-3'}>
|
||||
<HistoryToolbar
|
||||
onSettingsChange={setToolbarState}
|
||||
tags={tags}
|
||||
onClearHistory={clearHistory}
|
||||
onRefreshHistory={refreshHistory}
|
||||
onExportHistory={exportHistory}
|
||||
onImportHistory={importHistory}
|
||||
onUploadAll={uploadAll}
|
||||
/>
|
||||
</Row>
|
||||
<HistoryContent
|
||||
viewState={toolbarState.viewState}
|
||||
entries={entriesToShow}
|
||||
onPinClick={pinClick}
|
||||
onRemoveClick={removeFromHistoryClick}
|
||||
onDeleteClick={deleteNoteClick}
|
||||
return <Fragment>
|
||||
<ErrorModal show={error !== ''} onHide={resetError}
|
||||
titleI18nKey={error !== '' ? `landing.history.error.${error}.title` : ''}>
|
||||
<h5>
|
||||
<Trans i18nKey={error !== '' ? `landing.history.error.${error}.text` : ''}/>
|
||||
</h5>
|
||||
</ErrorModal>
|
||||
<h1 className="mb-4"><Trans i18nKey="landing.navigation.history"/></h1>
|
||||
<Row className={'justify-content-center mt-5 mb-3'}>
|
||||
<HistoryToolbar
|
||||
onSettingsChange={setToolbarState}
|
||||
tags={tags}
|
||||
onClearHistory={clearHistory}
|
||||
onRefreshHistory={refreshHistory}
|
||||
onExportHistory={exportHistory}
|
||||
onImportHistory={importHistory}
|
||||
onUploadAll={uploadAll}
|
||||
/>
|
||||
</Fragment>
|
||||
)
|
||||
</Row>
|
||||
<HistoryContent
|
||||
viewState={toolbarState.viewState}
|
||||
entries={entriesToShow}
|
||||
onPinClick={pinClick}
|
||||
onRemoveClick={removeFromHistoryClick}
|
||||
onDeleteClick={deleteNoteClick}
|
||||
/>
|
||||
</Fragment>
|
||||
}
|
||||
|
||||
export default HistoryPage
|
||||
|
|
|
@ -6,26 +6,22 @@ import { CoverButtons } from './cover-buttons/cover-buttons'
|
|||
import { FeatureLinks } from './feature-links'
|
||||
import screenshot from './img/screenshot.png'
|
||||
|
||||
const IntroPage: React.FC = () => {
|
||||
export const IntroPage: React.FC = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<h1 dir='auto' className={'align-items-center d-flex justify-content-center'}>
|
||||
<ForkAwesomeIcon icon="file-text" className={'mr-2'}/>
|
||||
<span>HedgeDoc</span>
|
||||
<Branding/>
|
||||
</h1>
|
||||
<p className="lead mb-5">
|
||||
<Trans i18nKey="app.slogan"/>
|
||||
</p>
|
||||
return <Fragment>
|
||||
<h1 dir='auto' className={'align-items-center d-flex justify-content-center'}>
|
||||
<ForkAwesomeIcon icon="file-text" className={'mr-2'}/>
|
||||
<span>HedgeDoc</span>
|
||||
<Branding/>
|
||||
</h1>
|
||||
<p className="lead mb-5">
|
||||
<Trans i18nKey="app.slogan"/>
|
||||
</p>
|
||||
|
||||
<CoverButtons/>
|
||||
<CoverButtons/>
|
||||
|
||||
<img alt={t('landing.intro.screenShotAltText')} src={screenshot} className="img-fluid mb-5"/>
|
||||
<FeatureLinks/>
|
||||
</Fragment>
|
||||
)
|
||||
<img alt={t('landing.intro.screenShotAltText')} src={screenshot} className="img-fluid mb-5"/>
|
||||
<FeatureLinks/>
|
||||
</Fragment>
|
||||
}
|
||||
|
||||
export { IntroPage }
|
||||
|
|
|
@ -100,7 +100,7 @@ export interface ViaOneClickProps {
|
|||
optionalName?: string;
|
||||
}
|
||||
|
||||
const ViaOneClick: React.FC<ViaOneClickProps> = ({ oneClickType, optionalName }) => {
|
||||
export const ViaOneClick: React.FC<ViaOneClickProps> = ({ oneClickType, optionalName }) => {
|
||||
const backendUrl = useSelector((state: ApplicationState) => state.apiUrl.apiUrl)
|
||||
const { name, icon, className, url } = getMetadata(backendUrl, oneClickType)
|
||||
const text = optionalName || name
|
||||
|
@ -116,5 +116,3 @@ const ViaOneClick: React.FC<ViaOneClickProps> = ({ oneClickType, optionalName })
|
|||
</SocialLinkButton>
|
||||
)
|
||||
}
|
||||
|
||||
export { ViaOneClick }
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React from 'react'
|
||||
import React, { Fragment } from 'react'
|
||||
import { Card, Col, Row } from 'react-bootstrap'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
@ -10,6 +10,7 @@ import { ViaLdap } from './auth/via-ldap'
|
|||
import { OneClickType, ViaOneClick } from './auth/via-one-click'
|
||||
import { ViaOpenId } from './auth/via-openid'
|
||||
import equal from 'fast-deep-equal'
|
||||
|
||||
export const LoginPage: React.FC = () => {
|
||||
useTranslation()
|
||||
const authProviders = useSelector((state: ApplicationState) => state.config.authProviders, equal)
|
||||
|
@ -38,7 +39,7 @@ export const LoginPage: React.FC = () => {
|
|||
)
|
||||
}
|
||||
|
||||
return (
|
||||
return <Fragment>
|
||||
<div className="my-3">
|
||||
<Row className="h-100 flex justify-content-center">
|
||||
<ShowIf condition={authProviders.internal || authProviders.ldap || authProviders.openid}>
|
||||
|
@ -76,5 +77,5 @@ export const LoginPage: React.FC = () => {
|
|||
</ShowIf>
|
||||
</Row>
|
||||
</div>
|
||||
)
|
||||
</Fragment>
|
||||
}
|
||||
|
|
|
@ -1,19 +1,21 @@
|
|||
import React, { useEffect, useRef } from 'react'
|
||||
import { renderAbc } from 'abcjs'
|
||||
|
||||
export interface AbcFrameProps {
|
||||
code: string
|
||||
}
|
||||
|
||||
export const AbcFrame: React.FC<AbcFrameProps> = ({ code }) => {
|
||||
const container = useRef<HTMLDivElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (container.current) {
|
||||
renderAbc(container.current, code)
|
||||
if (!container.current) {
|
||||
return
|
||||
}
|
||||
const actualContainer = container.current
|
||||
import(/* webpackChunkName: "abc.js" */ 'abcjs').then((imp) => {
|
||||
imp.renderAbc(actualContainer, code)
|
||||
}).catch(() => { console.error('error while loading abcjs') })
|
||||
}, [code])
|
||||
|
||||
return (
|
||||
<div ref={container} className={'bg-white text-center'}/>
|
||||
)
|
||||
return <div ref={container} className={'bg-white text-center'}/>
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { parse } from 'flowchart.js'
|
||||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import { Alert } from 'react-bootstrap'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
|
@ -17,20 +16,21 @@ export const FlowChart: React.FC<FlowChartProps> = ({ code }) => {
|
|||
if (diagramRef.current === null) {
|
||||
return
|
||||
}
|
||||
const parserOutput = parse(code)
|
||||
try {
|
||||
parserOutput.drawSVG(diagramRef.current, {
|
||||
'line-width': 2,
|
||||
fill: 'none',
|
||||
'font-size': '16px',
|
||||
'font-family': 'Source Code Pro, twemoji, monospace'
|
||||
})
|
||||
setError(false)
|
||||
} catch (error) {
|
||||
setError(true)
|
||||
}
|
||||
|
||||
const currentDiagramRef = diagramRef.current
|
||||
import(/* webpackChunkName: "flowchart.js" */ 'flowchart.js').then((imp) => {
|
||||
const parserOutput = imp.parse(code)
|
||||
try {
|
||||
parserOutput.drawSVG(currentDiagramRef, {
|
||||
'line-width': 2,
|
||||
fill: 'none',
|
||||
'font-size': '16px',
|
||||
'font-family': 'Source Code Pro, twemoji, monospace'
|
||||
})
|
||||
setError(false)
|
||||
} catch (error) {
|
||||
setError(true)
|
||||
}
|
||||
}).catch(() => { console.error('error while loading flowchart.js') })
|
||||
|
||||
return () => {
|
||||
Array.from(currentDiagramRef.children).forEach(value => value.remove())
|
||||
|
@ -41,8 +41,8 @@ export const FlowChart: React.FC<FlowChartProps> = ({ code }) => {
|
|||
return (
|
||||
<Alert variant={'danger'}>
|
||||
<Trans i18nKey={'renderer.flowchart.invalidSyntax'}/>
|
||||
</Alert>
|
||||
)
|
||||
</Alert>)
|
||||
} else {
|
||||
return <div ref={diagramRef} className={'text-center'}/>
|
||||
}
|
||||
return <div ref={diagramRef} className={'text-center'}/>
|
||||
}
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
import { graphviz } from 'd3-graphviz'
|
||||
import React, { Fragment, useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { Alert } from 'react-bootstrap'
|
||||
import '@hpcc-js/wasm'
|
||||
import { ShowIf } from '../../../common/show-if/show-if'
|
||||
|
||||
export interface GraphvizFrameProps {
|
||||
|
@ -25,14 +23,18 @@ export const GraphvizFrame: React.FC<GraphvizFrameProps> = ({ code }) => {
|
|||
if (!container.current) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
setError(undefined)
|
||||
graphviz(container.current, { useWorker: false, zoom: false })
|
||||
.onerror(showError)
|
||||
.renderDot(code)
|
||||
} catch (error) {
|
||||
showError(error)
|
||||
}
|
||||
const actualContainer = container.current
|
||||
|
||||
Promise.all([import(/* webpackChunkName: "d3-graphviz" */ 'd3-graphviz'), import('@hpcc-js/wasm')]).then(([imp]) => {
|
||||
try {
|
||||
setError(undefined)
|
||||
imp.graphviz(actualContainer, { useWorker: false, zoom: false })
|
||||
.onerror(showError)
|
||||
.renderDot(code)
|
||||
} catch (error) {
|
||||
showError(error)
|
||||
}
|
||||
}).catch(() => { console.error('error while loading graphviz') })
|
||||
}, [code, error, showError])
|
||||
|
||||
return <Fragment>
|
||||
|
@ -42,3 +44,5 @@ export const GraphvizFrame: React.FC<GraphvizFrameProps> = ({ code }) => {
|
|||
<div className={'text-center'} ref={container} />
|
||||
</Fragment>
|
||||
}
|
||||
|
||||
export default GraphvizFrame
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import hljs from 'highlight.js'
|
||||
import React, { Fragment, useMemo } from 'react'
|
||||
import React, { Fragment, ReactElement, useEffect, useState } from 'react'
|
||||
import ReactHtmlParser from 'react-html-parser'
|
||||
import { CopyToClipboardButton } from '../../../../common/copyable/copy-to-clipboard-button/copy-to-clipboard-button'
|
||||
import '../../../utils/button-inside.scss'
|
||||
|
@ -21,10 +20,6 @@ export const escapeHtml = (unsafe: string): string => {
|
|||
.replace(/'/g, ''')
|
||||
}
|
||||
|
||||
const checkIfLanguageIsSupported = (language: string): boolean => {
|
||||
return hljs.listLanguages().includes(language)
|
||||
}
|
||||
|
||||
const correctLanguage = (language: string | undefined): string | undefined => {
|
||||
switch (language) {
|
||||
case 'html':
|
||||
|
@ -34,29 +29,36 @@ const correctLanguage = (language: string | undefined): string | undefined => {
|
|||
}
|
||||
}
|
||||
|
||||
const replaceCode = (code: string): ReactElement[][] => {
|
||||
return code.split('\n')
|
||||
.filter(line => !!line)
|
||||
.map(line => ReactHtmlParser(line))
|
||||
}
|
||||
|
||||
export const HighlightedCode: React.FC<HighlightedCodeProps> = ({ code, language, startLineNumber, wrapLines }) => {
|
||||
const highlightedCode = useMemo(() => {
|
||||
const replacedLanguage = correctLanguage(language)
|
||||
return ((!!replacedLanguage && checkIfLanguageIsSupported(replacedLanguage)) ? hljs.highlight(replacedLanguage, code).value : escapeHtml(code))
|
||||
.split('\n')
|
||||
.filter(line => !!line)
|
||||
.map(line => ReactHtmlParser(line))
|
||||
}, [code, language])
|
||||
const [dom, setDom] = useState<ReactElement[]>()
|
||||
|
||||
useEffect(() => {
|
||||
import(/* webpackChunkName: "highlight.js" */ 'highlight.js').then((hljs) => {
|
||||
const correctedLanguage = correctLanguage(language)
|
||||
const languageSupported = (lang: string) => hljs.listLanguages().includes(lang)
|
||||
const unreplacedCode = !!correctedLanguage && languageSupported(correctedLanguage) ? hljs.highlight(correctedLanguage, code).value : escapeHtml(code)
|
||||
const replacedDom = replaceCode(unreplacedCode).map((line, index) => (
|
||||
<Fragment key={index}>
|
||||
<span className={'linenumber'} data-line-number={(startLineNumber || 1) + index}/>
|
||||
<div className={'codeline'}>
|
||||
{line}
|
||||
</div>
|
||||
</Fragment>
|
||||
))
|
||||
setDom(replacedDom)
|
||||
}).catch(() => { console.error('error while loading highlight.js') })
|
||||
}, [code, language, startLineNumber])
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<code className={`hljs ${startLineNumber !== undefined ? 'showGutter' : ''} ${wrapLines ? 'wrapLines' : ''}`}>
|
||||
{
|
||||
highlightedCode
|
||||
.map((line, index) => (
|
||||
<Fragment key={index}>
|
||||
<span className={'linenumber'} data-line-number={(startLineNumber || 1) + index}/>
|
||||
<div className={'codeline'}>
|
||||
{line}
|
||||
</div>
|
||||
</Fragment>
|
||||
))
|
||||
}
|
||||
{ dom }
|
||||
</code>
|
||||
<div className={'text-right button-inside'}>
|
||||
<CopyToClipboardButton content={code}/>
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import { DomElement } from 'domhandler'
|
||||
import React from 'react'
|
||||
import 'katex/dist/katex.min.css'
|
||||
import TeX from '@matejmazur/react-katex'
|
||||
import { ComponentReplacer } from '../ComponentReplacer'
|
||||
import './katex.scss'
|
||||
|
||||
const getNodeIfKatexBlock = (node: DomElement): (DomElement|undefined) => {
|
||||
if (node.name !== 'p' || !node.children || node.children.length === 0) {
|
||||
|
@ -17,13 +16,15 @@ const getNodeIfInlineKatex = (node: DomElement): (DomElement|undefined) => {
|
|||
return (node.name === 'app-katex' && node.attribs?.inline !== undefined) ? node : undefined
|
||||
}
|
||||
|
||||
const KaTeX = React.lazy(() => import(/* webpackChunkName: "katex" */ '@matejmazur/react-katex'))
|
||||
|
||||
export class KatexReplacer extends ComponentReplacer {
|
||||
public getReplacement (node: DomElement): React.ReactElement | undefined {
|
||||
const katex = getNodeIfKatexBlock(node) || getNodeIfInlineKatex(node)
|
||||
if (katex?.children && katex.children[0]) {
|
||||
const mathJaxContent = katex.children[0]?.data as string
|
||||
const isInline = (katex.attribs?.inline) !== undefined
|
||||
return <TeX block={!isInline} math={mathJaxContent} errorColor={'#cc0000'}/>
|
||||
return <KaTeX block={!isInline} math={mathJaxContent} errorColor={'#cc0000'}/>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
@import '../../../../../node_modules/katex/dist/katex.min';
|
|
@ -1,5 +1,3 @@
|
|||
import { transform } from 'markmap-lib/dist/transform'
|
||||
import { Markmap } from 'markmap-lib/dist/view'
|
||||
import React, { useEffect, useRef } from 'react'
|
||||
|
||||
export interface MarkmapFrameProps {
|
||||
|
@ -13,12 +11,16 @@ export const MarkmapFrame: React.FC<MarkmapFrameProps> = ({ code }) => {
|
|||
if (!diagramContainer.current) {
|
||||
return
|
||||
}
|
||||
const svg: SVGSVGElement = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
|
||||
svg.setAttribute('width', '100%')
|
||||
diagramContainer.current.querySelectorAll('svg').forEach(child => child.remove())
|
||||
diagramContainer.current.appendChild(svg)
|
||||
const data = transform(code)
|
||||
Markmap.create(svg, {}, data)
|
||||
const actualContainer = diagramContainer.current
|
||||
Promise.all([import(/* webpackChunkName: "markmap" */ 'markmap-lib/dist/transform'), import(/* webpackChunkName: "markmap" */ 'markmap-lib/dist/view')])
|
||||
.then(([transform, view]) => {
|
||||
const svg: SVGSVGElement = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
|
||||
svg.setAttribute('width', '100%')
|
||||
actualContainer.querySelectorAll('svg').forEach(child => child.remove())
|
||||
actualContainer.appendChild(svg)
|
||||
const data = transform.transform(code)
|
||||
view.Markmap.create(svg, {}, data)
|
||||
}).catch(() => { console.error('error while loading markmap') })
|
||||
}, [code])
|
||||
|
||||
return <div className={'text-center'} ref={diagramContainer}/>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React, { Fragment, useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { Alert } from 'react-bootstrap'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import embed, { VisualizationSpec } from 'vega-embed'
|
||||
import { VisualizationSpec } from 'vega-embed'
|
||||
import { ShowIf } from '../../../common/show-if/show-if'
|
||||
|
||||
export interface VegaChartProps {
|
||||
|
@ -25,27 +25,31 @@ export const VegaChart: React.FC<VegaChartProps> = ({ code }) => {
|
|||
if (!diagramContainer.current) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const spec: VisualizationSpec = JSON.parse(code)
|
||||
showError('')
|
||||
embed(diagramContainer.current, spec, {
|
||||
actions: {
|
||||
export: true,
|
||||
source: false,
|
||||
compiled: false,
|
||||
editor: false
|
||||
},
|
||||
i18n: {
|
||||
PNG_ACTION: t('renderer.vega-lite.png'),
|
||||
SVG_ACTION: t('renderer.vega-lite.svg')
|
||||
import(/* webpackChunkName: "vega" */ 'vega-embed').then((embed) => {
|
||||
try {
|
||||
if (!diagramContainer.current) {
|
||||
return
|
||||
}
|
||||
})
|
||||
.then(result => console.log(result))
|
||||
.catch(err => showError(err))
|
||||
} catch (err) {
|
||||
showError(t('renderer.vega-lite.errorJson'))
|
||||
}
|
||||
|
||||
const spec = JSON.parse(code) as VisualizationSpec
|
||||
embed.default(diagramContainer.current, spec, {
|
||||
actions: {
|
||||
export: true,
|
||||
source: false,
|
||||
compiled: false,
|
||||
editor: false
|
||||
},
|
||||
i18n: {
|
||||
PNG_ACTION: t('renderer.vega-lite.png'),
|
||||
SVG_ACTION: t('renderer.vega-lite.svg')
|
||||
}
|
||||
})
|
||||
.then(() => showError(''))
|
||||
.catch(err => showError(err))
|
||||
} catch (err) {
|
||||
showError(t('renderer.vega-lite.errorJson'))
|
||||
}
|
||||
}).catch(() => { console.error('error while loading vega-light') })
|
||||
}, [code, showError, t])
|
||||
|
||||
return <Fragment>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { DomElement } from 'domhandler'
|
||||
import React, { Fragment, ReactElement } from 'react'
|
||||
import React, { ReactElement, Suspense } from 'react'
|
||||
import { convertNodeToElement, Transform } from 'react-html-parser'
|
||||
import {
|
||||
ComponentReplacer,
|
||||
|
@ -69,7 +69,9 @@ export const buildTransformer = (lineKeys: (LineKeys[] | undefined), allReplacer
|
|||
} else if (tryReplacement === undefined) {
|
||||
return nativeRenderer(node, key)
|
||||
} else {
|
||||
return <Fragment key={key}>{tryReplacement}</Fragment>
|
||||
return <Suspense key={key} fallback={<span>Loading...</span>}>
|
||||
{ tryReplacement }
|
||||
</Suspense>
|
||||
}
|
||||
}
|
||||
return transform
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React from 'react'
|
||||
import React, { Fragment } from 'react'
|
||||
import { Col, Row } from 'react-bootstrap'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { Redirect } from 'react-router'
|
||||
|
@ -18,7 +18,7 @@ export const ProfilePage: React.FC = () => {
|
|||
)
|
||||
}
|
||||
|
||||
return (
|
||||
return <Fragment>
|
||||
<div className="my-3">
|
||||
<Row className="h-100 flex justify-content-center">
|
||||
<Col lg={6}>
|
||||
|
@ -30,5 +30,5 @@ export const ProfilePage: React.FC = () => {
|
|||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
)
|
||||
</Fragment>
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { FormEvent, useCallback, useEffect, useState } from 'react'
|
||||
import React, { FormEvent, Fragment, useCallback, useEffect, useState } from 'react'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { Redirect } from 'react-router'
|
||||
import { doInternalRegister } from '../../api/auth'
|
||||
|
@ -53,7 +53,7 @@ export const RegisterPage: React.FC = () => {
|
|||
)
|
||||
}
|
||||
|
||||
return (
|
||||
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'>
|
||||
|
@ -138,5 +138,5 @@ export const RegisterPage: React.FC = () => {
|
|||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
)
|
||||
</Fragment>
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ import { BrowserRouter as Router, Redirect, Route, Switch } from 'react-router-d
|
|||
import { ApplicationLoader } from './components/application-loader/application-loader'
|
||||
import { NotFoundErrorScreen } from './components/common/routing/not-found-error-screen'
|
||||
import { Redirector } from './components/common/routing/redirector'
|
||||
import { Editor } from './components/editor/editor'
|
||||
import { ErrorBoundary } from './components/error-boundary/error-boundary'
|
||||
import { HistoryPage } from './components/history-page/history-page'
|
||||
import { IntroPage } from './components/intro-page/intro-page'
|
||||
|
@ -15,8 +14,10 @@ import { ProfilePage } from './components/profile-page/profile-page'
|
|||
import { RegisterPage } from './components/register-page/register-page'
|
||||
import { store } from './redux'
|
||||
import * as serviceWorker from './service-worker'
|
||||
import './style/index.scss'
|
||||
import './style/dark.scss'
|
||||
import './style/index.scss'
|
||||
|
||||
const Editor = React.lazy(() => import(/* webpackPrefetch: true */ './components/editor/editor'))
|
||||
|
||||
ReactDOM.render(
|
||||
<Provider store={store}>
|
||||
|
|
|
@ -1,10 +1,5 @@
|
|||
import { Reducer } from 'redux'
|
||||
import {
|
||||
BannerActions,
|
||||
BannerActionType,
|
||||
BannerState,
|
||||
SetBannerAction
|
||||
} from './types'
|
||||
import { BannerActions, BannerActionType, BannerState, SetBannerAction } from './types'
|
||||
|
||||
export const initialState: BannerState = {
|
||||
show: true,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Config } from '../../api/config/types'
|
||||
import { store } from '..'
|
||||
import { Config } from '../../api/config/types'
|
||||
import { ConfigActionType, SetConfigAction } from './types'
|
||||
|
||||
export const setConfig = (state: Config): void => {
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
@import '../../node_modules/react-bootstrap-typeahead/css/Typeahead';
|
||||
@import "fonts/source-code-pro/source-code-pro";
|
||||
@import "fonts/twemoji/twemoji";
|
||||
@import '../../node_modules/fork-awesome/css/fork-awesome.min';
|
||||
|
||||
.text-black, body.dark .text-black {
|
||||
color: $black;
|
||||
|
@ -56,3 +57,19 @@ body {
|
|||
.cursor-zoom-out {
|
||||
cursor: zoom-out;
|
||||
}
|
||||
|
||||
.faded-fa {
|
||||
.fa, &::after {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
&:hover .fa, &:hover::after {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.dropup .dropdown-toggle, .dropdown-toggle {
|
||||
&.no-arrow::after {
|
||||
content: initial;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue