Files
BoosAPI/desktop/libs/markstream-vue/packages/markstream-vue2/src/utils/nestedNodes.ts
T
boos e0a5f57793 feat(desktop): embed BoosAPI Desktop client as a subproject
Vendor the rebranded BoosAPI Desktop client (Tauri + Vue) into metapi as
a monorepo subdirectory under desktop/. Source only — node_modules,
dist, Cargo target and the upstream .git history are excluded.

desktop/ keeps its own independent Cargo workspace (desktop/src-tauri
with crates/*); it is not part of the backend boosapi crate, so a
`cargo check` at the metapi root still compiles only the backend. Build
the desktop app from desktop/src-tauri per desktop/README.md.

Note: app icons are still placeholder assets pending the final BoosAPI
logo. The updater signing keys live outside the repo and are not
included.
2026-06-22 11:32:57 +08:00

98 lines
2.9 KiB
TypeScript

import type { BaseNode, MarkdownIt, ParseOptions } from 'stream-markdown-parser'
import { getMarkdown, mergeCustomHtmlTags, parseMarkdownToStructure } from 'stream-markdown-parser'
export interface NestedMarkdownNodesInput {
node?: (BaseNode & Record<string, any>) | null
nodes?: readonly BaseNode[] | null
content?: string | null
}
export interface NestedMarkdownNodesOptions {
cacheKey?: string
final?: boolean
parseOptions?: ParseOptions
customHtmlTags?: readonly string[]
customMarkdownIt?: (markdown: MarkdownIt) => MarkdownIt
}
const DEFAULT_CACHE_KEY = 'markstream-vue2-nested-nodes'
const markdownCache = new Map<string, MarkdownIt>()
export function parseNestedMarkdownToNodes(
input: NestedMarkdownNodesInput,
options: NestedMarkdownNodesOptions = {},
): BaseNode[] {
if (Array.isArray(input.nodes))
return input.nodes.slice()
const nestedNode = input.node
if (nestedNode) {
const children = getNodeList(nestedNode.children)
if (children.length > 0)
return children.slice()
}
const content = resolveContent(input)
if (!content)
return []
const parseOptions = resolveParseOptions(input, options)
const markdown = resolveMarkdownInstance(options)
return parseMarkdownToStructure(content, markdown, parseOptions)
}
function resolveContent(input: NestedMarkdownNodesInput) {
if (typeof input.content === 'string')
return input.content
if (typeof input.node?.content === 'string')
return input.node.content
return ''
}
function resolveParseOptions(
input: NestedMarkdownNodesInput,
options: NestedMarkdownNodesOptions,
): ParseOptions | undefined {
const base = options.parseOptions ?? {}
const resolvedFinal = options.final ?? resolveFinalFromNode(input.node)
const customHtmlTags = mergeCustomHtmlTags(options.customHtmlTags, (base as any).customHtmlTags)
if (resolvedFinal == null && customHtmlTags.length === 0)
return base
return {
...(base as any),
...(resolvedFinal == null ? {} : { final: resolvedFinal }),
...(customHtmlTags.length === 0 ? {} : { customHtmlTags }),
} as ParseOptions
}
function resolveFinalFromNode(node?: (BaseNode & Record<string, any>) | null) {
if (!node || typeof node !== 'object')
return undefined
if (typeof node.loading === 'boolean')
return !node.loading
return undefined
}
function resolveMarkdownInstance(options: NestedMarkdownNodesOptions) {
const normalizedTags = mergeCustomHtmlTags(options.customHtmlTags, (options.parseOptions as any)?.customHtmlTags)
const cacheKey = `${options.cacheKey || DEFAULT_CACHE_KEY}::${normalizedTags.join(',')}`
let markdown = markdownCache.get(cacheKey)
if (!markdown) {
markdown = getMarkdown(cacheKey, {
customHtmlTags: normalizedTags,
})
markdownCache.set(cacheKey, markdown)
}
return options.customMarkdownIt
? options.customMarkdownIt(markdown)
: markdown
}
function getNodeList(value: unknown) {
return Array.isArray(value) ? value as BaseNode[] : []
}