mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-20 18:25:21 -04: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
|
@ -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
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue