mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-20 10:15:17 -04:00
refactor: organize app extensions
Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
parent
8cddc96881
commit
1e4709c087
209 changed files with 286 additions and 243 deletions
|
@ -0,0 +1,133 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Highlighted Code can hide the line numbers 1`] = `
|
||||
<code
|
||||
class="hljs wrapLines"
|
||||
data-testid="code-highlighter"
|
||||
>
|
||||
<span
|
||||
class="linenumber"
|
||||
>
|
||||
1
|
||||
</span>
|
||||
<div
|
||||
class="codeline"
|
||||
>
|
||||
<span>
|
||||
const a = 1
|
||||
</span>
|
||||
</div>
|
||||
</code>
|
||||
`;
|
||||
|
||||
exports[`Highlighted Code highlights code 1`] = `
|
||||
<code
|
||||
class="hljs showGutter "
|
||||
data-testid="code-highlighter"
|
||||
>
|
||||
<span
|
||||
class="linenumber"
|
||||
>
|
||||
1
|
||||
</span>
|
||||
<div
|
||||
class="codeline"
|
||||
>
|
||||
<span
|
||||
class="hljs-keyword"
|
||||
>
|
||||
const
|
||||
</span>
|
||||
a =
|
||||
<span
|
||||
class="hljs-number"
|
||||
>
|
||||
1
|
||||
</span>
|
||||
</div>
|
||||
</code>
|
||||
`;
|
||||
|
||||
exports[`Highlighted Code renders plain text 1`] = `
|
||||
<code
|
||||
class="hljs showGutter "
|
||||
data-testid="code-highlighter"
|
||||
>
|
||||
<span
|
||||
class="linenumber"
|
||||
>
|
||||
1
|
||||
</span>
|
||||
<div
|
||||
class="codeline"
|
||||
>
|
||||
<span>
|
||||
a
|
||||
</span>
|
||||
</div>
|
||||
<span
|
||||
class="linenumber"
|
||||
>
|
||||
2
|
||||
</span>
|
||||
<div
|
||||
class="codeline"
|
||||
>
|
||||
<span>
|
||||
b
|
||||
</span>
|
||||
</div>
|
||||
<span
|
||||
class="linenumber"
|
||||
>
|
||||
3
|
||||
</span>
|
||||
<div
|
||||
class="codeline"
|
||||
>
|
||||
<span>
|
||||
c
|
||||
</span>
|
||||
</div>
|
||||
</code>
|
||||
`;
|
||||
|
||||
exports[`Highlighted Code starts with a specific line 1`] = `
|
||||
<code
|
||||
class="hljs showGutter wrapLines"
|
||||
data-testid="code-highlighter"
|
||||
>
|
||||
<span
|
||||
class="linenumber"
|
||||
>
|
||||
100
|
||||
</span>
|
||||
<div
|
||||
class="codeline"
|
||||
>
|
||||
<span>
|
||||
const a = 1
|
||||
</span>
|
||||
</div>
|
||||
</code>
|
||||
`;
|
||||
|
||||
exports[`Highlighted Code wraps code 1`] = `
|
||||
<code
|
||||
class="hljs showGutter wrapLines"
|
||||
data-testid="code-highlighter"
|
||||
>
|
||||
<span
|
||||
class="linenumber"
|
||||
>
|
||||
1
|
||||
</span>
|
||||
<div
|
||||
class="codeline"
|
||||
>
|
||||
<span>
|
||||
const a = 1
|
||||
</span>
|
||||
</div>
|
||||
</code>
|
||||
`;
|
|
@ -0,0 +1,57 @@
|
|||
/*!
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
.code-highlighter {
|
||||
position: relative;
|
||||
|
||||
:global(code.hljs) {
|
||||
overflow-x: auto;
|
||||
background-color: rgba(27, 31, 35, .05);
|
||||
padding: 16px;
|
||||
display: grid !important;
|
||||
grid-template-columns: auto minmax(0, 1fr);
|
||||
|
||||
:global(body.dark) & {
|
||||
background-color: rgb(27, 31, 35);
|
||||
}
|
||||
|
||||
.codeline {
|
||||
grid-column: 2;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.linenumber {
|
||||
grid-column: 1;
|
||||
position: relative;
|
||||
cursor: default;
|
||||
z-index: 4;
|
||||
padding: 0 8px 0 0;
|
||||
min-width: 20px;
|
||||
box-sizing: content-box;
|
||||
color: #afafaf;
|
||||
border-right: 3px solid #6ce26c;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
user-select: none;
|
||||
align-items: flex-end;
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.showGutter {
|
||||
.linenumber {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.codeline {
|
||||
margin: 0 0 0 16px;
|
||||
}
|
||||
}
|
||||
|
||||
&.wrapLines .codeline {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { mockI18n } from '../../../test-utils/mock-i18n'
|
||||
import HighlightedCode from './highlighted-code'
|
||||
import { render, screen } from '@testing-library/react'
|
||||
|
||||
describe('Highlighted Code', () => {
|
||||
beforeAll(() => mockI18n())
|
||||
|
||||
it('renders plain text', async () => {
|
||||
render(<HighlightedCode code={'a\nb\nc'} startLineNumber={1} language={''} wrapLines={false}></HighlightedCode>)
|
||||
expect(await screen.findByTestId('code-highlighter')).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('highlights code', async () => {
|
||||
render(
|
||||
<HighlightedCode
|
||||
code={'const a = 1'}
|
||||
language={'typescript'}
|
||||
startLineNumber={1}
|
||||
wrapLines={false}></HighlightedCode>
|
||||
)
|
||||
expect(await screen.findByTestId('code-highlighter')).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('wraps code', async () => {
|
||||
render(<HighlightedCode code={'const a = 1'} wrapLines={true} startLineNumber={1} language={''}></HighlightedCode>)
|
||||
expect(await screen.findByTestId('code-highlighter')).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('starts with a specific line', async () => {
|
||||
render(
|
||||
<HighlightedCode code={'const a = 1'} startLineNumber={100} language={''} wrapLines={true}></HighlightedCode>
|
||||
)
|
||||
expect(await screen.findByTestId('code-highlighter')).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('can hide the line numbers', async () => {
|
||||
render(
|
||||
<HighlightedCode
|
||||
code={'const a = 1'}
|
||||
startLineNumber={undefined}
|
||||
language={''}
|
||||
wrapLines={true}></HighlightedCode>
|
||||
)
|
||||
expect(await screen.findByTestId('code-highlighter')).toMatchSnapshot()
|
||||
})
|
||||
})
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { AsyncLoadingBoundary } from '../../../components/common/async-loading-boundary/async-loading-boundary'
|
||||
import { CopyToClipboardButton } from '../../../components/common/copyable/copy-to-clipboard-button/copy-to-clipboard-button'
|
||||
import { cypressAttribute, cypressId } from '../../../utils/cypress-attribute'
|
||||
import { testId } from '../../../utils/test-id'
|
||||
import styles from './highlighted-code.module.scss'
|
||||
import { useAsyncHighlightJsImport } from './hooks/use-async-highlight-js-import'
|
||||
import { useAttachLineNumbers } from './hooks/use-attach-line-numbers'
|
||||
import { useCodeDom } from './hooks/use-code-dom'
|
||||
import React from 'react'
|
||||
|
||||
export interface HighlightedCodeProps {
|
||||
code: string
|
||||
language?: string
|
||||
startLineNumber?: number
|
||||
wrapLines: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the given code as highlighted code block.
|
||||
*
|
||||
* @param code The code to highlight
|
||||
* @param language The language that should be used for highlighting
|
||||
* @param startLineNumber The number of the first line in the block. Will be 1 if omitted.
|
||||
* @param wrapLines Defines if lines should be wrapped or if the block should show a scroll bar.
|
||||
* @see https://highlightjs.org/
|
||||
*/
|
||||
export const HighlightedCode: React.FC<HighlightedCodeProps> = ({ code, language, startLineNumber, wrapLines }) => {
|
||||
const showGutter = startLineNumber !== undefined
|
||||
const { value: hljsApi, loading, error } = useAsyncHighlightJsImport()
|
||||
const codeDom = useCodeDom(code, hljsApi, language)
|
||||
const wrappedDomLines = useAttachLineNumbers(codeDom, startLineNumber)
|
||||
|
||||
return (
|
||||
<AsyncLoadingBoundary loading={loading || !hljsApi} error={!!error} componentName={'highlight.js'}>
|
||||
<div className={styles['code-highlighter']} {...cypressId('highlighted-code-block')}>
|
||||
<code
|
||||
{...testId('code-highlighter')}
|
||||
{...cypressId('code-highlighter')}
|
||||
{...cypressAttribute('showgutter', showGutter ? 'true' : 'false')}
|
||||
{...cypressAttribute('wraplines', wrapLines ? 'true' : 'false')}
|
||||
className={`hljs ${showGutter ? styles['showGutter'] : ''} ${wrapLines ? styles['wrapLines'] : ''}`}>
|
||||
{wrappedDomLines}
|
||||
</code>
|
||||
<div className={'text-right button-inside'}>
|
||||
<CopyToClipboardButton content={code} {...cypressId('copy-code-button')} />
|
||||
</div>
|
||||
</div>
|
||||
</AsyncLoadingBoundary>
|
||||
)
|
||||
}
|
||||
|
||||
export default HighlightedCode
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { Logger } from '../../../../utils/logger'
|
||||
import type { HLJSApi } from 'highlight.js'
|
||||
import { useAsync } from 'react-use'
|
||||
import type { AsyncState } from 'react-use/lib/useAsyncFn'
|
||||
|
||||
const log = new Logger('HighlightedCode')
|
||||
|
||||
/**
|
||||
* Lazy loads the highlight js library.
|
||||
*
|
||||
* @return the loaded js lib
|
||||
*/
|
||||
export const useAsyncHighlightJsImport = (): AsyncState<HLJSApi> => {
|
||||
return useAsync(async () => {
|
||||
try {
|
||||
return (await import(/* webpackChunkName: "highlight.js" */ '../preconfigured-highlight-js')).default
|
||||
} catch (error) {
|
||||
log.error('Error while loading highlight.js', error)
|
||||
throw error
|
||||
}
|
||||
}, [])
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { cypressId } from '../../../../utils/cypress-attribute'
|
||||
import styles from '../highlighted-code.module.scss'
|
||||
import type { ReactElement } from 'react'
|
||||
import { Fragment, useMemo } from 'react'
|
||||
|
||||
/**
|
||||
* Wraps the given {@link ReactElement elements} to attach line numbers to them.
|
||||
*
|
||||
* @param lines The elements to wrap
|
||||
* @param startLineNumber The line number to start with. Will default to 1 if omitted.
|
||||
*/
|
||||
export const useAttachLineNumbers = (
|
||||
lines: undefined | ReactElement[],
|
||||
startLineNumber = 1
|
||||
): undefined | ReactElement[] =>
|
||||
useMemo(
|
||||
() =>
|
||||
lines?.map((line, index) => (
|
||||
<Fragment key={index}>
|
||||
<span {...cypressId('linenumber')} className={styles['linenumber']}>
|
||||
{startLineNumber + index}
|
||||
</span>
|
||||
<div {...cypressId('codeline')} className={styles['codeline']}>
|
||||
{line}
|
||||
</div>
|
||||
</Fragment>
|
||||
)),
|
||||
[startLineNumber, lines]
|
||||
)
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { HtmlToReact } from '../../../../components/common/html-to-react/html-to-react'
|
||||
import type { HLJSApi } from 'highlight.js'
|
||||
import type { ReactElement } from 'react'
|
||||
import React, { useMemo } from 'react'
|
||||
|
||||
/**
|
||||
* Highlights the given code using highlight.js. If the language wasn't recognized then it won't be highlighted.
|
||||
*
|
||||
* @param code The code to highlight
|
||||
* @param hljs The highlight.js API. Needs to be imported or lazy loaded.
|
||||
* @param language The language of the code to use for highlighting
|
||||
* @return The react elements that represent the highlighted code
|
||||
*/
|
||||
export const useCodeDom = (code: string, hljs: HLJSApi | undefined, language?: string): ReactElement[] | undefined => {
|
||||
return useMemo(() => {
|
||||
if (!hljs) {
|
||||
return
|
||||
}
|
||||
if (!!language && hljs.listLanguages().includes(language)) {
|
||||
const highlightedHtml = hljs.highlight(code, { language }).value
|
||||
return createHtmlLinesToReactDOM(omitNewLineAtEnd(highlightedHtml).split('\n'))
|
||||
} else {
|
||||
return createPlaintextToReactDOM(code)
|
||||
}
|
||||
}, [code, hljs, language])
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given html code to react elements without any custom transformation but with sanitizing.
|
||||
*
|
||||
* @param code The code to convert
|
||||
* @return the code represented as react elements
|
||||
*/
|
||||
const createHtmlLinesToReactDOM = (code: string[]): ReactElement[] => {
|
||||
return code.map((line, lineIndex) => <HtmlToReact htmlCode={line} key={lineIndex} />)
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given line based text to plain text react elements but without interpreting them as html first.
|
||||
*
|
||||
* @param text The text to convert
|
||||
* @return the text represented as react elements.
|
||||
*/
|
||||
const createPlaintextToReactDOM = (text: string): ReactElement[] => {
|
||||
return text.split('\n').map((line, lineIndex) => React.createElement('span', { key: lineIndex }, line))
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the given code but without the last new line if the string ends with a new line.
|
||||
*
|
||||
* @param code The code to inspect
|
||||
* @return the modified code
|
||||
*/
|
||||
const omitNewLineAtEnd = (code: string): string => {
|
||||
if (code.endsWith('\n')) {
|
||||
return code.slice(0, -1)
|
||||
} else {
|
||||
return code
|
||||
}
|
||||
}
|
|
@ -0,0 +1,389 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import hljs from 'highlight.js/lib/core'
|
||||
import abnf from 'highlight.js/lib/languages/abnf'
|
||||
import accesslog from 'highlight.js/lib/languages/accesslog'
|
||||
import actionscript from 'highlight.js/lib/languages/actionscript'
|
||||
import ada from 'highlight.js/lib/languages/ada'
|
||||
import angelscript from 'highlight.js/lib/languages/angelscript'
|
||||
import apache from 'highlight.js/lib/languages/apache'
|
||||
import applescript from 'highlight.js/lib/languages/applescript'
|
||||
import arcade from 'highlight.js/lib/languages/arcade'
|
||||
import arduino from 'highlight.js/lib/languages/arduino'
|
||||
import armasm from 'highlight.js/lib/languages/armasm'
|
||||
import asciidoc from 'highlight.js/lib/languages/asciidoc'
|
||||
import aspectj from 'highlight.js/lib/languages/aspectj'
|
||||
import autohotkey from 'highlight.js/lib/languages/autohotkey'
|
||||
import autoit from 'highlight.js/lib/languages/autoit'
|
||||
import avrasm from 'highlight.js/lib/languages/avrasm'
|
||||
import awk from 'highlight.js/lib/languages/awk'
|
||||
import axapta from 'highlight.js/lib/languages/axapta'
|
||||
import bash from 'highlight.js/lib/languages/bash'
|
||||
import basic from 'highlight.js/lib/languages/basic'
|
||||
import bnf from 'highlight.js/lib/languages/bnf'
|
||||
import brainfuck from 'highlight.js/lib/languages/brainfuck'
|
||||
import c from 'highlight.js/lib/languages/c'
|
||||
import cal from 'highlight.js/lib/languages/cal'
|
||||
import capnproto from 'highlight.js/lib/languages/capnproto'
|
||||
import ceylon from 'highlight.js/lib/languages/ceylon'
|
||||
import clean from 'highlight.js/lib/languages/clean'
|
||||
import clojure from 'highlight.js/lib/languages/clojure'
|
||||
import clojure_repl from 'highlight.js/lib/languages/clojure-repl'
|
||||
import cmake from 'highlight.js/lib/languages/cmake'
|
||||
import coffeescript from 'highlight.js/lib/languages/coffeescript'
|
||||
import coq from 'highlight.js/lib/languages/coq'
|
||||
import cos from 'highlight.js/lib/languages/cos'
|
||||
import cpp from 'highlight.js/lib/languages/cpp'
|
||||
import crmsh from 'highlight.js/lib/languages/crmsh'
|
||||
import crystal from 'highlight.js/lib/languages/crystal'
|
||||
import csharp from 'highlight.js/lib/languages/csharp'
|
||||
import csp from 'highlight.js/lib/languages/csp'
|
||||
import css from 'highlight.js/lib/languages/css'
|
||||
import d from 'highlight.js/lib/languages/d'
|
||||
import dart from 'highlight.js/lib/languages/dart'
|
||||
import delphi from 'highlight.js/lib/languages/delphi'
|
||||
import diff from 'highlight.js/lib/languages/diff'
|
||||
import django from 'highlight.js/lib/languages/django'
|
||||
import dns from 'highlight.js/lib/languages/dns'
|
||||
import dockerfile from 'highlight.js/lib/languages/dockerfile'
|
||||
import dos from 'highlight.js/lib/languages/dos'
|
||||
import dsconfig from 'highlight.js/lib/languages/dsconfig'
|
||||
import dts from 'highlight.js/lib/languages/dts'
|
||||
import dust from 'highlight.js/lib/languages/dust'
|
||||
import ebnf from 'highlight.js/lib/languages/ebnf'
|
||||
import elixir from 'highlight.js/lib/languages/elixir'
|
||||
import elm from 'highlight.js/lib/languages/elm'
|
||||
import erb from 'highlight.js/lib/languages/erb'
|
||||
import erlang from 'highlight.js/lib/languages/erlang'
|
||||
import erlang_repl from 'highlight.js/lib/languages/erlang-repl'
|
||||
import excel from 'highlight.js/lib/languages/excel'
|
||||
import fix from 'highlight.js/lib/languages/fix'
|
||||
import flix from 'highlight.js/lib/languages/flix'
|
||||
import fortran from 'highlight.js/lib/languages/fortran'
|
||||
import fsharp from 'highlight.js/lib/languages/fsharp'
|
||||
import gams from 'highlight.js/lib/languages/gams'
|
||||
import gauss from 'highlight.js/lib/languages/gauss'
|
||||
import gcode from 'highlight.js/lib/languages/gcode'
|
||||
import gherkin from 'highlight.js/lib/languages/gherkin'
|
||||
import glsl from 'highlight.js/lib/languages/glsl'
|
||||
import gml from 'highlight.js/lib/languages/gml'
|
||||
import go from 'highlight.js/lib/languages/go'
|
||||
import golo from 'highlight.js/lib/languages/golo'
|
||||
import gradle from 'highlight.js/lib/languages/gradle'
|
||||
import groovy from 'highlight.js/lib/languages/groovy'
|
||||
import haml from 'highlight.js/lib/languages/haml'
|
||||
import handlebars from 'highlight.js/lib/languages/handlebars'
|
||||
import haskell from 'highlight.js/lib/languages/haskell'
|
||||
import haxe from 'highlight.js/lib/languages/haxe'
|
||||
import hsp from 'highlight.js/lib/languages/hsp'
|
||||
import http from 'highlight.js/lib/languages/http'
|
||||
import hy from 'highlight.js/lib/languages/hy'
|
||||
import inform7 from 'highlight.js/lib/languages/inform7'
|
||||
import ini from 'highlight.js/lib/languages/ini'
|
||||
import irpf90 from 'highlight.js/lib/languages/irpf90'
|
||||
import isbl from 'highlight.js/lib/languages/isbl'
|
||||
import java from 'highlight.js/lib/languages/java'
|
||||
import javascript from 'highlight.js/lib/languages/javascript'
|
||||
import jboss_cli from 'highlight.js/lib/languages/jboss-cli'
|
||||
import json from 'highlight.js/lib/languages/json'
|
||||
import julia from 'highlight.js/lib/languages/julia'
|
||||
import julia_repl from 'highlight.js/lib/languages/julia-repl'
|
||||
import kotlin from 'highlight.js/lib/languages/kotlin'
|
||||
import lasso from 'highlight.js/lib/languages/lasso'
|
||||
import latex from 'highlight.js/lib/languages/latex'
|
||||
import ldif from 'highlight.js/lib/languages/ldif'
|
||||
import leaf from 'highlight.js/lib/languages/leaf'
|
||||
import less from 'highlight.js/lib/languages/less'
|
||||
import lisp from 'highlight.js/lib/languages/lisp'
|
||||
import livecodeserver from 'highlight.js/lib/languages/livecodeserver'
|
||||
import livescript from 'highlight.js/lib/languages/livescript'
|
||||
import llvm from 'highlight.js/lib/languages/llvm'
|
||||
import lsl from 'highlight.js/lib/languages/lsl'
|
||||
import lua from 'highlight.js/lib/languages/lua'
|
||||
import makefile from 'highlight.js/lib/languages/makefile'
|
||||
import markdown from 'highlight.js/lib/languages/markdown'
|
||||
import mathematica from 'highlight.js/lib/languages/mathematica'
|
||||
import matlab from 'highlight.js/lib/languages/matlab'
|
||||
import maxima from 'highlight.js/lib/languages/maxima'
|
||||
import mel from 'highlight.js/lib/languages/mel'
|
||||
import mercury from 'highlight.js/lib/languages/mercury'
|
||||
import mipsasm from 'highlight.js/lib/languages/mipsasm'
|
||||
import mizar from 'highlight.js/lib/languages/mizar'
|
||||
import mojolicious from 'highlight.js/lib/languages/mojolicious'
|
||||
import monkey from 'highlight.js/lib/languages/monkey'
|
||||
import moonscript from 'highlight.js/lib/languages/moonscript'
|
||||
import n1ql from 'highlight.js/lib/languages/n1ql'
|
||||
import nestedtext from 'highlight.js/lib/languages/nestedtext'
|
||||
import nginx from 'highlight.js/lib/languages/nginx'
|
||||
import nim from 'highlight.js/lib/languages/nim'
|
||||
import nix from 'highlight.js/lib/languages/nix'
|
||||
import node_repl from 'highlight.js/lib/languages/node-repl'
|
||||
import nsis from 'highlight.js/lib/languages/nsis'
|
||||
import objectivec from 'highlight.js/lib/languages/objectivec'
|
||||
import ocaml from 'highlight.js/lib/languages/ocaml'
|
||||
import openscad from 'highlight.js/lib/languages/openscad'
|
||||
import oxygene from 'highlight.js/lib/languages/oxygene'
|
||||
import parser3 from 'highlight.js/lib/languages/parser3'
|
||||
import perl from 'highlight.js/lib/languages/perl'
|
||||
import pf from 'highlight.js/lib/languages/pf'
|
||||
import pgsql from 'highlight.js/lib/languages/pgsql'
|
||||
import php from 'highlight.js/lib/languages/php'
|
||||
import php_template from 'highlight.js/lib/languages/php-template'
|
||||
import plaintext from 'highlight.js/lib/languages/plaintext'
|
||||
import pony from 'highlight.js/lib/languages/pony'
|
||||
import powershell from 'highlight.js/lib/languages/powershell'
|
||||
import processing from 'highlight.js/lib/languages/processing'
|
||||
import profile from 'highlight.js/lib/languages/profile'
|
||||
import prolog from 'highlight.js/lib/languages/prolog'
|
||||
import properties from 'highlight.js/lib/languages/properties'
|
||||
import protobuf from 'highlight.js/lib/languages/protobuf'
|
||||
import puppet from 'highlight.js/lib/languages/puppet'
|
||||
import purebasic from 'highlight.js/lib/languages/purebasic'
|
||||
import python from 'highlight.js/lib/languages/python'
|
||||
import python_repl from 'highlight.js/lib/languages/python-repl'
|
||||
import q from 'highlight.js/lib/languages/q'
|
||||
import qml from 'highlight.js/lib/languages/qml'
|
||||
import r from 'highlight.js/lib/languages/r'
|
||||
import reasonml from 'highlight.js/lib/languages/reasonml'
|
||||
import rib from 'highlight.js/lib/languages/rib'
|
||||
import roboconf from 'highlight.js/lib/languages/roboconf'
|
||||
import routeros from 'highlight.js/lib/languages/routeros'
|
||||
import rsl from 'highlight.js/lib/languages/rsl'
|
||||
import ruby from 'highlight.js/lib/languages/ruby'
|
||||
import ruleslanguage from 'highlight.js/lib/languages/ruleslanguage'
|
||||
import rust from 'highlight.js/lib/languages/rust'
|
||||
import sas from 'highlight.js/lib/languages/sas'
|
||||
import scala from 'highlight.js/lib/languages/scala'
|
||||
import scheme from 'highlight.js/lib/languages/scheme'
|
||||
import scilab from 'highlight.js/lib/languages/scilab'
|
||||
import scss from 'highlight.js/lib/languages/scss'
|
||||
import shell from 'highlight.js/lib/languages/shell'
|
||||
import smali from 'highlight.js/lib/languages/smali'
|
||||
import smalltalk from 'highlight.js/lib/languages/smalltalk'
|
||||
import sml from 'highlight.js/lib/languages/sml'
|
||||
import sqf from 'highlight.js/lib/languages/sqf'
|
||||
import sql from 'highlight.js/lib/languages/sql'
|
||||
import stan from 'highlight.js/lib/languages/stan'
|
||||
import stata from 'highlight.js/lib/languages/stata'
|
||||
import step21 from 'highlight.js/lib/languages/step21'
|
||||
import stylus from 'highlight.js/lib/languages/stylus'
|
||||
import subunit from 'highlight.js/lib/languages/subunit'
|
||||
import swift from 'highlight.js/lib/languages/swift'
|
||||
import taggerscript from 'highlight.js/lib/languages/taggerscript'
|
||||
import tap from 'highlight.js/lib/languages/tap'
|
||||
import tcl from 'highlight.js/lib/languages/tcl'
|
||||
import thrift from 'highlight.js/lib/languages/thrift'
|
||||
import tp from 'highlight.js/lib/languages/tp'
|
||||
import twig from 'highlight.js/lib/languages/twig'
|
||||
import typescript from 'highlight.js/lib/languages/typescript'
|
||||
import vala from 'highlight.js/lib/languages/vala'
|
||||
import vbnet from 'highlight.js/lib/languages/vbnet'
|
||||
import vbscript from 'highlight.js/lib/languages/vbscript'
|
||||
import vbscript_html from 'highlight.js/lib/languages/vbscript-html'
|
||||
import verilog from 'highlight.js/lib/languages/verilog'
|
||||
import vhdl from 'highlight.js/lib/languages/vhdl'
|
||||
import vim from 'highlight.js/lib/languages/vim'
|
||||
import wasm from 'highlight.js/lib/languages/wasm'
|
||||
import x86asm from 'highlight.js/lib/languages/x86asm'
|
||||
import xl from 'highlight.js/lib/languages/xl'
|
||||
import xml from 'highlight.js/lib/languages/xml'
|
||||
import xquery from 'highlight.js/lib/languages/xquery'
|
||||
import yaml from 'highlight.js/lib/languages/yaml'
|
||||
import zephir from 'highlight.js/lib/languages/zephir'
|
||||
|
||||
hljs.registerLanguage('abnf', abnf)
|
||||
hljs.registerLanguage('accesslog', accesslog)
|
||||
hljs.registerLanguage('actionscript', actionscript)
|
||||
hljs.registerLanguage('ada', ada)
|
||||
hljs.registerLanguage('angelscript', angelscript)
|
||||
hljs.registerLanguage('apache', apache)
|
||||
hljs.registerLanguage('applescript', applescript)
|
||||
hljs.registerLanguage('arcade', arcade)
|
||||
hljs.registerLanguage('arduino', arduino)
|
||||
hljs.registerLanguage('armasm', armasm)
|
||||
hljs.registerLanguage('xml', xml)
|
||||
hljs.registerLanguage('asciidoc', asciidoc)
|
||||
hljs.registerLanguage('aspectj', aspectj)
|
||||
hljs.registerLanguage('autohotkey', autohotkey)
|
||||
hljs.registerLanguage('autoit', autoit)
|
||||
hljs.registerLanguage('avrasm', avrasm)
|
||||
hljs.registerLanguage('awk', awk)
|
||||
hljs.registerLanguage('axapta', axapta)
|
||||
hljs.registerLanguage('bash', bash)
|
||||
hljs.registerLanguage('basic', basic)
|
||||
hljs.registerLanguage('bnf', bnf)
|
||||
hljs.registerLanguage('brainfuck', brainfuck)
|
||||
hljs.registerLanguage('c', c)
|
||||
hljs.registerLanguage('cal', cal)
|
||||
hljs.registerLanguage('capnproto', capnproto)
|
||||
hljs.registerLanguage('ceylon', ceylon)
|
||||
hljs.registerLanguage('clean', clean)
|
||||
hljs.registerLanguage('clojure', clojure)
|
||||
hljs.registerLanguage('clojure-repl', clojure_repl)
|
||||
hljs.registerLanguage('cmake', cmake)
|
||||
hljs.registerLanguage('coffeescript', coffeescript)
|
||||
hljs.registerLanguage('coq', coq)
|
||||
hljs.registerLanguage('cos', cos)
|
||||
hljs.registerLanguage('cpp', cpp)
|
||||
hljs.registerLanguage('crmsh', crmsh)
|
||||
hljs.registerLanguage('crystal', crystal)
|
||||
hljs.registerLanguage('csharp', csharp)
|
||||
hljs.registerLanguage('csp', csp)
|
||||
hljs.registerLanguage('css', css)
|
||||
hljs.registerLanguage('d', d)
|
||||
hljs.registerLanguage('markdown', markdown)
|
||||
hljs.registerLanguage('dart', dart)
|
||||
hljs.registerLanguage('delphi', delphi)
|
||||
hljs.registerLanguage('diff', diff)
|
||||
hljs.registerLanguage('django', django)
|
||||
hljs.registerLanguage('dns', dns)
|
||||
hljs.registerLanguage('dockerfile', dockerfile)
|
||||
hljs.registerLanguage('dos', dos)
|
||||
hljs.registerLanguage('dsconfig', dsconfig)
|
||||
hljs.registerLanguage('dts', dts)
|
||||
hljs.registerLanguage('dust', dust)
|
||||
hljs.registerLanguage('ebnf', ebnf)
|
||||
hljs.registerLanguage('elixir', elixir)
|
||||
hljs.registerLanguage('elm', elm)
|
||||
hljs.registerLanguage('ruby', ruby)
|
||||
hljs.registerLanguage('erb', erb)
|
||||
hljs.registerLanguage('erlang-repl', erlang_repl)
|
||||
hljs.registerLanguage('erlang', erlang)
|
||||
hljs.registerLanguage('excel', excel)
|
||||
hljs.registerLanguage('fix', fix)
|
||||
hljs.registerLanguage('flix', flix)
|
||||
hljs.registerLanguage('fortran', fortran)
|
||||
hljs.registerLanguage('fsharp', fsharp)
|
||||
hljs.registerLanguage('gams', gams)
|
||||
hljs.registerLanguage('gauss', gauss)
|
||||
hljs.registerLanguage('gcode', gcode)
|
||||
hljs.registerLanguage('gherkin', gherkin)
|
||||
hljs.registerLanguage('glsl', glsl)
|
||||
hljs.registerLanguage('gml', gml)
|
||||
hljs.registerLanguage('go', go)
|
||||
hljs.registerLanguage('golo', golo)
|
||||
hljs.registerLanguage('gradle', gradle)
|
||||
hljs.registerLanguage('groovy', groovy)
|
||||
hljs.registerLanguage('haml', haml)
|
||||
hljs.registerLanguage('handlebars', handlebars)
|
||||
hljs.registerLanguage('haskell', haskell)
|
||||
hljs.registerLanguage('haxe', haxe)
|
||||
hljs.registerLanguage('hsp', hsp)
|
||||
hljs.registerLanguage('html', xml)
|
||||
hljs.registerLanguage('http', http)
|
||||
hljs.registerLanguage('hy', hy)
|
||||
hljs.registerLanguage('inform7', inform7)
|
||||
hljs.registerLanguage('ini', ini)
|
||||
hljs.registerLanguage('irpf90', irpf90)
|
||||
hljs.registerLanguage('isbl', isbl)
|
||||
hljs.registerLanguage('java', java)
|
||||
hljs.registerLanguage('javascript', javascript)
|
||||
hljs.registerLanguage('jboss-cli', jboss_cli)
|
||||
hljs.registerLanguage('js', javascript)
|
||||
hljs.registerLanguage('json', json)
|
||||
hljs.registerLanguage('julia', julia)
|
||||
hljs.registerLanguage('julia-repl', julia_repl)
|
||||
hljs.registerLanguage('kotlin', kotlin)
|
||||
hljs.registerLanguage('lasso', lasso)
|
||||
hljs.registerLanguage('latex', latex)
|
||||
hljs.registerLanguage('ldif', ldif)
|
||||
hljs.registerLanguage('leaf', leaf)
|
||||
hljs.registerLanguage('less', less)
|
||||
hljs.registerLanguage('lisp', lisp)
|
||||
hljs.registerLanguage('livecodeserver', livecodeserver)
|
||||
hljs.registerLanguage('livescript', livescript)
|
||||
hljs.registerLanguage('llvm', llvm)
|
||||
hljs.registerLanguage('lsl', lsl)
|
||||
hljs.registerLanguage('lua', lua)
|
||||
hljs.registerLanguage('makefile', makefile)
|
||||
hljs.registerLanguage('mathematica', mathematica)
|
||||
hljs.registerLanguage('matlab', matlab)
|
||||
hljs.registerLanguage('maxima', maxima)
|
||||
hljs.registerLanguage('mel', mel)
|
||||
hljs.registerLanguage('mercury', mercury)
|
||||
hljs.registerLanguage('mipsasm', mipsasm)
|
||||
hljs.registerLanguage('mizar', mizar)
|
||||
hljs.registerLanguage('perl', perl)
|
||||
hljs.registerLanguage('mojolicious', mojolicious)
|
||||
hljs.registerLanguage('monkey', monkey)
|
||||
hljs.registerLanguage('moonscript', moonscript)
|
||||
hljs.registerLanguage('nestedtext', nestedtext)
|
||||
hljs.registerLanguage('n1ql', n1ql)
|
||||
hljs.registerLanguage('nginx', nginx)
|
||||
hljs.registerLanguage('nim', nim)
|
||||
hljs.registerLanguage('nix', nix)
|
||||
hljs.registerLanguage('node-repl', node_repl)
|
||||
hljs.registerLanguage('nsis', nsis)
|
||||
hljs.registerLanguage('objectivec', objectivec)
|
||||
hljs.registerLanguage('ocaml', ocaml)
|
||||
hljs.registerLanguage('openscad', openscad)
|
||||
hljs.registerLanguage('oxygene', oxygene)
|
||||
hljs.registerLanguage('parser3', parser3)
|
||||
hljs.registerLanguage('pf', pf)
|
||||
hljs.registerLanguage('pgsql', pgsql)
|
||||
hljs.registerLanguage('php', php)
|
||||
hljs.registerLanguage('php-template', php_template)
|
||||
hljs.registerLanguage('plaintext', plaintext)
|
||||
hljs.registerLanguage('pony', pony)
|
||||
hljs.registerLanguage('powershell', powershell)
|
||||
hljs.registerLanguage('processing', processing)
|
||||
hljs.registerLanguage('profile', profile)
|
||||
hljs.registerLanguage('prolog', prolog)
|
||||
hljs.registerLanguage('properties', properties)
|
||||
hljs.registerLanguage('protobuf', protobuf)
|
||||
hljs.registerLanguage('puppet', puppet)
|
||||
hljs.registerLanguage('purebasic', purebasic)
|
||||
hljs.registerLanguage('python', python)
|
||||
hljs.registerLanguage('python-repl', python_repl)
|
||||
hljs.registerLanguage('q', q)
|
||||
hljs.registerLanguage('qml', qml)
|
||||
hljs.registerLanguage('r', r)
|
||||
hljs.registerLanguage('reasonml', reasonml)
|
||||
hljs.registerLanguage('rib', rib)
|
||||
hljs.registerLanguage('roboconf', roboconf)
|
||||
hljs.registerLanguage('routeros', routeros)
|
||||
hljs.registerLanguage('rsl', rsl)
|
||||
hljs.registerLanguage('ruleslanguage', ruleslanguage)
|
||||
hljs.registerLanguage('rust', rust)
|
||||
hljs.registerLanguage('sas', sas)
|
||||
hljs.registerLanguage('scala', scala)
|
||||
hljs.registerLanguage('scheme', scheme)
|
||||
hljs.registerLanguage('scilab', scilab)
|
||||
hljs.registerLanguage('scss', scss)
|
||||
hljs.registerLanguage('shell', shell)
|
||||
hljs.registerLanguage('smali', smali)
|
||||
hljs.registerLanguage('smalltalk', smalltalk)
|
||||
hljs.registerLanguage('sml', sml)
|
||||
hljs.registerLanguage('sqf', sqf)
|
||||
hljs.registerLanguage('sql', sql)
|
||||
hljs.registerLanguage('stan', stan)
|
||||
hljs.registerLanguage('stata', stata)
|
||||
hljs.registerLanguage('step21', step21)
|
||||
hljs.registerLanguage('stylus', stylus)
|
||||
hljs.registerLanguage('subunit', subunit)
|
||||
hljs.registerLanguage('swift', swift)
|
||||
hljs.registerLanguage('taggerscript', taggerscript)
|
||||
hljs.registerLanguage('yaml', yaml)
|
||||
hljs.registerLanguage('tap', tap)
|
||||
hljs.registerLanguage('tcl', tcl)
|
||||
hljs.registerLanguage('thrift', thrift)
|
||||
hljs.registerLanguage('tp', tp)
|
||||
hljs.registerLanguage('twig', twig)
|
||||
hljs.registerLanguage('typescript', typescript)
|
||||
hljs.registerLanguage('vala', vala)
|
||||
hljs.registerLanguage('vbnet', vbnet)
|
||||
hljs.registerLanguage('vbscript', vbscript)
|
||||
hljs.registerLanguage('vbscript-html', vbscript_html)
|
||||
hljs.registerLanguage('verilog', verilog)
|
||||
hljs.registerLanguage('vhdl', vhdl)
|
||||
hljs.registerLanguage('vim', vim)
|
||||
hljs.registerLanguage('x86asm', x86asm)
|
||||
hljs.registerLanguage('xl', xl)
|
||||
hljs.registerLanguage('xquery', xquery)
|
||||
hljs.registerLanguage('zephir', zephir)
|
||||
hljs.registerLanguage('wasmn', wasm)
|
||||
|
||||
export default hljs
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { allAppExtensions } from '../../../../extensions/extra-integrations/all-app-extensions'
|
||||
import { allAppExtensions } from '../../../../extensions/all-app-extensions'
|
||||
import type { CheatsheetEntry, CheatsheetExtension } from '../../cheatsheet/cheatsheet-extension'
|
||||
import { isCheatsheetGroup } from '../../cheatsheet/cheatsheet-extension'
|
||||
import { CategoryAccordion } from './category-accordion'
|
||||
|
@ -25,10 +25,7 @@ export const CheatsheetContent: React.FC = () => {
|
|||
setSelectedEntry(isCheatsheetGroup(value) ? value.entries[0] : value)
|
||||
}, [])
|
||||
|
||||
const extensions = useMemo(
|
||||
() => allAppExtensions.flatMap((extension) => extension.buildCheatsheetExtensions()),
|
||||
[]
|
||||
)
|
||||
const extensions = useMemo(() => allAppExtensions.flatMap((extension) => extension.buildCheatsheetExtensions()), [])
|
||||
|
||||
return (
|
||||
<Row className={`mt-2`}>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import HighlightedCode from '../../../../extensions/extra-integrations/highlighted-code-fence/highlighted-code'
|
||||
import HighlightedCode from '../../../common/highlighted-code/highlighted-code'
|
||||
import { HtmlToReact } from '../../../common/html-to-react/html-to-react'
|
||||
import { ExtensionEventEmitterProvider } from '../../../markdown-renderer/hooks/use-extension-event-emitter'
|
||||
import { RendererType } from '../../../render-page/window-post-message-communicator/rendering-message'
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { allAppExtensions } from '../../../../extensions/extra-integrations/all-app-extensions'
|
||||
import { allAppExtensions } from '../../../../extensions/all-app-extensions'
|
||||
import type { CheatsheetExtensionComponentProps } from '../../cheatsheet/cheatsheet-extension'
|
||||
import { isCheatsheetGroup } from '../../cheatsheet/cheatsheet-extension'
|
||||
import type { ReactElement } from 'react'
|
||||
|
|
|
@ -8,7 +8,7 @@ import { ORIGIN, useBaseUrl } from '../../../hooks/common/use-base-url'
|
|||
import { useDarkModeState } from '../../../hooks/common/use-dark-mode-state'
|
||||
import { useMayEdit } from '../../../hooks/common/use-may-edit'
|
||||
import { cypressAttribute, cypressId } from '../../../utils/cypress-attribute'
|
||||
import { findLanguageByCodeBlockName } from '../../markdown-renderer/extensions/base/code-block-markdown-extension/find-language-by-code-block-name'
|
||||
import { findLanguageByCodeBlockName } from '../../markdown-renderer/extensions/_base-classes/code-block-markdown-extension/find-language-by-code-block-name'
|
||||
import type { ScrollProps } from '../synced-scroll/scroll-props'
|
||||
import styles from './extended-codemirror/codemirror.module.scss'
|
||||
import { useCodeMirrorAutocompletionsExtension } from './hooks/codemirror-extensions/use-code-mirror-autocompletions-extension'
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { allAppExtensions } from '../../../../../extensions/extra-integrations/all-app-extensions'
|
||||
import { allAppExtensions } from '../../../../../extensions/all-app-extensions'
|
||||
import { autocompletion } from '@codemirror/autocomplete'
|
||||
import type { Extension } from '@codemirror/state'
|
||||
import { useMemo } from 'react'
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { allAppExtensions } from '../../../../extensions/extra-integrations/all-app-extensions'
|
||||
import { allAppExtensions } from '../../../../extensions/all-app-extensions'
|
||||
import type { ReactElement } from 'react'
|
||||
import React, { Fragment, useMemo } from 'react'
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { allAppExtensions } from '../../../../extensions/extra-integrations/all-app-extensions'
|
||||
import { allAppExtensions } from '../../../../extensions/all-app-extensions'
|
||||
import { useDarkModeState } from '../../../../hooks/common/use-dark-mode-state'
|
||||
import { FrontmatterLinter } from './frontmatter-linter'
|
||||
import type { Diagnostic } from '@codemirror/lint'
|
||||
|
|
|
@ -1,70 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { AppExtension } from '../../../../extensions/base/app-extension'
|
||||
import type { CheatsheetExtension } from '../../../editor-page/cheatsheet/cheatsheet-extension'
|
||||
import { basicCompletion } from '../../../editor-page/editor-pane/autocompletions/basic-completion'
|
||||
import type { MarkdownRendererExtension } from '../base/markdown-renderer-extension'
|
||||
import { BasicMarkdownSyntaxMarkdownExtension } from './basic-markdown-syntax-markdown-extension'
|
||||
import type { CompletionSource } from '@codemirror/autocomplete'
|
||||
import { t } from 'i18next'
|
||||
|
||||
export class BasicMarkdownSyntaxAppExtension extends AppExtension {
|
||||
buildMarkdownRendererExtensions(): MarkdownRendererExtension[] {
|
||||
return [new BasicMarkdownSyntaxMarkdownExtension()]
|
||||
}
|
||||
|
||||
buildCheatsheetExtensions(): CheatsheetExtension[] {
|
||||
return [
|
||||
{
|
||||
i18nKey: 'basics.basicFormatting',
|
||||
categoryI18nKey: 'basic'
|
||||
},
|
||||
{
|
||||
i18nKey: 'basics.abbreviation',
|
||||
categoryI18nKey: 'basic'
|
||||
},
|
||||
{
|
||||
i18nKey: 'basics.footnote',
|
||||
categoryI18nKey: 'basic'
|
||||
},
|
||||
{
|
||||
i18nKey: 'basics.headlines',
|
||||
categoryI18nKey: 'basic',
|
||||
entries: [
|
||||
{
|
||||
i18nKey: 'hashtag'
|
||||
},
|
||||
{
|
||||
i18nKey: 'equal'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
i18nKey: 'basics.code',
|
||||
categoryI18nKey: 'basic',
|
||||
entries: [{ i18nKey: 'inline' }, { i18nKey: 'block' }]
|
||||
},
|
||||
{
|
||||
i18nKey: 'basics.lists',
|
||||
categoryI18nKey: 'basic',
|
||||
entries: [{ i18nKey: 'unordered' }, { i18nKey: 'ordered' }]
|
||||
},
|
||||
{
|
||||
i18nKey: 'basics.images',
|
||||
categoryI18nKey: 'basic',
|
||||
entries: [{ i18nKey: 'basic' }, { i18nKey: 'size' }]
|
||||
},
|
||||
{
|
||||
i18nKey: 'basics.links',
|
||||
categoryI18nKey: 'basic'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
buildAutocompletion(): CompletionSource[] {
|
||||
return [basicCompletion(/(^|\s)\[/, '[](https://)', t('editor.autocompletions.link') ?? undefined)]
|
||||
}
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { MarkdownRendererExtension } from '../base/markdown-renderer-extension'
|
||||
import { imageSize } from '@hedgedoc/markdown-it-plugins'
|
||||
import type MarkdownIt from 'markdown-it'
|
||||
import abbreviation from 'markdown-it-abbr'
|
||||
import definitionList from 'markdown-it-deflist'
|
||||
import footnote from 'markdown-it-footnote'
|
||||
import inserted from 'markdown-it-ins'
|
||||
import marked from 'markdown-it-mark'
|
||||
import subscript from 'markdown-it-sub'
|
||||
import superscript from 'markdown-it-sup'
|
||||
|
||||
/**
|
||||
* Adds some common markdown syntaxes to the markdown rendering.
|
||||
*/
|
||||
export class BasicMarkdownSyntaxMarkdownExtension extends MarkdownRendererExtension {
|
||||
public configureMarkdownIt(markdownIt: MarkdownIt): void {
|
||||
abbreviation(markdownIt)
|
||||
definitionList(markdownIt)
|
||||
subscript(markdownIt)
|
||||
superscript(markdownIt)
|
||||
inserted(markdownIt)
|
||||
marked(markdownIt)
|
||||
footnote(markdownIt)
|
||||
imageSize(markdownIt)
|
||||
}
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`bootstrap icon markdown extension doesn't render invalid icon 1`] = `
|
||||
<div>
|
||||
<p>
|
||||
:bi-INVALIDICONNAME:
|
||||
</p>
|
||||
|
||||
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`bootstrap icon markdown extension doesn't render missing icon 1`] = `
|
||||
<div>
|
||||
<p>
|
||||
:bi-:
|
||||
</p>
|
||||
|
||||
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`bootstrap icon markdown extension renders correct icon 1`] = `
|
||||
<div>
|
||||
<p>
|
||||
<span
|
||||
data-svg-mock="true"
|
||||
data-testid="lazy-bootstrap-icon-alarm"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
width="1em"
|
||||
/>
|
||||
</p>
|
||||
|
||||
|
||||
</div>
|
||||
`;
|
|
@ -1,37 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { AppExtension } from '../../../../extensions/base/app-extension'
|
||||
import { BootstrapLazyIcons } from '../../../common/icons/bootstrap-icons'
|
||||
import type { CheatsheetExtension } from '../../../editor-page/cheatsheet/cheatsheet-extension'
|
||||
import { regexCompletion } from '../../../editor-page/editor-pane/autocompletions/regex-completion'
|
||||
import type { MarkdownRendererExtension } from '../base/markdown-renderer-extension'
|
||||
import { BootstrapIconMarkdownExtension } from './bootstrap-icon-markdown-extension'
|
||||
import type { CompletionSource } from '@codemirror/autocomplete'
|
||||
import { t } from 'i18next'
|
||||
|
||||
const bootstrapIconNames = Object.keys(BootstrapLazyIcons)
|
||||
|
||||
export class BootstrapIconAppExtension extends AppExtension {
|
||||
buildMarkdownRendererExtensions(): MarkdownRendererExtension[] {
|
||||
return [new BootstrapIconMarkdownExtension()]
|
||||
}
|
||||
|
||||
buildCheatsheetExtensions(): CheatsheetExtension[] {
|
||||
return [{ i18nKey: 'bootstrapIcon', readMoreUrl: new URL('https://icons.getbootstrap.com/') }]
|
||||
}
|
||||
|
||||
buildAutocompletion(): CompletionSource[] {
|
||||
return [
|
||||
regexCompletion(
|
||||
/:(?:[\w-]+:?)?/,
|
||||
bootstrapIconNames.map((icon) => ({
|
||||
detail: t('editor.autocompletions.icon') ?? undefined,
|
||||
label: `:bi-${icon}:`
|
||||
}))
|
||||
)
|
||||
]
|
||||
}
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { isBootstrapIconName } from '../../../common/icons/bootstrap-icons'
|
||||
import { LazyBootstrapIcon } from '../../../common/icons/lazy-bootstrap-icon'
|
||||
import type { NodeReplacement } from '../../replace-components/component-replacer'
|
||||
import { ComponentReplacer, DO_NOT_REPLACE } from '../../replace-components/component-replacer'
|
||||
import { BootstrapIconMarkdownExtension } from './bootstrap-icon-markdown-extension'
|
||||
import type { Element } from 'domhandler'
|
||||
import React from 'react'
|
||||
|
||||
/**
|
||||
* Replaces a bootstrap icon tag with the bootstrap icon react component.
|
||||
*
|
||||
* @see BootstrapIcon
|
||||
*/
|
||||
export class BootstrapIconComponentReplacer extends ComponentReplacer {
|
||||
constructor() {
|
||||
super()
|
||||
}
|
||||
|
||||
public replace(node: Element): NodeReplacement {
|
||||
const iconName = this.extractIconName(node)
|
||||
if (!iconName || !isBootstrapIconName(iconName)) {
|
||||
return DO_NOT_REPLACE
|
||||
}
|
||||
return React.createElement(LazyBootstrapIcon, { icon: iconName })
|
||||
}
|
||||
|
||||
private extractIconName(element: Element): string | undefined {
|
||||
return element.name === BootstrapIconMarkdownExtension.tagName && element.attribs && element.attribs.id
|
||||
? element.attribs.id
|
||||
: undefined
|
||||
}
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { TestMarkdownRenderer } from '../../test-utils/test-markdown-renderer'
|
||||
import { BootstrapIconMarkdownExtension } from './bootstrap-icon-markdown-extension'
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import React from 'react'
|
||||
|
||||
describe('bootstrap icon markdown extension', () => {
|
||||
it('renders correct icon', async () => {
|
||||
const view = render(
|
||||
<TestMarkdownRenderer extensions={[new BootstrapIconMarkdownExtension()]} content={':bi-alarm:'} />
|
||||
)
|
||||
await screen.findByTestId('lazy-bootstrap-icon-alarm')
|
||||
expect(view.container).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it("doesn't render missing icon", () => {
|
||||
const view = render(<TestMarkdownRenderer extensions={[new BootstrapIconMarkdownExtension()]} content={':bi-:'} />)
|
||||
expect(view.container).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it("doesn't render invalid icon", () => {
|
||||
const view = render(
|
||||
<TestMarkdownRenderer extensions={[new BootstrapIconMarkdownExtension()]} content={':bi-INVALIDICONNAME:'} />
|
||||
)
|
||||
expect(view.container).toMatchSnapshot()
|
||||
})
|
||||
})
|
|
@ -1,29 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { ComponentReplacer } from '../../replace-components/component-replacer'
|
||||
import { MarkdownRendererExtension } from '../base/markdown-renderer-extension'
|
||||
import { BootstrapIconComponentReplacer } from './bootstrap-icon-component-replacer'
|
||||
import { replaceBootstrapIconsMarkdownItPlugin } from './replace-bootstrap-icons'
|
||||
import type MarkdownIt from 'markdown-it'
|
||||
|
||||
/**
|
||||
* Adds Bootstrap icons via the :bi-$name: syntax.
|
||||
*/
|
||||
export class BootstrapIconMarkdownExtension extends MarkdownRendererExtension {
|
||||
public static readonly tagName = 'app-bootstrap-icon'
|
||||
|
||||
public configureMarkdownIt(markdownIt: MarkdownIt): void {
|
||||
replaceBootstrapIconsMarkdownItPlugin(markdownIt)
|
||||
}
|
||||
|
||||
public buildReplacers(): ComponentReplacer[] {
|
||||
return [new BootstrapIconComponentReplacer()]
|
||||
}
|
||||
|
||||
public buildTagNameAllowList(): string[] {
|
||||
return [BootstrapIconMarkdownExtension.tagName]
|
||||
}
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { replaceBootstrapIconsMarkdownItPlugin } from './replace-bootstrap-icons'
|
||||
import MarkdownIt from 'markdown-it'
|
||||
|
||||
describe('Replace bootstrap icons', () => {
|
||||
let markdownIt: MarkdownIt
|
||||
|
||||
beforeEach(() => {
|
||||
markdownIt = new MarkdownIt('default', {
|
||||
html: false,
|
||||
breaks: true,
|
||||
langPrefix: '',
|
||||
typographer: true
|
||||
})
|
||||
markdownIt.use(replaceBootstrapIconsMarkdownItPlugin)
|
||||
})
|
||||
it(`can detect a correct icon`, () => {
|
||||
expect(markdownIt.renderInline(':bi-alarm:')).toBe('<app-bootstrap-icon id="alarm"></app-bootstrap-icon>')
|
||||
})
|
||||
|
||||
it("won't detect an invalid id", () => {
|
||||
const invalidIcon = ':bi-invalid:'
|
||||
expect(markdownIt.renderInline(invalidIcon)).toBe(invalidIcon)
|
||||
})
|
||||
|
||||
it("won't detect an empty id", () => {
|
||||
const invalidIcon = ':bi-:'
|
||||
expect(markdownIt.renderInline(invalidIcon)).toBe(invalidIcon)
|
||||
})
|
||||
|
||||
it("won't detect a wrong id", () => {
|
||||
const invalidIcon = ':bi-%?(:'
|
||||
expect(markdownIt.renderInline(invalidIcon)).toBe(invalidIcon)
|
||||
})
|
||||
})
|
|
@ -1,30 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { RegexOptions } from '../../../../external-types/markdown-it-regex/interface'
|
||||
import { isBootstrapIconName } from '../../../common/icons/bootstrap-icons'
|
||||
import { BootstrapIconMarkdownExtension } from './bootstrap-icon-markdown-extension'
|
||||
import type MarkdownIt from 'markdown-it'
|
||||
import markdownItRegex from 'markdown-it-regex'
|
||||
|
||||
const biRegex = /:bi-([\w-]+):/i
|
||||
|
||||
/**
|
||||
* Replacer for bootstrap icon via the :bi-$name: syntax.
|
||||
*/
|
||||
export const replaceBootstrapIconsMarkdownItPlugin: MarkdownIt.PluginSimple = (markdownIt: MarkdownIt) =>
|
||||
markdownItRegex(markdownIt, {
|
||||
name: 'bootstrap-icons',
|
||||
regex: biRegex,
|
||||
replace: (match) => {
|
||||
if (isBootstrapIconName(match)) {
|
||||
// ESLint wants to collapse this tag, but then the tag won't be valid html anymore.
|
||||
// noinspection CheckTagEmptyBody
|
||||
return `<${BootstrapIconMarkdownExtension.tagName} id="${match}"></${BootstrapIconMarkdownExtension.tagName}>`
|
||||
} else {
|
||||
return `:bi-${match}:`
|
||||
}
|
||||
}
|
||||
} as RegexOptions)
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
import { Logger } from '../../../utils/logger'
|
||||
import { isDevMode } from '../../../utils/test-modes'
|
||||
import { MarkdownRendererExtension } from './base/markdown-renderer-extension'
|
||||
import { MarkdownRendererExtension } from './_base-classes/markdown-renderer-extension'
|
||||
import type MarkdownIt from 'markdown-it'
|
||||
|
||||
const log = new Logger('DebuggerMarkdownExtension')
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Emoji Markdown Extension renders a skin tone code 1`] = `
|
||||
<div>
|
||||
<p>
|
||||
🏽
|
||||
</p>
|
||||
|
||||
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Emoji Markdown Extension renders an emoji code 1`] = `
|
||||
<div>
|
||||
<p>
|
||||
😄
|
||||
</p>
|
||||
|
||||
|
||||
</div>
|
||||
`;
|
|
@ -1,40 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { AppExtension } from '../../../../extensions/base/app-extension'
|
||||
import type { CheatsheetExtension } from '../../../editor-page/cheatsheet/cheatsheet-extension'
|
||||
import { regexCompletion } from '../../../editor-page/editor-pane/autocompletions/regex-completion'
|
||||
import type { MarkdownRendererExtension } from '../base/markdown-renderer-extension'
|
||||
import { EmojiMarkdownExtension } from './emoji-markdown-extension'
|
||||
import { emojiShortcodes } from './mapping'
|
||||
import type { CompletionSource } from '@codemirror/autocomplete'
|
||||
import { t } from 'i18next'
|
||||
|
||||
export class EmojiAppExtension extends AppExtension {
|
||||
buildMarkdownRendererExtensions(): MarkdownRendererExtension[] {
|
||||
return [new EmojiMarkdownExtension()]
|
||||
}
|
||||
|
||||
buildCheatsheetExtensions(): CheatsheetExtension[] {
|
||||
return [
|
||||
{
|
||||
i18nKey: 'emoji',
|
||||
readMoreUrl: new URL('https://twemoji.twitter.com/')
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
buildAutocompletion(): CompletionSource[] {
|
||||
return [
|
||||
regexCompletion(
|
||||
/:(?:[\w-+]+:?)?/,
|
||||
emojiShortcodes.map((shortcode) => ({
|
||||
detail: t('editor.autocompletions.emoji') ?? undefined,
|
||||
label: `:${shortcode}:`
|
||||
}))
|
||||
)
|
||||
]
|
||||
}
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { mockI18n } from '../../../../test-utils/mock-i18n'
|
||||
import { TestMarkdownRenderer } from '../../test-utils/test-markdown-renderer'
|
||||
import { EmojiMarkdownExtension } from './emoji-markdown-extension'
|
||||
import { render } from '@testing-library/react'
|
||||
import React from 'react'
|
||||
|
||||
describe('Emoji Markdown Extension', () => {
|
||||
beforeAll(async () => {
|
||||
await mockI18n()
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
jest.resetModules()
|
||||
jest.restoreAllMocks()
|
||||
})
|
||||
|
||||
it('renders an emoji code', () => {
|
||||
const view = render(<TestMarkdownRenderer extensions={[new EmojiMarkdownExtension()]} content={':smile:'} />)
|
||||
expect(view.container).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('renders a skin tone code', () => {
|
||||
const view = render(<TestMarkdownRenderer extensions={[new EmojiMarkdownExtension()]} content={':skin-tone-3:'} />)
|
||||
expect(view.container).toMatchSnapshot()
|
||||
})
|
||||
})
|
|
@ -1,20 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { MarkdownRendererExtension } from '../base/markdown-renderer-extension'
|
||||
import { combinedEmojiData } from './mapping'
|
||||
import type MarkdownIt from 'markdown-it'
|
||||
import emoji from 'markdown-it-emoji/bare'
|
||||
|
||||
/**
|
||||
* Adds support for utf-8 emojis.
|
||||
*/
|
||||
export class EmojiMarkdownExtension extends MarkdownRendererExtension {
|
||||
public configureMarkdownIt(markdownIt: MarkdownIt): void {
|
||||
markdownIt.use(emoji, {
|
||||
defs: combinedEmojiData
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import emojiData from 'emoji-picker-element-data/en/emojibase/data.json'
|
||||
|
||||
interface EmojiEntry {
|
||||
shortcodes: string[]
|
||||
emoji: string
|
||||
}
|
||||
|
||||
type ShortCodeMap = { [key: string]: string }
|
||||
|
||||
const shortCodeMap = (emojiData as unknown as EmojiEntry[]).reduce((reduceObject, emoji) => {
|
||||
emoji.shortcodes.forEach((shortcode) => {
|
||||
reduceObject[shortcode] = emoji.emoji
|
||||
})
|
||||
return reduceObject
|
||||
}, {} as ShortCodeMap)
|
||||
|
||||
const emojiSkinToneModifierMap = [1, 2, 3, 4, 5].reduce((reduceObject, modifierValue) => {
|
||||
const lightSkinCode = 127995
|
||||
const codepoint = lightSkinCode + (modifierValue - 1)
|
||||
const shortcode = `skin-tone-${modifierValue}`
|
||||
reduceObject[shortcode] = `&#${codepoint};`
|
||||
return reduceObject
|
||||
}, {} as ShortCodeMap)
|
||||
|
||||
export const combinedEmojiData = {
|
||||
...shortCodeMap,
|
||||
...emojiSkinToneModifierMap
|
||||
}
|
||||
|
||||
export const emojiShortcodes = Object.keys(combinedEmojiData)
|
|
@ -1,27 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { MarkdownRendererExtensionOptions } from '../../../../extensions/base/app-extension'
|
||||
import { AppExtension } from '../../../../extensions/base/app-extension'
|
||||
import { RendererType } from '../../../render-page/window-post-message-communicator/rendering-message'
|
||||
import type { MarkdownRendererExtension } from '../base/markdown-renderer-extension'
|
||||
import { ExtractFirstHeadlineEditorExtension } from './extract-first-headline-editor-extension'
|
||||
import { ExtractFirstHeadlineMarkdownExtension } from './extract-first-headline-markdown-extension'
|
||||
|
||||
/**
|
||||
* Provides first headline extraction
|
||||
*/
|
||||
export class ExtractFirstHeadlineAppExtension extends AppExtension {
|
||||
buildMarkdownRendererExtensions(options: MarkdownRendererExtensionOptions): MarkdownRendererExtension[] {
|
||||
if (options.rendererType === RendererType.SIMPLE) {
|
||||
return []
|
||||
}
|
||||
return [new ExtractFirstHeadlineMarkdownExtension(options.eventEmitter)]
|
||||
}
|
||||
|
||||
buildEditorExtensionComponent(): React.FC {
|
||||
return ExtractFirstHeadlineEditorExtension
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { updateNoteTitleByFirstHeading } from '../../../../redux/note-details/methods'
|
||||
import { useExtensionEventEmitterHandler } from '../../hooks/use-extension-event-emitter'
|
||||
import { ExtractFirstHeadlineNodeProcessor } from './extract-first-headline-node-processor'
|
||||
import type React from 'react'
|
||||
|
||||
/**
|
||||
* Receives the {@link ExtractFirstHeadlineNodeProcessor.EVENT_NAME first heading extraction event}
|
||||
* and saves the title in the global application state.
|
||||
*/
|
||||
export const ExtractFirstHeadlineEditorExtension: React.FC = () => {
|
||||
useExtensionEventEmitterHandler(ExtractFirstHeadlineNodeProcessor.EVENT_NAME, updateNoteTitleByFirstHeading)
|
||||
return null
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { NodeProcessor } from '../../node-preprocessors/node-processor'
|
||||
import { EventMarkdownRendererExtension } from '../base/event-markdown-renderer-extension'
|
||||
import { ExtractFirstHeadlineNodeProcessor } from './extract-first-headline-node-processor'
|
||||
|
||||
/**
|
||||
* Adds first heading extraction to the renderer
|
||||
*/
|
||||
export class ExtractFirstHeadlineMarkdownExtension extends EventMarkdownRendererExtension {
|
||||
buildNodeProcessors(): NodeProcessor[] {
|
||||
return [new ExtractFirstHeadlineNodeProcessor(this.eventEmitter)]
|
||||
}
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { NodeProcessor } from '../../node-preprocessors/node-processor'
|
||||
import { extractFirstHeading } from '@hedgedoc/commons'
|
||||
import { Optional } from '@mrdrogdrog/optional'
|
||||
import type { Document } from 'domhandler'
|
||||
import type { EventEmitter2 } from 'eventemitter2'
|
||||
|
||||
/**
|
||||
* Searches for the first headline tag and extracts its plain text content.
|
||||
*/
|
||||
export class ExtractFirstHeadlineNodeProcessor extends NodeProcessor {
|
||||
public static readonly EVENT_NAME = 'HeadlineExtracted'
|
||||
|
||||
constructor(private eventEmitter: EventEmitter2) {
|
||||
super()
|
||||
}
|
||||
|
||||
process(nodes: Document): Document {
|
||||
Optional.ofNullable(extractFirstHeading(nodes))
|
||||
.filter((text) => text !== '')
|
||||
.ifPresent((text) => this.eventEmitter.emit(ExtractFirstHeadlineNodeProcessor.EVENT_NAME, text))
|
||||
return nodes
|
||||
}
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { MarkdownRendererExtension } from './base/markdown-renderer-extension'
|
||||
import type MarkdownIt from 'markdown-it'
|
||||
import anchor from 'markdown-it-anchor'
|
||||
|
||||
/**
|
||||
* Adds headline anchors to the markdown rendering.
|
||||
*/
|
||||
export class HeadlineAnchorsMarkdownExtension extends MarkdownRendererExtension {
|
||||
public configureMarkdownIt(markdownIt: MarkdownIt): void {
|
||||
anchor(markdownIt, {
|
||||
permalink: anchor.permalink.ariaHidden({
|
||||
symbol: '🔗',
|
||||
class: 'heading-anchor text-dark',
|
||||
renderHref: (slug: string): string => `#${slug}`,
|
||||
placement: 'before'
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { AppExtension } from '../../../../extensions/base/app-extension'
|
||||
import type { CheatsheetExtension } from '../../../editor-page/cheatsheet/cheatsheet-extension'
|
||||
import type { MarkdownRendererExtension } from '../base/markdown-renderer-extension'
|
||||
import { IframeCapsuleMarkdownExtension } from './iframe-capsule-markdown-extension'
|
||||
|
||||
export class IframeCapsuleAppExtension extends AppExtension {
|
||||
buildMarkdownRendererExtensions(): MarkdownRendererExtension[] {
|
||||
return [new IframeCapsuleMarkdownExtension()]
|
||||
}
|
||||
|
||||
buildCheatsheetExtensions(): CheatsheetExtension[] {
|
||||
return [
|
||||
{
|
||||
i18nKey: 'iframeCapsule',
|
||||
categoryI18nKey: 'embedding'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { ComponentReplacer } from '../../replace-components/component-replacer'
|
||||
import { MarkdownRendererExtension } from '../base/markdown-renderer-extension'
|
||||
import { IframeCapsuleReplacer } from './iframe-capsule-replacer'
|
||||
|
||||
/**
|
||||
* Adds a replacer that capsules iframes in a click shield.
|
||||
*/
|
||||
export class IframeCapsuleMarkdownExtension extends MarkdownRendererExtension {
|
||||
public buildReplacers(): ComponentReplacer[] {
|
||||
return [new IframeCapsuleReplacer()]
|
||||
}
|
||||
|
||||
public buildTagNameAllowList(): string[] {
|
||||
return ['iframe']
|
||||
}
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { ClickShield } from '../../replace-components/click-shield/click-shield'
|
||||
import type { NativeRenderer, NodeReplacement, SubNodeTransform } from '../../replace-components/component-replacer'
|
||||
import { ComponentReplacer, DO_NOT_REPLACE } from '../../replace-components/component-replacer'
|
||||
import type { Element } from 'domhandler'
|
||||
import React from 'react'
|
||||
import { Globe as IconGlobe } from 'react-bootstrap-icons'
|
||||
|
||||
/**
|
||||
* Capsules <iframe> elements with a click shield.
|
||||
*
|
||||
* @see ClickShield
|
||||
*/
|
||||
export class IframeCapsuleReplacer extends ComponentReplacer {
|
||||
replace(node: Element, subNodeTransform: SubNodeTransform, nativeRenderer: NativeRenderer): NodeReplacement {
|
||||
return node.name !== 'iframe' ? (
|
||||
DO_NOT_REPLACE
|
||||
) : (
|
||||
<ClickShield
|
||||
hoverIcon={IconGlobe}
|
||||
targetDescription={node.attribs.src}
|
||||
data-cypress-id={'iframe-capsule-click-shield'}>
|
||||
{nativeRenderer()}
|
||||
</ClickShield>
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { ImagePlaceholderMarkdownExtension } from './image-placeholder-markdown-extension'
|
||||
import type MarkdownIt from 'markdown-it/lib'
|
||||
|
||||
/**
|
||||
* A {@link MarkdownIt.PluginSimple markdown it plugin} that adds the line number of the markdown code to every placeholder image.
|
||||
*
|
||||
* @param markdownIt The markdown it instance to which the plugin should be added
|
||||
*/
|
||||
export const addLineToPlaceholderImageTags: MarkdownIt.PluginSimple = (markdownIt: MarkdownIt) => {
|
||||
markdownIt.core.ruler.push('image-placeholder', (state) => {
|
||||
state.tokens.forEach((token) => {
|
||||
if (token.type !== 'inline') {
|
||||
return
|
||||
}
|
||||
token.children?.forEach((childToken) => {
|
||||
if (
|
||||
childToken.type === 'image' &&
|
||||
childToken.attrGet('src') === ImagePlaceholderMarkdownExtension.PLACEHOLDER_URL
|
||||
) {
|
||||
const line = token.map?.[0]
|
||||
if (line !== undefined) {
|
||||
childToken.attrSet('data-line', String(line))
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
return true
|
||||
})
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { Logger } from '../../../../../utils/logger'
|
||||
import { FileContentFormat, readFile } from '../../../../../utils/read-file'
|
||||
import { useRendererToEditorCommunicator } from '../../../../editor-page/render-context/renderer-to-editor-communicator-context-provider'
|
||||
import { CommunicationMessageType } from '../../../../render-page/window-post-message-communicator/rendering-message'
|
||||
import { useCallback } from 'react'
|
||||
|
||||
const log = new Logger('useOnImageUpload')
|
||||
|
||||
/**
|
||||
* Provides a callback that sends a {@link File file} to the editor via iframe communication.
|
||||
*
|
||||
* @param lineIndex The index of the line in the markdown content where the placeholder is defined
|
||||
* @param placeholderIndexInLine The index of the placeholder in the markdown content line
|
||||
*/
|
||||
export const useOnImageUpload = (
|
||||
lineIndex: number | undefined,
|
||||
placeholderIndexInLine: number | undefined
|
||||
): ((file: File) => void) => {
|
||||
const communicator = useRendererToEditorCommunicator()
|
||||
|
||||
return useCallback(
|
||||
(file: File) => {
|
||||
readFile(file, FileContentFormat.DATA_URL)
|
||||
.then((dataUri) => {
|
||||
communicator.sendMessageToOtherSide({
|
||||
type: CommunicationMessageType.IMAGE_UPLOAD,
|
||||
dataUri,
|
||||
fileName: file.name,
|
||||
lineIndex,
|
||||
placeholderIndexInLine
|
||||
})
|
||||
})
|
||||
.catch((error: ProgressEvent) => log.error('Error while uploading image', error))
|
||||
},
|
||||
[communicator, placeholderIndexInLine, lineIndex]
|
||||
)
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { calculatePlaceholderContainerSize } from '../utils/build-placeholder-size-css'
|
||||
import type { CSSProperties } from 'react'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
/**
|
||||
* Creates the style attribute for a placeholder container with width and height.
|
||||
*
|
||||
* @param width The wanted width
|
||||
* @param height The wanted height
|
||||
* @return The created style attributes
|
||||
*/
|
||||
export const usePlaceholderSizeStyle = (width?: string | number, height?: string | number): CSSProperties => {
|
||||
return useMemo(() => {
|
||||
const [convertedWidth, convertedHeight] = calculatePlaceholderContainerSize(width, height)
|
||||
|
||||
return {
|
||||
width: `${convertedWidth}px`,
|
||||
height: `${convertedHeight}px`
|
||||
}
|
||||
}, [height, width])
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { AppExtension } from '../../../../extensions/base/app-extension'
|
||||
import type { CheatsheetExtension } from '../../../editor-page/cheatsheet/cheatsheet-extension'
|
||||
import { basicCompletion } from '../../../editor-page/editor-pane/autocompletions/basic-completion'
|
||||
import type { MarkdownRendererExtension } from '../base/markdown-renderer-extension'
|
||||
import { ImagePlaceholderMarkdownExtension } from './image-placeholder-markdown-extension'
|
||||
import type { CompletionSource } from '@codemirror/autocomplete'
|
||||
import { t } from 'i18next'
|
||||
|
||||
export class ImagePlaceholderAppExtension extends AppExtension {
|
||||
buildMarkdownRendererExtensions(): MarkdownRendererExtension[] {
|
||||
return [new ImagePlaceholderMarkdownExtension()]
|
||||
}
|
||||
|
||||
buildCheatsheetExtensions(): CheatsheetExtension[] {
|
||||
return [
|
||||
{
|
||||
i18nKey: 'imagePlaceholder'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
buildAutocompletion(): CompletionSource[] {
|
||||
return [
|
||||
basicCompletion(/(^|\s)!\[?/, '', t('editor.autocompletions.image') ?? undefined),
|
||||
basicCompletion(
|
||||
/(^|\s)!\[?/,
|
||||
'',
|
||||
t('editor.autocompletions.imageWithDimensions') ?? undefined
|
||||
)
|
||||
]
|
||||
}
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { ComponentReplacer } from '../../replace-components/component-replacer'
|
||||
import { MarkdownRendererExtension } from '../base/markdown-renderer-extension'
|
||||
import { addLineToPlaceholderImageTags } from './add-line-to-placeholder-image-tags'
|
||||
import { ImagePlaceholderReplacer } from './image-placeholder-replacer'
|
||||
import type MarkdownIt from 'markdown-it/lib'
|
||||
|
||||
/**
|
||||
* Adds support for {@link ImagePlaceholder}.
|
||||
*/
|
||||
export class ImagePlaceholderMarkdownExtension extends MarkdownRendererExtension {
|
||||
public static readonly PLACEHOLDER_URL = 'https://'
|
||||
|
||||
configureMarkdownIt(markdownIt: MarkdownIt): void {
|
||||
addLineToPlaceholderImageTags(markdownIt)
|
||||
}
|
||||
|
||||
buildReplacers(): ComponentReplacer[] {
|
||||
return [new ImagePlaceholderReplacer()]
|
||||
}
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { NodeReplacement } from '../../replace-components/component-replacer'
|
||||
import { ComponentReplacer, DO_NOT_REPLACE } from '../../replace-components/component-replacer'
|
||||
import { ImagePlaceholder } from './image-placeholder'
|
||||
import { ImagePlaceholderMarkdownExtension } from './image-placeholder-markdown-extension'
|
||||
import type { Element } from 'domhandler'
|
||||
|
||||
/**
|
||||
* Replaces every image tag that has the {@link ImagePlaceholderMarkdownExtension.PLACEHOLDER_URL placeholder url} with the {@link ImagePlaceholder image placeholder element}.
|
||||
*/
|
||||
export class ImagePlaceholderReplacer extends ComponentReplacer {
|
||||
private countPerSourceLine = new Map<number, number>()
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
}
|
||||
|
||||
reset(): void {
|
||||
this.countPerSourceLine = new Map<number, number>()
|
||||
}
|
||||
|
||||
replace(node: Element): NodeReplacement {
|
||||
if (node.name !== 'img' || node.attribs?.src !== ImagePlaceholderMarkdownExtension.PLACEHOLDER_URL) {
|
||||
return DO_NOT_REPLACE
|
||||
}
|
||||
const lineIndex = Number(node.attribs['data-line'])
|
||||
const indexInLine = this.countPerSourceLine.get(lineIndex) ?? 0
|
||||
this.countPerSourceLine.set(lineIndex, indexInLine + 1)
|
||||
return (
|
||||
<ImagePlaceholder
|
||||
alt={node.attribs.alt}
|
||||
title={node.attribs.title}
|
||||
width={node.attribs.width}
|
||||
height={node.attribs.height}
|
||||
lineIndex={isNaN(lineIndex) ? undefined : lineIndex}
|
||||
placeholderIndexInLine={indexInLine}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
/*!
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
.image-drop {
|
||||
border: 3px dashed var(--bs-dark);
|
||||
|
||||
border-radius: 3px;
|
||||
transition: background-color 50ms, color 50ms;
|
||||
|
||||
.altText {
|
||||
text-overflow: ellipsis;
|
||||
flex: 1 1;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
|
@ -1,114 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { cypressId } from '../../../../utils/cypress-attribute'
|
||||
import { UiIcon } from '../../../common/icons/ui-icon'
|
||||
import { acceptedMimeTypes } from '../../../common/upload-image-mimetypes'
|
||||
import { useOnImageUpload } from './hooks/use-on-image-upload'
|
||||
import { usePlaceholderSizeStyle } from './hooks/use-placeholder-size-style'
|
||||
import styles from './image-placeholder.module.scss'
|
||||
import React, { useCallback, useMemo, useRef, useState } from 'react'
|
||||
import { Button } from 'react-bootstrap'
|
||||
import { Upload as IconUpload } from 'react-bootstrap-icons'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
|
||||
export interface PlaceholderImageFrameProps {
|
||||
alt?: string
|
||||
title?: string
|
||||
width?: string | number
|
||||
height?: string | number
|
||||
lineIndex?: number
|
||||
placeholderIndexInLine?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a placeholder for an actual image with the possibility to upload images via button or drag'n'drop.
|
||||
*
|
||||
* @param alt The alt text of the image. Will be shown in the placeholder
|
||||
* @param title The title text of the image. Will be shown in the placeholder
|
||||
* @param width The width of the placeholder
|
||||
* @param height The height of the placeholder
|
||||
* @param lineIndex The index of the line in the markdown content where the placeholder is defined
|
||||
* @param placeholderIndexInLine The index of the placeholder in the markdown line
|
||||
*/
|
||||
export const ImagePlaceholder: React.FC<PlaceholderImageFrameProps> = ({
|
||||
alt,
|
||||
title,
|
||||
width,
|
||||
height,
|
||||
lineIndex,
|
||||
placeholderIndexInLine
|
||||
}) => {
|
||||
useTranslation()
|
||||
const fileInputReference = useRef<HTMLInputElement>(null)
|
||||
const onImageUpload = useOnImageUpload(lineIndex, placeholderIndexInLine)
|
||||
|
||||
const [showDragStatus, setShowDragStatus] = useState(false)
|
||||
|
||||
const onDropHandler = useCallback(
|
||||
(event: React.DragEvent<HTMLSpanElement>) => {
|
||||
event.preventDefault()
|
||||
if (event?.dataTransfer?.files?.length > 0) {
|
||||
onImageUpload(event.dataTransfer.files[0])
|
||||
}
|
||||
},
|
||||
[onImageUpload]
|
||||
)
|
||||
|
||||
const onDragOverHandler = useCallback((event: React.DragEvent<HTMLSpanElement>) => {
|
||||
event.preventDefault()
|
||||
setShowDragStatus(true)
|
||||
}, [])
|
||||
|
||||
const onDragLeave = useCallback(() => {
|
||||
setShowDragStatus(false)
|
||||
}, [])
|
||||
|
||||
const onChangeHandler = useCallback(
|
||||
(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const fileList = event.target.files
|
||||
if (!fileList || fileList.length < 1) {
|
||||
return
|
||||
}
|
||||
onImageUpload(fileList[0])
|
||||
},
|
||||
[onImageUpload]
|
||||
)
|
||||
|
||||
const uploadButtonClicked = useCallback(() => fileInputReference.current?.click(), [])
|
||||
const containerStyle = usePlaceholderSizeStyle(width, height)
|
||||
|
||||
const containerDragClasses = useMemo(() => (showDragStatus ? 'bg-primary text-white' : 'text-dark'), [showDragStatus])
|
||||
|
||||
return (
|
||||
<span
|
||||
{...cypressId('image-placeholder-image-drop')}
|
||||
className={`${styles['image-drop']} d-inline-flex flex-column align-items-center ${containerDragClasses} p-1`}
|
||||
style={containerStyle}
|
||||
onDrop={onDropHandler}
|
||||
onDragOver={onDragOverHandler}
|
||||
onDragLeave={onDragLeave}>
|
||||
<input
|
||||
type='file'
|
||||
className='d-none'
|
||||
accept={acceptedMimeTypes}
|
||||
onChange={onChangeHandler}
|
||||
ref={fileInputReference}
|
||||
/>
|
||||
<div className={'align-items-center flex-column justify-content-center flex-fill d-flex'}>
|
||||
<div className={'d-flex flex-column'}>
|
||||
<span className='my-2'>
|
||||
<Trans i18nKey={'editor.embeddings.placeholderImage.placeholderText'} />
|
||||
</span>
|
||||
<span className={styles['altText']}>{alt ?? title ?? ''}</span>
|
||||
</div>
|
||||
</div>
|
||||
<Button size={'sm'} variant={'primary'} onClick={uploadButtonClicked}>
|
||||
<UiIcon icon={IconUpload} className='my-2' />
|
||||
<Trans i18nKey={'editor.embeddings.placeholderImage.upload'} className='my-2' />
|
||||
</Button>
|
||||
</span>
|
||||
)
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { calculatePlaceholderContainerSize, parseSizeNumber } from './build-placeholder-size-css'
|
||||
|
||||
describe('parseSizeNumber', () => {
|
||||
it('undefined', () => {
|
||||
expect(parseSizeNumber(undefined)).toBe(undefined)
|
||||
})
|
||||
it('zero as number', () => {
|
||||
expect(parseSizeNumber(0)).toBe(0)
|
||||
})
|
||||
it('positive number', () => {
|
||||
expect(parseSizeNumber(234)).toBe(234)
|
||||
})
|
||||
it('negative number', () => {
|
||||
expect(parseSizeNumber(-123)).toBe(-123)
|
||||
})
|
||||
it('zero as string', () => {
|
||||
expect(parseSizeNumber('0')).toBe(0)
|
||||
})
|
||||
it('negative number as string', () => {
|
||||
expect(parseSizeNumber('-123')).toBe(-123)
|
||||
})
|
||||
it('positive number as string', () => {
|
||||
expect(parseSizeNumber('345')).toBe(345)
|
||||
})
|
||||
it('positive number with px as string', () => {
|
||||
expect(parseSizeNumber('456px')).toBe(456)
|
||||
})
|
||||
it('negative number with px as string', () => {
|
||||
expect(parseSizeNumber('-456px')).toBe(-456)
|
||||
})
|
||||
})
|
||||
|
||||
describe('calculatePlaceholderContainerSize', () => {
|
||||
it('width undefined | height undefined', () => {
|
||||
expect(calculatePlaceholderContainerSize(undefined, undefined)).toStrictEqual([500, 200])
|
||||
})
|
||||
it('width 200 | height undefined', () => {
|
||||
expect(calculatePlaceholderContainerSize(200, undefined)).toStrictEqual([200, 80])
|
||||
})
|
||||
it('width undefined | height 100', () => {
|
||||
expect(calculatePlaceholderContainerSize(undefined, 100)).toStrictEqual([250, 100])
|
||||
})
|
||||
it('width "0" | height 0', () => {
|
||||
expect(calculatePlaceholderContainerSize('0', 0)).toStrictEqual([0, 0])
|
||||
})
|
||||
it('width 0 | height "0"', () => {
|
||||
expect(calculatePlaceholderContainerSize(0, '0')).toStrictEqual([0, 0])
|
||||
})
|
||||
it('width -345 | height 234', () => {
|
||||
expect(calculatePlaceholderContainerSize(-345, 234)).toStrictEqual([-345, 234])
|
||||
})
|
||||
it('width 345 | height -234', () => {
|
||||
expect(calculatePlaceholderContainerSize(345, -234)).toStrictEqual([345, -234])
|
||||
})
|
||||
it('width "-345" | height -234', () => {
|
||||
expect(calculatePlaceholderContainerSize('-345', -234)).toStrictEqual([-345, -234])
|
||||
})
|
||||
it('width -345 | height "-234"', () => {
|
||||
expect(calculatePlaceholderContainerSize(-345, '-234')).toStrictEqual([-345, -234])
|
||||
})
|
||||
})
|
|
@ -1,66 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
const regex = /^(-?[0-9]+)px$/
|
||||
|
||||
/**
|
||||
* Inspects the given value and checks if it is a number or a pixel size string.
|
||||
*
|
||||
* @param value The value to check
|
||||
* @return the number representation of the string or undefined if it couldn't be parsed
|
||||
*/
|
||||
export const parseSizeNumber = (value: string | number | undefined): number | undefined => {
|
||||
if (value === undefined) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
if (typeof value === 'number') {
|
||||
return value
|
||||
}
|
||||
|
||||
const regexMatches = regex.exec(value)
|
||||
if (regexMatches !== null) {
|
||||
if (regexMatches && regexMatches.length > 1) {
|
||||
return parseInt(regexMatches[1])
|
||||
} else {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
if (!Number.isNaN(value)) {
|
||||
return parseInt(value)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the final width and height for a placeholder container.
|
||||
* Every parameter that is empty will be defaulted using a 500:200 ratio.
|
||||
*
|
||||
* @param width The wanted width
|
||||
* @param height The wanted height
|
||||
* @return the calculated size
|
||||
*/
|
||||
export const calculatePlaceholderContainerSize = (
|
||||
width: string | number | undefined,
|
||||
height: string | number | undefined
|
||||
): [width: number, height: number] => {
|
||||
const defaultWidth = 500
|
||||
const defaultHeight = 200
|
||||
const ratio = defaultWidth / defaultHeight
|
||||
|
||||
const convertedWidth = parseSizeNumber(width)
|
||||
const convertedHeight = parseSizeNumber(height)
|
||||
|
||||
if (convertedWidth === undefined && convertedHeight !== undefined) {
|
||||
return [convertedHeight * ratio, convertedHeight]
|
||||
} else if (convertedWidth !== undefined && convertedHeight === undefined) {
|
||||
return [convertedWidth, convertedWidth * (1 / ratio)]
|
||||
} else if (convertedWidth !== undefined && convertedHeight !== undefined) {
|
||||
return [convertedWidth, convertedHeight]
|
||||
} else {
|
||||
return [defaultWidth, defaultHeight]
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { ComponentReplacer } from '../../replace-components/component-replacer'
|
||||
import { MarkdownRendererExtension } from '../base/markdown-renderer-extension'
|
||||
import { MarkdownRendererExtension } from '../_base-classes/markdown-renderer-extension'
|
||||
import { ProxyImageReplacer } from './proxy-image-replacer'
|
||||
|
||||
/**
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { ComponentReplacer } from '../../replace-components/component-replacer'
|
||||
import { MarkdownRendererExtension } from '../base/markdown-renderer-extension'
|
||||
import { MarkdownRendererExtension } from '../_base-classes/markdown-renderer-extension'
|
||||
import type { LineMarkers } from './add-line-marker-markdown-it-plugin'
|
||||
import { addLineMarkerMarkdownItPlugin } from './add-line-marker-markdown-it-plugin'
|
||||
import { LinemarkerReplacer } from './linemarker-replacer'
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
import type { NodeProcessor } from '../../node-preprocessors/node-processor'
|
||||
import type { ComponentReplacer } from '../../replace-components/component-replacer'
|
||||
import { MarkdownRendererExtension } from '../base/markdown-renderer-extension'
|
||||
import { MarkdownRendererExtension } from '../_base-classes/markdown-renderer-extension'
|
||||
import { AnchorNodePreprocessor } from './anchor-node-preprocessor'
|
||||
import { JumpAnchorReplacer } from './jump-anchor-replacer'
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { MarkdownRendererExtension } from '../base/markdown-renderer-extension'
|
||||
import { MarkdownRendererExtension } from '../_base-classes/markdown-renderer-extension'
|
||||
import type MarkdownIt from 'markdown-it'
|
||||
import linkify from 'markdown-it/lib/rules_core/linkify'
|
||||
import tlds from 'tlds'
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { NodeProcessor } from '../../node-preprocessors/node-processor'
|
||||
import { MarkdownRendererExtension } from '../base/markdown-renderer-extension'
|
||||
import { MarkdownRendererExtension } from '../_base-classes/markdown-renderer-extension'
|
||||
import { RevealCommentCommandNodePreprocessor } from './process-reveal-comment-nodes'
|
||||
import { addSlideSectionsMarkdownItPlugin } from './reveal-sections'
|
||||
import type MarkdownIt from 'markdown-it'
|
||||
|
|
|
@ -1,39 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { MarkdownRendererExtensionOptions } from '../../../../extensions/base/app-extension'
|
||||
import { AppExtension } from '../../../../extensions/base/app-extension'
|
||||
import type { CheatsheetExtension } from '../../../editor-page/cheatsheet/cheatsheet-extension'
|
||||
import { basicCompletion } from '../../../editor-page/editor-pane/autocompletions/basic-completion'
|
||||
import type { MarkdownRendererExtension } from '../base/markdown-renderer-extension'
|
||||
import { TableOfContentsMarkdownExtension } from './table-of-contents-markdown-extension'
|
||||
import type { CompletionSource } from '@codemirror/autocomplete'
|
||||
import { t } from 'i18next'
|
||||
|
||||
export class TableOfContentsAppExtension extends AppExtension {
|
||||
buildMarkdownRendererExtensions(options: MarkdownRendererExtensionOptions): MarkdownRendererExtension[] {
|
||||
return [new TableOfContentsMarkdownExtension(options.eventEmitter)]
|
||||
}
|
||||
|
||||
buildCheatsheetExtensions(): CheatsheetExtension[] {
|
||||
return [
|
||||
{
|
||||
i18nKey: 'toc',
|
||||
entries: [
|
||||
{
|
||||
i18nKey: 'basic'
|
||||
},
|
||||
{
|
||||
i18nKey: 'levelLimit'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
buildAutocompletion(): CompletionSource[] {
|
||||
return [basicCompletion(/\[(?:t|to|toc)?/, '[toc]', t('editor.autocompletions.toc') ?? undefined)]
|
||||
}
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { tocSlugify } from '../../../editor-page/table-of-contents/toc-slugify'
|
||||
import { EventMarkdownRendererExtension } from '../base/event-markdown-renderer-extension'
|
||||
import type { TocAst } from '@hedgedoc/markdown-it-plugins'
|
||||
import { toc } from '@hedgedoc/markdown-it-plugins'
|
||||
import equal from 'fast-deep-equal'
|
||||
import type MarkdownIt from 'markdown-it'
|
||||
|
||||
/**
|
||||
* Adds table of content to the markdown rendering.
|
||||
*/
|
||||
export class TableOfContentsMarkdownExtension extends EventMarkdownRendererExtension {
|
||||
public static readonly EVENT_NAME = 'TocChange'
|
||||
private lastAst: TocAst | undefined = undefined
|
||||
|
||||
public configureMarkdownIt(markdownIt: MarkdownIt): void {
|
||||
const eventEmitter = this.eventEmitter
|
||||
if (eventEmitter !== undefined) {
|
||||
toc(markdownIt, {
|
||||
listType: 'ul',
|
||||
level: [1, 2, 3],
|
||||
callback: (ast: TocAst): void => {
|
||||
if (equal(ast, this.lastAst)) {
|
||||
return
|
||||
}
|
||||
this.lastAst = ast
|
||||
eventEmitter.emit(TableOfContentsMarkdownExtension.EVENT_NAME, ast)
|
||||
},
|
||||
slugify: tocSlugify
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,8 +3,8 @@
|
|||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { usePlaceholderSizeStyle } from '../../../../extensions/essential-app-extensions/image-placeholder/hooks/use-placeholder-size-style'
|
||||
import { UiIcon } from '../../../common/icons/ui-icon'
|
||||
import { usePlaceholderSizeStyle } from '../image-placeholder/hooks/use-placeholder-size-style'
|
||||
import React from 'react'
|
||||
import { GearFill as IconGearFill } from 'react-bootstrap-icons'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { ComponentReplacer } from '../../replace-components/component-replacer'
|
||||
import { MarkdownRendererExtension } from '../base/markdown-renderer-extension'
|
||||
import { MarkdownRendererExtension } from '../_base-classes/markdown-renderer-extension'
|
||||
import { UploadIndicatingImageFrameReplacer } from './upload-indicating-image-frame-replacer'
|
||||
|
||||
/**
|
||||
|
|
|
@ -3,10 +3,10 @@
|
|||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { allAppExtensions } from '../../../extensions/extra-integrations/all-app-extensions'
|
||||
import { allAppExtensions } from '../../../extensions/all-app-extensions'
|
||||
import { useFrontendConfig } from '../../common/frontend-config-context/use-frontend-config'
|
||||
import type { RendererType } from '../../render-page/window-post-message-communicator/rendering-message'
|
||||
import type { MarkdownRendererExtension } from '../extensions/base/markdown-renderer-extension'
|
||||
import type { MarkdownRendererExtension } from '../extensions/_base-classes/markdown-renderer-extension'
|
||||
import { DebuggerMarkdownExtension } from '../extensions/debugger-markdown-extension'
|
||||
import { ProxyImageMarkdownExtension } from '../extensions/image/proxy-image-markdown-extension'
|
||||
import { LinkAdjustmentMarkdownExtension } from '../extensions/link-replacer/link-adjustment-markdown-extension'
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { MarkdownRendererExtension } from '../../extensions/base/markdown-renderer-extension'
|
||||
import type { MarkdownRendererExtension } from '../../extensions/_base-classes/markdown-renderer-extension'
|
||||
import type { Document } from 'domhandler'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { MarkdownRendererExtension } from '../../extensions/base/markdown-renderer-extension'
|
||||
import type { MarkdownRendererExtension } from '../../extensions/_base-classes/markdown-renderer-extension'
|
||||
import MarkdownIt from 'markdown-it/lib'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { HtmlToReact } from '../../common/html-to-react/html-to-react'
|
||||
import type { MarkdownRendererExtension } from '../extensions/base/markdown-renderer-extension'
|
||||
import type { MarkdownRendererExtension } from '../extensions/_base-classes/markdown-renderer-extension'
|
||||
import { useCombinedNodePreprocessor } from './hooks/use-combined-node-preprocessor'
|
||||
import { useConfiguredMarkdownIt } from './hooks/use-configured-markdown-it'
|
||||
import { LineContentToLineIdMapper } from './utils/line-content-to-line-id-mapper'
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { MarkdownRendererExtension } from '../../extensions/base/markdown-renderer-extension'
|
||||
import { MarkdownRendererExtension } from '../../extensions/_base-classes/markdown-renderer-extension'
|
||||
import type { NodeProcessor } from '../../node-preprocessors/node-processor'
|
||||
import type { ComponentReplacer } from '../../replace-components/component-replacer'
|
||||
import { TestNodeProcessor } from './test-node-processor'
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { StoreProvider } from '../../../redux/store-provider'
|
||||
import type { MarkdownRendererExtension } from '../extensions/base/markdown-renderer-extension'
|
||||
import type { MarkdownRendererExtension } from '../extensions/_base-classes/markdown-renderer-extension'
|
||||
import { MarkdownToReact } from '../markdown-to-react/markdown-to-react'
|
||||
import React, { useMemo } from 'react'
|
||||
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
*/
|
||||
import { cypressId } from '../../../../utils/cypress-attribute'
|
||||
import type { ScrollProps } from '../../../editor-page/synced-scroll/scroll-props'
|
||||
import { HeadlineAnchorsMarkdownExtension } from '../../../markdown-renderer/extensions/headline-anchors-markdown-extension'
|
||||
import type { LineMarkers } from '../../../markdown-renderer/extensions/linemarker/add-line-marker-markdown-it-plugin'
|
||||
import { LinemarkerMarkdownExtension } from '../../../markdown-renderer/extensions/linemarker/linemarker-markdown-extension'
|
||||
import { useCalculateLineMarkerPosition } from '../../../markdown-renderer/hooks/use-calculate-line-marker-positions'
|
||||
|
@ -69,13 +68,7 @@ export const DocumentMarkdownRenderer: React.FC<DocumentMarkdownRendererProps> =
|
|||
const extensions = useMarkdownExtensions(
|
||||
baseUrl,
|
||||
RendererType.DOCUMENT,
|
||||
useMemo(
|
||||
() => [
|
||||
new HeadlineAnchorsMarkdownExtension(),
|
||||
new LinemarkerMarkdownExtension((values) => (currentLineMarkers.current = values))
|
||||
],
|
||||
[]
|
||||
)
|
||||
useMemo(() => [new LinemarkerMarkdownExtension((values) => (currentLineMarkers.current = values))], [])
|
||||
)
|
||||
useCalculateLineMarkerPosition(markdownBodyRef, currentLineMarkers.current, recalculateLineMarkers)
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { TableOfContentsMarkdownExtension } from '../../../markdown-renderer/extensions/table-of-contents/table-of-contents-markdown-extension'
|
||||
import { TableOfContentsMarkdownExtension } from '../../../../extensions/essential-app-extensions/table-of-contents/table-of-contents-markdown-extension'
|
||||
import { useExtensionEventEmitterHandler } from '../../../markdown-renderer/hooks/use-extension-event-emitter'
|
||||
import styles from './markdown-document.module.scss'
|
||||
import { WidthBasedTableOfContents } from './width-based-table-of-contents'
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue