mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-14 07:04:45 -04:00
Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de>
This commit is contained in:
parent
0ef81eca5d
commit
1777ba848f
16 changed files with 261 additions and 173 deletions
|
@ -1,8 +1,8 @@
|
||||||
/*
|
/*
|
||||||
SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||||
|
*
|
||||||
SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { TocAst } from 'markdown-it-toc-done-right'
|
import { TocAst } from 'markdown-it-toc-done-right'
|
||||||
import React, { RefObject, useRef, useState } from 'react'
|
import React, { RefObject, useRef, useState } from 'react'
|
||||||
|
@ -31,7 +31,8 @@ export interface DocumentRenderPaneProps {
|
||||||
wide?: boolean
|
wide?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DocumentRenderPane: React.FC<DocumentRenderPaneProps> = ({
|
export const DocumentRenderPane: React.FC<DocumentRenderPaneProps> = (
|
||||||
|
{
|
||||||
extraClasses,
|
extraClasses,
|
||||||
onFirstHeadingChange,
|
onFirstHeadingChange,
|
||||||
onLineMarkerPositionChanged,
|
onLineMarkerPositionChanged,
|
||||||
|
@ -41,13 +42,12 @@ export const DocumentRenderPane: React.FC<DocumentRenderPaneProps> = ({
|
||||||
onTaskCheckedChange,
|
onTaskCheckedChange,
|
||||||
documentRenderPaneRef,
|
documentRenderPaneRef,
|
||||||
wide
|
wide
|
||||||
}) => {
|
}) => {
|
||||||
const [tocAst, setTocAst] = useState<TocAst>()
|
const [tocAst, setTocAst] = useState<TocAst>()
|
||||||
const { width } = useResizeObserver(documentRenderPaneRef ? { ref: documentRenderPaneRef } : undefined)
|
const { width } = useResizeObserver(documentRenderPaneRef ? { ref: documentRenderPaneRef } : undefined)
|
||||||
const realWidth = width || 0
|
const realWidth = width || 0
|
||||||
const rendererRef = useRef<HTMLDivElement|null>(null)
|
const rendererRef = useRef<HTMLDivElement | null>(null)
|
||||||
const markdownContent = useSelector((state: ApplicationState) => state.documentContent.content)
|
const markdownContent = useSelector((state: ApplicationState) => state.documentContent.content)
|
||||||
const yamlDeprecatedTags = useSelector((state: ApplicationState) => state.documentContent.metadata.deprecatedTagsSyntax)
|
|
||||||
const changeLineMarker = useAdaptedLineMarkerCallback(documentRenderPaneRef, rendererRef, onLineMarkerPositionChanged)
|
const changeLineMarker = useAdaptedLineMarkerCallback(documentRenderPaneRef, rendererRef, onLineMarkerPositionChanged)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -55,10 +55,8 @@ export const DocumentRenderPane: React.FC<DocumentRenderPaneProps> = ({
|
||||||
ref={documentRenderPaneRef} onScroll={onScrollRenderer} onMouseEnter={onMouseEnterRenderer}>
|
ref={documentRenderPaneRef} onScroll={onScrollRenderer} onMouseEnter={onMouseEnterRenderer}>
|
||||||
<div className={'col-md'}/>
|
<div className={'col-md'}/>
|
||||||
<div className={'bg-light flex-fill'}>
|
<div className={'bg-light flex-fill'}>
|
||||||
<ShowIf condition={yamlDeprecatedTags}>
|
|
||||||
<YamlArrayDeprecationAlert/>
|
<YamlArrayDeprecationAlert/>
|
||||||
</ShowIf>
|
<div>
|
||||||
<div >
|
|
||||||
<FullMarkdownRenderer
|
<FullMarkdownRenderer
|
||||||
rendererRef={rendererRef}
|
rendererRef={rendererRef}
|
||||||
className={'flex-fill mb-3'}
|
className={'flex-fill mb-3'}
|
||||||
|
|
|
@ -6,16 +6,24 @@
|
||||||
|
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Alert } from 'react-bootstrap'
|
import { Alert } from 'react-bootstrap'
|
||||||
import { Trans } from 'react-i18next';
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
import { TranslatedExternalLink } from '../../common/links/translated-external-link'
|
import { useSelector } from 'react-redux'
|
||||||
import links from '../../../links.json'
|
import links from '../../../links.json'
|
||||||
|
import { ApplicationState } from '../../../redux'
|
||||||
|
import { TranslatedExternalLink } from '../../common/links/translated-external-link'
|
||||||
|
import { ShowIf } from '../../common/show-if/show-if'
|
||||||
|
|
||||||
export const YamlArrayDeprecationAlert: React.FC = () => {
|
export const YamlArrayDeprecationAlert: React.FC = () => {
|
||||||
return (
|
useTranslation()
|
||||||
|
const yamlDeprecatedTags = useSelector((state: ApplicationState) => state.documentContent.metadata.deprecatedTagsSyntax)
|
||||||
|
|
||||||
|
return <ShowIf condition={yamlDeprecatedTags}>
|
||||||
<Alert data-cy={'yamlArrayDeprecationAlert'} variant='warning' dir='auto'>
|
<Alert data-cy={'yamlArrayDeprecationAlert'} variant='warning' dir='auto'>
|
||||||
<Trans i18nKey='editor.deprecatedTags' />
|
<span className={'text-wrap'}>
|
||||||
|
<Trans i18nKey='editor.deprecatedTags'/>
|
||||||
|
</span>
|
||||||
<br/>
|
<br/>
|
||||||
<TranslatedExternalLink i18nKey={'common.readForMoreInfo'} href={links.faq} className={'text-primary'}/>
|
<TranslatedExternalLink i18nKey={'common.readForMoreInfo'} href={links.faq} className={'text-primary'}/>
|
||||||
</Alert>
|
</Alert>
|
||||||
);
|
</ShowIf>
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,32 +1,29 @@
|
||||||
/*
|
/*
|
||||||
SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||||
|
*
|
||||||
SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import MarkdownIt from 'markdown-it'
|
import MarkdownIt from 'markdown-it'
|
||||||
import React, { ReactElement, RefObject, useMemo, useRef } from 'react'
|
import React, { RefObject, useMemo } from 'react'
|
||||||
import { Alert } from 'react-bootstrap'
|
|
||||||
import ReactHtmlParser from 'react-html-parser'
|
|
||||||
import { Trans } from 'react-i18next'
|
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
import { ApplicationState } from '../../redux'
|
import { ApplicationState } from '../../redux'
|
||||||
import { ShowIf } from '../common/show-if/show-if'
|
import { DocumentLengthLimitReachedAlert } from './document-length-limit-reached-alert'
|
||||||
|
import { useConvertMarkdownToReactDom } from './hooks/use-convert-markdown-to-react-dom'
|
||||||
import './markdown-renderer.scss'
|
import './markdown-renderer.scss'
|
||||||
import { ComponentReplacer } from './replace-components/ComponentReplacer'
|
import { ComponentReplacer } from './replace-components/ComponentReplacer'
|
||||||
import { AdditionalMarkdownRendererProps, LineKeys } from './types'
|
import { AdditionalMarkdownRendererProps } from './types'
|
||||||
import { buildTransformer } from './utils/html-react-transformer'
|
|
||||||
import { calculateNewLineNumberMapping } from './utils/line-number-mapping'
|
|
||||||
|
|
||||||
export interface BasicMarkdownRendererProps {
|
export interface BasicMarkdownRendererProps {
|
||||||
componentReplacers?: () => ComponentReplacer[],
|
componentReplacers?: () => ComponentReplacer[],
|
||||||
markdownIt: MarkdownIt,
|
markdownIt: MarkdownIt,
|
||||||
documentReference?: RefObject<HTMLDivElement>
|
documentReference?: RefObject<HTMLDivElement>
|
||||||
onBeforeRendering?: () => void
|
onBeforeRendering?: () => void
|
||||||
onPostRendering?: () => void
|
onAfterRendering?: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export const BasicMarkdownRenderer: React.FC<BasicMarkdownRendererProps & AdditionalMarkdownRendererProps> = ({
|
export const BasicMarkdownRenderer: React.FC<BasicMarkdownRendererProps & AdditionalMarkdownRendererProps> = (
|
||||||
|
{
|
||||||
className,
|
className,
|
||||||
content,
|
content,
|
||||||
wide,
|
wide,
|
||||||
|
@ -34,38 +31,15 @@ export const BasicMarkdownRenderer: React.FC<BasicMarkdownRendererProps & Additi
|
||||||
markdownIt,
|
markdownIt,
|
||||||
documentReference,
|
documentReference,
|
||||||
onBeforeRendering,
|
onBeforeRendering,
|
||||||
onPostRendering
|
onAfterRendering
|
||||||
}) => {
|
}) => {
|
||||||
const maxLength = useSelector((state: ApplicationState) => state.config.maxDocumentLength)
|
const maxLength = useSelector((state: ApplicationState) => state.config.maxDocumentLength)
|
||||||
|
const trimmedContent = useMemo(() => content.length > maxLength ? content.substr(0, maxLength) : content, [content, maxLength])
|
||||||
const oldMarkdownLineKeys = useRef<LineKeys[]>()
|
const markdownReactDom = useConvertMarkdownToReactDom(trimmedContent, markdownIt, componentReplacers, onBeforeRendering, onAfterRendering)
|
||||||
const lastUsedLineId = useRef<number>(0)
|
|
||||||
|
|
||||||
const markdownReactDom: ReactElement[] = useMemo(() => {
|
|
||||||
if (onBeforeRendering) {
|
|
||||||
onBeforeRendering()
|
|
||||||
}
|
|
||||||
const trimmedContent = content.substr(0, maxLength)
|
|
||||||
const html: string = markdownIt.render(trimmedContent)
|
|
||||||
const contentLines = trimmedContent.split('\n')
|
|
||||||
const { lines: newLines, lastUsedLineId: newLastUsedLineId } = calculateNewLineNumberMapping(contentLines, oldMarkdownLineKeys.current ?? [], lastUsedLineId.current)
|
|
||||||
oldMarkdownLineKeys.current = newLines
|
|
||||||
lastUsedLineId.current = newLastUsedLineId
|
|
||||||
const transformer = componentReplacers ? buildTransformer(newLines, componentReplacers()) : undefined
|
|
||||||
const rendering = ReactHtmlParser(html, { transform: transformer })
|
|
||||||
if (onPostRendering) {
|
|
||||||
onPostRendering()
|
|
||||||
}
|
|
||||||
return rendering
|
|
||||||
}, [onBeforeRendering, onPostRendering, content, maxLength, markdownIt, componentReplacers])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`${className || ''} d-flex flex-column align-items-center ${wide ? 'wider' : ''}`}>
|
<div className={`${className ?? ''} d-flex flex-column align-items-center ${wide ? 'wider' : ''}`}>
|
||||||
<ShowIf condition={content.length > maxLength}>
|
<DocumentLengthLimitReachedAlert contentLength={content.length}/>
|
||||||
<Alert variant='danger' dir={'auto'} data-cy={'limitReachedMessage'}>
|
|
||||||
<Trans i18nKey={'editor.error.limitReached.description'} values={{ maxLength }}/>
|
|
||||||
</Alert>
|
|
||||||
</ShowIf>
|
|
||||||
<div ref={documentReference} className={'markdown-body w-100 d-flex flex-column align-items-center'}>
|
<div ref={documentReference} className={'markdown-body w-100 d-flex flex-column align-items-center'}>
|
||||||
{markdownReactDom}
|
{markdownReactDom}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react'
|
||||||
|
import { Alert } from 'react-bootstrap'
|
||||||
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
|
import { useSelector } from 'react-redux'
|
||||||
|
import { ApplicationState } from '../../redux'
|
||||||
|
import { ShowIf } from '../common/show-if/show-if'
|
||||||
|
|
||||||
|
export interface DocumentLengthLimitReachedAlertProps {
|
||||||
|
contentLength: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DocumentLengthLimitReachedAlert: React.FC<DocumentLengthLimitReachedAlertProps> = ({ contentLength }) => {
|
||||||
|
useTranslation()
|
||||||
|
const maxLength = useSelector((state: ApplicationState) => state.config.maxDocumentLength)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ShowIf condition={contentLength > maxLength}>
|
||||||
|
<Alert variant='danger' dir={'auto'} data-cy={'limitReachedMessage'}>
|
||||||
|
<Trans i18nKey={'editor.error.limitReached.description'} values={{ maxLength }}/>
|
||||||
|
</Alert>
|
||||||
|
</ShowIf>)
|
||||||
|
}
|
|
@ -1,8 +1,8 @@
|
||||||
/*
|
/*
|
||||||
SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||||
|
*
|
||||||
SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { TocAst } from 'markdown-it-toc-done-right'
|
import { TocAst } from 'markdown-it-toc-done-right'
|
||||||
import React, { RefObject, useCallback, useMemo, useRef, useState } from 'react'
|
import React, { RefObject, useCallback, useMemo, useRef, useState } from 'react'
|
||||||
|
@ -102,7 +102,7 @@ export const FullMarkdownRenderer: React.FC<FullMarkdownRendererProps & Addition
|
||||||
markdownIt={markdownIt}
|
markdownIt={markdownIt}
|
||||||
documentReference={documentElement}
|
documentReference={documentElement}
|
||||||
onBeforeRendering={clearMetadata}
|
onBeforeRendering={clearMetadata}
|
||||||
onPostRendering={checkYamlErrorState}
|
onAfterRendering={checkYamlErrorState}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import MarkdownIt from 'markdown-it/lib'
|
||||||
|
import { ReactElement, useMemo, useRef } from 'react'
|
||||||
|
import ReactHtmlParser from 'react-html-parser'
|
||||||
|
import { ComponentReplacer } from '../replace-components/ComponentReplacer'
|
||||||
|
import { LineKeys } from '../types'
|
||||||
|
import { buildTransformer } from '../utils/html-react-transformer'
|
||||||
|
import { calculateNewLineNumberMapping } from '../utils/line-number-mapping'
|
||||||
|
|
||||||
|
export const useConvertMarkdownToReactDom = (
|
||||||
|
markdownCode: string,
|
||||||
|
markdownIt: MarkdownIt,
|
||||||
|
componentReplacers?: () => ComponentReplacer[],
|
||||||
|
onPreRendering?: () => void,
|
||||||
|
onPostRendering?: () => void): ReactElement[] => {
|
||||||
|
const oldMarkdownLineKeys = useRef<LineKeys[]>()
|
||||||
|
const lastUsedLineId = useRef<number>(0)
|
||||||
|
|
||||||
|
return useMemo(() => {
|
||||||
|
if (onPreRendering) {
|
||||||
|
onPreRendering()
|
||||||
|
}
|
||||||
|
const html = markdownIt.render(markdownCode)
|
||||||
|
const contentLines = markdownCode.split('\n')
|
||||||
|
const {
|
||||||
|
lines: newLines,
|
||||||
|
lastUsedLineId: newLastUsedLineId
|
||||||
|
} = calculateNewLineNumberMapping(contentLines, oldMarkdownLineKeys.current ?? [], lastUsedLineId.current)
|
||||||
|
oldMarkdownLineKeys.current = newLines
|
||||||
|
lastUsedLineId.current = newLastUsedLineId
|
||||||
|
const transformer = componentReplacers ? buildTransformer(newLines, componentReplacers()) : undefined
|
||||||
|
const rendering = ReactHtmlParser(html, { transform: transformer })
|
||||||
|
if (onPostRendering) {
|
||||||
|
onPostRendering()
|
||||||
|
}
|
||||||
|
return rendering
|
||||||
|
}, [onPreRendering, onPostRendering, markdownCode, markdownIt, componentReplacers])
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
/*
|
/*!
|
||||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
@ -27,18 +27,15 @@
|
||||||
|
|
||||||
h1, h2, h3, h4, h5, h6 {
|
h1, h2, h3, h4, h5, h6 {
|
||||||
a.heading-anchor {
|
a.heading-anchor {
|
||||||
margin-left: -1.25em;
|
|
||||||
font-size: 0.75em;
|
font-size: 0.75em;
|
||||||
margin-top: 0.25em;
|
margin-top: 0.25em;
|
||||||
opacity: 0;
|
opacity: 0.3;
|
||||||
|
transition: opacity 0.1s;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&:hover a.heading-anchor {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
blockquote .quote-extra {
|
blockquote .quote-extra {
|
||||||
|
|
|
@ -10,10 +10,10 @@ import { ReactElement } from 'react'
|
||||||
|
|
||||||
export type SubNodeTransform = (node: DomElement, subIndex: number) => ReactElement | void | null
|
export type SubNodeTransform = (node: DomElement, subIndex: number) => ReactElement | void | null
|
||||||
|
|
||||||
export type NativeRenderer = (node: DomElement, key: number) => ReactElement
|
export type NativeRenderer = () => ReactElement
|
||||||
|
|
||||||
export type MarkdownItPlugin = MarkdownIt.PluginSimple | MarkdownIt.PluginWithOptions | MarkdownIt.PluginWithParams
|
export type MarkdownItPlugin = MarkdownIt.PluginSimple | MarkdownIt.PluginWithOptions | MarkdownIt.PluginWithParams
|
||||||
|
|
||||||
export abstract class ComponentReplacer {
|
export abstract class ComponentReplacer {
|
||||||
public abstract getReplacement (node: DomElement, subNodeTransform: SubNodeTransform): (ReactElement | null | undefined);
|
public abstract getReplacement (node: DomElement, subNodeTransform: SubNodeTransform, nativeRenderer: NativeRenderer): (ReactElement | null | undefined);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react'
|
||||||
|
import { Modal } from 'react-bootstrap'
|
||||||
|
import { ForkAwesomeIcon } from '../../../common/fork-awesome/fork-awesome-icon'
|
||||||
|
import "./lightbox.scss"
|
||||||
|
|
||||||
|
export interface ImageLightboxModalProps {
|
||||||
|
show: boolean
|
||||||
|
onHide: () => void
|
||||||
|
alt?: string
|
||||||
|
src?: string
|
||||||
|
title?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ImageLightboxModal: React.FC<ImageLightboxModalProps> = ({ show, onHide, src, alt, title }) => {
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
animation={true}
|
||||||
|
centered={true}
|
||||||
|
dialogClassName={'text-dark lightbox'}
|
||||||
|
show={show && !!src}
|
||||||
|
onHide={onHide}
|
||||||
|
size={'xl'}>
|
||||||
|
<Modal.Header closeButton={true}>
|
||||||
|
<Modal.Title className={'h6'}>
|
||||||
|
<ForkAwesomeIcon icon={'picture-o'}/>
|
||||||
|
|
||||||
|
<span>{alt ?? title ?? ''}</span>
|
||||||
|
</Modal.Title>
|
||||||
|
</Modal.Header>
|
||||||
|
<img alt={alt} src={src} title={title} className={'w-100 cursor-zoom-out'} onClick={onHide}/>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,18 +1,18 @@
|
||||||
/*
|
/*
|
||||||
SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||||
|
*
|
||||||
SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { DomElement } from 'domhandler'
|
import { DomElement } from 'domhandler'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { ComponentReplacer } from '../ComponentReplacer'
|
import { ComponentReplacer } from '../ComponentReplacer'
|
||||||
import { ImageFrame } from './image-frame'
|
import { ProxyImageFrame } from './proxy-image-frame'
|
||||||
|
|
||||||
export class ImageReplacer extends ComponentReplacer {
|
export class ImageReplacer extends ComponentReplacer {
|
||||||
public getReplacement (node: DomElement): React.ReactElement | undefined {
|
public getReplacement (node: DomElement): React.ReactElement | undefined {
|
||||||
if (node.name === 'img' && node.attribs) {
|
if (node.name === 'img' && node.attribs) {
|
||||||
return <ImageFrame
|
return <ProxyImageFrame
|
||||||
id={node.attribs.id}
|
id={node.attribs.id}
|
||||||
className={node.attribs.class}
|
className={node.attribs.class}
|
||||||
src={node.attribs.src}
|
src={node.attribs.src}
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { Fragment, useState } from 'react'
|
||||||
|
import { ImageLightboxModal } from './image-lightbox-modal'
|
||||||
|
import "./lightbox.scss"
|
||||||
|
|
||||||
|
export const LightboxImageFrame: React.FC<React.ImgHTMLAttributes<HTMLImageElement>> = (
|
||||||
|
{
|
||||||
|
alt,
|
||||||
|
title,
|
||||||
|
src,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
|
const [showFullscreenImage, setShowFullscreenImage] = useState(false)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<img alt={alt} src={src} title={title} {...props} className={'cursor-zoom-in'}
|
||||||
|
onClick={() => setShowFullscreenImage(true)}/>
|
||||||
|
<ImageLightboxModal
|
||||||
|
show={showFullscreenImage}
|
||||||
|
onHide={() => setShowFullscreenImage(false)} title={title} src={src} alt={alt}/>
|
||||||
|
</Fragment>
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,36 +0,0 @@
|
||||||
/*
|
|
||||||
SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
|
||||||
|
|
||||||
SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
*/
|
|
||||||
|
|
||||||
import React, { Fragment, useState } from 'react'
|
|
||||||
import { Modal } from 'react-bootstrap'
|
|
||||||
import { ForkAwesomeIcon } from '../../../common/fork-awesome/fork-awesome-icon'
|
|
||||||
import "./lightbox.scss"
|
|
||||||
|
|
||||||
export const LightboxedImageFrame: React.FC<React.ImgHTMLAttributes<HTMLImageElement>> = ({ alt, ...props }) => {
|
|
||||||
const [showFullscreenImage, setShowFullscreenImage] = useState(false)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Fragment>
|
|
||||||
<img alt={alt} {...props} className={'cursor-zoom-in'} onClick={() => setShowFullscreenImage(true)}/>
|
|
||||||
<Modal
|
|
||||||
animation={true}
|
|
||||||
centered={true}
|
|
||||||
dialogClassName={'text-dark lightbox'}
|
|
||||||
show={showFullscreenImage}
|
|
||||||
onHide={() => setShowFullscreenImage(false)}
|
|
||||||
size={'xl'}>
|
|
||||||
<Modal.Header closeButton={true}>
|
|
||||||
<Modal.Title className={'h6'}>
|
|
||||||
<ForkAwesomeIcon icon={'picture-o'}/>
|
|
||||||
|
|
||||||
<span>{alt ?? ''}</span>
|
|
||||||
</Modal.Title>
|
|
||||||
</Modal.Header>
|
|
||||||
<img alt={alt} {...props} className={'w-100 cursor-zoom-out'} onClick={() => setShowFullscreenImage(false)}/>
|
|
||||||
</Modal>
|
|
||||||
</Fragment>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,16 +1,22 @@
|
||||||
/*
|
/*
|
||||||
SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||||
|
*
|
||||||
SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
import { getProxiedUrl } from '../../../../api/media'
|
import { getProxiedUrl } from '../../../../api/media'
|
||||||
import { ApplicationState } from '../../../../redux'
|
import { ApplicationState } from '../../../../redux'
|
||||||
import { LightboxedImageFrame } from './lightboxedImageFrame'
|
import { LightboxImageFrame } from './lightbox-image-frame'
|
||||||
|
|
||||||
export const ImageFrame: React.FC<React.ImgHTMLAttributes<HTMLImageElement>> = ({ src, title, alt, ...props }) => {
|
export const ProxyImageFrame: React.FC<React.ImgHTMLAttributes<HTMLImageElement>> = (
|
||||||
|
{
|
||||||
|
src,
|
||||||
|
title,
|
||||||
|
alt,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
const [imageUrl, setImageUrl] = useState('')
|
const [imageUrl, setImageUrl] = useState('')
|
||||||
const imageProxyEnabled = useSelector((state: ApplicationState) => state.config.useImageProxy)
|
const imageProxyEnabled = useSelector((state: ApplicationState) => state.config.useImageProxy)
|
||||||
|
|
||||||
|
@ -25,11 +31,11 @@ export const ImageFrame: React.FC<React.ImgHTMLAttributes<HTMLImageElement>> = (
|
||||||
|
|
||||||
if (imageProxyEnabled) {
|
if (imageProxyEnabled) {
|
||||||
return (
|
return (
|
||||||
<LightboxedImageFrame src={imageUrl} title={title ?? alt ?? ''} alt={alt} {...props}/>
|
<LightboxImageFrame src={imageUrl} title={title ?? alt ?? ''} alt={alt} {...props}/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LightboxedImageFrame src={src ?? ''} title={title ?? alt ?? ''} alt={alt} {...props}/>
|
<LightboxImageFrame src={src ?? ''} title={title ?? alt ?? ''} alt={alt} {...props}/>
|
||||||
)
|
)
|
||||||
}
|
}
|
|
@ -1,8 +1,8 @@
|
||||||
/*
|
/*
|
||||||
SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||||
|
*
|
||||||
SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Alert } from 'react-bootstrap'
|
import { Alert } from 'react-bootstrap'
|
||||||
|
@ -15,8 +15,10 @@ export const DeprecationWarning: React.FC = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Alert data-cy={'yaml'} className={'mt-2'} variant={'warning'}>
|
<Alert data-cy={'yaml'} className={'mt-2'} variant={'warning'}>
|
||||||
|
<span className={'text-wrap'}>
|
||||||
<Trans i18nKey={'renderer.sequence.deprecationWarning'}/>
|
<Trans i18nKey={'renderer.sequence.deprecationWarning'}/>
|
||||||
|
</span>
|
||||||
|
<br/>
|
||||||
<TranslatedExternalLink i18nKey={'common.readForMoreInfo'} className={'text-primary'} href={links.faq}/>
|
<TranslatedExternalLink i18nKey={'common.readForMoreInfo'} className={'text-primary'} href={links.faq}/>
|
||||||
</Alert>
|
</Alert>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
/*
|
/*
|
||||||
SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||||
|
*
|
||||||
SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { DomElement } from 'domhandler'
|
import { DomElement } from 'domhandler'
|
||||||
import React, { ReactElement, Suspense } from 'react'
|
import React, { ReactElement, Suspense } from 'react'
|
||||||
import { convertNodeToElement, Transform } from 'react-html-parser'
|
import { convertNodeToElement, Transform } from 'react-html-parser'
|
||||||
import { ComponentReplacer, SubNodeTransform } from '../replace-components/ComponentReplacer'
|
import { ComponentReplacer, NativeRenderer, SubNodeTransform } from '../replace-components/ComponentReplacer'
|
||||||
import { LineKeys } from '../types'
|
import { LineKeys } from '../types'
|
||||||
|
|
||||||
export interface TextDifferenceResult {
|
export interface TextDifferenceResult {
|
||||||
|
@ -45,9 +45,9 @@ export const calculateKeyFromLineMarker = (node: DomElement, lineKeys?: LineKeys
|
||||||
return `${lineKeys[startLine].id}_${lineKeys[endLine].id}`
|
return `${lineKeys[startLine].id}_${lineKeys[endLine].id}`
|
||||||
}
|
}
|
||||||
|
|
||||||
export const findNodeReplacement = (node: DomElement, key: string, allReplacers: ComponentReplacer[], subNodeTransform: SubNodeTransform): ReactElement|null|undefined => {
|
export const findNodeReplacement = (node: DomElement, key: string, allReplacers: ComponentReplacer[], subNodeTransform: SubNodeTransform, nativeRenderer: NativeRenderer): ReactElement | null | undefined => {
|
||||||
return allReplacers
|
return allReplacers
|
||||||
.map((componentReplacer) => componentReplacer.getReplacement(node, subNodeTransform))
|
.map((componentReplacer) => componentReplacer.getReplacement(node, subNodeTransform, nativeRenderer))
|
||||||
.find((replacement) => replacement !== undefined)
|
.find((replacement) => replacement !== undefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,18 +62,18 @@ export const renderNativeNode = (node: DomElement, key: string, transform: Trans
|
||||||
|
|
||||||
export const buildTransformer = (lineKeys: (LineKeys[] | undefined), allReplacers: ComponentReplacer[]):Transform => {
|
export const buildTransformer = (lineKeys: (LineKeys[] | undefined), allReplacers: ComponentReplacer[]):Transform => {
|
||||||
const transform: Transform = (node, index) => {
|
const transform: Transform = (node, index) => {
|
||||||
const nativeRenderer = (subNode: DomElement, subKey: string) => renderNativeNode(subNode, subKey, transform)
|
const nativeRenderer: NativeRenderer = () => renderNativeNode(node, key, transform)
|
||||||
const subNodeTransform:SubNodeTransform = (subNode, subIndex) => transform(subNode, subIndex, transform)
|
const subNodeTransform: SubNodeTransform = (subNode, subIndex) => transform(subNode, subIndex, transform)
|
||||||
|
|
||||||
const key = calculateKeyFromLineMarker(node, lineKeys) ?? (-index).toString()
|
const key = calculateKeyFromLineMarker(node, lineKeys) ?? (-index).toString()
|
||||||
const tryReplacement = findNodeReplacement(node, key, allReplacers, subNodeTransform)
|
const tryReplacement = findNodeReplacement(node, key, allReplacers, subNodeTransform, nativeRenderer)
|
||||||
if (tryReplacement === null) {
|
if (tryReplacement === null) {
|
||||||
return null
|
return null
|
||||||
} else if (tryReplacement === undefined) {
|
} else if (tryReplacement === undefined) {
|
||||||
return nativeRenderer(node, key)
|
return nativeRenderer()
|
||||||
} else {
|
} else {
|
||||||
return <Suspense key={key} fallback={<span>Loading...</span>}>
|
return <Suspense key={key} fallback={<span>Loading...</span>}>
|
||||||
{ tryReplacement }
|
{tryReplacement}
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import { useIsDarkModeActivated } from './use-is-dark-mode-activated'
|
import { useIsDarkModeActivated } from './use-is-dark-mode-activated'
|
||||||
|
|
||||||
export const useApplyDarkMode = ():void => {
|
export const useApplyDarkMode = (): void => {
|
||||||
const darkModeActivated = useIsDarkModeActivated()
|
const darkModeActivated = useIsDarkModeActivated()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue