mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2024-11-22 01:36:29 -05:00
refactor: move frontmatter parser into commons package
Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
parent
4d0a2cb79e
commit
db43e1db3f
26 changed files with 462 additions and 321 deletions
|
@ -41,6 +41,7 @@
|
||||||
"domhandler": "5.0.3",
|
"domhandler": "5.0.3",
|
||||||
"eventemitter2": "6.4.9",
|
"eventemitter2": "6.4.9",
|
||||||
"isomorphic-ws": "5.0.0",
|
"isomorphic-ws": "5.0.0",
|
||||||
|
"joi": "17.9.2",
|
||||||
"reveal.js": "4.5.0",
|
"reveal.js": "4.5.0",
|
||||||
"ws": "8.13.0",
|
"ws": "8.13.0",
|
||||||
"yjs": "13.6.1"
|
"yjs": "13.6.1"
|
||||||
|
|
|
@ -26,11 +26,17 @@ export { waitForOtherPromisesToFinish } from './utils/wait-for-other-promises-to
|
||||||
|
|
||||||
export { RealtimeDoc } from './y-doc-sync/realtime-doc.js'
|
export { RealtimeDoc } from './y-doc-sync/realtime-doc.js'
|
||||||
|
|
||||||
export * from './title-extraction/frontmatter-extractor/extractor.js'
|
export * from './frontmatter-extractor/extractor.js'
|
||||||
export * from './title-extraction/frontmatter-extractor/types.js'
|
export * from './frontmatter-extractor/types.js'
|
||||||
|
export * from './note-frontmatter-parser/parse-raw-frontmatter-from-yaml.js'
|
||||||
|
export * from './note-frontmatter-parser/convert-raw-frontmatter-to-note-frontmatter.js'
|
||||||
|
export * from './note-frontmatter-parser/default-values.js'
|
||||||
|
export * from './note-frontmatter-parser/parse-tags.js'
|
||||||
|
export * from './note-frontmatter-parser/types.js'
|
||||||
|
|
||||||
export * from './title-extraction/generate-note-title.js'
|
export * from './title-extraction/generate-note-title.js'
|
||||||
export * from './title-extraction/types/iso6391.js'
|
export * from './note-frontmatter/iso6391.js'
|
||||||
export * from './title-extraction/types/frontmatter.js'
|
export * from './note-frontmatter/frontmatter.js'
|
||||||
export * from './title-extraction/types/slide-show-options.js'
|
export * from './note-frontmatter/slide-show-options.js'
|
||||||
|
|
||||||
export { extractFirstHeading } from './title-extraction/extract-first-heading.js'
|
export { extractFirstHeading } from './title-extraction/extract-first-heading.js'
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
import {
|
||||||
|
NoteFrontmatter,
|
||||||
|
NoteTextDirection,
|
||||||
|
NoteType,
|
||||||
|
OpenGraph
|
||||||
|
} from '../note-frontmatter/frontmatter.js'
|
||||||
|
import { SlideOptions } from '../note-frontmatter/slide-show-options.js'
|
||||||
|
import { convertRawFrontmatterToNoteFrontmatter } from './convert-raw-frontmatter-to-note-frontmatter.js'
|
||||||
|
import { describe, expect, it } from '@jest/globals'
|
||||||
|
|
||||||
|
describe('convertRawFrontmatterToNoteFrontmatter', () => {
|
||||||
|
it.each([false, true])(
|
||||||
|
'returns the correct note frontmatter with `breaks: %s`',
|
||||||
|
(breaks) => {
|
||||||
|
const slideOptions: SlideOptions = {}
|
||||||
|
const opengraph: OpenGraph = {}
|
||||||
|
expect(
|
||||||
|
convertRawFrontmatterToNoteFrontmatter({
|
||||||
|
title: 'title',
|
||||||
|
description: 'description',
|
||||||
|
robots: 'robots',
|
||||||
|
lang: 'de',
|
||||||
|
type: NoteType.DOCUMENT,
|
||||||
|
dir: NoteTextDirection.LTR,
|
||||||
|
license: 'license',
|
||||||
|
breaks: breaks,
|
||||||
|
opengraph: opengraph,
|
||||||
|
slideOptions: slideOptions,
|
||||||
|
tags: 'tags'
|
||||||
|
})
|
||||||
|
).toStrictEqual({
|
||||||
|
title: 'title',
|
||||||
|
description: 'description',
|
||||||
|
robots: 'robots',
|
||||||
|
newlinesAreBreaks: breaks,
|
||||||
|
lang: 'de',
|
||||||
|
type: NoteType.DOCUMENT,
|
||||||
|
dir: NoteTextDirection.LTR,
|
||||||
|
opengraph: opengraph,
|
||||||
|
slideOptions: slideOptions,
|
||||||
|
license: 'license',
|
||||||
|
tags: ['tags']
|
||||||
|
} as NoteFrontmatter)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
|
@ -0,0 +1,30 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
import { NoteFrontmatter } from '../note-frontmatter/frontmatter.js'
|
||||||
|
import { parseTags } from './parse-tags.js'
|
||||||
|
import { RawNoteFrontmatter } from './types.js'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new frontmatter metadata instance based on the given raw metadata properties.
|
||||||
|
* @param rawData A {@link RawNoteFrontmatter} object containing the properties of the parsed yaml frontmatter.
|
||||||
|
*/
|
||||||
|
export const convertRawFrontmatterToNoteFrontmatter = (
|
||||||
|
rawData: RawNoteFrontmatter
|
||||||
|
): NoteFrontmatter => {
|
||||||
|
return {
|
||||||
|
title: rawData.title,
|
||||||
|
description: rawData.description,
|
||||||
|
robots: rawData.robots,
|
||||||
|
newlinesAreBreaks: rawData.breaks,
|
||||||
|
lang: rawData.lang,
|
||||||
|
type: rawData.type,
|
||||||
|
dir: rawData.dir,
|
||||||
|
opengraph: rawData.opengraph,
|
||||||
|
slideOptions: rawData.slideOptions,
|
||||||
|
license: rawData.license,
|
||||||
|
tags: parseTags(rawData.tags)
|
||||||
|
}
|
||||||
|
}
|
33
commons/src/note-frontmatter-parser/default-values.ts
Normal file
33
commons/src/note-frontmatter-parser/default-values.ts
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
import {
|
||||||
|
NoteFrontmatter,
|
||||||
|
NoteTextDirection,
|
||||||
|
NoteType
|
||||||
|
} from '../note-frontmatter/frontmatter.js'
|
||||||
|
import { SlideOptions } from '../note-frontmatter/slide-show-options.js'
|
||||||
|
|
||||||
|
export const defaultSlideOptions: SlideOptions = {
|
||||||
|
transition: 'zoom',
|
||||||
|
autoSlide: 0,
|
||||||
|
autoSlideStoppable: true,
|
||||||
|
backgroundTransition: 'fade',
|
||||||
|
slideNumber: false
|
||||||
|
}
|
||||||
|
|
||||||
|
export const defaultNoteFrontmatter: NoteFrontmatter = {
|
||||||
|
title: '',
|
||||||
|
description: '',
|
||||||
|
tags: [],
|
||||||
|
robots: '',
|
||||||
|
lang: 'en',
|
||||||
|
dir: NoteTextDirection.LTR,
|
||||||
|
newlinesAreBreaks: true,
|
||||||
|
license: '',
|
||||||
|
type: NoteType.DOCUMENT,
|
||||||
|
opengraph: {},
|
||||||
|
slideOptions: defaultSlideOptions
|
||||||
|
}
|
|
@ -0,0 +1,76 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
import { parseRawFrontmatterFromYaml } from './parse-raw-frontmatter-from-yaml.js'
|
||||||
|
import { describe, expect, it } from '@jest/globals'
|
||||||
|
|
||||||
|
describe('yaml frontmatter', () => {
|
||||||
|
it('should parse "title"', () => {
|
||||||
|
const noteFrontmatter = parseRawFrontmatterFromYaml('title: test')
|
||||||
|
expect(noteFrontmatter.value?.title).toEqual('test')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should parse "robots"', () => {
|
||||||
|
const noteFrontmatter = parseRawFrontmatterFromYaml('robots: index, follow')
|
||||||
|
expect(noteFrontmatter.value?.robots).toEqual('index, follow')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should parse the deprecated tags syntax', () => {
|
||||||
|
const noteFrontmatter = parseRawFrontmatterFromYaml('tags: test123, abc')
|
||||||
|
expect(noteFrontmatter.value?.tags).toEqual('test123, abc')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should parse the tags list syntax', () => {
|
||||||
|
const noteFrontmatter = parseRawFrontmatterFromYaml(`tags:
|
||||||
|
- test123
|
||||||
|
- abc
|
||||||
|
`)
|
||||||
|
expect(noteFrontmatter.value?.tags).toEqual(['test123', 'abc'])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should parse the tag inline-list syntax', () => {
|
||||||
|
const noteFrontmatter = parseRawFrontmatterFromYaml(
|
||||||
|
"tags: ['test123', 'abc']"
|
||||||
|
)
|
||||||
|
expect(noteFrontmatter.value?.tags).toEqual(['test123', 'abc'])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should parse "breaks"', () => {
|
||||||
|
const noteFrontmatter = parseRawFrontmatterFromYaml('breaks: false')
|
||||||
|
expect(noteFrontmatter.value?.breaks).toEqual(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should parse an opengraph title', () => {
|
||||||
|
const noteFrontmatter = parseRawFrontmatterFromYaml(`opengraph:
|
||||||
|
title: Testtitle
|
||||||
|
`)
|
||||||
|
expect(noteFrontmatter.value?.opengraph.title).toEqual('Testtitle')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should parse multiple opengraph values', () => {
|
||||||
|
const noteFrontmatter = parseRawFrontmatterFromYaml(`opengraph:
|
||||||
|
title: Testtitle
|
||||||
|
image: https://dummyimage.com/48.png
|
||||||
|
image:type: image/png
|
||||||
|
`)
|
||||||
|
expect(noteFrontmatter.value?.opengraph.title).toEqual('Testtitle')
|
||||||
|
expect(noteFrontmatter.value?.opengraph.image).toEqual(
|
||||||
|
'https://dummyimage.com/48.png'
|
||||||
|
)
|
||||||
|
expect(noteFrontmatter.value?.opengraph['image:type']).toEqual('image/png')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('allows unknown additional options', () => {
|
||||||
|
const noteFrontmatter = parseRawFrontmatterFromYaml(`title: title
|
||||||
|
additonal: "additonal"`)
|
||||||
|
|
||||||
|
expect(noteFrontmatter.value?.title).toBe('title')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('throws an error if the yaml is invalid', () => {
|
||||||
|
const a = parseRawFrontmatterFromYaml('A: asd\n B: asd')
|
||||||
|
expect(a.error?.message).toStrictEqual('Invalid YAML')
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,94 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
import {
|
||||||
|
NoteTextDirection,
|
||||||
|
NoteType,
|
||||||
|
OpenGraph
|
||||||
|
} from '../note-frontmatter/frontmatter.js'
|
||||||
|
import { ISO6391 } from '../note-frontmatter/iso6391.js'
|
||||||
|
import { SlideOptions } from '../note-frontmatter/slide-show-options.js'
|
||||||
|
import { defaultNoteFrontmatter } from './default-values.js'
|
||||||
|
import type { RawNoteFrontmatter } from './types.js'
|
||||||
|
import type { ValidationError } from 'joi'
|
||||||
|
import Joi from 'joi'
|
||||||
|
import { load } from 'js-yaml'
|
||||||
|
|
||||||
|
const schema = Joi.object<RawNoteFrontmatter>({
|
||||||
|
title: Joi.string().optional().default(defaultNoteFrontmatter.title),
|
||||||
|
description: Joi.string()
|
||||||
|
.optional()
|
||||||
|
.default(defaultNoteFrontmatter.description),
|
||||||
|
tags: Joi.alternatives(
|
||||||
|
Joi.array().items(Joi.string()),
|
||||||
|
Joi.string(),
|
||||||
|
Joi.number().cast('string')
|
||||||
|
)
|
||||||
|
.optional()
|
||||||
|
.default(defaultNoteFrontmatter.tags),
|
||||||
|
robots: Joi.string().optional().default(defaultNoteFrontmatter.robots),
|
||||||
|
lang: Joi.string()
|
||||||
|
.valid(...ISO6391)
|
||||||
|
.optional()
|
||||||
|
.default(defaultNoteFrontmatter.lang),
|
||||||
|
dir: Joi.string()
|
||||||
|
.valid(...Object.values(NoteTextDirection))
|
||||||
|
.optional()
|
||||||
|
.default(defaultNoteFrontmatter.dir),
|
||||||
|
breaks: Joi.boolean()
|
||||||
|
.optional()
|
||||||
|
.default(defaultNoteFrontmatter.newlinesAreBreaks),
|
||||||
|
license: Joi.string().optional().default(defaultNoteFrontmatter.license),
|
||||||
|
type: Joi.string()
|
||||||
|
.valid(...Object.values(NoteType))
|
||||||
|
.optional()
|
||||||
|
.default(defaultNoteFrontmatter.type),
|
||||||
|
slideOptions: Joi.object<SlideOptions>({
|
||||||
|
autoSlide: Joi.number().optional(),
|
||||||
|
transition: Joi.string().optional(),
|
||||||
|
backgroundTransition: Joi.string().optional(),
|
||||||
|
autoSlideStoppable: Joi.boolean().optional(),
|
||||||
|
slideNumber: Joi.boolean().optional()
|
||||||
|
})
|
||||||
|
.optional()
|
||||||
|
.default(defaultNoteFrontmatter.slideOptions),
|
||||||
|
opengraph: Joi.object<OpenGraph>({
|
||||||
|
title: Joi.string().optional(),
|
||||||
|
image: Joi.string().uri().optional()
|
||||||
|
})
|
||||||
|
.unknown(true)
|
||||||
|
.optional()
|
||||||
|
.default(defaultNoteFrontmatter.opengraph)
|
||||||
|
})
|
||||||
|
.default(defaultNoteFrontmatter)
|
||||||
|
.unknown(true)
|
||||||
|
|
||||||
|
const loadYaml = (rawYaml: string): unknown => {
|
||||||
|
try {
|
||||||
|
return load(rawYaml)
|
||||||
|
} catch {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ParserResult =
|
||||||
|
| {
|
||||||
|
error: undefined
|
||||||
|
warning?: ValidationError
|
||||||
|
value: RawNoteFrontmatter
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
error: Error
|
||||||
|
warning?: ValidationError
|
||||||
|
value: undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
export const parseRawFrontmatterFromYaml = (rawYaml: string): ParserResult => {
|
||||||
|
const rawNoteFrontmatter = loadYaml(rawYaml)
|
||||||
|
if (rawNoteFrontmatter === undefined) {
|
||||||
|
return { error: new Error('Invalid YAML'), value: undefined }
|
||||||
|
}
|
||||||
|
return schema.validate(rawNoteFrontmatter, { convert: true })
|
||||||
|
}
|
31
commons/src/note-frontmatter-parser/parse-tags.spec.ts
Normal file
31
commons/src/note-frontmatter-parser/parse-tags.spec.ts
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
import { parseTags } from './parse-tags.js'
|
||||||
|
import { expect, it, describe } from '@jest/globals'
|
||||||
|
|
||||||
|
describe('parse tags', () => {
|
||||||
|
it('converts comma separated string tags into string list', () => {
|
||||||
|
expect(parseTags('a,b,c,d,e,f')).toStrictEqual([
|
||||||
|
'a',
|
||||||
|
'b',
|
||||||
|
'c',
|
||||||
|
'd',
|
||||||
|
'e',
|
||||||
|
'f'
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('accepts a string list as tags', () => {
|
||||||
|
expect(parseTags(['a', 'b', ' c', 'd ', 'e', 'f'])).toStrictEqual([
|
||||||
|
'a',
|
||||||
|
'b',
|
||||||
|
'c',
|
||||||
|
'd',
|
||||||
|
'e',
|
||||||
|
'f'
|
||||||
|
])
|
||||||
|
})
|
||||||
|
})
|
17
commons/src/note-frontmatter-parser/parse-tags.ts
Normal file
17
commons/src/note-frontmatter-parser/parse-tags.ts
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the given value as tags array.
|
||||||
|
*
|
||||||
|
* @param rawTags The raw value to parse
|
||||||
|
* @return the parsed tags
|
||||||
|
*/
|
||||||
|
export const parseTags = (rawTags: string | string[]): string[] => {
|
||||||
|
return (Array.isArray(rawTags) ? rawTags : rawTags.split(','))
|
||||||
|
.map((entry) => entry.trim())
|
||||||
|
.filter((tag) => !!tag)
|
||||||
|
}
|
26
commons/src/note-frontmatter-parser/types.ts
Normal file
26
commons/src/note-frontmatter-parser/types.ts
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
import {
|
||||||
|
Iso6391Language,
|
||||||
|
NoteTextDirection,
|
||||||
|
NoteType,
|
||||||
|
OpenGraph
|
||||||
|
} from '../note-frontmatter/frontmatter.js'
|
||||||
|
import { SlideOptions } from '../note-frontmatter/slide-show-options.js'
|
||||||
|
|
||||||
|
export interface RawNoteFrontmatter {
|
||||||
|
title: string
|
||||||
|
description: string
|
||||||
|
tags: string | string[]
|
||||||
|
robots: string
|
||||||
|
lang: Iso6391Language
|
||||||
|
dir: NoteTextDirection
|
||||||
|
breaks: boolean
|
||||||
|
license: string
|
||||||
|
type: NoteType
|
||||||
|
slideOptions: SlideOptions
|
||||||
|
opengraph: OpenGraph
|
||||||
|
}
|
|
@ -16,7 +16,7 @@ export enum NoteTextDirection {
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum NoteType {
|
export enum NoteType {
|
||||||
DOCUMENT = '',
|
DOCUMENT = 'document',
|
||||||
SLIDE = 'slide'
|
SLIDE = 'slide'
|
||||||
}
|
}
|
||||||
export interface NoteFrontmatter {
|
export interface NoteFrontmatter {
|
|
@ -28,6 +28,7 @@ export const ISO6391 = [
|
||||||
'ar-ye',
|
'ar-ye',
|
||||||
'as',
|
'as',
|
||||||
'ay',
|
'ay',
|
||||||
|
'de',
|
||||||
'de-at',
|
'de-at',
|
||||||
'de-ch',
|
'de-ch',
|
||||||
'de-li',
|
'de-li',
|
|
@ -12,4 +12,4 @@ type WantedRevealOptions =
|
||||||
| 'backgroundTransition'
|
| 'backgroundTransition'
|
||||||
| 'slideNumber'
|
| 'slideNumber'
|
||||||
|
|
||||||
export type SlideOptions = Required<Pick<RevealOptions, WantedRevealOptions>>
|
export type SlideOptions = Pick<RevealOptions, WantedRevealOptions>
|
|
@ -3,9 +3,12 @@
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
import {
|
||||||
|
NoteFrontmatter,
|
||||||
|
NoteTextDirection,
|
||||||
|
NoteType
|
||||||
|
} from '../note-frontmatter/frontmatter.js'
|
||||||
import { generateNoteTitle } from './generate-note-title.js'
|
import { generateNoteTitle } from './generate-note-title.js'
|
||||||
import { NoteFrontmatter, NoteTextDirection } from './types/frontmatter.js'
|
|
||||||
import { NoteType } from './types/frontmatter.js'
|
|
||||||
import { describe, expect, it } from '@jest/globals'
|
import { describe, expect, it } from '@jest/globals'
|
||||||
|
|
||||||
const testFrontmatter: NoteFrontmatter = {
|
const testFrontmatter: NoteFrontmatter = {
|
||||||
|
@ -36,7 +39,7 @@ describe('generate note title', () => {
|
||||||
title: 'frontmatter',
|
title: 'frontmatter',
|
||||||
opengraph: { title: 'opengraph' }
|
opengraph: { title: 'opengraph' }
|
||||||
},
|
},
|
||||||
'first-heading'
|
() => 'first-heading'
|
||||||
)
|
)
|
||||||
expect(actual).toEqual('frontmatter')
|
expect(actual).toEqual('frontmatter')
|
||||||
})
|
})
|
||||||
|
@ -44,13 +47,16 @@ describe('generate note title', () => {
|
||||||
it('will choose the opengraph title second', () => {
|
it('will choose the opengraph title second', () => {
|
||||||
const actual = generateNoteTitle(
|
const actual = generateNoteTitle(
|
||||||
{ ...testFrontmatter, opengraph: { title: 'opengraph' } },
|
{ ...testFrontmatter, opengraph: { title: 'opengraph' } },
|
||||||
'first-heading'
|
() => 'first-heading'
|
||||||
)
|
)
|
||||||
expect(actual).toEqual('opengraph')
|
expect(actual).toEqual('opengraph')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('will choose the first heading third', () => {
|
it('will choose the first heading third', () => {
|
||||||
const actual = generateNoteTitle({ ...testFrontmatter }, 'first-heading')
|
const actual = generateNoteTitle(
|
||||||
|
{ ...testFrontmatter },
|
||||||
|
() => 'first-heading'
|
||||||
|
)
|
||||||
expect(actual).toEqual('first-heading')
|
expect(actual).toEqual('first-heading')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -3,28 +3,24 @@
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import type { NoteFrontmatter } from './types/frontmatter.js'
|
import type { NoteFrontmatter } from '../note-frontmatter/frontmatter.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates the note title from the given frontmatter or the first heading in the markdown content.
|
* Generates the note title from the given frontmatter or the first heading in the markdown content.
|
||||||
*
|
*
|
||||||
* @param frontmatter The frontmatter of the note
|
* @param frontmatter The frontmatter of the note
|
||||||
* @param firstHeading The first heading in the markdown content
|
* @param firstHeadingProvider A function that provides the first heading of the markdown content
|
||||||
* @return The title from the frontmatter or, if no title is present in the frontmatter, the first heading.
|
* @return The title from the frontmatter or, if no title is present in the frontmatter, the first heading.
|
||||||
*/
|
*/
|
||||||
export const generateNoteTitle = (
|
export const generateNoteTitle = (
|
||||||
frontmatter: NoteFrontmatter,
|
frontmatter: NoteFrontmatter | undefined,
|
||||||
firstHeading?: string
|
firstHeadingProvider: () => string | undefined
|
||||||
): string => {
|
): string => {
|
||||||
if (frontmatter?.title && frontmatter?.title !== '') {
|
if (frontmatter?.title) {
|
||||||
return frontmatter.title.trim()
|
return frontmatter.title.trim()
|
||||||
} else if (
|
} else if (frontmatter?.opengraph.title) {
|
||||||
frontmatter?.opengraph &&
|
return (frontmatter?.opengraph.title ?? firstHeadingProvider() ?? '').trim()
|
||||||
frontmatter?.opengraph.title !== undefined &&
|
|
||||||
frontmatter?.opengraph.title !== ''
|
|
||||||
) {
|
|
||||||
return (frontmatter?.opengraph.title ?? firstHeading ?? '').trim()
|
|
||||||
} else {
|
} else {
|
||||||
return (firstHeading ?? '').trim()
|
return (firstHeadingProvider() ?? '').trim()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,13 +3,11 @@
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import type { RawNoteFrontmatter } from '../../../../redux/note-details/raw-note-frontmatter-parser/types'
|
|
||||||
import type { Linter } from './linter'
|
import type { Linter } from './linter'
|
||||||
import type { Diagnostic } from '@codemirror/lint'
|
import type { Diagnostic } from '@codemirror/lint'
|
||||||
import type { EditorView } from '@codemirror/view'
|
import type { EditorView } from '@codemirror/view'
|
||||||
import { extractFrontmatter } from '@hedgedoc/commons'
|
import { extractFrontmatter, parseRawFrontmatterFromYaml, parseTags } from '@hedgedoc/commons'
|
||||||
import { t } from 'i18next'
|
import { t } from 'i18next'
|
||||||
import { load } from 'js-yaml'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@link Linter linter} for the yaml frontmatter.
|
* Creates a {@link Linter linter} for the yaml frontmatter.
|
||||||
|
@ -23,27 +21,42 @@ export class FrontmatterLinter implements Linter {
|
||||||
if (frontmatterExtraction === undefined) {
|
if (frontmatterExtraction === undefined) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
const startOfYaml = lines[0].length + 1
|
|
||||||
const frontmatterLines = lines.slice(1, frontmatterExtraction.lineOffset - 1)
|
const frontmatterLines = lines.slice(1, frontmatterExtraction.lineOffset - 1)
|
||||||
const rawNoteFrontmatter = FrontmatterLinter.loadYaml(frontmatterExtraction.rawText)
|
const startOfYaml = lines[0].length + 1
|
||||||
if (rawNoteFrontmatter === undefined) {
|
const endOfYaml = startOfYaml + frontmatterLines.join('\n').length
|
||||||
return [
|
const rawNoteFrontmatter = parseRawFrontmatterFromYaml(frontmatterExtraction.rawText)
|
||||||
{
|
if (rawNoteFrontmatter.error) {
|
||||||
from: startOfYaml,
|
return this.createErrorDiagnostics(startOfYaml, endOfYaml, rawNoteFrontmatter.error, 'error')
|
||||||
to: startOfYaml + frontmatterLines.join('\n').length,
|
} else if (rawNoteFrontmatter.warning) {
|
||||||
message: t('editor.linter.frontmatter'),
|
return this.createErrorDiagnostics(startOfYaml, endOfYaml, rawNoteFrontmatter.warning, 'warning')
|
||||||
severity: 'error'
|
} else if (!Array.isArray(rawNoteFrontmatter.value.tags)) {
|
||||||
}
|
return this.createReplaceSingleStringTagsDiagnostic(rawNoteFrontmatter.value.tags, frontmatterLines, startOfYaml)
|
||||||
]
|
|
||||||
}
|
}
|
||||||
if (typeof rawNoteFrontmatter.tags !== 'string' && typeof rawNoteFrontmatter.tags !== 'number') {
|
return []
|
||||||
return []
|
}
|
||||||
}
|
|
||||||
const tags: string[] =
|
private createErrorDiagnostics(
|
||||||
rawNoteFrontmatter?.tags
|
startOfYaml: number,
|
||||||
.toString()
|
endOfYaml: number,
|
||||||
.split(',')
|
error: Error,
|
||||||
.map((entry) => entry.trim()) ?? []
|
severity: 'error' | 'warning'
|
||||||
|
): Diagnostic[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
from: startOfYaml,
|
||||||
|
to: endOfYaml,
|
||||||
|
message: error.message,
|
||||||
|
severity: severity
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
private createReplaceSingleStringTagsDiagnostic(
|
||||||
|
rawTags: string,
|
||||||
|
frontmatterLines: string[],
|
||||||
|
startOfYaml: number
|
||||||
|
): Diagnostic[] {
|
||||||
|
const tags: string[] = parseTags(rawTags)
|
||||||
const replacedText = 'tags:\n- ' + tags.join('\n- ')
|
const replacedText = 'tags:\n- ' + tags.join('\n- ')
|
||||||
const tagsLineIndex = frontmatterLines.findIndex((value) => value.startsWith('tags: '))
|
const tagsLineIndex = frontmatterLines.findIndex((value) => value.startsWith('tags: '))
|
||||||
const linesBeforeTagsLine = frontmatterLines.slice(0, tagsLineIndex)
|
const linesBeforeTagsLine = frontmatterLines.slice(0, tagsLineIndex)
|
||||||
|
@ -68,12 +81,4 @@ export class FrontmatterLinter implements Linter {
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
private static loadYaml(raw: string): RawNoteFrontmatter | undefined {
|
|
||||||
try {
|
|
||||||
return load(raw) as RawNoteFrontmatter
|
|
||||||
} catch {
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,10 +5,15 @@
|
||||||
*/
|
*/
|
||||||
import { calculateLineStartIndexes } from './calculate-line-start-indexes'
|
import { calculateLineStartIndexes } from './calculate-line-start-indexes'
|
||||||
import { initialState } from './initial-state'
|
import { initialState } from './initial-state'
|
||||||
import { createNoteFrontmatterFromYaml } from './raw-note-frontmatter-parser/parser'
|
|
||||||
import type { NoteDetails } from './types/note-details'
|
import type { NoteDetails } from './types/note-details'
|
||||||
import { extractFrontmatter, generateNoteTitle } from '@hedgedoc/commons'
|
import type { FrontmatterExtractionResult, NoteFrontmatter } from '@hedgedoc/commons'
|
||||||
import type { FrontmatterExtractionResult } from '@hedgedoc/commons'
|
import {
|
||||||
|
convertRawFrontmatterToNoteFrontmatter,
|
||||||
|
extractFrontmatter,
|
||||||
|
generateNoteTitle,
|
||||||
|
parseRawFrontmatterFromYaml
|
||||||
|
} from '@hedgedoc/commons'
|
||||||
|
import { Optional } from '@mrdrogdrog/optional'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copies a {@link NoteDetails} but with another markdown content.
|
* Copies a {@link NoteDetails} but with another markdown content.
|
||||||
|
@ -62,7 +67,7 @@ const buildStateFromMarkdownContentAndLines = (
|
||||||
},
|
},
|
||||||
startOfContentLineOffset: 0,
|
startOfContentLineOffset: 0,
|
||||||
rawFrontmatter: '',
|
rawFrontmatter: '',
|
||||||
title: generateNoteTitle(initialState.frontmatter, state.firstHeading),
|
title: generateNoteTitle(initialState.frontmatter, () => state.firstHeading),
|
||||||
frontmatter: initialState.frontmatter
|
frontmatter: initialState.frontmatter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -81,22 +86,27 @@ const buildStateFromFrontmatterUpdate = (
|
||||||
if (frontmatterExtraction.rawText === state.rawFrontmatter) {
|
if (frontmatterExtraction.rawText === state.rawFrontmatter) {
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
try {
|
return buildStateFromFrontmatter(state, parseFrontmatter(frontmatterExtraction), frontmatterExtraction)
|
||||||
const frontmatter = createNoteFrontmatterFromYaml(frontmatterExtraction.rawText)
|
}
|
||||||
return {
|
|
||||||
...state,
|
const parseFrontmatter = (frontmatterExtraction: FrontmatterExtractionResult) => {
|
||||||
rawFrontmatter: frontmatterExtraction.rawText,
|
return Optional.of(parseRawFrontmatterFromYaml(frontmatterExtraction.rawText))
|
||||||
frontmatter: frontmatter,
|
.filter((frontmatter) => frontmatter.error === undefined)
|
||||||
title: generateNoteTitle(frontmatter, state.firstHeading),
|
.map((frontmatter) => frontmatter.value)
|
||||||
startOfContentLineOffset: frontmatterExtraction.lineOffset
|
.map((value) => convertRawFrontmatterToNoteFrontmatter(value))
|
||||||
}
|
.orElse(initialState.frontmatter)
|
||||||
} catch (e) {
|
}
|
||||||
return {
|
|
||||||
...state,
|
const buildStateFromFrontmatter = (
|
||||||
title: generateNoteTitle(initialState.frontmatter, state.firstHeading),
|
state: NoteDetails,
|
||||||
rawFrontmatter: frontmatterExtraction.rawText,
|
noteFrontmatter: NoteFrontmatter,
|
||||||
frontmatter: initialState.frontmatter,
|
frontmatterExtraction: FrontmatterExtractionResult
|
||||||
startOfContentLineOffset: frontmatterExtraction.lineOffset
|
) => {
|
||||||
}
|
return {
|
||||||
|
...state,
|
||||||
|
title: generateNoteTitle(noteFrontmatter, () => state.firstHeading),
|
||||||
|
rawFrontmatter: frontmatterExtraction.rawText,
|
||||||
|
frontmatter: noteFrontmatter,
|
||||||
|
startOfContentLineOffset: frontmatterExtraction.lineOffset
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,16 +4,7 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import type { NoteDetails } from './types/note-details'
|
import type { NoteDetails } from './types/note-details'
|
||||||
import { NoteTextDirection, NoteType } from '@hedgedoc/commons'
|
import { defaultNoteFrontmatter } from '@hedgedoc/commons'
|
||||||
import type { SlideOptions } from '@hedgedoc/commons'
|
|
||||||
|
|
||||||
export const initialSlideOptions: SlideOptions = {
|
|
||||||
transition: 'zoom',
|
|
||||||
autoSlide: 0,
|
|
||||||
autoSlideStoppable: true,
|
|
||||||
backgroundTransition: 'fade',
|
|
||||||
slideNumber: false
|
|
||||||
}
|
|
||||||
|
|
||||||
export const initialState: NoteDetails = {
|
export const initialState: NoteDetails = {
|
||||||
updateUsername: null,
|
updateUsername: null,
|
||||||
|
@ -40,17 +31,5 @@ export const initialState: NoteDetails = {
|
||||||
editedBy: [],
|
editedBy: [],
|
||||||
title: '',
|
title: '',
|
||||||
firstHeading: '',
|
firstHeading: '',
|
||||||
frontmatter: {
|
frontmatter: defaultNoteFrontmatter
|
||||||
title: '',
|
|
||||||
description: '',
|
|
||||||
tags: [],
|
|
||||||
robots: '',
|
|
||||||
lang: 'en',
|
|
||||||
dir: NoteTextDirection.LTR,
|
|
||||||
newlinesAreBreaks: true,
|
|
||||||
license: '',
|
|
||||||
type: NoteType.DOCUMENT,
|
|
||||||
opengraph: {},
|
|
||||||
slideOptions: initialSlideOptions
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,64 +0,0 @@
|
||||||
/*
|
|
||||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
*/
|
|
||||||
import { createNoteFrontmatterFromYaml } from './parser'
|
|
||||||
|
|
||||||
describe('yaml frontmatter', () => {
|
|
||||||
it('should parse "title"', () => {
|
|
||||||
const noteFrontmatter = createNoteFrontmatterFromYaml('title: test')
|
|
||||||
expect(noteFrontmatter.title).toEqual('test')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should parse "robots"', () => {
|
|
||||||
const noteFrontmatter = createNoteFrontmatterFromYaml('robots: index, follow')
|
|
||||||
expect(noteFrontmatter.robots).toEqual('index, follow')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should parse the deprecated tags syntax', () => {
|
|
||||||
const noteFrontmatter = createNoteFrontmatterFromYaml('tags: test123, abc')
|
|
||||||
expect(noteFrontmatter.tags).toEqual(['test123', 'abc'])
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should parse the tags list syntax', () => {
|
|
||||||
const noteFrontmatter = createNoteFrontmatterFromYaml(`tags:
|
|
||||||
- test123
|
|
||||||
- abc
|
|
||||||
`)
|
|
||||||
expect(noteFrontmatter.tags).toEqual(['test123', 'abc'])
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should parse the tag inline-list syntax', () => {
|
|
||||||
const noteFrontmatter = createNoteFrontmatterFromYaml("tags: ['test123', 'abc']")
|
|
||||||
expect(noteFrontmatter.tags).toEqual(['test123', 'abc'])
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should parse "breaks"', () => {
|
|
||||||
const noteFrontmatter = createNoteFrontmatterFromYaml('breaks: false')
|
|
||||||
expect(noteFrontmatter.newlinesAreBreaks).toEqual(false)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should parse an empty opengraph object', () => {
|
|
||||||
const noteFrontmatter = createNoteFrontmatterFromYaml('opengraph:')
|
|
||||||
expect(noteFrontmatter.opengraph).toEqual({})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should parse an opengraph title', () => {
|
|
||||||
const noteFrontmatter = createNoteFrontmatterFromYaml(`opengraph:
|
|
||||||
title: Testtitle
|
|
||||||
`)
|
|
||||||
expect(noteFrontmatter.opengraph.title).toEqual('Testtitle')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should parse multiple opengraph values', () => {
|
|
||||||
const noteFrontmatter = createNoteFrontmatterFromYaml(`opengraph:
|
|
||||||
title: Testtitle
|
|
||||||
image: https://dummyimage.com/48.png
|
|
||||||
image:type: image/png
|
|
||||||
`)
|
|
||||||
expect(noteFrontmatter.opengraph.title).toEqual('Testtitle')
|
|
||||||
expect(noteFrontmatter.opengraph.image).toEqual('https://dummyimage.com/48.png')
|
|
||||||
expect(noteFrontmatter.opengraph['image:type']).toEqual('image/png')
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -1,139 +0,0 @@
|
||||||
/*
|
|
||||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
*/
|
|
||||||
import { initialSlideOptions, initialState } from '../initial-state'
|
|
||||||
import type { RawNoteFrontmatter } from './types'
|
|
||||||
import type { Iso6391Language, NoteFrontmatter, OpenGraph, SlideOptions } from '@hedgedoc/commons'
|
|
||||||
import { ISO6391, NoteTextDirection, NoteType } from '@hedgedoc/commons'
|
|
||||||
import { load } from 'js-yaml'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new frontmatter metadata instance based on a raw yaml string.
|
|
||||||
* @param rawYaml The frontmatter content in yaml format.
|
|
||||||
* @throws Error when the content string is invalid yaml.
|
|
||||||
* @return Frontmatter metadata instance containing the parsed properties from the yaml content.
|
|
||||||
*/
|
|
||||||
export const createNoteFrontmatterFromYaml = (rawYaml: string): NoteFrontmatter => {
|
|
||||||
const rawNoteFrontmatter = load(rawYaml) as RawNoteFrontmatter
|
|
||||||
return parseRawNoteFrontmatter(rawNoteFrontmatter)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new frontmatter metadata instance based on the given raw metadata properties.
|
|
||||||
* @param rawData A {@link RawNoteFrontmatter} object containing the properties of the parsed yaml frontmatter.
|
|
||||||
*/
|
|
||||||
const parseRawNoteFrontmatter = (rawData: RawNoteFrontmatter): NoteFrontmatter => {
|
|
||||||
let tags: string[]
|
|
||||||
if (typeof rawData?.tags === 'string') {
|
|
||||||
tags = rawData?.tags?.split(',').map((entry) => entry.trim()) ?? []
|
|
||||||
} else if (typeof rawData?.tags === 'object') {
|
|
||||||
tags = rawData?.tags?.filter((tag) => tag !== null) ?? []
|
|
||||||
} else {
|
|
||||||
tags = [...initialState.frontmatter.tags]
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
title: rawData.title ?? initialState.frontmatter.title,
|
|
||||||
description: rawData.description ?? initialState.frontmatter.description,
|
|
||||||
robots: rawData.robots ?? initialState.frontmatter.robots,
|
|
||||||
newlinesAreBreaks: parseBoolean(rawData.breaks) ?? initialState.frontmatter.newlinesAreBreaks,
|
|
||||||
lang: parseLanguage(rawData),
|
|
||||||
type: parseNoteType(rawData),
|
|
||||||
dir: parseTextDirection(rawData),
|
|
||||||
opengraph: parseOpenGraph(rawData),
|
|
||||||
slideOptions: parseSlideOptions(rawData),
|
|
||||||
license: rawData.license ?? initialState.frontmatter.license,
|
|
||||||
tags
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses the {@link OpenGraph open graph} from the {@link RawNoteFrontmatter}.
|
|
||||||
*
|
|
||||||
* @param rawData The raw note frontmatter data.
|
|
||||||
* @return the parsed {@link OpenGraph open graph}
|
|
||||||
*/
|
|
||||||
const parseOpenGraph = (rawData: RawNoteFrontmatter): OpenGraph => {
|
|
||||||
return { ...(rawData.opengraph ?? initialState.frontmatter.opengraph) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses the {@link Iso6391Language iso 6391 language code} from the {@link RawNoteFrontmatter}.
|
|
||||||
*
|
|
||||||
* @param rawData The raw note frontmatter data.
|
|
||||||
* @return the parsed {@link Iso6391Language iso 6391 language code}
|
|
||||||
*/
|
|
||||||
const parseLanguage = (rawData: RawNoteFrontmatter): Iso6391Language => {
|
|
||||||
return (rawData.lang ? ISO6391.find((lang) => lang === rawData.lang) : undefined) ?? initialState.frontmatter.lang
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses the {@link NoteType note type} from the {@link RawNoteFrontmatter}.
|
|
||||||
*
|
|
||||||
* @param rawData The raw note frontmatter data.
|
|
||||||
* @return the parsed {@link NoteType note type}
|
|
||||||
*/
|
|
||||||
const parseNoteType = (rawData: RawNoteFrontmatter): NoteType => {
|
|
||||||
return rawData.type !== undefined
|
|
||||||
? rawData.type === NoteType.SLIDE
|
|
||||||
? NoteType.SLIDE
|
|
||||||
: NoteType.DOCUMENT
|
|
||||||
: initialState.frontmatter.type
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses the {@link NoteTextDirection note text direction} from the {@link RawNoteFrontmatter}.
|
|
||||||
*
|
|
||||||
* @param rawData The raw note frontmatter data.
|
|
||||||
* @return the parsed {@link NoteTextDirection note text direction}
|
|
||||||
*/
|
|
||||||
const parseTextDirection = (rawData: RawNoteFrontmatter): NoteTextDirection => {
|
|
||||||
return rawData.dir !== undefined
|
|
||||||
? rawData.dir === NoteTextDirection.LTR
|
|
||||||
? NoteTextDirection.LTR
|
|
||||||
: NoteTextDirection.RTL
|
|
||||||
: initialState.frontmatter.dir
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses the {@link SlideOptions} from the {@link RawNoteFrontmatter}.
|
|
||||||
*
|
|
||||||
* @param rawData The raw note frontmatter data.
|
|
||||||
* @return the parsed slide options
|
|
||||||
*/
|
|
||||||
const parseSlideOptions = (rawData: RawNoteFrontmatter): SlideOptions => {
|
|
||||||
const rawSlideOptions = rawData?.slideOptions
|
|
||||||
return {
|
|
||||||
autoSlide: parseNumber(rawSlideOptions?.autoSlide) ?? initialSlideOptions.autoSlide,
|
|
||||||
transition: rawSlideOptions?.transition ?? initialSlideOptions.transition,
|
|
||||||
backgroundTransition: rawSlideOptions?.backgroundTransition ?? initialSlideOptions.backgroundTransition,
|
|
||||||
autoSlideStoppable: parseBoolean(rawSlideOptions?.autoSlideStoppable) ?? initialSlideOptions.autoSlideStoppable,
|
|
||||||
slideNumber: parseBoolean(rawSlideOptions?.slideNumber) ?? initialSlideOptions.slideNumber
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses an unknown variable into a boolean.
|
|
||||||
*
|
|
||||||
* @param rawData The raw data
|
|
||||||
* @return The parsed boolean or undefined if it's not possible to parse the data.
|
|
||||||
*/
|
|
||||||
const parseBoolean = (rawData: unknown | undefined): boolean | undefined => {
|
|
||||||
return rawData === undefined ? undefined : rawData === true
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses an unknown variable into a number.
|
|
||||||
*
|
|
||||||
* @param rawData The raw data
|
|
||||||
* @return The parsed number or undefined if it's not possible to parse the data.
|
|
||||||
*/
|
|
||||||
const parseNumber = (rawData: unknown | undefined): number | undefined => {
|
|
||||||
if (rawData === undefined) {
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
const numValue = Number(rawData)
|
|
||||||
return isNaN(numValue) ? undefined : numValue
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
/*
|
|
||||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
*/
|
|
||||||
|
|
||||||
export interface RawNoteFrontmatter {
|
|
||||||
title: string | undefined
|
|
||||||
description: string | undefined
|
|
||||||
tags: string | number | string[] | undefined
|
|
||||||
robots: string | undefined
|
|
||||||
lang: string | undefined
|
|
||||||
dir: string | undefined
|
|
||||||
breaks: boolean | undefined
|
|
||||||
license: string | undefined
|
|
||||||
type: string | undefined
|
|
||||||
slideOptions: { [key: string]: string } | null
|
|
||||||
opengraph: { [key: string]: string } | null
|
|
||||||
}
|
|
|
@ -16,6 +16,6 @@ export const buildStateFromFirstHeadingUpdate = (state: NoteDetails, firstHeadin
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
firstHeading: firstHeading,
|
firstHeading: firstHeading,
|
||||||
title: generateNoteTitle(state.frontmatter, firstHeading)
|
title: generateNoteTitle(state.frontmatter, () => firstHeading)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2386,6 +2386,7 @@ __metadata:
|
||||||
eventemitter2: 6.4.9
|
eventemitter2: 6.4.9
|
||||||
isomorphic-ws: 5.0.0
|
isomorphic-ws: 5.0.0
|
||||||
jest: 29.5.0
|
jest: 29.5.0
|
||||||
|
joi: 17.9.2
|
||||||
microbundle: 0.15.1
|
microbundle: 0.15.1
|
||||||
prettier: 2.8.8
|
prettier: 2.8.8
|
||||||
reveal.js: 4.5.0
|
reveal.js: 4.5.0
|
||||||
|
|
Loading…
Reference in a new issue