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:
@@ -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')
|
||||
}
|
||||
Reference in New Issue
Block a user