From dc11daa54041b429ae75157e12d60a32c91a9070 Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Fri, 25 Sep 2026 19:33:26 +0200 Subject: [PATCH] 10a - a list a numbered list becomes merges with its neighbours, and a note's name is cleaned before it is checked --- src/markdown/emit/plain-inline.ts | 12 ++++++++++-- src/markdown/emit/plain-reduction.ts | 20 ++++++++++++-------- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/markdown/emit/plain-inline.ts b/src/markdown/emit/plain-inline.ts index 401e561..4cd11e7 100644 --- a/src/markdown/emit/plain-inline.ts +++ b/src/markdown/emit/plain-inline.ts @@ -49,7 +49,7 @@ function nodeLeaves(node: AdfNode, container: LineContainer, path: ConvertErrorP if (node.type === 'placeholder') return success([]) if (node.type === 'mention') return success(textLeaves(nonEmpty(attrs['text']) ?? idMention(attrs['id']), marks, container)) if (node.type === 'status') return success(textLeaves(attrs['text'], marks, container)) - if (['extension', 'inlineExtension'].includes(node.type)) return success(textLeaves(nonEmpty(attrs['text']), marks, container, nonEmpty(attrs['extensionKey']) ?? 'extension')) + if (['extension', 'inlineExtension'].includes(node.type)) return success(textLeaves(nonEmpty(attrs['text']), marks, container, noteName(attrs['extensionKey']) ?? 'extension')) if (node.type === 'syncBlock') return success(noteLeaves('synced block')) if (['media', 'mediaInline'].includes(node.type)) return success(mediaLeaves(attrs, marks, container)) if (['blockCard', 'embedCard', 'inlineCard'].includes(node.type)) return success(cardLeaves(attrs, marks, container)) @@ -87,7 +87,15 @@ function mediaLeaves(attrs: Readonly, marks: readonly AdfMark[], } function noteLeaves(name: string): AdfNode[] { - return [textLeaf(`(${name.replace(/[\r\u0000]/g, '').replace(/\n/g, ' ')} not included)`, [{ type: 'em' }])] + return [textLeaf(`(${name} not included)`, [{ type: 'em' }])] +} + +function noteName(value: unknown): string | undefined { + return typeof value === 'string' ? nonEmpty(oneLine(value).trim()) : undefined +} + +export function oneLine(text: string): string { + return text.replace(/[\r\u0000]/g, '').replace(/\n/g, ' ') } function nonEmpty(value: unknown): string | undefined { diff --git a/src/markdown/emit/plain-reduction.ts b/src/markdown/emit/plain-reduction.ts index 1729ba3..5be4c8e 100644 --- a/src/markdown/emit/plain-reduction.ts +++ b/src/markdown/emit/plain-reduction.ts @@ -3,7 +3,7 @@ import { adfDocumentFault, nodeAttrs, nodeContent } from '../../adf/document.ts' import { blockNodeModel } from '../../adf/block-nodes.ts' import { commonMarkSpelling, largestListMarker, type SpellingMemo } from './adf-to-markdown.ts' import { failure, faulted, success, type ConvertErrorPath, type Result } from '../../result.ts' -import { inlineLeaves, isBlockNodeType, reduceInline, writableHref } from './plain-inline.ts' +import { inlineLeaves, isBlockNodeType, oneLine, reduceInline, writableHref } from './plain-inline.ts' import { inlineNodeModel } from '../../adf/inline-nodes.ts' import { languageSlot } from '../code-language.ts' import { largestNesting } from '../../nesting.ts' @@ -114,16 +114,20 @@ function plainSequence(blocks: readonly AdfNode[], reduction: Reduction): Result const listed = sequence[index] if (listed === undefined || (listed.type !== 'bulletList' && listed.type !== 'orderedList')) continue const block = numberedPastMarkers(listed) - sequence[index] = block - if (commonMarkSpelling(block, reduction.path, reduction.depth, reduction.memo)?.ok === true) continue - const held = nodeContent(block).flatMap(nodeContent) - const from = Math.max(0, index - 1) - sequence = [...sequence.slice(0, from), ...mergedLists([...sequence.slice(from, index), ...held, ...sequence.slice(index + 1, index + 2)]), ...sequence.slice(index + 2)] - index = from - 1 + const spelled = block === listed && commonMarkSpelling(block, reduction.path, reduction.depth, reduction.memo)?.ok === true + if (spelled) continue + sequence = spliced(sequence, index, block === listed ? nodeContent(block).flatMap(nodeContent) : [block]) + index = Math.max(0, index - 1) - 1 } return success(sequence) } +// The replacement merges with the lists beside it, so no two lists of one type stand adjacent. +function spliced(sequence: readonly AdfNode[], index: number, replacement: readonly AdfNode[]): AdfNode[] { + const from = Math.max(0, index - 1) + return [...sequence.slice(0, from), ...mergedLists([...sequence.slice(from, index), ...replacement, ...sequence.slice(index + 1, index + 2)]), ...sequence.slice(index + 2)] +} + // A numbered list whose markers run past CommonMark's keeps its numbers as text in a bullet list. function numberedPastMarkers(list: AdfNode): AdfNode { const order = nodeAttrs(list)['order'] @@ -319,7 +323,7 @@ function reduceMedia(media: AdfNode, reduction: Reduction): Result { const url = attrs['url'] if (attrs['type'] !== 'external' || typeof url !== 'string') return paragraphOfNode(media, reduction) const held = attrs['alt'] - const alt = typeof held === 'string' ? held.replace(/[\r\u0000]/g, '').replace(/\n/g, ' ').trim() : '' + const alt = typeof held === 'string' ? oneLine(held).trim() : '' const external: AdfNode = { attrs: alt === '' ? { type: 'external', url: writableHref(url) } : { alt, type: 'external', url: writableHref(url) }, type: 'media' } const image: AdfNode = { attrs: { layout: 'center' }, content: [external], type: 'mediaSingle' } return success([image])