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,41 @@
import { describe, expect, it } from 'vitest'
import { collect, hasNode, paragraphHasCheckbox, textIncludes } from './midstate-utils'
describe('midstate-utils', () => {
it('collect finds nested nodes of a given type', () => {
const nodes = [
{ type: 'paragraph', children: [{ type: 'text', content: 'a' }] },
{ type: 'list', items: [{ type: 'list_item', children: [{ type: 'paragraph', children: [{ type: 'text', content: 'b' }] }] }] },
{ type: 'custom', children: [{ type: 'emphasis', children: [{ type: 'text', content: 'x' }] }] },
]
const paras = collect(nodes as any, 'paragraph')
expect(paras.length).toBeGreaterThanOrEqual(1)
const emph = collect(nodes as any, 'emphasis')
expect(emph.length).toBe(1)
})
it('hasNode returns true when node exists', () => {
const nodes = [{ type: 'a' }, { type: 'b' }, { type: 'c' }]
expect(hasNode(nodes as any, 'b')).toBe(true)
expect(hasNode(nodes as any, 'z')).toBe(false)
})
it('textIncludes works on simple structures', () => {
const nodes = [{ type: 'paragraph', children: [{ type: 'text', content: 'hello world' }] }]
expect(textIncludes(nodes as any, 'hello')).toBe(true)
expect(textIncludes(nodes as any, 'bye')).toBe(false)
})
it('textIncludes supports RegExp matching', () => {
const nodes = [{ type: 'paragraph', children: [{ type: 'text', content: 'Hello WORLD' }] }]
expect(textIncludes(nodes as any, /WORLD$/i)).toBe(true)
expect(textIncludes(nodes as any, /nope/)).toBe(false)
})
it('paragraphHasCheckbox detects checkbox children', () => {
const p = { type: 'paragraph', children: [{ type: 'checkbox', checked: true }, { type: 'text', content: 'hi' }] }
expect(paragraphHasCheckbox([p] as any)).toBe(true)
const p2 = { type: 'paragraph', children: [{ type: 'text', content: 'nope' }] }
expect(paragraphHasCheckbox([p2] as any)).toBe(false)
})
})
@@ -0,0 +1,164 @@
export function collect(nodes: any[], type: string) {
const out: any[] = []
const walk = (n: any) => {
if (!n)
return
if (Array.isArray(n))
return n.forEach(walk)
if (n.type === type)
out.push(n)
if (Array.isArray(n.children))
n.children.forEach(walk)
if (Array.isArray(n.items))
n.items.forEach(walk)
// table nodes store content under header/rows/cells rather than children
if (n.type === 'table') {
if (n.header)
walk(n.header)
if (Array.isArray(n.rows))
n.rows.forEach(walk)
}
else if (n.type === 'table_row') {
if (Array.isArray(n.cells))
n.cells.forEach(walk)
}
}
;(nodes || []).forEach(walk)
return out
}
export function hasNode(nodes: any[], type: string) {
return collect(nodes, type).length > 0
}
export function fenceOrParagraph(nodes: any[]) {
const fence = hasNode(nodes, 'code_block') || hasNode(nodes, 'fence')
const para = hasNode(nodes, 'paragraph')
return fence || para
}
export function isParagraphOrArray(nodes: any[]) {
return Array.isArray(nodes) || hasNode(nodes, 'paragraph')
}
export function links(nodes: any[]) {
return collect(nodes, 'link')
}
export function hasLoadingLink(nodes: any[]) {
const ls = links(nodes) || []
return ls.some((l: any) => !!l.loading)
}
export function textIncludes(nodes: any[], s: string | RegExp) {
if (!s)
return false
const seen = new Set<any>()
const walk = (n: any): boolean => {
if (!n)
return false
// allow passing a raw string directly
if (typeof n === 'string') {
if (s instanceof RegExp)
return s.test(n)
return n.includes(s as string)
}
if (seen.has(n))
return false
seen.add(n)
if (Array.isArray(n)) {
for (const it of n) {
if (walk(it))
return true
}
return false
}
// check common textual fields
const checkFields = ['content', 'text', 'raw', 'href', 'src', 'title', 'name', 'markup']
for (const f of checkFields) {
if (typeof n[f] === 'string') {
if (s instanceof RegExp) {
if (s.test(n[f]))
return true
}
else if (n[f].includes(s)) {
return true
}
}
}
// also inspect attributes if present (array of [key,value])
if (Array.isArray(n.attrs)) {
for (const a of n.attrs) {
if (Array.isArray(a) && a.length >= 2) {
const v = String(a[1])
if (s instanceof RegExp) {
if (s.test(v))
return true
}
else if (v.includes(s)) {
return true
}
}
}
}
// recurse into children/items
if (Array.isArray(n.children)) {
for (const c of n.children) {
if (walk(c))
return true
}
}
if (Array.isArray(n.items)) {
for (const it of n.items) {
if (walk(it))
return true
}
}
// table content traversal
if (n.type === 'table') {
if (n.header && walk(n.header))
return true
if (Array.isArray(n.rows)) {
for (const r of n.rows) {
if (walk(r))
return true
}
}
}
else if (n.type === 'table_row') {
if (Array.isArray(n.cells)) {
for (const c of n.cells) {
if (walk(c))
return true
}
}
}
return false
}
try {
return walk(nodes as any)
}
catch {
return false
}
}
export function hasParagraph(nodes: any[]) {
return hasNode(nodes, 'paragraph')
}
export function hasEmoji(nodes: any[]) {
return collect(nodes, 'emoji').length > 0
}
export function paragraphFirst(nodes: any[]) {
const ps = collect(nodes, 'paragraph')
return ps.length > 0 ? ps[0] : null
}
export function paragraphHasCheckbox(nodes: any[]) {
const p = paragraphFirst(nodes)
if (!p || !Array.isArray(p.children))
return false
return p.children.some((c: any) => c.type === 'checkbox' || c.type === 'checkbox_input')
}