mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2024-11-21 17:26:29 -05:00
feat: add linter and linterGutter (#2237)
Signed-off-by: Philip Molares <philip.molares@udo.edu> Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de> Co-authored-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
parent
57cc08739d
commit
1bd18cc0ee
33 changed files with 471 additions and 182 deletions
|
@ -1,26 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
describe('YAML Array for deprecated syntax of document tags in frontmatter', () => {
|
||||
beforeEach(() => {
|
||||
cy.visitTestNote()
|
||||
})
|
||||
|
||||
it('is shown when using old syntax', () => {
|
||||
cy.setCodemirrorContent('---\ntags: a, b, c\n---')
|
||||
cy.getIframeBody().findByCypressId('yamlArrayDeprecationAlert').should('be.visible')
|
||||
})
|
||||
|
||||
it("isn't shown when using inline yaml-array", () => {
|
||||
cy.setCodemirrorContent("---\ntags: ['a', 'b', 'c']\n---")
|
||||
cy.getIframeBody().findByCypressId('yamlArrayDeprecationAlert').should('not.exist')
|
||||
})
|
||||
|
||||
it("isn't shown when using multi line yaml-array", () => {
|
||||
cy.setCodemirrorContent('---\ntags:\n - a\n - b\n - c\n---')
|
||||
cy.getIframeBody().findByCypressId('yamlArrayDeprecationAlert').should('not.exist')
|
||||
})
|
||||
})
|
|
@ -25,9 +25,6 @@
|
|||
"mermaid": {
|
||||
"unknownError": "Unknown rendering error. Please check your browser console."
|
||||
},
|
||||
"sequence": {
|
||||
"deprecationWarning": "The use of 'sequence' as code block language is deprecated."
|
||||
},
|
||||
"vega-lite": {
|
||||
"png": "Save as PNG",
|
||||
"svg": "Save as SVG",
|
||||
|
@ -228,6 +225,13 @@
|
|||
}
|
||||
},
|
||||
"editor": {
|
||||
"linter": {
|
||||
"defaultAction": "Fix",
|
||||
"sequence": "The use of 'sequence' as code block language is deprecated and will be removed in a future release.",
|
||||
"shortcode": "The {{shortcode}} short-code is deprecated and will be removed in a future release. Use a single line URL instead.",
|
||||
"frontmatter": "The yaml-metadata is invalid.",
|
||||
"frontmatter-tags": "The comma-separated definition of tags in the yaml-metadata is deprecated. Use a yaml-array instead."
|
||||
},
|
||||
"upload": {
|
||||
"uploadFile": {
|
||||
"withoutDescription": "Uploading file {{fileName}}",
|
||||
|
@ -238,8 +242,6 @@
|
|||
},
|
||||
"untitledNote": "Untitled",
|
||||
"placeholder": "← Start by entering a title here\n===\nVisit the features page if you don't know what to do.\nHappy hacking :)",
|
||||
"invalidYaml": "The yaml-header is invalid. See <0></0> for more information.",
|
||||
"deprecatedTags": "The comma-separated definition of tags in the yaml-metadata is deprecated. Use a yaml-array instead.",
|
||||
"infoToc": "Structure your note with headings to see a table-of-contents here.",
|
||||
"help": {
|
||||
"shortcuts": {
|
||||
|
|
|
@ -44,6 +44,7 @@
|
|||
"@codemirror/lang-markdown": "6.0.1",
|
||||
"@codemirror/language": "6.2.1",
|
||||
"@codemirror/language-data": "6.1.0",
|
||||
"@codemirror/lint": "6.0.0",
|
||||
"@codemirror/state": "6.1.0",
|
||||
"@codemirror/theme-one-dark": "6.0.0",
|
||||
"@codemirror/view": "6.1.2",
|
||||
|
|
|
@ -36,6 +36,13 @@ import { useInsertNoteContentIntoYTextInMockModeEffect } from './hooks/yjs/use-i
|
|||
import { useOnFirstEditorUpdateExtension } from './hooks/yjs/use-on-first-editor-update-extension'
|
||||
import { useIsConnectionSynced } from './hooks/yjs/use-is-connection-synced'
|
||||
import { useMarkdownContentYText } from './hooks/yjs/use-markdown-content-y-text'
|
||||
import { lintGutter } from '@codemirror/lint'
|
||||
import { useLinter } from './linter/linter'
|
||||
import { YoutubeMarkdownExtension } from '../../markdown-renderer/markdown-extension/youtube/youtube-markdown-extension'
|
||||
import { VimeoMarkdownExtension } from '../../markdown-renderer/markdown-extension/vimeo/vimeo-markdown-extension'
|
||||
import { SequenceDiagramMarkdownExtension } from '../../markdown-renderer/markdown-extension/sequence-diagram/sequence-diagram-markdown-extension'
|
||||
import { LegacyShortcodesMarkdownExtension } from '../../markdown-renderer/markdown-extension/legacy-short-codes/legacy-shortcodes-markdown-extension'
|
||||
import { FrontmatterLinter } from './linter/frontmatter-linter'
|
||||
|
||||
/**
|
||||
* Renders the text editor pane of the editor.
|
||||
|
@ -78,8 +85,23 @@ export const EditorPane: React.FC<ScrollProps> = ({ scrollState, onScroll, onMak
|
|||
const [firstEditorUpdateExtension, firstUpdateHappened] = useOnFirstEditorUpdateExtension()
|
||||
useInsertNoteContentIntoYTextInMockModeEffect(firstUpdateHappened, websocketConnection)
|
||||
|
||||
// ToDo: Don't initialize new extension array here, instead refactor to global extension array
|
||||
const markdownExtensionsLinters = useMemo(() => {
|
||||
return [
|
||||
new YoutubeMarkdownExtension(),
|
||||
new VimeoMarkdownExtension(),
|
||||
new SequenceDiagramMarkdownExtension(),
|
||||
new LegacyShortcodesMarkdownExtension()
|
||||
]
|
||||
.flatMap((extension) => extension.buildLinter())
|
||||
.concat(new FrontmatterLinter())
|
||||
}, [])
|
||||
const linter = useLinter(markdownExtensionsLinters)
|
||||
|
||||
const extensions = useMemo(
|
||||
() => [
|
||||
linter,
|
||||
lintGutter(),
|
||||
markdown({
|
||||
base: markdownLanguage,
|
||||
codeLanguages: (input) => findLanguageByCodeBlockName(languages, input)
|
||||
|
@ -95,6 +117,7 @@ export const EditorPane: React.FC<ScrollProps> = ({ scrollState, onScroll, onMak
|
|||
firstEditorUpdateExtension
|
||||
],
|
||||
[
|
||||
linter,
|
||||
editorScrollExtension,
|
||||
tablePasteExtensions,
|
||||
fileInsertExtension,
|
||||
|
|
|
@ -21,6 +21,11 @@
|
|||
display: none;
|
||||
}
|
||||
|
||||
.cm-diagnostic {
|
||||
max-width: 400px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
//workarounds for line break problem.. see https://github.com/yjs/y-codemirror.next/pull/12
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { mockI18n } from '../../../markdown-renderer/test-utils/mock-i18n'
|
||||
import type { Diagnostic } from '@codemirror/lint'
|
||||
import { mockEditorView } from './single-line-regex-linter.spec'
|
||||
import { FrontmatterLinter } from './frontmatter-linter'
|
||||
import { t } from 'i18next'
|
||||
|
||||
const testFrontmatterLinter = (
|
||||
editorContent: string,
|
||||
expectedDiagnostics: Partial<Diagnostic>,
|
||||
expectedReplacement?: string
|
||||
): void => {
|
||||
const frontmatterLinter = new FrontmatterLinter()
|
||||
const editorView = mockEditorView(editorContent)
|
||||
const calculatedDiagnostics = frontmatterLinter.lint(editorView)
|
||||
expect(calculatedDiagnostics).toHaveLength(1)
|
||||
expect(calculatedDiagnostics[0].from).toEqual(expectedDiagnostics.from)
|
||||
expect(calculatedDiagnostics[0].to).toEqual(expectedDiagnostics.to)
|
||||
expect(calculatedDiagnostics[0].severity).toEqual(expectedDiagnostics.severity)
|
||||
if (expectedReplacement !== undefined) {
|
||||
const spy = jest.spyOn(editorView, 'dispatch')
|
||||
expect(calculatedDiagnostics[0].actions).toHaveLength(1)
|
||||
expect(calculatedDiagnostics[0].actions?.[0].name).toEqual(t('editor.linter.defaultAction'))
|
||||
calculatedDiagnostics[0].actions?.[0].apply(editorView, calculatedDiagnostics[0].from, calculatedDiagnostics[0].to)
|
||||
expect(spy).toHaveBeenCalledWith({
|
||||
changes: {
|
||||
from: calculatedDiagnostics[0].from,
|
||||
to: calculatedDiagnostics[0].to,
|
||||
insert: expectedReplacement
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
describe('FrontmatterLinter', () => {
|
||||
beforeAll(async () => {
|
||||
await mockI18n()
|
||||
})
|
||||
describe('with invalid tags', () => {
|
||||
it('(one)', () => {
|
||||
testFrontmatterLinter(
|
||||
'---\ntags: a\n---',
|
||||
{
|
||||
from: 4,
|
||||
to: 11,
|
||||
severity: 'warning'
|
||||
},
|
||||
'tags:\n- a'
|
||||
)
|
||||
})
|
||||
it('(one, but a number)', () => {
|
||||
testFrontmatterLinter(
|
||||
'---\ntags: 1\n---',
|
||||
{
|
||||
from: 4,
|
||||
to: 11,
|
||||
severity: 'warning'
|
||||
},
|
||||
'tags:\n- 1'
|
||||
)
|
||||
})
|
||||
it('(multiple)', () => {
|
||||
testFrontmatterLinter(
|
||||
'---\ntags: 123, a\n---',
|
||||
{
|
||||
from: 4,
|
||||
to: 16,
|
||||
severity: 'warning'
|
||||
},
|
||||
'tags:\n- 123\n- a'
|
||||
)
|
||||
})
|
||||
})
|
||||
it('with invalid yaml', () => {
|
||||
testFrontmatterLinter('---\n1\n 2: 3\n---', {
|
||||
from: 0,
|
||||
to: 16,
|
||||
severity: 'error'
|
||||
})
|
||||
})
|
||||
})
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { Linter } from './linter'
|
||||
import type { EditorView } from '@codemirror/view'
|
||||
import type { Diagnostic } from '@codemirror/lint'
|
||||
import { extractFrontmatter } from '../../../../redux/note-details/frontmatter-extractor/extractor'
|
||||
import { load } from 'js-yaml'
|
||||
import type { RawNoteFrontmatter } from '../../../../redux/note-details/raw-note-frontmatter-parser/types'
|
||||
import { t } from 'i18next'
|
||||
|
||||
/**
|
||||
* Creates a {@link Linter linter} for the yaml frontmatter.
|
||||
*
|
||||
* It checks that the yaml is valid and that the tags are not using the deprecated comma-seperated list syntax.
|
||||
*/
|
||||
export class FrontmatterLinter implements Linter {
|
||||
lint(view: EditorView): Diagnostic[] {
|
||||
const lines = view.state.doc.toString().split('\n')
|
||||
const frontmatterExtraction = extractFrontmatter(lines)
|
||||
if (!frontmatterExtraction.isPresent) {
|
||||
return []
|
||||
}
|
||||
const frontmatterLines = lines.slice(0, frontmatterExtraction.lineOffset + 1)
|
||||
const rawNoteFrontmatter = FrontmatterLinter.loadYaml(frontmatterExtraction.rawText)
|
||||
if (rawNoteFrontmatter === undefined) {
|
||||
return [
|
||||
{
|
||||
from: 0,
|
||||
to: frontmatterLines.join('\n').length,
|
||||
message: t('editor.linter.frontmatter'),
|
||||
severity: 'error'
|
||||
}
|
||||
]
|
||||
}
|
||||
if (typeof rawNoteFrontmatter.tags !== 'string' && typeof rawNoteFrontmatter.tags !== 'number') {
|
||||
return []
|
||||
}
|
||||
const tags: string[] =
|
||||
rawNoteFrontmatter?.tags
|
||||
.toString()
|
||||
.split(',')
|
||||
.map((entry) => entry.trim()) ?? []
|
||||
const replacedText = 'tags:\n- ' + tags.join('\n- ')
|
||||
const tagsLineIndex = frontmatterLines.findIndex((value) => value.startsWith('tags: '))
|
||||
const linesBeforeTagsLine = frontmatterLines.slice(0, tagsLineIndex)
|
||||
const from = linesBeforeTagsLine.join('\n').length + 1
|
||||
const to = from + frontmatterLines[tagsLineIndex].length
|
||||
return [
|
||||
{
|
||||
from: from,
|
||||
to: to,
|
||||
actions: [
|
||||
{
|
||||
name: t('editor.linter.defaultAction'),
|
||||
apply: (view: EditorView, from: number, to: number) => {
|
||||
view.dispatch({
|
||||
changes: { from, to, insert: replacedText }
|
||||
})
|
||||
}
|
||||
}
|
||||
],
|
||||
message: t('editor.linter.frontmatter-tags'),
|
||||
severity: 'warning'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
private static loadYaml(raw: string): RawNoteFrontmatter | undefined {
|
||||
try {
|
||||
return load(raw) as RawNoteFrontmatter
|
||||
} catch {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
}
|
30
src/components/editor-page/editor-pane/linter/linter.ts
Normal file
30
src/components/editor-page/editor-pane/linter/linter.ts
Normal file
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import type { Diagnostic } from '@codemirror/lint'
|
||||
import { linter } from '@codemirror/lint'
|
||||
import { useMemo } from 'react'
|
||||
import type { Extension } from '@codemirror/state'
|
||||
import type { EditorView } from '@codemirror/view'
|
||||
|
||||
/**
|
||||
* The Linter interface.
|
||||
*
|
||||
* This should be implemented by each linter we want to use.
|
||||
*/
|
||||
export interface Linter {
|
||||
lint(view: EditorView): Diagnostic[]
|
||||
}
|
||||
|
||||
/**
|
||||
* The hook to create a single codemirror linter from all our linters.
|
||||
*
|
||||
* @param linters The {@link Linter linters} to use for the codemirror linter.
|
||||
* @return The build codemirror linter
|
||||
*/
|
||||
export const useLinter = (linters: Linter[]): Extension => {
|
||||
return useMemo(() => linter((view) => linters.flatMap((aLinter) => aLinter.lint(view))), [linters])
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { SingleLineRegexLinter } from './single-line-regex-linter'
|
||||
import type { Diagnostic } from '@codemirror/lint'
|
||||
import { mockI18n } from '../../../markdown-renderer/test-utils/mock-i18n'
|
||||
import type { EditorView } from '@codemirror/view'
|
||||
import { Mock } from 'ts-mockery'
|
||||
import type { EditorState, Text } from '@codemirror/state'
|
||||
|
||||
export const mockEditorView = (content: string): EditorView => {
|
||||
const docMock = Mock.of<Text>()
|
||||
docMock.toString = () => content
|
||||
return Mock.of<EditorView>({
|
||||
state: Mock.of<EditorState>({
|
||||
doc: docMock
|
||||
}),
|
||||
dispatch: jest.fn()
|
||||
})
|
||||
}
|
||||
|
||||
const testSingleLineRegexLinter = (
|
||||
regex: RegExp,
|
||||
replace: (match: string) => string,
|
||||
content: string,
|
||||
expectedDiagnostics: Partial<Diagnostic>[]
|
||||
): void => {
|
||||
const testMessage = 'message'
|
||||
const testActionLabel = 'actionLabel'
|
||||
const singleLineRegexLinter = new SingleLineRegexLinter(regex, testMessage, replace, testActionLabel)
|
||||
const editorView = mockEditorView(content)
|
||||
const calculatedDiagnostics = singleLineRegexLinter.lint(editorView)
|
||||
expect(calculatedDiagnostics).toHaveLength(expectedDiagnostics.length)
|
||||
calculatedDiagnostics.forEach((calculatedDiagnostic, index) => {
|
||||
expect(calculatedDiagnostic.from).toEqual(expectedDiagnostics[index].from)
|
||||
expect(calculatedDiagnostic.to).toEqual(expectedDiagnostics[index].to)
|
||||
expect(calculatedDiagnostic.message).toEqual(testMessage)
|
||||
expect(calculatedDiagnostic.severity).toEqual('warning')
|
||||
expect(calculatedDiagnostic.actions).toHaveLength(1)
|
||||
expect(calculatedDiagnostic.actions?.[0].name).toEqual(testActionLabel)
|
||||
})
|
||||
}
|
||||
|
||||
describe('SingleLineRegexLinter', () => {
|
||||
beforeAll(async () => {
|
||||
await mockI18n()
|
||||
})
|
||||
it('works for a simple regex', () => {
|
||||
testSingleLineRegexLinter(/^foo$/, () => 'bar', 'This\nis\na\ntest\nfoo\nbar\n123', [
|
||||
{
|
||||
from: 15,
|
||||
to: 18
|
||||
}
|
||||
])
|
||||
})
|
||||
it('works for a multiple hits', () => {
|
||||
testSingleLineRegexLinter(/^foo$/, () => 'bar', 'This\nfoo\na\ntest\nfoo\nbar\n123', [
|
||||
{
|
||||
from: 5,
|
||||
to: 8
|
||||
},
|
||||
{
|
||||
from: 16,
|
||||
to: 19
|
||||
}
|
||||
])
|
||||
})
|
||||
it('work if there are no hits', () => {
|
||||
testSingleLineRegexLinter(/^nothing$/, () => 'bar', 'This\nfoo\na\ntest\nfoo\nbar\n123', [])
|
||||
})
|
||||
})
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import type { Linter } from './linter'
|
||||
import type { EditorView } from '@codemirror/view'
|
||||
import type { Diagnostic } from '@codemirror/lint'
|
||||
import { t } from 'i18next'
|
||||
|
||||
interface LineWithStartIndex {
|
||||
line: string
|
||||
startIndex: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link Linter linter} from {@link RegExp regexp} for single lines.
|
||||
*
|
||||
* @param regex The {@link RegExp regexp} to execute on each line
|
||||
* @param message The message to display if the {@link RegExp regexp} hits
|
||||
* @param replace The function to replace what was found by {@link RegExp regexp}
|
||||
* @param actionLabel The optional label to translate and use as the fix button for the linter.
|
||||
*/
|
||||
export class SingleLineRegexLinter implements Linter {
|
||||
constructor(
|
||||
private regex: RegExp,
|
||||
private message: string,
|
||||
private replace: (match: string) => string,
|
||||
private actionLabel?: string
|
||||
) {}
|
||||
|
||||
lint(view: EditorView): Diagnostic[] {
|
||||
return view.state.doc
|
||||
.toString()
|
||||
.split('\n')
|
||||
.reduce(
|
||||
(state, line, lineIndex, lines) => [
|
||||
...state,
|
||||
{
|
||||
line,
|
||||
startIndex: lineIndex === 0 ? 0 : state[lineIndex - 1].startIndex + lines[lineIndex - 1].length + 1
|
||||
} as LineWithStartIndex
|
||||
],
|
||||
[] as LineWithStartIndex[]
|
||||
)
|
||||
.map(({ line, startIndex }) => ({
|
||||
lineStartIndex: startIndex,
|
||||
regexResult: this.regex.exec(line)
|
||||
}))
|
||||
.filter(({ regexResult }) => regexResult !== null && regexResult.length !== 0)
|
||||
.map(({ lineStartIndex, regexResult }) => this.createDiagnostic(lineStartIndex, regexResult as RegExpExecArray))
|
||||
}
|
||||
|
||||
private createDiagnostic(from: number, found: RegExpExecArray): Diagnostic {
|
||||
const replacedText = this.replace(found[1])
|
||||
return {
|
||||
from: from,
|
||||
to: from + found[0].length,
|
||||
actions: [
|
||||
{
|
||||
name: t(this.actionLabel ?? 'editor.linter.defaultAction'),
|
||||
apply: (view: EditorView, from: number, to: number) => {
|
||||
view.dispatch({
|
||||
changes: { from, to, insert: replacedText }
|
||||
})
|
||||
}
|
||||
}
|
||||
],
|
||||
message: this.message,
|
||||
severity: 'warning'
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import React from 'react'
|
||||
import { Alert } from 'react-bootstrap'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import links from '../../../links.json'
|
||||
import { TranslatedExternalLink } from '../../common/links/translated-external-link'
|
||||
import { ShowIf } from '../../common/show-if/show-if'
|
||||
import type { CommonModalProps } from '../../common/modals/common-modal'
|
||||
import { cypressId } from '../../../utils/cypress-attribute'
|
||||
|
||||
/**
|
||||
* Renders an alert that indicated that the front matter tags property is using a deprecated format.
|
||||
*
|
||||
* @param show If the alert should be shown.
|
||||
*/
|
||||
export const YamlArrayDeprecationAlert: React.FC<Partial<CommonModalProps>> = ({ show }) => {
|
||||
useTranslation()
|
||||
|
||||
return (
|
||||
<ShowIf condition={!!show}>
|
||||
<Alert {...cypressId('yamlArrayDeprecationAlert')} className={'text-wrap'} variant='warning' dir='auto'>
|
||||
<span className={'text-wrap'}>
|
||||
<span className={'text-wrap'}>
|
||||
<Trans i18nKey='editor.deprecatedTags' />
|
||||
</span>
|
||||
</span>
|
||||
<br />
|
||||
<TranslatedExternalLink i18nKey={'common.readForMoreInfo'} href={links.faq} className={'text-primary'} />
|
||||
</Alert>
|
||||
</ShowIf>
|
||||
)
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import React from 'react'
|
||||
import { Alert } from 'react-bootstrap'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { InternalLink } from '../common/links/internal-link'
|
||||
import { ShowIf } from '../common/show-if/show-if'
|
||||
import type { SimpleAlertProps } from '../common/simple-alert/simple-alert-props'
|
||||
|
||||
/**
|
||||
* Renders an alert if the frontmatter yaml is invalid.
|
||||
*
|
||||
* @param show If this alert should be shown
|
||||
*/
|
||||
export const InvalidYamlAlert: React.FC<SimpleAlertProps> = ({ show }) => {
|
||||
useTranslation()
|
||||
|
||||
return (
|
||||
<ShowIf condition={show}>
|
||||
<Alert variant='warning' dir='auto'>
|
||||
<Trans i18nKey='editor.invalidYaml'>
|
||||
<InternalLink text='yaml-metadata' href='/n/yaml-metadata' className='text-primary' />
|
||||
</Trans>
|
||||
</Alert>
|
||||
</ShowIf>
|
||||
)
|
||||
}
|
|
@ -1,14 +1,17 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { MarkdownExtension } from '../markdown-extension'
|
||||
import type MarkdownIt from 'markdown-it'
|
||||
import { legacyPdfShortCode } from './replace-legacy-pdf-short-code'
|
||||
import { legacySlideshareShortCode } from './replace-legacy-slideshare-short-code'
|
||||
import { legacySpeakerdeckShortCode } from './replace-legacy-speakerdeck-short-code'
|
||||
import { legacyPdfRegex, legacyPdfShortCode } from './replace-legacy-pdf-short-code'
|
||||
import { legacySlideshareRegex, legacySlideshareShortCode } from './replace-legacy-slideshare-short-code'
|
||||
import { legacySpeakerdeckRegex, legacySpeakerdeckShortCode } from './replace-legacy-speakerdeck-short-code'
|
||||
import { SingleLineRegexLinter } from '../../../editor-page/editor-pane/linter/single-line-regex-linter'
|
||||
import type { Linter } from '../../../editor-page/editor-pane/linter/linter'
|
||||
import { t } from 'i18next'
|
||||
|
||||
/**
|
||||
* Adds support for legacy shortcodes (pdf, slideshare and speakerdeck) by replacing them with anchor elements.
|
||||
|
@ -19,4 +22,24 @@ export class LegacyShortcodesMarkdownExtension extends MarkdownExtension {
|
|||
legacySlideshareShortCode(markdownIt)
|
||||
legacySpeakerdeckShortCode(markdownIt)
|
||||
}
|
||||
|
||||
public buildLinter(): Linter[] {
|
||||
return [
|
||||
new SingleLineRegexLinter(
|
||||
legacySpeakerdeckRegex,
|
||||
t('editor.linter.shortcode', { shortcode: 'SpeakerDeck' }),
|
||||
(match: string) => `https://speakerdeck.com/${match}`
|
||||
),
|
||||
new SingleLineRegexLinter(
|
||||
legacySlideshareRegex,
|
||||
t('editor.linter.shortcode', { shortcode: 'SlideShare' }),
|
||||
(match: string) => `https://www.slideshare.net/${match}`
|
||||
),
|
||||
new SingleLineRegexLinter(
|
||||
legacyPdfRegex,
|
||||
t('editor.linter.shortcode', { shortcode: 'PDF' }),
|
||||
(match: string) => match
|
||||
)
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ import markdownItRegex from 'markdown-it-regex'
|
|||
import type MarkdownIt from 'markdown-it/lib'
|
||||
import type { RegexOptions } from '../../../../external-types/markdown-it-regex/interface'
|
||||
|
||||
const finalRegex = /^{%pdf (\S*) *%}$/
|
||||
export const legacyPdfRegex = /^{%pdf (\S*) *%}$/
|
||||
|
||||
/**
|
||||
* Configure the given {@link MarkdownIt} to render legacy hedgedoc 1 pdf shortcodes as html links.
|
||||
|
@ -18,7 +18,7 @@ const finalRegex = /^{%pdf (\S*) *%}$/
|
|||
export const legacyPdfShortCode: MarkdownIt.PluginSimple = (markdownIt) => {
|
||||
markdownItRegex(markdownIt, {
|
||||
name: 'legacy-pdf-short-code',
|
||||
regex: finalRegex,
|
||||
regex: legacyPdfRegex,
|
||||
replace: (match) => `<a href="${match}">${match}</a>`
|
||||
} as RegexOptions)
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ import markdownItRegex from 'markdown-it-regex'
|
|||
import type MarkdownIt from 'markdown-it/lib'
|
||||
import type { RegexOptions } from '../../../../external-types/markdown-it-regex/interface'
|
||||
|
||||
const finalRegex = /^{%slideshare (\w+\/[\w-]+) ?%}$/
|
||||
export const legacySlideshareRegex = /^{%slideshare (\w+\/[\w-]+) ?%}$/
|
||||
|
||||
/**
|
||||
* Configure the given {@link MarkdownIt} to render legacy hedgedoc 1 slideshare shortcodes as HTML links.
|
||||
|
@ -18,7 +18,7 @@ const finalRegex = /^{%slideshare (\w+\/[\w-]+) ?%}$/
|
|||
export const legacySlideshareShortCode: MarkdownIt.PluginSimple = (markdownIt) => {
|
||||
markdownItRegex(markdownIt, {
|
||||
name: 'legacy-slideshare-short-code',
|
||||
regex: finalRegex,
|
||||
regex: legacySlideshareRegex,
|
||||
replace: (match) => `<a href='https://www.slideshare.net/${match}'>https://www.slideshare.net/${match}</a>`
|
||||
} as RegexOptions)
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ import markdownItRegex from 'markdown-it-regex'
|
|||
import type MarkdownIt from 'markdown-it/lib'
|
||||
import type { RegexOptions } from '../../../../external-types/markdown-it-regex/interface'
|
||||
|
||||
const finalRegex = /^{%speakerdeck (\w+\/[\w-]+) ?%}$/
|
||||
export const legacySpeakerdeckRegex = /^{%speakerdeck (\w+\/[\w-]+) ?%}$/
|
||||
|
||||
/**
|
||||
* Configure the given {@link MarkdownIt} to render legacy hedgedoc 1 speakerdeck shortcodes as HTML links.
|
||||
|
@ -18,7 +18,7 @@ const finalRegex = /^{%speakerdeck (\w+\/[\w-]+) ?%}$/
|
|||
export const legacySpeakerdeckShortCode: MarkdownIt.PluginSimple = (markdownIt) => {
|
||||
markdownItRegex(markdownIt, {
|
||||
name: 'legacy-speakerdeck-short-code',
|
||||
regex: finalRegex,
|
||||
regex: legacySpeakerdeckRegex,
|
||||
replace: (match) => `<a href="https://speakerdeck.com/${match}">https://speakerdeck.com/${match}</a>`
|
||||
} as RegexOptions)
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
import type MarkdownIt from 'markdown-it'
|
||||
import type { NodeProcessor } from '../node-preprocessors/node-processor'
|
||||
import type { ComponentReplacer } from '../replace-components/component-replacer'
|
||||
import type { Linter } from '../../editor-page/editor-pane/linter/linter'
|
||||
|
||||
/**
|
||||
* Base class for Markdown extensions.
|
||||
|
@ -33,4 +34,8 @@ export abstract class MarkdownExtension {
|
|||
public buildTagNameWhitelist(): string[] {
|
||||
return []
|
||||
}
|
||||
|
||||
public buildLinter(): Linter[] {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import React from 'react'
|
||||
import { Alert } from 'react-bootstrap'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import links from '../../../../links.json'
|
||||
import { cypressId } from '../../../../utils/cypress-attribute'
|
||||
import { TranslatedExternalLink } from '../../../common/links/translated-external-link'
|
||||
|
||||
/**
|
||||
* Renders a deprecation warning.
|
||||
*/
|
||||
export const DeprecationWarning: React.FC = () => {
|
||||
useTranslation()
|
||||
|
||||
return (
|
||||
<Alert {...cypressId('yaml')} className={'mt-2'} variant={'warning'}>
|
||||
<span className={'text-wrap'}>
|
||||
<Trans i18nKey={'renderer.sequence.deprecationWarning'} />
|
||||
</span>
|
||||
<br />
|
||||
<TranslatedExternalLink i18nKey={'common.readForMoreInfo'} className={'text-primary'} href={links.faq} />
|
||||
</Alert>
|
||||
)
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
@ -8,6 +8,9 @@ import { CodeBlockComponentReplacer } from '../../replace-components/code-block-
|
|||
import type { ComponentReplacer } from '../../replace-components/component-replacer'
|
||||
import { SequenceDiagram } from './sequence-diagram'
|
||||
import { CodeBlockMarkdownExtension } from '../code-block-markdown-extension/code-block-markdown-extension'
|
||||
import type { Linter } from '../../../editor-page/editor-pane/linter/linter'
|
||||
import { SingleLineRegexLinter } from '../../../editor-page/editor-pane/linter/single-line-regex-linter'
|
||||
import { t } from 'i18next'
|
||||
|
||||
/**
|
||||
* Adds legacy support for sequence diagram to the markdown rendering using code fences with "sequence" as language.
|
||||
|
@ -16,4 +19,8 @@ export class SequenceDiagramMarkdownExtension extends CodeBlockMarkdownExtension
|
|||
public buildReplacers(): ComponentReplacer[] {
|
||||
return [new CodeBlockComponentReplacer(SequenceDiagram, 'sequence')]
|
||||
}
|
||||
|
||||
public buildLinter(): Linter[] {
|
||||
return [new SingleLineRegexLinter(/```sequence/, t('editor.linter.sequence'), () => '```mermaid\nsequenceDiagram')]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import React, { Fragment } from 'react'
|
||||
import React from 'react'
|
||||
import type { CodeProps } from '../../replace-components/code-block-component-replacer'
|
||||
import { MermaidChart } from '../mermaid/mermaid-chart'
|
||||
import { DeprecationWarning } from './deprecation-warning'
|
||||
|
||||
/**
|
||||
* Renders a sequence diagram with a deprecation notice.
|
||||
|
@ -15,10 +14,5 @@ import { DeprecationWarning } from './deprecation-warning'
|
|||
* @param code the sequence diagram code
|
||||
*/
|
||||
export const SequenceDiagram: React.FC<CodeProps> = ({ code }) => {
|
||||
return (
|
||||
<Fragment>
|
||||
<DeprecationWarning />
|
||||
<MermaidChart code={'sequenceDiagram\n' + code} />
|
||||
</Fragment>
|
||||
)
|
||||
return <MermaidChart code={'sequenceDiagram\n' + code} />
|
||||
}
|
||||
|
|
|
@ -9,6 +9,8 @@ import { VimeoMarkdownExtension } from './vimeo-markdown-extension'
|
|||
import type MarkdownIt from 'markdown-it'
|
||||
import markdownItRegex from 'markdown-it-regex'
|
||||
|
||||
export const legacyVimeoRegex = /^{%vimeo ([\d]{6,11}) ?%}$/
|
||||
|
||||
/**
|
||||
* Configure the given {@link MarkdownIt} to render legacy hedgedoc 1 vimeo short codes as embeddings.
|
||||
*
|
||||
|
@ -16,7 +18,7 @@ import markdownItRegex from 'markdown-it-regex'
|
|||
*/
|
||||
const replaceLegacyVimeoShortCode: RegexOptions = {
|
||||
name: 'legacy-vimeo-short-code',
|
||||
regex: /^{%vimeo ([\d]{6,11}) ?%}$/,
|
||||
regex: legacyVimeoRegex,
|
||||
replace: (match) => {
|
||||
// ESLint wants to collapse this tag, but then the tag won't be valid html anymore.
|
||||
// noinspection CheckTagEmptyBody
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
@ -10,7 +10,10 @@ import type { ComponentReplacer } from '../../replace-components/component-repla
|
|||
import { CustomTagWithIdComponentReplacer } from '../../replace-components/custom-tag-with-id-component-replacer'
|
||||
import { replaceVimeoLinkMarkdownItPlugin } from './replace-vimeo-link'
|
||||
import { VimeoFrame } from './vimeo-frame'
|
||||
import { replaceLegacyVimeoShortCodeMarkdownItPlugin } from './replace-legacy-vimeo-short-code'
|
||||
import { legacyVimeoRegex, replaceLegacyVimeoShortCodeMarkdownItPlugin } from './replace-legacy-vimeo-short-code'
|
||||
import type { Linter } from '../../../editor-page/editor-pane/linter/linter'
|
||||
import { SingleLineRegexLinter } from '../../../editor-page/editor-pane/linter/single-line-regex-linter'
|
||||
import { t } from 'i18next'
|
||||
|
||||
/**
|
||||
* Adds vimeo video embeddings using link detection and the legacy vimeo short code syntax.
|
||||
|
@ -30,4 +33,14 @@ export class VimeoMarkdownExtension extends MarkdownExtension {
|
|||
public buildTagNameWhitelist(): string[] {
|
||||
return [VimeoMarkdownExtension.tagName]
|
||||
}
|
||||
|
||||
public buildLinter(): Linter[] {
|
||||
return [
|
||||
new SingleLineRegexLinter(
|
||||
legacyVimeoRegex,
|
||||
t('editor.linter.shortcode', { shortcode: 'Vimeo' }),
|
||||
(match: string) => `https://player.vimeo.com/video/${match}`
|
||||
)
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,8 @@ import { YoutubeMarkdownExtension } from './youtube-markdown-extension'
|
|||
import markdownItRegex from 'markdown-it-regex'
|
||||
import type MarkdownIt from 'markdown-it'
|
||||
|
||||
export const legacyYouTubeRegex = /^{%youtube ([^"&?\\/\s]{11}) ?%}$/
|
||||
|
||||
/**
|
||||
* Configure the given {@link MarkdownIt} to render legacy hedgedoc 1 youtube short codes as embeddings.
|
||||
*
|
||||
|
@ -17,7 +19,7 @@ import type MarkdownIt from 'markdown-it'
|
|||
export const replaceLegacyYoutubeShortCodeMarkdownItPlugin: MarkdownIt.PluginSimple = (markdownIt: MarkdownIt): void =>
|
||||
markdownItRegex(markdownIt, {
|
||||
name: 'legacy-youtube-short-code',
|
||||
regex: /^{%youtube ([^"&?\\/\s]{11}) ?%}$/,
|
||||
regex: legacyYouTubeRegex,
|
||||
replace: (match) => {
|
||||
// ESLint wants to collapse this tag, but then the tag won't be valid html anymore.
|
||||
// noinspection CheckTagEmptyBody
|
||||
|
|
|
@ -1,16 +1,19 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { MarkdownExtension } from '../markdown-extension'
|
||||
import { replaceYouTubeLinkMarkdownItPlugin } from './replace-youtube-link'
|
||||
import { replaceLegacyYoutubeShortCodeMarkdownItPlugin } from './replace-legacy-youtube-short-code'
|
||||
import { legacyYouTubeRegex, replaceLegacyYoutubeShortCodeMarkdownItPlugin } from './replace-legacy-youtube-short-code'
|
||||
import type MarkdownIt from 'markdown-it'
|
||||
import type { ComponentReplacer } from '../../replace-components/component-replacer'
|
||||
import { CustomTagWithIdComponentReplacer } from '../../replace-components/custom-tag-with-id-component-replacer'
|
||||
import { YouTubeFrame } from './youtube-frame'
|
||||
import type { Linter } from '../../../editor-page/editor-pane/linter/linter'
|
||||
import { SingleLineRegexLinter } from '../../../editor-page/editor-pane/linter/single-line-regex-linter'
|
||||
import { t } from 'i18next'
|
||||
|
||||
/**
|
||||
* Adds YouTube video embeddings using link detection and the legacy YouTube short code syntax.
|
||||
|
@ -30,4 +33,14 @@ export class YoutubeMarkdownExtension extends MarkdownExtension {
|
|||
public buildTagNameWhitelist(): string[] {
|
||||
return [YoutubeMarkdownExtension.tagName]
|
||||
}
|
||||
|
||||
public buildLinter(): Linter[] {
|
||||
return [
|
||||
new SingleLineRegexLinter(
|
||||
legacyYouTubeRegex,
|
||||
t('editor.linter.shortcode', { shortcode: 'YouTube' }),
|
||||
(match: string) => `https://www.youtube.com/watch?v=${match}`
|
||||
)
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,6 @@ import type { TocAst } from 'markdown-it-toc-done-right'
|
|||
import type { MutableRefObject } from 'react'
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import useResizeObserver from '@react-hook/resize-observer'
|
||||
import { YamlArrayDeprecationAlert } from '../editor-page/renderer-pane/yaml-array-deprecation-alert'
|
||||
import { useDocumentSyncScrolling } from './hooks/sync-scroll/use-document-sync-scrolling'
|
||||
import type { ScrollProps } from '../editor-page/synced-scroll/scroll-props'
|
||||
import { DocumentMarkdownRenderer } from '../markdown-renderer/document-markdown-renderer'
|
||||
|
@ -17,7 +16,6 @@ import styles from './markdown-document.module.scss'
|
|||
import { WidthBasedTableOfContents } from './width-based-table-of-contents'
|
||||
import { ShowIf } from '../common/show-if/show-if'
|
||||
import { useApplicationState } from '../../hooks/common/use-application-state'
|
||||
import { InvalidYamlAlert } from '../markdown-renderer/invalid-yaml-alert'
|
||||
import type { RendererFrontmatterInfo } from '../../redux/note-details/types/note-details'
|
||||
|
||||
export interface RendererProps extends ScrollProps {
|
||||
|
@ -106,8 +104,6 @@ export const MarkdownDocument: React.FC<MarkdownDocumentProps> = ({
|
|||
onTouchStart={onMakeScrollSource}>
|
||||
<div className={styles['markdown-document-side']} />
|
||||
<div className={styles['markdown-document-content']}>
|
||||
<InvalidYamlAlert show={!!frontmatterInfo?.frontmatterInvalid} />
|
||||
<YamlArrayDeprecationAlert show={!!frontmatterInfo?.deprecatedSyntax} />
|
||||
<DocumentMarkdownRenderer
|
||||
outerContainerRef={rendererRef}
|
||||
className={`mb-3 ${additionalRendererClasses ?? ''}`}
|
||||
|
|
|
@ -92,7 +92,6 @@ const buildStateFromFrontmatterUpdate = (
|
|||
title: generateNoteTitle(frontmatter, state.firstHeading),
|
||||
frontmatterRendererInfo: {
|
||||
lineOffset: frontmatterExtraction.lineOffset,
|
||||
deprecatedSyntax: frontmatter.deprecatedTagsSyntax,
|
||||
frontmatterInvalid: false,
|
||||
slideOptions: frontmatter.slideOptions
|
||||
}
|
||||
|
@ -105,7 +104,6 @@ const buildStateFromFrontmatterUpdate = (
|
|||
frontmatter: initialState.frontmatter,
|
||||
frontmatterRendererInfo: {
|
||||
lineOffset: frontmatterExtraction.lineOffset,
|
||||
deprecatedSyntax: false,
|
||||
frontmatterInvalid: true,
|
||||
slideOptions: initialState.frontmatterRendererInfo.slideOptions
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
@ -28,7 +28,6 @@ export const initialState: NoteDetails = {
|
|||
rawFrontmatter: '',
|
||||
frontmatterRendererInfo: {
|
||||
frontmatterInvalid: false,
|
||||
deprecatedSyntax: false,
|
||||
lineOffset: 0,
|
||||
slideOptions: initialSlideOptions
|
||||
},
|
||||
|
@ -50,7 +49,6 @@ export const initialState: NoteDetails = {
|
|||
title: '',
|
||||
description: '',
|
||||
tags: [],
|
||||
deprecatedTagsSyntax: false,
|
||||
robots: '',
|
||||
lang: 'en',
|
||||
dir: NoteTextDirection.LTR,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
@ -20,7 +20,6 @@ describe('yaml frontmatter', () => {
|
|||
it('should parse the deprecated tags syntax', () => {
|
||||
const noteFrontmatter = createNoteFrontmatterFromYaml('tags: test123, abc')
|
||||
expect(noteFrontmatter.tags).toEqual(['test123', 'abc'])
|
||||
expect(noteFrontmatter.deprecatedTagsSyntax).toEqual(true)
|
||||
})
|
||||
|
||||
it('should parse the tags list syntax', () => {
|
||||
|
@ -29,13 +28,11 @@ describe('yaml frontmatter', () => {
|
|||
- abc
|
||||
`)
|
||||
expect(noteFrontmatter.tags).toEqual(['test123', 'abc'])
|
||||
expect(noteFrontmatter.deprecatedTagsSyntax).toEqual(false)
|
||||
})
|
||||
|
||||
it('should parse the tag inline-list syntax', () => {
|
||||
const noteFrontmatter = createNoteFrontmatterFromYaml("tags: ['test123', 'abc']")
|
||||
expect(noteFrontmatter.tags).toEqual(['test123', 'abc'])
|
||||
expect(noteFrontmatter.deprecatedTagsSyntax).toEqual(false)
|
||||
})
|
||||
|
||||
it('should parse "breaks"', () => {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
@ -29,16 +29,12 @@ export const createNoteFrontmatterFromYaml = (rawYaml: string): NoteFrontmatter
|
|||
*/
|
||||
const parseRawNoteFrontmatter = (rawData: RawNoteFrontmatter): NoteFrontmatter => {
|
||||
let tags: string[]
|
||||
let deprecatedTagsSyntax: boolean
|
||||
if (typeof rawData?.tags === 'string') {
|
||||
tags = rawData?.tags?.split(',').map((entry) => entry.trim()) ?? []
|
||||
deprecatedTagsSyntax = true
|
||||
} else if (typeof rawData?.tags === 'object') {
|
||||
tags = rawData?.tags?.filter((tag) => tag !== null) ?? []
|
||||
deprecatedTagsSyntax = false
|
||||
} else {
|
||||
tags = [...initialState.frontmatter.tags]
|
||||
deprecatedTagsSyntax = false
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -53,8 +49,7 @@ const parseRawNoteFrontmatter = (rawData: RawNoteFrontmatter): NoteFrontmatter =
|
|||
dir: parseTextDirection(rawData),
|
||||
opengraph: parseOpenGraph(rawData),
|
||||
slideOptions: parseSlideOptions(rawData),
|
||||
tags,
|
||||
deprecatedTagsSyntax
|
||||
tags
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
@ -7,7 +7,7 @@
|
|||
export interface RawNoteFrontmatter {
|
||||
title: string | undefined
|
||||
description: string | undefined
|
||||
tags: string | string[] | undefined
|
||||
tags: string | number | string[] | undefined
|
||||
robots: string | undefined
|
||||
lang: string | undefined
|
||||
dir: string | undefined
|
||||
|
|
|
@ -81,7 +81,6 @@ describe('build state from set note data from server', () => {
|
|||
title: '',
|
||||
description: '',
|
||||
tags: [],
|
||||
deprecatedTagsSyntax: false,
|
||||
robots: '',
|
||||
lang: 'en',
|
||||
dir: NoteTextDirection.LTR,
|
||||
|
@ -100,7 +99,6 @@ describe('build state from set note data from server', () => {
|
|||
},
|
||||
frontmatterRendererInfo: {
|
||||
frontmatterInvalid: false,
|
||||
deprecatedSyntax: false,
|
||||
lineOffset: 0,
|
||||
slideOptions: initialSlideOptions
|
||||
},
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
@ -37,7 +37,6 @@ export interface NoteFrontmatter {
|
|||
title: string
|
||||
description: string
|
||||
tags: string[]
|
||||
deprecatedTagsSyntax: boolean
|
||||
robots: string
|
||||
lang: Iso6391Language
|
||||
dir: NoteTextDirection
|
||||
|
@ -62,6 +61,5 @@ export enum NoteType {
|
|||
export interface RendererFrontmatterInfo {
|
||||
lineOffset: number
|
||||
frontmatterInvalid: boolean
|
||||
deprecatedSyntax: boolean
|
||||
slideOptions: SlideOptions
|
||||
}
|
||||
|
|
|
@ -1655,7 +1655,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@codemirror/lint@npm:^6.0.0":
|
||||
"@codemirror/lint@npm:6.0.0, @codemirror/lint@npm:^6.0.0":
|
||||
version: 6.0.0
|
||||
resolution: "@codemirror/lint@npm:6.0.0"
|
||||
dependencies:
|
||||
|
@ -1902,6 +1902,7 @@ __metadata:
|
|||
"@codemirror/lang-markdown": 6.0.1
|
||||
"@codemirror/language": 6.2.1
|
||||
"@codemirror/language-data": 6.1.0
|
||||
"@codemirror/lint": 6.0.0
|
||||
"@codemirror/state": 6.1.0
|
||||
"@codemirror/theme-one-dark": 6.0.0
|
||||
"@codemirror/view": 6.1.2
|
||||
|
|
Loading…
Reference in a new issue