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.
This commit is contained in:
boos
2026-06-22 11:32:57 +08:00
parent d9c78ecc73
commit e0a5f57793
2305 changed files with 449355 additions and 0 deletions
@@ -0,0 +1,411 @@
import { getMarkdown, parseMarkdownToStructure } from 'stream-markdown-parser'
import { describe, expect, it } from 'vitest'
import { links, textIncludes } from './utils/midstate-utils'
const md = getMarkdown('t')
const MARKDOWN = `**Markdown链接**:
1. [GitHub官网](https://github.com)
2. [知乎 - 有问题就会有答案](https://www.zhihu.com)
3. **加粗链接**:[Google](https://www.google.com)
4. 嵌套格式的链接:[*斜体链接*](https://example.com)
**普通链接**:
1. https://www.wikipedia.org
2. http://example.com/path?query=test
3. 纯文本URL:https://markdown-guide.readthedocs.io
`
describe('link parsing', () => {
it('parses inline and bare links into link nodes', () => {
const nodes = parseMarkdownToStructure(MARKDOWN, md)
// Flatten paragraph children for assertions
const allLinkNodes: any[] = []
const collect = (n: any) => {
if (!n)
return
if (n.type === 'link')
allLinkNodes.push(n)
if (Array.isArray(n.children)) {
for (const c of n.children) collect(c)
}
if (Array.isArray(n.items)) {
for (const it of n.items) collect(it)
}
}
for (const n of nodes) collect(n)
// Some links may initially be loading (href empty) but should include texts
const texts = allLinkNodes.map(l => l.text?.toString() || '')
expect(texts).toContain('GitHub官网')
expect(texts).toContain('知乎 - 有问题就会有答案')
expect(texts).toContain('Google')
expect(texts).toContain('https://www.wikipedia.org')
expect(texts).toContain('http://example.com/path?query=test')
expect(texts).toContain('https://markdown-guide.readthedocs.io')
// The parser may keep emphasis markup in link.text (e.g. "*斜体链接*"),
// so accept either the raw form or verify the nested emphasis child contains the plain text.
// The parser may keep emphasis markup in link.text or emit nested emphasis nodes.
// Accept any of these by inspecting the link nodes with the tolerant helper.
const foundItalic = allLinkNodes.some(l =>
textIncludes(l, '斜体链接') || textIncludes(l, '*斜体链接*') || textIncludes(l, '_斜体链接_'),
)
expect(foundItalic).toBe(true)
// Check for bare URLs rendered as text nodes inside paragraphs
const containsBare = nodes.some((n: any) => {
return textIncludes(n, 'https://www.wikipedia.org') || textIncludes(n, 'http://example.com/path?query=test') || textIncludes(n, 'https://markdown-guide.readthedocs.io')
})
expect(containsBare).toBe(true)
})
it('test the link tag', () => {
const nodes = parseMarkdownToStructure(`<a href="https://example.com">Example</a>`, md) as any
const p = nodes[0]
const children = p.children
expect(children.length).toBe(1)
const link = p.children[0]
expect(link.type).toBe('link')
expect(link.href).toBe('https://example.com')
expect(link.text).toBe('Example')
expect(link.children[0].type).toBe('text')
expect(link.children[0].content).toBe('Example')
})
it('test the intermediate state of the link tag', () => {
const nodes = parseMarkdownToStructure(`<a href="https://example.com">`, md) as any
expect(nodes.length).toBe(1)
})
it('parses link with parentheses and CJK brackets as a single link', () => {
const special = '[【名称】(test).mp4](https://github.com/Simon-He95/markstream-vueer)'
const nodes = parseMarkdownToStructure(special, md)
// Flatten and collect link nodes
const links: any[] = []
const walk = (n: any) => {
if (!n)
return
if (n.type === 'link')
links.push(n)
if (Array.isArray(n.children))
n.children.forEach(walk)
if (Array.isArray(n.items))
n.items.forEach(walk)
}
nodes.forEach(walk)
// Expect only a single link node in the paragraph without stray text nodes
expect(links.length).toBe(1)
expect(links[0].href).toBe('https://github.com/Simon-He95/markstream-vueer')
// Ensure the full visible text is preserved inside the link
// Accept either direct text aggregation or nested children text
const text = links[0].text || ''
const childText = (links[0].children || [])
.map((c: any) => (c.content ?? c.text ?? ''))
.join('')
expect(text || childText).toBe('【名称】(test).mp4')
// Also assert the paragraph has exactly one child which is the link
const para = nodes.find((n: any) => n.type === 'paragraph') as any
expect(para).toBeTruthy()
expect(Array.isArray(para.children)).toBe(true)
expect(para.children.length).toBe(1)
expect(para.children[0].type).toBe('link')
})
it('parses link with parentheses and emphasis and ListNode', () => {
const special = '- **[Link (Test 1)](https://simonhe.me/)**'
const nodes = parseMarkdownToStructure(special, md)
expect(nodes[0]).toMatchInlineSnapshot(`
{
"items": [
{
"children": [
{
"children": [
{
"children": [
{
"attrs": [
[
"href",
"https://simonhe.me/",
],
],
"children": [
{
"center": false,
"content": "Link (Test 1)",
"raw": "Link (Test 1)",
"type": "text",
},
],
"href": "https://simonhe.me/",
"loading": false,
"raw": "[Link (Test 1)](https://simonhe.me/)",
"text": "Link (Test 1)",
"title": null,
"type": "link",
},
],
"raw": "**Link (Test 1)**",
"type": "strong",
},
],
"raw": "**[Link (Test 1)](https://simonhe.me/)**",
"type": "paragraph",
},
],
"raw": "**[Link (Test 1)](https://simonhe.me/)**",
"type": "list_item",
},
],
"ordered": false,
"raw": "**[Link (Test 1)](https://simonhe.me/)**",
"start": undefined,
"type": "list",
}
`)
})
it('parses link with parentheses and emphasis', () => {
const special = '**[Link (Test 2)](https://simonhe.me/)**'
const nodes = parseMarkdownToStructure(special, md)
expect(nodes[0]).toMatchInlineSnapshot(`
{
"children": [
{
"children": [
{
"attrs": [
[
"href",
"https://simonhe.me/",
],
],
"children": [
{
"center": false,
"content": "Link (Test 2)",
"raw": "Link (Test 2)",
"type": "text",
},
],
"href": "https://simonhe.me/",
"loading": false,
"raw": "[Link (Test 2)](https://simonhe.me/)",
"text": "Link (Test 2)",
"title": null,
"type": "link",
},
],
"raw": "**Link (Test 2)**",
"type": "strong",
},
],
"raw": "**[Link (Test 2)](https://simonhe.me/)**",
"type": "paragraph",
}
`)
})
it('parses emphasized link with asterisk inside list item', () => {
const special = '- [*DR (Danmarks Radio)*](https://www.dr.dk/nyheder)'
const nodes = parseMarkdownToStructure(special, md)
// Collect link nodes
const links: any[] = []
const walk = (n: any) => {
if (!n)
return
if (n.type === 'link')
links.push(n)
if (Array.isArray(n.children))
n.children.forEach(walk)
if (Array.isArray(n.items))
n.items.forEach(walk)
}
nodes.forEach(walk)
expect(links.length).toBe(1)
expect(links[0].href).toBe('https://www.dr.dk/nyheder')
// The parser may keep emphasis markup in link.text or emit nested emphasis nodes.
// Accept either form by using the tolerant helper.
const found
= textIncludes(links[0], 'DR (Danmarks Radio)')
|| textIncludes(links[0], '*DR (Danmarks Radio)*')
|| textIncludes(links[0], '_DR (Danmarks Radio)_')
expect(found).toBe(true)
})
it('parses URL with inline trailing text (no newline)', () => {
const mdText = `第一个气泡(无换行):
http://127.0.0.1:8001/upload/20251118/4737bbe0-c42e-11f0-8471-37360564882d.docx 请在文档末尾增加一段示例内容`
const nodes = parseMarkdownToStructure(mdText, md)
expect(
textIncludes(
nodes,
'http://127.0.0.1:8001/upload/20251118/4737bbe0-c42e-11f0-8471-37360564882d.docx',
),
).toBe(true)
expect(textIncludes(nodes, '请在文档末尾增加一段示例内容')).toBe(true)
})
it('parses URL followed by newline and trailing text', () => {
const mdText = `第二个气泡(有换行):
http://127.0.0.1:8001/upload/20251118/4737bbe0-c42e-11f0-8471-37360564882d.docx
请在文档末尾增加一段示例内容`
const nodes = parseMarkdownToStructure(mdText, md)
expect(
textIncludes(
nodes,
'http://127.0.0.1:8001/upload/20251118/4737bbe0-c42e-11f0-8471-37360564882d.docx',
),
).toBe(true)
expect(textIncludes(nodes, '请在文档末尾增加一段示例内容')).toBe(true)
// Ensure a hard line break node is present for the newline between URL and trailing text
let foundHardBreak = false
const walk = (n: any) => {
if (!n)
return
if (n.type === 'hardbreak')
foundHardBreak = true
if (Array.isArray(n.children))
n.children.forEach(walk)
if (Array.isArray(n.items))
n.items.forEach(walk)
}
nodes.forEach(walk)
expect(foundHardBreak).toBe(true)
})
it('parses Danish list item with parenthetical URL', () => {
const special = '- **Kilde:**\u202FGrammatip – “Tekstretter” (https://www.grammatip.com/tekstretter) – her beskrives, hvordan værktøjet automatisk markerer fejl, giver fejltyper, leverer farvekodet feedback, giver lærerne et samlet overblik og muligheden for at tilpasse, hvilke fejl eleven skal se.'
const nodes = parseMarkdownToStructure(special, md)
// Find the paragraph node inside the list item
let para: any = null
const findPara = (n: any) => {
if (!n)
return
if (n.type === 'paragraph') {
para = n
return
}
if (Array.isArray(n.children))
n.children.forEach(findPara)
if (Array.isArray(n.items))
n.items.forEach(findPara)
}
nodes.forEach(findPara)
expect(para).toBeTruthy()
// Flatten paragraph descendants into ordered tokens, marking links specially
const tokens: string[] = []
const flatten = (n: any) => {
if (!n)
return
if (n.type === 'link') {
tokens.push(`<LINK:${n.href || n.text || ''}>`)
return
}
if (n.type === 'text') {
tokens.push(n.content || n.text || '')
return
}
if (Array.isArray(n.children)) {
for (const c of n.children) flatten(c)
}
}
flatten(para)
// There should be at least one link token with the expected href
const linkIndex = tokens.findIndex(t => t.startsWith('<LINK:'))
expect(linkIndex).toBeGreaterThanOrEqual(0)
const linkToken = tokens[linkIndex]
expect(linkToken).toContain('https://www.grammatip.com/tekstretter')
// Check text immediately before the link contains the title/context
const before = tokens[linkIndex - 1] || ''
const hasBefore = before.includes('Tekstretter') || before.includes('Grammatip') || before.includes('Kilde')
expect(hasBefore).toBe(true)
// Check text immediately after the link contains the trailing explanation
const afterTokens = tokens.slice(linkIndex + 1)
const afterFull = afterTokens.join('').trim()
const expectedAfter = ') – her beskrives, hvordan værktøjet automatisk markerer fejl, giver fejltyper, leverer farvekodet feedback, giver lærerne et samlet overblik og muligheden for at tilpasse, hvilke fejl eleven skal se.'
expect(afterFull).toBe(expectedAfter)
})
it('does not include ! suffix in linkify/autolink bare URLs', () => {
const cjkBang = '图表URL: https://www.baidu.com/img/flexible/logo/pc/result.png!文字文字'
const nodes1 = parseMarkdownToStructure(cjkBang, md)
const l1 = links(nodes1)
expect(l1.length).toBe(1)
expect(l1[0].href).toBe('https://www.baidu.com/img/flexible/logo/pc/result.png')
expect(l1[0].text).toBe('https://www.baidu.com/img/flexible/logo/pc/result.png')
expect(textIncludes(nodes1, '!文字文字')).toBe(true)
expect(textIncludes(l1[0], '!')).toBe(false)
const angle = '<https://www.baidu.com/img/flexible/logo/pc/result.png!文字文字>'
const nodes3 = parseMarkdownToStructure(angle, md)
const l3 = links(nodes3)
expect(l3.length).toBe(1)
expect(l3[0].href).toBe('https://www.baidu.com/img/flexible/logo/pc/result.png')
expect(l3[0].text).toBe('https://www.baidu.com/img/flexible/logo/pc/result.png')
expect(textIncludes(nodes3, '!文字文字')).toBe(true)
expect(textIncludes(l3[0], '!')).toBe(false)
})
it('keeps ASCII ! when it is part of the URL', () => {
const mdText = [
'https://example.com/a!b',
'https://example.com/?q=!',
'https://example.com/#!',
'<https://example.com/a!b>',
].join('\n')
const nodes = parseMarkdownToStructure(mdText, md)
const ls = links(nodes)
const hrefs = ls.map((l: any) => String(l.href || ''))
expect(hrefs).toContain('https://example.com/a!b')
expect(hrefs).toContain('https://example.com/?q=!')
expect(hrefs).toContain('https://example.com/#!')
})
it('does not merge punctuation ! into explicit markdown links', () => {
const nodes = parseMarkdownToStructure('[x](https://example.com/#)!', md)
const ls = links(nodes)
expect(ls.length).toBe(1)
expect(ls[0].href).toBe('https://example.com/#')
expect(ls[0].text).toBe('x')
expect(textIncludes(nodes, '!')).toBe(true)
})
it('respects customMarkdownIt validateLink: rejects javascript: and renders as plain text', () => {
const mdSafe = getMarkdown('validate-link-test')
mdSafe.set?.({ validateLink: (url: string) => !/^\s*javascript:/i.test(url.trim()) })
const nodes = parseMarkdownToStructure('[click me](javascript:alert(1))', mdSafe, { final: true })
const ls = links(nodes)
expect(ls.length).toBe(0)
expect(textIncludes(nodes, 'click me')).toBe(true)
// No link node may have javascript: href (paragraph.raw may still hold original source)
const anyBadHref = ls.some((l: any) => String(l.href || '').toLowerCase().includes('javascript:'))
expect(anyBadHref).toBe(false)
})
it('respects customMarkdownIt validateLink: allows https when validateLink returns true', () => {
const mdSafe = getMarkdown('validate-link-allow')
mdSafe.set?.({ validateLink: (url: string) => url.startsWith('https://') })
const nodes = parseMarkdownToStructure('[safe](https://example.com) [unsafe](http://example.com)', mdSafe, { final: true })
const ls = links(nodes)
expect(ls.length).toBe(1)
expect(ls[0].href).toBe('https://example.com')
expect(ls[0].text).toBe('safe')
expect(textIncludes(nodes, 'unsafe')).toBe(true)
})
})