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:
Philip Molares 2022-07-31 18:25:03 +02:00 committed by GitHub
parent 57cc08739d
commit 1bd18cc0ee
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
33 changed files with 471 additions and 182 deletions

View file

@ -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')
})
})

View file

@ -25,9 +25,6 @@
"mermaid": { "mermaid": {
"unknownError": "Unknown rendering error. Please check your browser console." "unknownError": "Unknown rendering error. Please check your browser console."
}, },
"sequence": {
"deprecationWarning": "The use of 'sequence' as code block language is deprecated."
},
"vega-lite": { "vega-lite": {
"png": "Save as PNG", "png": "Save as PNG",
"svg": "Save as SVG", "svg": "Save as SVG",
@ -228,6 +225,13 @@
} }
}, },
"editor": { "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": { "upload": {
"uploadFile": { "uploadFile": {
"withoutDescription": "Uploading file {{fileName}}", "withoutDescription": "Uploading file {{fileName}}",
@ -238,8 +242,6 @@
}, },
"untitledNote": "Untitled", "untitledNote": "Untitled",
"placeholder": "← Start by entering a title here\n===\nVisit the features page if you don't know what to do.\nHappy hacking :)", "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.", "infoToc": "Structure your note with headings to see a table-of-contents here.",
"help": { "help": {
"shortcuts": { "shortcuts": {

View file

@ -44,6 +44,7 @@
"@codemirror/lang-markdown": "6.0.1", "@codemirror/lang-markdown": "6.0.1",
"@codemirror/language": "6.2.1", "@codemirror/language": "6.2.1",
"@codemirror/language-data": "6.1.0", "@codemirror/language-data": "6.1.0",
"@codemirror/lint": "6.0.0",
"@codemirror/state": "6.1.0", "@codemirror/state": "6.1.0",
"@codemirror/theme-one-dark": "6.0.0", "@codemirror/theme-one-dark": "6.0.0",
"@codemirror/view": "6.1.2", "@codemirror/view": "6.1.2",

View file

@ -36,6 +36,13 @@ import { useInsertNoteContentIntoYTextInMockModeEffect } from './hooks/yjs/use-i
import { useOnFirstEditorUpdateExtension } from './hooks/yjs/use-on-first-editor-update-extension' import { useOnFirstEditorUpdateExtension } from './hooks/yjs/use-on-first-editor-update-extension'
import { useIsConnectionSynced } from './hooks/yjs/use-is-connection-synced' import { useIsConnectionSynced } from './hooks/yjs/use-is-connection-synced'
import { useMarkdownContentYText } from './hooks/yjs/use-markdown-content-y-text' 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. * 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() const [firstEditorUpdateExtension, firstUpdateHappened] = useOnFirstEditorUpdateExtension()
useInsertNoteContentIntoYTextInMockModeEffect(firstUpdateHappened, websocketConnection) 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( const extensions = useMemo(
() => [ () => [
linter,
lintGutter(),
markdown({ markdown({
base: markdownLanguage, base: markdownLanguage,
codeLanguages: (input) => findLanguageByCodeBlockName(languages, input) codeLanguages: (input) => findLanguageByCodeBlockName(languages, input)
@ -95,6 +117,7 @@ export const EditorPane: React.FC<ScrollProps> = ({ scrollState, onScroll, onMak
firstEditorUpdateExtension firstEditorUpdateExtension
], ],
[ [
linter,
editorScrollExtension, editorScrollExtension,
tablePasteExtensions, tablePasteExtensions,
fileInsertExtension, fileInsertExtension,

View file

@ -21,6 +21,11 @@
display: none; display: none;
} }
.cm-diagnostic {
max-width: 400px;
padding: 10px;
}
//workarounds for line break problem.. see https://github.com/yjs/y-codemirror.next/pull/12 //workarounds for line break problem.. see https://github.com/yjs/y-codemirror.next/pull/12
} }
} }

View file

@ -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'
})
})
})

View file

@ -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
}
}
}

View 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])
}

View file

@ -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', [])
})
})

View file

@ -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'
}
}
}

