e0a5f57793
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.
98 lines
2.9 KiB
TypeScript
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[] : []
|
|
}
|