termrelay 0.1.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/CHANGELOG.md +12 -0
- package/LICENSE +21 -0
- package/README.md +86 -0
- package/dist/cli.js +7291 -0
- package/dist/fileBridge.js +427 -0
- package/dist/web/assets/GitGraphPanel-CiNGLdBM.js +6 -0
- package/dist/web/assets/MarkdownBody-D2i4rRHg.js +3 -0
- package/dist/web/assets/UpdateMarkdown-LGUTjy25.js +1 -0
- package/dist/web/assets/index-CQnawMbc.js +249 -0
- package/dist/web/assets/index-anyFeNWc.css +1 -0
- package/dist/web/assets/index-qFnPHo04.js +29 -0
- package/dist/web/assets/syntax-highlighting-A5oyJQsB.js +5 -0
- package/dist/web/index.html +16 -0
- package/dist/web/logo.svg +16 -0
- package/dist/web/third-party-notices.txt +24 -0
- package/package.json +84 -0
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
// packages/adapter-pi/src/fileSnapshots.ts
|
|
2
|
+
import { constants } from "node:fs";
|
|
3
|
+
import {
|
|
4
|
+
chmod,
|
|
5
|
+
link,
|
|
6
|
+
lstat,
|
|
7
|
+
mkdir,
|
|
8
|
+
open,
|
|
9
|
+
readdir,
|
|
10
|
+
realpath,
|
|
11
|
+
rename,
|
|
12
|
+
unlink
|
|
13
|
+
} from "node:fs/promises";
|
|
14
|
+
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
15
|
+
import { randomUUID } from "node:crypto";
|
|
16
|
+
|
|
17
|
+
// packages/adapter-pi/src/fileRecords.ts
|
|
18
|
+
import { createHash } from "node:crypto";
|
|
19
|
+
var FILE_RECORD_KEY = "termrelayFiles";
|
|
20
|
+
var FILE_RESTORE_ENTRY = "termrelay-file-restore";
|
|
21
|
+
var FILE_RESTORE_COMMAND = "termrelay-restore-files";
|
|
22
|
+
var MAX_FILE_BYTES = 256 * 1024;
|
|
23
|
+
var MAX_CHANGED_FILES = 100;
|
|
24
|
+
function fileRecordId(batchId, path) {
|
|
25
|
+
return createHash("sha256").update(`${batchId}\0${path}`).digest("hex").slice(0, 32);
|
|
26
|
+
}
|
|
27
|
+
function sameFileVersion(a, b) {
|
|
28
|
+
return a.content === b.content && a.mode === b.mode;
|
|
29
|
+
}
|
|
30
|
+
function nativeFileBatch(value) {
|
|
31
|
+
const batch = object(value);
|
|
32
|
+
if (batch.version !== 1 || typeof batch.batchId !== "string" || batch.batchId.length > 256 || !Array.isArray(batch.files) || batch.files.length > MAX_CHANGED_FILES)
|
|
33
|
+
return;
|
|
34
|
+
const files = [];
|
|
35
|
+
const ids = /* @__PURE__ */ new Set();
|
|
36
|
+
for (const raw of batch.files) {
|
|
37
|
+
const file = object(raw);
|
|
38
|
+
if (typeof file.path !== "string" || file.path.length > 2048 || !safeRelativePath(file.path) || file.id !== fileRecordId(batch.batchId, file.path) || ids.has(file.id))
|
|
39
|
+
return;
|
|
40
|
+
if (typeof file.revision !== "number" || !Number.isSafeInteger(file.revision) || file.revision < 1)
|
|
41
|
+
return;
|
|
42
|
+
const before = fileVersion(file.before);
|
|
43
|
+
const after = fileVersion(file.after);
|
|
44
|
+
if (!before || !after) return;
|
|
45
|
+
ids.add(file.id);
|
|
46
|
+
files.push({ id: file.id, path: file.path, revision: file.revision, before, after });
|
|
47
|
+
}
|
|
48
|
+
return { version: 1, batchId: batch.batchId, files };
|
|
49
|
+
}
|
|
50
|
+
function nativeRestoreRecord(value) {
|
|
51
|
+
const record = object(value);
|
|
52
|
+
if (typeof record.requestId !== "string" || !/^[\w-]{1,128}$/.test(record.requestId) || record.direction !== "undo" && record.direction !== "redo" || !Array.isArray(record.ids) || record.ids.length < 1 || record.ids.length > MAX_CHANGED_FILES || !record.ids.every((id) => typeof id === "string" && /^[a-f0-9]{32}$/.test(id)) || new Set(record.ids).size !== record.ids.length)
|
|
53
|
+
return;
|
|
54
|
+
return { requestId: record.requestId, ids: record.ids, direction: record.direction };
|
|
55
|
+
}
|
|
56
|
+
function safeRelativePath(path) {
|
|
57
|
+
return Boolean(path) && !path.startsWith("/") && !path.includes("\0") && !path.split("/").some((part) => !part || part === "." || part === ".." || part === ".git");
|
|
58
|
+
}
|
|
59
|
+
function nativeBranchFiles(entries) {
|
|
60
|
+
const state = new NativeFileState();
|
|
61
|
+
for (const raw of entries) {
|
|
62
|
+
const entry = object(raw);
|
|
63
|
+
const message = object(entry.message);
|
|
64
|
+
if (entry.type === "message" && message.role === "toolResult") {
|
|
65
|
+
const batch = nativeFileBatch(object(message.details)[FILE_RECORD_KEY]);
|
|
66
|
+
if (batch) state.batch(batch);
|
|
67
|
+
}
|
|
68
|
+
if (entry.type === "custom" && entry.customType === FILE_RESTORE_ENTRY) {
|
|
69
|
+
const record = nativeRestoreRecord(entry.data);
|
|
70
|
+
if (record) state.restore(record);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return state;
|
|
74
|
+
}
|
|
75
|
+
var NativeFileState = class {
|
|
76
|
+
files = /* @__PURE__ */ new Map();
|
|
77
|
+
restored = /* @__PURE__ */ new Map();
|
|
78
|
+
requests = /* @__PURE__ */ new Set();
|
|
79
|
+
redoGroups = /* @__PURE__ */ new Map();
|
|
80
|
+
heads = /* @__PURE__ */ new Map();
|
|
81
|
+
batch(batch) {
|
|
82
|
+
const accepted = /* @__PURE__ */ new Set();
|
|
83
|
+
for (const file of batch.files) {
|
|
84
|
+
if ((this.files.get(file.id)?.revision ?? 0) >= file.revision) continue;
|
|
85
|
+
this.files.set(file.id, file);
|
|
86
|
+
this.restored.delete(file.id);
|
|
87
|
+
this.redoGroups.delete(file.path);
|
|
88
|
+
this.heads.set(file.path, file.after);
|
|
89
|
+
accepted.add(file.id);
|
|
90
|
+
}
|
|
91
|
+
return accepted;
|
|
92
|
+
}
|
|
93
|
+
ordered(ids) {
|
|
94
|
+
const selected = new Set(ids);
|
|
95
|
+
return [...this.files.values()].filter((file) => selected.has(file.id));
|
|
96
|
+
}
|
|
97
|
+
restore(record) {
|
|
98
|
+
if (this.requests.has(record.requestId)) return false;
|
|
99
|
+
const selected = this.ordered(record.ids);
|
|
100
|
+
if (selected.length !== record.ids.length) return false;
|
|
101
|
+
let merged;
|
|
102
|
+
try {
|
|
103
|
+
merged = mergeFileRecords(selected);
|
|
104
|
+
} catch {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
this.requests.add(record.requestId);
|
|
108
|
+
for (const file of selected) this.restored.set(file.id, record.direction);
|
|
109
|
+
for (const file of merged) {
|
|
110
|
+
this.heads.set(file.path, record.direction === "undo" ? file.before : file.after);
|
|
111
|
+
const ids = record.direction === "undo" ? selected.filter((item) => item.path === file.path).map((item) => item.id) : (this.redoGroups.get(file.path) ?? []).filter((id) => this.restored.get(id) === "undo");
|
|
112
|
+
if (ids.length) this.redoGroups.set(file.path, ids);
|
|
113
|
+
else this.redoGroups.delete(file.path);
|
|
114
|
+
}
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
function mergeFileRecords(files) {
|
|
119
|
+
const merged = /* @__PURE__ */ new Map();
|
|
120
|
+
for (const file of files) {
|
|
121
|
+
const prior = merged.get(file.path);
|
|
122
|
+
if (prior && !sameFileVersion(prior.after, file.before))
|
|
123
|
+
throw new Error(`${file.path} \u7684\u4FEE\u6539\u8BB0\u5F55\u4E0D\u8FDE\u7EED\uFF0C\u65E0\u6CD5\u5B89\u5168\u6062\u590D\u3002`);
|
|
124
|
+
merged.set(file.path, prior ? { ...file, before: prior.before } : file);
|
|
125
|
+
}
|
|
126
|
+
return [...merged.values()];
|
|
127
|
+
}
|
|
128
|
+
function fileVersion(value) {
|
|
129
|
+
const version = object(value);
|
|
130
|
+
if (version.content !== null && (typeof version.content !== "string" || Buffer.byteLength(version.content) > MAX_FILE_BYTES || version.content.includes("\0")))
|
|
131
|
+
return;
|
|
132
|
+
if (typeof version.mode !== "number" || !Number.isInteger(version.mode) || version.mode < 0 || version.mode > 511 || version.content === null && version.mode !== 0)
|
|
133
|
+
return;
|
|
134
|
+
return { content: version.content, mode: version.mode };
|
|
135
|
+
}
|
|
136
|
+
function object(value) {
|
|
137
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// packages/adapter-pi/src/fileSnapshots.ts
|
|
141
|
+
var MAX_SCAN_FILES = 2500;
|
|
142
|
+
var MAX_SCAN_BYTES = 16 * 1024 * 1024;
|
|
143
|
+
var SKIP_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
144
|
+
".git",
|
|
145
|
+
"node_modules",
|
|
146
|
+
".tmp",
|
|
147
|
+
"dist",
|
|
148
|
+
"dist-ts",
|
|
149
|
+
"coverage",
|
|
150
|
+
"test-results",
|
|
151
|
+
".next",
|
|
152
|
+
".venv"
|
|
153
|
+
]);
|
|
154
|
+
var MISSING_FILE = { content: null, mode: 0 };
|
|
155
|
+
var FileCapture = class extends Map {
|
|
156
|
+
observed = /* @__PURE__ */ new Set();
|
|
157
|
+
limited = false;
|
|
158
|
+
};
|
|
159
|
+
function projectRelativePath(cwd, input) {
|
|
160
|
+
const path = relative(cwd, isAbsolute(input) ? input : resolve(cwd, input.replace(/^@/, "")));
|
|
161
|
+
return safeRelativePath(path) ? path : void 0;
|
|
162
|
+
}
|
|
163
|
+
async function captureFiles(cwd, paths) {
|
|
164
|
+
const root = await realpath(cwd);
|
|
165
|
+
const result = new FileCapture();
|
|
166
|
+
result.limited = paths.length > MAX_SCAN_FILES;
|
|
167
|
+
let bytes = 0;
|
|
168
|
+
for (let offset = 0; offset < Math.min(paths.length, MAX_SCAN_FILES); offset += 16) {
|
|
169
|
+
const versions = await Promise.all(
|
|
170
|
+
paths.slice(offset, offset + 16).map(async (input) => {
|
|
171
|
+
const path = projectRelativePath(root, input);
|
|
172
|
+
if (!path) return;
|
|
173
|
+
result.observed.add(path);
|
|
174
|
+
try {
|
|
175
|
+
return { path, version: await readVersion(root, path) };
|
|
176
|
+
} catch {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
})
|
|
180
|
+
);
|
|
181
|
+
for (const entry of versions) {
|
|
182
|
+
if (!entry) continue;
|
|
183
|
+
bytes += Buffer.byteLength(entry.version.content ?? "");
|
|
184
|
+
if (bytes > MAX_SCAN_BYTES) {
|
|
185
|
+
result.limited = true;
|
|
186
|
+
return result;
|
|
187
|
+
}
|
|
188
|
+
result.set(entry.path, entry.version);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return result;
|
|
192
|
+
}
|
|
193
|
+
async function captureWorkspace(cwd) {
|
|
194
|
+
const paths = [];
|
|
195
|
+
const queue = [""];
|
|
196
|
+
for (let index = 0; index < queue.length && paths.length < MAX_SCAN_FILES && index < MAX_SCAN_FILES; index++) {
|
|
197
|
+
let entries;
|
|
198
|
+
try {
|
|
199
|
+
entries = await readdir(join(cwd, queue[index]), { withFileTypes: true });
|
|
200
|
+
} catch {
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
204
|
+
const path = join(queue[index], entry.name);
|
|
205
|
+
if (entry.isDirectory() && !SKIP_DIRECTORIES.has(entry.name)) queue.push(path);
|
|
206
|
+
else if (entry.isFile()) paths.push(path);
|
|
207
|
+
if (paths.length >= MAX_SCAN_FILES) break;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
const captured = await captureFiles(cwd, paths);
|
|
211
|
+
captured.limited ||= paths.length >= MAX_SCAN_FILES || queue.length >= MAX_SCAN_FILES;
|
|
212
|
+
return captured;
|
|
213
|
+
}
|
|
214
|
+
async function restoreFileVersions(cwd, files, direction) {
|
|
215
|
+
const root = await realpath(cwd);
|
|
216
|
+
const plans = mergeFileRecords(files).map((file) => ({
|
|
217
|
+
path: file.path,
|
|
218
|
+
expected: direction === "undo" ? file.after : file.before,
|
|
219
|
+
target: direction === "undo" ? file.before : file.after
|
|
220
|
+
}));
|
|
221
|
+
for (const plan of plans) {
|
|
222
|
+
const current = await readVersion(root, plan.path);
|
|
223
|
+
if (!sameFileVersion(current, plan.expected))
|
|
224
|
+
throw new Error(`${plan.path} \u5B58\u5728\u540E\u7EED\u4FEE\u6539\uFF0C\u672A\u64A4\u9500\u4EFB\u4F55\u6587\u4EF6\u3002`);
|
|
225
|
+
}
|
|
226
|
+
const committed = [];
|
|
227
|
+
try {
|
|
228
|
+
for (const plan of plans) {
|
|
229
|
+
await replaceVersion(root, plan.path, plan.expected, plan.target);
|
|
230
|
+
committed.push(plan);
|
|
231
|
+
}
|
|
232
|
+
} catch (error) {
|
|
233
|
+
const failures = [];
|
|
234
|
+
for (const plan of committed.reverse()) {
|
|
235
|
+
try {
|
|
236
|
+
await replaceVersion(root, plan.path, plan.target, plan.expected);
|
|
237
|
+
} catch {
|
|
238
|
+
failures.push(plan.path);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
if (failures.length) throw new Error(`\u6062\u590D\u4E2D\u65AD\uFF0C\u4EE5\u4E0B\u6587\u4EF6\u9700\u8981\u68C0\u67E5\uFF1A${failures.join("\u3001")}`);
|
|
242
|
+
throw error;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
async function checkedPath(root, path) {
|
|
246
|
+
if (await realpath(root) !== root) throw new Error("\u9879\u76EE\u76EE\u5F55\u5DF2\u6539\u53D8\uFF0C\u8BF7\u91CD\u65B0\u6253\u5F00\u4F1A\u8BDD");
|
|
247
|
+
if (!safeRelativePath(path)) throw new Error("\u6587\u4EF6\u8DEF\u5F84\u8D85\u51FA\u5F53\u524D\u9879\u76EE\u8303\u56F4");
|
|
248
|
+
let current = root;
|
|
249
|
+
const parts = path.split("/");
|
|
250
|
+
for (const [index, part] of parts.entries()) {
|
|
251
|
+
current = join(current, part);
|
|
252
|
+
try {
|
|
253
|
+
const info = await lstat(current);
|
|
254
|
+
if (info.isSymbolicLink() || index < parts.length - 1 && !info.isDirectory() || index === parts.length - 1 && (!info.isFile() || info.nlink !== 1))
|
|
255
|
+
throw new Error(`${path} \u4E0D\u662F\u53EF\u5B89\u5168\u6062\u590D\u7684\u666E\u901A\u6587\u4EF6`);
|
|
256
|
+
} catch (error) {
|
|
257
|
+
if (errorCode(error) === "ENOENT") return join(root, path);
|
|
258
|
+
throw error;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return current;
|
|
262
|
+
}
|
|
263
|
+
async function readVersion(root, path) {
|
|
264
|
+
const absolute = await checkedPath(root, path);
|
|
265
|
+
let file;
|
|
266
|
+
try {
|
|
267
|
+
file = await open(absolute, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
268
|
+
} catch (error) {
|
|
269
|
+
if (errorCode(error) === "ENOENT") return MISSING_FILE;
|
|
270
|
+
throw error;
|
|
271
|
+
}
|
|
272
|
+
try {
|
|
273
|
+
const before = await file.stat();
|
|
274
|
+
if (!before.isFile() || before.nlink !== 1 || before.size > MAX_FILE_BYTES)
|
|
275
|
+
throw new Error(`${path} \u8D85\u51FA\u6587\u672C\u6587\u4EF6\u6062\u590D\u8303\u56F4`);
|
|
276
|
+
const buffer = await file.readFile();
|
|
277
|
+
const after = await file.stat();
|
|
278
|
+
if (buffer.length > MAX_FILE_BYTES || before.size !== after.size || before.mtimeMs !== after.mtimeMs || buffer.includes(0))
|
|
279
|
+
throw new Error(`${path} \u65E0\u6CD5\u5EFA\u7ACB\u5B8C\u6574\u5185\u5BB9\u8BB0\u5F55`);
|
|
280
|
+
const content = buffer.toString("utf8");
|
|
281
|
+
if (!Buffer.from(content).equals(buffer)) throw new Error(`${path} \u4E0D\u662F UTF-8 \u6587\u672C\u6587\u4EF6`);
|
|
282
|
+
return { content, mode: after.mode & 511 };
|
|
283
|
+
} finally {
|
|
284
|
+
await file.close();
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
async function replaceVersion(root, path, expected, target) {
|
|
288
|
+
const absolute = await checkedPath(root, path);
|
|
289
|
+
if (target.content !== null) await mkdir(dirname(absolute), { recursive: true });
|
|
290
|
+
const temporary = join(dirname(absolute), `.termrelay-restore-${randomUUID()}`);
|
|
291
|
+
try {
|
|
292
|
+
if (target.content !== null) {
|
|
293
|
+
const file = await open(temporary, "wx", 384);
|
|
294
|
+
try {
|
|
295
|
+
await file.writeFile(target.content, "utf8");
|
|
296
|
+
await file.sync();
|
|
297
|
+
} finally {
|
|
298
|
+
await file.close();
|
|
299
|
+
}
|
|
300
|
+
await chmod(temporary, target.mode);
|
|
301
|
+
}
|
|
302
|
+
if (!sameFileVersion(await readVersion(root, path), expected))
|
|
303
|
+
throw new Error(`${path} \u5B58\u5728\u540E\u7EED\u4FEE\u6539\uFF0C\u6062\u590D\u5DF2\u505C\u6B62\u3002`);
|
|
304
|
+
await checkedPath(root, path);
|
|
305
|
+
if (target.content === null) {
|
|
306
|
+
if (expected.content !== null) await unlink(absolute);
|
|
307
|
+
} else if (expected.content === null) await link(temporary, absolute);
|
|
308
|
+
else await rename(temporary, absolute);
|
|
309
|
+
} finally {
|
|
310
|
+
await unlink(temporary).catch((error) => {
|
|
311
|
+
if (errorCode(error) !== "ENOENT") throw error;
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
function errorCode(error) {
|
|
316
|
+
return error && typeof error === "object" && "code" in error ? error.code : void 0;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// packages/adapter-pi/src/fileBridge.ts
|
|
320
|
+
function fileBridge(pi) {
|
|
321
|
+
const calls = /* @__PURE__ */ new Map();
|
|
322
|
+
let batchId = "";
|
|
323
|
+
const files = /* @__PURE__ */ new Map();
|
|
324
|
+
let restoring = false;
|
|
325
|
+
pi.on("tool_call", async (event, ctx) => {
|
|
326
|
+
const workspace = ["bash", "shell", "exec", "exec_command"].includes(event.toolName);
|
|
327
|
+
const paths = mutationPaths(event.toolName, event.input, ctx.cwd);
|
|
328
|
+
if (!workspace && !paths.length) return;
|
|
329
|
+
const user = [...ctx.sessionManager.getBranch()].reverse().map(object).find((entry) => entry.type === "message" && object(entry.message).role === "user");
|
|
330
|
+
const currentBatch = typeof user?.id === "string" ? `turn-${user.id}` : `orphan-${event.toolCallId}`;
|
|
331
|
+
if (batchId !== currentBatch) {
|
|
332
|
+
batchId = currentBatch;
|
|
333
|
+
files.clear();
|
|
334
|
+
calls.clear();
|
|
335
|
+
for (const file of nativeBranchFiles(ctx.sessionManager.getBranch()).files.values()) {
|
|
336
|
+
if (file.id === fileRecordId(batchId, file.path)) files.set(file.id, file);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
try {
|
|
340
|
+
const before = workspace ? await captureWorkspace(ctx.cwd) : await captureFiles(ctx.cwd, paths);
|
|
341
|
+
calls.set(event.toolCallId, { batchId, before, workspace });
|
|
342
|
+
} catch {
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
pi.on("tool_result", async (event, ctx) => {
|
|
346
|
+
const call = calls.get(event.toolCallId);
|
|
347
|
+
calls.delete(event.toolCallId);
|
|
348
|
+
if (!call) return;
|
|
349
|
+
try {
|
|
350
|
+
const after = call.workspace ? await captureWorkspace(ctx.cwd) : await captureFiles(ctx.cwd, [...call.before.observed]);
|
|
351
|
+
const missing = [...call.before.keys()].filter((path) => !after.has(path));
|
|
352
|
+
const reread = await captureFiles(ctx.cwd, missing);
|
|
353
|
+
for (const [path, version] of reread) after.set(path, version);
|
|
354
|
+
const changed = [];
|
|
355
|
+
for (const path of /* @__PURE__ */ new Set([...call.before.keys(), ...after.keys()])) {
|
|
356
|
+
if (!call.before.has(path) && (call.before.observed.has(path) || call.before.limited || !call.workspace))
|
|
357
|
+
continue;
|
|
358
|
+
const old = call.before.get(path) ?? MISSING_FILE;
|
|
359
|
+
const next = after.get(path);
|
|
360
|
+
if (!next || sameFileVersion(old, next)) continue;
|
|
361
|
+
const id = fileRecordId(call.batchId, path);
|
|
362
|
+
if (!files.has(id) && files.size >= MAX_CHANGED_FILES) continue;
|
|
363
|
+
const record = {
|
|
364
|
+
id,
|
|
365
|
+
path,
|
|
366
|
+
revision: (files.get(id)?.revision ?? 0) + 1,
|
|
367
|
+
before: files.get(id)?.before ?? old,
|
|
368
|
+
after: next
|
|
369
|
+
};
|
|
370
|
+
files.set(id, record);
|
|
371
|
+
changed.push(record);
|
|
372
|
+
}
|
|
373
|
+
if (changed.length)
|
|
374
|
+
return {
|
|
375
|
+
details: {
|
|
376
|
+
...object(event.details),
|
|
377
|
+
[FILE_RECORD_KEY]: { version: 1, batchId: call.batchId, files: changed }
|
|
378
|
+
}
|
|
379
|
+
};
|
|
380
|
+
} catch {
|
|
381
|
+
}
|
|
382
|
+
});
|
|
383
|
+
pi.registerCommand(FILE_RESTORE_COMMAND, {
|
|
384
|
+
description: "\u64A4\u9500\u6216\u91CD\u65B0\u5E94\u7528\u5F53\u524D\u539F\u751F\u4F1A\u8BDD\u5DF2\u8BB0\u5F55\u7684\u6587\u4EF6\u4FEE\u6539",
|
|
385
|
+
handler: async (args, ctx) => {
|
|
386
|
+
const request = nativeRestoreRecord(JSON.parse(args));
|
|
387
|
+
if (!request) throw new Error("\u65E0\u6548\u7684\u6587\u4EF6\u6062\u590D\u8BF7\u6C42");
|
|
388
|
+
if (restoring || !ctx.isIdle()) throw new Error("\u8BF7\u7B49\u5F85 Pi \u672C\u8F6E\u6267\u884C\u7ED3\u675F\u540E\u518D\u6062\u590D\u6587\u4EF6");
|
|
389
|
+
const native = nativeBranchFiles(ctx.sessionManager.getBranch());
|
|
390
|
+
if (native.requests.has(request.requestId)) return;
|
|
391
|
+
for (const id of request.ids) {
|
|
392
|
+
const file = native.files.get(id);
|
|
393
|
+
if (!file) throw new Error("\u6587\u4EF6\u8BB0\u5F55\u4E0D\u5C5E\u4E8E\u5F53\u524D\u4F1A\u8BDD\u5206\u652F");
|
|
394
|
+
const undone = native.restored.get(id) === "undo";
|
|
395
|
+
if (request.direction === "undo" && undone || request.direction === "redo" && !undone)
|
|
396
|
+
throw new Error("\u6587\u4EF6\u6062\u590D\u72B6\u6001\u5DF2\u6539\u53D8\uFF0C\u8BF7\u5237\u65B0\u540E\u91CD\u8BD5");
|
|
397
|
+
}
|
|
398
|
+
const selected = native.ordered(request.ids);
|
|
399
|
+
restoring = true;
|
|
400
|
+
try {
|
|
401
|
+
await restoreFileVersions(ctx.cwd, selected, request.direction);
|
|
402
|
+
pi.appendEntry(FILE_RESTORE_ENTRY, request);
|
|
403
|
+
} finally {
|
|
404
|
+
restoring = false;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
function mutationPaths(name, input, cwd) {
|
|
410
|
+
const args = object(input);
|
|
411
|
+
if (["edit", "write", "write_file", "delete_file"].includes(name)) {
|
|
412
|
+
const raw = args.path ?? args.file_path;
|
|
413
|
+
const path = typeof raw === "string" ? projectRelativePath(cwd, raw) : void 0;
|
|
414
|
+
return path ? [path] : [];
|
|
415
|
+
}
|
|
416
|
+
if (name !== "apply_patch") return [];
|
|
417
|
+
const patch = typeof input === "string" ? input : args.input ?? args.patch;
|
|
418
|
+
if (typeof patch !== "string" || patch.length > 1e6) return [];
|
|
419
|
+
return [
|
|
420
|
+
...new Set(
|
|
421
|
+
[...patch.matchAll(/^\*\*\* (?:(?:Add|Update|Delete) File|Move to): (.+)$/gm)].map((match) => projectRelativePath(cwd, match[1].replace(/\r$/, ""))).filter((path) => Boolean(path))
|
|
422
|
+
)
|
|
423
|
+
];
|
|
424
|
+
}
|
|
425
|
+
export {
|
|
426
|
+
fileBridge as default
|
|
427
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import{c as ae,r as p,f as ce,G as Z,j as n,a as le,s as z,b as de,d as ue}from"./index-CQnawMbc.js";import"./syntax-highlighting-A5oyJQsB.js";/**
|
|
2
|
+
* @license lucide-react v0.468.0 - ISC
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the ISC license.
|
|
5
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/const he=ae("Tag",[["path",{d:"M12.586 2.586A2 2 0 0 0 11.172 2H4a2 2 0 0 0-2 2v7.172a2 2 0 0 0 .586 1.414l8.704 8.704a2.426 2.426 0 0 0 3.42 0l6.58-6.58a2.426 2.426 0 0 0 0-3.42z",key:"vktsd0"}],["circle",{cx:"7.5",cy:"7.5",r:".5",fill:"currentColor",key:"kqv944"}]]),k=24,J=16,W=["#0085d9","#d9008f","#00d90a","#d98500","#a300d9","#ff0000","#00d9cc","#e138e8","#85d900","#dc5b23","#6f24d6","#ffcc00"];function pe(r,s,c,g){let i=-1;const x=()=>W[++i%W.length];let j=[];const v=new Map;for(const o of s)v.set(o.oid,[...v.get(o.oid)??[],o]);const a=o=>o.ref===c.ref?0:o.ref===g?1:o.kind==="local"?2:o.kind==="remote"?3:4;return r.map(o=>{var T,e;const m=j,u=[],N=m.findIndex(t=>t.id===o.oid),M=N<0?m.length:N;let w=!1;for(const t of m)t.id===o.oid?!w&&o.parentIds[0]&&(u.push({id:o.parentIds[0],color:t.color}),w=!0):u.push(t);for(let t=w?1:0;t<o.parentIds.length;t++){const d=o.parentIds[t];u.push({id:d,color:x()})}const b=(o.parentIds.length?(T=u[M])==null?void 0:T.color:void 0)??((e=m[M])==null?void 0:e.color)??x();return j=u,{commit:o,input:m,output:u,column:M,color:b,current:o.oid===c.oid,refs:(v.get(o.oid)??[]).map(t=>({...t,color:b})).sort((t,d)=>a(t)-a(d)||t.name.localeCompare(d.name))}})}function oe(r){var v;const s=[],c=a=>J*(a+1),g=k/2,i=(a,o,m,u)=>{const N=(u-o)*.8;return`M ${c(a)} ${o} C ${c(a)} ${o+N} ${c(m)} ${u-N} ${c(m)} ${u}`};let x=0;for(let a=0;a<r.input.length;a++){const o=r.input[a];o.id===r.commit.oid?a!==r.column?s.push({color:o.color,d:i(a,0,r.column,g)}):r.commit.parentIds.length&&x++:o.id===((v=r.output[x])==null?void 0:v.id)&&(s.push({color:o.color,d:a===x?`M ${c(a)} 0 V ${k}`:i(a,0,x,k)}),x++)}for(const a of r.commit.parentIds.slice(1)){let o=r.output.length-1;for(;o>=0&&r.output[o].id!==a;)o--;o<0||s.push({color:r.output[o].color,d:i(r.column,g,o,k)})}r.input.some(a=>a.id===r.commit.oid)&&s.push({color:r.input[r.column].color,d:`M ${c(r.column)} 0 V ${g}`}),r.commit.parentIds.length&&s.push({color:r.color,d:`M ${c(r.column)} ${g} V ${k}`});const j=[{r:4,strokeWidth:r.current?2:1,inner:r.current}];return{paths:s,circles:j,cx:c(r.column),cy:g,width:J*(Math.max(r.input.length,r.output.length,r.column+1,1)+1)}}function ge(r,s,c,g,i){const[x,j]=p.useState({loading:!1,loadingMore:!1,outdated:!1}),v=p.useRef(x),a=p.useRef(i);a.current=i;const o=p.useRef(void 0);return p.useEffect(()=>{if(!s||!r)return;let m=!1,u,N;const M=e=>{v.current={...v.current,...e},j(v.current)},w=async(e,t=!1)=>{if(m||document.hidden||u)return;window.clearTimeout(N);const d=new AbortController;u=d,M({loading:!e,loadingMore:!!e});try{const f=await ce(r,e,d.signal);if(m||d.signal.aborted)return;if(f.projectId!==r)throw new Error("Git 图表与当前项目不匹配,请重试");const y=v.current.graph;if(e){if(!y||y.revision!==f.revision)throw new Z("历史已更新,刷新查看",!0);const S=new Set(y.commits.map(L=>L.oid));if(f.commits.some(L=>S.has(L.oid)))throw new Error("图表分页包含重复提交,请重新读取图表");M({graph:{...f,commits:[...y.commits,...f.commits]},pageError:void 0,error:void 0})}else y&&y.revision===f.revision&&y.repository===f.repository&&!t?M({outdated:!1,error:void 0}):!y||t||g.current?(M({graph:f,error:void 0,pageError:void 0,outdated:!1}),a.current()):M({outdated:!0,error:void 0})}catch(f){if(m||d.signal.aborted)return;const y=f instanceof Error?f.message:"无法读取 Git 图表";f instanceof Z&&f.changed&&v.current.graph?M({outdated:!0}):M(e?{pageError:y}:{error:y})}finally{!m&&u===d&&(u=void 0,M({loading:!1,loadingMore:!1}),document.hidden||(N=window.setTimeout(()=>void w(),3e3)))}},b=()=>{window.clearTimeout(N),u==null||u.abort(),u=void 0},T=()=>{document.hidden?b():w()};return o.current={more:()=>{const{graph:e,outdated:t}=v.current;!t&&(e!=null&&e.nextCursor)&&w(e.nextCursor)},refresh:()=>{b(),w(void 0,!0)}},w(),window.addEventListener("focus",T),document.addEventListener("visibilitychange",T),()=>{m=!0,b(),o.current=void 0,window.removeEventListener("focus",T),document.removeEventListener("visibilitychange",T)}},[r,s,c,g]),{...x,loadMore:()=>{var m;return(m=o.current)==null?void 0:m.more()},refresh:()=>{var m;return(m=o.current)==null?void 0:m.refresh()}}}const ee=["图形","提交说明","日期","作者"],D=64;function te(r,s,c){const g=Math.max(D-r[s],Math.min(r[s+1]-D,c)),i=[...r];return i[s]=r[s]+g,i[s+1]=r[s+1]-g,i}function me({active:r,minimumTableWidth:s,onChange:c,onInteraction:g}){const i=p.useRef(null),x=p.useRef(void 0),[j,v]=p.useState(!1),[a,o]=p.useState([64,280,136,120]),m=p.useCallback(()=>{var d;const e=(d=i.current)==null?void 0:d.children;if(!e||e.length!==4)return;const t=Array.from(e,f=>f.getBoundingClientRect().width);return t.every(f=>f>0)?t:void 0},[]);p.useLayoutEffect(()=>{const e=()=>{const d=m();d&&o(f=>f.every((y,S)=>y===d[S])?f:d)},t=new ResizeObserver(e);for(const d of i.current.children)t.observe(d);return e(),()=>t.disconnect()},[m]);const u=p.useCallback(()=>{const e=x.current;x.current=void 0,e!=null&&e.element.hasPointerCapture(e.pointerId)&&e.element.releasePointerCapture(e.pointerId),v(!1)},[]);p.useEffect(()=>(r||u(),u),[r,u]),p.useEffect(()=>{const e=()=>{document.hidden&&u()},t=d=>{d.key==="Escape"&&u()};return window.addEventListener("blur",u),window.addEventListener("resize",u),window.addEventListener("keydown",t),document.addEventListener("visibilitychange",e),()=>{window.removeEventListener("blur",u),window.removeEventListener("resize",u),window.removeEventListener("keydown",t),document.removeEventListener("visibilitychange",e),u()}},[u]);const N=e=>c({graph:e[0],date:e[2],author:e[3],minimumTableWidth:s}),M=(e,t)=>{if(!r||!e.isPrimary||e.button!==0||x.current)return;const d=m();d&&(e.preventDefault(),g(),e.currentTarget.focus({preventScroll:!0}),e.currentTarget.setPointerCapture(e.pointerId),x.current={pointerId:e.pointerId,element:e.currentTarget,index:t,x:e.clientX,sizes:d},v(!0))},w=e=>{const t=x.current;!t||t.pointerId!==e.pointerId||(t.sizes=te(t.sizes,t.index,e.clientX-t.x),t.x=e.clientX,N(t.sizes))},b=e=>{var t;e.pointerId===((t=x.current)==null?void 0:t.pointerId)&&u()},T=(e,t)=>{const d=m();if(!r||!d||x.current)return;const f=e.shiftKey?32:8,y=e.key==="ArrowLeft"?-f:e.key==="ArrowRight"?f:e.key==="Home"?D-d[t]:e.key==="End"?d[t+1]-D:void 0;y!==void 0&&(e.preventDefault(),g(),N(te(d,t,y)))};return n.jsx("div",{ref:i,className:"git-graph-columns","data-resizing":j,onMouseEnter:g,children:ee.map((e,t)=>n.jsxs("span",{children:[n.jsx("span",{className:"git-graph-column-title",children:e}),t<ee.length-1?n.jsx("span",{className:"git-graph-column-resizer",role:"separator",tabIndex:0,"aria-label":`调整${e}列宽`,"aria-orientation":"vertical","aria-valuemin":D,"aria-valuemax":Math.round(a[t]+a[t+1]-D),"aria-valuenow":Math.round(a[t]),"aria-valuetext":`${Math.round(a[t])} 像素`,onPointerDown:d=>M(d,t),onPointerMove:w,onPointerUp:b,onPointerCancel:b,onLostPointerCapture:b,onKeyDown:d=>T(d,t)}):null]},e))})}const re=8,q=31,ne=new Intl.DateTimeFormat("zh-CN",{year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",hour12:!1});function ye({project:r,active:s,hidden:c,revision:g,columnWidths:i,onColumnWidthsChange:x}){var Y;const j=p.useRef(null),v=p.useRef(null),a=p.useRef(!0),[o,m]=p.useState(0),[u,N]=p.useState(0),[M,w]=p.useState(!1),[b,T]=p.useState(0),[e,t]=p.useState(),d=p.useRef(void 0),f=()=>window.clearTimeout(d.current),y=()=>{f(),d.current=window.setTimeout(()=>t(void 0),180)};p.useEffect(()=>()=>window.clearTimeout(d.current),[]);const S=p.useId(),L=p.useId(),G=ge(r==null?void 0:r.id,s,g,a,()=>{j.current&&(j.current.scrollTop=0),a.current=!0,m(0),T(0),t(void 0)}),{graph:l,loading:K,loadingMore:H,outdated:O,error:A,pageError:B}=G,I=p.useMemo(()=>l?pe(l.commits,l.refs,l.head,l.upstreamRef):[],[l]),_=Math.max(0,Math.floor(o/k)-re),P=Math.max(0,u-q),U=Math.min(I.length,Math.ceil((o+P)/k)+re),se=l!=null&&l.nextCursor||H||l!=null&&l.truncated||B?32:0,V=p.useMemo(()=>Math.max(64,...I.map(h=>oe(h).width)),[I]),Q=Math.max((i==null?void 0:i.minimumTableWidth)??V+536,((i==null?void 0:i.graph)??V)+((i==null?void 0:i.date)??136)+((i==null?void 0:i.author)??120)+64);p.useLayoutEffect(()=>{if(!s||!j.current)return;const h=j.current,E=()=>{N(h.clientHeight),m(h.scrollTop),a.current=h.scrollTop<=1};E();const $=new ResizeObserver(E);return $.observe(h),()=>$.disconnect()},[s]),p.useEffect(()=>{s&&u>0&&(l!=null&&l.nextCursor)&&!K&&!H&&!B&&!A&&!O&&o+P>=I.length*k-k*5&&G.loadMore()},[s,u,P,o,l,K,H,B,A,O,I.length,G.loadMore]);const ie=h=>{let E=b;const $=Math.max(1,Math.floor(P/k));switch(h.key){case"ArrowDown":E++;break;case"ArrowUp":E--;break;case"PageDown":E+=$;break;case"PageUp":E-=$;break;case"Home":E=0;break;case"End":E=I.length-1;break;case"Escape":t(void 0),w(!1);return;default:return}h.preventDefault(),E=Math.max(0,Math.min(I.length-1,E)),T(E),w(!0),t(void 0);const C=j.current;if(!C)return;const F=E*k;F<C.scrollTop?C.scrollTop=F:F+k>C.scrollTop+P&&(C.scrollTop=F+k-P)};let R=e;if(!R&&M&&b>=_&&b<U&&j.current){const h=j.current.getBoundingClientRect();R={index:b,rect:new DOMRect(h.left,h.top+q+b*k-o,h.width,k)}}const X=s&&R?I[R.index]:void 0;return n.jsxs("section",{id:"git-graph-panel",className:"git-graph-panel","aria-label":"Git 图表",hidden:c,inert:c,children:[n.jsxs("header",{className:"git-graph-header",children:[n.jsx("h2",{children:"Git 图表"}),r?n.jsx("span",{className:"git-graph-project",title:r.cwd,children:r.name}):null,n.jsx("span",{className:"git-graph-scope",children:"所有分支"})]}),l!=null&&l.repository&&!l.rootProject?n.jsx("p",{className:"git-graph-note",children:"所属仓库历史"}):null,O?n.jsxs("p",{className:"git-graph-note",role:"status",children:["历史已更新,",n.jsx("button",{className:"git-retry",onClick:G.refresh,children:"刷新查看"})]}):null,A?n.jsxs("p",{className:"git-graph-note git-status-error",role:"alert",children:[A," ",n.jsx("button",{className:"git-retry",onClick:G.refresh,children:"重试 Git 图表"})]}):null,l!=null&&l.shallow?n.jsx("p",{className:"git-graph-note",children:"浅克隆:仅显示本机已有的历史"}):null,n.jsx("div",{ref:j,className:"git-graph-viewport",style:{"--git-graph-width":`${(i==null?void 0:i.graph)??V}px`,"--git-graph-date-width":`${(i==null?void 0:i.date)??136}px`,"--git-graph-author-width":`${(i==null?void 0:i.author)??120}px`,"--git-graph-table-min-width":`${Q}px`,"--git-graph-row-height":`${k}px`,"--git-graph-header-height":`${q}px`},onMouseLeave:y,onScroll:h=>{a.current=h.currentTarget.scrollTop<=1,m(h.currentTarget.scrollTop),t(void 0)},children:I.length?n.jsxs("div",{className:"git-graph-table",children:[n.jsx(me,{active:s,minimumTableWidth:Q,onChange:x,onInteraction:()=>{f(),t(void 0),w(!1)}}),n.jsx("div",{ref:v,role:"listbox",tabIndex:0,"aria-label":"Git 提交图表","aria-busy":H||K&&!l,"aria-activedescendant":M&&b>=_&&b<U?`${L}-${b}`:void 0,"aria-describedby":X&&M?S:void 0,onKeyDown:ie,onFocus:()=>w(!0),onBlur:()=>w(!1),className:"git-graph-rows",style:{height:I.length*k},children:I.slice(_,U).map((h,E)=>{const $=_+E;return n.jsxs("div",{id:`${L}-${$}`,className:"git-graph-row",role:"option","aria-selected":b===$,"aria-posinset":$+1,"aria-setsize":l!=null&&l.nextCursor||l!=null&&l.truncated?-1:I.length,"aria-label":`${h.current?"当前提交,":""}${h.commit.subject||"无提交标题"},${ne.format(h.commit.timestamp)},${h.commit.author},${h.commit.oid.slice(0,8)}`,"data-current":h.current,"data-oid":h.commit.oid,style:{top:$*k,"--git-graph-color":h.color},onMouseEnter:C=>{f(),t({index:$,rect:C.currentTarget.getBoundingClientRect()})},onClick:()=>{var C;T($),(C=v.current)==null||C.focus({preventScroll:!0})},children:[n.jsx("span",{className:"git-graph-graph-cell",children:n.jsx(fe,{row:h})}),n.jsxs("span",{className:"git-graph-description",children:[h.current?n.jsx("span",{className:"git-graph-head-dot","aria-hidden":"true"}):null,n.jsx(xe,{row:h,headRef:(l==null?void 0:l.head.ref)??null}),n.jsx("span",{className:"git-graph-subject",children:z(h.commit.subject)||"无提交标题"}),h.commit.shallowBoundary?n.jsx("span",{className:"git-graph-boundary",children:"浅克隆边界"}):null]}),n.jsx("time",{className:"git-graph-date",dateTime:new Date(h.commit.timestamp).toISOString(),children:ne.format(h.commit.timestamp)}),n.jsx("span",{className:"git-graph-author",children:z(h.commit.author)})]},h.commit.oid)})}),se?n.jsxs("div",{className:"git-graph-footer",children:[n.jsx("svg",{className:"git-graph-continuation",width:(i==null?void 0:i.graph)??V,height:32,"aria-hidden":"true",children:(Y=I.at(-1))==null?void 0:Y.output.map((h,E)=>n.jsx("path",{d:`M ${(E+1)*J} 0 V 32`,stroke:h.color,strokeWidth:2},E))}),B?n.jsxs("span",{role:"alert",children:[B," ",n.jsx("button",{className:"git-retry",onClick:G.loadMore,children:"重试加载"})]}):l!=null&&l.truncated?n.jsx("span",{role:"status",children:"已显示前 5000 条提交,历史未完整显示"}):O?null:n.jsx("button",{className:"git-retry",disabled:H,onClick:G.loadMore,children:H?"正在加载…":"加载更多提交"})]}):null]}):n.jsxs("div",{className:"git-graph-empty",role:"status",children:[n.jsx(le,{size:30,"aria-hidden":"true"}),n.jsx("p",{children:r?l&&!l.repository?"此项目不是 Git 仓库":l?"此仓库还没有提交":A?"暂时无法显示图表":"正在读取 Git 图表…":"请先在 Code 页面导入项目"})]})}),X&&R?n.jsx(ve,{id:S,row:X,rect:R.rect,onMouseEnter:()=>{f(),t(R)},onMouseLeave:y}):null]})}function fe({row:r}){const s=oe(r);return n.jsxs("svg",{className:"git-graph-lines",width:s.width,height:k,"aria-hidden":"true",children:[s.paths.map((c,g)=>n.jsx("path",{d:c.d,stroke:"var(--git-graph-row-background)",strokeWidth:4,fill:"none"},`outline-${g}`)),s.paths.map((c,g)=>n.jsx("path",{d:c.d,stroke:c.color,strokeWidth:2,fill:"none",strokeLinecap:"round"},g)),s.circles.map((c,g)=>n.jsx("circle",{cx:s.cx,cy:s.cy,r:c.r,strokeWidth:c.strokeWidth,stroke:c.inner?r.color:"var(--git-graph-row-background)",fill:c.inner?"var(--git-graph-row-background)":r.color},g))]})}function xe({row:r,headRef:s}){return r.refs.length?n.jsx("span",{className:"git-graph-badges","aria-hidden":"true",children:r.refs.map(c=>{const g=c.kind==="tag"?he:de;return n.jsxs("span",{className:"git-graph-badge","data-active":c.ref===s,"data-kind":c.kind,children:[n.jsx(g,{size:18}),n.jsx("span",{children:z(c.name)})]},c.ref)})}):null}function ve({id:r,row:s,rect:c,onMouseEnter:g,onMouseLeave:i}){const x=p.useRef(null),[j,v]=p.useState({left:0,top:0});return p.useLayoutEffect(()=>{if(!x.current)return;const a=x.current.getBoundingClientRect();v({left:Math.max(8,Math.min(c.left+24,window.innerWidth-a.width-8)),top:Math.max(8,Math.min(c.bottom+6+a.height>window.innerHeight-8?c.top-a.height-6:c.bottom+6,window.innerHeight-a.height-8))})},[s,c]),ue.createPortal(n.jsxs("div",{ref:x,id:r,role:"tooltip",className:"git-graph-tooltip",style:j,onMouseEnter:g,onMouseLeave:i,children:[n.jsxs("div",{className:"git-graph-tooltip-author",children:[z(s.commit.author)," ",n.jsx("time",{dateTime:new Date(s.commit.timestamp).toISOString(),children:new Date(s.commit.timestamp).toLocaleString("zh-CN")})]}),n.jsx("p",{children:z(s.commit.message)||"无提交说明"}),s.commit.messageTruncated?n.jsx("p",{className:"git-graph-tooltip-muted",children:"提交说明过长,仅显示前 8192 个字符"}):null,s.refs.length?n.jsx("div",{className:"git-graph-tooltip-refs",children:s.refs.map(a=>n.jsxs("span",{children:[a.kind==="tag"?"标签":a.kind==="remote"?"远程分支":"分支",":",z(a.name)]},a.ref))}):null,n.jsx("code",{children:s.commit.oid})]}),document.body)}export{ye as GitGraphPanel};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import{r as m,j as h,s as _,C as M,e as L}from"./index-CQnawMbc.js";import{c as P,v as D,M as H,r as R}from"./index-qFnPHo04.js";import{g as q,c as z}from"./syntax-highlighting-A5oyJQsB.js";const N=(function(e,t,n){const r=P(n);if(!e||!e.type||!e.children)throw new Error("Expected parent node");if(typeof t=="number"){if(t<0||t===Number.POSITIVE_INFINITY)throw new Error("Expected positive finite number as index")}else if(t=e.children.indexOf(t),t<0)throw new Error("Expected child node or index");for(;++t<e.children.length;)if(r(e.children[t],t,e))return e.children[t]}),d=(function(e){if(e==null)return $;if(typeof e=="string")return W(e);if(typeof e=="object")return V(e);if(typeof e=="function")return b(e);throw new Error("Expected function, string, or array as `test`")});function V(e){const t=[];let n=-1;for(;++n<e.length;)t[n]=d(e[n]);return b(r);function r(...i){let o=-1;for(;++o<t.length;)if(t[o].apply(this,i))return!0;return!1}}function W(e){return b(t);function t(n){return n.tagName===e}}function b(e){return t;function t(n,r,i){return!!(G(n)&&e.call(this,n,typeof r=="number"?r:void 0,i||void 0))}}function $(e){return!!(e&&typeof e=="object"&&"type"in e&&e.type==="element"&&"tagName"in e&&typeof e.tagName=="string")}function G(e){return e!==null&&typeof e=="object"&&"type"in e&&"tagName"in e}const E=/\n/g,j=/[\t ]+/g,x=d("br"),v=d(ee),U=d("p"),A=d("tr"),Y=d(["datalist","head","noembed","noframes","noscript","rp","script","style","template","title",Z,te]),C=d(["address","article","aside","blockquote","body","caption","center","dd","dialog","dir","dl","dt","div","figure","figcaption","footer","form,","h1","h2","h3","h4","h5","h6","header","hgroup","hr","html","legend","li","listing","main","menu","nav","ol","p","plaintext","pre","section","ul","xmp"]);function J(e,t){const n=t||{},r="children"in e?e.children:[],i=C(e),o=S(e,{whitespace:n.whitespace||"normal"}),s=[];(e.type==="text"||e.type==="comment")&&s.push(...B(e,{breakBefore:!0,breakAfter:!0}));let a=-1;for(;++a<r.length;)s.push(...T(r[a],e,{whitespace:o,breakBefore:a?void 0:i,breakAfter:a<r.length-1?x(r[a+1]):i}));const c=[];let l;for(a=-1;++a<s.length;){const p=s[a];typeof p=="number"?l!==void 0&&p>l&&(l=p):p&&(l!==void 0&&l>-1&&c.push(`
|
|
2
|
+
`.repeat(l)||" "),l=-1,c.push(p))}return c.join("")}function T(e,t,n){return e.type==="element"?K(e,t,n):e.type==="text"?n.whitespace==="normal"?B(e,n):Q(e):[]}function K(e,t,n){const r=S(e,n),i=e.children||[];let o=-1,s=[];if(Y(e))return s;let a,c;for(x(e)||A(e)&&N(t,e,A)?c=`
|
|
3
|
+
`:U(e)?(a=2,c=2):C(e)&&(a=1,c=1);++o<i.length;)s=s.concat(T(i[o],e,{whitespace:r,breakBefore:o?void 0:a,breakAfter:o<i.length-1?x(i[o+1]):c}));return v(e)&&N(t,e,v)&&s.push(" "),a&&s.unshift(a),c&&s.push(c),s}function B(e,t){const n=String(e.value),r=[],i=[];let o=0;for(;o<=n.length;){E.lastIndex=o;const c=E.exec(n),l=c&&"index"in c?c.index:n.length;r.push(X(n.slice(o,l).replace(/[\u061C\u200E\u200F\u202A-\u202E\u2066-\u2069]/g,""),o===0?t.breakBefore:!0,l===n.length?t.breakAfter:!0)),o=l+1}let s=-1,a;for(;++s<r.length;)r[s].charCodeAt(r[s].length-1)===8203||s<r.length-1&&r[s+1].charCodeAt(0)===8203?(i.push(r[s]),a=void 0):r[s]?(typeof a=="number"&&i.push(a),i.push(r[s]),a=0):(s===0||s===r.length-1)&&i.push(0);return i}function Q(e){return[String(e.value)]}function X(e,t,n){const r=[];let i=0,o;for(;i<e.length;){j.lastIndex=i;const s=j.exec(e);o=s?s.index:e.length,!i&&!o&&s&&!t&&r.push(""),i!==o&&r.push(e.slice(i,o)),i=s?o+s[0].length:o}return i!==o&&!n&&r.push(""),r.join(" ")}function S(e,t){if(e.type==="element"){const n=e.properties||{};switch(e.tagName){case"listing":case"plaintext":case"xmp":return"pre";case"nobr":return"nowrap";case"pre":return n.wrap?"pre-wrap":"pre";case"td":case"th":return n.noWrap?"nowrap":t.whitespace;case"textarea":return"pre-wrap"}}return t.whitespace}function Z(e){return!!(e.properties||{}).hidden}function ee(e){return e.tagName==="td"||e.tagName==="th"}function te(e){return e.tagName==="dialog"&&!(e.properties||{}).open}const ne={};function re(e){const t=e||ne,n=t.aliases,r=t.detect||!1,i=t.languages||q,o=t.plainText,s=t.prefix,a=t.subset;let c="hljs";const l=z(i);if(n&&l.registerAlias(n),s){const p=s.indexOf("-");c=p===-1?s:s.slice(0,p)}return function(p,O){D(p,"element",function(u,le,y){if(u.tagName!=="code"||!y||y.type!=="element"||y.tagName!=="pre")return;const f=ie(u);if(f===!1||!f&&!r||f&&o&&o.includes(f))return;Array.isArray(u.properties.className)||(u.properties.className=[]),u.properties.className.includes(c)||u.properties.className.unshift(c);const k=J(u,{whitespace:"pre"});let g;try{g=f?l.highlight(f,k,{prefix:s}):l.highlightAuto(k,{prefix:s,subset:a})}catch(F){const w=F;if(f&&/Unknown language/.test(w.message)){O.message("Cannot highlight as `"+f+"`, it’s not registered",{ancestors:[y,u],cause:w,place:u.position,ruleId:"missing-language",source:"rehype-highlight"});return}throw w}!f&&g.data&&g.data.language&&u.properties.className.push("language-"+g.data.language),g.children.length>0&&(u.children=g.children)})}}function ie(e){const t=e.properties.className;let n=-1;if(!Array.isArray(t))return;let r;for(;++n<t.length;){const i=String(t[n]);if(i==="no-highlight"||i==="nohighlight")return!1;!r&&i.slice(0,5)==="lang-"&&(r=i.slice(5)),!r&&i.slice(0,9)==="language-"&&(r=i.slice(9))}return r}const se={detect:!0,subset:["javascript","typescript","python","bash","json","css","xml","java","cpp","csharp","go","rust","sql","yaml","markdown"]},he=m.memo(function({content:t}){return h.jsx("div",{className:"markdown-body",children:h.jsx(H,{skipHtml:!0,remarkPlugins:[R],rehypePlugins:[[re,se]],components:{a:({href:n,children:r})=>h.jsx("a",{href:ce(n),target:"_blank",rel:"noreferrer",children:r}),pre:oe},children:_(t)})})});function oe({children:e}){const[t,n]=m.useState("idle"),r=m.useRef(void 0),i=I(e).replace(/\n$/,"");m.useEffect(()=>()=>{r.current!==void 0&&window.clearTimeout(r.current)},[]);const o=async()=>{try{await ae(i),n("copied")}catch{n("failed")}r.current!==void 0&&window.clearTimeout(r.current),r.current=window.setTimeout(()=>n("idle"),1800)},s=t==="copied"?"代码已复制":t==="failed"?"复制失败":"复制代码";return h.jsxs("div",{className:"code-block",children:[h.jsx("pre",{tabIndex:0,children:e}),h.jsx("button",{type:"button",className:`code-copy-button ${t}`,"aria-label":s,title:s,onMouseDown:a=>a.preventDefault(),onClick:()=>void o(),children:t==="copied"?h.jsx(M,{size:14,"aria-hidden":"true"}):h.jsx(L,{size:14,"aria-hidden":"true"})})]})}function I(e){return m.Children.toArray(e).map(t=>typeof t=="string"||typeof t=="number"?String(t):m.isValidElement(t)?I(t.props.children):"").join("")}async function ae(e){var i;let t;if((i=navigator.clipboard)!=null&&i.writeText)try{await navigator.clipboard.writeText(e);return}catch(o){t=o}const n=document.createElement("textarea");n.value=e,n.setAttribute("readonly",""),n.style.position="fixed",n.style.opacity="0",document.body.append(n),n.select();const r=document.execCommand("copy");if(n.remove(),!r)throw t instanceof Error?t:new Error("Clipboard is unavailable")}function ce(e){if(e&&/^(https?:|mailto:|#)/i.test(e))return e}export{he as MarkdownBody};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{j as a,i as t}from"./index-CQnawMbc.js";import{M as n,r as o}from"./index-qFnPHo04.js";import"./syntax-highlighting-A5oyJQsB.js";function l({markdown:e}){return a.jsx(n,{skipHtml:!0,remarkPlugins:[o],disallowedElements:["img","input"],urlTransform:r=>t(r)?r:"",components:{a:({href:r,children:s})=>t(r)?a.jsx("a",{href:r,target:"_blank",rel:"noreferrer",children:s}):a.jsx("span",{children:s})},children:e})}export{l as default};
|