View file

@ -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>
)
}

View file

@ -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>
)
}

View file

@ -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 * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import { MarkdownExtension } from '../markdown-extension' import { MarkdownExtension } from '../markdown-extension'
import type MarkdownIt from 'markdown-it' import type MarkdownIt from 'markdown-it'
import { legacyPdfShortCode } from './replace-legacy-pdf-short-code' import { legacyPdfRegex, legacyPdfShortCode } from './replace-legacy-pdf-short-code'
import { legacySlideshareShortCode } from './replace-legacy-slideshare-short-code' import { legacySlideshareRegex, legacySlideshareShortCode } from './replace-legacy-slideshare-short-code'
import { legacySpeakerdeckShortCode } from './replace-legacy-speakerdeck-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. * 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) legacySlideshareShortCode(markdownIt)
legacySpeakerdeckShortCode(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
)
]
}
} }

View file

@ -8,7 +8,7 @@ import markdownItRegex from 'markdown-it-regex'
import type MarkdownIt from 'markdown-it/lib' import type MarkdownIt from 'markdown-it/lib'
import type { RegexOptions } from '../../../../external-types/markdown-it-regex/interface' 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. * 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) => { export const legacyPdfShortCode: MarkdownIt.PluginSimple = (markdownIt) => {
markdownItRegex(markdownIt, { markdownItRegex(markdownIt, {
name: 'legacy-pdf-short-code', name: 'legacy-pdf-short-code',
regex: finalRegex, regex: legacyPdfRegex,
replace: (match) => `<a href="${match}">${match}</a>` replace: (match) => `<a href="${match}">${match}</a>`
} as RegexOptions) } as RegexOptions)
} }

View file

@ -8,7 +8,7 @@ import markdownItRegex from 'markdown-it-regex'
import type MarkdownIt from 'markdown-it/lib' import type MarkdownIt from 'markdown-it/lib'
import type { RegexOptions } from '../../../../external-types/markdown-it-regex/interface' 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. * 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) => { export const legacySlideshareShortCode: MarkdownIt.PluginSimple = (markdownIt) => {
markdownItRegex(markdownIt, { markdownItRegex(markdownIt, {
name: 'legacy-slideshare-short-code', name: 'legacy-slideshare-short-code',
regex: finalRegex, regex: legacySlideshareRegex,
replace: (match) => `<a href='https://www.slideshare.net/${match}'>https://www.slideshare.net/${match}</a>` replace: (match) => `<a href='https://www.slideshare.net/${match}'>https://www.slideshare.net/${match}</a>`
} as RegexOptions) } as RegexOptions)
} }

View file

@ -8,7 +8,7 @@ import markdownItRegex from 'markdown-it-regex'
import type MarkdownIt from 'markdown-it/lib' import type MarkdownIt from 'markdown-it/lib'
import type { RegexOptions } from '../../../../external-types/markdown-it-regex/interface' 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. * 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) => { export const legacySpeakerdeckShortCode: MarkdownIt.PluginSimple = (markdownIt) => {
markdownItRegex(markdownIt, { markdownItRegex(markdownIt, {
name: 'legacy-speakerdeck-short-code', name: 'legacy-speakerdeck-short-code',
regex: finalRegex, regex: legacySpeakerdeckRegex,
replace: (match) => `<a href="https://speakerdeck.com/${match}">https://speakerdeck.com/${match}</a>` replace: (match) => `<a href="https://speakerdeck.com/${match}">https://speakerdeck.com/${match}</a>`
} as RegexOptions) } as RegexOptions)
} }

View file

