weread-export 0.1.7 → 0.1.9
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/lib/client.js +362 -276
- package/lib/client.js.map +1 -1
- package/lib/index.js +106 -1
- package/lib/types/client/api.d.ts +10 -0
- package/lib/types/routes.d.ts +10 -0
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -65,7 +65,27 @@ window.__ModuleLoader__.load({
|
|
|
65
65
|
}
|
|
66
66
|
/** Open the host OS folder chooser; resolves the picked absolute path. */
|
|
67
67
|
async pickDir() {
|
|
68
|
-
return request("/api/weread-export/pick-dir", {
|
|
68
|
+
return request("/api/weread-export/pick-dir", {
|
|
69
|
+
method: "POST",
|
|
70
|
+
headers: { "Content-Type": "application/json" },
|
|
71
|
+
body: JSON.stringify({})
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
/** Send a test memo to flomo to verify the shared credential. */
|
|
75
|
+
async testFlomo() {
|
|
76
|
+
return request("/api/weread-export/test-flomo", {
|
|
77
|
+
method: "POST",
|
|
78
|
+
headers: { "Content-Type": "application/json" },
|
|
79
|
+
body: JSON.stringify({})
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
/** Verify the Notion token and target page accessibility. */
|
|
83
|
+
async testNotion() {
|
|
84
|
+
return request("/api/weread-export/test-notion", {
|
|
85
|
+
method: "POST",
|
|
86
|
+
headers: { "Content-Type": "application/json" },
|
|
87
|
+
body: JSON.stringify({})
|
|
88
|
+
});
|
|
69
89
|
}
|
|
70
90
|
async exportFlomo(bookId, tag, limit = 20) {
|
|
71
91
|
return request("/api/weread-export/flomo", {
|
|
@@ -91,11 +111,13 @@ window.__ModuleLoader__.load({
|
|
|
91
111
|
//#region src/client/WereadSettingsPanel.tsx
|
|
92
112
|
/**
|
|
93
113
|
* WeRead settings panel — rendered inside the web settings page
|
|
94
|
-
* (settings.section entry).
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
114
|
+
* (settings.section entry).
|
|
115
|
+
*
|
|
116
|
+
* Layout: each export target configures its own parameters in its own card
|
|
117
|
+
* (flomo: tag + URL/key; Notion: token + target page; local: directory),
|
|
118
|
+
* and the quick-export card only picks a destination (flomo / Notion /
|
|
119
|
+
* local / all) — no "default vs this-run" duplication. Plain React, inline
|
|
120
|
+
* styles only.
|
|
99
121
|
*/
|
|
100
122
|
/** Module-level API client (stateless; the component closes over it). */
|
|
101
123
|
const api = new WereadApi();
|
|
@@ -117,7 +139,6 @@ window.__ModuleLoader__.load({
|
|
|
117
139
|
fontSize: "13px",
|
|
118
140
|
margin: 0
|
|
119
141
|
},
|
|
120
|
-
/** One visually separated group card. */
|
|
121
142
|
group: {
|
|
122
143
|
display: "flex",
|
|
123
144
|
flexDirection: "column",
|
|
@@ -137,14 +158,6 @@ window.__ModuleLoader__.load({
|
|
|
137
158
|
paddingBottom: "6px",
|
|
138
159
|
borderBottom: "1px solid rgba(128,128,128,0.18)"
|
|
139
160
|
},
|
|
140
|
-
subTitle: {
|
|
141
|
-
fontWeight: 600,
|
|
142
|
-
fontSize: "12px",
|
|
143
|
-
opacity: .9,
|
|
144
|
-
margin: "4px 0 0",
|
|
145
|
-
paddingTop: "8px",
|
|
146
|
-
borderTop: "1px dashed rgba(128,128,128,0.25)"
|
|
147
|
-
},
|
|
148
161
|
status: {
|
|
149
162
|
fontSize: "12px",
|
|
150
163
|
opacity: .85,
|
|
@@ -237,23 +250,21 @@ window.__ModuleLoader__.load({
|
|
|
237
250
|
/** Status line for the current config view. */
|
|
238
251
|
function statusText(view) {
|
|
239
252
|
if (view === null) return "加载中…";
|
|
240
|
-
if (!view.configured) return "未配置 — 打开 https://weread.qq.com/r/weread-skills,用微信读书账号登录后点击「创建 Key」并复制(wrk-
|
|
241
|
-
|
|
242
|
-
return "已配置 · Key " + view.apiKeyMasked + "\n默认导出目标 " + destLabel + " · 导出条数 " + (view.exportLimit === 0 ? "全部" : view.exportLimit + " 条") + " · 默认标签 #" + view.defaultFlomoTag + "\nprompt 处理 " + (view.usePrompt ? "开(" + (view.llmConfigured ? view.llmModel + " @ " + view.llmBaseUrl : "LLM 未配置") + ")" : "关") + " · Notion " + (view.notionConfigured ? "已配置" + (view.notionTargetPageId !== "" ? " + 目标页" : "(未填目标页)") : "未配置") + " · flomo " + (view.flomoConfigured ? "已配置(" + view.flomoSource + " " + view.flomoMasked + ")" : "未配置") + " · 缓存书架 " + view.cachedShelfBooks + " 本 / 有笔记 " + view.cachedNoteBooks + " 本" + (view.lastSyncAt !== "" ? " · 最近同步 " + view.lastSyncAt : "");
|
|
253
|
+
if (!view.configured) return "未配置 — 打开 https://weread.qq.com/r/weread-skills,用微信读书账号登录后点击「创建 Key」并复制(wrk- 开头),粘贴到「① 连接」卡片。";
|
|
254
|
+
return "已配置 · Key " + view.apiKeyMasked + " · 导出条数 " + (view.exportLimit === 0 ? "全部" : view.exportLimit + " 条") + "\nflomo " + (view.flomoConfigured ? "已配置(" + view.flomoSource + " " + view.flomoMasked + ")" : "未配置") + " · Notion " + (view.notionConfigured ? "已配置" + (view.notionTargetPageId !== "" ? " + 目标页" : "(未填目标页)") : "未配置") + " · 本地 " + (view.localExportDir !== "" ? "已配置" : "未配置") + " · prompt " + (view.usePrompt ? "开(" + (view.llmConfigured ? view.llmModel : "LLM 未配置") + ")" : "关") + " · 缓存书架 " + view.cachedShelfBooks + " 本 / 有笔记 " + view.cachedNoteBooks + " 本";
|
|
243
255
|
}
|
|
244
256
|
/** The WeRead settings panel component. */
|
|
245
257
|
function WereadSettingsPanel() {
|
|
246
258
|
const [view, setView] = (0, react.useState)(null);
|
|
247
259
|
const [apiKey, setApiKey] = (0, react.useState)("");
|
|
248
|
-
const [defaultFlomoTag, setDefaultFlomoTag] = (0, react.useState)("");
|
|
249
260
|
const [limitChoice, setLimitChoice] = (0, react.useState)("20");
|
|
250
261
|
const [customLimit, setCustomLimit] = (0, react.useState)("");
|
|
251
|
-
const [
|
|
252
|
-
const [localExportDir, setLocalExportDir] = (0, react.useState)("");
|
|
253
|
-
const [notionToken, setNotionToken] = (0, react.useState)("");
|
|
262
|
+
const [defaultFlomoTag, setDefaultFlomoTag] = (0, react.useState)("");
|
|
254
263
|
const [flomoWebhookUrl, setFlomoWebhookUrl] = (0, react.useState)("");
|
|
255
264
|
const [flomoApiKey, setFlomoApiKey] = (0, react.useState)("");
|
|
265
|
+
const [notionToken, setNotionToken] = (0, react.useState)("");
|
|
256
266
|
const [notionTargetPageId, setNotionTargetPageId] = (0, react.useState)("");
|
|
267
|
+
const [localExportDir, setLocalExportDir] = (0, react.useState)("");
|
|
257
268
|
const [usePrompt, setUsePrompt] = (0, react.useState)(false);
|
|
258
269
|
const [llmBaseUrl, setLlmBaseUrl] = (0, react.useState)("");
|
|
259
270
|
const [llmApiKey, setLlmApiKey] = (0, react.useState)("");
|
|
@@ -263,9 +274,7 @@ window.__ModuleLoader__.load({
|
|
|
263
274
|
const [message, setMessage] = (0, react.useState)("");
|
|
264
275
|
const [books, setBooks] = (0, react.useState)([]);
|
|
265
276
|
const [bookId, setBookId] = (0, react.useState)("");
|
|
266
|
-
const [destNow, setDestNow] = (0, react.useState)("");
|
|
267
|
-
const [localDirNow, setLocalDirNow] = (0, react.useState)("");
|
|
268
|
-
const [flomoTagNow, setFlomoTagNow] = (0, react.useState)("");
|
|
277
|
+
const [destNow, setDestNow] = (0, react.useState)("flomo");
|
|
269
278
|
const [usePromptNow, setUsePromptNow] = (0, react.useState)(null);
|
|
270
279
|
/** Map the persisted exportLimit to the choice selector. */
|
|
271
280
|
const limitFromView = (value) => {
|
|
@@ -282,7 +291,6 @@ window.__ModuleLoader__.load({
|
|
|
282
291
|
const next = await api.getStatus();
|
|
283
292
|
setView(next);
|
|
284
293
|
setDefaultFlomoTag((prev) => prev === "" ? next.defaultFlomoTag : prev);
|
|
285
|
-
setFlomoTagNow((prev) => prev === "" ? next.defaultFlomoTag : prev);
|
|
286
294
|
setLimitChoice(limitFromView(next.exportLimit));
|
|
287
295
|
if (![
|
|
288
296
|
0,
|
|
@@ -290,9 +298,8 @@ window.__ModuleLoader__.load({
|
|
|
290
298
|
50,
|
|
291
299
|
100
|
|
292
300
|
].includes(next.exportLimit)) setCustomLimit(String(next.exportLimit));
|
|
293
|
-
setExportDest(next.exportDest);
|
|
294
|
-
setLocalExportDir(next.localExportDir);
|
|
295
301
|
setNotionTargetPageId(next.notionTargetPageId);
|
|
302
|
+
setLocalExportDir(next.localExportDir);
|
|
296
303
|
setUsePrompt(next.usePrompt);
|
|
297
304
|
setLlmBaseUrl(next.llmBaseUrl);
|
|
298
305
|
setLlmModel(next.llmModel);
|
|
@@ -324,69 +331,116 @@ window.__ModuleLoader__.load({
|
|
|
324
331
|
}
|
|
325
332
|
return Number(limitChoice) || 0;
|
|
326
333
|
};
|
|
327
|
-
const
|
|
334
|
+
const afterSave = async (okMessage) => {
|
|
335
|
+
setView(await api.getStatus());
|
|
336
|
+
setMessage(okMessage);
|
|
337
|
+
setApiKey("");
|
|
338
|
+
setFlomoWebhookUrl("");
|
|
339
|
+
setFlomoApiKey("");
|
|
340
|
+
setNotionToken("");
|
|
341
|
+
setLlmApiKey("");
|
|
342
|
+
};
|
|
343
|
+
/** ① connection: API key + export limit. */
|
|
344
|
+
const saveMainConfig = async () => {
|
|
328
345
|
setBusy(true);
|
|
329
346
|
setMessage("");
|
|
330
347
|
try {
|
|
331
348
|
const patch = {};
|
|
332
349
|
if (apiKey.trim() !== "") patch.apiKey = apiKey.trim();
|
|
333
|
-
if (defaultFlomoTag.trim() !== "") patch.defaultFlomoTag = defaultFlomoTag.trim();
|
|
334
350
|
patch.exportLimit = resolveExportLimit();
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
351
|
+
const next = await api.setConfig(patch);
|
|
352
|
+
setMessage(next.configured ? "配置已保存。" : "配置未保存完整:缺少 API Key。");
|
|
353
|
+
await afterSave("");
|
|
354
|
+
} catch (error) {
|
|
355
|
+
setMessage("保存失败: " + String(error instanceof Error ? error.message : error));
|
|
356
|
+
} finally {
|
|
357
|
+
setBusy(false);
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
/** ② flomo: tag + URL / key. */
|
|
361
|
+
const saveFlomoConfig = async () => {
|
|
362
|
+
setBusy(true);
|
|
363
|
+
setMessage("");
|
|
364
|
+
try {
|
|
365
|
+
const patch = {};
|
|
366
|
+
if (defaultFlomoTag.trim() !== "") patch.defaultFlomoTag = defaultFlomoTag.trim();
|
|
339
367
|
if (flomoWebhookUrl.trim() !== "") patch.flomoWebhookUrl = flomoWebhookUrl.trim();
|
|
340
368
|
if (flomoApiKey.trim() !== "") patch.flomoApiKey = flomoApiKey.trim();
|
|
341
|
-
patch
|
|
369
|
+
await api.setConfig(patch);
|
|
370
|
+
await afterSave("flomo 配置已保存。");
|
|
371
|
+
} catch (error) {
|
|
372
|
+
setMessage("保存失败: " + String(error instanceof Error ? error.message : error));
|
|
373
|
+
} finally {
|
|
374
|
+
setBusy(false);
|
|
375
|
+
}
|
|
376
|
+
};
|
|
377
|
+
/** ③ notion: token + target page. */
|
|
378
|
+
const saveNotionConfig = async () => {
|
|
379
|
+
setBusy(true);
|
|
380
|
+
setMessage("");
|
|
381
|
+
try {
|
|
382
|
+
const patch = {};
|
|
383
|
+
if (notionToken.trim() !== "") patch.notionToken = notionToken.trim();
|
|
384
|
+
if (notionTargetPageId.trim() !== "") patch.notionTargetPageId = notionTargetPageId.trim();
|
|
385
|
+
await api.setConfig(patch);
|
|
386
|
+
await afterSave("Notion 配置已保存。");
|
|
387
|
+
} catch (error) {
|
|
388
|
+
setMessage("保存失败: " + String(error instanceof Error ? error.message : error));
|
|
389
|
+
} finally {
|
|
390
|
+
setBusy(false);
|
|
391
|
+
}
|
|
392
|
+
};
|
|
393
|
+
/** ④ local: export directory. */
|
|
394
|
+
const saveLocalConfig = async () => {
|
|
395
|
+
setBusy(true);
|
|
396
|
+
setMessage("");
|
|
397
|
+
try {
|
|
398
|
+
const patch = {};
|
|
399
|
+
if (localExportDir.trim() !== "") patch.localExportDir = localExportDir.trim();
|
|
400
|
+
await api.setConfig(patch);
|
|
401
|
+
await afterSave("本地导出目录已保存。");
|
|
402
|
+
} catch (error) {
|
|
403
|
+
setMessage("保存失败: " + String(error instanceof Error ? error.message : error));
|
|
404
|
+
} finally {
|
|
405
|
+
setBusy(false);
|
|
406
|
+
}
|
|
407
|
+
};
|
|
408
|
+
/** ⑤ AI: prompt toggle + LLM config + template. */
|
|
409
|
+
const saveAIConfig = async () => {
|
|
410
|
+
setBusy(true);
|
|
411
|
+
setMessage("");
|
|
412
|
+
try {
|
|
413
|
+
const patch = { usePrompt };
|
|
342
414
|
if (llmBaseUrl.trim() !== "") patch.llmBaseUrl = llmBaseUrl.trim();
|
|
343
415
|
if (llmApiKey.trim() !== "") patch.llmApiKey = llmApiKey.trim();
|
|
344
416
|
if (llmModel.trim() !== "") patch.llmModel = llmModel.trim();
|
|
345
417
|
if (exportPrompt.trim() !== "") patch.exportPrompt = exportPrompt;
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
setView(status);
|
|
349
|
-
setMessage(next.configured ? "配置已保存。" : "配置未保存完整:缺少 API Key。");
|
|
350
|
-
setApiKey("");
|
|
351
|
-
setNotionToken("");
|
|
352
|
-
setLlmApiKey("");
|
|
353
|
-
setFlomoWebhookUrl("");
|
|
354
|
-
setFlomoApiKey("");
|
|
418
|
+
await api.setConfig(patch);
|
|
419
|
+
await afterSave("AI 配置已保存。");
|
|
355
420
|
} catch (error) {
|
|
356
421
|
setMessage("保存失败: " + String(error instanceof Error ? error.message : error));
|
|
357
422
|
} finally {
|
|
358
423
|
setBusy(false);
|
|
359
424
|
}
|
|
360
425
|
};
|
|
361
|
-
const
|
|
426
|
+
const clearConfig = async () => {
|
|
362
427
|
setBusy(true);
|
|
363
428
|
setMessage("");
|
|
364
429
|
try {
|
|
365
|
-
await api.setConfig({
|
|
366
|
-
|
|
367
|
-
setMessage("已清除 flomo 配置。");
|
|
430
|
+
await api.setConfig({ reset: true });
|
|
431
|
+
await afterSave("已清除全部配置。");
|
|
368
432
|
} catch (error) {
|
|
369
433
|
setMessage("清除失败: " + String(error instanceof Error ? error.message : error));
|
|
370
434
|
} finally {
|
|
371
435
|
setBusy(false);
|
|
372
436
|
}
|
|
373
437
|
};
|
|
374
|
-
const
|
|
438
|
+
const clearFlomoConfig = async () => {
|
|
375
439
|
setBusy(true);
|
|
376
440
|
setMessage("");
|
|
377
441
|
try {
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
...next,
|
|
381
|
-
flomoConfigured: view?.flomoConfigured ?? false,
|
|
382
|
-
flomoSource: view?.flomoSource ?? "",
|
|
383
|
-
flomoMasked: view?.flomoMasked ?? "",
|
|
384
|
-
flomoConfigPath: view?.flomoConfigPath ?? "",
|
|
385
|
-
cachedShelfBooks: view?.cachedShelfBooks ?? 0,
|
|
386
|
-
cachedNoteBooks: view?.cachedNoteBooks ?? 0,
|
|
387
|
-
cacheUpdatedAt: view?.cacheUpdatedAt ?? ""
|
|
388
|
-
});
|
|
389
|
-
setMessage("已清除配置。");
|
|
442
|
+
await api.setConfig({ flomoReset: true });
|
|
443
|
+
await afterSave("已清除 flomo 配置。");
|
|
390
444
|
} catch (error) {
|
|
391
445
|
setMessage("清除失败: " + String(error instanceof Error ? error.message : error));
|
|
392
446
|
} finally {
|
|
@@ -405,6 +459,50 @@ window.__ModuleLoader__.load({
|
|
|
405
459
|
setBusy(false);
|
|
406
460
|
}
|
|
407
461
|
};
|
|
462
|
+
/** Test flomo: saves any unsaved fields first, then sends a test memo. */
|
|
463
|
+
const testFlomoConfig = async () => {
|
|
464
|
+
setBusy(true);
|
|
465
|
+
setMessage("测试中…(会向 flomo 发送一条测试 MEMO)");
|
|
466
|
+
try {
|
|
467
|
+
if (defaultFlomoTag.trim() !== "" || flomoWebhookUrl.trim() !== "" || flomoApiKey.trim() !== "") {
|
|
468
|
+
const patch = {};
|
|
469
|
+
if (defaultFlomoTag.trim() !== "") patch.defaultFlomoTag = defaultFlomoTag.trim();
|
|
470
|
+
if (flomoWebhookUrl.trim() !== "") patch.flomoWebhookUrl = flomoWebhookUrl.trim();
|
|
471
|
+
if (flomoApiKey.trim() !== "") patch.flomoApiKey = flomoApiKey.trim();
|
|
472
|
+
await api.setConfig(patch);
|
|
473
|
+
setFlomoWebhookUrl("");
|
|
474
|
+
setFlomoApiKey("");
|
|
475
|
+
setView(await api.getStatus());
|
|
476
|
+
}
|
|
477
|
+
const result = await api.testFlomo();
|
|
478
|
+
setMessage(result.ok ? "✅ " + result.message : "测试失败: " + result.message);
|
|
479
|
+
} catch (error) {
|
|
480
|
+
setMessage("测试失败: " + String(error instanceof Error ? error.message : error));
|
|
481
|
+
} finally {
|
|
482
|
+
setBusy(false);
|
|
483
|
+
}
|
|
484
|
+
};
|
|
485
|
+
/** Test Notion: saves any unsaved fields first, then verifies token + page. */
|
|
486
|
+
const testNotionConfig = async () => {
|
|
487
|
+
setBusy(true);
|
|
488
|
+
setMessage("测试中…");
|
|
489
|
+
try {
|
|
490
|
+
if (notionToken.trim() !== "" || notionTargetPageId.trim() !== "") {
|
|
491
|
+
const patch = {};
|
|
492
|
+
if (notionToken.trim() !== "") patch.notionToken = notionToken.trim();
|
|
493
|
+
if (notionTargetPageId.trim() !== "") patch.notionTargetPageId = notionTargetPageId.trim();
|
|
494
|
+
await api.setConfig(patch);
|
|
495
|
+
setNotionToken("");
|
|
496
|
+
setView(await api.getStatus());
|
|
497
|
+
}
|
|
498
|
+
const result = await api.testNotion();
|
|
499
|
+
setMessage(result.ok ? "✅ " + result.message : "测试失败: " + result.message);
|
|
500
|
+
} catch (error) {
|
|
501
|
+
setMessage("测试失败: " + String(error instanceof Error ? error.message : error));
|
|
502
|
+
} finally {
|
|
503
|
+
setBusy(false);
|
|
504
|
+
}
|
|
505
|
+
};
|
|
408
506
|
/** Open the host OS folder chooser and apply the picked path. */
|
|
409
507
|
const pickFolder = async (apply) => {
|
|
410
508
|
setBusy(true);
|
|
@@ -441,11 +539,12 @@ window.__ModuleLoader__.load({
|
|
|
441
539
|
setMessage("请先同步以加载书籍列表。");
|
|
442
540
|
return;
|
|
443
541
|
}
|
|
444
|
-
const dest = destNow ||
|
|
445
|
-
if (dest === "local" &&
|
|
446
|
-
setMessage("
|
|
542
|
+
const dest = destNow || "flomo";
|
|
543
|
+
if (dest === "local" && (view?.localExportDir ?? "") === "") {
|
|
544
|
+
setMessage("本地导出需要先在「④ 本地导出」配置导出目录。");
|
|
447
545
|
return;
|
|
448
546
|
}
|
|
547
|
+
if (dest === "all" && (view?.localExportDir ?? "") === "") setMessage("全选导出包含本地目标,但「④ 本地导出」未配置目录;本地部分将跳过(其余目标照常导出)。");
|
|
449
548
|
setBusy(true);
|
|
450
549
|
setMessage("导出中…");
|
|
451
550
|
try {
|
|
@@ -453,12 +552,6 @@ window.__ModuleLoader__.load({
|
|
|
453
552
|
bookId,
|
|
454
553
|
dest
|
|
455
554
|
};
|
|
456
|
-
if (dest === "local") body.localDir = localDirNow.trim();
|
|
457
|
-
if (dest === "flomo" && flomoTagNow.trim() !== "") body.tag = flomoTagNow.trim();
|
|
458
|
-
if (dest === "all") {
|
|
459
|
-
if (localDirNow.trim() !== "") body.localDir = localDirNow.trim();
|
|
460
|
-
if (flomoTagNow.trim() !== "") body.tag = flomoTagNow.trim();
|
|
461
|
-
}
|
|
462
555
|
if (usePromptNow !== null) body.usePrompt = usePromptNow;
|
|
463
556
|
const result = await api.exportData(body);
|
|
464
557
|
setMessage(result.ok ? result.message : "导出失败: " + result.message);
|
|
@@ -484,13 +577,13 @@ window.__ModuleLoader__.load({
|
|
|
484
577
|
children: [
|
|
485
578
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
486
579
|
style: s.groupTitle,
|
|
487
|
-
children: "①
|
|
580
|
+
children: "① 连接"
|
|
488
581
|
}),
|
|
489
582
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
490
583
|
style: s.row,
|
|
491
584
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
492
585
|
style: s.input,
|
|
493
|
-
placeholder: "Skills API Key(wrk- 开头)",
|
|
586
|
+
placeholder: "微信读书 Skills API Key(wrk- 开头)",
|
|
494
587
|
value: apiKey,
|
|
495
588
|
onChange: (e) => setApiKey(e.target.value)
|
|
496
589
|
})
|
|
@@ -498,19 +591,6 @@ window.__ModuleLoader__.load({
|
|
|
498
591
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
499
592
|
style: s.row,
|
|
500
593
|
children: [
|
|
501
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
502
|
-
style: s.label,
|
|
503
|
-
children: "默认导出标签"
|
|
504
|
-
}),
|
|
505
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
506
|
-
style: {
|
|
507
|
-
...s.input,
|
|
508
|
-
width: "130px"
|
|
509
|
-
},
|
|
510
|
-
placeholder: "如 读书笔记",
|
|
511
|
-
value: defaultFlomoTag,
|
|
512
|
-
onChange: (e) => setDefaultFlomoTag(e.target.value)
|
|
513
|
-
}),
|
|
514
594
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
515
595
|
style: s.label,
|
|
516
596
|
children: "导出条数"
|
|
@@ -554,184 +634,13 @@ window.__ModuleLoader__.load({
|
|
|
554
634
|
placeholder: "条数",
|
|
555
635
|
value: customLimit,
|
|
556
636
|
onChange: (e) => setCustomLimit(e.target.value)
|
|
557
|
-
})
|
|
558
|
-
]
|
|
559
|
-
}),
|
|
560
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
561
|
-
style: s.row,
|
|
562
|
-
children: [
|
|
563
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
564
|
-
style: s.label,
|
|
565
|
-
children: "默认目标"
|
|
566
|
-
}),
|
|
567
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
568
|
-
style: {
|
|
569
|
-
...s.select,
|
|
570
|
-
flex: 0,
|
|
571
|
-
minWidth: "130px"
|
|
572
|
-
},
|
|
573
|
-
value: exportDest,
|
|
574
|
-
onChange: (e) => setExportDest(e.target.value),
|
|
575
|
-
children: [
|
|
576
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
577
|
-
value: "flomo",
|
|
578
|
-
children: "flomo"
|
|
579
|
-
}),
|
|
580
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
581
|
-
value: "local",
|
|
582
|
-
children: "本地文件"
|
|
583
|
-
}),
|
|
584
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
585
|
-
value: "notion",
|
|
586
|
-
children: "Notion"
|
|
587
|
-
}),
|
|
588
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
589
|
-
value: "all",
|
|
590
|
-
children: "全选(flomo+本地+Notion)"
|
|
591
|
-
})
|
|
592
|
-
]
|
|
593
|
-
}),
|
|
594
|
-
(exportDest === "local" || exportDest === "all") && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
595
|
-
style: s.input,
|
|
596
|
-
placeholder: "本地导出目录(绝对路径,可留空导出时填)",
|
|
597
|
-
value: localExportDir,
|
|
598
|
-
onChange: (e) => setLocalExportDir(e.target.value)
|
|
599
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
600
|
-
style: s.button,
|
|
601
|
-
onClick: () => void pickFolder((p) => setLocalExportDir(p)),
|
|
602
|
-
disabled: busy,
|
|
603
|
-
children: "选择文件夹…"
|
|
604
|
-
})] })
|
|
605
|
-
]
|
|
606
|
-
}),
|
|
607
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
608
|
-
style: s.subTitle,
|
|
609
|
-
children: "快捷导出"
|
|
610
|
-
}),
|
|
611
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
612
|
-
style: s.row,
|
|
613
|
-
children: [
|
|
614
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
615
|
-
style: s.label,
|
|
616
|
-
children: "选择书籍"
|
|
617
|
-
}),
|
|
618
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
|
|
619
|
-
style: s.select,
|
|
620
|
-
value: bookId,
|
|
621
|
-
onChange: (e) => setBookId(e.target.value),
|
|
622
|
-
disabled: books.length === 0,
|
|
623
|
-
children: books.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
624
|
-
value: "",
|
|
625
|
-
children: "(缓存无书籍,先同步)"
|
|
626
|
-
}) : books.map((b) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
|
|
627
|
-
value: b.bookId,
|
|
628
|
-
children: [
|
|
629
|
-
"《",
|
|
630
|
-
b.title,
|
|
631
|
-
"》",
|
|
632
|
-
b.author !== "" ? " · " + b.author : ""
|
|
633
|
-
]
|
|
634
|
-
}, b.bookId))
|
|
635
|
-
}),
|
|
636
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
637
|
-
style: s.button,
|
|
638
|
-
onClick: () => void runSync(),
|
|
639
|
-
disabled: busy || !view?.configured,
|
|
640
|
-
children: "同步书架/笔记"
|
|
641
|
-
})
|
|
642
|
-
]
|
|
643
|
-
}),
|
|
644
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
645
|
-
style: s.row,
|
|
646
|
-
children: [
|
|
647
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
648
|
-
style: s.label,
|
|
649
|
-
children: "本次目标"
|
|
650
|
-
}),
|
|
651
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
652
|
-
style: {
|
|
653
|
-
...s.select,
|
|
654
|
-
flex: 0,
|
|
655
|
-
minWidth: "130px"
|
|
656
|
-
},
|
|
657
|
-
value: destNow,
|
|
658
|
-
onChange: (e) => setDestNow(e.target.value),
|
|
659
|
-
children: [
|
|
660
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
661
|
-
value: "flomo",
|
|
662
|
-
children: "flomo"
|
|
663
|
-
}),
|
|
664
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
665
|
-
value: "local",
|
|
666
|
-
children: "本地文件"
|
|
667
|
-
}),
|
|
668
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
669
|
-
value: "notion",
|
|
670
|
-
children: "Notion"
|
|
671
|
-
}),
|
|
672
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
673
|
-
value: "all",
|
|
674
|
-
children: "全选(flomo+本地+Notion)"
|
|
675
|
-
})
|
|
676
|
-
]
|
|
677
|
-
}),
|
|
678
|
-
(destNow === "local" || destNow === "all") && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
679
|
-
style: s.input,
|
|
680
|
-
placeholder: destNow === "local" ? "导出路径(必填)" : "本地路径(留空用默认)",
|
|
681
|
-
value: localDirNow,
|
|
682
|
-
onChange: (e) => setLocalDirNow(e.target.value)
|
|
683
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
684
|
-
style: s.button,
|
|
685
|
-
onClick: () => void pickFolder((p) => setLocalDirNow(p)),
|
|
686
|
-
disabled: busy,
|
|
687
|
-
children: "选择文件夹…"
|
|
688
|
-
})] }),
|
|
689
|
-
(destNow === "flomo" || destNow === "all") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
690
|
-
style: {
|
|
691
|
-
...s.input,
|
|
692
|
-
width: "150px"
|
|
693
|
-
},
|
|
694
|
-
placeholder: destNow === "all" ? "flomo 标签(留空用默认)" : "本次标签(#)",
|
|
695
|
-
value: flomoTagNow,
|
|
696
|
-
onChange: (e) => setFlomoTagNow(e.target.value)
|
|
697
|
-
})
|
|
698
|
-
]
|
|
699
|
-
}),
|
|
700
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
701
|
-
style: s.row,
|
|
702
|
-
children: [
|
|
703
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
704
|
-
style: {
|
|
705
|
-
...s.label,
|
|
706
|
-
display: "flex",
|
|
707
|
-
alignItems: "center",
|
|
708
|
-
gap: "4px",
|
|
709
|
-
cursor: "pointer"
|
|
710
|
-
},
|
|
711
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
712
|
-
type: "checkbox",
|
|
713
|
-
checked: usePromptNow === null ? view?.usePrompt ?? false : usePromptNow,
|
|
714
|
-
onChange: (e) => setUsePromptNow(e.target.checked)
|
|
715
|
-
}), "本次用 prompt 处理(默认跟随配置)"]
|
|
716
637
|
}),
|
|
717
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { style: s.flex }),
|
|
718
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
719
|
-
style: s.button,
|
|
720
|
-
onClick: () => void runExportNow(),
|
|
721
|
-
disabled: busy || !view?.configured,
|
|
722
|
-
children: ["导出到 ", destNow === "all" ? "全部" : destNow === "local" ? "本地" : destNow === "notion" ? "Notion" : "flomo"]
|
|
723
|
-
})
|
|
724
|
-
]
|
|
725
|
-
}),
|
|
726
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
727
|
-
style: s.row,
|
|
728
|
-
children: [
|
|
729
638
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { style: s.flex }),
|
|
730
639
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
731
640
|
style: s.button,
|
|
732
|
-
onClick: () => void
|
|
641
|
+
onClick: () => void saveMainConfig(),
|
|
733
642
|
disabled: busy,
|
|
734
|
-
children: "
|
|
643
|
+
children: "保存"
|
|
735
644
|
}),
|
|
736
645
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
737
646
|
style: s.button,
|
|
@@ -743,7 +652,7 @@ window.__ModuleLoader__.load({
|
|
|
743
652
|
style: s.button,
|
|
744
653
|
onClick: () => void clearConfig(),
|
|
745
654
|
disabled: busy,
|
|
746
|
-
children: "
|
|
655
|
+
children: "清除全部"
|
|
747
656
|
})
|
|
748
657
|
]
|
|
749
658
|
})
|
|
@@ -758,7 +667,22 @@ window.__ModuleLoader__.load({
|
|
|
758
667
|
}),
|
|
759
668
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
760
669
|
style: view?.flomoConfigured === false ? s.statusWarn : s.status,
|
|
761
|
-
children: view === null ? "加载中…" : view.flomoConfigured ? "已配置:" + view.flomoSource + " " + view.flomoMasked + "(与「Flomo
|
|
670
|
+
children: view === null ? "加载中…" : view.flomoConfigured ? "已配置:" + view.flomoSource + " " + view.flomoMasked + "(与「Flomo」面板共享凭据)" : "未配置 — 在 flomo 设置页(flomoapp.com/mine?source=incoming_webhook)获取 API URL。"
|
|
671
|
+
}),
|
|
672
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
673
|
+
style: s.row,
|
|
674
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
675
|
+
style: s.label,
|
|
676
|
+
children: "标签"
|
|
677
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
678
|
+
style: {
|
|
679
|
+
...s.input,
|
|
680
|
+
width: "160px"
|
|
681
|
+
},
|
|
682
|
+
placeholder: "如 读书笔记(#)",
|
|
683
|
+
value: defaultFlomoTag,
|
|
684
|
+
onChange: (e) => setDefaultFlomoTag(e.target.value)
|
|
685
|
+
})]
|
|
762
686
|
}),
|
|
763
687
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
764
688
|
style: s.row,
|
|
@@ -782,10 +706,16 @@ window.__ModuleLoader__.load({
|
|
|
782
706
|
}),
|
|
783
707
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
784
708
|
style: s.button,
|
|
785
|
-
onClick: () => void
|
|
709
|
+
onClick: () => void saveFlomoConfig(),
|
|
786
710
|
disabled: busy,
|
|
787
711
|
children: "保存 flomo"
|
|
788
712
|
}),
|
|
713
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
714
|
+
style: s.button,
|
|
715
|
+
onClick: () => void testFlomoConfig(),
|
|
716
|
+
disabled: busy || !view?.flomoConfigured,
|
|
717
|
+
children: "测试 flomo"
|
|
718
|
+
}),
|
|
789
719
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
790
720
|
style: s.button,
|
|
791
721
|
onClick: () => void clearFlomoConfig(),
|
|
@@ -803,12 +733,16 @@ window.__ModuleLoader__.load({
|
|
|
803
733
|
style: s.groupTitle,
|
|
804
734
|
children: "③ Notion 导出"
|
|
805
735
|
}),
|
|
736
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
737
|
+
style: view?.notionConfigured === false ? s.statusWarn : s.status,
|
|
738
|
+
children: view === null ? "加载中…" : view.notionConfigured ? "已配置" + (view.notionTargetPageId !== "" ? " · 目标页已填" : "(未填目标页)") : "未配置 — 在 notion.so/my-integrations 创建 Integration 并复制 Token。"
|
|
739
|
+
}),
|
|
806
740
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
807
741
|
style: s.row,
|
|
808
742
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
809
743
|
style: s.input,
|
|
810
744
|
type: "password",
|
|
811
|
-
placeholder: "Notion Integration Token
|
|
745
|
+
placeholder: "Notion Integration Token",
|
|
812
746
|
value: notionToken,
|
|
813
747
|
onChange: (e) => setNotionToken(e.target.value)
|
|
814
748
|
})
|
|
@@ -821,6 +755,60 @@ window.__ModuleLoader__.load({
|
|
|
821
755
|
value: notionTargetPageId,
|
|
822
756
|
onChange: (e) => setNotionTargetPageId(e.target.value)
|
|
823
757
|
})
|
|
758
|
+
}),
|
|
759
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
760
|
+
style: s.row,
|
|
761
|
+
children: [
|
|
762
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { style: s.flex }),
|
|
763
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
764
|
+
style: s.button,
|
|
765
|
+
onClick: () => void saveNotionConfig(),
|
|
766
|
+
disabled: busy,
|
|
767
|
+
children: "保存 Notion"
|
|
768
|
+
}),
|
|
769
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
770
|
+
style: s.button,
|
|
771
|
+
onClick: () => void testNotionConfig(),
|
|
772
|
+
disabled: busy || !view?.notionConfigured,
|
|
773
|
+
children: "测试 Notion"
|
|
774
|
+
})
|
|
775
|
+
]
|
|
776
|
+
})
|
|
777
|
+
]
|
|
778
|
+
}),
|
|
779
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
780
|
+
style: s.group,
|
|
781
|
+
children: [
|
|
782
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
783
|
+
style: s.groupTitle,
|
|
784
|
+
children: "④ 本地导出"
|
|
785
|
+
}),
|
|
786
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
787
|
+
style: view?.localExportDir === "" ? s.statusWarn : s.status,
|
|
788
|
+
children: view === null ? "加载中…" : view.localExportDir !== "" ? "已配置:" + view.localExportDir : "未配置 — 选择或填写一个导出目录,导出为 Markdown 文件。"
|
|
789
|
+
}),
|
|
790
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
791
|
+
style: s.row,
|
|
792
|
+
children: [
|
|
793
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
794
|
+
style: s.input,
|
|
795
|
+
placeholder: "导出目录(绝对路径)",
|
|
796
|
+
value: localExportDir,
|
|
797
|
+
onChange: (e) => setLocalExportDir(e.target.value)
|
|
798
|
+
}),
|
|
799
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
800
|
+
style: s.button,
|
|
801
|
+
onClick: () => void pickFolder((p) => setLocalExportDir(p)),
|
|
802
|
+
disabled: busy,
|
|
803
|
+
children: "选择文件夹…"
|
|
804
|
+
}),
|
|
805
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
806
|
+
style: s.button,
|
|
807
|
+
onClick: () => void saveLocalConfig(),
|
|
808
|
+
disabled: busy,
|
|
809
|
+
children: "保存本地"
|
|
810
|
+
})
|
|
811
|
+
]
|
|
824
812
|
})
|
|
825
813
|
]
|
|
826
814
|
}),
|
|
@@ -829,7 +817,7 @@ window.__ModuleLoader__.load({
|
|
|
829
817
|
children: [
|
|
830
818
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
831
819
|
style: s.groupTitle,
|
|
832
|
-
children: "
|
|
820
|
+
children: "⑤ AI · prompt 处理(导出前用 LLM 按模板整理)"
|
|
833
821
|
}),
|
|
834
822
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
835
823
|
style: s.row,
|
|
@@ -880,6 +868,114 @@ window.__ModuleLoader__.load({
|
|
|
880
868
|
value: exportPrompt,
|
|
881
869
|
onChange: (e) => setExportPrompt(e.target.value)
|
|
882
870
|
})
|
|
871
|
+
}),
|
|
872
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
873
|
+
style: s.row,
|
|
874
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { style: s.flex }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
875
|
+
style: s.button,
|
|
876
|
+
onClick: () => void saveAIConfig(),
|
|
877
|
+
disabled: busy,
|
|
878
|
+
children: "保存 AI"
|
|
879
|
+
})]
|
|
880
|
+
})
|
|
881
|
+
]
|
|
882
|
+
}),
|
|
883
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
884
|
+
style: s.group,
|
|
885
|
+
children: [
|
|
886
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
887
|
+
style: s.groupTitle,
|
|
888
|
+
children: "⑥ 快捷导出"
|
|
889
|
+
}),
|
|
890
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
891
|
+
style: s.row,
|
|
892
|
+
children: [
|
|
893
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
894
|
+
style: s.label,
|
|
895
|
+
children: "选择书籍"
|
|
896
|
+
}),
|
|
897
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
|
|
898
|
+
style: s.select,
|
|
899
|
+
value: bookId,
|
|
900
|
+
onChange: (e) => setBookId(e.target.value),
|
|
901
|
+
disabled: books.length === 0,
|
|
902
|
+
children: books.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
903
|
+
value: "",
|
|
904
|
+
children: "(缓存无书籍,先同步)"
|
|
905
|
+
}) : books.map((b) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
|
|
906
|
+
value: b.bookId,
|
|
907
|
+
children: [
|
|
908
|
+
"《",
|
|
909
|
+
b.title,
|
|
910
|
+
"》",
|
|
911
|
+
b.author !== "" ? " · " + b.author : ""
|
|
912
|
+
]
|
|
913
|
+
}, b.bookId))
|
|
914
|
+
}),
|
|
915
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
916
|
+
style: s.button,
|
|
917
|
+
onClick: () => void runSync(),
|
|
918
|
+
disabled: busy || !view?.configured,
|
|
919
|
+
children: "同步书架/笔记"
|
|
920
|
+
})
|
|
921
|
+
]
|
|
922
|
+
}),
|
|
923
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
924
|
+
style: s.row,
|
|
925
|
+
children: [
|
|
926
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
927
|
+
style: s.label,
|
|
928
|
+
children: "导出到"
|
|
929
|
+
}),
|
|
930
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
931
|
+
style: {
|
|
932
|
+
...s.select,
|
|
933
|
+
flex: 0,
|
|
934
|
+
minWidth: "130px"
|
|
935
|
+
},
|
|
936
|
+
value: destNow,
|
|
937
|
+
onChange: (e) => setDestNow(e.target.value),
|
|
938
|
+
children: [
|
|
939
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
940
|
+
value: "flomo",
|
|
941
|
+
children: "flomo"
|
|
942
|
+
}),
|
|
943
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
944
|
+
value: "local",
|
|
945
|
+
children: "本地文件"
|
|
946
|
+
}),
|
|
947
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
948
|
+
value: "notion",
|
|
949
|
+
children: "Notion"
|
|
950
|
+
}),
|
|
951
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
952
|
+
value: "all",
|
|
953
|
+
children: "全选(flomo+本地+Notion)"
|
|
954
|
+
})
|
|
955
|
+
]
|
|
956
|
+
}),
|
|
957
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
958
|
+
style: {
|
|
959
|
+
...s.label,
|
|
960
|
+
display: "flex",
|
|
961
|
+
alignItems: "center",
|
|
962
|
+
gap: "4px",
|
|
963
|
+
cursor: "pointer"
|
|
964
|
+
},
|
|
965
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
966
|
+
type: "checkbox",
|
|
967
|
+
checked: usePromptNow === null ? view?.usePrompt ?? false : usePromptNow,
|
|
968
|
+
onChange: (e) => setUsePromptNow(e.target.checked)
|
|
969
|
+
}), "本次用 prompt"]
|
|
970
|
+
}),
|
|
971
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { style: s.flex }),
|
|
972
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
973
|
+
style: s.button,
|
|
974
|
+
onClick: () => void runExportNow(),
|
|
975
|
+
disabled: busy || !view?.configured,
|
|
976
|
+
children: ["导出到 ", destNow === "all" ? "全部" : destNow === "local" ? "本地" : destNow === "notion" ? "Notion" : "flomo"]
|
|
977
|
+
})
|
|
978
|
+
]
|
|
883
979
|
})
|
|
884
980
|
]
|
|
885
981
|
}),
|
|
@@ -893,21 +989,11 @@ window.__ModuleLoader__.load({
|
|
|
893
989
|
}),
|
|
894
990
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
895
991
|
style: s.hint,
|
|
896
|
-
children: "
|
|
992
|
+
children: "【各目标独立配置】②flomo:标签 + API URL/Key(与「Flomo」面板共享凭据);③Notion:Integration Token + 目标页(页面需分享给该 Integration);④本地:导出目录(导出为 Markdown 文件)。每个卡片「保存/测试」独立操作。"
|
|
897
993
|
}),
|
|
898
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
994
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
899
995
|
style: s.hint,
|
|
900
|
-
children:
|
|
901
|
-
"【AI prompt】启用后导出前会调用 LLM(OpenAI 兼容,默认 DeepSeek)按模板整理划线内容再导出。模板支持 ",
|
|
902
|
-
"{title}",
|
|
903
|
-
" ",
|
|
904
|
-
"{author}",
|
|
905
|
-
" ",
|
|
906
|
-
"{highlights}",
|
|
907
|
-
" ",
|
|
908
|
-
"{thoughts}",
|
|
909
|
-
" 占位符,可随意编辑;本次导出也可临时开关。"
|
|
910
|
-
]
|
|
996
|
+
children: "【导出】「⑥ 快捷导出」只选目标:flomo / 本地 / Notion / 全选(一次导出到所有已配置目标,未配置的跳过并说明);prompt 处理可选,导出前按 ⑤ 模板整理一次再分发。"
|
|
911
997
|
}),
|
|
912
998
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
913
999
|
style: s.hint,
|
package/lib/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","names":[],"sources":["../src/client/api.ts","../src/client/WereadSettingsPanel.tsx","../src/client/index.ts"],"sourcesContent":["/**\n * Browser-side API client for the /api/weread-export route family. The only\n * data access path the settings panel uses — plain fetch, same origin.\n */\n\n/** Public config view (mirrors the host contract). */\nexport interface WereadConfigView {\n configured: boolean\n apiKeyMasked: string\n defaultFlomoTag: string\n /** 0 = export ALL highlights; N > 0 = cap at N. */\n exportLimit: number\n exportDest: string\n localExportDir: string\n notionConfigured: boolean\n notionTargetPageId: string\n usePrompt: boolean\n llmConfigured: boolean\n llmBaseUrl: string\n llmModel: string\n lastSyncAt: string\n configPath: string\n}\n\n/** Status view with cache + flomo stats. */\nexport interface WereadStatusView extends WereadConfigView {\n flomoConfigured: boolean\n flomoSource: string\n flomoMasked: string\n flomoConfigPath: string\n cachedShelfBooks: number\n cachedNoteBooks: number\n cacheUpdatedAt: string\n}\n\n/** Sync result. */\nexport interface WereadSyncResult {\n ok: boolean\n message: string\n shelfBooks: number\n notebooks: number\n}\n\n/** One cached book for the export picker. */\nexport interface WereadBook {\n bookId: string\n title: string\n author: string\n}\n\n/** Flomo export result. */\nexport interface WereadFlomoResult {\n ok: boolean\n message: string\n sent: number\n memoCount?: number\n bookId?: string\n}\n\n/** Error carrying the route's JSON error message. */\nexport class WereadApiError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'WereadApiError'\n }\n}\n\n/** Parse a JSON response or throw a WereadApiError. */\nasync function readJson<T>(response: Response): Promise<T> {\n let body: unknown\n try {\n body = await response.json()\n } catch {\n throw new WereadApiError(`HTTP ${response.status}: invalid JSON response`)\n }\n if (!response.ok) {\n const message = typeof body === 'object' && body !== null && typeof (body as { error?: unknown }).error === 'string'\n ? (body as { error: string }).error\n : `HTTP ${response.status}`\n throw new WereadApiError(message)\n }\n return body as T\n}\n\n/** Plain fetch helper with an error wrapper. */\nasync function request<T>(path: string, init?: RequestInit): Promise<T> {\n let response: Response\n try {\n response = await fetch(path, init)\n } catch (error) {\n throw new WereadApiError('网络请求失败: ' + String(error instanceof Error ? error.message : error))\n }\n return readJson<T>(response)\n}\n\n/** The weread panel API. */\nexport class WereadApi {\n async getConfig(): Promise<WereadConfigView> {\n return request<WereadConfigView>('/api/weread-export/config')\n }\n\n async setConfig(patch: Record<string, unknown>): Promise<WereadConfigView> {\n return request<WereadConfigView>('/api/weread-export/config', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(patch),\n })\n }\n\n async getStatus(): Promise<WereadStatusView> {\n return request<WereadStatusView>('/api/weread-export/status')\n }\n\n async test(): Promise<{ ok: boolean; message: string }> {\n return request<{ ok: boolean; message: string }>('/api/weread-export/test', { method: 'POST' })\n }\n\n async sync(): Promise<WereadSyncResult> {\n return request<WereadSyncResult>('/api/weread-export/sync', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({}),\n })\n }\n\n async books(): Promise<WereadBook[]> {\n const data = await request<{ books: WereadBook[] }>('/api/weread-export/books')\n return data.books ?? []\n }\n\n /** Open the host OS folder chooser; resolves the picked absolute path. */\n async pickDir(): Promise<{ ok: boolean; path?: string; cancelled?: boolean; unsupported?: boolean; message?: string }> {\n return request<{ ok: boolean; path?: string; cancelled?: boolean; unsupported?: boolean; message?: string }>('/api/weread-export/pick-dir', { method: 'POST' })\n }\n\n async exportFlomo(bookId: string, tag: string, limit = 20): Promise<WereadFlomoResult> {\n return request<WereadFlomoResult>('/api/weread-export/flomo', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ bookId, tag, limit }),\n })\n }\n\n /** Multi-target export: flomo / local / notion with optional prompt. */\n async exportData(body: Record<string, unknown>): Promise<WereadFlomoResult> {\n return request<WereadFlomoResult>('/api/weread-export/export', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n })\n }\n}\n","/**\n * WeRead settings panel — rendered inside the web settings page\n * (settings.section entry). Connection setup (Skills API key), export\n * preferences (destination flomo/local/Notion, per-export limit, local dir,\n * Notion token + target page, LLM prompt processing with a user-editable\n * template), manual sync, and a quick multi-target export. Plain React,\n * inline styles only.\n */\nimport { useCallback, useEffect, useState } from 'react'\nimport {\n WereadApi, type WereadConfigView, type WereadStatusView, type WereadSyncResult, type WereadBook, type WereadFlomoResult,\n} from './api.ts'\n\n/** Module-level API client (stateless; the component closes over it). */\nconst api = new WereadApi()\n\n/** One shared style sheet (kept tiny and theme-agnostic). */\nconst s = {\n card: {\n display: 'flex',\n flexDirection: 'column',\n gap: '10px',\n maxWidth: '680px',\n padding: '14px 16px',\n borderRadius: '10px',\n border: '1px solid rgba(128,128,128,0.3)',\n fontSize: '13px',\n color: 'inherit',\n } as const,\n title: { fontWeight: 600, fontSize: '13px', margin: 0 } as const,\n /** One visually separated group card. */\n group: {\n display: 'flex',\n flexDirection: 'column',\n gap: '8px',\n marginTop: '6px',\n padding: '12px 14px',\n borderRadius: '10px',\n border: '1px solid rgba(128,128,128,0.22)',\n background: 'rgba(128,128,128,0.05)',\n minWidth: 0,\n } as const,\n groupTitle: {\n fontWeight: 600,\n fontSize: '12px',\n opacity: 0.95,\n margin: 0,\n paddingBottom: '6px',\n borderBottom: '1px solid rgba(128,128,128,0.18)',\n } as const,\n subTitle: {\n fontWeight: 600,\n fontSize: '12px',\n opacity: 0.9,\n margin: '4px 0 0',\n paddingTop: '8px',\n borderTop: '1px dashed rgba(128,128,128,0.25)',\n } as const,\n status: { fontSize: '12px', opacity: 0.85, whiteSpace: 'pre-wrap' } as const,\n statusWarn: { fontSize: '12px', opacity: 0.9, color: '#c9763a' } as const,\n row: { display: 'flex', gap: '6px', alignItems: 'center', minWidth: 0, flexWrap: 'wrap' } as const,\n col: { display: 'flex', flexDirection: 'column', gap: '4px', minWidth: 0 } as const,\n label: { fontSize: '12px', opacity: 0.8, whiteSpace: 'nowrap' } as const,\n input: {\n flex: 1,\n minWidth: 0,\n boxSizing: 'border-box',\n padding: '5px 8px',\n borderRadius: '6px',\n border: '1px solid rgba(128,128,128,0.35)',\n background: 'rgba(128,128,128,0.08)',\n color: 'inherit',\n fontSize: '12px',\n } as const,\n textarea: {\n width: '100%',\n minHeight: '96px',\n boxSizing: 'border-box',\n padding: '6px 8px',\n borderRadius: '6px',\n border: '1px solid rgba(128,128,128,0.35)',\n background: 'rgba(128,128,128,0.08)',\n color: 'inherit',\n fontSize: '12px',\n fontFamily: 'inherit',\n lineHeight: 1.5,\n resize: 'vertical',\n } as const,\n select: {\n flex: 1,\n minWidth: 0,\n maxWidth: '100%',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n padding: '4px 6px',\n borderRadius: '6px',\n border: '1px solid rgba(128,128,128,0.35)',\n background: 'rgba(128,128,128,0.08)',\n color: 'inherit',\n fontSize: '12px',\n } as const,\n flex: { flex: 1 } as const,\n button: {\n padding: '4px 10px',\n borderRadius: '6px',\n cursor: 'pointer',\n border: '1px solid rgba(128,128,128,0.4)',\n background: 'rgba(128,128,128,0.14)',\n color: 'inherit',\n fontSize: '12px',\n whiteSpace: 'nowrap',\n } as const,\n msg: { fontSize: '12px', whiteSpace: 'pre-wrap', wordBreak: 'break-all', opacity: 0.9 } as const,\n hint: { fontSize: '11px', opacity: 0.75, lineHeight: 1.6 } as const,\n}\n\n/** Status line for the current config view. */\nfunction statusText(view: WereadStatusView | null): string {\n if (view === null) return '加载中…'\n if (!view.configured) {\n return '未配置 — 打开 https://weread.qq.com/r/weread-skills,用微信读书账号登录后点击「创建 Key」并复制(wrk- 开头),粘贴到上方输入框。'\n }\n const destLabel = view.exportDest === 'local' ? '本地文件' : (view.exportDest === 'notion' ? 'Notion' : 'flomo')\n return (\n '已配置 · Key ' + view.apiKeyMasked +\n '\\n默认导出目标 ' + destLabel + ' · 导出条数 ' + (view.exportLimit === 0 ? '全部' : view.exportLimit + ' 条') +\n ' · 默认标签 #' + view.defaultFlomoTag +\n '\\nprompt 处理 ' + (view.usePrompt ? '开(' + (view.llmConfigured ? view.llmModel + ' @ ' + view.llmBaseUrl : 'LLM 未配置') + ')' : '关') +\n ' · Notion ' + (view.notionConfigured ? '已配置' + (view.notionTargetPageId !== '' ? ' + 目标页' : '(未填目标页)') : '未配置') +\n ' · flomo ' + (view.flomoConfigured ? '已配置(' + view.flomoSource + ' ' + view.flomoMasked + ')' : '未配置') +\n ' · 缓存书架 ' + view.cachedShelfBooks + ' 本 / 有笔记 ' + view.cachedNoteBooks + ' 本' +\n (view.lastSyncAt !== '' ? ' · 最近同步 ' + view.lastSyncAt : '')\n )\n}\n\n/** The WeRead settings panel component. */\nexport function WereadSettingsPanel(): JSX.Element {\n const [view, setView] = useState<WereadStatusView | null>(null)\n const [apiKey, setApiKey] = useState('')\n const [defaultFlomoTag, setDefaultFlomoTag] = useState('')\n const [limitChoice, setLimitChoice] = useState('20')\n const [customLimit, setCustomLimit] = useState('')\n const [exportDest, setExportDest] = useState('flomo')\n const [localExportDir, setLocalExportDir] = useState('')\n const [notionToken, setNotionToken] = useState('')\n const [flomoWebhookUrl, setFlomoWebhookUrl] = useState('')\n const [flomoApiKey, setFlomoApiKey] = useState('')\n const [notionTargetPageId, setNotionTargetPageId] = useState('')\n const [usePrompt, setUsePrompt] = useState(false)\n const [llmBaseUrl, setLlmBaseUrl] = useState('')\n const [llmApiKey, setLlmApiKey] = useState('')\n const [llmModel, setLlmModel] = useState('')\n const [exportPrompt, setExportPrompt] = useState('')\n const [busy, setBusy] = useState(false)\n const [message, setMessage] = useState('')\n const [books, setBooks] = useState<WereadBook[]>([])\n const [bookId, setBookId] = useState('')\n // Per-export (this run) controls.\n const [destNow, setDestNow] = useState('')\n const [localDirNow, setLocalDirNow] = useState('')\n const [flomoTagNow, setFlomoTagNow] = useState('')\n const [usePromptNow, setUsePromptNow] = useState<boolean | null>(null)\n\n /** Map the persisted exportLimit to the choice selector. */\n const limitFromView = (value: number): string => {\n if (value === 0) return '0'\n if ([20, 50, 100].includes(value)) return String(value)\n return 'custom'\n }\n\n const refresh = useCallback(async () => {\n try {\n const next = await api.getStatus()\n setView(next)\n setDefaultFlomoTag((prev) => prev === '' ? next.defaultFlomoTag : prev)\n setFlomoTagNow((prev) => prev === '' ? next.defaultFlomoTag : prev)\n setLimitChoice(limitFromView(next.exportLimit))\n if (![0, 20, 50, 100].includes(next.exportLimit)) setCustomLimit(String(next.exportLimit))\n setExportDest(next.exportDest)\n setLocalExportDir(next.localExportDir)\n setNotionTargetPageId(next.notionTargetPageId)\n setUsePrompt(next.usePrompt)\n setLlmBaseUrl(next.llmBaseUrl)\n setLlmModel(next.llmModel)\n if (destNow === '') setDestNow(next.exportDest)\n } catch (error) {\n setMessage('状态读取失败: ' + String(error instanceof Error ? error.message : error))\n }\n }, [destNow])\n\n useEffect(() => {\n void refresh()\n }, [refresh])\n\n const reloadBooks = useCallback(async () => {\n try {\n const list = await api.books()\n setBooks(list)\n if (list.length > 0) setBookId((prev) => prev !== '' && list.some((b) => b.bookId === prev) ? prev : (list[0]?.bookId ?? ''))\n } catch {\n setBooks([])\n }\n }, [])\n\n useEffect(() => {\n void reloadBooks()\n }, [reloadBooks])\n\n /** Resolve the chosen export limit to a number (0 = all). */\n const resolveExportLimit = (): number => {\n if (limitChoice === 'custom') {\n const parsed = Number(customLimit)\n return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : 0\n }\n return Number(limitChoice) || 0\n }\n\n const saveConfig = async (): Promise<void> => {\n setBusy(true)\n setMessage('')\n try {\n const patch: Record<string, unknown> = {}\n if (apiKey.trim() !== '') patch.apiKey = apiKey.trim()\n if (defaultFlomoTag.trim() !== '') patch.defaultFlomoTag = defaultFlomoTag.trim()\n patch.exportLimit = resolveExportLimit()\n patch.exportDest = exportDest\n if (localExportDir.trim() !== '') patch.localExportDir = localExportDir.trim()\n if (notionToken.trim() !== '') patch.notionToken = notionToken.trim()\n if (notionTargetPageId.trim() !== '') patch.notionTargetPageId = notionTargetPageId.trim()\n if (flomoWebhookUrl.trim() !== '') patch.flomoWebhookUrl = flomoWebhookUrl.trim()\n if (flomoApiKey.trim() !== '') patch.flomoApiKey = flomoApiKey.trim()\n patch.usePrompt = usePrompt\n if (llmBaseUrl.trim() !== '') patch.llmBaseUrl = llmBaseUrl.trim()\n if (llmApiKey.trim() !== '') patch.llmApiKey = llmApiKey.trim()\n if (llmModel.trim() !== '') patch.llmModel = llmModel.trim()\n if (exportPrompt.trim() !== '') patch.exportPrompt = exportPrompt\n const next: WereadConfigView = await api.setConfig(patch)\n const status = await api.getStatus()\n setView(status)\n setMessage(next.configured ? '配置已保存。' : '配置未保存完整:缺少 API Key。')\n setApiKey('')\n setNotionToken('')\n setLlmApiKey('')\n setFlomoWebhookUrl('')\n setFlomoApiKey('')\n } catch (error) {\n setMessage('保存失败: ' + String(error instanceof Error ? error.message : error))\n } finally {\n setBusy(false)\n }\n }\n\n const clearFlomoConfig = async (): Promise<void> => {\n setBusy(true)\n setMessage('')\n try {\n await api.setConfig({ flomoReset: true })\n setView(await api.getStatus())\n setMessage('已清除 flomo 配置。')\n } catch (error) {\n setMessage('清除失败: ' + String(error instanceof Error ? error.message : error))\n } finally {\n setBusy(false)\n }\n }\n\n const clearConfig = async (): Promise<void> => {\n setBusy(true)\n setMessage('')\n try {\n const next: WereadConfigView = await api.setConfig({ reset: true })\n setView({ ...next, flomoConfigured: view?.flomoConfigured ?? false, flomoSource: view?.flomoSource ?? '', flomoMasked: view?.flomoMasked ?? '', flomoConfigPath: view?.flomoConfigPath ?? '', cachedShelfBooks: view?.cachedShelfBooks ?? 0, cachedNoteBooks: view?.cachedNoteBooks ?? 0, cacheUpdatedAt: view?.cacheUpdatedAt ?? '' })\n setMessage('已清除配置。')\n } catch (error) {\n setMessage('清除失败: ' + String(error instanceof Error ? error.message : error))\n } finally {\n setBusy(false)\n }\n }\n\n const runTest = async (): Promise<void> => {\n setBusy(true)\n setMessage('测试中…')\n try {\n const result = await api.test()\n setMessage(result.ok ? result.message : '测试失败: ' + result.message)\n } catch (error) {\n setMessage('测试失败: ' + String(error instanceof Error ? error.message : error))\n } finally {\n setBusy(false)\n }\n }\n\n /** Open the host OS folder chooser and apply the picked path. */\n const pickFolder = async (apply: (p: string) => void): Promise<void> => {\n setBusy(true)\n setMessage('')\n try {\n const result = await api.pickDir()\n if (result.ok && result.path !== undefined) {\n apply(result.path)\n setMessage('已选择文件夹:' + result.path)\n } else if (result.cancelled === true) {\n setMessage('已取消选择。')\n } else {\n setMessage(result.message ?? '无法弹出文件夹选择(当前环境不支持),请手动输入路径。')\n }\n } catch (error) {\n setMessage('选择文件夹失败: ' + String(error instanceof Error ? error.message : error))\n } finally {\n setBusy(false)\n }\n }\n\n const runSync = async (): Promise<void> => {\n setBusy(true)\n setMessage('同步中…')\n try {\n const result: WereadSyncResult = await api.sync()\n setMessage(result.ok ? result.message : '同步失败: ' + result.message)\n await refresh()\n await reloadBooks()\n } catch (error) {\n setMessage('同步失败: ' + String(error instanceof Error ? error.message : error))\n } finally {\n setBusy(false)\n }\n }\n\n const runExportNow = async (): Promise<void> => {\n if (bookId === '') {\n setMessage('请先同步以加载书籍列表。')\n return\n }\n const dest = destNow || view?.exportDest || 'flomo'\n if (dest === 'local' && localDirNow.trim() === '') {\n setMessage('本地导出需要填写导出路径(每次必填)。')\n return\n }\n setBusy(true)\n setMessage('导出中…')\n try {\n const body: Record<string, unknown> = { bookId, dest }\n if (dest === 'local') body.localDir = localDirNow.trim()\n if (dest === 'flomo' && flomoTagNow.trim() !== '') body.tag = flomoTagNow.trim()\n if (dest === 'all') {\n // Use per-run values when provided; otherwise fall back to config.\n if (localDirNow.trim() !== '') body.localDir = localDirNow.trim()\n if (flomoTagNow.trim() !== '') body.tag = flomoTagNow.trim()\n }\n if (usePromptNow !== null) body.usePrompt = usePromptNow\n const result: WereadFlomoResult = await api.exportData(body)\n setMessage(result.ok ? result.message : '导出失败: ' + result.message)\n } catch (error) {\n setMessage('导出失败: ' + String(error instanceof Error ? error.message : error))\n } finally {\n setBusy(false)\n }\n }\n\n return (\n <div style={s.card}>\n <h3 style={s.title}>微信读书</h3>\n <div style={view?.configured === false ? s.statusWarn : s.status}>{statusText(view)}</div>\n\n <div style={s.group}>\n <div style={s.groupTitle}>① 连接与导出偏好</div>\n <div style={s.row}>\n <input\n style={s.input}\n placeholder=\"Skills API Key(wrk- 开头)\"\n value={apiKey}\n onChange={(e) => setApiKey(e.target.value)}\n />\n </div>\n <div style={s.row}>\n <span style={s.label}>默认导出标签</span>\n <input\n style={{ ...s.input, width: '130px' }}\n placeholder=\"如 读书笔记\"\n value={defaultFlomoTag}\n onChange={(e) => setDefaultFlomoTag(e.target.value)}\n />\n <span style={s.label}>导出条数</span>\n <select style={{ ...s.select, flex: 0, minWidth: '104px' }} value={limitChoice} onChange={(e) => setLimitChoice(e.target.value)}>\n <option value=\"0\">全部导出</option>\n <option value=\"20\">20 条(默认)</option>\n <option value=\"50\">50 条</option>\n <option value=\"100\">100 条</option>\n <option value=\"custom\">自定义…</option>\n </select>\n {limitChoice === 'custom' && (\n <input style={{ ...s.input, width: '64px' }} placeholder=\"条数\" value={customLimit} onChange={(e) => setCustomLimit(e.target.value)} />\n )}\n </div>\n <div style={s.row}>\n <span style={s.label}>默认目标</span>\n <select style={{ ...s.select, flex: 0, minWidth: '130px' }} value={exportDest} onChange={(e) => setExportDest(e.target.value)}>\n <option value=\"flomo\">flomo</option>\n <option value=\"local\">本地文件</option>\n <option value=\"notion\">Notion</option>\n <option value=\"all\">全选(flomo+本地+Notion)</option>\n </select>\n {(exportDest === 'local' || exportDest === 'all') && (\n <>\n <input style={s.input} placeholder=\"本地导出目录(绝对路径,可留空导出时填)\" value={localExportDir} onChange={(e) => setLocalExportDir(e.target.value)} />\n <button style={s.button} onClick={() => void pickFolder((p) => setLocalExportDir(p))} disabled={busy}>选择文件夹…</button>\n </>\n )}\n </div>\n\n <div style={s.subTitle}>快捷导出</div>\n <div style={s.row}>\n <span style={s.label}>选择书籍</span>\n <select style={s.select} value={bookId} onChange={(e) => setBookId(e.target.value)} disabled={books.length === 0}>\n {books.length === 0 ? <option value=\"\">(缓存无书籍,先同步)</option> : books.map((b) => (\n <option key={b.bookId} value={b.bookId}>《{b.title}》{b.author !== '' ? ' · ' + b.author : ''}</option>\n ))}\n </select>\n <button style={s.button} onClick={() => void runSync()} disabled={busy || !view?.configured}>同步书架/笔记</button>\n </div>\n <div style={s.row}>\n <span style={s.label}>本次目标</span>\n <select style={{ ...s.select, flex: 0, minWidth: '130px' }} value={destNow} onChange={(e) => setDestNow(e.target.value)}>\n <option value=\"flomo\">flomo</option>\n <option value=\"local\">本地文件</option>\n <option value=\"notion\">Notion</option>\n <option value=\"all\">全选(flomo+本地+Notion)</option>\n </select>\n {(destNow === 'local' || destNow === 'all') && (\n <>\n <input style={s.input} placeholder={destNow === 'local' ? '导出路径(必填)' : '本地路径(留空用默认)'} value={localDirNow} onChange={(e) => setLocalDirNow(e.target.value)} />\n <button style={s.button} onClick={() => void pickFolder((p) => setLocalDirNow(p))} disabled={busy}>选择文件夹…</button>\n </>\n )}\n {(destNow === 'flomo' || destNow === 'all') && (\n <input style={{ ...s.input, width: '150px' }} placeholder={destNow === 'all' ? 'flomo 标签(留空用默认)' : '本次标签(#)'} value={flomoTagNow} onChange={(e) => setFlomoTagNow(e.target.value)} />\n )}\n </div>\n <div style={s.row}>\n <label style={{ ...s.label, display: 'flex', alignItems: 'center', gap: '4px', cursor: 'pointer' }}>\n <input\n type=\"checkbox\"\n checked={usePromptNow === null ? (view?.usePrompt ?? false) : usePromptNow}\n onChange={(e) => setUsePromptNow(e.target.checked)}\n />\n 本次用 prompt 处理(默认跟随配置)\n </label>\n <div style={s.flex} />\n <button style={s.button} onClick={() => void runExportNow()} disabled={busy || !view?.configured}>\n 导出到 {destNow === 'all' ? '全部' : (destNow === 'local' ? '本地' : (destNow === 'notion' ? 'Notion' : 'flomo'))}\n </button>\n </div>\n\n <div style={s.row}>\n <div style={s.flex} />\n <button style={s.button} onClick={() => void saveConfig()} disabled={busy}>保存配置</button>\n <button style={s.button} onClick={() => void runTest()} disabled={busy || !view?.configured}>测试连接</button>\n <button style={s.button} onClick={() => void clearConfig()} disabled={busy}>清除</button>\n </div>\n </div>\n\n <div style={s.group}>\n <div style={s.groupTitle}>② flomo 导出</div>\n <div style={view?.flomoConfigured === false ? s.statusWarn : s.status}>\n {view === null\n ? '加载中…'\n : (view.flomoConfigured\n ? '已配置:' + view.flomoSource + ' ' + view.flomoMasked + '(与「Flomo」面板共享,文件 ' + view.flomoConfigPath + ')'\n : '未配置 — 在 flomo 设置页(flomoapp.com/mine?source=incoming_webhook)获取 API URL,粘贴到下方。')}\n </div>\n <div style={s.row}>\n <input style={s.input} type=\"password\" placeholder=\"flomo API URL(https://flomoapp.com/iwh/xxxx)\" value={flomoWebhookUrl} onChange={(e) => setFlomoWebhookUrl(e.target.value)} />\n </div>\n <div style={s.row}>\n <input style={s.input} type=\"password\" placeholder=\"或 flomo API Key(新版,与 URL 二选一)\" value={flomoApiKey} onChange={(e) => setFlomoApiKey(e.target.value)} />\n <button style={s.button} onClick={() => void saveConfig()} disabled={busy}>保存 flomo</button>\n <button style={s.button} onClick={() => void clearFlomoConfig()} disabled={busy}>清除</button>\n </div>\n </div>\n\n <div style={s.group}>\n <div style={s.groupTitle}>③ Notion 导出</div>\n <div style={s.row}>\n <input style={s.input} type=\"password\" placeholder=\"Notion Integration Token(notion.so/my-integrations 创建)\" value={notionToken} onChange={(e) => setNotionToken(e.target.value)} />\n </div>\n <div style={s.row}>\n <input style={s.input} placeholder=\"目标父页面 URL 或 32 位 ID(页面需分享给该 Integration)\" value={notionTargetPageId} onChange={(e) => setNotionTargetPageId(e.target.value)} />\n </div>\n </div>\n\n <div style={s.group}>\n <div style={s.groupTitle}>④ AI · prompt 处理(导出前用 LLM 按模板整理)</div>\n <div style={s.row}>\n <label style={{ ...s.label, display: 'flex', alignItems: 'center', gap: '4px', cursor: 'pointer' }}>\n <input type=\"checkbox\" checked={usePrompt} onChange={(e) => setUsePrompt(e.target.checked)} />\n 启用\n </label>\n <input style={s.input} placeholder=\"Base URL(默认 https://api.deepseek.com/v1)\" value={llmBaseUrl} onChange={(e) => setLlmBaseUrl(e.target.value)} />\n <input style={{ ...s.input, width: '130px' }} placeholder=\"模型\" value={llmModel} onChange={(e) => setLlmModel(e.target.value)} />\n <input style={s.input} type=\"password\" placeholder=\"LLM API Key(自定义)\" value={llmApiKey} onChange={(e) => setLlmApiKey(e.target.value)} />\n </div>\n <div style={s.col}>\n <textarea\n style={s.textarea}\n placeholder=\"导出 prompt 模板,可用 {title} {author} {highlights} {thoughts} 占位符\"\n value={exportPrompt}\n onChange={(e) => setExportPrompt(e.target.value)}\n />\n </div>\n </div>\n\n {message !== '' && <div style={s.msg}>{message}</div>}\n <div style={s.hint}>\n 【API Key】打开 https://weread.qq.com/r/weread-skills → 微信读书账号登录 → 「创建 Key」→ 复制(wrk- 开头)。Key 绑定你的账号身份,请勿泄露。存于 ~/.dsh/weread-export.json(权限 0600)。\n </div>\n <div style={s.hint}>\n 【导出目标】flomo = 发到浮墨(复用「Flomo」面板凭据,超长自动拆多条 MEMO);本地 = 导出 Markdown 文件到指定目录(每次导出需填路径);Notion = 用本插件自己的 Integration Token 在目标父页面下创建子页面写入(目标页需分享给该 Integration)。\n </div>\n <div style={s.hint}>\n 【AI prompt】启用后导出前会调用 LLM(OpenAI 兼容,默认 DeepSeek)按模板整理划线内容再导出。模板支持 {'{title}'} {'{author}'} {'{highlights}'} {'{thoughts}'} 占位符,可随意编辑;本次导出也可临时开关。\n </div>\n <div style={s.hint}>\n 【导出条数】「全部导出」= 全部划线都导出(flomo 超长自动拆多条);「20/50/100/自定义」= 最多导出的条数。\n 同步后可用 weread_shelf / weread_notes / weread_readdata / weread_search / weread_book / weread_export 等工具。\n </div>\n </div>\n )\n}\n","/**\n * weread-export — browser half. Registers the WeRead settings panel into the\n * web settings page (settings.section entry). The panel configures the\n * Skills API key, default flomo tag, drives manual syncs, and quick-exports\n * highlights to flomo. Failure policy: registration problems are logged,\n * never thrown — the web shell fails the whole boot when a plugin apply\n * throws, and an external plugin must not take the GUI down.\n */\n// Type-only: pulls the settings-surface SlotMap merge (the 'settings.section'\n// entry) and the client runtime Context merge.\nimport type {} from '@deepseek-ai/dsh-client-ui-settings/client'\nimport type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'\nimport { WereadSettingsPanel } from './WereadSettingsPanel.tsx'\n\n/** Required services. */\nexport const inject = ['slots']\n\n/**\n * Register the WeRead settings page.\n * @param ctx - client root context.\n */\nexport function apply(ctx: ClientContext): void {\n try {\n ctx.slots.inject('settings.section', () => ctx.slots.register({\n name: 'settings.section',\n id: 'weread',\n order: 316,\n label: () => '微信读书',\n }, WereadSettingsPanel))\n } catch (error) {\n console.warn('[weread-export] settings panel registration failed:', error)\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"client.js","names":[],"sources":["../src/client/api.ts","../src/client/WereadSettingsPanel.tsx","../src/client/index.ts"],"sourcesContent":["/**\n * Browser-side API client for the /api/weread-export route family. The only\n * data access path the settings panel uses — plain fetch, same origin.\n */\n\n/** Public config view (mirrors the host contract). */\nexport interface WereadConfigView {\n configured: boolean\n apiKeyMasked: string\n defaultFlomoTag: string\n /** 0 = export ALL highlights; N > 0 = cap at N. */\n exportLimit: number\n exportDest: string\n localExportDir: string\n notionConfigured: boolean\n notionTargetPageId: string\n usePrompt: boolean\n llmConfigured: boolean\n llmBaseUrl: string\n llmModel: string\n lastSyncAt: string\n configPath: string\n}\n\n/** Status view with cache + flomo stats. */\nexport interface WereadStatusView extends WereadConfigView {\n flomoConfigured: boolean\n flomoSource: string\n flomoMasked: string\n flomoConfigPath: string\n cachedShelfBooks: number\n cachedNoteBooks: number\n cacheUpdatedAt: string\n}\n\n/** Sync result. */\nexport interface WereadSyncResult {\n ok: boolean\n message: string\n shelfBooks: number\n notebooks: number\n}\n\n/** One cached book for the export picker. */\nexport interface WereadBook {\n bookId: string\n title: string\n author: string\n}\n\n/** Flomo export result. */\nexport interface WereadFlomoResult {\n ok: boolean\n message: string\n sent: number\n memoCount?: number\n bookId?: string\n}\n\n/** Error carrying the route's JSON error message. */\nexport class WereadApiError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'WereadApiError'\n }\n}\n\n/** Parse a JSON response or throw a WereadApiError. */\nasync function readJson<T>(response: Response): Promise<T> {\n let body: unknown\n try {\n body = await response.json()\n } catch {\n throw new WereadApiError(`HTTP ${response.status}: invalid JSON response`)\n }\n if (!response.ok) {\n const message = typeof body === 'object' && body !== null && typeof (body as { error?: unknown }).error === 'string'\n ? (body as { error: string }).error\n : `HTTP ${response.status}`\n throw new WereadApiError(message)\n }\n return body as T\n}\n\n/** Plain fetch helper with an error wrapper. */\nasync function request<T>(path: string, init?: RequestInit): Promise<T> {\n let response: Response\n try {\n response = await fetch(path, init)\n } catch (error) {\n throw new WereadApiError('网络请求失败: ' + String(error instanceof Error ? error.message : error))\n }\n return readJson<T>(response)\n}\n\n/** The weread panel API. */\nexport class WereadApi {\n async getConfig(): Promise<WereadConfigView> {\n return request<WereadConfigView>('/api/weread-export/config')\n }\n\n async setConfig(patch: Record<string, unknown>): Promise<WereadConfigView> {\n return request<WereadConfigView>('/api/weread-export/config', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(patch),\n })\n }\n\n async getStatus(): Promise<WereadStatusView> {\n return request<WereadStatusView>('/api/weread-export/status')\n }\n\n async test(): Promise<{ ok: boolean; message: string }> {\n return request<{ ok: boolean; message: string }>('/api/weread-export/test', { method: 'POST' })\n }\n\n async sync(): Promise<WereadSyncResult> {\n return request<WereadSyncResult>('/api/weread-export/sync', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({}),\n })\n }\n\n async books(): Promise<WereadBook[]> {\n const data = await request<{ books: WereadBook[] }>('/api/weread-export/books')\n return data.books ?? []\n }\n\n /** Open the host OS folder chooser; resolves the picked absolute path. */\n async pickDir(): Promise<{ ok: boolean; path?: string; cancelled?: boolean; unsupported?: boolean; message?: string }> {\n return request<{ ok: boolean; path?: string; cancelled?: boolean; unsupported?: boolean; message?: string }>('/api/weread-export/pick-dir', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({}),\n })\n }\n\n /** Send a test memo to flomo to verify the shared credential. */\n async testFlomo(): Promise<{ ok: boolean; message: string }> {\n return request<{ ok: boolean; message: string }>('/api/weread-export/test-flomo', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({}),\n })\n }\n\n /** Verify the Notion token and target page accessibility. */\n async testNotion(): Promise<{ ok: boolean; message: string }> {\n return request<{ ok: boolean; message: string }>('/api/weread-export/test-notion', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({}),\n })\n }\n\n async exportFlomo(bookId: string, tag: string, limit = 20): Promise<WereadFlomoResult> {\n return request<WereadFlomoResult>('/api/weread-export/flomo', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ bookId, tag, limit }),\n })\n }\n\n /** Multi-target export: flomo / local / notion with optional prompt. */\n async exportData(body: Record<string, unknown>): Promise<WereadFlomoResult> {\n return request<WereadFlomoResult>('/api/weread-export/export', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n })\n }\n}\n","/**\n * WeRead settings panel — rendered inside the web settings page\n * (settings.section entry).\n *\n * Layout: each export target configures its own parameters in its own card\n * (flomo: tag + URL/key; Notion: token + target page; local: directory),\n * and the quick-export card only picks a destination (flomo / Notion /\n * local / all) — no \"default vs this-run\" duplication. Plain React, inline\n * styles only.\n */\nimport { useCallback, useEffect, useState } from 'react'\nimport {\n WereadApi, type WereadConfigView, type WereadStatusView, type WereadSyncResult, type WereadBook, type WereadFlomoResult,\n} from './api.ts'\n\n/** Module-level API client (stateless; the component closes over it). */\nconst api = new WereadApi()\n\n/** One shared style sheet (kept tiny and theme-agnostic). */\nconst s = {\n card: {\n display: 'flex',\n flexDirection: 'column',\n gap: '10px',\n maxWidth: '680px',\n padding: '14px 16px',\n borderRadius: '10px',\n border: '1px solid rgba(128,128,128,0.3)',\n fontSize: '13px',\n color: 'inherit',\n } as const,\n title: { fontWeight: 600, fontSize: '13px', margin: 0 } as const,\n group: {\n display: 'flex',\n flexDirection: 'column',\n gap: '8px',\n marginTop: '6px',\n padding: '12px 14px',\n borderRadius: '10px',\n border: '1px solid rgba(128,128,128,0.22)',\n background: 'rgba(128,128,128,0.05)',\n minWidth: 0,\n } as const,\n groupTitle: {\n fontWeight: 600,\n fontSize: '12px',\n opacity: 0.95,\n margin: 0,\n paddingBottom: '6px',\n borderBottom: '1px solid rgba(128,128,128,0.18)',\n } as const,\n status: { fontSize: '12px', opacity: 0.85, whiteSpace: 'pre-wrap' } as const,\n statusWarn: { fontSize: '12px', opacity: 0.9, color: '#c9763a' } as const,\n row: { display: 'flex', gap: '6px', alignItems: 'center', minWidth: 0, flexWrap: 'wrap' } as const,\n col: { display: 'flex', flexDirection: 'column', gap: '4px', minWidth: 0 } as const,\n label: { fontSize: '12px', opacity: 0.8, whiteSpace: 'nowrap' } as const,\n input: {\n flex: 1,\n minWidth: 0,\n boxSizing: 'border-box',\n padding: '5px 8px',\n borderRadius: '6px',\n border: '1px solid rgba(128,128,128,0.35)',\n background: 'rgba(128,128,128,0.08)',\n color: 'inherit',\n fontSize: '12px',\n } as const,\n textarea: {\n width: '100%',\n minHeight: '96px',\n boxSizing: 'border-box',\n padding: '6px 8px',\n borderRadius: '6px',\n border: '1px solid rgba(128,128,128,0.35)',\n background: 'rgba(128,128,128,0.08)',\n color: 'inherit',\n fontSize: '12px',\n fontFamily: 'inherit',\n lineHeight: 1.5,\n resize: 'vertical',\n } as const,\n select: {\n flex: 1,\n minWidth: 0,\n maxWidth: '100%',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n padding: '4px 6px',\n borderRadius: '6px',\n border: '1px solid rgba(128,128,128,0.35)',\n background: 'rgba(128,128,128,0.08)',\n color: 'inherit',\n fontSize: '12px',\n } as const,\n flex: { flex: 1 } as const,\n button: {\n padding: '4px 10px',\n borderRadius: '6px',\n cursor: 'pointer',\n border: '1px solid rgba(128,128,128,0.4)',\n background: 'rgba(128,128,128,0.14)',\n color: 'inherit',\n fontSize: '12px',\n whiteSpace: 'nowrap',\n } as const,\n msg: { fontSize: '12px', whiteSpace: 'pre-wrap', wordBreak: 'break-all', opacity: 0.9 } as const,\n hint: { fontSize: '11px', opacity: 0.75, lineHeight: 1.6 } as const,\n}\n\n/** Status line for the current config view. */\nfunction statusText(view: WereadStatusView | null): string {\n if (view === null) return '加载中…'\n if (!view.configured) {\n return '未配置 — 打开 https://weread.qq.com/r/weread-skills,用微信读书账号登录后点击「创建 Key」并复制(wrk- 开头),粘贴到「① 连接」卡片。'\n }\n return (\n '已配置 · Key ' + view.apiKeyMasked + ' · 导出条数 ' + (view.exportLimit === 0 ? '全部' : view.exportLimit + ' 条') +\n '\\nflomo ' + (view.flomoConfigured ? '已配置(' + view.flomoSource + ' ' + view.flomoMasked + ')' : '未配置') +\n ' · Notion ' + (view.notionConfigured ? '已配置' + (view.notionTargetPageId !== '' ? ' + 目标页' : '(未填目标页)') : '未配置') +\n ' · 本地 ' + (view.localExportDir !== '' ? '已配置' : '未配置') +\n ' · prompt ' + (view.usePrompt ? '开(' + (view.llmConfigured ? view.llmModel : 'LLM 未配置') + ')' : '关') +\n ' · 缓存书架 ' + view.cachedShelfBooks + ' 本 / 有笔记 ' + view.cachedNoteBooks + ' 本'\n )\n}\n\n/** The WeRead settings panel component. */\nexport function WereadSettingsPanel(): JSX.Element {\n const [view, setView] = useState<WereadStatusView | null>(null)\n // ① connection\n const [apiKey, setApiKey] = useState('')\n const [limitChoice, setLimitChoice] = useState('20')\n const [customLimit, setCustomLimit] = useState('')\n // ② flomo\n const [defaultFlomoTag, setDefaultFlomoTag] = useState('')\n const [flomoWebhookUrl, setFlomoWebhookUrl] = useState('')\n const [flomoApiKey, setFlomoApiKey] = useState('')\n // ③ notion\n const [notionToken, setNotionToken] = useState('')\n const [notionTargetPageId, setNotionTargetPageId] = useState('')\n // ④ local\n const [localExportDir, setLocalExportDir] = useState('')\n // ⑤ AI\n const [usePrompt, setUsePrompt] = useState(false)\n const [llmBaseUrl, setLlmBaseUrl] = useState('')\n const [llmApiKey, setLlmApiKey] = useState('')\n const [llmModel, setLlmModel] = useState('')\n const [exportPrompt, setExportPrompt] = useState('')\n // shared + quick export\n const [busy, setBusy] = useState(false)\n const [message, setMessage] = useState('')\n const [books, setBooks] = useState<WereadBook[]>([])\n const [bookId, setBookId] = useState('')\n const [destNow, setDestNow] = useState('flomo')\n const [usePromptNow, setUsePromptNow] = useState<boolean | null>(null)\n\n /** Map the persisted exportLimit to the choice selector. */\n const limitFromView = (value: number): string => {\n if (value === 0) return '0'\n if ([20, 50, 100].includes(value)) return String(value)\n return 'custom'\n }\n\n const refresh = useCallback(async () => {\n try {\n const next = await api.getStatus()\n setView(next)\n setDefaultFlomoTag((prev) => prev === '' ? next.defaultFlomoTag : prev)\n setLimitChoice(limitFromView(next.exportLimit))\n if (![0, 20, 50, 100].includes(next.exportLimit)) setCustomLimit(String(next.exportLimit))\n setNotionTargetPageId(next.notionTargetPageId)\n setLocalExportDir(next.localExportDir)\n setUsePrompt(next.usePrompt)\n setLlmBaseUrl(next.llmBaseUrl)\n setLlmModel(next.llmModel)\n if (destNow === '') setDestNow(next.exportDest)\n } catch (error) {\n setMessage('状态读取失败: ' + String(error instanceof Error ? error.message : error))\n }\n }, [destNow])\n\n useEffect(() => {\n void refresh()\n }, [refresh])\n\n const reloadBooks = useCallback(async () => {\n try {\n const list = await api.books()\n setBooks(list)\n if (list.length > 0) setBookId((prev) => prev !== '' && list.some((b) => b.bookId === prev) ? prev : (list[0]?.bookId ?? ''))\n } catch {\n setBooks([])\n }\n }, [])\n\n useEffect(() => {\n void reloadBooks()\n }, [reloadBooks])\n\n /** Resolve the chosen export limit to a number (0 = all). */\n const resolveExportLimit = (): number => {\n if (limitChoice === 'custom') {\n const parsed = Number(customLimit)\n return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : 0\n }\n return Number(limitChoice) || 0\n }\n\n const afterSave = async (okMessage: string): Promise<void> => {\n setView(await api.getStatus())\n setMessage(okMessage)\n setApiKey('')\n setFlomoWebhookUrl('')\n setFlomoApiKey('')\n setNotionToken('')\n setLlmApiKey('')\n }\n\n /** ① connection: API key + export limit. */\n const saveMainConfig = async (): Promise<void> => {\n setBusy(true)\n setMessage('')\n try {\n const patch: Record<string, unknown> = {}\n if (apiKey.trim() !== '') patch.apiKey = apiKey.trim()\n patch.exportLimit = resolveExportLimit()\n const next: WereadConfigView = await api.setConfig(patch)\n setMessage(next.configured ? '配置已保存。' : '配置未保存完整:缺少 API Key。')\n await afterSave('')\n } catch (error) {\n setMessage('保存失败: ' + String(error instanceof Error ? error.message : error))\n } finally {\n setBusy(false)\n }\n }\n\n /** ② flomo: tag + URL / key. */\n const saveFlomoConfig = async (): Promise<void> => {\n setBusy(true)\n setMessage('')\n try {\n const patch: Record<string, unknown> = {}\n if (defaultFlomoTag.trim() !== '') patch.defaultFlomoTag = defaultFlomoTag.trim()\n if (flomoWebhookUrl.trim() !== '') patch.flomoWebhookUrl = flomoWebhookUrl.trim()\n if (flomoApiKey.trim() !== '') patch.flomoApiKey = flomoApiKey.trim()\n await api.setConfig(patch)\n await afterSave('flomo 配置已保存。')\n } catch (error) {\n setMessage('保存失败: ' + String(error instanceof Error ? error.message : error))\n } finally {\n setBusy(false)\n }\n }\n\n /** ③ notion: token + target page. */\n const saveNotionConfig = async (): Promise<void> => {\n setBusy(true)\n setMessage('')\n try {\n const patch: Record<string, unknown> = {}\n if (notionToken.trim() !== '') patch.notionToken = notionToken.trim()\n if (notionTargetPageId.trim() !== '') patch.notionTargetPageId = notionTargetPageId.trim()\n await api.setConfig(patch)\n await afterSave('Notion 配置已保存。')\n } catch (error) {\n setMessage('保存失败: ' + String(error instanceof Error ? error.message : error))\n } finally {\n setBusy(false)\n }\n }\n\n /** ④ local: export directory. */\n const saveLocalConfig = async (): Promise<void> => {\n setBusy(true)\n setMessage('')\n try {\n const patch: Record<string, unknown> = {}\n if (localExportDir.trim() !== '') patch.localExportDir = localExportDir.trim()\n await api.setConfig(patch)\n await afterSave('本地导出目录已保存。')\n } catch (error) {\n setMessage('保存失败: ' + String(error instanceof Error ? error.message : error))\n } finally {\n setBusy(false)\n }\n }\n\n /** ⑤ AI: prompt toggle + LLM config + template. */\n const saveAIConfig = async (): Promise<void> => {\n setBusy(true)\n setMessage('')\n try {\n const patch: Record<string, unknown> = { usePrompt }\n if (llmBaseUrl.trim() !== '') patch.llmBaseUrl = llmBaseUrl.trim()\n if (llmApiKey.trim() !== '') patch.llmApiKey = llmApiKey.trim()\n if (llmModel.trim() !== '') patch.llmModel = llmModel.trim()\n if (exportPrompt.trim() !== '') patch.exportPrompt = exportPrompt\n await api.setConfig(patch)\n await afterSave('AI 配置已保存。')\n } catch (error) {\n setMessage('保存失败: ' + String(error instanceof Error ? error.message : error))\n } finally {\n setBusy(false)\n }\n }\n\n const clearConfig = async (): Promise<void> => {\n setBusy(true)\n setMessage('')\n try {\n await api.setConfig({ reset: true })\n await afterSave('已清除全部配置。')\n } catch (error) {\n setMessage('清除失败: ' + String(error instanceof Error ? error.message : error))\n } finally {\n setBusy(false)\n }\n }\n\n const clearFlomoConfig = async (): Promise<void> => {\n setBusy(true)\n setMessage('')\n try {\n await api.setConfig({ flomoReset: true })\n await afterSave('已清除 flomo 配置。')\n } catch (error) {\n setMessage('清除失败: ' + String(error instanceof Error ? error.message : error))\n } finally {\n setBusy(false)\n }\n }\n\n const runTest = async (): Promise<void> => {\n setBusy(true)\n setMessage('测试中…')\n try {\n const result = await api.test()\n setMessage(result.ok ? result.message : '测试失败: ' + result.message)\n } catch (error) {\n setMessage('测试失败: ' + String(error instanceof Error ? error.message : error))\n } finally {\n setBusy(false)\n }\n }\n\n /** Test flomo: saves any unsaved fields first, then sends a test memo. */\n const testFlomoConfig = async (): Promise<void> => {\n setBusy(true)\n setMessage('测试中…(会向 flomo 发送一条测试 MEMO)')\n try {\n if (defaultFlomoTag.trim() !== '' || flomoWebhookUrl.trim() !== '' || flomoApiKey.trim() !== '') {\n const patch: Record<string, unknown> = {}\n if (defaultFlomoTag.trim() !== '') patch.defaultFlomoTag = defaultFlomoTag.trim()\n if (flomoWebhookUrl.trim() !== '') patch.flomoWebhookUrl = flomoWebhookUrl.trim()\n if (flomoApiKey.trim() !== '') patch.flomoApiKey = flomoApiKey.trim()\n await api.setConfig(patch)\n setFlomoWebhookUrl('')\n setFlomoApiKey('')\n setView(await api.getStatus())\n }\n const result = await api.testFlomo()\n setMessage(result.ok ? '✅ ' + result.message : '测试失败: ' + result.message)\n } catch (error) {\n setMessage('测试失败: ' + String(error instanceof Error ? error.message : error))\n } finally {\n setBusy(false)\n }\n }\n\n /** Test Notion: saves any unsaved fields first, then verifies token + page. */\n const testNotionConfig = async (): Promise<void> => {\n setBusy(true)\n setMessage('测试中…')\n try {\n if (notionToken.trim() !== '' || notionTargetPageId.trim() !== '') {\n const patch: Record<string, unknown> = {}\n if (notionToken.trim() !== '') patch.notionToken = notionToken.trim()\n if (notionTargetPageId.trim() !== '') patch.notionTargetPageId = notionTargetPageId.trim()\n await api.setConfig(patch)\n setNotionToken('')\n setView(await api.getStatus())\n }\n const result = await api.testNotion()\n setMessage(result.ok ? '✅ ' + result.message : '测试失败: ' + result.message)\n } catch (error) {\n setMessage('测试失败: ' + String(error instanceof Error ? error.message : error))\n } finally {\n setBusy(false)\n }\n }\n\n /** Open the host OS folder chooser and apply the picked path. */\n const pickFolder = async (apply: (p: string) => void): Promise<void> => {\n setBusy(true)\n setMessage('')\n try {\n const result = await api.pickDir()\n if (result.ok && result.path !== undefined) {\n apply(result.path)\n setMessage('已选择文件夹:' + result.path)\n } else if (result.cancelled === true) {\n setMessage('已取消选择。')\n } else {\n setMessage(result.message ?? '无法弹出文件夹选择(当前环境不支持),请手动输入路径。')\n }\n } catch (error) {\n setMessage('选择文件夹失败: ' + String(error instanceof Error ? error.message : error))\n } finally {\n setBusy(false)\n }\n }\n\n const runSync = async (): Promise<void> => {\n setBusy(true)\n setMessage('同步中…')\n try {\n const result: WereadSyncResult = await api.sync()\n setMessage(result.ok ? result.message : '同步失败: ' + result.message)\n await refresh()\n await reloadBooks()\n } catch (error) {\n setMessage('同步失败: ' + String(error instanceof Error ? error.message : error))\n } finally {\n setBusy(false)\n }\n }\n\n const runExportNow = async (): Promise<void> => {\n if (bookId === '') {\n setMessage('请先同步以加载书籍列表。')\n return\n }\n const dest = destNow || 'flomo'\n if (dest === 'local' && (view?.localExportDir ?? '') === '') {\n setMessage('本地导出需要先在「④ 本地导出」配置导出目录。')\n return\n }\n if (dest === 'all' && (view?.localExportDir ?? '') === '') {\n setMessage('全选导出包含本地目标,但「④ 本地导出」未配置目录;本地部分将跳过(其余目标照常导出)。')\n }\n setBusy(true)\n setMessage('导出中…')\n try {\n const body: Record<string, unknown> = { bookId, dest }\n if (usePromptNow !== null) body.usePrompt = usePromptNow\n const result: WereadFlomoResult = await api.exportData(body)\n setMessage(result.ok ? result.message : '导出失败: ' + result.message)\n } catch (error) {\n setMessage('导出失败: ' + String(error instanceof Error ? error.message : error))\n } finally {\n setBusy(false)\n }\n }\n\n return (\n <div style={s.card}>\n <h3 style={s.title}>微信读书</h3>\n <div style={view?.configured === false ? s.statusWarn : s.status}>{statusText(view)}</div>\n\n <div style={s.group}>\n <div style={s.groupTitle}>① 连接</div>\n <div style={s.row}>\n <input\n style={s.input}\n placeholder=\"微信读书 Skills API Key(wrk- 开头)\"\n value={apiKey}\n onChange={(e) => setApiKey(e.target.value)}\n />\n </div>\n <div style={s.row}>\n <span style={s.label}>导出条数</span>\n <select style={{ ...s.select, flex: 0, minWidth: '104px' }} value={limitChoice} onChange={(e) => setLimitChoice(e.target.value)}>\n <option value=\"0\">全部导出</option>\n <option value=\"20\">20 条(默认)</option>\n <option value=\"50\">50 条</option>\n <option value=\"100\">100 条</option>\n <option value=\"custom\">自定义…</option>\n </select>\n {limitChoice === 'custom' && (\n <input style={{ ...s.input, width: '64px' }} placeholder=\"条数\" value={customLimit} onChange={(e) => setCustomLimit(e.target.value)} />\n )}\n <div style={s.flex} />\n <button style={s.button} onClick={() => void saveMainConfig()} disabled={busy}>保存</button>\n <button style={s.button} onClick={() => void runTest()} disabled={busy || !view?.configured}>测试连接</button>\n <button style={s.button} onClick={() => void clearConfig()} disabled={busy}>清除全部</button>\n </div>\n </div>\n\n <div style={s.group}>\n <div style={s.groupTitle}>② flomo 导出</div>\n <div style={view?.flomoConfigured === false ? s.statusWarn : s.status}>\n {view === null\n ? '加载中…'\n : (view.flomoConfigured\n ? '已配置:' + view.flomoSource + ' ' + view.flomoMasked + '(与「Flomo」面板共享凭据)'\n : '未配置 — 在 flomo 设置页(flomoapp.com/mine?source=incoming_webhook)获取 API URL。')}\n </div>\n <div style={s.row}>\n <span style={s.label}>标签</span>\n <input style={{ ...s.input, width: '160px' }} placeholder=\"如 读书笔记(#)\" value={defaultFlomoTag} onChange={(e) => setDefaultFlomoTag(e.target.value)} />\n </div>\n <div style={s.row}>\n <input style={s.input} type=\"password\" placeholder=\"flomo API URL(https://flomoapp.com/iwh/xxxx)\" value={flomoWebhookUrl} onChange={(e) => setFlomoWebhookUrl(e.target.value)} />\n </div>\n <div style={s.row}>\n <input style={s.input} type=\"password\" placeholder=\"或 flomo API Key(新版,与 URL 二选一)\" value={flomoApiKey} onChange={(e) => setFlomoApiKey(e.target.value)} />\n <button style={s.button} onClick={() => void saveFlomoConfig()} disabled={busy}>保存 flomo</button>\n <button style={s.button} onClick={() => void testFlomoConfig()} disabled={busy || !view?.flomoConfigured}>测试 flomo</button>\n <button style={s.button} onClick={() => void clearFlomoConfig()} disabled={busy}>清除</button>\n </div>\n </div>\n\n <div style={s.group}>\n <div style={s.groupTitle}>③ Notion 导出</div>\n <div style={view?.notionConfigured === false ? s.statusWarn : s.status}>\n {view === null\n ? '加载中…'\n : (view.notionConfigured\n ? '已配置' + (view.notionTargetPageId !== '' ? ' · 目标页已填' : '(未填目标页)')\n : '未配置 — 在 notion.so/my-integrations 创建 Integration 并复制 Token。')}\n </div>\n <div style={s.row}>\n <input style={s.input} type=\"password\" placeholder=\"Notion Integration Token\" value={notionToken} onChange={(e) => setNotionToken(e.target.value)} />\n </div>\n <div style={s.row}>\n <input style={s.input} placeholder=\"目标父页面 URL 或 32 位 ID(页面需分享给该 Integration)\" value={notionTargetPageId} onChange={(e) => setNotionTargetPageId(e.target.value)} />\n </div>\n <div style={s.row}>\n <div style={s.flex} />\n <button style={s.button} onClick={() => void saveNotionConfig()} disabled={busy}>保存 Notion</button>\n <button style={s.button} onClick={() => void testNotionConfig()} disabled={busy || !view?.notionConfigured}>测试 Notion</button>\n </div>\n </div>\n\n <div style={s.group}>\n <div style={s.groupTitle}>④ 本地导出</div>\n <div style={view?.localExportDir === '' ? s.statusWarn : s.status}>\n {view === null ? '加载中…' : (view.localExportDir !== '' ? '已配置:' + view.localExportDir : '未配置 — 选择或填写一个导出目录,导出为 Markdown 文件。')}\n </div>\n <div style={s.row}>\n <input style={s.input} placeholder=\"导出目录(绝对路径)\" value={localExportDir} onChange={(e) => setLocalExportDir(e.target.value)} />\n <button style={s.button} onClick={() => void pickFolder((p) => setLocalExportDir(p))} disabled={busy}>选择文件夹…</button>\n <button style={s.button} onClick={() => void saveLocalConfig()} disabled={busy}>保存本地</button>\n </div>\n </div>\n\n <div style={s.group}>\n <div style={s.groupTitle}>⑤ AI · prompt 处理(导出前用 LLM 按模板整理)</div>\n <div style={s.row}>\n <label style={{ ...s.label, display: 'flex', alignItems: 'center', gap: '4px', cursor: 'pointer' }}>\n <input type=\"checkbox\" checked={usePrompt} onChange={(e) => setUsePrompt(e.target.checked)} />\n 启用\n </label>\n <input style={s.input} placeholder=\"Base URL(默认 https://api.deepseek.com/v1)\" value={llmBaseUrl} onChange={(e) => setLlmBaseUrl(e.target.value)} />\n <input style={{ ...s.input, width: '130px' }} placeholder=\"模型\" value={llmModel} onChange={(e) => setLlmModel(e.target.value)} />\n <input style={s.input} type=\"password\" placeholder=\"LLM API Key(自定义)\" value={llmApiKey} onChange={(e) => setLlmApiKey(e.target.value)} />\n </div>\n <div style={s.col}>\n <textarea\n style={s.textarea}\n placeholder=\"导出 prompt 模板,可用 {title} {author} {highlights} {thoughts} 占位符\"\n value={exportPrompt}\n onChange={(e) => setExportPrompt(e.target.value)}\n />\n </div>\n <div style={s.row}>\n <div style={s.flex} />\n <button style={s.button} onClick={() => void saveAIConfig()} disabled={busy}>保存 AI</button>\n </div>\n </div>\n\n <div style={s.group}>\n <div style={s.groupTitle}>⑥ 快捷导出</div>\n <div style={s.row}>\n <span style={s.label}>选择书籍</span>\n <select style={s.select} value={bookId} onChange={(e) => setBookId(e.target.value)} disabled={books.length === 0}>\n {books.length === 0 ? <option value=\"\">(缓存无书籍,先同步)</option> : books.map((b) => (\n <option key={b.bookId} value={b.bookId}>《{b.title}》{b.author !== '' ? ' · ' + b.author : ''}</option>\n ))}\n </select>\n <button style={s.button} onClick={() => void runSync()} disabled={busy || !view?.configured}>同步书架/笔记</button>\n </div>\n <div style={s.row}>\n <span style={s.label}>导出到</span>\n <select style={{ ...s.select, flex: 0, minWidth: '130px' }} value={destNow} onChange={(e) => setDestNow(e.target.value)}>\n <option value=\"flomo\">flomo</option>\n <option value=\"local\">本地文件</option>\n <option value=\"notion\">Notion</option>\n <option value=\"all\">全选(flomo+本地+Notion)</option>\n </select>\n <label style={{ ...s.label, display: 'flex', alignItems: 'center', gap: '4px', cursor: 'pointer' }}>\n <input\n type=\"checkbox\"\n checked={usePromptNow === null ? (view?.usePrompt ?? false) : usePromptNow}\n onChange={(e) => setUsePromptNow(e.target.checked)}\n />\n 本次用 prompt\n </label>\n <div style={s.flex} />\n <button style={s.button} onClick={() => void runExportNow()} disabled={busy || !view?.configured}>\n 导出到 {destNow === 'all' ? '全部' : (destNow === 'local' ? '本地' : (destNow === 'notion' ? 'Notion' : 'flomo'))}\n </button>\n </div>\n </div>\n\n {message !== '' && <div style={s.msg}>{message}</div>}\n <div style={s.hint}>\n 【API Key】打开 https://weread.qq.com/r/weread-skills → 微信读书账号登录 → 「创建 Key」→ 复制(wrk- 开头)。Key 绑定你的账号身份,请勿泄露。存于 ~/.dsh/weread-export.json(权限 0600)。\n </div>\n <div style={s.hint}>\n 【各目标独立配置】②flomo:标签 + API URL/Key(与「Flomo」面板共享凭据);③Notion:Integration Token + 目标页(页面需分享给该 Integration);④本地:导出目录(导出为 Markdown 文件)。每个卡片「保存/测试」独立操作。\n </div>\n <div style={s.hint}>\n 【导出】「⑥ 快捷导出」只选目标:flomo / 本地 / Notion / 全选(一次导出到所有已配置目标,未配置的跳过并说明);prompt 处理可选,导出前按 ⑤ 模板整理一次再分发。\n </div>\n <div style={s.hint}>\n 【导出条数】「全部导出」= 全部划线都导出(flomo 超长自动拆多条);「20/50/100/自定义」= 最多导出的条数。\n 同步后可用 weread_shelf / weread_notes / weread_readdata / weread_search / weread_book / weread_export 等工具。\n </div>\n </div>\n )\n}\n","/**\n * weread-export — browser half. Registers the WeRead settings panel into the\n * web settings page (settings.section entry). The panel configures the\n * Skills API key, default flomo tag, drives manual syncs, and quick-exports\n * highlights to flomo. Failure policy: registration problems are logged,\n * never thrown — the web shell fails the whole boot when a plugin apply\n * throws, and an external plugin must not take the GUI down.\n */\n// Type-only: pulls the settings-surface SlotMap merge (the 'settings.section'\n// entry) and the client runtime Context merge.\nimport type {} from '@deepseek-ai/dsh-client-ui-settings/client'\nimport type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'\nimport { WereadSettingsPanel } from './WereadSettingsPanel.tsx'\n\n/** Required services. */\nexport const inject = ['slots']\n\n/**\n * Register the WeRead settings page.\n * @param ctx - client root context.\n */\nexport function apply(ctx: ClientContext): void {\n try {\n ctx.slots.inject('settings.section', () => ctx.slots.register({\n name: 'settings.section',\n id: 'weread',\n order: 316,\n label: () => '微信读书',\n }, WereadSettingsPanel))\n } catch (error) {\n console.warn('[weread-export] settings panel registration failed:', error)\n }\n}\n"],"mappings":"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"}
|
package/lib/index.js
CHANGED
|
@@ -1943,7 +1943,9 @@ const WEREAD_API = {
|
|
|
1943
1943
|
export: "/api/weread-export/export",
|
|
1944
1944
|
flomo: "/api/weread-export/flomo",
|
|
1945
1945
|
books: "/api/weread-export/books",
|
|
1946
|
-
pickDir: "/api/weread-export/pick-dir"
|
|
1946
|
+
pickDir: "/api/weread-export/pick-dir",
|
|
1947
|
+
testFlomo: "/api/weread-export/test-flomo",
|
|
1948
|
+
testNotion: "/api/weread-export/test-notion"
|
|
1947
1949
|
};
|
|
1948
1950
|
/** Cap on JSON request bodies. */
|
|
1949
1951
|
const MAX_JSON_BODY_BYTES = 256 * 1024;
|
|
@@ -2166,6 +2168,109 @@ function makeRoutes(deps) {
|
|
|
2166
2168
|
}
|
|
2167
2169
|
}
|
|
2168
2170
|
},
|
|
2171
|
+
{
|
|
2172
|
+
kind: "exact",
|
|
2173
|
+
path: WEREAD_API.testFlomo,
|
|
2174
|
+
handler: async (req, res) => {
|
|
2175
|
+
if (!guard(req, res, "POST")) return;
|
|
2176
|
+
const url = await resolveFlomoUrl();
|
|
2177
|
+
if (url === null) {
|
|
2178
|
+
writeJson(res, 200, {
|
|
2179
|
+
ok: false,
|
|
2180
|
+
message: "flomo 未配置:请先在「② flomo 导出」填写 API URL / API Key 并保存。"
|
|
2181
|
+
});
|
|
2182
|
+
return;
|
|
2183
|
+
}
|
|
2184
|
+
try {
|
|
2185
|
+
writeJson(res, 200, await postMemo(url, "✅ weread-export 测试:flomo 配置有效(" + (/* @__PURE__ */ new Date()).toISOString() + ")"));
|
|
2186
|
+
} catch (error) {
|
|
2187
|
+
writeJson(res, 200, {
|
|
2188
|
+
ok: false,
|
|
2189
|
+
message: "测试失败:" + String(error instanceof Error ? error.message : error)
|
|
2190
|
+
});
|
|
2191
|
+
}
|
|
2192
|
+
}
|
|
2193
|
+
},
|
|
2194
|
+
{
|
|
2195
|
+
kind: "exact",
|
|
2196
|
+
path: WEREAD_API.testNotion,
|
|
2197
|
+
handler: async (req, res) => {
|
|
2198
|
+
if (!guard(req, res, "POST")) return;
|
|
2199
|
+
if (!(await store.view()).notionConfigured) {
|
|
2200
|
+
writeJson(res, 200, {
|
|
2201
|
+
ok: false,
|
|
2202
|
+
message: "Notion 未配置:请先在「③ Notion 导出」填写 Integration Token 并保存。"
|
|
2203
|
+
});
|
|
2204
|
+
return;
|
|
2205
|
+
}
|
|
2206
|
+
const creds = await store.load();
|
|
2207
|
+
try {
|
|
2208
|
+
const meRes = await fetch("https://api.notion.com/v1/users/me", {
|
|
2209
|
+
headers: {
|
|
2210
|
+
"Authorization": "Bearer " + creds.notionToken.trim(),
|
|
2211
|
+
"Notion-Version": NOTION_VERSION
|
|
2212
|
+
},
|
|
2213
|
+
signal: AbortSignal.timeout(15e3)
|
|
2214
|
+
});
|
|
2215
|
+
if (!meRes.ok) {
|
|
2216
|
+
const body = await meRes.text().catch(() => "");
|
|
2217
|
+
writeJson(res, 200, {
|
|
2218
|
+
ok: false,
|
|
2219
|
+
message: "Token 无效(HTTP " + meRes.status + "):" + body.slice(0, 200) + "。请检查 Token 是否正确。"
|
|
2220
|
+
});
|
|
2221
|
+
return;
|
|
2222
|
+
}
|
|
2223
|
+
const workspace = (await meRes.json().catch(() => ({}))).name ?? "未知工作区";
|
|
2224
|
+
if (creds.notionTargetPageId.trim() === "") {
|
|
2225
|
+
writeJson(res, 200, {
|
|
2226
|
+
ok: true,
|
|
2227
|
+
message: "Token 有效(工作区:" + workspace + ")。未配置目标页面,导出时还需在「③ Notion 导出」填写目标页。"
|
|
2228
|
+
});
|
|
2229
|
+
return;
|
|
2230
|
+
}
|
|
2231
|
+
let pageId;
|
|
2232
|
+
try {
|
|
2233
|
+
pageId = normalizeNotionPageId(creds.notionTargetPageId);
|
|
2234
|
+
} catch (error) {
|
|
2235
|
+
writeJson(res, 200, {
|
|
2236
|
+
ok: false,
|
|
2237
|
+
message: "Token 有效(工作区:" + workspace + "),但目标页面 ID 无法解析:" + String(error instanceof Error ? error.message : error)
|
|
2238
|
+
});
|
|
2239
|
+
return;
|
|
2240
|
+
}
|
|
2241
|
+
const pageRes = await fetch("https://api.notion.com/v1/pages/" + pageId, {
|
|
2242
|
+
headers: {
|
|
2243
|
+
"Authorization": "Bearer " + creds.notionToken.trim(),
|
|
2244
|
+
"Notion-Version": NOTION_VERSION
|
|
2245
|
+
},
|
|
2246
|
+
signal: AbortSignal.timeout(15e3)
|
|
2247
|
+
});
|
|
2248
|
+
if (pageRes.status === 404) {
|
|
2249
|
+
writeJson(res, 200, {
|
|
2250
|
+
ok: false,
|
|
2251
|
+
message: "Token 有效(工作区:" + workspace + "),但目标页面不可访问(404):请先在 Notion 中把该页面分享给此 Integration(页面右上角 ··· → Connections → 添加 Integration)。"
|
|
2252
|
+
});
|
|
2253
|
+
return;
|
|
2254
|
+
}
|
|
2255
|
+
if (!pageRes.ok) {
|
|
2256
|
+
writeJson(res, 200, {
|
|
2257
|
+
ok: false,
|
|
2258
|
+
message: "Token 有效(工作区:" + workspace + "),但读取目标页失败(HTTP " + pageRes.status + ")。"
|
|
2259
|
+
});
|
|
2260
|
+
return;
|
|
2261
|
+
}
|
|
2262
|
+
writeJson(res, 200, {
|
|
2263
|
+
ok: true,
|
|
2264
|
+
message: "✅ Notion 配置有效:工作区「" + workspace + "」,目标页面可访问。"
|
|
2265
|
+
});
|
|
2266
|
+
} catch (error) {
|
|
2267
|
+
writeJson(res, 200, {
|
|
2268
|
+
ok: false,
|
|
2269
|
+
message: "测试失败:" + String(error instanceof Error ? error.message : error)
|
|
2270
|
+
});
|
|
2271
|
+
}
|
|
2272
|
+
}
|
|
2273
|
+
},
|
|
2169
2274
|
{
|
|
2170
2275
|
kind: "exact",
|
|
2171
2276
|
path: WEREAD_API.books,
|
|
@@ -74,6 +74,16 @@ export declare class WereadApi {
|
|
|
74
74
|
unsupported?: boolean;
|
|
75
75
|
message?: string;
|
|
76
76
|
}>;
|
|
77
|
+
/** Send a test memo to flomo to verify the shared credential. */
|
|
78
|
+
testFlomo(): Promise<{
|
|
79
|
+
ok: boolean;
|
|
80
|
+
message: string;
|
|
81
|
+
}>;
|
|
82
|
+
/** Verify the Notion token and target page accessibility. */
|
|
83
|
+
testNotion(): Promise<{
|
|
84
|
+
ok: boolean;
|
|
85
|
+
message: string;
|
|
86
|
+
}>;
|
|
77
87
|
exportFlomo(bookId: string, tag: string, limit?: number): Promise<WereadFlomoResult>;
|
|
78
88
|
/** Multi-target export: flomo / local / notion with optional prompt. */
|
|
79
89
|
exportData(body: Record<string, unknown>): Promise<WereadFlomoResult>;
|
package/lib/types/routes.d.ts
CHANGED
|
@@ -26,6 +26,8 @@ export declare const WEREAD_API: {
|
|
|
26
26
|
readonly flomo: "/api/weread-export/flomo";
|
|
27
27
|
readonly books: "/api/weread-export/books";
|
|
28
28
|
readonly pickDir: "/api/weread-export/pick-dir";
|
|
29
|
+
readonly testFlomo: "/api/weread-export/test-flomo";
|
|
30
|
+
readonly testNotion: "/api/weread-export/test-notion";
|
|
29
31
|
};
|
|
30
32
|
/** Route handler context. */
|
|
31
33
|
export interface RouteContext {
|
|
@@ -58,6 +60,14 @@ export declare function makeRoutes(deps: RouteContext): ({
|
|
|
58
60
|
kind: "exact";
|
|
59
61
|
path: "/api/weread-export/pick-dir";
|
|
60
62
|
handler: (req: IncomingMessage, res: ServerResponse) => Promise<void>;
|
|
63
|
+
} | {
|
|
64
|
+
kind: "exact";
|
|
65
|
+
path: "/api/weread-export/test-flomo";
|
|
66
|
+
handler: (req: IncomingMessage, res: ServerResponse) => Promise<void>;
|
|
67
|
+
} | {
|
|
68
|
+
kind: "exact";
|
|
69
|
+
path: "/api/weread-export/test-notion";
|
|
70
|
+
handler: (req: IncomingMessage, res: ServerResponse) => Promise<void>;
|
|
61
71
|
} | {
|
|
62
72
|
kind: "exact";
|
|
63
73
|
path: "/api/weread-export/books";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "weread-export",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "微信读书 (WeChat Reading) integration for DeepSeek Harness: connect the official WeRead Skills Agent Gateway with a wrk- API key, then browse your bookshelf, export highlights & thoughts, query reading stats, and send highlights to flomo — agent tools plus a web settings panel",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|