1
0
forked from boos/BoosAPI

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,125 @@
import type { OnChanges } from '@angular/core'
import type { AngularRenderableNode, AngularRenderContext } from '../shared/node-helpers'
import { CommonModule } from '@angular/common'
import { ChangeDetectionStrategy, Component, forwardRef, Input } from '@angular/core'
import { NestedRendererComponent } from '../NestedRenderer/NestedRenderer.component'
import { NodeOutletComponent } from '../NodeOutlet/NodeOutlet.component'
import {
getNodeList,
getString,
isMediaOnlyParagraphNodes,
isWhitespaceTextNode,
normalizeMediaOnlyParagraphNodes,
splitParagraphChildren,
} from '../shared/node-helpers'
type ParagraphSegment
= | { kind: 'inline', nodes: AngularRenderableNode[], mediaOnly: boolean }
| { kind: 'block', node: AngularRenderableNode }
@Component({
selector: 'markstream-angular-paragraph-node',
standalone: true,
imports: [
CommonModule,
forwardRef(() => NestedRendererComponent),
forwardRef(() => NodeOutletComponent),
],
template: `
<ng-container *ngIf="segments.length > 0; else simpleParagraph">
<ng-container *ngFor="let segment of segments; let idx = index; trackBy: trackByIndex">
<p *ngIf="segment.kind === 'inline'; else blockSegment" dir="auto" class="paragraph-node">
<ng-container *ngIf="$any(segment).mediaOnly; else nestedInline">
<ng-container *ngFor="let child of $any(segment).nodes; let childIdx = index; trackBy: trackByChildIndex">
<ng-container *ngIf="isWhitespaceText(child); else inlineNode">{{ getTextContent(child) }}</ng-container>
<ng-template #inlineNode>
<markstream-angular-node-outlet
[node]="child"
[context]="context"
[indexKey]="childPrefix(idx, childIdx)"
/>
</ng-template>
</ng-container>
</ng-container>
<ng-template #nestedInline>
<markstream-angular-nested-renderer
[nodes]="$any(segment).nodes"
[context]="context"
[indexPrefix]="segmentPrefix(idx)"
/>
</ng-template>
</p>
<ng-template #blockSegment>
<markstream-angular-node-outlet
[node]="$any(segment).node"
[context]="context"
[indexKey]="segmentPrefix(idx)"
/>
</ng-template>
</ng-container>
</ng-container>
<ng-template #simpleParagraph>
<p dir="auto" class="paragraph-node">
<markstream-angular-nested-renderer
[nodes]="children"
[context]="context"
[indexPrefix]="indexKey || 'paragraph'"
/>
</p>
</ng-template>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ParagraphNodeComponent implements OnChanges {
@Input({ required: true }) node!: AngularRenderableNode
@Input() context?: AngularRenderContext
@Input() indexKey?: string
segments: ParagraphSegment[] = []
get children() {
return getNodeList((this.node as any)?.children)
}
ngOnChanges() {
this.segments = splitParagraphChildren(this.children).map((segment) => {
if (segment.kind === 'block')
return segment
const mediaOnly = isMediaOnlyParagraphNodes(segment.nodes)
return {
kind: 'inline',
nodes: mediaOnly ? normalizeMediaOnlyParagraphNodes(segment.nodes) : segment.nodes,
mediaOnly,
}
})
}
trackByIndex = (index: number) => {
return `${this.indexKey || 'paragraph'}-${index}`
}
trackByChildIndex = (index: number) => {
return index
}
segmentPrefix(index: number) {
return `${this.indexKey || 'paragraph'}-${index}`
}
childPrefix(segmentIndex: number, childIndex: number) {
return `${this.segmentPrefix(segmentIndex)}-${childIndex}`
}
isWhitespaceText(node: AngularRenderableNode) {
return isWhitespaceTextNode(node)
}
getTextContent(node: AngularRenderableNode) {
return getString((node as any)?.content)
}
}