@ -7,6 +7,7 @@
import type MarkdownIt from 'markdown-it' import type MarkdownIt from 'markdown-it'
import type { NodeProcessor } from '../node-preprocessors/node-processor' import type { NodeProcessor } from '../node-preprocessors/node-processor'
import type { ComponentReplacer } from '../replace-components/component-replacer' import type { ComponentReplacer } from '../replace-components/component-replacer'
import type { Linter } from '../../editor-page/editor-pane/linter/linter'
/** /**
* Base class for Markdown extensions. * Base class for Markdown extensions.
@ -33,4 +34,8 @@ export abstract class MarkdownExtension {
public buildTagNameWhitelist(): string[] { public buildTagNameWhitelist(): string[] {
return [] return []
} }
public buildLinter(): Linter[] {
return []
}
} }

View file

@ -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>
)
}

View file

@ -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 * 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 type { ComponentReplacer } from '../../replace-components/component-replacer'
import { SequenceDiagram } from './sequence-diagram' import { SequenceDiagram } from './sequence-diagram'
import { CodeBlockMarkdownExtension } from '../code-block-markdown-extension/code-block-markdown-extension' 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. * 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[] { public buildReplacers(): ComponentReplacer[] {
return [new CodeBlockComponentReplacer(SequenceDiagram, 'sequence')] return [new CodeBlockComponentReplacer(SequenceDiagram, 'sequence')]
} }
public buildLinter(): Linter[] {
return [new SingleLineRegexLinter(/```sequence/, t('editor.linter.sequence'), () => '```mermaid\nsequenceDiagram')]
}
} }

View file

@ -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 * 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 type { CodeProps } from '../../replace-components/code-block-component-replacer'
import { MermaidChart } from '../mermaid/mermaid-chart' import { MermaidChart } from '../mermaid/mermaid-chart'
import { DeprecationWarning } from './deprecation-warning'
/** /**
* Renders a sequence diagram with a deprecation notice. * Renders a sequence diagram with a deprecation notice.
@ -15,10 +14,5 @@ import { DeprecationWarning } from './deprecation-warning'
* @param code the sequence diagram code * @param code the sequence diagram code
*/ */
export const SequenceDiagram: React.FC<CodeProps> = ({ code }) => { export const SequenceDiagram: React.FC<CodeProps> = ({ code }) => {
return ( return <MermaidChart code={'sequenceDiagram\n' + code} />
<Fragment>
<DeprecationWarning />
<MermaidChart code={'sequenceDiagram\n' + code} />
</Fragment>
)
} }

View file

