viteboard 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/content-api-plugin.mjs +1 -1
- package/content/machine/guide.md +50 -22
- package/content/machine/llms.txt +65 -3
- package/content/machine/viteboard.json +26 -11
- package/dist/assets/index-2x-lv2vK.css +1 -0
- package/dist/assets/index-DUCBvK-t.js +95 -0
- package/dist/index.html +2 -2
- package/package.json +1 -1
- package/vite.config.ts +0 -286
- package/dist/assets/index-D2W76uu9.js +0 -96
- package/dist/assets/index-rMkUjGXP.css +0 -1
package/dist/index.html
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
<link rel="describedby" href="/llms.txt" />
|
|
7
7
|
<link rel="alternate" type="application/json" href="/.well-known/viteboard.json" />
|
|
8
8
|
<title>ViteBoard</title>
|
|
9
|
-
<script type="module" crossorigin src="/assets/index-
|
|
10
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
9
|
+
<script type="module" crossorigin src="/assets/index-DUCBvK-t.js"></script>
|
|
10
|
+
<link rel="stylesheet" crossorigin href="/assets/index-2x-lv2vK.css">
|
|
11
11
|
</head>
|
|
12
12
|
<body>
|
|
13
13
|
<div id="app"></div>
|
package/package.json
CHANGED
package/vite.config.ts
CHANGED
|
@@ -39,8 +39,6 @@ const MIME_EXT: Record<string, string> = {
|
|
|
39
39
|
"image/svg+xml": ".svg",
|
|
40
40
|
};
|
|
41
41
|
const MANAGED_ASSET_RE = /^assets\/[a-f0-9]{64}\.(png|jpe?g|webp|gif|svg)$/i;
|
|
42
|
-
const ACTOR_TTL_MS = 30000;
|
|
43
|
-
const LOCK_TTL_MS = 30000;
|
|
44
42
|
const ROOT_LINK_HEADER = '</llms.txt>; rel="describedby"; type="text/plain", </.well-known/viteboard.json>; rel="alternate"; type="application/json"';
|
|
45
43
|
|
|
46
44
|
const execFileAsync = promisify(execFile);
|
|
@@ -88,51 +86,12 @@ type TreeNode =
|
|
|
88
86
|
| { type: "file"; name: string; path: string }
|
|
89
87
|
| { type: "dir"; name: string; path: string; children: TreeNode[] };
|
|
90
88
|
|
|
91
|
-
type ActorState =
|
|
92
|
-
| "idle"
|
|
93
|
-
| "viewing-doc"
|
|
94
|
-
| "editing-doc"
|
|
95
|
-
| "viewing-board"
|
|
96
|
-
| "editing-board"
|
|
97
|
-
| "viewing-project"
|
|
98
|
-
| "writing";
|
|
99
|
-
|
|
100
|
-
type ActorSessionRecord = {
|
|
101
|
-
actorId: string;
|
|
102
|
-
actorType: "human" | "agent";
|
|
103
|
-
label: string;
|
|
104
|
-
route: string;
|
|
105
|
-
state: ActorState;
|
|
106
|
-
rootId?: string;
|
|
107
|
-
projectId?: string;
|
|
108
|
-
docPath?: string;
|
|
109
|
-
boardId?: string;
|
|
110
|
-
updatedAt: number;
|
|
111
|
-
expiresAt: number;
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
type LockRecord = {
|
|
115
|
-
lockId: string;
|
|
116
|
-
actorId: string;
|
|
117
|
-
actorType: "human" | "agent";
|
|
118
|
-
label: string;
|
|
119
|
-
targetType: "doc" | "board";
|
|
120
|
-
rootId: string;
|
|
121
|
-
path?: string;
|
|
122
|
-
boardId?: string;
|
|
123
|
-
acquiredAt: number;
|
|
124
|
-
expiresAt: number;
|
|
125
|
-
};
|
|
126
|
-
|
|
127
89
|
function contentApiPlugin(): Plugin {
|
|
128
90
|
const cwd = process.cwd();
|
|
129
91
|
const packageRoot = path.dirname(fileURLToPath(import.meta.url));
|
|
130
92
|
const workspaceDir = path.resolve(cwd, WORKSPACE_DIR);
|
|
131
93
|
const workspaceFile = path.join(workspaceDir, WORKSPACE_FILE);
|
|
132
94
|
const machineDir = path.join(packageRoot, "content", "machine");
|
|
133
|
-
const actorSessions = new Map<string, ActorSessionRecord>();
|
|
134
|
-
const locks = new Map<string, LockRecord>();
|
|
135
|
-
const sseClients = new Set<ServerResponse>();
|
|
136
95
|
|
|
137
96
|
function send(res: ServerResponse, status: number, body: unknown): void {
|
|
138
97
|
res.statusCode = status;
|
|
@@ -146,77 +105,6 @@ function contentApiPlugin(): Plugin {
|
|
|
146
105
|
res.end(body);
|
|
147
106
|
}
|
|
148
107
|
|
|
149
|
-
function sendSse(res: ServerResponse, event: string, body: unknown): void {
|
|
150
|
-
res.write(`event: ${event}\n`);
|
|
151
|
-
res.write(`data: ${JSON.stringify(body)}\n\n`);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function broadcast(event: string, body: unknown): void {
|
|
155
|
-
for (const client of sseClients) {
|
|
156
|
-
try {
|
|
157
|
-
sendSse(client, event, body);
|
|
158
|
-
} catch {
|
|
159
|
-
sseClients.delete(client);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
function actorFromHeaders(req: IncomingMessage): { actorId: string; actorType: "human" | "agent"; label: string } | null {
|
|
165
|
-
const actorId = String(req.headers["x-viteboard-actor-id"] ?? "").trim();
|
|
166
|
-
if (!actorId) return null;
|
|
167
|
-
const actorType = String(req.headers["x-viteboard-actor-type"] ?? "human") === "agent" ? "agent" : "human";
|
|
168
|
-
const label = String(req.headers["x-viteboard-actor-label"] ?? "").trim() || (actorType === "agent" ? "Agent" : "ViteBoard UI");
|
|
169
|
-
return { actorId, actorType, label };
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
function lockKey(target: { targetType: "doc" | "board"; rootId: string; path?: string; boardId?: string }): string {
|
|
173
|
-
const suffix = target.targetType === "doc" ? target.path ?? "" : target.boardId ?? "";
|
|
174
|
-
return `${target.targetType}:${target.rootId}:${suffix}`;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
function activeActors(): ActorSessionRecord[] {
|
|
178
|
-
return [...actorSessions.values()].sort((a, b) => a.label.localeCompare(b.label));
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
function activeLocks(): LockRecord[] {
|
|
182
|
-
return [...locks.values()].sort((a, b) => a.acquiredAt - b.acquiredAt);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
function broadcastPresence(): void {
|
|
186
|
-
broadcast("presence", { actors: activeActors(), locks: activeLocks() });
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
function broadcastLocks(): void {
|
|
190
|
-
broadcast("lock-changed", { locks: activeLocks() });
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
function pruneEphemeral(): void {
|
|
194
|
-
const now = Date.now();
|
|
195
|
-
let changedPresence = false;
|
|
196
|
-
let changedLocks = false;
|
|
197
|
-
for (const [actorId, actor] of actorSessions) {
|
|
198
|
-
if (actor.expiresAt <= now) {
|
|
199
|
-
actorSessions.delete(actorId);
|
|
200
|
-
changedPresence = true;
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
for (const [key, lock] of locks) {
|
|
204
|
-
if (lock.expiresAt <= now || !actorSessions.has(lock.actorId)) {
|
|
205
|
-
locks.delete(key);
|
|
206
|
-
changedLocks = true;
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
if (changedPresence) broadcastPresence();
|
|
210
|
-
else if (changedLocks) broadcastLocks();
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
function extendActorLocks(actorId: string): void {
|
|
214
|
-
const now = Date.now();
|
|
215
|
-
for (const lock of locks.values()) {
|
|
216
|
-
if (lock.actorId === actorId) lock.expiresAt = now + LOCK_TTL_MS;
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
|
|
220
108
|
async function readMachineAsset(fileName: string): Promise<string> {
|
|
221
109
|
return fs.readFile(path.join(machineDir, fileName), "utf-8");
|
|
222
110
|
}
|
|
@@ -225,8 +113,6 @@ function contentApiPlugin(): Plugin {
|
|
|
225
113
|
return createHash("sha1").update(content).digest("hex");
|
|
226
114
|
}
|
|
227
115
|
|
|
228
|
-
setInterval(pruneEphemeral, 10000).unref?.();
|
|
229
|
-
|
|
230
116
|
function readBody(req: IncomingMessage): Promise<string> {
|
|
231
117
|
return new Promise((resolve, reject) => {
|
|
232
118
|
let data = "";
|
|
@@ -755,169 +641,11 @@ function contentApiPlugin(): Plugin {
|
|
|
755
641
|
void (async () => {
|
|
756
642
|
try {
|
|
757
643
|
const method = (req.method ?? "GET").toUpperCase();
|
|
758
|
-
const actor = actorFromHeaders(req);
|
|
759
|
-
pruneEphemeral();
|
|
760
644
|
|
|
761
645
|
if (route === "/__api/workspace" && method === "GET") {
|
|
762
646
|
return send(res, 200, await toWorkspaceResponse(await getWorkspace()));
|
|
763
647
|
}
|
|
764
648
|
|
|
765
|
-
if (route === "/__api/events" && method === "GET") {
|
|
766
|
-
res.statusCode = 200;
|
|
767
|
-
res.setHeader("Content-Type", "text/event-stream; charset=utf-8");
|
|
768
|
-
res.setHeader("Cache-Control", "no-cache, no-transform");
|
|
769
|
-
res.setHeader("Connection", "keep-alive");
|
|
770
|
-
res.write(": connected\n\n");
|
|
771
|
-
sseClients.add(res);
|
|
772
|
-
sendSse(res, "hello", { actors: activeActors(), locks: activeLocks() });
|
|
773
|
-
req.on("close", () => {
|
|
774
|
-
sseClients.delete(res);
|
|
775
|
-
});
|
|
776
|
-
return;
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
if (route === "/__api/actors/session" && method === "GET") {
|
|
780
|
-
return send(res, 200, { actors: activeActors() });
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
if (route === "/__api/actors/session" && method === "POST") {
|
|
784
|
-
const body = JSON.parse(await readBody(req));
|
|
785
|
-
const actorId = String(body.actorId ?? actor?.actorId ?? "").trim();
|
|
786
|
-
if (!actorId) return send(res, 400, { error: "actorId is required" });
|
|
787
|
-
const actorType = String(body.actorType ?? actor?.actorType ?? "human") === "agent" ? "agent" : "human";
|
|
788
|
-
const label = String(body.label ?? actor?.label ?? "").trim() || (actorType === "agent" ? "Agent" : "ViteBoard UI");
|
|
789
|
-
const now = Date.now();
|
|
790
|
-
actorSessions.set(actorId, {
|
|
791
|
-
actorId,
|
|
792
|
-
actorType,
|
|
793
|
-
label,
|
|
794
|
-
route: String(body.route ?? "#/"),
|
|
795
|
-
state: String(body.state ?? "idle") as ActorState,
|
|
796
|
-
rootId: typeof body.rootId === "string" ? body.rootId : undefined,
|
|
797
|
-
projectId: typeof body.projectId === "string" ? body.projectId : undefined,
|
|
798
|
-
docPath: typeof body.docPath === "string" ? body.docPath : undefined,
|
|
799
|
-
boardId: typeof body.boardId === "string" ? body.boardId : undefined,
|
|
800
|
-
selectedElementIds: Array.isArray(body.selectedElementIds)
|
|
801
|
-
? body.selectedElementIds.filter((item: unknown): item is string => typeof item === "string")
|
|
802
|
-
: undefined,
|
|
803
|
-
updatedAt: now,
|
|
804
|
-
expiresAt: now + ACTOR_TTL_MS,
|
|
805
|
-
});
|
|
806
|
-
extendActorLocks(actorId);
|
|
807
|
-
broadcastPresence();
|
|
808
|
-
return send(res, 200, { ok: true });
|
|
809
|
-
}
|
|
810
|
-
|
|
811
|
-
if (route === "/__api/actors/heartbeat" && method === "POST") {
|
|
812
|
-
const body = JSON.parse(await readBody(req));
|
|
813
|
-
const actorId = String(body.actorId ?? actor?.actorId ?? "").trim();
|
|
814
|
-
if (!actorId) return send(res, 400, { error: "actorId is required" });
|
|
815
|
-
const current = actorSessions.get(actorId);
|
|
816
|
-
if (!current) return send(res, 404, { error: "Actor session not found" });
|
|
817
|
-
const now = Date.now();
|
|
818
|
-
const next: ActorSessionRecord = {
|
|
819
|
-
...current,
|
|
820
|
-
route: typeof body.route === "string" ? body.route : current.route,
|
|
821
|
-
state: typeof body.state === "string" ? (body.state as ActorState) : current.state,
|
|
822
|
-
rootId: typeof body.rootId === "string" ? body.rootId : current.rootId,
|
|
823
|
-
projectId: typeof body.projectId === "string" ? body.projectId : current.projectId,
|
|
824
|
-
docPath: typeof body.docPath === "string" ? body.docPath : current.docPath,
|
|
825
|
-
boardId: typeof body.boardId === "string" ? body.boardId : current.boardId,
|
|
826
|
-
selectedElementIds: Array.isArray(body.selectedElementIds)
|
|
827
|
-
? body.selectedElementIds.filter((item: unknown): item is string => typeof item === "string")
|
|
828
|
-
: current.selectedElementIds,
|
|
829
|
-
updatedAt: now,
|
|
830
|
-
expiresAt: now + ACTOR_TTL_MS,
|
|
831
|
-
};
|
|
832
|
-
actorSessions.set(actorId, next);
|
|
833
|
-
extendActorLocks(actorId);
|
|
834
|
-
broadcastPresence();
|
|
835
|
-
return send(res, 200, { ok: true });
|
|
836
|
-
}
|
|
837
|
-
|
|
838
|
-
if (route === "/__api/actors/session" && method === "DELETE") {
|
|
839
|
-
const actorId = parsed.searchParams.get("actorId") ?? actor?.actorId ?? "";
|
|
840
|
-
if (!actorId) return send(res, 400, { error: "actorId is required" });
|
|
841
|
-
actorSessions.delete(actorId);
|
|
842
|
-
let changedLocks = false;
|
|
843
|
-
for (const [key, lock] of locks) {
|
|
844
|
-
if (lock.actorId === actorId) {
|
|
845
|
-
locks.delete(key);
|
|
846
|
-
changedLocks = true;
|
|
847
|
-
}
|
|
848
|
-
}
|
|
849
|
-
if (changedLocks) broadcastLocks();
|
|
850
|
-
broadcastPresence();
|
|
851
|
-
return send(res, 200, { ok: true });
|
|
852
|
-
}
|
|
853
|
-
|
|
854
|
-
if (route === "/__api/locks" && method === "GET") {
|
|
855
|
-
return send(res, 200, { locks: activeLocks() });
|
|
856
|
-
}
|
|
857
|
-
|
|
858
|
-
if (route === "/__api/locks/acquire" && method === "POST") {
|
|
859
|
-
const body = JSON.parse(await readBody(req));
|
|
860
|
-
if (!actor?.actorId) return send(res, 400, { error: "actor identity is required" });
|
|
861
|
-
const targetType = body.targetType === "board" ? "board" : body.targetType === "doc" ? "doc" : null;
|
|
862
|
-
const rootId = String(body.rootId ?? "").trim();
|
|
863
|
-
if (!targetType || !rootId) return send(res, 400, { error: "targetType and rootId are required" });
|
|
864
|
-
const resolved = await resolveRoot(rootId);
|
|
865
|
-
if (!resolved) return send(res, 404, { error: "Docs root not found" });
|
|
866
|
-
const targetPath = targetType === "doc" ? String(body.path ?? "").trim() : undefined;
|
|
867
|
-
const targetBoardId = targetType === "board" ? String(body.boardId ?? "").trim() : undefined;
|
|
868
|
-
const targetAbs = targetType === "doc"
|
|
869
|
-
? safeResolve(resolved.root.absPath, targetPath ?? "")
|
|
870
|
-
: safeResolve(resolved.root.absPath, `${targetBoardId}.board.json`);
|
|
871
|
-
if (!targetAbs) {
|
|
872
|
-
return send(res, 400, { error: targetType === "doc" ? "Invalid path" : "Invalid boardId" });
|
|
873
|
-
}
|
|
874
|
-
const key = lockKey({ targetType, rootId, path: targetPath, boardId: targetBoardId });
|
|
875
|
-
const now = Date.now();
|
|
876
|
-
const existing = locks.get(key);
|
|
877
|
-
if (existing && existing.actorId !== actor.actorId && existing.expiresAt > now) {
|
|
878
|
-
return send(res, 409, { error: `${existing.label} is editing this ${targetType}.`, lock: existing });
|
|
879
|
-
}
|
|
880
|
-
const nextLock: LockRecord = existing && existing.actorId === actor.actorId
|
|
881
|
-
? { ...existing, expiresAt: now + LOCK_TTL_MS }
|
|
882
|
-
: {
|
|
883
|
-
lockId: `lock-${hashPath(`${key}:${now}`)}`,
|
|
884
|
-
actorId: actor.actorId,
|
|
885
|
-
actorType: actor.actorType,
|
|
886
|
-
label: actor.label,
|
|
887
|
-
targetType,
|
|
888
|
-
rootId,
|
|
889
|
-
path: targetPath,
|
|
890
|
-
boardId: targetBoardId,
|
|
891
|
-
acquiredAt: now,
|
|
892
|
-
expiresAt: now + LOCK_TTL_MS,
|
|
893
|
-
};
|
|
894
|
-
locks.set(key, nextLock);
|
|
895
|
-
broadcastLocks();
|
|
896
|
-
broadcastPresence();
|
|
897
|
-
return send(res, 200, { ok: true, lock: nextLock });
|
|
898
|
-
}
|
|
899
|
-
|
|
900
|
-
if (route === "/__api/locks/release" && method === "POST") {
|
|
901
|
-
const body = JSON.parse(await readBody(req));
|
|
902
|
-
if (!actor?.actorId) return send(res, 400, { error: "actor identity is required" });
|
|
903
|
-
const targetType = body.targetType === "board" ? "board" : body.targetType === "doc" ? "doc" : null;
|
|
904
|
-
const rootId = String(body.rootId ?? "").trim();
|
|
905
|
-
if (!targetType || !rootId) return send(res, 400, { error: "targetType and rootId are required" });
|
|
906
|
-
const key = lockKey({
|
|
907
|
-
targetType,
|
|
908
|
-
rootId,
|
|
909
|
-
path: targetType === "doc" ? String(body.path ?? "").trim() : undefined,
|
|
910
|
-
boardId: targetType === "board" ? String(body.boardId ?? "").trim() : undefined,
|
|
911
|
-
});
|
|
912
|
-
const existing = locks.get(key);
|
|
913
|
-
if (!existing) return send(res, 200, { ok: true });
|
|
914
|
-
if (existing.actorId !== actor.actorId) return send(res, 403, { error: "Lock owned by another actor" });
|
|
915
|
-
locks.delete(key);
|
|
916
|
-
broadcastLocks();
|
|
917
|
-
broadcastPresence();
|
|
918
|
-
return send(res, 200, { ok: true });
|
|
919
|
-
}
|
|
920
|
-
|
|
921
649
|
if (route === "/__api/workspace/project" && method === "POST") {
|
|
922
650
|
const { rootPath, label } = JSON.parse(await readBody(req));
|
|
923
651
|
const absRoot = await canonicalizeProjectRoot(String(rootPath ?? "").trim());
|
|
@@ -938,7 +666,6 @@ function contentApiPlugin(): Plugin {
|
|
|
938
666
|
if (existingIndex === -1) manifest.projects.push(project);
|
|
939
667
|
else manifest.projects.splice(existingIndex, 1, project);
|
|
940
668
|
await saveManifest(manifest);
|
|
941
|
-
broadcast("workspace-changed", { action: "project-added", projectId: project.id, actorId: actor?.actorId ?? null });
|
|
942
669
|
return send(res, 200, await toWorkspaceResponse(await getWorkspace()));
|
|
943
670
|
}
|
|
944
671
|
|
|
@@ -961,7 +688,6 @@ function contentApiPlugin(): Plugin {
|
|
|
961
688
|
if (existingIndex === -1) manifest.projects.push(project);
|
|
962
689
|
else manifest.projects.splice(existingIndex, 1, project);
|
|
963
690
|
await saveManifest(manifest);
|
|
964
|
-
broadcast("workspace-changed", { action: "project-added", projectId: project.id, actorId: actor?.actorId ?? null });
|
|
965
691
|
return send(res, 200, { cancelled: false, workspace: await toWorkspaceResponse(await getWorkspace()), addedProjectId: project.id });
|
|
966
692
|
}
|
|
967
693
|
|
|
@@ -977,7 +703,6 @@ function contentApiPlugin(): Plugin {
|
|
|
977
703
|
const root = refreshedProject?.docsRoots.find((item) => item.absPath === docsPath)
|
|
978
704
|
?? refreshedProject?.docsRoots[0];
|
|
979
705
|
if (!root) return send(res, 500, { error: "Could not create docs root" });
|
|
980
|
-
broadcast("workspace-changed", { action: "docs-root-created", projectId: project.id, rootId: root.id, actorId: actor?.actorId ?? null });
|
|
981
706
|
return send(res, 200, {
|
|
982
707
|
root: {
|
|
983
708
|
id: root.id,
|
|
@@ -1002,7 +727,6 @@ function contentApiPlugin(): Plugin {
|
|
|
1002
727
|
const refreshedProject = workspace.find((item) => item.id === project.id);
|
|
1003
728
|
const root = refreshedProject?.docsRoots.find((item) => item.absPath === docsPath);
|
|
1004
729
|
if (!root) return send(res, 500, { error: "Could not create docs area" });
|
|
1005
|
-
broadcast("workspace-changed", { action: "docs-area-created", projectId: project.id, rootId: root.id, actorId: actor?.actorId ?? null });
|
|
1006
730
|
return send(res, 200, {
|
|
1007
731
|
root: {
|
|
1008
732
|
id: root.id,
|
|
@@ -1022,7 +746,6 @@ function contentApiPlugin(): Plugin {
|
|
|
1022
746
|
if (!project) return send(res, 404, { error: "Project not found" });
|
|
1023
747
|
const docsPath = path.join(project.rootPath, String(parentPath ?? ""), "docs");
|
|
1024
748
|
await fs.mkdir(docsPath, { recursive: true });
|
|
1025
|
-
broadcast("workspace-changed", { action: "docs-folder-created", projectId: project.id, actorId: actor?.actorId ?? null });
|
|
1026
749
|
return send(res, 200, { ok: true, workspace: await toWorkspaceResponse(await getWorkspace()) });
|
|
1027
750
|
}
|
|
1028
751
|
|
|
@@ -1031,7 +754,6 @@ function contentApiPlugin(): Plugin {
|
|
|
1031
754
|
const manifest = await loadManifest();
|
|
1032
755
|
manifest.projects = manifest.projects.filter((item) => item.id !== id);
|
|
1033
756
|
await saveManifest(manifest);
|
|
1034
|
-
broadcast("workspace-changed", { action: "project-removed", projectId: id, actorId: actor?.actorId ?? null });
|
|
1035
757
|
return send(res, 200, await toWorkspaceResponse(await getWorkspace()));
|
|
1036
758
|
}
|
|
1037
759
|
|
|
@@ -1091,7 +813,6 @@ function contentApiPlugin(): Plugin {
|
|
|
1091
813
|
const nextContent = String(content ?? "");
|
|
1092
814
|
await fs.writeFile(abs, nextContent, "utf-8");
|
|
1093
815
|
const revision = revisionForText(nextContent);
|
|
1094
|
-
broadcast("file-changed", { rootId: resolved.root.id, path: String(rel), action: "write", actorId: actor?.actorId ?? null });
|
|
1095
816
|
return send(res, 200, { ok: true, path: rel, revision });
|
|
1096
817
|
}
|
|
1097
818
|
|
|
@@ -1102,7 +823,6 @@ function contentApiPlugin(): Plugin {
|
|
|
1102
823
|
const abs = safeResolve(resolved.root.absPath, String(rel));
|
|
1103
824
|
if (!abs) return send(res, 400, { error: "Invalid path" });
|
|
1104
825
|
await fs.mkdir(abs, { recursive: true });
|
|
1105
|
-
broadcast("workspace-changed", { action: "directory-created", rootId: resolved.root.id, path: String(rel), actorId: actor?.actorId ?? null });
|
|
1106
826
|
return send(res, 200, { ok: true, path: rel });
|
|
1107
827
|
}
|
|
1108
828
|
|
|
@@ -1118,7 +838,6 @@ function contentApiPlugin(): Plugin {
|
|
|
1118
838
|
} catch (err) {
|
|
1119
839
|
if ((err as NodeJS.ErrnoException).code !== "ENOENT") throw err;
|
|
1120
840
|
}
|
|
1121
|
-
broadcast("file-changed", { rootId: resolved.root.id, path: rel, action: "delete", actorId: actor?.actorId ?? null });
|
|
1122
841
|
return send(res, 200, { ok: true });
|
|
1123
842
|
}
|
|
1124
843
|
|
|
@@ -1139,7 +858,6 @@ function contentApiPlugin(): Plugin {
|
|
|
1139
858
|
}
|
|
1140
859
|
throw err;
|
|
1141
860
|
}
|
|
1142
|
-
broadcast("file-changed", { rootId: resolved.root.id, path: String(newPath), oldPath: String(oldPath), action: "rename", actorId: actor?.actorId ?? null });
|
|
1143
861
|
return send(res, 200, { ok: true, path: newPath });
|
|
1144
862
|
}
|
|
1145
863
|
|
|
@@ -1182,7 +900,6 @@ function contentApiPlugin(): Plugin {
|
|
|
1182
900
|
const nextContent = JSON.stringify(doc, null, 2) + "\n";
|
|
1183
901
|
await fs.writeFile(abs, nextContent, "utf-8");
|
|
1184
902
|
const revision = revisionForText(nextContent);
|
|
1185
|
-
broadcast("board-changed", { rootId: resolved.root.id, boardId: String(id), action: "write", actorId: actor?.actorId ?? null });
|
|
1186
903
|
return send(res, 200, { ok: true, id, revision });
|
|
1187
904
|
}
|
|
1188
905
|
|
|
@@ -1198,7 +915,6 @@ function contentApiPlugin(): Plugin {
|
|
|
1198
915
|
} catch (err) {
|
|
1199
916
|
if ((err as NodeJS.ErrnoException).code !== "ENOENT") throw err;
|
|
1200
917
|
}
|
|
1201
|
-
broadcast("board-changed", { rootId: resolved.root.id, boardId: id, action: "delete", actorId: actor?.actorId ?? null });
|
|
1202
918
|
return send(res, 200, { ok: true, id });
|
|
1203
919
|
}
|
|
1204
920
|
|
|
@@ -1236,7 +952,6 @@ function contentApiPlugin(): Plugin {
|
|
|
1236
952
|
} catch {
|
|
1237
953
|
await fs.writeFile(abs, bytes);
|
|
1238
954
|
}
|
|
1239
|
-
broadcast("asset-changed", { rootId: resolved.root.id, path: rel, action: "write", actorId: actor?.actorId ?? null });
|
|
1240
955
|
return send(res, 200, { ok: true, path: rel });
|
|
1241
956
|
}
|
|
1242
957
|
|
|
@@ -1248,7 +963,6 @@ function contentApiPlugin(): Plugin {
|
|
|
1248
963
|
resolved.root.absPath,
|
|
1249
964
|
Array.isArray(preservePaths) ? preservePaths.filter((item): item is string => typeof item === "string") : []
|
|
1250
965
|
);
|
|
1251
|
-
if (removed > 0) broadcast("asset-changed", { rootId: resolved.root.id, action: "cleanup", removed, actorId: actor?.actorId ?? null });
|
|
1252
966
|
return send(res, 200, { ok: true, removed });
|
|
1253
967
|
}
|
|
1254
968
|
|