@guowenzhang/dsh-whiteboard 0.2.0
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/LICENSE +202 -0
- package/NOTICE +32 -0
- package/README.md +29 -0
- package/README.zh.md +29 -0
- package/cordis.patch.yml +3 -0
- package/files/README.md +1 -0
- package/lib/client.js +31034 -0
- package/lib/index.mjs +241 -0
- package/package.json +99 -0
package/lib/index.mjs
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { fileURLToPath } from "node:url";
|
|
2
|
+
import { lstat, mkdir, readFile, readdir, stat, unlink, writeFile } from "node:fs/promises";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
4
|
+
import { setSandboxMode } from "@deepseek-ai/dsh-sandbox-policy";
|
|
5
|
+
import { Remote, TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
|
|
6
|
+
//#region src/board-name.ts
|
|
7
|
+
/** Validate a single filename stem. The extension is managed by the plugin. */
|
|
8
|
+
function normalizeBoardName(value) {
|
|
9
|
+
if (typeof value !== "string") throw new Error("WHITEBOARD_INVALID_NAME");
|
|
10
|
+
const name = value.trim().replace(/\.drawio$/iu, "");
|
|
11
|
+
if (!name || name.length > 100 || /[<>:"/\\|?*\x00-\x1f]/u.test(name) || /[. ]$/u.test(name) || /^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\..*)?$/iu.test(name)) throw new Error("WHITEBOARD_INVALID_NAME");
|
|
12
|
+
return name;
|
|
13
|
+
}
|
|
14
|
+
//#endregion
|
|
15
|
+
//#region src/boards.ts
|
|
16
|
+
const BLANK_BOARD = "<mxGraphModel dx=\"900\" dy=\"600\" grid=\"1\" gridSize=\"10\" guides=\"1\" tooltips=\"1\" connect=\"1\" arrows=\"1\" fold=\"1\" page=\"1\" pageScale=\"1\" pageWidth=\"850\" pageHeight=\"1100\"><root><mxCell id=\"0\"/><mxCell id=\"1\" parent=\"0\"/></root></mxGraphModel>";
|
|
17
|
+
function titleOf(name) {
|
|
18
|
+
return name.slice(0, -7);
|
|
19
|
+
}
|
|
20
|
+
function boardId(value) {
|
|
21
|
+
if (typeof value !== "string" || !value.toLowerCase().endsWith(".drawio") || value.length <= 7 || value.length > 255 || /[<>:"/\\|?*\x00-\x1f]/u.test(value)) throw new Error("WHITEBOARD_INVALID_ID");
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
24
|
+
var BoardStore = class {
|
|
25
|
+
directory;
|
|
26
|
+
constructor(directory) {
|
|
27
|
+
this.directory = directory;
|
|
28
|
+
}
|
|
29
|
+
async list() {
|
|
30
|
+
await mkdir(this.directory, { recursive: true });
|
|
31
|
+
const files = (await readdir(this.directory, { withFileTypes: true })).filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith(".drawio"));
|
|
32
|
+
return (await Promise.all(files.map(async (entry) => {
|
|
33
|
+
const info = await stat(join(this.directory, entry.name));
|
|
34
|
+
let title = titleOf(entry.name);
|
|
35
|
+
if (/^[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}\.drawio$/iu.test(entry.name)) try {
|
|
36
|
+
const metadata = JSON.parse(await readFile(join(this.directory, `${title}.json`), "utf8"));
|
|
37
|
+
if (typeof metadata === "object" && metadata !== null && typeof metadata.title === "string") title = metadata.title;
|
|
38
|
+
} catch {}
|
|
39
|
+
return {
|
|
40
|
+
id: entry.name,
|
|
41
|
+
title,
|
|
42
|
+
path: join(this.directory, entry.name),
|
|
43
|
+
directory: this.directory,
|
|
44
|
+
updatedAt: info.mtimeMs
|
|
45
|
+
};
|
|
46
|
+
}))).sort((a, b) => b.updatedAt - a.updatedAt || a.id.localeCompare(b.id));
|
|
47
|
+
}
|
|
48
|
+
async create(value) {
|
|
49
|
+
await mkdir(this.directory, { recursive: true });
|
|
50
|
+
const name = `${normalizeBoardName(value)}.drawio`;
|
|
51
|
+
const path = join(this.directory, name);
|
|
52
|
+
try {
|
|
53
|
+
await writeFile(path, BLANK_BOARD, { flag: "wx" });
|
|
54
|
+
} catch (error) {
|
|
55
|
+
if (error.code === "EEXIST") throw new Error("WHITEBOARD_NAME_EXISTS");
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
const info = await stat(path);
|
|
59
|
+
return {
|
|
60
|
+
id: name,
|
|
61
|
+
title: titleOf(name),
|
|
62
|
+
path,
|
|
63
|
+
directory: this.directory,
|
|
64
|
+
updatedAt: info.mtimeMs
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
async preview(value) {
|
|
68
|
+
const path = join(this.directory, boardId(value));
|
|
69
|
+
const info = await lstat(path);
|
|
70
|
+
if (!info.isFile()) throw new Error("WHITEBOARD_INVALID_ID");
|
|
71
|
+
if (info.size > 4194304) return null;
|
|
72
|
+
return readFile(path, "utf8");
|
|
73
|
+
}
|
|
74
|
+
async delete(value, expectedUpdatedAt) {
|
|
75
|
+
const id = boardId(value);
|
|
76
|
+
const path = join(this.directory, id);
|
|
77
|
+
const info = await lstat(path);
|
|
78
|
+
if (!info.isFile()) throw new Error("WHITEBOARD_INVALID_ID");
|
|
79
|
+
if (typeof expectedUpdatedAt !== "number" || info.mtimeMs !== expectedUpdatedAt) throw new Error("WHITEBOARD_MODIFIED");
|
|
80
|
+
await unlink(path);
|
|
81
|
+
if (/^[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}\.drawio$/iu.test(id)) try {
|
|
82
|
+
await unlink(join(this.directory, `${titleOf(id)}.json`));
|
|
83
|
+
} catch (error) {
|
|
84
|
+
if (error.code !== "ENOENT") throw error;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
//#endregion
|
|
89
|
+
//#region src/board-service.ts
|
|
90
|
+
var __runInitializers = function(thisArg, initializers, value) {
|
|
91
|
+
var useValue = arguments.length > 2;
|
|
92
|
+
for (var i = 0; i < initializers.length; i++) value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
|
|
93
|
+
return useValue ? value : void 0;
|
|
94
|
+
};
|
|
95
|
+
var __esDecorate = function(ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
|
|
96
|
+
function accept(f) {
|
|
97
|
+
if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected");
|
|
98
|
+
return f;
|
|
99
|
+
}
|
|
100
|
+
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
|
|
101
|
+
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
|
|
102
|
+
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
|
|
103
|
+
var _, done = false;
|
|
104
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
105
|
+
var context = {};
|
|
106
|
+
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
|
|
107
|
+
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
|
|
108
|
+
context.addInitializer = function(f) {
|
|
109
|
+
if (done) throw new TypeError("Cannot add initializers after decoration has completed");
|
|
110
|
+
extraInitializers.push(accept(f || null));
|
|
111
|
+
};
|
|
112
|
+
var result = (0, decorators[i])(kind === "accessor" ? {
|
|
113
|
+
get: descriptor.get,
|
|
114
|
+
set: descriptor.set
|
|
115
|
+
} : descriptor[key], context);
|
|
116
|
+
if (kind === "accessor") {
|
|
117
|
+
if (result === void 0) continue;
|
|
118
|
+
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
|
|
119
|
+
if (_ = accept(result.get)) descriptor.get = _;
|
|
120
|
+
if (_ = accept(result.set)) descriptor.set = _;
|
|
121
|
+
if (_ = accept(result.init)) initializers.unshift(_);
|
|
122
|
+
} else if (_ = accept(result)) {
|
|
123
|
+
if (kind === "field") initializers.unshift(_);
|
|
124
|
+
else descriptor[key] = _;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
128
|
+
done = true;
|
|
129
|
+
};
|
|
130
|
+
let BoardService = (() => {
|
|
131
|
+
let _classSuper = TypertRemoteService;
|
|
132
|
+
let _instanceExtraInitializers = [];
|
|
133
|
+
let _listBoards_decorators;
|
|
134
|
+
let _createBoard_decorators;
|
|
135
|
+
let _previewBoard_decorators;
|
|
136
|
+
let _deleteBoard_decorators;
|
|
137
|
+
let _grantBoardSession_decorators;
|
|
138
|
+
return class BoardService extends _classSuper {
|
|
139
|
+
static {
|
|
140
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
141
|
+
_listBoards_decorators = [Remote("listBoards")];
|
|
142
|
+
_createBoard_decorators = [Remote("createBoard")];
|
|
143
|
+
_previewBoard_decorators = [Remote("previewBoard")];
|
|
144
|
+
_deleteBoard_decorators = [Remote("deleteBoard")];
|
|
145
|
+
_grantBoardSession_decorators = [Remote("grantBoardSession")];
|
|
146
|
+
__esDecorate(this, null, _listBoards_decorators, {
|
|
147
|
+
kind: "method",
|
|
148
|
+
name: "listBoards",
|
|
149
|
+
static: false,
|
|
150
|
+
private: false,
|
|
151
|
+
access: {
|
|
152
|
+
has: (obj) => "listBoards" in obj,
|
|
153
|
+
get: (obj) => obj.listBoards
|
|
154
|
+
},
|
|
155
|
+
metadata: _metadata
|
|
156
|
+
}, null, _instanceExtraInitializers);
|
|
157
|
+
__esDecorate(this, null, _createBoard_decorators, {
|
|
158
|
+
kind: "method",
|
|
159
|
+
name: "createBoard",
|
|
160
|
+
static: false,
|
|
161
|
+
private: false,
|
|
162
|
+
access: {
|
|
163
|
+
has: (obj) => "createBoard" in obj,
|
|
164
|
+
get: (obj) => obj.createBoard
|
|
165
|
+
},
|
|
166
|
+
metadata: _metadata
|
|
167
|
+
}, null, _instanceExtraInitializers);
|
|
168
|
+
__esDecorate(this, null, _previewBoard_decorators, {
|
|
169
|
+
kind: "method",
|
|
170
|
+
name: "previewBoard",
|
|
171
|
+
static: false,
|
|
172
|
+
private: false,
|
|
173
|
+
access: {
|
|
174
|
+
has: (obj) => "previewBoard" in obj,
|
|
175
|
+
get: (obj) => obj.previewBoard
|
|
176
|
+
},
|
|
177
|
+
metadata: _metadata
|
|
178
|
+
}, null, _instanceExtraInitializers);
|
|
179
|
+
__esDecorate(this, null, _deleteBoard_decorators, {
|
|
180
|
+
kind: "method",
|
|
181
|
+
name: "deleteBoard",
|
|
182
|
+
static: false,
|
|
183
|
+
private: false,
|
|
184
|
+
access: {
|
|
185
|
+
has: (obj) => "deleteBoard" in obj,
|
|
186
|
+
get: (obj) => obj.deleteBoard
|
|
187
|
+
},
|
|
188
|
+
metadata: _metadata
|
|
189
|
+
}, null, _instanceExtraInitializers);
|
|
190
|
+
__esDecorate(this, null, _grantBoardSession_decorators, {
|
|
191
|
+
kind: "method",
|
|
192
|
+
name: "grantBoardSession",
|
|
193
|
+
static: false,
|
|
194
|
+
private: false,
|
|
195
|
+
access: {
|
|
196
|
+
has: (obj) => "grantBoardSession" in obj,
|
|
197
|
+
get: (obj) => obj.grantBoardSession
|
|
198
|
+
},
|
|
199
|
+
metadata: _metadata
|
|
200
|
+
}, null, _instanceExtraInitializers);
|
|
201
|
+
if (_metadata) Object.defineProperty(this, Symbol.metadata, {
|
|
202
|
+
enumerable: true,
|
|
203
|
+
configurable: true,
|
|
204
|
+
writable: true,
|
|
205
|
+
value: _metadata
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
store = __runInitializers(this, _instanceExtraInitializers);
|
|
209
|
+
constructor(ctx, store) {
|
|
210
|
+
super(ctx, "whiteboard");
|
|
211
|
+
this.store = store;
|
|
212
|
+
}
|
|
213
|
+
async listBoards() {
|
|
214
|
+
return this.store.list();
|
|
215
|
+
}
|
|
216
|
+
async createBoard(request) {
|
|
217
|
+
return this.store.create(request?.name);
|
|
218
|
+
}
|
|
219
|
+
async previewBoard(request) {
|
|
220
|
+
return this.store.preview(request?.id);
|
|
221
|
+
}
|
|
222
|
+
async deleteBoard(request) {
|
|
223
|
+
return this.store.delete(request?.id, request?.expectedUpdatedAt);
|
|
224
|
+
}
|
|
225
|
+
async grantBoardSession(request) {
|
|
226
|
+
const session = typeof request?.sessionId === "string" ? this.ctx.sessions.get(request.sessionId) : void 0;
|
|
227
|
+
const expected = resolve(this.store.directory).toLowerCase();
|
|
228
|
+
if (!session || typeof session.header.cwd !== "string" || resolve(session.header.cwd).toLowerCase() !== expected) throw new Error("Whiteboard session must belong to the plugin files directory");
|
|
229
|
+
setSandboxMode(session, "workspace-write");
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
})();
|
|
233
|
+
//#endregion
|
|
234
|
+
//#region src/index.ts
|
|
235
|
+
const name = "whiteboard";
|
|
236
|
+
const inject = ["sessions"];
|
|
237
|
+
function apply(ctx) {
|
|
238
|
+
new BoardService(ctx, new BoardStore(fileURLToPath(new URL("../files/", import.meta.url))));
|
|
239
|
+
}
|
|
240
|
+
//#endregion
|
|
241
|
+
export { apply, inject, name };
|
package/package.json
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@guowenzhang/dsh-whiteboard",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Plugin-owned whiteboards with a card gallery and fullscreen draw.io editing for DeepSeek Harness",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "Apache-2.0",
|
|
7
|
+
"main": "lib/index.mjs",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./lib/index.mjs",
|
|
10
|
+
"./client": "./lib/client.js",
|
|
11
|
+
"./package.json": "./package.json",
|
|
12
|
+
"./cordis.patch.yml": "./cordis.patch.yml"
|
|
13
|
+
},
|
|
14
|
+
"engines": { "node": "^22.19.0 || >=24.0.0", "dsh": ">=0.2.0-rc.1" },
|
|
15
|
+
"dsh": {
|
|
16
|
+
"bundle": { "patch": "./cordis.patch.yml" },
|
|
17
|
+
"client": {
|
|
18
|
+
"inject": [
|
|
19
|
+
"@deepseek-ai/dsh-api-remotes",
|
|
20
|
+
"@deepseek-ai/dsh-api-session-controller",
|
|
21
|
+
"@deepseek-ai/dsh-client-locale",
|
|
22
|
+
"@deepseek-ai/dsh-client-ui-renderer",
|
|
23
|
+
"@deepseek-ai/dsh-client-ui-slots",
|
|
24
|
+
"@deepseek-ai/dsh-client-ui-layout",
|
|
25
|
+
"@deepseek-ai/dsh-client-ui-sidebar",
|
|
26
|
+
"@deepseek-ai/dsh-client-ui-sidebar-right",
|
|
27
|
+
"@deepseek-ai/dsh-client-ui-workspace"
|
|
28
|
+
],
|
|
29
|
+
"platform": "web"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"files": ["lib", "files/README.md", "cordis.patch.yml", "README.md", "README.zh.md", "LICENSE", "NOTICE"],
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "tsdown -c tsdown.config.ts && node build-client.mjs",
|
|
35
|
+
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
36
|
+
"test": "vitest run"
|
|
37
|
+
},
|
|
38
|
+
"peerDependencies": {
|
|
39
|
+
"@guowenzhang/dsh-drawioedit": ">=1.0.0",
|
|
40
|
+
"@deepseek-ai/cordis": "*",
|
|
41
|
+
"@deepseek-ai/dsh-api-remotes": ">=0.2.0-rc.1",
|
|
42
|
+
"@deepseek-ai/dsh-api-session-controller": ">=0.2.0-rc.1",
|
|
43
|
+
"@deepseek-ai/dsh-client-locale": ">=0.2.0-rc.1",
|
|
44
|
+
"@deepseek-ai/dsh-client-ui-renderer": ">=0.2.0-rc.1",
|
|
45
|
+
"@deepseek-ai/dsh-client-ui-slots": ">=0.2.0-rc.1",
|
|
46
|
+
"@deepseek-ai/dsh-client-ui-layout": ">=0.2.0-rc.1",
|
|
47
|
+
"@deepseek-ai/dsh-client-ui-sidebar": ">=0.2.0-rc.1",
|
|
48
|
+
"@deepseek-ai/dsh-client-ui-sidebar-right": ">=0.2.0-rc.1",
|
|
49
|
+
"@deepseek-ai/dsh-client-ui-workspace": ">=0.2.0-rc.1",
|
|
50
|
+
"@deepseek-ai/dsh-sandbox-policy": ">=0.2.0-rc.1",
|
|
51
|
+
"@deepseek-ai/dsh-session": ">=0.2.0-rc.1",
|
|
52
|
+
"@deepseek-ai/dsh-util-workspace-path": ">=0.2.0-rc.1",
|
|
53
|
+
"@deepseek-ai/dsh-typert-protocol": ">=0.2.0-rc.1",
|
|
54
|
+
"react": "*"
|
|
55
|
+
},
|
|
56
|
+
"peerDependenciesMeta": {
|
|
57
|
+
"@deepseek-ai/cordis": { "optional": true },
|
|
58
|
+
"@deepseek-ai/dsh-api-remotes": { "optional": true },
|
|
59
|
+
"@deepseek-ai/dsh-api-session-controller": { "optional": true },
|
|
60
|
+
"@deepseek-ai/dsh-client-locale": { "optional": true },
|
|
61
|
+
"@deepseek-ai/dsh-client-ui-renderer": { "optional": true },
|
|
62
|
+
"@deepseek-ai/dsh-client-ui-slots": { "optional": true },
|
|
63
|
+
"@deepseek-ai/dsh-client-ui-layout": { "optional": true },
|
|
64
|
+
"@deepseek-ai/dsh-client-ui-sidebar": { "optional": true },
|
|
65
|
+
"@deepseek-ai/dsh-client-ui-sidebar-right": { "optional": true },
|
|
66
|
+
"@deepseek-ai/dsh-client-ui-workspace": { "optional": true },
|
|
67
|
+
"@deepseek-ai/dsh-sandbox-policy": { "optional": true },
|
|
68
|
+
"@deepseek-ai/dsh-session": { "optional": true },
|
|
69
|
+
"@deepseek-ai/dsh-util-workspace-path": { "optional": true },
|
|
70
|
+
"@deepseek-ai/dsh-typert-protocol": { "optional": true },
|
|
71
|
+
"react": { "optional": true }
|
|
72
|
+
},
|
|
73
|
+
"devDependencies": {
|
|
74
|
+
"@deepseek-ai/cordis": "^4.0.4",
|
|
75
|
+
"@deepseek-ai/dsh-api-remotes": "0.2.0-rc.1",
|
|
76
|
+
"@deepseek-ai/dsh-api-session-controller": "0.2.0-rc.1",
|
|
77
|
+
"@deepseek-ai/dsh-client-locale": "0.2.0-rc.1",
|
|
78
|
+
"@deepseek-ai/dsh-client-ui-renderer": "0.2.0-rc.1",
|
|
79
|
+
"@deepseek-ai/dsh-client-ui-slots": "0.2.0-rc.1",
|
|
80
|
+
"@deepseek-ai/dsh-client-ui-layout": "0.2.0-rc.1",
|
|
81
|
+
"@deepseek-ai/dsh-client-ui-sidebar": "0.2.0-rc.1",
|
|
82
|
+
"@deepseek-ai/dsh-client-ui-sidebar-right": "0.2.0-rc.1",
|
|
83
|
+
"@deepseek-ai/dsh-client-ui-workspace": "0.2.0-rc.1",
|
|
84
|
+
"@deepseek-ai/dsh-sandbox-policy": "0.2.0-rc.1",
|
|
85
|
+
"@deepseek-ai/dsh-session": "0.2.0-rc.1",
|
|
86
|
+
"@deepseek-ai/dsh-util-workspace-path": "0.2.0-rc.1",
|
|
87
|
+
"@deepseek-ai/dsh-typert-protocol": "0.2.0-rc.1",
|
|
88
|
+
"@maxgraph/core": "^0.24.0",
|
|
89
|
+
"@types/node": "^26.5.0",
|
|
90
|
+
"@types/react": "~18.3.1",
|
|
91
|
+
"fflate": "^0.8.3",
|
|
92
|
+
"lightningcss": "^1.32.0",
|
|
93
|
+
"react": "^18.3.1",
|
|
94
|
+
"rolldown": "^1.0.0",
|
|
95
|
+
"tsdown": "^0.22.2",
|
|
96
|
+
"typescript": "^6.0.3",
|
|
97
|
+
"vitest": "^3.2.4"
|
|
98
|
+
}
|
|
99
|
+
}
|