@ -9,6 +9,8 @@ import { VimeoMarkdownExtension } from './vimeo-markdown-extension'
import type MarkdownIt from 'markdown-it' import type MarkdownIt from 'markdown-it'
import markdownItRegex from 'markdown-it-regex' 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. * 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 = { const replaceLegacyVimeoShortCode: RegexOptions = {
name: 'legacy-vimeo-short-code', name: 'legacy-vimeo-short-code',
regex: /^{%vimeo ([\d]{6,11}) ?%}$/, regex: legacyVimeoRegex,
replace: (match) => { replace: (match) => {
// ESLint wants to collapse this tag, but then the tag won't be valid html anymore. // ESLint wants to collapse this tag, but then the tag won't be valid html anymore.
// noinspection CheckTagEmptyBody // noinspection CheckTagEmptyBody

View file

@ -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 * 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 { CustomTagWithIdComponentReplacer } from '../../replace-components/custom-tag-with-id-component-replacer'
import { replaceVimeoLinkMarkdownItPlugin } from './replace-vimeo-link' import { replaceVimeoLinkMarkdownItPlugin } from './replace-vimeo-link'
import { VimeoFrame } from './vimeo-frame' 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. * 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[] { public buildTagNameWhitelist(): string[] {
return [VimeoMarkdownExtension.tagName] return [VimeoMarkdownExtension.tagName]
} }
public buildLinter(): Linter[] {
return [
new SingleLineRegexLinter(
legacyVimeoRegex,
t('editor.linter.shortcode', { shortcode: 'Vimeo' }),
(match: string) => `https://player.vimeo.com/video/${match}`
)
]
}
} }

View file

@ -9,6 +9,8 @@ import { YoutubeMarkdownExtension } from './youtube-markdown-extension'
import markdownItRegex from 'markdown-it-regex' import markdownItRegex from 'markdown-it-regex'
import type MarkdownIt from 'markdown-it' 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. * 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 => export const replaceLegacyYoutubeShortCodeMarkdownItPlugin: MarkdownIt.PluginSimple = (markdownIt: MarkdownIt): void =>
markdownItRegex(markdownIt, { markdownItRegex(markdownIt, {
name: 'legacy-youtube-short-code', name: 'legacy-youtube-short-code',
regex: /^{%youtube ([^"&?\\/\s]{11}) ?%}$/, regex: legacyYouTubeRegex,
replace: (match) => { replace: (match) => {
// ESLint wants to collapse this tag, but then the tag won't be valid html anymore. // ESLint wants to collapse this tag, but then the tag won't be valid html anymore.
// noinspection CheckTagEmptyBody // noinspection CheckTagEmptyBody

View file

@ -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 * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import { MarkdownExtension } from '../markdown-extension' import { MarkdownExtension } from '../markdown-extension'
import { replaceYouTubeLinkMarkdownItPlugin } from './replace-youtube-link' 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 MarkdownIt from 'markdown-it'
import type { ComponentReplacer } from '../../replace-components/component-replacer' import type { ComponentReplacer } from '../../replace-components/component-replacer'
import { CustomTagWithIdComponentReplacer } from '../../replace-components/custom-tag-with-id-component-replacer' import { CustomTagWithIdComponentReplacer } from '../../replace-components/custom-tag-with-id-component-replacer'
import { YouTubeFrame } from './youtube-frame' 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. * 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[] { public buildTagNameWhitelist(): string[] {
return [YoutubeMarkdownExtension.tagName] 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}`
)
]
}
} }

View file

@ -8,7 +8,6 @@ import type { TocAst } from 'markdown-it-toc-done-right'
import type { MutableRefObject } from 'react' import type { MutableRefObject } from 'react'
import React, { useEffect, useMemo, useRef, useState } from 'react' import React, { useEffect, useMemo, useRef, useState } from 'react'
import useResizeObserver from '@react-hook/resize-observer' 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 { useDocumentSyncScrolling } from './hooks/sync-scroll/use-document-sync-scrolling'
import type { ScrollProps } from '../editor-page/synced-scroll/scroll-props' import type { ScrollProps } from '../editor-page/synced-scroll/scroll-props'
import { DocumentMarkdownRenderer } from '../markdown-renderer/document-markdown-renderer' 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 { WidthBasedTableOfContents } from './width-based-table-of-contents'
import { ShowIf } from '../common/show-if/show-if' import { ShowIf } from '../common/show-if/show-if'
import { useApplicationState } from '../../hooks/common/use-application-state' 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' import type { RendererFrontmatterInfo } from '../../redux/note-details/types/note-details'
export interface RendererProps extends ScrollProps { export interface RendererProps extends ScrollProps {
@ -106,8 +104,6 @@ export const MarkdownDocument: React.FC<MarkdownDocumentProps> = ({
onTouchStart={onMakeScrollSource}> onTouchStart={onMakeScrollSource}>
<div className={styles['markdown-document-side']} /> <div className={styles['markdown-document-side']} />
<div className={styles['markdown-document-content']}> <div className={styles['markdown-document-content']}>
<InvalidYamlAlert show={!!frontmatterInfo?.frontmatterInvalid} />
<YamlArrayDeprecationAlert show={!!frontmatterInfo?.deprecatedSyntax} />
<DocumentMarkdownRenderer <DocumentMarkdownRenderer
outerContainerRef={rendererRef} outerContainerRef={rendererRef}
className={`mb-3 ${additionalRendererClasses ?? ''}`} className={`mb-3 ${additionalRendererClasses ?? ''}`}

View file

@ -92,7 +92,6 @@ const buildStateFromFrontmatterUpdate = (
title: generateNoteTitle(frontmatter, state.firstHeading), title: generateNoteTitle(frontmatter, state.firstHeading),
frontmatterRendererInfo: { frontmatterRendererInfo: {
lineOffset: frontmatterExtraction.lineOffset, lineOffset: frontmatterExtraction.lineOffset,
deprecatedSyntax: frontmatter.deprecatedTagsSyntax,
frontmatterInvalid: false, frontmatterInvalid: false,
slideOptions: frontmatter.slideOptions slideOptions: frontmatter.slideOptions
} }
@ -105,7 +104,6 @@ const buildStateFromFrontmatterUpdate = (
frontmatter: initialState.frontmatter, frontmatter: initialState.frontmatter,
frontmatterRendererInfo: { frontmatterRendererInfo: {
lineOffset: frontmatterExtraction.lineOffset, lineOffset: frontmatterExtraction.lineOffset,
deprecatedSyntax: false,
frontmatterInvalid: true, frontmatterInvalid: true,
slideOptions: initialState.frontmatterRendererInfo.slideOptions slideOptions: initialState.frontmatterRendererInfo.slideOptions
} }

View file

@ -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 * SPDX-License-Identifier: AGPL-3.0-only
*/ */
@ -28,7 +28,6 @@ export const initialState: NoteDetails = {
rawFrontmatter: '', rawFrontmatter: '',
frontmatterRendererInfo: { frontmatterRendererInfo: {
frontmatterInvalid: false, frontmatterInvalid: false,
deprecatedSyntax: false,
lineOffset: 0, lineOffset: 0,
slideOptions: initialSlideOptions slideOptions: initialSlideOptions
}, },
@ -50,7 +49,6 @@ export const initialState: NoteDetails = {
title: '', title: '',
description: '', description: '',
tags: [], tags: [],
deprecatedTagsSyntax: false,
robots: '', robots: '',
lang: 'en', lang: 'en',
dir: NoteTextDirection.LTR, dir: NoteTextDirection.LTR,

View file

@ -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 * SPDX-License-Identifier: AGPL-3.0-only
*/ */
@ -20,7 +20,6 @@ describe('yaml frontmatter', () => {
it('should parse the deprecated tags syntax', () => { it('should parse the deprecated tags syntax', () => {
const noteFrontmatter = createNoteFrontmatterFromYaml('tags: test123, abc') const noteFrontmatter = createNoteFrontmatterFromYaml('tags: test123, abc')
expect(noteFrontmatter.tags).toEqual(['test123', 'abc']) expect(noteFrontmatter.tags).toEqual(['test123', 'abc'])
expect(noteFrontmatter.deprecatedTagsSyntax).toEqual(true)
}) })
it('should parse the tags list syntax', () => { it('should parse the tags list syntax', () => {
@ -29,13 +28,11 @@ describe('yaml frontmatter', () => {
- abc - abc
`) `)
expect(noteFrontmatter.tags).toEqual(['test123', 'abc']) expect(noteFrontmatter.tags).toEqual(['test123', 'abc'])
expect(noteFrontmatter.deprecatedTagsSyntax).toEqual(false)
}) })
it('should parse the tag inline-list syntax', () => { it('should parse the tag inline-list syntax', () => {
const noteFrontmatter = createNoteFrontmatterFromYaml("tags: ['test123', 'abc']") const noteFrontmatter = createNoteFrontmatterFromYaml("tags: ['test123', 'abc']")
expect(noteFrontmatter.tags).toEqual(['test123', 'abc']) expect(noteFrontmatter.tags).toEqual(['test123', 'abc'])
expect(noteFrontmatter.deprecatedTagsSyntax).toEqual(false)
}) })
it('should parse "breaks"', () => { it('should parse "breaks"', () => {

View file

@ -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 * SPDX-License-Identifier: AGPL-3.0-only
*/ */
@ -29,16 +29,12 @@ export const createNoteFrontmatterFromYaml = (rawYaml: string): NoteFrontmatter
*/ */
const parseRawNoteFrontmatter = (rawData: RawNoteFrontmatter): NoteFrontmatter => { const parseRawNoteFrontmatter = (rawData: RawNoteFrontmatter): NoteFrontmatter => {
let tags: string[] let tags: string[]
let deprecatedTagsSyntax: boolean
if (typeof rawData?.tags === 'string') { if (typeof rawData?.tags === 'string') {
tags = rawData?.tags?.split(',').map((entry) => entry.trim()) ?? [] tags = rawData?.tags?.split(',').map((entry) => entry.trim()) ?? []
deprecatedTagsSyntax = true
} else if (typeof rawData?.tags === 'object') { } else if (typeof rawData?.tags === 'object') {
tags = rawData?.tags?.filter((tag) => tag !== null) ?? [] tags = rawData?.tags?.filter((tag) => tag !== null) ?? []
deprecatedTagsSyntax = false
} else { } else {
tags = [...initialState.frontmatter.tags] tags = [...initialState.frontmatter.tags]
deprecatedTagsSyntax = false
} }
return { return {
@ -53,8 +49,7 @@ const parseRawNoteFrontmatter = (rawData: RawNoteFrontmatter): NoteFrontmatter =
dir: parseTextDirection(rawData), dir: parseTextDirection(rawData),
opengraph: parseOpenGraph(rawData), opengraph: parseOpenGraph(rawData),
slideOptions: parseSlideOptions(rawData), slideOptions: parseSlideOptions(rawData),
tags, tags
deprecatedTagsSyntax
} }
} }

View file

@ -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 * SPDX-License-Identifier: AGPL-3.0-only
*/ */
@ -7,7 +7,7 @@
export interface RawNoteFrontmatter { export interface RawNoteFrontmatter {
title: string | undefined title: string | undefined
description: string | undefined description: string | undefined
tags: string | string[] | undefined tags: string | number | string[] | undefined
robots: string | undefined robots: string | undefined
lang: string | undefined lang: string | undefined
dir: string | undefined dir: string | undefined

View file

@ -81,7 +81,6 @@ describe('build state from set note data from server', () => {
title: '', title: '',
description: '', description: '',
tags: [], tags: [],
deprecatedTagsSyntax: false,
robots: '', robots: '',
lang: 'en', lang: 'en',
dir: NoteTextDirection.LTR, dir: NoteTextDirection.LTR,
@ -100,7 +99,6 @@ describe('build state from set note data from server', () => {
}, },
frontmatterRendererInfo: { frontmatterRendererInfo: {
frontmatterInvalid: false, frontmatterInvalid: false,
deprecatedSyntax: false,
lineOffset: 0, lineOffset: 0,
slideOptions: initialSlideOptions slideOptions: initialSlideOptions
}, },

View file

@ -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 * SPDX-License-Identifier: AGPL-3.0-only
*/ */
@ -37,7 +37,6 @@ export interface NoteFrontmatter {
title: string title: string
description: string description: string
tags: string[] tags: string[]
deprecatedTagsSyntax: boolean
robots: string robots: string
lang: Iso6391Language lang: Iso6391Language
dir: NoteTextDirection dir: NoteTextDirection
@ -62,6 +61,5 @@ export enum NoteType {
export interface RendererFrontmatterInfo { export interface RendererFrontmatterInfo {
lineOffset: number lineOffset: number
frontmatterInvalid: boolean frontmatterInvalid: boolean
deprecatedSyntax: boolean
slideOptions: SlideOptions slideOptions: SlideOptions
} }

View file

@ -1655,7 +1655,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@codemirror/lint@npm:^6.0.0": "@codemirror/lint@npm:6.0.0, @codemirror/lint@npm:^6.0.0":
version: 6.0.0 version: 6.0.0
resolution: "@codemirror/lint@npm:6.0.0" resolution: "@codemirror/lint@npm:6.0.0"
dependencies: dependencies:
@ -1902,6 +1902,7 @@ __metadata:
"@codemirror/lang-markdown": 6.0.1 "@codemirror/lang-markdown": 6.0.1
"@codemirror/language": 6.2.1 "@codemirror/language": 6.2.1
"@codemirror/language-data": 6.1.0 "@codemirror/language-data": 6.1.0
"@codemirror/lint": 6.0.0
"@codemirror/state": 6.1.0 "@codemirror/state": 6.1.0
"@codemirror/theme-one-dark": 6.0.0 "@codemirror/theme-one-dark": 6.0.0
"@codemirror/view": 6.1.2 "@codemirror/view": 6.1.2