dsh-plugin-office-markdown 1.2.1
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 +21 -0
- package/README.en.md +156 -0
- package/README.md +144 -0
- package/cordis.patch.yml +35 -0
- package/lib/client.js +589 -0
- package/lib/convert.js +796 -0
- package/lib/env.js +602 -0
- package/lib/fallback-node.js +334 -0
- package/lib/fallback.py +579 -0
- package/lib/index.js +1443 -0
- package/lib/paths.js +76 -0
- package/lib/removal-watchdog.js +359 -0
- package/lib/settings-api.js +381 -0
- package/package.json +55 -0
package/lib/client.js
ADDED
|
@@ -0,0 +1,589 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({ id: "dsh-plugin-office-markdown", factory: function (require) {
|
|
2
|
+
var module = { exports: {} };
|
|
3
|
+
var exports = module.exports;
|
|
4
|
+
|
|
5
|
+
/*!
|
|
6
|
+
* dsh-plugin-office-markdown — client half (browser)
|
|
7
|
+
*
|
|
8
|
+
* A hand-written DSH client module in the `window.__ModuleLoader__.load({ id,
|
|
9
|
+
* factory })` handshake, so no build step and no bundler are needed: the file
|
|
10
|
+
* in the package is exactly what the shell evaluates. `react` stays external —
|
|
11
|
+
* the web shell seeds it in the module table and the loader-provided `require`
|
|
12
|
+
* resolves it inside the factory.
|
|
13
|
+
*
|
|
14
|
+
* Contract: `{ inject: ['slots'], apply(ctx) }`. It registers one settings page
|
|
15
|
+
* into the `settings.section` slot and renders it with React.createElement.
|
|
16
|
+
*
|
|
17
|
+
* The page itself only talks to the host half through the package-private JSON
|
|
18
|
+
* API `/office-markdown/api/*`, which is served by `lib/settings-api.js`. It
|
|
19
|
+
* never installs anything by itself: the user picks an interpreter, clicks
|
|
20
|
+
* 一键配置, and the host runs pip in the background while this page polls the
|
|
21
|
+
* job endpoint for progress.
|
|
22
|
+
*/
|
|
23
|
+
var React = require("react");
|
|
24
|
+
if (React && React.default) React = React.default;
|
|
25
|
+
var h = React.createElement;
|
|
26
|
+
var useState = React.useState;
|
|
27
|
+
var useEffect = React.useEffect;
|
|
28
|
+
var useCallback = React.useCallback;
|
|
29
|
+
|
|
30
|
+
var API = "/office-markdown/api";
|
|
31
|
+
var inject = ["slots"];
|
|
32
|
+
|
|
33
|
+
var COLOR = {
|
|
34
|
+
ok: "#22a06b",
|
|
35
|
+
bad: "#e5484d",
|
|
36
|
+
warn: "#d97706",
|
|
37
|
+
dim: "rgba(127,127,127,0.95)",
|
|
38
|
+
line: "rgba(127,127,127,0.30)",
|
|
39
|
+
fill: "rgba(127,127,127,0.10)"
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
function fmtBytes(n) {
|
|
43
|
+
var v = Number(n) || 0;
|
|
44
|
+
if (v < 1024) return v + " B";
|
|
45
|
+
if (v < 1048576) return (v / 1024).toFixed(1) + " KB";
|
|
46
|
+
if (v < 1073741824) return (v / 1048576).toFixed(1) + " MB";
|
|
47
|
+
return (v / 1073741824).toFixed(2) + " GB";
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function getJSON(path) {
|
|
51
|
+
return fetch(API + path, { headers: { accept: "application/json" } }).then(function (r) {
|
|
52
|
+
return r.json();
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function postJSON(path, body) {
|
|
57
|
+
return fetch(API + path, {
|
|
58
|
+
method: "POST",
|
|
59
|
+
headers: { "content-type": "application/json", accept: "application/json" },
|
|
60
|
+
body: JSON.stringify(body || {})
|
|
61
|
+
}).then(function (r) {
|
|
62
|
+
return r.json();
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function messageOf(x) {
|
|
67
|
+
return String((x && x.message) || x || "未知错误");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
var S = {
|
|
71
|
+
box: { fontSize: 13, lineHeight: 1.65, color: "inherit", display: "flex", flexDirection: "column", gap: 14 },
|
|
72
|
+
head: { display: "flex", alignItems: "baseline", gap: 8, flexWrap: "wrap" },
|
|
73
|
+
title: { fontSize: 15, fontWeight: 600, margin: 0 },
|
|
74
|
+
dim: { color: COLOR.dim, fontSize: 12 },
|
|
75
|
+
card: {
|
|
76
|
+
border: "1px solid " + COLOR.line,
|
|
77
|
+
borderRadius: 8,
|
|
78
|
+
padding: "10px 12px",
|
|
79
|
+
display: "flex",
|
|
80
|
+
flexDirection: "column",
|
|
81
|
+
gap: 8
|
|
82
|
+
},
|
|
83
|
+
cardHead: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 8, flexWrap: "wrap" },
|
|
84
|
+
row: { display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap" },
|
|
85
|
+
mono: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace", fontSize: 12 },
|
|
86
|
+
badge: {
|
|
87
|
+
border: "1px solid currentColor",
|
|
88
|
+
borderRadius: 999,
|
|
89
|
+
padding: "0px 8px",
|
|
90
|
+
fontSize: 11,
|
|
91
|
+
whiteSpace: "nowrap",
|
|
92
|
+
lineHeight: "18px"
|
|
93
|
+
},
|
|
94
|
+
btn: {
|
|
95
|
+
padding: "5px 12px",
|
|
96
|
+
borderRadius: 6,
|
|
97
|
+
border: "1px solid " + COLOR.line,
|
|
98
|
+
background: COLOR.fill,
|
|
99
|
+
color: "inherit",
|
|
100
|
+
cursor: "pointer",
|
|
101
|
+
fontSize: 12,
|
|
102
|
+
font: "inherit"
|
|
103
|
+
},
|
|
104
|
+
btnOff: {
|
|
105
|
+
padding: "5px 12px",
|
|
106
|
+
borderRadius: 6,
|
|
107
|
+
border: "1px solid " + COLOR.line,
|
|
108
|
+
background: "transparent",
|
|
109
|
+
color: COLOR.dim,
|
|
110
|
+
cursor: "not-allowed",
|
|
111
|
+
fontSize: 12,
|
|
112
|
+
font: "inherit"
|
|
113
|
+
},
|
|
114
|
+
log: {
|
|
115
|
+
fontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",
|
|
116
|
+
fontSize: 11,
|
|
117
|
+
lineHeight: 1.5,
|
|
118
|
+
whiteSpace: "pre-wrap",
|
|
119
|
+
maxHeight: 190,
|
|
120
|
+
overflow: "auto",
|
|
121
|
+
background: COLOR.fill,
|
|
122
|
+
borderRadius: 6,
|
|
123
|
+
padding: 8,
|
|
124
|
+
margin: 0
|
|
125
|
+
},
|
|
126
|
+
list: { display: "flex", flexDirection: "column", gap: 6, margin: 0, padding: 0, listStyle: "none" },
|
|
127
|
+
li: { display: "flex", alignItems: "baseline", gap: 8, flexWrap: "wrap" }
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
function renderTest(r) {
|
|
131
|
+
if (!r) return null;
|
|
132
|
+
if (r.ok !== true) {
|
|
133
|
+
return h(
|
|
134
|
+
"div",
|
|
135
|
+
null,
|
|
136
|
+
h("div", { style: { color: COLOR.bad } }, "试转失败:" + (r.error || "未知错误")),
|
|
137
|
+
r.sourcePath ? h("div", { style: S.dim }, "源文件:" + r.sourcePath) : null,
|
|
138
|
+
r.converterAttempts && r.converterAttempts.length
|
|
139
|
+
? h("div", { style: S.dim }, "尝试过的转换器:" + r.converterAttempts.join(" · "))
|
|
140
|
+
: null,
|
|
141
|
+
r.notices && r.notices.length ? h("div", { style: S.dim }, r.notices.join(" · ")) : null
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
if (r.skipped) return h("div", { style: S.dim }, r.message || "无需转换。");
|
|
145
|
+
return h(
|
|
146
|
+
"div",
|
|
147
|
+
null,
|
|
148
|
+
h(
|
|
149
|
+
"div",
|
|
150
|
+
{ style: S.row },
|
|
151
|
+
h(
|
|
152
|
+
"span",
|
|
153
|
+
{ style: Object.assign({}, S.badge, { color: r.fidelity === "limited" ? COLOR.warn : COLOR.ok }) },
|
|
154
|
+
r.fidelity === "limited" ? "保真度有限(内置兜底)" : "保真度高(MarkItDown)"
|
|
155
|
+
),
|
|
156
|
+
h("span", null, r.converterLabel || r.converter || "未知转换器"),
|
|
157
|
+
h("span", { style: S.dim },
|
|
158
|
+
fmtBytes(r.markdownBytes) + " · 约 " + (r.lineCount || 0) + " 行 · " +
|
|
159
|
+
"约 " + (r.estimatedTokens || 0) + " tokens · " + ((r.elapsedMs || 0) / 1000).toFixed(1) + "s")
|
|
160
|
+
),
|
|
161
|
+
h("div", { style: S.mono }, r.markdownPath || ""),
|
|
162
|
+
r.preview ? h("pre", { style: S.log }, r.preview + (r.previewTruncated ? "\n…(预览已截断)" : "")) : null
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function Panel() {
|
|
167
|
+
var stStatus = useState(null);
|
|
168
|
+
var status = stStatus[0];
|
|
169
|
+
var setStatus = stStatus[1];
|
|
170
|
+
|
|
171
|
+
var stError = useState("");
|
|
172
|
+
var error = stError[0];
|
|
173
|
+
var setError = stError[1];
|
|
174
|
+
|
|
175
|
+
var stJob = useState(null);
|
|
176
|
+
var job = stJob[0];
|
|
177
|
+
var setJob = stJob[1];
|
|
178
|
+
|
|
179
|
+
var stProbe = useState(null);
|
|
180
|
+
var probe = stProbe[0];
|
|
181
|
+
var setProbe = stProbe[1];
|
|
182
|
+
|
|
183
|
+
var stBusy = useState(false);
|
|
184
|
+
var busy = stBusy[0];
|
|
185
|
+
var setBusy = stBusy[1];
|
|
186
|
+
|
|
187
|
+
var stToast = useState("");
|
|
188
|
+
var toast = stToast[0];
|
|
189
|
+
var setToast = stToast[1];
|
|
190
|
+
|
|
191
|
+
var stTarget = useState("");
|
|
192
|
+
var target = stTarget[0];
|
|
193
|
+
var setTarget = stTarget[1];
|
|
194
|
+
|
|
195
|
+
var stTestPath = useState("");
|
|
196
|
+
var testPath = stTestPath[0];
|
|
197
|
+
var setTestPath = stTestPath[1];
|
|
198
|
+
|
|
199
|
+
var stTestResult = useState(null);
|
|
200
|
+
var testResult = stTestResult[0];
|
|
201
|
+
var setTestResult = stTestResult[1];
|
|
202
|
+
|
|
203
|
+
var stTestBusy = useState(false);
|
|
204
|
+
var testBusy = stTestBusy[0];
|
|
205
|
+
var setTestBusy = stTestBusy[1];
|
|
206
|
+
|
|
207
|
+
var refresh = useCallback(function () {
|
|
208
|
+
getJSON("/status")
|
|
209
|
+
.then(function (d) {
|
|
210
|
+
if (!d || d.ok !== true) {
|
|
211
|
+
setError((d && d.error) || "无法读取插件状态");
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
setStatus(d);
|
|
215
|
+
setError("");
|
|
216
|
+
if (d.job) setJob(d.job);
|
|
217
|
+
})
|
|
218
|
+
.catch(function (x) {
|
|
219
|
+
setError("无法连接插件后台(" + messageOf(x) + "):请确认 dsh-plugin-office-markdown 的宿主半边已激活。");
|
|
220
|
+
});
|
|
221
|
+
}, []);
|
|
222
|
+
|
|
223
|
+
// `force` re-runs the whole probe on the host; without it the host answers
|
|
224
|
+
// from its cache (probeTtlMs, 10 minutes by default), because walking every
|
|
225
|
+
// interpreter costs several process spawns each.
|
|
226
|
+
var probeNow = useCallback(function (force) {
|
|
227
|
+
setBusy(true);
|
|
228
|
+
getJSON(force === true ? "/probe?force=1" : "/probe")
|
|
229
|
+
.then(function (d) {
|
|
230
|
+
if (!d || d.ok !== true) {
|
|
231
|
+
setError((d && d.error) || "检查本机环境失败");
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
setProbe(d);
|
|
235
|
+
setError("");
|
|
236
|
+
var list = (d.python && d.python.candidates) || [];
|
|
237
|
+
var best = null;
|
|
238
|
+
for (var i = 0; i < list.length; i += 1) {
|
|
239
|
+
if (list[i].usable && list[i].hasPip) { best = list[i]; break; }
|
|
240
|
+
}
|
|
241
|
+
if (best) setTarget(best.command);
|
|
242
|
+
})
|
|
243
|
+
.catch(function (x) {
|
|
244
|
+
setError("检查本机环境失败:" + messageOf(x));
|
|
245
|
+
})
|
|
246
|
+
.then(function () {
|
|
247
|
+
setBusy(false);
|
|
248
|
+
});
|
|
249
|
+
}, []);
|
|
250
|
+
|
|
251
|
+
var testConvert = useCallback(function () {
|
|
252
|
+
var p = String(testPath || "").trim();
|
|
253
|
+
if (!p) {
|
|
254
|
+
setTestResult({ ok: false, error: "请先填写要试转的文件的绝对路径。" });
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
setTestBusy(true);
|
|
258
|
+
setTestResult(null);
|
|
259
|
+
postJSON("/convert-test", { path: p })
|
|
260
|
+
.then(function (d) {
|
|
261
|
+
setTestResult(d || { ok: false, error: "后台没有返回结果" });
|
|
262
|
+
})
|
|
263
|
+
.catch(function (x) {
|
|
264
|
+
setTestResult({ ok: false, error: messageOf(x) });
|
|
265
|
+
})
|
|
266
|
+
.then(function () {
|
|
267
|
+
setTestBusy(false);
|
|
268
|
+
});
|
|
269
|
+
}, [testPath]);
|
|
270
|
+
|
|
271
|
+
useEffect(function () {
|
|
272
|
+
refresh();
|
|
273
|
+
probeNow();
|
|
274
|
+
}, [refresh, probeNow]);
|
|
275
|
+
|
|
276
|
+
// While a pip install/uninstall runs on the host, poll for its log.
|
|
277
|
+
var runningJobId = job && job.state === "running" ? job.id : "";
|
|
278
|
+
useEffect(function () {
|
|
279
|
+
if (!runningJobId) return undefined;
|
|
280
|
+
var stopped = false;
|
|
281
|
+
var timer = null;
|
|
282
|
+
function tick() {
|
|
283
|
+
getJSON("/job")
|
|
284
|
+
.then(function (d) {
|
|
285
|
+
if (stopped) return;
|
|
286
|
+
var j = d && d.job;
|
|
287
|
+
if (j) setJob(j);
|
|
288
|
+
if (j && j.state === "running") timer = setTimeout(tick, 1200);
|
|
289
|
+
else {
|
|
290
|
+
refresh();
|
|
291
|
+
probeNow();
|
|
292
|
+
}
|
|
293
|
+
})
|
|
294
|
+
.catch(function () {
|
|
295
|
+
if (!stopped) timer = setTimeout(tick, 2500);
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
timer = setTimeout(tick, 800);
|
|
299
|
+
return function () {
|
|
300
|
+
stopped = true;
|
|
301
|
+
if (timer) clearTimeout(timer);
|
|
302
|
+
};
|
|
303
|
+
}, [runningJobId, refresh, probeNow]);
|
|
304
|
+
|
|
305
|
+
function start(kind) {
|
|
306
|
+
setToast("");
|
|
307
|
+
var call = kind === "install" ? postJSON("/env/install", { target: target }) : postJSON("/env/uninstall", {});
|
|
308
|
+
call
|
|
309
|
+
.then(function (d) {
|
|
310
|
+
if (!d || d.ok !== true) {
|
|
311
|
+
setToast((d && d.error) || "启动失败");
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
setJob(d.job);
|
|
315
|
+
setToast(kind === "install" ? "已开始安装 MarkItDown,进度见下方日志。" : "已开始卸载插件配置的环境,进度见下方日志。");
|
|
316
|
+
})
|
|
317
|
+
.catch(function (x) {
|
|
318
|
+
setToast("启动失败:" + messageOf(x));
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
var converter = (status && status.converter) || null;
|
|
323
|
+
var usingFallback = !converter || converter.usingFallback;
|
|
324
|
+
var jobRunning = !!(job && job.state === "running");
|
|
325
|
+
var candidates = (probe && probe.python && probe.python.candidates) || [];
|
|
326
|
+
var snapshot = (probe && probe.snapshot) || (status && status.snapshot) || null;
|
|
327
|
+
|
|
328
|
+
return h(
|
|
329
|
+
"div",
|
|
330
|
+
{ style: S.box },
|
|
331
|
+
|
|
332
|
+
h(
|
|
333
|
+
"div",
|
|
334
|
+
{ style: S.head },
|
|
335
|
+
h("h3", { style: S.title }, "Office 转换(MarkItDown)"),
|
|
336
|
+
h("span", { style: S.dim }, "v" + ((status && status.plugin && status.plugin.version) || "?")),
|
|
337
|
+
h("span", { style: S.dim }, "工具 " + ((status && status.plugin && status.plugin.tool) || "read_office_as_markdown"))
|
|
338
|
+
),
|
|
339
|
+
h(
|
|
340
|
+
"div",
|
|
341
|
+
{ style: S.dim },
|
|
342
|
+
"把 .docx / .xlsx / .pptx / .pdf 先转成 Markdown 再读取,避免二进制内容直接塞进上下文。转换全部在本机完成,不修改原文件,不消耗 API 额度。"
|
|
343
|
+
),
|
|
344
|
+
|
|
345
|
+
// ---- current converter ------------------------------------------------
|
|
346
|
+
h(
|
|
347
|
+
"div",
|
|
348
|
+
{ style: S.card },
|
|
349
|
+
h(
|
|
350
|
+
"div",
|
|
351
|
+
{ style: S.cardHead },
|
|
352
|
+
h(
|
|
353
|
+
"div",
|
|
354
|
+
{ style: S.row },
|
|
355
|
+
h(
|
|
356
|
+
"span",
|
|
357
|
+
{ style: Object.assign({}, S.badge, { color: usingFallback ? COLOR.warn : COLOR.ok }) },
|
|
358
|
+
usingFallback ? "内置兜底" : "本机 MarkItDown"
|
|
359
|
+
),
|
|
360
|
+
h("span", null, converter ? converter.summary : "正在读取状态…")
|
|
361
|
+
),
|
|
362
|
+
h(
|
|
363
|
+
"button",
|
|
364
|
+
{ style: busy ? S.btnOff : S.btn, disabled: busy, onClick: refresh },
|
|
365
|
+
"刷新"
|
|
366
|
+
)
|
|
367
|
+
),
|
|
368
|
+
converter && converter.chain && converter.chain.length
|
|
369
|
+
? h(
|
|
370
|
+
"ul",
|
|
371
|
+
{ style: S.list },
|
|
372
|
+
converter.chain.map(function (c, i) {
|
|
373
|
+
return h(
|
|
374
|
+
"li",
|
|
375
|
+
{ key: "c" + i, style: S.li },
|
|
376
|
+
h("span", { style: Object.assign({}, S.badge, { color: c.fidelity === "limited" ? COLOR.warn : COLOR.ok }) }, c.fidelity === "limited" ? "保真度有限" : "保真度高"),
|
|
377
|
+
h("span", null, c.label)
|
|
378
|
+
);
|
|
379
|
+
})
|
|
380
|
+
)
|
|
381
|
+
: null,
|
|
382
|
+
converter && converter.notices && converter.notices.length
|
|
383
|
+
? h("div", { style: S.dim }, converter.notices.join(" · "))
|
|
384
|
+
: null
|
|
385
|
+
),
|
|
386
|
+
|
|
387
|
+
// ---- python environments ---------------------------------------------
|
|
388
|
+
h(
|
|
389
|
+
"div",
|
|
390
|
+
{ style: S.card },
|
|
391
|
+
h(
|
|
392
|
+
"div",
|
|
393
|
+
{ style: S.cardHead },
|
|
394
|
+
h("div", { style: S.row }, h("b", null, "Python 环境"), h("span", { style: S.dim }, "探测顺序 pythonPrefer: " + ((probe && probe.python && probe.python.prefer) || (converter && converter.pythonPrefer) || "auto"))),
|
|
395
|
+
h(
|
|
396
|
+
"div",
|
|
397
|
+
{ style: S.row },
|
|
398
|
+
h(
|
|
399
|
+
"button",
|
|
400
|
+
{ style: busy ? S.btnOff : S.btn, disabled: busy, onClick: function () { probeNow(false); } },
|
|
401
|
+
busy ? "检查中…" : "检查本机环境"
|
|
402
|
+
),
|
|
403
|
+
h(
|
|
404
|
+
"button",
|
|
405
|
+
{ style: busy ? S.btnOff : S.btn, disabled: busy, onClick: function () { probeNow(true); } },
|
|
406
|
+
"重新检查"
|
|
407
|
+
),
|
|
408
|
+
probe && probe.cached ? h("span", { style: S.dim }, "(缓存结果,点「重新检查」强制重探)") : null
|
|
409
|
+
)
|
|
410
|
+
),
|
|
411
|
+
h(
|
|
412
|
+
"div",
|
|
413
|
+
{ style: S.dim },
|
|
414
|
+
"插件本身不安装任何东西。列表里第一个已装 MarkItDown 的解释器就是当前使用中的那个;若一个都没有,插件改用内置兜底转换器(保真度有限)。"
|
|
415
|
+
),
|
|
416
|
+
candidates.length
|
|
417
|
+
? h(
|
|
418
|
+
"ul",
|
|
419
|
+
{ style: S.list },
|
|
420
|
+
candidates.map(function (c, i) {
|
|
421
|
+
var okc = c.hasMarkitdown;
|
|
422
|
+
var inUse = c.command === (probe.python && probe.python.active);
|
|
423
|
+
return h(
|
|
424
|
+
"li",
|
|
425
|
+
{ key: "p" + i, style: S.li },
|
|
426
|
+
h(
|
|
427
|
+
"span",
|
|
428
|
+
{ style: Object.assign({}, S.badge, { color: c.pending ? COLOR.warn : okc ? COLOR.ok : COLOR.dim }) },
|
|
429
|
+
c.pending ? "⏱ 未响应" : inUse ? "✔ 使用中" : okc ? "已安装" : c.usable ? "未安装" : "不可用"
|
|
430
|
+
),
|
|
431
|
+
h("span", { style: S.mono }, c.command),
|
|
432
|
+
h("span", { style: S.dim }, "[" + c.sourceLabel + "]"),
|
|
433
|
+
c.pythonVersion ? h("span", { style: S.dim }, c.pythonVersion) : null,
|
|
434
|
+
c.hasMarkitdown ? h("span", { style: S.dim }, "markitdown " + (c.markitdownVersion || "")) : null,
|
|
435
|
+
!okc && c.error ? h("span", { style: S.dim }, "— " + c.error) : null
|
|
436
|
+
);
|
|
437
|
+
})
|
|
438
|
+
)
|
|
439
|
+
: h("div", { style: S.dim }, "点击「检查本机环境」开始探测。"),
|
|
440
|
+
candidates.length
|
|
441
|
+
? h(
|
|
442
|
+
"div",
|
|
443
|
+
{ style: S.row },
|
|
444
|
+
h(
|
|
445
|
+
"label",
|
|
446
|
+
{ style: S.row },
|
|
447
|
+
h("span", { style: S.dim }, "安装到"),
|
|
448
|
+
h(
|
|
449
|
+
"select",
|
|
450
|
+
{
|
|
451
|
+
style: Object.assign({}, S.btn, { cursor: "pointer" }),
|
|
452
|
+
value: target,
|
|
453
|
+
onChange: function (e) {
|
|
454
|
+
setTarget(e.target.value);
|
|
455
|
+
}
|
|
456
|
+
},
|
|
457
|
+
candidates
|
|
458
|
+
.filter(function (c) {
|
|
459
|
+
return c.usable;
|
|
460
|
+
})
|
|
461
|
+
.map(function (c, i) {
|
|
462
|
+
return h(
|
|
463
|
+
"option",
|
|
464
|
+
{ key: "o" + i, value: c.command },
|
|
465
|
+
c.command + " [" + c.sourceLabel + "]" + (c.hasMarkitdown ? " 已安装" : c.hasPip ? "" : " 无 pip")
|
|
466
|
+
);
|
|
467
|
+
})
|
|
468
|
+
)
|
|
469
|
+
),
|
|
470
|
+
h(
|
|
471
|
+
"button",
|
|
472
|
+
{ style: jobRunning ? S.btnOff : S.btn, disabled: jobRunning, onClick: function () { start("install"); } },
|
|
473
|
+
"一键配置 MarkItDown 环境"
|
|
474
|
+
)
|
|
475
|
+
)
|
|
476
|
+
: null,
|
|
477
|
+
h("div", { style: S.dim }, "一键配置会在这个解释器里安装 markitdown,并记录下需要负责的包,供日后精确卸载。如果它已经有了 MarkItDown,会跳过安装、直接完成登记。")
|
|
478
|
+
),
|
|
479
|
+
|
|
480
|
+
// ---- managed environment ---------------------------------------------
|
|
481
|
+
h(
|
|
482
|
+
"div",
|
|
483
|
+
{ style: S.card },
|
|
484
|
+
h("div", { style: S.cardHead }, h("b", null, "插件配置的环境")),
|
|
485
|
+
snapshot && snapshot.present
|
|
486
|
+
? h(
|
|
487
|
+
"div",
|
|
488
|
+
null,
|
|
489
|
+
h(
|
|
490
|
+
"div",
|
|
491
|
+
{ style: S.row },
|
|
492
|
+
h("span", { style: Object.assign({}, S.badge, { color: COLOR.ok }) }, snapshot.adopted ? "已接管(原本已安装)" : "由本插件安装"),
|
|
493
|
+
h("span", { style: S.mono }, snapshot.python || "")
|
|
494
|
+
),
|
|
495
|
+
h("div", { style: S.dim }, (snapshot.adopted ? "登记时间 " : "安装时间 ") + (snapshot.installedAt || "未知") + " · 可卸载 " + ((snapshot.added || []).length) + " 个包"),
|
|
496
|
+
snapshot.keep && snapshot.keep.length
|
|
497
|
+
? h("div", { style: S.dim }, "另有 " + snapshot.keep.length + " 个包被 DSH 运行时或其它组件共用,不会卸载。")
|
|
498
|
+
: null,
|
|
499
|
+
h("div", { style: S.dim }, "记录文件:" + (snapshot.path || "~/.dsh/dsh-plugin-office-markdown.env.json")),
|
|
500
|
+
h(
|
|
501
|
+
"div",
|
|
502
|
+
{ style: S.row },
|
|
503
|
+
h(
|
|
504
|
+
"button",
|
|
505
|
+
{ style: jobRunning ? S.btnOff : S.btn, disabled: jobRunning, onClick: function () { start("uninstall"); } },
|
|
506
|
+
"卸载插件配置的环境"
|
|
507
|
+
)
|
|
508
|
+
),
|
|
509
|
+
h("div", { style: S.dim }, "只会卸载记录里属于 MarkItDown 的包;DSH 运行时自带的包(python-docx / openpyxl 等)和你原有的其它 Python 包一个都不会动。"),
|
|
510
|
+
h("div", { style: S.dim }, "在 DSH 里卸载本插件时,这一步会自动完成。")
|
|
511
|
+
)
|
|
512
|
+
: h(
|
|
513
|
+
"div",
|
|
514
|
+
null,
|
|
515
|
+
h("div", { style: S.row }, h("span", { style: Object.assign({}, S.badge, { color: COLOR.dim }) }, "未登记"), h("span", null, "MarkItDown 不是由本插件配置的")),
|
|
516
|
+
h("div", { style: S.dim }, "点上面的「一键配置」可以把当前解释器登记到插件名下;登记后,在 DSH 里卸载本插件时会一并移除 MarkItDown 与它的依赖。"),
|
|
517
|
+
h("div", { style: S.dim }, "未经登记的 MarkItDown 不会被卸载,你自己装的 Python 包也始终原样保留。")
|
|
518
|
+
)
|
|
519
|
+
),
|
|
520
|
+
|
|
521
|
+
// ---- test conversion --------------------------------------------------
|
|
522
|
+
h(
|
|
523
|
+
"div",
|
|
524
|
+
{ style: S.card },
|
|
525
|
+
h(
|
|
526
|
+
"div",
|
|
527
|
+
{ style: S.cardHead },
|
|
528
|
+
h("b", null, "试转一个文件"),
|
|
529
|
+
h("span", { style: S.dim }, "不经过模型,直接在本机跑一遍完整的转换链")
|
|
530
|
+
),
|
|
531
|
+
h(
|
|
532
|
+
"div",
|
|
533
|
+
{ style: S.row },
|
|
534
|
+
h("input", {
|
|
535
|
+
style: Object.assign({}, S.mono, {
|
|
536
|
+
flex: "1 1 420px",
|
|
537
|
+
minWidth: 240,
|
|
538
|
+
padding: "5px 8px",
|
|
539
|
+
borderRadius: 6,
|
|
540
|
+
border: "1px solid " + COLOR.line,
|
|
541
|
+
background: "transparent",
|
|
542
|
+
color: "inherit",
|
|
543
|
+
font: "inherit"
|
|
544
|
+
}),
|
|
545
|
+
placeholder: "绝对路径,例如 D:\\work\\报表.xlsx",
|
|
546
|
+
value: testPath,
|
|
547
|
+
onChange: function (e) { setTestPath(e.target.value); },
|
|
548
|
+
onKeyDown: function (e) { if (e.key === "Enter") testConvert(); }
|
|
549
|
+
}),
|
|
550
|
+
h("button", { style: testBusy ? S.btnOff : S.btn, disabled: testBusy, onClick: testConvert }, testBusy ? "转换中…" : "试转")
|
|
551
|
+
),
|
|
552
|
+
h(
|
|
553
|
+
"div",
|
|
554
|
+
{ style: S.dim },
|
|
555
|
+
"用来确认「换了这台机器还好不好使」:走的是和工具完全相同的转换链,但不走缓存。产物固定叫 <文件名>-test.md," +
|
|
556
|
+
"放在源文件旁边(配了 tmpDir 就放那里),每次覆盖、不会堆积,也不会被工具误当成缓存结果。"
|
|
557
|
+
),
|
|
558
|
+
testResult ? renderTest(testResult) : null
|
|
559
|
+
),
|
|
560
|
+
|
|
561
|
+
// ---- job / messages ---------------------------------------------------
|
|
562
|
+
job && job.log && job.log.length
|
|
563
|
+
? h(
|
|
564
|
+
"div",
|
|
565
|
+
{ style: S.card },
|
|
566
|
+
h("div", { style: S.cardHead }, h("b", null, job.kind === "install" ? "安装进度" : "卸载进度"), h("span", { style: Object.assign({}, S.badge, { color: job.state === "failed" ? COLOR.bad : job.state === "done" ? COLOR.ok : COLOR.warn }) }, job.state === "running" ? "进行中" : job.state === "done" ? "已完成" : "失败")),
|
|
567
|
+
h("pre", { style: S.log }, job.log.join("\n")),
|
|
568
|
+
job.error ? h("div", { style: { color: COLOR.bad } }, job.error) : null
|
|
569
|
+
)
|
|
570
|
+
: null,
|
|
571
|
+
|
|
572
|
+
toast ? h("div", { style: S.dim }, toast) : null,
|
|
573
|
+
error ? h("div", { style: { color: COLOR.bad } }, error) : null
|
|
574
|
+
);
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
function apply(ctx) {
|
|
578
|
+
ctx.slots.inject("settings.section", function () {
|
|
579
|
+
ctx.slots.register(
|
|
580
|
+
{ name: "settings.section", id: "office-markdown", order: 450, label: "Office 转换" },
|
|
581
|
+
function () {
|
|
582
|
+
return h(Panel);
|
|
583
|
+
}
|
|
584
|
+
);
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
module.exports = { apply: apply, inject: inject };
|
|
589
|
+
return module.exports; } });
|