diff --git a/backend/src/explore/explore.service.spec.ts b/backend/src/explore/explore.service.spec.ts index 793925c0c..f43918307 100644 --- a/backend/src/explore/explore.service.spec.ts +++ b/backend/src/explore/explore.service.spec.ts @@ -5,7 +5,7 @@ */ import { describe, it, expect, beforeAll, beforeEach, afterEach, jest } from '@jest/globals'; import { SortMode } from '@hedgedoc/commons'; -import { OptionalNoteType, OptionalSortMode } from '@hedgedoc/commons'; +import type { OptionalSortMode } from '@hedgedoc/commons'; import { FieldNameGroup, FieldNameNote, @@ -157,7 +157,7 @@ describe('ExploreService', () => { /select "alias"."alias" as "primaryAlias", "revision"."title" as "title", "revision"."note_type" as "noteType", "user"."username" as "ownerUsername", "note"."created_at" as "createdAt", "revision"."created_at" as "lastChangedAt", "revision"."uuid" as "revisionUuid" from "note" inner join "alias" on "alias"."note_id" = "note"."id" inner join "user" on "user"."id" = "note"."owner_id" inner join \(select "uuid", "note_id" from \(select "uuid", "note_id", row_number\(\) over \(partition by "note_id" order by "created_at" desc\) as rn from "revision"\) as "latest_revisions_per_note" where "rn" = \$1\) as "latest_revision" on "latest_revision"."note_id" = "note"."id" inner join "revision" on "revision"."note_id" = "note"."id" and "revision"."uuid" = "latest_revision"."uuid" where "alias"."is_primary" = \$2 and "note"."owner_id" = \$3 and LOWER\("revision"."title"\) LIKE \$4 order by "revision"."created_at" desc limit \$5/, [1, true, mockUserId, '%test%', ENTRIES_PER_PAGE_LIMIT], ], - ] as [string, OptionalNoteType, OptionalSortMode, string, RegExp, unknown[]][])( + ] as [string, NoteType, OptionalSortMode, string, RegExp, unknown[]][])( 'correctly get all notes owned by user with', (name, noteType, sortBy, search, regex, bindings) => { // oxlint-disable-next-line jest/valid-title @@ -226,7 +226,7 @@ describe('ExploreService', () => { /select "alias"."alias" as "primaryAlias", "revision"."title" as "title", "revision"."note_type" as "noteType", "user"."username" as "ownerUsername", "note"."created_at" as "createdAt", "revision"."created_at" as "lastChangedAt", "revision"."uuid" as "revisionUuid" from "note_user_permission" inner join "note" on "note_user_permission"."note_id" = "note"."id" inner join "alias" on "alias"."note_id" = "note"."id" inner join "user" on "user"."id" = "note"."owner_id" inner join \(select "uuid", "note_id" from \(select "uuid", "note_id", row_number\(\) over \(partition by "note_id" order by "created_at" desc\) as rn from "revision"\) as "latest_revisions_per_note" where "rn" = \$1\) as "latest_revision" on "latest_revision"."note_id" = "note"."id" inner join "revision" on "revision"."note_id" = "note"."id" and "revision"."uuid" = "latest_revision"."uuid" where "alias"."is_primary" = \$2 and "note_user_permission"."user_id" = \$3 and LOWER\("revision"."title"\) LIKE \$4 order by "revision"."created_at" desc limit \$5/, [1, true, mockUserId, '%test%', ENTRIES_PER_PAGE_LIMIT], ], - ] as [string, OptionalNoteType, OptionalSortMode, string, RegExp, unknown[]][])( + ] as [string, NoteType, OptionalSortMode, string, RegExp, unknown[]][])( 'correctly get all notes shared with the user with', (name, noteType, sortBy, search, regex, bindings) => { // oxlint-disable-next-line jest/valid-title @@ -295,7 +295,7 @@ describe('ExploreService', () => { /select "alias"."alias" as "primaryAlias", "revision"."title" as "title", "revision"."note_type" as "noteType", "user"."username" as "ownerUsername", "note"."created_at" as "createdAt", "revision"."created_at" as "lastChangedAt", "revision"."uuid" as "revisionUuid" from "note" inner join "note_group_permission" on "note"."id" = "note_group_permission"."note_id" inner join "alias" on "alias"."note_id" = "note"."id" inner join "user" on "user"."id" = "note"."owner_id" inner join \(select "uuid", "note_id" from \(select "uuid", "note_id", row_number\(\) over \(partition by "note_id" order by "created_at" desc\) as rn from "revision"\) as "latest_revisions_per_note" where "rn" = \$1\) as "latest_revision" on "latest_revision"."note_id" = "note"."id" inner join "revision" on "revision"."note_id" = "note"."id" and "revision"."uuid" = "latest_revision"."uuid" where "alias"."is_primary" = \$2 and "note_group_permission"."group_id" = \$3 and "note"."publicly_visible" = \$4 and LOWER\("revision"."title"\) LIKE \$5 order by "revision"."created_at" desc limit \$6/, [1, true, mockEveryoneGroupId, true, '%test%', ENTRIES_PER_PAGE_LIMIT], ], - ] as [string, OptionalNoteType, OptionalSortMode, string, RegExp, unknown[]][])( + ] as [string, NoteType, OptionalSortMode, string, RegExp, unknown[]][])( 'correctly get all public notes with', (name, noteType, sortBy, search, regex, bindings) => { // oxlint-disable-next-line jest/valid-title @@ -403,7 +403,7 @@ describe('ExploreService', () => { /select "alias"."alias" as "primaryAlias", "revision"."title" as "title", "revision"."note_type" as "noteType", "user"."username" as "ownerUsername", "note"."created_at" as "createdAt", "revision"."created_at" as "lastChangedAt", "revision"."uuid" as "revisionUuid", "visited_notes"."visited_at" as "lastVisitedAt", "note"."id" as "noteId" from "note" inner join "alias" on "alias"."note_id" = "note"."id" inner join "user" on "user"."id" = "note"."owner_id" inner join \(select "uuid", "note_id" from \(select "uuid", "note_id", row_number\(\) over \(partition by "note_id" order by "created_at" desc\) as rn from "revision"\) as "latest_revisions_per_note" where "rn" = \$1\) as "latest_revision" on "latest_revision"."note_id" = "note"."id" inner join "revision" on "revision"."note_id" = "note"."id" and "revision"."uuid" = "latest_revision"."uuid" left join "visited_notes" on "visited_notes"."note_id" = "note"."id" where "alias"."is_primary" = \$2 and "visited_notes"."user_id" = \$3 and LOWER\("revision"."title"\) LIKE \$4 order by "revision"."created_at" desc limit \$5/, [1, true, mockUserId, '%test%', ENTRIES_PER_PAGE_LIMIT], ], - ] as [string, OptionalNoteType, OptionalSortMode, string, RegExp, unknown[]][])( + ] as [string, NoteType, OptionalSortMode, string, RegExp, unknown[]][])( 'correctly get all notes visited by the user with', (name, noteType, sortBy, search, regex, bindings) => { // oxlint-disable-next-line jest/valid-title diff --git a/backend/src/revisions/utils/extract-revision-metadata-from-content.spec.ts b/backend/src/revisions/utils/extract-revision-metadata-from-content.spec.ts index d46d4ffff..28d9524e8 100644 --- a/backend/src/revisions/utils/extract-revision-metadata-from-content.spec.ts +++ b/backend/src/revisions/utils/extract-revision-metadata-from-content.spec.ts @@ -20,7 +20,7 @@ describe('revision entity', () => { '---\ntitle: \n - 1\n - 2\n---\nThis is a note content', ); - expect(title).toBe(''); + expect(title).toBe('1,2'); expect(description).toBe(''); expect(tags).toStrictEqual([]); }); diff --git a/backend/src/revisions/utils/extract-revision-metadata-from-content.ts b/backend/src/revisions/utils/extract-revision-metadata-from-content.ts index bd0b68409..58402d212 100644 --- a/backend/src/revisions/utils/extract-revision-metadata-from-content.ts +++ b/backend/src/revisions/utils/extract-revision-metadata-from-content.ts @@ -4,14 +4,13 @@ * SPDX-License-Identifier: AGPL-3.0-only */ import { - convertRawFrontmatterToNoteFrontmatter, defaultNoteFrontmatter, extractFirstHeading, extractFrontmatter, generateNoteTitle, - NoteFrontmatter, + type NoteFrontmatter, NoteType, - parseRawFrontmatterFromYaml, + parseNoteFrontmatter, } from '@hedgedoc/commons'; import { parseDocument } from 'htmlparser2'; import MarkdownIt from 'markdown-it'; @@ -81,11 +80,11 @@ function parseFrontmatter(content: string): FrontmatterParserResult | undefined } const firstLineOfContentIndex = extractionResult.lineOffset + 1; - const rawDataValidation = parseRawFrontmatterFromYaml(rawText); + const frontmatterParseResult = parseNoteFrontmatter(rawText); const noteFrontmatter = - rawDataValidation.error !== undefined + frontmatterParseResult.error !== undefined ? defaultNoteFrontmatter - : convertRawFrontmatterToNoteFrontmatter(rawDataValidation.value); + : frontmatterParseResult.value; return { frontmatter: noteFrontmatter, firstLineOfContentIndex: firstLineOfContentIndex, diff --git a/commons/package.json b/commons/package.json index b0acf220a..aca34d6de 100644 --- a/commons/package.json +++ b/commons/package.json @@ -46,7 +46,6 @@ "dependencies": { "domhandler": "5.0.3", "eventemitter2": "6.4.9", - "joi": "17.13.3", "js-yaml": "4.1.1", "reveal.js": "5.2.1", "ws": "8.19.0", diff --git a/commons/src/index.ts b/commons/src/index.ts index 83dd81531..ac51ff58f 100644 --- a/commons/src/index.ts +++ b/commons/src/index.ts @@ -9,7 +9,6 @@ export * from './explore-page/index.js' export * from './frontmatter-extractor/index.js' export * from './message-transporters/index.js' export * from './note-frontmatter/index.js' -export * from './note-frontmatter-parser/index.js' export * from './parse-url/index.js' export * from './permissions/index.js' export * from './title-extraction/index.js' diff --git a/commons/src/message-transporters/message.ts b/commons/src/message-transporters/message.ts index 49de1b9a8..db05f51fd 100644 --- a/commons/src/message-transporters/message.ts +++ b/commons/src/message-transporters/message.ts @@ -3,7 +3,7 @@ * * SPDX-License-Identifier: AGPL-3.0-only */ -import { RealtimeUser, RemoteCursor } from './realtime-user.js' +import type { RealtimeUser, RemoteCursor } from './realtime-user.js' export enum MessageType { NOTE_CONTENT_STATE_REQUEST = 'NOTE_CONTENT_STATE_REQUEST', diff --git a/commons/src/note-frontmatter-parser/convert-raw-frontmatter-to-note-frontmatter.spec.ts b/commons/src/note-frontmatter-parser/convert-raw-frontmatter-to-note-frontmatter.spec.ts deleted file mode 100644 index abd2d1ae8..000000000 --- a/commons/src/note-frontmatter-parser/convert-raw-frontmatter-to-note-frontmatter.spec.ts +++ /dev/null @@ -1,48 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 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) - }) -}) diff --git a/commons/src/note-frontmatter-parser/convert-raw-frontmatter-to-note-frontmatter.ts b/commons/src/note-frontmatter-parser/convert-raw-frontmatter-to-note-frontmatter.ts deleted file mode 100644 index 4d8c3b5af..000000000 --- a/commons/src/note-frontmatter-parser/convert-raw-frontmatter-to-note-frontmatter.ts +++ /dev/null @@ -1,30 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ -import { NoteFrontmatter } from '../note-frontmatter/index.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), - } -} diff --git a/commons/src/note-frontmatter-parser/index.ts b/commons/src/note-frontmatter-parser/index.ts deleted file mode 100644 index e207ee8a9..000000000 --- a/commons/src/note-frontmatter-parser/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ - -export * from './parse-raw-frontmatter-from-yaml.js' -export * from './convert-raw-frontmatter-to-note-frontmatter.js' -export * from './parse-tags.js' -export * from './types.js' diff --git a/commons/src/note-frontmatter-parser/parse-raw-frontmatter-from-yaml.spec.ts b/commons/src/note-frontmatter-parser/parse-raw-frontmatter-from-yaml.spec.ts deleted file mode 100644 index 52372b96e..000000000 --- a/commons/src/note-frontmatter-parser/parse-raw-frontmatter-from-yaml.spec.ts +++ /dev/null @@ -1,72 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 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') - }) -}) diff --git a/commons/src/note-frontmatter-parser/parse-raw-frontmatter-from-yaml.ts b/commons/src/note-frontmatter-parser/parse-raw-frontmatter-from-yaml.ts deleted file mode 100644 index 7cf5f7736..000000000 --- a/commons/src/note-frontmatter-parser/parse-raw-frontmatter-from-yaml.ts +++ /dev/null @@ -1,76 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ -import { defaultNoteFrontmatter } from '../note-frontmatter/index.js' -import { NoteTextDirection, NoteType, OpenGraph } from '../note-frontmatter/index.js' -import { SlideOptions } from '../note-frontmatter/index.js' -import { ISO6391 } from '../note-frontmatter/iso6391.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({ - 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) - .lowercase() - .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({ - 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({ - title: Joi.string().optional(), - image: Joi.string().uri().optional(), - }) - .unknown(true) - .optional() - .default(defaultNoteFrontmatter.opengraph), -}) - .default(defaultNoteFrontmatter) - .unknown(true) - -type ParserResult = - | { - error: undefined - warning?: ValidationError - value: RawNoteFrontmatter - } - | { - error: Error - warning?: ValidationError - value: undefined - } - -export const parseRawFrontmatterFromYaml = (rawYaml: string): ParserResult => { - try { - const rawNoteFrontmatter = load(rawYaml) - return schema.validate(rawNoteFrontmatter, { convert: true }) - } catch { - return { error: new Error('Invalid YAML'), value: undefined } - } -} diff --git a/commons/src/note-frontmatter-parser/parse-tags.spec.ts b/commons/src/note-frontmatter-parser/parse-tags.spec.ts deleted file mode 100644 index 670a0930b..000000000 --- a/commons/src/note-frontmatter-parser/parse-tags.spec.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 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', - ]) - }) -}) diff --git a/commons/src/note-frontmatter-parser/parse-tags.ts b/commons/src/note-frontmatter-parser/parse-tags.ts deleted file mode 100644 index c4c7d7543..000000000 --- a/commons/src/note-frontmatter-parser/parse-tags.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * 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) -} diff --git a/commons/src/note-frontmatter-parser/types.ts b/commons/src/note-frontmatter-parser/types.ts deleted file mode 100644 index c3575dca0..000000000 --- a/commons/src/note-frontmatter-parser/types.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ -import { - Iso6391Language, - NoteTextDirection, - NoteType, - OpenGraph, -} from '../note-frontmatter/index.js' -import { SlideOptions } from '../note-frontmatter/index.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 -} diff --git a/commons/src/note-frontmatter/default-note-frontmatter.ts b/commons/src/note-frontmatter/default-note-frontmatter.ts new file mode 100644 index 000000000..c5a9dcb68 --- /dev/null +++ b/commons/src/note-frontmatter/default-note-frontmatter.ts @@ -0,0 +1,28 @@ +/* + * SPDX-FileCopyrightText: 2026 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { NoteType } from './note-type.js' +import { NoteTextDirection } from './note-text-direction.js' + +export const defaultNoteFrontmatter = { + title: '', + description: '', + tags: [], + type: NoteType.DOCUMENT as const, + breaks: true, + dir: NoteTextDirection.LTR, + robots: '', + lang: 'en' as const, + license: '', + opengraph: {}, + slideOptions: { + transition: 'zoom', + autoSlide: 0, + autoSlideStoppable: true, + backgroundTransition: 'fade', + slideNumber: false, + } as const, +} diff --git a/commons/src/note-frontmatter/default-values.ts b/commons/src/note-frontmatter/default-values.ts deleted file mode 100644 index 03123c7a4..000000000 --- a/commons/src/note-frontmatter/default-values.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ -import { NoteFrontmatter, NoteTextDirection, NoteType } from './frontmatter.js' -import { SlideOptions } from './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, -} diff --git a/commons/src/note-frontmatter/frontmatter.ts b/commons/src/note-frontmatter/frontmatter.ts deleted file mode 100644 index 94e512514..000000000 --- a/commons/src/note-frontmatter/frontmatter.ts +++ /dev/null @@ -1,37 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ -import { ISO6391 } from './iso6391.js' -import { SlideOptions } from './slide-show-options.js' - -export type Iso6391Language = (typeof ISO6391)[number] - -export type OpenGraph = Record - -export enum NoteTextDirection { - LTR = 'ltr', - RTL = 'rtl', -} - -export enum NoteType { - DOCUMENT = 'document', - SLIDE = 'slide', -} - -export type OptionalNoteType = NoteType | '' - -export interface NoteFrontmatter { - title: string - description: string - tags: string[] - robots: string - lang: Iso6391Language - dir: NoteTextDirection - newlinesAreBreaks: boolean - license: string - type: NoteType - opengraph: OpenGraph - slideOptions: SlideOptions -} diff --git a/commons/src/note-frontmatter/index.ts b/commons/src/note-frontmatter/index.ts index 82311938d..8c560a843 100644 --- a/commons/src/note-frontmatter/index.ts +++ b/commons/src/note-frontmatter/index.ts @@ -4,7 +4,10 @@ * SPDX-License-Identifier: AGPL-3.0-only */ +export * from './default-note-frontmatter.js' export * from './iso6391.js' -export * from './frontmatter.js' -export * from './slide-show-options.js' -export * from './default-values.js' +export * from './note-frontmatter.js' +export * from './note-text-direction.js' +export * from './note-type.js' +export * from './parse-note-frontmatter.js' +export * from './parse-tags-field.js' diff --git a/commons/src/note-frontmatter/note-frontmatter.ts b/commons/src/note-frontmatter/note-frontmatter.ts new file mode 100644 index 000000000..4196f767f --- /dev/null +++ b/commons/src/note-frontmatter/note-frontmatter.ts @@ -0,0 +1,58 @@ +/* + * SPDX-FileCopyrightText: 2026 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ +import z from 'zod' +import type { RevealOptions } from 'reveal.js' +import { parseTagsField } from './parse-tags-field.js' +import { NoteType } from './note-type.js' +import { ISO6391 } from './iso6391.js' +import { defaultNoteFrontmatter } from './default-note-frontmatter.js' +import { NoteTextDirection } from './note-text-direction.js' + +// Reveal.js provides types but no runtime validation of fields, so we validate them as unknown and accept everything +const slideOptionsSchema = z + .record(z.unknown()) + .default(defaultNoteFrontmatter.slideOptions) + .transform((slideOptions) => slideOptions as Partial) + +export const NoteFrontmatterSchema = z + .object({ + title: z.coerce.string().default(defaultNoteFrontmatter.title).describe('Title of the note'), + description: z.coerce + .string() + .default(defaultNoteFrontmatter.description) + .describe('Description of the note'), + tags: z + .preprocess(parseTagsField, z.array(z.string())) + .describe('List of tags for filtering on the explore page') + .default([]), + type: z + .nativeEnum(NoteType) + .default(defaultNoteFrontmatter.type) + .describe('Type of the renderer to use'), + robots: z.string().default(defaultNoteFrontmatter.robots).describe('Robots meta tag'), + lang: z.enum(ISO6391).default(defaultNoteFrontmatter.lang).describe('Language of the note'), + dir: z + .nativeEnum(NoteTextDirection) + .default(defaultNoteFrontmatter.dir) + .describe('Text writing direction'), + breaks: z + .boolean() + .default(defaultNoteFrontmatter.breaks) + .describe('Treat newlines as line break'), + license: z + .string() + .default(defaultNoteFrontmatter.license) + .describe('License header field to add to the HTML'), + opengraph: z + .record(z.coerce.string()) + .default(defaultNoteFrontmatter.opengraph) + .describe('OpenGraph meta tags'), + slideOptions: slideOptionsSchema.describe('Reveal.js options for slides'), + }) + .describe('Frontmatter options parsed by HedgeDoc, others are ignored') + .passthrough() + +export type NoteFrontmatter = z.infer diff --git a/commons/src/note-frontmatter/note-text-direction.ts b/commons/src/note-frontmatter/note-text-direction.ts new file mode 100644 index 000000000..a14e46fc4 --- /dev/null +++ b/commons/src/note-frontmatter/note-text-direction.ts @@ -0,0 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2026 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ +export enum NoteTextDirection { + LTR = 'ltr', + RTL = 'rtl', +} diff --git a/commons/src/note-frontmatter/note-type.ts b/commons/src/note-frontmatter/note-type.ts new file mode 100644 index 000000000..a5ee74e55 --- /dev/null +++ b/commons/src/note-frontmatter/note-type.ts @@ -0,0 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ +export enum NoteType { + DOCUMENT = 'document', + SLIDE = 'slide', +} diff --git a/commons/src/note-frontmatter/parse-note-frontmatter.spec.ts b/commons/src/note-frontmatter/parse-note-frontmatter.spec.ts new file mode 100644 index 000000000..d9d8a5aea --- /dev/null +++ b/commons/src/note-frontmatter/parse-note-frontmatter.spec.ts @@ -0,0 +1,25 @@ +/* + * SPDX-FileCopyrightText: 2026 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ +import { describe, expect, it } from '@jest/globals' +import { parseNoteFrontmatter } from './parse-note-frontmatter.js' + +describe('parseNoteFrontmatter', () => { + it('marks comma-separated tags as deprecated', () => { + const result = parseNoteFrontmatter('tags: foo, bar') + + expect(result.error).toBeUndefined() + expect(result.usesDeprecatedTagsFormat).toBe(true) + expect(result.value?.tags).toStrictEqual(['foo', 'bar']) + }) + + it('does not mark array tags as deprecated', () => { + const result = parseNoteFrontmatter('tags:\n- foo\n- bar') + + expect(result.error).toBeUndefined() + expect(result.usesDeprecatedTagsFormat).toBe(false) + expect(result.value?.tags).toStrictEqual(['foo', 'bar']) + }) +}) diff --git a/commons/src/note-frontmatter/parse-note-frontmatter.ts b/commons/src/note-frontmatter/parse-note-frontmatter.ts new file mode 100644 index 000000000..a64647bb8 --- /dev/null +++ b/commons/src/note-frontmatter/parse-note-frontmatter.ts @@ -0,0 +1,80 @@ +/* + * SPDX-FileCopyrightText: 2026 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { load } from 'js-yaml' +import { type NoteFrontmatter, NoteFrontmatterSchema } from './note-frontmatter.js' + +export type NoteFrontmatterParserResult = + | { + error: undefined + value: NoteFrontmatter + usesDeprecatedTagsFormat: boolean + rawTagsField: unknown + } + | { + error: Error + value: undefined + usesDeprecatedTagsFormat: boolean + rawTagsField: unknown + } + +/** + * Parses the note frontmatter from the given YAML string and returns the parsed result with collected errors. + * + * @param rawYaml The raw YAML string to parse + * @returns A wrapper containing the parsed note frontmatter and an error in case parsing failed + */ +export const parseNoteFrontmatter = (rawYaml: string): NoteFrontmatterParserResult => { + try { + const rawNoteFrontmatter = load(rawYaml) + const rawNoteFrontmatterRecord = + typeof rawNoteFrontmatter === 'object' && + rawNoteFrontmatter !== null && + !Array.isArray(rawNoteFrontmatter) + ? (rawNoteFrontmatter as Record) + : undefined + const rawTagsField = rawNoteFrontmatterRecord?.tags + const usesDeprecatedTagsFormat = + rawNoteFrontmatterRecord !== undefined && + Object.hasOwn(rawNoteFrontmatterRecord, 'tags') && + !Array.isArray(rawNoteFrontmatterRecord.tags) + if ( + typeof rawNoteFrontmatter !== 'object' || + rawNoteFrontmatter === null || + Array.isArray(rawNoteFrontmatter) || + Object.keys(rawNoteFrontmatter).length === 0 + ) { + return { + error: new Error('Invalid YAML'), + value: undefined, + usesDeprecatedTagsFormat: false, + rawTagsField: undefined, + } + } + const parsedNoteFrontmatter = NoteFrontmatterSchema.safeParse(rawNoteFrontmatter) + if (!parsedNoteFrontmatter.success) { + return { + error: parsedNoteFrontmatter.error, + value: undefined, + usesDeprecatedTagsFormat, + rawTagsField: rawTagsField, + } + } + return { + error: undefined, + value: parsedNoteFrontmatter.data, + usesDeprecatedTagsFormat, + rawTagsField: rawTagsField, + } + } catch (error: unknown) { + return { + error: error as Error, + value: undefined, + usesDeprecatedTagsFormat: false, + rawTagsField: undefined, + } + } +} diff --git a/commons/src/note-frontmatter/parse-tags-field.spec.ts b/commons/src/note-frontmatter/parse-tags-field.spec.ts new file mode 100644 index 000000000..edc13bdd0 --- /dev/null +++ b/commons/src/note-frontmatter/parse-tags-field.spec.ts @@ -0,0 +1,29 @@ +/* + * SPDX-FileCopyrightText: 2026 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ +import { describe, expect, it } from '@jest/globals' +import { parseTagsField } from './parse-tags-field.js' + +describe('parseTagsField', () => { + it('parses comma-separated strings', () => { + expect(parseTagsField('foo, bar,baz')).toStrictEqual(['foo', 'bar', 'baz']) + }) + + it('removes empty string entries from comma-separated strings', () => { + expect(parseTagsField('foo,, bar')).toStrictEqual(['foo', 'bar']) + }) + + it('parses arrays by stringifying each item', () => { + expect(parseTagsField(['foo', 123, true])).toStrictEqual(['foo', '123', 'true']) + }) + + it('removes empty string entries from array entries', () => { + expect(parseTagsField(['foo', '', 'bar'])).toStrictEqual(['foo', 'bar']) + }) + + it('returns an empty array for invalid input', () => { + expect(parseTagsField(undefined)).toStrictEqual([]) + }) +}) diff --git a/commons/src/note-frontmatter/parse-tags-field.ts b/commons/src/note-frontmatter/parse-tags-field.ts new file mode 100644 index 000000000..6476a2d98 --- /dev/null +++ b/commons/src/note-frontmatter/parse-tags-field.ts @@ -0,0 +1,26 @@ +/* + * SPDX-FileCopyrightText: 2026 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +/** + * Parses the tags field from the note frontmatter, which should be an array, + * but for backwards-compatibility we also accept comma-separated strings. + * Empty tags are dropped from the list and whitespace is trimmed. + * + * @param input The input to parse + * @returns The parsed list of tags or an empty array if the input is invalid + */ +export const parseTagsField = (input: unknown): string[] => { + if (typeof input === 'string') { + return input + .split(',') + .map((tag) => tag.trim()) + .filter((tag) => !!tag) + } + if (Array.isArray(input)) { + return input.map((tag) => tag.toString().trim()).filter((tag) => !!tag) + } + return [] +} diff --git a/commons/src/note-frontmatter/slide-show-options.ts b/commons/src/note-frontmatter/slide-show-options.ts deleted file mode 100644 index a53a2f55d..000000000 --- a/commons/src/note-frontmatter/slide-show-options.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ -import type { RevealOptions } from 'reveal.js' - -type WantedRevealOptions = - | 'autoSlide' - | 'autoSlideStoppable' - | 'transition' - | 'backgroundTransition' - | 'slideNumber' - -export type SlideOptions = Pick diff --git a/commons/src/title-extraction/generate-note-title.spec.ts b/commons/src/title-extraction/generate-note-title.spec.ts index 3fe686502..6011ebd34 100644 --- a/commons/src/title-extraction/generate-note-title.spec.ts +++ b/commons/src/title-extraction/generate-note-title.spec.ts @@ -3,9 +3,11 @@ * * SPDX-License-Identifier: AGPL-3.0-only */ -import { NoteFrontmatter, NoteTextDirection, NoteType } from '../note-frontmatter/frontmatter.js' -import { generateNoteTitle } from './generate-note-title.js' import { describe, expect, it } from '@jest/globals' +import type { NoteFrontmatter } from '../note-frontmatter/note-frontmatter.js' +import { NoteType } from '../note-frontmatter/note-type.js' +import { generateNoteTitle } from './generate-note-title.js' +import { NoteTextDirection } from '../note-frontmatter/note-text-direction.js' const testFrontmatter: NoteFrontmatter = { title: '', @@ -14,7 +16,7 @@ const testFrontmatter: NoteFrontmatter = { robots: '', lang: 'en', dir: NoteTextDirection.LTR, - newlinesAreBreaks: true, + breaks: true, license: '', type: NoteType.DOCUMENT, opengraph: {}, diff --git a/commons/src/title-extraction/generate-note-title.ts b/commons/src/title-extraction/generate-note-title.ts index 6478c7a6c..60d56a2ef 100644 --- a/commons/src/title-extraction/generate-note-title.ts +++ b/commons/src/title-extraction/generate-note-title.ts @@ -3,7 +3,7 @@ * * SPDX-License-Identifier: AGPL-3.0-only */ -import type { NoteFrontmatter } from '../note-frontmatter/frontmatter.js' +import type { NoteFrontmatter } from '../note-frontmatter/note-frontmatter.js' /** * Generates the note title from the given frontmatter or the first heading in the markdown content. diff --git a/frontend/src/components/common/renderer-iframe/hooks/use-send-additional-configuration-to-renderer.ts b/frontend/src/components/common/renderer-iframe/hooks/use-send-additional-configuration-to-renderer.ts index 68fc253db..6dc943592 100644 --- a/frontend/src/components/common/renderer-iframe/hooks/use-send-additional-configuration-to-renderer.ts +++ b/frontend/src/components/common/renderer-iframe/hooks/use-send-additional-configuration-to-renderer.ts @@ -15,7 +15,7 @@ import { useMemo } from 'react' */ export const useSendAdditionalConfigurationToRenderer = (rendererReady: boolean): void => { const darkModePreference = useApplicationState((state) => state.darkMode.darkModePreference) - const newlinesAreBreaks = useApplicationState((state) => state.noteDetails?.frontmatter.newlinesAreBreaks) + const newlinesAreBreaks = useApplicationState((state) => state.noteDetails?.frontmatter.breaks) useSendToRenderer( useMemo(() => { diff --git a/frontend/src/components/editor-page/editor-pane/linter/frontmatter-linter.spec.ts b/frontend/src/components/editor-page/editor-pane/linter/frontmatter-linter.spec.ts index 704c5abc0..e5b88efa4 100644 --- a/frontend/src/components/editor-page/editor-pane/linter/frontmatter-linter.spec.ts +++ b/frontend/src/components/editor-page/editor-pane/linter/frontmatter-linter.spec.ts @@ -75,6 +75,12 @@ describe('FrontmatterLinter', () => { ) }) }) + it('does not warn for the new array format', () => { + const frontmatterLinter = new FrontmatterLinter() + const editorView = mockEditorView('---\ntags:\n- a\n---') + + expect(frontmatterLinter.lint(editorView)).toStrictEqual([]) + }) it('with invalid yaml', () => { testFrontmatterLinter('---\n1\n 2: 3\n---', { from: 4, diff --git a/frontend/src/components/editor-page/editor-pane/linter/frontmatter-linter.ts b/frontend/src/components/editor-page/editor-pane/linter/frontmatter-linter.ts index 315df4fda..25cba3d82 100644 --- a/frontend/src/components/editor-page/editor-pane/linter/frontmatter-linter.ts +++ b/frontend/src/components/editor-page/editor-pane/linter/frontmatter-linter.ts @@ -6,7 +6,7 @@ import type { Linter } from './linter' import type { Diagnostic } from '@codemirror/lint' import type { EditorView } from '@codemirror/view' -import { extractFrontmatter, parseRawFrontmatterFromYaml, parseTags } from '@hedgedoc/commons' +import { extractFrontmatter, parseNoteFrontmatter, parseTagsField } from '@hedgedoc/commons' import { t } from 'i18next' /** @@ -24,13 +24,15 @@ export class FrontmatterLinter implements Linter { const frontmatterLines = lines.slice(1, frontmatterExtraction.lineOffset - 1) const startOfYaml = lines[0].length + 1 const endOfYaml = startOfYaml + frontmatterLines.join('\n').length - const rawNoteFrontmatter = parseRawFrontmatterFromYaml(frontmatterExtraction.rawText) - if (rawNoteFrontmatter.error) { - return this.createErrorDiagnostics(startOfYaml, endOfYaml, rawNoteFrontmatter.error, 'error') - } else if (rawNoteFrontmatter.warning) { - return this.createErrorDiagnostics(startOfYaml, endOfYaml, rawNoteFrontmatter.warning, 'warning') - } else if (!Array.isArray(rawNoteFrontmatter.value.tags)) { - return this.createReplaceSingleStringTagsDiagnostic(rawNoteFrontmatter.value.tags, frontmatterLines, startOfYaml) + const frontmatterParseResult = parseNoteFrontmatter(frontmatterExtraction.rawText) + if (frontmatterParseResult.error) { + return this.createErrorDiagnostics(startOfYaml, endOfYaml, frontmatterParseResult.error, 'error') + } else if (frontmatterParseResult.usesDeprecatedTagsFormat) { + return this.createReplaceSingleStringTagsDiagnostic( + frontmatterParseResult.rawTagsField, + frontmatterLines, + startOfYaml + ) } return [] } @@ -52,12 +54,12 @@ export class FrontmatterLinter implements Linter { } private createReplaceSingleStringTagsDiagnostic( - rawTags: string, + rawTags: unknown, frontmatterLines: string[], startOfYaml: number ): Diagnostic[] { - const tags: string[] = parseTags(rawTags) - const replacedText = 'tags:\n- ' + tags.join('\n- ') + const tags = this.parseDeprecatedTags(rawTags) + const replacedText = `tags:\n- ${tags.join('\n- ')}` const tagsLineIndex = frontmatterLines.findIndex((value) => value.startsWith('tags: ')) const linesBeforeTagsLine = frontmatterLines.slice(0, tagsLineIndex) const from = startOfYaml + linesBeforeTagsLine.join('\n').length + linesBeforeTagsLine.length @@ -81,4 +83,17 @@ export class FrontmatterLinter implements Linter { } ] } + + private parseDeprecatedTags(rawTags: unknown): string[] { + if (rawTags === undefined || rawTags === null) { + return [] + } + if (typeof rawTags === 'string' || Array.isArray(rawTags)) { + return parseTagsField(rawTags) + } + if (typeof rawTags === 'number' || typeof rawTags === 'boolean' || typeof rawTags === 'bigint') { + return parseTagsField(String(rawTags)) + } + return [] + } } diff --git a/frontend/src/components/markdown-renderer/hooks/use-reveal.ts b/frontend/src/components/markdown-renderer/hooks/use-reveal.ts index 9c2290856..cf148c2ac 100644 --- a/frontend/src/components/markdown-renderer/hooks/use-reveal.ts +++ b/frontend/src/components/markdown-renderer/hooks/use-reveal.ts @@ -4,9 +4,9 @@ * SPDX-License-Identifier: AGPL-3.0-only */ import { Logger } from '../../../utils/logger' -import type { SlideOptions } from '@hedgedoc/commons' import { useEffect, useRef, useState } from 'react' import type Reveal from 'reveal.js' +import type { RevealOptions } from 'reveal.js' const log = new Logger('reveal.js') @@ -33,7 +33,7 @@ const initialSlideState: SlideState = { * @return The current state of reveal.js * @see https://revealjs.com/ */ -export const useReveal = (markdownContentLines: string[], slideOptions?: SlideOptions): REVEAL_STATUS => { +export const useReveal = (markdownContentLines: string[], slideOptions?: RevealOptions): REVEAL_STATUS => { const [deck, setDeck] = useState() const [revealStatus, setRevealStatus] = useState(REVEAL_STATUS.NOT_INITIALISED) const currentSlideState = useRef(initialSlideState) diff --git a/frontend/src/components/render-page/render-page-content.tsx b/frontend/src/components/render-page/render-page-content.tsx index d0a522a45..7e8d84f37 100644 --- a/frontend/src/components/render-page/render-page-content.tsx +++ b/frontend/src/components/render-page/render-page-content.tsx @@ -14,7 +14,7 @@ import { useRendererReceiveHandler } from './window-post-message-communicator/ho import type { BaseConfiguration } from './window-post-message-communicator/rendering-message' import { CommunicationMessageType, RendererType } from './window-post-message-communicator/rendering-message' import { countWords } from './word-counter' -import type { SlideOptions } from '@hedgedoc/commons' +import type { RevealOptions } from 'reveal.js' import { EventEmitter2 } from 'eventemitter2' import React, { useCallback, useDeferredValue, useEffect, useMemo, useRef, useState } from 'react' import { setPrintMode } from '../../redux/print-mode/methods' @@ -32,7 +32,7 @@ export const RenderPageContent: React.FC = () => { const communicator = useRendererToEditorCommunicator() const sendScrolling = useRef(false) const [newLinesAreBreaks, setNewLinesAreBreaks] = useState(true) - const [slideOptions, setSlideOptions] = useState() + const [slideOptions, setSlideOptions] = useState() useRendererReceiveHandler( CommunicationMessageType.SET_SLIDE_OPTIONS, diff --git a/frontend/src/components/render-page/renderers/slideshow/slideshow-markdown-renderer.tsx b/frontend/src/components/render-page/renderers/slideshow/slideshow-markdown-renderer.tsx index 901c5c19e..63a109f4e 100644 --- a/frontend/src/components/render-page/renderers/slideshow/slideshow-markdown-renderer.tsx +++ b/frontend/src/components/render-page/renderers/slideshow/slideshow-markdown-renderer.tsx @@ -11,11 +11,11 @@ import { RendererType } from '../../window-post-message-communicator/rendering-m import type { CommonMarkdownRendererProps } from '../common-markdown-renderer-props' import { LoadingSlide } from './loading-slide' import styles from './slideshow.module.scss' -import type { SlideOptions } from '@hedgedoc/commons' +import type { RevealOptions } from 'reveal.js' import React, { useMemo, useRef } from 'react' export interface SlideshowMarkdownRendererProps extends CommonMarkdownRendererProps { - slideOptions?: SlideOptions + slideOptions?: RevealOptions } /** diff --git a/frontend/src/components/render-page/window-post-message-communicator/rendering-message.ts b/frontend/src/components/render-page/window-post-message-communicator/rendering-message.ts index 537a569f4..3f5945c2d 100644 --- a/frontend/src/components/render-page/window-post-message-communicator/rendering-message.ts +++ b/frontend/src/components/render-page/window-post-message-communicator/rendering-message.ts @@ -5,7 +5,7 @@ */ import type { DarkModePreference } from '../../../redux/dark-mode/types' import type { ScrollState } from '../../editor-page/synced-scroll/scroll-props' -import type { SlideOptions } from '@hedgedoc/commons' +import type { RevealOptions } from 'reveal.js' export enum CommunicationMessageType { SET_MARKDOWN_CONTENT = 'SET_MARKDOWN_CONTENT', @@ -81,7 +81,7 @@ export interface SetScrollStateMessage { export interface SetSlideOptionsMessage { type: CommunicationMessageType.SET_SLIDE_OPTIONS - slideOptions: SlideOptions + slideOptions: RevealOptions } export interface OnHeightChangeMessage { diff --git a/frontend/src/redux/note-details/build-state-from-updated-markdown-content.ts b/frontend/src/redux/note-details/build-state-from-updated-markdown-content.ts index 80aa288d2..f6b0c3139 100644 --- a/frontend/src/redux/note-details/build-state-from-updated-markdown-content.ts +++ b/frontend/src/redux/note-details/build-state-from-updated-markdown-content.ts @@ -7,13 +7,7 @@ import { calculateLineStartIndexes } from './calculate-line-start-indexes' import { initialState } from './initial-state' import type { NoteDetails } from './types' import type { FrontmatterExtractionResult, NoteFrontmatter } from '@hedgedoc/commons' -import { - convertRawFrontmatterToNoteFrontmatter, - extractFrontmatter, - generateNoteTitle, - parseRawFrontmatterFromYaml -} from '@hedgedoc/commons' -import { Optional } from '@mrdrogdrog/optional' +import { extractFrontmatter, generateNoteTitle, parseNoteFrontmatter } from '@hedgedoc/commons' /** * Copies a {@link NoteDetails} but with another markdown content. @@ -93,12 +87,12 @@ const buildStateFromFrontmatterUpdate = ( return buildStateFromFrontmatter(state, parseFrontmatter(frontmatterExtraction), frontmatterExtraction) } -const parseFrontmatter = (frontmatterExtraction: FrontmatterExtractionResult) => { - return Optional.of(parseRawFrontmatterFromYaml(frontmatterExtraction.rawText)) - .filter((frontmatter) => frontmatter.error === undefined) - .map((frontmatter) => frontmatter.value) - .map((value) => convertRawFrontmatterToNoteFrontmatter(value)) - .orElse(initialState.frontmatter) +const parseFrontmatter = (frontmatterExtraction: FrontmatterExtractionResult): NoteFrontmatter => { + const parseResult = parseNoteFrontmatter(frontmatterExtraction.rawText) + if (parseResult.error !== undefined) { + return initialState.frontmatter + } + return parseResult.value } const buildStateFromFrontmatter = ( diff --git a/frontend/src/redux/note-details/reducers/build-state-from-set-note-data-from-server.spec.ts b/frontend/src/redux/note-details/reducers/build-state-from-set-note-data-from-server.spec.ts index b79c1bfdb..7df09c181 100644 --- a/frontend/src/redux/note-details/reducers/build-state-from-set-note-data-from-server.spec.ts +++ b/frontend/src/redux/note-details/reducers/build-state-from-set-note-data-from-server.spec.ts @@ -78,7 +78,7 @@ describe('build state from set note data from server', () => { lang: 'en', license: '', dir: NoteTextDirection.LTR, - newlinesAreBreaks: true, + breaks: true, type: NoteType.DOCUMENT, opengraph: {}, slideOptions: { diff --git a/yarn.lock b/yarn.lock index 161f03185..cf4a45f30 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2832,22 +2832,6 @@ __metadata: languageName: node linkType: hard -"@hapi/hoek@npm:^9.0.0, @hapi/hoek@npm:^9.3.0": - version: 9.3.0 - resolution: "@hapi/hoek@npm:9.3.0" - checksum: 10c0/a096063805051fb8bba4c947e293c664b05a32b47e13bc654c0dd43813a1cec993bdd8f29ceb838020299e1d0f89f68dc0d62a603c13c9cc8541963f0beca055 - languageName: node - linkType: hard - -"@hapi/topo@npm:^5.1.0": - version: 5.1.0 - resolution: "@hapi/topo@npm:5.1.0" - dependencies: - "@hapi/hoek": "npm:^9.0.0" - checksum: 10c0/b16b06d9357947149e032bdf10151eb71aea8057c79c4046bf32393cb89d0d0f7ca501c40c0f7534a5ceca078de0700d2257ac855c15e59fe4e00bba2f25c86f - languageName: node - linkType: hard - "@hedgedoc/backend@workspace:backend": version: 0.0.0-use.local resolution: "@hedgedoc/backend@workspace:backend" @@ -2945,7 +2929,6 @@ __metadata: domhandler: "npm:5.0.3" eventemitter2: "npm:6.4.9" jest: "npm:29.7.0" - joi: "npm:17.13.3" js-yaml: "npm:4.1.1" reveal.js: "npm:5.2.1" ts-jest: "npm:29.4.6" @@ -5246,29 +5229,6 @@ __metadata: languageName: node linkType: hard -"@sideway/address@npm:^4.1.5": - version: 4.1.5 - resolution: "@sideway/address@npm:4.1.5" - dependencies: - "@hapi/hoek": "npm:^9.0.0" - checksum: 10c0/638eb6f7e7dba209053dd6c8da74d7cc995e2b791b97644d0303a7dd3119263bcb7225a4f6804d4db2bc4f96e5a9d262975a014f58eae4d1753c27cbc96ef959 - languageName: node - linkType: hard - -"@sideway/formula@npm:^3.0.1": - version: 3.0.1 - resolution: "@sideway/formula@npm:3.0.1" - checksum: 10c0/3fe81fa9662efc076bf41612b060eb9b02e846ea4bea5bd114f1662b7f1541e9dedcf98aff0d24400bcb92f113964a50e0290b86e284edbdf6346fa9b7e2bf2c - languageName: node - linkType: hard - -"@sideway/pinpoint@npm:^2.0.0": - version: 2.0.0 - resolution: "@sideway/pinpoint@npm:2.0.0" - checksum: 10c0/d2ca75dacaf69b8fc0bb8916a204e01def3105ee44d8be16c355e5f58189eb94039e15ce831f3d544f229889ccfa35562a0ce2516179f3a7ee1bbe0b71e55b36 - languageName: node - linkType: hard - "@sinclair/typebox@npm:^0.27.8": version: 0.27.8 resolution: "@sinclair/typebox@npm:0.27.8" @@ -12377,19 +12337,6 @@ __metadata: languageName: node linkType: hard -"joi@npm:17.13.3": - version: 17.13.3 - resolution: "joi@npm:17.13.3" - dependencies: - "@hapi/hoek": "npm:^9.3.0" - "@hapi/topo": "npm:^5.1.0" - "@sideway/address": "npm:^4.1.5" - "@sideway/formula": "npm:^3.0.1" - "@sideway/pinpoint": "npm:^2.0.0" - checksum: 10c0/9262aef1da3f1bec5b03caf50c46368899fe03b8ff26cbe3d53af4584dd1049079fc97230bbf1500b6149db7cc765b9ee45f0deb24bb6fc3fa06229d7148c17f - languageName: node - linkType: hard - "jose@npm:5.10.0": version: 5.10.0 resolution: "jose@npm:5.10.0"