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/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 PLUGIN_VERSION = "0.9.21";
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.scrollTop + scroller.clientHeight >= scroller.scrollHeight - 8
1118
- : window.innerHeight + window.scrollY >= document.documentElement.scrollHeight - 8;
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
- react_jsx_runtime.jsx("button", {
1375
- type: "button",
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
- updateInfo && updateInfo.ok && updateInfo.hasUpdate && !updateInfo.installable
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(() => {