dsh-free-search 0.9.21 → 0.9.23
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/README.md +46 -6
- package/lib/client.js +183 -178
- package/lib/index.js +75 -167
- package/lib/prompt_i18n.js +366 -312
- package/lib/research.js +5 -4
- package/lib/vertical.js +2 -1
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -31,7 +31,7 @@ window.__ModuleLoader__.load({
|
|
|
31
31
|
".dshfs-body{flex-direction:column;gap:20px;padding:0 20px 20px;display:flex;animation:dshfsFadeIn 240ms cubic-bezier(.4,0,.2,1) forwards}",
|
|
32
32
|
"@media (prefers-reduced-motion: reduce){.dshfs-body{animation:none}}",
|
|
33
33
|
".dshfs-rail{display:none;position:fixed;top:50%;transform:translateY(-50%);flex-direction:column;gap:2px;max-height:70vh;overflow:auto;z-index:5;padding:2px}",
|
|
34
|
-
".dshfs-railItem{display:flex;align-items:center;gap:6px;padding:3px 2px;border:0;background:none;font:inherit;font-size:12px;line-height:1.5;color:inherit;opacity:.42;text-align:left;cursor:pointer;transition:opacity 120ms}",
|
|
34
|
+
".dshfs-railItem{display:flex;align-items:center;gap:6px;padding:3px 2px;border:0;background:none;font:inherit;font-size:12px;line-height:1.5;color:inherit;opacity:.42;text-align:left;cursor:pointer;transition:opacity 120ms;text-decoration:none}",
|
|
35
35
|
".dshfs-railItem:hover{opacity:.85}",
|
|
36
36
|
".dshfs-railItemActive{opacity:.95;font-weight:600}",
|
|
37
37
|
".dshfs-railDot{flex:none;width:4px;height:4px;border-radius:50%;background:currentColor;opacity:.45}",
|
|
@@ -53,6 +53,9 @@ window.__ModuleLoader__.load({
|
|
|
53
53
|
"@media (prefers-color-scheme: dark){.dshfs-testOk{color:#30d158}}",
|
|
54
54
|
".dshfs-resultRow{display:flex;flex-direction:column;align-items:flex-start;gap:6px;min-width:0;margin-top:-4px}",
|
|
55
55
|
".dshfs-field{flex-direction:column;gap:8px;min-width:0;display:flex}",
|
|
56
|
+
".dshfs-preview{margin:0;max-height:260px;overflow:auto;white-space:pre-wrap;word-break:break-word;font-family:ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;font-size:12px;line-height:1.5;padding:10px 12px;border:1px solid rgba(0,0,0,.1);border-radius:10px;background:rgba(0,0,0,.03)}",
|
|
57
|
+
"@media (prefers-color-scheme: dark){.dshfs-preview{border-color:rgba(255,255,255,.1);background:rgba(255,255,255,.04)}}",
|
|
58
|
+
|
|
56
59
|
".dshfs-label{color:inherit;font-size:14px;font-weight:500;display:flex;align-items:center;gap:8px}",
|
|
57
60
|
".dshfs-select, .dshfs-input{border:1px solid rgba(0,0,0,.1);font:inherit;font-variant-numeric:tabular-nums;color:inherit;background:rgba(0,0,0,.03);border-radius:10px;padding:0 12px;height:38px;font-size:14px;transition:all 160ms cubic-bezier(.4,0,.2,1);width:100%;box-sizing:border-box}",
|
|
58
61
|
"@media (prefers-color-scheme: dark){.dshfs-select, .dshfs-input{border:1px solid rgba(255,255,255,.1);background:rgba(255,255,255,.05)}}",
|
|
@@ -123,9 +126,6 @@ window.__ModuleLoader__.load({
|
|
|
123
126
|
".dshfs-save:hover:not(:disabled){background:#0077ed;transform:translateY(-1px);box-shadow:0 2px 4px rgba(0,0,0,.15)}",
|
|
124
127
|
".dshfs-save:active:not(:disabled){transform:scale(0.98)}",
|
|
125
128
|
"@media (prefers-color-scheme: dark){.dshfs-save{background:#0a84ff;color:#ffffff}.dshfs-save:hover:not(:disabled){background:#1e8eff}}",
|
|
126
|
-
".dshfs-upgrade{border:1px solid rgba(52,199,89,.4);background:rgba(52,199,89,.15);color:#248a3d}",
|
|
127
|
-
"@media (prefers-color-scheme: dark){.dshfs-upgrade{color:#30d158}}",
|
|
128
|
-
".dshfs-upgrade:hover:not(:disabled){background:rgba(52,199,89,.25)}",
|
|
129
129
|
".dshfs-badge{background:rgba(0,0,0,.05);color:#1d1d1f;white-space:nowrap;border-radius:999px;flex:none;padding:2px 8px;font-size:11px;font-weight:600;letter-spacing:0.3px}",
|
|
130
130
|
"@media (prefers-color-scheme: dark){.dshfs-badge{background:rgba(255,255,255,.1);color:#f5f5f7}}",
|
|
131
131
|
".dshfs-badgeAuto{background:rgba(175,82,222,.15);color:#af52de;border:1px solid rgba(175,82,222,.2)}",
|
|
@@ -140,15 +140,7 @@ window.__ModuleLoader__.load({
|
|
|
140
140
|
"@media (prefers-color-scheme: dark){.dshfs-langToggle{color:#a1a1a6}}",
|
|
141
141
|
".dshfs-update{color:#cc7a00;flex:none;font-size:12px;white-space:nowrap;border:1px solid rgba(255,149,0,.3);background:rgba(255,149,0,.12);border-radius:999px;padding:2px 8px}",
|
|
142
142
|
"@media (prefers-color-scheme: dark){.dshfs-update{color:#ff9f0a}}",
|
|
143
|
-
".dshfs-updateOk{color:#248a3d;flex:none;font-size:12px;white-space:nowrap;border:1px solid rgba(52,199,89,.3);background:rgba(52,199,89,.12);border-radius:999px;padding:2px 8px}",
|
|
144
|
-
"@media (prefers-color-scheme: dark){.dshfs-updateOk{color:#30d158}}",
|
|
145
143
|
".dshfs-updateLine{display:flex;align-items:center;gap:10px;flex-wrap:wrap}",
|
|
146
|
-
".dshfs-updatePill{display:inline-flex;align-items:center;gap:6px;border:1px solid rgba(0,113,227,.2);background:rgba(0,113,227,.1);color:#0071e3;font:inherit;font-size:13px;font-weight:500;line-height:1;cursor:pointer;padding:6px 12px;border-radius:999px;text-decoration:none;white-space:nowrap;transition:all 180ms cubic-bezier(.4,0,.2,1)}",
|
|
147
|
-
"@media (prefers-color-scheme: dark){.dshfs-updatePill{color:#0a84ff;background:rgba(10,132,255,.15);border-color:rgba(10,132,255,.25)}}",
|
|
148
|
-
".dshfs-updatePill:hover:not(:disabled){background:rgba(0,113,227,.15);border-color:rgba(0,113,227,.3)}",
|
|
149
|
-
"@media (prefers-color-scheme: dark){.dshfs-updatePill:hover:not(:disabled){background:rgba(10,132,255,.2);border-color:rgba(10,132,255,.4)}}",
|
|
150
|
-
".dshfs-updatePill:disabled{opacity:.5;cursor:default}",
|
|
151
|
-
".dshfs-updateIcon{flex:none;display:block}",
|
|
152
144
|
".dshfs-version{color:#6e6e73;font-size:12px;font-variant-numeric:tabular-nums;white-space:nowrap}",
|
|
153
145
|
"@media (prefers-color-scheme: dark){.dshfs-version{color:#a1a1a6}}"
|
|
154
146
|
].join("");
|
|
@@ -255,18 +247,6 @@ window.__ModuleLoader__.load({
|
|
|
255
247
|
testOk: (r) => `✓ ${r.count} 条结果(引擎: ${r.engine})${r.content ? ` — ${r.content}` : ""}${r.sample ? ` · 例如 "${r.sample.slice(0, 40)}"` : ""}`,
|
|
256
248
|
testFail: (e) => `✗ ${e}`,
|
|
257
249
|
toggleLang: "EN",
|
|
258
|
-
checkUpdate: "检查更新",
|
|
259
|
-
checkingUpdate: "检查中…",
|
|
260
|
-
updateAvailable: (c, l) => `发现新版本 v${l}(当前 v${c})`,
|
|
261
|
-
updateLatest: (c) => `已是最新版本 v${c}`,
|
|
262
|
-
updateCheckFailed: "检查更新失败(无法访问 npm registry)",
|
|
263
|
-
updateView: "查看 →",
|
|
264
|
-
hasUpdate: "有更新",
|
|
265
|
-
upgrade: "升级",
|
|
266
|
-
upgrading: "升级中…",
|
|
267
|
-
upgradeLinkMode: "(本地开发模式,升级请用 git pull)",
|
|
268
|
-
upgradeDone: (l) => `升级到 v${l} 完成,重启 dsh 后生效`,
|
|
269
|
-
upgradeFailed: (m) => `升级失败:${m}`,
|
|
270
250
|
safeSearchLabel: "安全搜索过滤 (adlt)",
|
|
271
251
|
safeSearchOff: "关闭 —— 引擎默认(不加参数)",
|
|
272
252
|
safeSearchModerate: "中等 —— Bing 默认",
|
|
@@ -288,6 +268,11 @@ window.__ModuleLoader__.load({
|
|
|
288
268
|
navBehavior: "搜索行为",
|
|
289
269
|
navFetch: "抓取",
|
|
290
270
|
navPerformance: "性能",
|
|
271
|
+
navPrompt: "提示词",
|
|
272
|
+
promptLabel: "提示词",
|
|
273
|
+
promptPreviewToggle: "预览",
|
|
274
|
+
|
|
275
|
+
toolsLabel: "工具开关",
|
|
291
276
|
marketZhCN: "zh-CN —— 中国大陆(默认)",
|
|
292
277
|
marketZhTW: "zh-TW —— 台湾",
|
|
293
278
|
marketEnUS: "en-US —— 美国",
|
|
@@ -388,18 +373,6 @@ window.__ModuleLoader__.load({
|
|
|
388
373
|
testOk: (r) => `✓ ${r.count} results (engine: ${r.engine})${r.content ? ` — ${r.content}` : ""}${r.sample ? ` · e.g. "${r.sample.slice(0, 40)}"` : ""}`,
|
|
389
374
|
testFail: (e) => `✗ ${e}`,
|
|
390
375
|
toggleLang: "中文",
|
|
391
|
-
checkUpdate: "Check update",
|
|
392
|
-
checkingUpdate: "Checking…",
|
|
393
|
-
updateAvailable: (c, l) => `New version v${l} available (current v${c})`,
|
|
394
|
-
updateLatest: (c) => `You're on the latest version v${c}`,
|
|
395
|
-
updateCheckFailed: "Update check failed (cannot reach npm registry)",
|
|
396
|
-
updateView: "View →",
|
|
397
|
-
hasUpdate: "Update available",
|
|
398
|
-
upgrade: "Upgrade",
|
|
399
|
-
upgrading: "Upgrading…",
|
|
400
|
-
upgradeLinkMode: "(local dev install - use git pull to update)",
|
|
401
|
-
upgradeDone: (l) => `Upgraded to v${l} - restart dsh to apply`,
|
|
402
|
-
upgradeFailed: (m) => `Upgrade failed: ${m}`,
|
|
403
376
|
safeSearchLabel: "Safe search filter (adlt)",
|
|
404
377
|
safeSearchOff: "Off - engine default (no filtering)",
|
|
405
378
|
safeSearchModerate: "Moderate - Bing default",
|
|
@@ -421,6 +394,11 @@ window.__ModuleLoader__.load({
|
|
|
421
394
|
navBehavior: "Search behaviour",
|
|
422
395
|
navFetch: "Fetch",
|
|
423
396
|
navPerformance: "Performance",
|
|
397
|
+
navPrompt: "Prompt",
|
|
398
|
+
promptLabel: "Prompt",
|
|
399
|
+
promptPreviewToggle: "Preview",
|
|
400
|
+
|
|
401
|
+
toolsLabel: "Tools",
|
|
424
402
|
marketZhCN: "zh-CN - China (default)",
|
|
425
403
|
marketZhTW: "zh-TW - Taiwan",
|
|
426
404
|
marketEnUS: "en-US - United States",
|
|
@@ -437,7 +415,20 @@ window.__ModuleLoader__.load({
|
|
|
437
415
|
};
|
|
438
416
|
const tt = (lang) => I18N[String(lang ?? "").trim().toLowerCase() === "zh" ? "zh" : "en"];
|
|
439
417
|
// 当前插件版本(与 lib/index.js 的 PLUGIN_VERSION 保持一致;跟随上游 v0.7.5)
|
|
440
|
-
const
|
|
418
|
+
const PROMPT_BUILTIN_PAGES = 2;
|
|
419
|
+
// #102:可开关的工具(web_search / web_fetch 属于 DSH 内核,不在我们这边注册)
|
|
420
|
+
const TOOL_TOGGLES = [
|
|
421
|
+
"free_search_test",
|
|
422
|
+
"platform_search",
|
|
423
|
+
"video_search",
|
|
424
|
+
"weather",
|
|
425
|
+
"advanced_search",
|
|
426
|
+
"multi_search",
|
|
427
|
+
"research",
|
|
428
|
+
"cache_search",
|
|
429
|
+
"domain_search",
|
|
430
|
+
];
|
|
431
|
+
const PLUGIN_VERSION = "0.9.23";
|
|
441
432
|
const PLUGIN_REPO_URL = "https://github.com/DDDMUC/dsh-free-search";
|
|
442
433
|
// 右侧浮动目录(#85):条目顺序即页面顺序,anchor 是页面里对应 field 的 id。
|
|
443
434
|
const NAV_ITEMS = [
|
|
@@ -447,6 +438,7 @@ window.__ModuleLoader__.load({
|
|
|
447
438
|
{ id: "behavior", anchor: "dshfs-anchor-behavior" },
|
|
448
439
|
{ id: "fetch", anchor: "dshfs-anchor-fetch" },
|
|
449
440
|
{ id: "performance", anchor: "dshfs-anchor-performance" },
|
|
441
|
+
{ id: "prompt", anchor: "dshfs-anchor-prompt" },
|
|
450
442
|
];
|
|
451
443
|
// 目录跟着真正滚动的祖先走:DSH 页面通常不是 window 在滚。
|
|
452
444
|
const findScrollParent = (element) => {
|
|
@@ -585,15 +577,6 @@ window.__ModuleLoader__.load({
|
|
|
585
577
|
return response.json();
|
|
586
578
|
}
|
|
587
579
|
|
|
588
|
-
async function bridgeCheckUpdate() {
|
|
589
|
-
const response = await fetch(`${BRIDGE_PREFIX}/check-update`, {
|
|
590
|
-
method: "POST",
|
|
591
|
-
headers: { "content-type": "application/json" },
|
|
592
|
-
body: "{}",
|
|
593
|
-
});
|
|
594
|
-
return response.json();
|
|
595
|
-
}
|
|
596
|
-
|
|
597
580
|
async function bridgeCredentialsStatus() {
|
|
598
581
|
const response = await fetch(`${BRIDGE_PREFIX}/credentials-status`, {
|
|
599
582
|
method: "POST",
|
|
@@ -729,6 +712,11 @@ window.__ModuleLoader__.load({
|
|
|
729
712
|
const [lang, setLang] = react.useState("zh");
|
|
730
713
|
const [uiLang, setUiLang] = react.useState("");
|
|
731
714
|
const [promptLanguage, setPromptLanguage] = react.useState("en");
|
|
715
|
+
const [promptPages, setPromptPages] = react.useState([]);
|
|
716
|
+
const [promptPageIndex, setPromptPageIndex] = react.useState(0);
|
|
717
|
+
const [disabledTools, setDisabledTools] = react.useState([]);
|
|
718
|
+
const [builtinPrompts, setBuiltinPrompts] = react.useState({ en: "", zh: "" });
|
|
719
|
+
const [previewOpen, setPreviewOpen] = react.useState(false);
|
|
732
720
|
const [dirty, setDirty] = react.useState(false);
|
|
733
721
|
const [saving, setSaving] = react.useState(false);
|
|
734
722
|
const [failed, setFailed] = react.useState(false);
|
|
@@ -736,9 +724,6 @@ window.__ModuleLoader__.load({
|
|
|
736
724
|
const [saveError, setSaveError] = react.useState("");
|
|
737
725
|
const [testing, setTesting] = react.useState(false);
|
|
738
726
|
const [testResult, setTestResult] = react.useState(null);
|
|
739
|
-
const [checkingUpdate, setCheckingUpdate] = react.useState(false);
|
|
740
|
-
const [upgrading, setUpgrading] = react.useState(false);
|
|
741
|
-
const [updateInfo, setUpdateInfo] = react.useState(null);
|
|
742
727
|
// 回退优先级/池列表行内 key 输入:引擎 id → [当前值, setter](zhihu 两引擎共享 ZHIHU_API_KEY)
|
|
743
728
|
const KEY_INPUTS = {
|
|
744
729
|
anysearch: [anysearchKey, setAnysearchKey],
|
|
@@ -792,6 +777,10 @@ window.__ModuleLoader__.load({
|
|
|
792
777
|
try {
|
|
793
778
|
const result = await bridgeDescribe();
|
|
794
779
|
if (result.ok) {
|
|
780
|
+
const builtin = result.value.promptBuiltin;
|
|
781
|
+
if (builtin && typeof builtin === "object") {
|
|
782
|
+
setBuiltinPrompts({ en: String(builtin.en ?? ""), zh: String(builtin.zh ?? "") });
|
|
783
|
+
}
|
|
795
784
|
const view = result.value.namespaces.find((n) => n.ns === NS);
|
|
796
785
|
if (view) {
|
|
797
786
|
const v = view.value ?? {};
|
|
@@ -813,6 +802,9 @@ window.__ModuleLoader__.load({
|
|
|
813
802
|
setLang(v.lang ?? "zh");
|
|
814
803
|
setUiLang(typeof v.uiLang === "string" ? v.uiLang : "");
|
|
815
804
|
setPromptLanguage(v.promptLanguage === "zh" ? "zh" : "en");
|
|
805
|
+
setPromptPages(Array.isArray(v.promptPages) ? v.promptPages.map((page) => String(page ?? "")) : []);
|
|
806
|
+
setPromptPageIndex(Number.isFinite(v.promptPageIndex) ? Math.max(0, Math.floor(v.promptPageIndex)) : 0);
|
|
807
|
+
setDisabledTools(Array.isArray(v.disabledTools) ? v.disabledTools.map((id) => String(id)) : []);
|
|
816
808
|
setAnysearchKey(v.anysearchApiKey ?? "");
|
|
817
809
|
setExaKey(v.exaApiKey ?? "");
|
|
818
810
|
setTavilyKey(v.tavilyApiKey ?? "");
|
|
@@ -961,6 +953,9 @@ window.__ModuleLoader__.load({
|
|
|
961
953
|
ops.push({ op: "set", path: ["blockedDomains"], value: parseBlockedDomains(blockedDomains).domains });
|
|
962
954
|
ops.push({ op: "set", path: ["uiLang"], value: uiLang });
|
|
963
955
|
ops.push({ op: "set", path: ["promptLanguage"], value: promptLanguage });
|
|
956
|
+
ops.push({ op: "set", path: ["promptPages"], value: promptPages });
|
|
957
|
+
ops.push({ op: "set", path: ["promptPageIndex"], value: Math.max(0, Math.min(promptPageIndex, PROMPT_BUILTIN_PAGES + promptPages.length - 1)) });
|
|
958
|
+
ops.push({ op: "set", path: ["disabledTools"], value: disabledTools });
|
|
964
959
|
ops.push({ op: "set", path: ["keyStorage"], value: keyStorage });
|
|
965
960
|
ops.push({ op: "set", path: ["safeSearch"], value: safeSearch });
|
|
966
961
|
ops.push({ op: "set", path: ["bingMarket"], value: bingMarket });
|
|
@@ -1040,51 +1035,6 @@ window.__ModuleLoader__.load({
|
|
|
1040
1035
|
}
|
|
1041
1036
|
};
|
|
1042
1037
|
|
|
1043
|
-
const runCheckUpdate = async () => {
|
|
1044
|
-
setCheckingUpdate(true);
|
|
1045
|
-
setUpdateInfo(null);
|
|
1046
|
-
setFailed(false);
|
|
1047
|
-
setSaveError("");
|
|
1048
|
-
try {
|
|
1049
|
-
const result = await bridgeCheckUpdate();
|
|
1050
|
-
if (result.ok) {
|
|
1051
|
-
setUpdateInfo({ ok: true, ...result.value });
|
|
1052
|
-
} else {
|
|
1053
|
-
setUpdateInfo({ ok: false });
|
|
1054
|
-
}
|
|
1055
|
-
} catch {
|
|
1056
|
-
setUpdateInfo({ ok: false });
|
|
1057
|
-
} finally {
|
|
1058
|
-
setCheckingUpdate(false);
|
|
1059
|
-
}
|
|
1060
|
-
};
|
|
1061
|
-
|
|
1062
|
-
const runUpdate = async () => {
|
|
1063
|
-
setUpgrading(true);
|
|
1064
|
-
setUpdateInfo(null);
|
|
1065
|
-
setFailed(false);
|
|
1066
|
-
setSaveError("");
|
|
1067
|
-
try {
|
|
1068
|
-
const response = await fetch(`${BRIDGE_PREFIX}/update`, {
|
|
1069
|
-
method: "POST",
|
|
1070
|
-
headers: { "content-type": "application/json" },
|
|
1071
|
-
body: "{}",
|
|
1072
|
-
});
|
|
1073
|
-
const result = await response.json();
|
|
1074
|
-
if (result.ok) {
|
|
1075
|
-
setUpdateInfo({ ok: true, hasUpdate: false, upgraded: true, message: result.value.message, latest: result.value.latest });
|
|
1076
|
-
} else {
|
|
1077
|
-
setUpdateInfo({ ok: false, upgradeFailed: result.message ?? "upgrade failed" });
|
|
1078
|
-
}
|
|
1079
|
-
} catch {
|
|
1080
|
-
setUpdateInfo({ ok: false, upgradeFailed: "request failed" });
|
|
1081
|
-
} finally {
|
|
1082
|
-
setUpgrading(false);
|
|
1083
|
-
}
|
|
1084
|
-
};
|
|
1085
|
-
|
|
1086
|
-
// 浮动目录(#85):放在卡片右侧的页面留白里,绝对定位不参与卡片布局,
|
|
1087
|
-
// 所以正文宽度和 0.9.8 完全一致;留白不足(窄窗口/卡片贴边)时直接隐藏。
|
|
1088
1038
|
react.useEffect(() => {
|
|
1089
1039
|
if (!open) return undefined;
|
|
1090
1040
|
const place = () => {
|
|
@@ -1105,6 +1055,31 @@ window.__ModuleLoader__.load({
|
|
|
1105
1055
|
return () => window.removeEventListener("resize", place);
|
|
1106
1056
|
}, [open, state.status]);
|
|
1107
1057
|
|
|
1058
|
+
// 分区链接:地址栏里出现 #/panel/plugins/dsh-free-search/<分区>(或任意 …/<分区>)就滚过去。
|
|
1059
|
+
// 本插件**只读不写** URL —— 地址栏的唯一写者是 dsh-url-router,写这里会跟它抢。
|
|
1060
|
+
react.useEffect(() => {
|
|
1061
|
+
if (!pageMode) return undefined;
|
|
1062
|
+
const sectionOfHash = () => {
|
|
1063
|
+
const hash = String((typeof window !== "undefined" && window.location && window.location.hash) || "");
|
|
1064
|
+
const parts = decodeURIComponent(hash.replace(/^#\/?/, "")).split("/").filter(Boolean);
|
|
1065
|
+
const index = parts.lastIndexOf("plugins");
|
|
1066
|
+
if (index >= 0) return parts[index + 2] || "";
|
|
1067
|
+
return parts.length === 1 ? parts[0] : "";
|
|
1068
|
+
};
|
|
1069
|
+
const apply = () => {
|
|
1070
|
+
const id = sectionOfHash();
|
|
1071
|
+
if (!id || !NAV_ITEMS.some((entry) => entry.id === id)) return;
|
|
1072
|
+
const node = document.querySelector(`[data-dsh-section="${id}"]`)
|
|
1073
|
+
|| (NAV_ITEMS.find((entry) => entry.id === id) ? document.getElementById(NAV_ITEMS.find((entry) => entry.id === id).anchor) : null);
|
|
1074
|
+
if (!node || typeof node.scrollIntoView !== "function") return;
|
|
1075
|
+
setRailActive(id);
|
|
1076
|
+
node.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
1077
|
+
};
|
|
1078
|
+
apply();
|
|
1079
|
+
window.addEventListener("hashchange", apply);
|
|
1080
|
+
return () => window.removeEventListener("hashchange", apply);
|
|
1081
|
+
}, [pageMode]);
|
|
1082
|
+
|
|
1108
1083
|
// 当前分组:分组顶部越过 35% 激活线就算当前;滚到底高亮最后一组。
|
|
1109
1084
|
react.useEffect(() => {
|
|
1110
1085
|
if (!open) return undefined;
|
|
@@ -1113,9 +1088,16 @@ window.__ModuleLoader__.load({
|
|
|
1113
1088
|
if (!target) return undefined;
|
|
1114
1089
|
const compute = () => {
|
|
1115
1090
|
const viewportHeight = scroller ? scroller.clientHeight : window.innerHeight;
|
|
1091
|
+
// "At the bottom" 必须建立在"这个容器真的能滚"之上。原来只看 scrollTop + clientHeight
|
|
1092
|
+
// >= scrollHeight - 8:一旦 findScrollParent 挑中一个不滚动的祖先(scrollHeight ===
|
|
1093
|
+
// clientHeight),这个判断恒为真,于是当前分区**永远**是最后一节,地址栏永远是
|
|
1094
|
+
// performance(用户 2026-10-31 实测)。契约见 dsh-url-router/docs/panel-sections.md 五.1:
|
|
1095
|
+
// 当前分区是"读者正在看的那一节",不是"猜最后一节"。
|
|
1116
1096
|
const atBottom = scroller
|
|
1117
|
-
? scroller.
|
|
1118
|
-
|
|
1097
|
+
? scroller.scrollHeight > scroller.clientHeight + 8
|
|
1098
|
+
&& scroller.scrollTop + scroller.clientHeight >= scroller.scrollHeight - 8
|
|
1099
|
+
: document.documentElement.scrollHeight > window.innerHeight + 8
|
|
1100
|
+
&& window.innerHeight + window.scrollY >= document.documentElement.scrollHeight - 8;
|
|
1119
1101
|
if (atBottom) {
|
|
1120
1102
|
setRailActive(NAV_ITEMS[NAV_ITEMS.length - 1].id);
|
|
1121
1103
|
return;
|
|
@@ -1155,6 +1137,7 @@ window.__ModuleLoader__.load({
|
|
|
1155
1137
|
behavior: t.navBehavior,
|
|
1156
1138
|
fetch: t.navFetch,
|
|
1157
1139
|
performance: t.navPerformance,
|
|
1140
|
+
prompt: t.navPrompt,
|
|
1158
1141
|
};
|
|
1159
1142
|
const goToSection = (id) => {
|
|
1160
1143
|
const item = NAV_ITEMS.find((entry) => entry.id === id);
|
|
@@ -1163,6 +1146,15 @@ window.__ModuleLoader__.load({
|
|
|
1163
1146
|
setRailActive(id);
|
|
1164
1147
|
section.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
1165
1148
|
};
|
|
1149
|
+
// #102:页 1 内置英文、页 2 内置中文、页 3 起自定义;当前页即注入内容。
|
|
1150
|
+
const promptPageTotal = PROMPT_BUILTIN_PAGES + promptPages.length;
|
|
1151
|
+
const promptPageKind = (index) => (index === 0 ? "builtin-en" : index === 1 ? "builtin-zh" : "user");
|
|
1152
|
+
const promptPageTextAt = (index) => {
|
|
1153
|
+
const kind = promptPageKind(index);
|
|
1154
|
+
if (kind === "builtin-en") return builtinPrompts.en;
|
|
1155
|
+
if (kind === "builtin-zh") return builtinPrompts.zh;
|
|
1156
|
+
return promptPages[index - PROMPT_BUILTIN_PAGES] ?? "";
|
|
1157
|
+
};
|
|
1166
1158
|
const title = "Free Search";
|
|
1167
1159
|
const description = t.description;
|
|
1168
1160
|
const enabledEngineCount = ENGINES.filter((e) => e.id !== "auto" && e.id !== "multi" && !disabledEngines.includes(e.id)).length;
|
|
@@ -1198,6 +1190,7 @@ window.__ModuleLoader__.load({
|
|
|
1198
1190
|
return react_jsx_runtime.jsx("div", {
|
|
1199
1191
|
className: "dshfs-field",
|
|
1200
1192
|
id: poolKey === "zh" ? "dshfs-anchor-pools" : undefined,
|
|
1193
|
+
"data-dsh-section": poolKey === "zh" ? "pools" : undefined,
|
|
1201
1194
|
children: [
|
|
1202
1195
|
react_jsx_runtime.jsx("div", {
|
|
1203
1196
|
className: "dshfs-label",
|
|
@@ -1328,6 +1321,9 @@ window.__ModuleLoader__.load({
|
|
|
1328
1321
|
return react_jsx_runtime.jsx("li", {
|
|
1329
1322
|
className: (open ? "dshfs-card dshfs-cardOpen" : "dshfs-card") + (pageMode ? " dshfs-pageMode" : ""),
|
|
1330
1323
|
"data-dsh-fs-section": railActive,
|
|
1324
|
+
// 通用契约:当前分区。侧栏每一项也有 data-dsh-fs-section(值是它自己的 id),
|
|
1325
|
+
// 所以读的人分不清"当前"和"某一项" —— 根上这个才是状态。
|
|
1326
|
+
"data-dsh-panel-section": railActive,
|
|
1331
1327
|
children: [
|
|
1332
1328
|
react_jsx_runtime.jsx("button", {
|
|
1333
1329
|
type: "button",
|
|
@@ -1371,9 +1367,14 @@ window.__ModuleLoader__.load({
|
|
|
1371
1367
|
ref: railRef,
|
|
1372
1368
|
"aria-label": t.navLabel,
|
|
1373
1369
|
children: NAV_ITEMS.map((item) =>
|
|
1374
|
-
|
|
1375
|
-
|
|
1370
|
+
// 真链接:右键能"复制链接"、能开新标签、地址栏里就是分区。
|
|
1371
|
+
// 本插件不写 URL(地址栏的唯一写者是 dsh-url-router);href 只是给出
|
|
1372
|
+
// 这条分区的地址,点击时浏览器自己改 hash,滚动仍由 goToSection 立即完成。
|
|
1373
|
+
react_jsx_runtime.jsx("a", {
|
|
1376
1374
|
className: "dshfs-railItem" + (railActive === item.id ? " dshfs-railItemActive" : ""),
|
|
1375
|
+
href: `#/panel/plugins/dsh-free-search/${item.id}`,
|
|
1376
|
+
"data-dsh-fs-section": item.id,
|
|
1377
|
+
"data-dsh-section": item.id,
|
|
1377
1378
|
onClick: () => goToSection(item.id),
|
|
1378
1379
|
children: [
|
|
1379
1380
|
react_jsx_runtime.jsx("span", { className: "dshfs-railDot" }),
|
|
@@ -1387,6 +1388,7 @@ window.__ModuleLoader__.load({
|
|
|
1387
1388
|
react_jsx_runtime.jsx("div", {
|
|
1388
1389
|
className: "dshfs-field",
|
|
1389
1390
|
id: "dshfs-anchor-engines",
|
|
1391
|
+
"data-dsh-section": "engines",
|
|
1390
1392
|
children: [
|
|
1391
1393
|
react_jsx_runtime.jsx("div", {
|
|
1392
1394
|
className: "dshfs-label",
|
|
@@ -1474,6 +1476,7 @@ react_jsx_runtime.jsx("div", {
|
|
|
1474
1476
|
react_jsx_runtime.jsx("div", {
|
|
1475
1477
|
className: "dshfs-field",
|
|
1476
1478
|
id: "dshfs-anchor-fallback",
|
|
1479
|
+
"data-dsh-section": "fallback",
|
|
1477
1480
|
children: [
|
|
1478
1481
|
react_jsx_runtime.jsx("div", {
|
|
1479
1482
|
className: "dshfs-label",
|
|
@@ -1648,6 +1651,7 @@ react_jsx_runtime.jsx("div", {
|
|
|
1648
1651
|
react_jsx_runtime.jsx("div", {
|
|
1649
1652
|
className: "dshfs-field",
|
|
1650
1653
|
id: "dshfs-anchor-behavior",
|
|
1654
|
+
"data-dsh-section": "behavior",
|
|
1651
1655
|
children: [
|
|
1652
1656
|
react_jsx_runtime.jsx("div", {
|
|
1653
1657
|
className: "dshfs-label",
|
|
@@ -1772,6 +1776,7 @@ react_jsx_runtime.jsx("div", {
|
|
|
1772
1776
|
react_jsx_runtime.jsx("div", {
|
|
1773
1777
|
className: "dshfs-field",
|
|
1774
1778
|
id: "dshfs-anchor-fetch",
|
|
1779
|
+
"data-dsh-section": "fetch",
|
|
1775
1780
|
children: [
|
|
1776
1781
|
react_jsx_runtime.jsx("label", {
|
|
1777
1782
|
className: "dshfs-check",
|
|
@@ -1878,6 +1883,7 @@ react_jsx_runtime.jsx("div", {
|
|
|
1878
1883
|
react_jsx_runtime.jsx("div", {
|
|
1879
1884
|
className: "dshfs-field",
|
|
1880
1885
|
id: "dshfs-anchor-performance",
|
|
1886
|
+
"data-dsh-section": "performance",
|
|
1881
1887
|
children: [
|
|
1882
1888
|
react_jsx_runtime.jsx("div", {
|
|
1883
1889
|
className: "dshfs-label",
|
|
@@ -1992,6 +1998,69 @@ react_jsx_runtime.jsx("div", {
|
|
|
1992
1998
|
}),
|
|
1993
1999
|
],
|
|
1994
2000
|
}),
|
|
2001
|
+
react_jsx_runtime.jsx("div", {
|
|
2002
|
+
className: "dshfs-field",
|
|
2003
|
+
id: "dshfs-anchor-prompt",
|
|
2004
|
+
"data-dsh-section": "prompt",
|
|
2005
|
+
children: [
|
|
2006
|
+
react_jsx_runtime.jsx("div", { className: "dshfs-label", children: t.promptLabel }),
|
|
2007
|
+
react_jsx_runtime.jsx("div", {
|
|
2008
|
+
className: "dshfs-presetRow",
|
|
2009
|
+
children: [
|
|
2010
|
+
react_jsx_runtime.jsx("button", { type: "button", className: "dshfs-btn", disabled: promptPageIndex <= 0, onClick: () => setPromptPageIndex((i) => Math.max(0, i - 1)), children: "‹" }, "prev"),
|
|
2011
|
+
react_jsx_runtime.jsx("span", { className: "dshfs-hint", children: `${promptPageIndex + 1} / ${promptPageTotal}` }),
|
|
2012
|
+
react_jsx_runtime.jsx("button", { type: "button", className: "dshfs-btn", disabled: promptPageIndex >= promptPageTotal - 1, onClick: () => setPromptPageIndex((i) => Math.min(promptPageTotal - 1, i + 1)), children: "›" }, "next"),
|
|
2013
|
+
react_jsx_runtime.jsx("button", { type: "button", className: "dshfs-btn", disabled: !ready || saving, onClick: () => { setPromptPages((prev) => [...prev, ""]); setPromptPageIndex(promptPageTotal); setDirty(true); setFailed(false); setSaveError(""); }, children: "+" }, "add"),
|
|
2014
|
+
react_jsx_runtime.jsx("button", { type: "button", className: "dshfs-btn", disabled: !ready || saving || promptPageKind(promptPageIndex) !== "user", onClick: () => { const at = promptPageIndex - PROMPT_BUILTIN_PAGES; setPromptPages((prev) => prev.filter((_, i) => i !== at)); setPromptPageIndex((i) => Math.max(0, i - 1)); setDirty(true); setFailed(false); setSaveError(""); }, children: "−" }, "remove"),
|
|
2015
|
+
],
|
|
2016
|
+
}),
|
|
2017
|
+
react_jsx_runtime.jsx("textarea", {
|
|
2018
|
+
className: "dshfs-textarea",
|
|
2019
|
+
rows: 8,
|
|
2020
|
+
spellCheck: false,
|
|
2021
|
+
readOnly: promptPageKind(promptPageIndex) !== "user",
|
|
2022
|
+
value: promptPageTextAt(promptPageIndex),
|
|
2023
|
+
disabled: !ready || saving,
|
|
2024
|
+
onChange: (e) => {
|
|
2025
|
+
const at = promptPageIndex - PROMPT_BUILTIN_PAGES;
|
|
2026
|
+
const next = promptPages.slice();
|
|
2027
|
+
next[at] = e.target.value;
|
|
2028
|
+
setPromptPages(next);
|
|
2029
|
+
setDirty(true);
|
|
2030
|
+
setFailed(false);
|
|
2031
|
+
setSaveError("");
|
|
2032
|
+
},
|
|
2033
|
+
}),
|
|
2034
|
+
|
|
2035
|
+
react_jsx_runtime.jsx("button", { type: "button", className: "dshfs-btn", onClick: () => setPreviewOpen((open) => !open), children: (previewOpen ? "▾ " : "▸ ") + t.promptPreviewToggle }, "preview"),
|
|
2036
|
+
previewOpen ? react_jsx_runtime.jsx("pre", { className: "dshfs-preview", children: promptPageTextAt(promptPageIndex) }) : null,
|
|
2037
|
+
react_jsx_runtime.jsx("div", { className: "dshfs-label", children: t.toolsLabel }),
|
|
2038
|
+
react_jsx_runtime.jsx("div", {
|
|
2039
|
+
className: "dshfs-platforms",
|
|
2040
|
+
children: TOOL_TOGGLES.map((toolId) => {
|
|
2041
|
+
const on = !disabledTools.includes(toolId);
|
|
2042
|
+
return react_jsx_runtime.jsx("label", {
|
|
2043
|
+
className: "dshfs-platform",
|
|
2044
|
+
children: [
|
|
2045
|
+
react_jsx_runtime.jsx("input", {
|
|
2046
|
+
type: "checkbox",
|
|
2047
|
+
checked: on,
|
|
2048
|
+
disabled: !ready || saving,
|
|
2049
|
+
onChange: (e) => {
|
|
2050
|
+
const next = e.target.checked ? disabledTools.filter((id) => id !== toolId) : [...new Set([...disabledTools, toolId])];
|
|
2051
|
+
setDisabledTools(next);
|
|
2052
|
+
setDirty(true);
|
|
2053
|
+
setFailed(false);
|
|
2054
|
+
setSaveError("");
|
|
2055
|
+
},
|
|
2056
|
+
}),
|
|
2057
|
+
toolId,
|
|
2058
|
+
],
|
|
2059
|
+
}, toolId);
|
|
2060
|
+
}),
|
|
2061
|
+
}),
|
|
2062
|
+
],
|
|
2063
|
+
}),
|
|
1995
2064
|
react_jsx_runtime.jsx("div", {
|
|
1996
2065
|
className: "dshfs-resultRow",
|
|
1997
2066
|
children: [
|
|
@@ -2024,86 +2093,7 @@ react_jsx_runtime.jsx("div", {
|
|
|
2024
2093
|
className: "dshfs-footerLeft",
|
|
2025
2094
|
children: [
|
|
2026
2095
|
react_jsx_runtime.jsx("span", { className: "dshfs-version", children: "v" + PLUGIN_VERSION }),
|
|
2027
|
-
|
|
2028
|
-
? react_jsx_runtime.jsx("a", {
|
|
2029
|
-
className: "dshfs-updatePill",
|
|
2030
|
-
href: updateInfo.updateUrl,
|
|
2031
|
-
target: "_blank",
|
|
2032
|
-
rel: "noopener noreferrer",
|
|
2033
|
-
title: t.updateAvailable(updateInfo.current, updateInfo.latest),
|
|
2034
|
-
children: [
|
|
2035
|
-
react_jsx_runtime.jsx("svg", {
|
|
2036
|
-
className: "dshfs-updateIcon",
|
|
2037
|
-
viewBox: "0 0 16 16",
|
|
2038
|
-
width: 14,
|
|
2039
|
-
height: 14,
|
|
2040
|
-
"aria-hidden": "true",
|
|
2041
|
-
children: react_jsx_runtime.jsx("path", {
|
|
2042
|
-
d: "M8 2.2v6.4M5.2 6.4 8 9.2l2.8-2.8M3 10.8v1.4c0 .9.7 1.6 1.6 1.6h6.8c.9 0 1.6-.7 1.6-1.6v-1.4",
|
|
2043
|
-
fill: "none",
|
|
2044
|
-
stroke: "currentColor",
|
|
2045
|
-
strokeWidth: 1.6,
|
|
2046
|
-
strokeLinecap: "round",
|
|
2047
|
-
strokeLinejoin: "round",
|
|
2048
|
-
}),
|
|
2049
|
-
}),
|
|
2050
|
-
t.hasUpdate,
|
|
2051
|
-
],
|
|
2052
|
-
})
|
|
2053
|
-
: react_jsx_runtime.jsx("button", {
|
|
2054
|
-
className: "dshfs-updatePill",
|
|
2055
|
-
type: "button",
|
|
2056
|
-
title: updateInfo && updateInfo.ok && updateInfo.hasUpdate ? t.updateAvailable(updateInfo.current, updateInfo.latest) : undefined,
|
|
2057
|
-
onClick: updateInfo && updateInfo.ok && updateInfo.hasUpdate ? runUpdate : runCheckUpdate,
|
|
2058
|
-
disabled: upgrading || checkingUpdate || saving || !ready,
|
|
2059
|
-
children: [
|
|
2060
|
-
react_jsx_runtime.jsx("svg", {
|
|
2061
|
-
className: "dshfs-updateIcon",
|
|
2062
|
-
viewBox: "0 0 16 16",
|
|
2063
|
-
width: 14,
|
|
2064
|
-
height: 14,
|
|
2065
|
-
"aria-hidden": "true",
|
|
2066
|
-
children: react_jsx_runtime.jsx("path", {
|
|
2067
|
-
d: "M8 2.2v6.4M5.2 6.4 8 9.2l2.8-2.8M3 10.8v1.4c0 .9.7 1.6 1.6 1.6h6.8c.9 0 1.6-.7 1.6-1.6v-1.4",
|
|
2068
|
-
fill: "none",
|
|
2069
|
-
stroke: "currentColor",
|
|
2070
|
-
strokeWidth: 1.6,
|
|
2071
|
-
strokeLinecap: "round",
|
|
2072
|
-
strokeLinejoin: "round",
|
|
2073
|
-
}),
|
|
2074
|
-
}),
|
|
2075
|
-
upgrading
|
|
2076
|
-
? t.upgrading
|
|
2077
|
-
: checkingUpdate
|
|
2078
|
-
? t.checkingUpdate
|
|
2079
|
-
: updateInfo && updateInfo.ok && updateInfo.hasUpdate
|
|
2080
|
-
? t.hasUpdate
|
|
2081
|
-
: t.checkUpdate,
|
|
2082
|
-
],
|
|
2083
|
-
}),
|
|
2084
|
-
updateInfo && updateInfo.ok
|
|
2085
|
-
? updateInfo.upgraded
|
|
2086
|
-
? react_jsx_runtime.jsx("span", {
|
|
2087
|
-
className: "dshfs-updateOk",
|
|
2088
|
-
children: t.upgradeDone(updateInfo.latest),
|
|
2089
|
-
})
|
|
2090
|
-
: updateInfo.hasUpdate
|
|
2091
|
-
? updateInfo.installable
|
|
2092
|
-
? null
|
|
2093
|
-
: react_jsx_runtime.jsx("span", {
|
|
2094
|
-
className: "dshfs-version",
|
|
2095
|
-
children: t.upgradeLinkMode,
|
|
2096
|
-
})
|
|
2097
|
-
: react_jsx_runtime.jsx("span", {
|
|
2098
|
-
className: "dshfs-updateOk",
|
|
2099
|
-
children: t.updateLatest(updateInfo.current),
|
|
2100
|
-
})
|
|
2101
|
-
: updateInfo && !updateInfo.ok
|
|
2102
|
-
? react_jsx_runtime.jsx("span", {
|
|
2103
|
-
className: "dshfs-failed",
|
|
2104
|
-
children: updateInfo.upgradeFailed ? t.upgradeFailed(updateInfo.upgradeFailed) : t.updateCheckFailed,
|
|
2105
|
-
})
|
|
2106
|
-
: null,
|
|
2096
|
+
|
|
2107
2097
|
],
|
|
2108
2098
|
}),
|
|
2109
2099
|
react_jsx_runtime.jsx("div", {
|
|
@@ -2188,6 +2178,21 @@ react_jsx_runtime.jsx("div", {
|
|
|
2188
2178
|
// description 必须传函数:ui-commands 读回的是 contribution.description(),
|
|
2189
2179
|
// 传字符串会抛 TypeError: contribution.description is not a function;该异常
|
|
2190
2180
|
// 会让整份 "/" 候选列表一起失败,菜单空白、其他命令也一起点不到。
|
|
2181
|
+
// #101: also claim the bundle-level seat (keyed by the package name) so the
|
|
2182
|
+
// card renders on the bundle page itself, between the description and the
|
|
2183
|
+
// rows. The row-level seat stays registered for the older entry point.
|
|
2184
|
+
ctx.slots.inject("plugins.bundle.config", () =>
|
|
2185
|
+
ctx.slots.register(
|
|
2186
|
+
{
|
|
2187
|
+
name: "plugins.bundle.config",
|
|
2188
|
+
key: "dsh-free-search",
|
|
2189
|
+
},
|
|
2190
|
+
(slotProps) =>
|
|
2191
|
+
slotProps && slotProps.view === "summary"
|
|
2192
|
+
? summaryText()
|
|
2193
|
+
: react_jsx_runtime.jsx(FreeSearchCard, { page: true })
|
|
2194
|
+
)
|
|
2195
|
+
);
|
|
2191
2196
|
ctx.inject(["commandUi"], (sctx) => {
|
|
2192
2197
|
const command = sctx.get("commandUi");
|
|
2193
2198
|
sctx.effect(() => {
|