@klarkxy/dsh-zhihu 0.2.6 → 0.2.8

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
@@ -353,6 +353,17 @@ ${scope} {
353
353
  /* --- Layout scaffolding. --- */
354
354
  & .dsh-ui-stack { display: flex; flex-direction: column; gap: 12px; }
355
355
  & .dsh-ui-stack-lg { display: flex; flex-direction: column; gap: 20px; }
356
+ /* Plugin detail pages share one reading column. These are layout recipes;
357
+ * controls keep their own primitive styles. Form spacing follows the host's
358
+ * settings-form/fields.module.css and SettingsForm.module.css. */
359
+ & .dsh-ui-page { display: flex; flex-direction: column; gap: 16px; width: 100%; max-width: 760px; min-width: 0; }
360
+ & .dsh-ui-form { display: flex; flex-direction: column; gap: 0; min-width: 0; }
361
+ &.dsh-ui-form > .dsh-ui-field, & .dsh-ui-form > .dsh-ui-field { padding: 12px 0; }
362
+ & .dsh-ui-form-footer { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; padding-top: 16px; min-width: 0; }
363
+ & .dsh-ui-fields-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px 20px; min-width: 0; margin: 0; padding: 0; border: 0; }
364
+ @media (max-width: 560px) {
365
+ & .dsh-ui-fields-grid { grid-template-columns: minmax(0, 1fr); }
366
+ }
356
367
  & .dsh-ui-row { display: flex; align-items: center; gap: 8px; min-width: 0; }
357
368
  & .dsh-ui-row-wrap { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }
358
369
  & .dsh-ui-spacer { flex: 1; min-width: 0; }
@@ -380,6 +391,9 @@ ${scope} {
380
391
  * official primitive, so this only owns the vertical rhythm. --- */
381
392
  & .dsh-ui-field { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
382
393
  & .dsh-ui-field + .dsh-ui-field { border-top: 0.5px solid var(--dsw-alias-border-l2); padding-top: 12px; }
394
+ /* Grid fields share a row, so the stacked-field divider must not win later in
395
+ * the cascade. Reset it here, after the field recipe. */
396
+ & .dsh-ui-fields-grid > .dsh-ui-field { padding: 0; border: 0; }
383
397
  & .dsh-ui-label { display: flex; align-items: center; gap: 6px; font-size: 13px; line-height: 20px; font-weight: 500; color: var(--dsw-alias-label-primary); }
384
398
  & .dsh-ui-label-row { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
385
399
  & .dsh-ui-help { font-size: 12px; line-height: 1.6; color: var(--dsw-alias-label-secondary); }
@@ -570,7 +584,7 @@ ${scope} {
570
584
  & *, & *::before, & *::after { transition-duration: 0.01ms !important; animation-duration: 0.01ms !important; }
571
585
  }
572
586
  }
573
- `.replace(/^& (\.dsh-ui-[a-z0-9-]+)(?![+>~])/gm, (_match, className) => `&${className}, & ${className}`);
587
+ `.replace(/^& (\.dsh-ui-[a-z0-9-]+)(?=\s*\{)/gm, (_match, className) => `&${className}, & ${className}`);
574
588
  }
575
589
  //#endregion
576
590
  //#region src/client-styles.ts
@@ -592,16 +606,6 @@ ${scope} {
592
606
  */
593
607
  const zhihuClientStyles = `${officialUiCss("zhihu-panel")}
594
608
  .zhihu-panel {
595
- /* --- Plugin page. The contract's rules nest under the scope selector, so the
596
- * root's own column and reading width are the one part it cannot reach. --- */
597
- &.zhihu-settings-embed {
598
- display: flex;
599
- flex-direction: column;
600
- gap: 12px;
601
- width: 100%;
602
- max-width: 760px;
603
- min-width: 0;
604
- }
605
609
  /* The tab strip is a primitive; this only places it in the page column. */
606
610
  .zhihu-tabs { margin: 0; }
607
611
 
@@ -618,9 +622,8 @@ const zhihuClientStyles = `${officialUiCss("zhihu-panel")}
618
622
  background: var(--dsw-alias-interactive-bg-hover-danger);
619
623
  }
620
624
 
621
- /* --- Disclosures. The capability and fine-print blocks use the host
622
- * DisclosureRow; only the reasoning block is still a native <summary>. --- */
623
- .zhihu-ask-reasoning > summary { cursor: pointer; width: fit-content; }
625
+ /* --- Disclosures. Capability, fine print and the reasoning block all use the
626
+ * host DisclosureRow; only its detail list needs layout here. --- */
624
627
  .zhihu-guide-list { margin: 4px 0 0; padding-inline-start: 20px; display: grid; gap: 6px; }
625
628
 
626
629
  /* --- Result rows. The card, the row and the name all come from the contract; a
@@ -634,10 +637,9 @@ const zhihuClientStyles = `${officialUiCss("zhihu-panel")}
634
637
  .zhihu-ask-content { white-space: pre-wrap; }
635
638
 
636
639
  /* --- Open platform. The section is the last block of its tab, so it keeps a
637
- * trailing hairline; the raw response scrolls instead of growing the page. --- */
640
+ * trailing hairline; the response is the official JsonTree inspector. --- */
638
641
  .zhihu-open-platform { padding-bottom: 16px; border-bottom: 0.5px solid var(--dsw-alias-border-l2); }
639
642
  .zhihu-open-platform-quota > legend { padding: 0 8px 0 0; }
640
- .zhihu-open-platform-data { max-height: 480px; overflow: auto; margin: 8px 0 0; }
641
643
 
642
644
  /* --- Usage. The stat tiles are a grid and the day list is a table, neither of
643
645
  * which the contract has a recipe for; figures align in the columns they sit
@@ -661,7 +663,7 @@ const zhihuClientStyles = `${officialUiCss("zhihu-panel")}
661
663
  .zhihu-chart-bar-ok { fill: var(--dsw-alias-state-business-primary); }
662
664
  .zhihu-chart-bar-fail { fill: var(--dsw-alias-state-error-primary); }
663
665
  .zhihu-chart-hit { fill: transparent; }
664
- .zhihu-chart-grid { stroke: var(--dsw-alias-border-l2); stroke-width: 1; }
666
+ .zhihu-chart-grid { stroke: var(--dsw-alias-border-l2); stroke-width: 0.5; }
665
667
  .zhihu-chart-axis,
666
668
  .zhihu-chart-tick,
667
669
  .zhihu-chart-value { fill: var(--dsw-alias-label-secondary); font-size: var(--dsh-content-font-size-secondary); font-family: var(--ds-font-family-code); }
@@ -695,9 +697,9 @@ const zhihuClientStyles = `${officialUiCss("zhihu-panel")}
695
697
  .zhihu-quota-table td { padding: 5px 6px; }
696
698
  }
697
699
 
698
- /* --- File field. The input is visually hidden but stays focusable and in the
699
- * accessibility tree; its <label> is the visible trigger, so a click on the
700
- * label opens the file dialog natively and keyboard focus rings the label. --- */
700
+ /* --- File field. The input is visually hidden but stays in the accessibility
701
+ * tree; the official Button beside it is the visible trigger and forwards the
702
+ * click, so its own focus ring and disabled state apply. --- */
701
703
  .zhihu-file {
702
704
  position: absolute;
703
705
  width: 1px;
@@ -709,30 +711,6 @@ const zhihuClientStyles = `${officialUiCss("zhihu-panel")}
709
711
  white-space: nowrap;
710
712
  border: 0;
711
713
  }
712
- .zhihu-file-trigger {
713
- display: inline-flex;
714
- align-items: center;
715
- padding: 4px 12px;
716
- border: 0.5px solid var(--dsw-alias-border-l2);
717
- border-radius: var(--dsw-radius-md);
718
- color: var(--dsw-alias-label-primary);
719
- cursor: pointer;
720
- flex: none;
721
- }
722
- .zhihu-file-trigger:hover { background: var(--dsw-alias-interactive-bg-hover); }
723
- .zhihu-file-trigger[aria-disabled="true"] { opacity: .5; pointer-events: none; }
724
- .zhihu-file:focus-visible + .dsh-ui-row .zhihu-file-trigger { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: 2px; }
725
-
726
- /* 活动反馈:三点呼吸(pulse-dots),参数改写自 Amicro(MIT License,
727
- Copyright (c) 2026 Syed Subhan Uddin);装饰元素 aria-hidden。 */
728
- .zhihu-dots { display: inline-flex; align-items: center; gap: 3px; margin-inline-end: .4em; vertical-align: middle; color: var(--dsw-alias-state-business-primary); }
729
- .zhihu-dots i { width: .32em; height: .32em; min-width: 3px; min-height: 3px; border-radius: 50%; background: currentColor; animation: zhihu-activity-pulse 1.4s ease infinite; }
730
- .zhihu-dots i:nth-child(2) { animation-delay: .2s; }
731
- .zhihu-dots i:nth-child(3) { animation-delay: .4s; }
732
- @media (prefers-reduced-motion: reduce) {
733
- /* Stop the loop outright so the three dots stay lit instead of flickering. */
734
- .zhihu-dots i { animation: none; }
735
- }
736
714
  }
737
715
 
738
716
  /* --- Links. Ordinary DSH paints a light fill on a bare <a>, so plugin links
@@ -744,9 +722,6 @@ const zhihuClientStyles = `${officialUiCss("zhihu-panel")}
744
722
  color: var(--dsw-alias-brand-primary);
745
723
  background: none;
746
724
  }
747
-
748
- /* Keyframes cannot nest inside a style rule; the name is plugin-prefixed. */
749
- @keyframes zhihu-activity-pulse { 0%, 100% { opacity: .2; } 50% { opacity: 1; } }
750
725
  `;
751
726
  //#endregion
752
727
  //#region src/client-host-ui.tsx
@@ -891,6 +866,21 @@ const createOpenPlatformForm = () => ({
891
866
  apiIds: []
892
867
  });
893
868
  const isRecord = (value) => !!value && typeof value === "object" && !Array.isArray(value);
869
+ /** Localized copy for the official JSON inspector (the package is cordis-free). */
870
+ function jsonTreeLabels(t) {
871
+ return {
872
+ copyValue: t("复制值", "Copy value"),
873
+ copyJson: t("复制 JSON", "Copy JSON"),
874
+ copyPath: t("复制路径", "Copy path"),
875
+ copyPrettyJson: t("复制格式化 JSON", "Copy pretty JSON"),
876
+ copyCompactJson: t("复制紧凑 JSON", "Copy compact JSON"),
877
+ copied: t("已复制", "Copied"),
878
+ copyFailed: t("复制失败", "Copy failed"),
879
+ collapseNode: t("折叠节点", "Collapse node"),
880
+ expandNode: t("展开节点", "Expand node"),
881
+ copyButtonTitle: (action) => t(`${action}:点击复制`, `${action}: click to copy`)
882
+ };
883
+ }
894
884
  /** Int64 stays decimal text throughout the browser; never round-trip through Number. */
895
885
  function openPlatformOffset(value) {
896
886
  if (typeof value !== "string" || !/^\d+$/.test(value)) return null;
@@ -1294,9 +1284,15 @@ function ZhihuOpenPlatformSection({ rpc, quotaOnly = false, selectedOperation })
1294
1284
  className: "dsh-ui-banner dsh-ui-banner--danger",
1295
1285
  children: t("响应缺少正文(Body),未取得全文。", "The response has no body (Body); full text was not retrieved.")
1296
1286
  }),
1297
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
1298
- className: "zhihu-open-platform-data dsh-ui-code",
1299
- children: JSON.stringify(result.data, null, 2)
1287
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1288
+ className: "dsh-ui-scroll",
1289
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.JsonTree, {
1290
+ data: result.data,
1291
+ label: t("返回数据", "Response data"),
1292
+ copyable: true,
1293
+ expandTopLevel: true,
1294
+ labels: jsonTreeLabels(t)
1295
+ })
1300
1296
  })
1301
1297
  ]
1302
1298
  })
@@ -1727,6 +1723,358 @@ function ZhihuQuotaSection({ rpc }) {
1727
1723
  });
1728
1724
  }
1729
1725
  //#endregion
1726
+ //#region src/client-sync-credentials.tsx
1727
+ /**
1728
+ * 知乎发布助手的应用凭据设置。
1729
+ *
1730
+ * 只有插件设置页(plugins.bundle.config)里才有这段表单:右侧栏工作台不发凭据,
1731
+ * 也不重复提供入口。凭据本身由宿主管理(remote.credentials),插件只读写引用。
1732
+ */
1733
+ const ZHIHU_APP_KEY_REF = "ZHIHU_OPENAPI_APP_KEY";
1734
+ const ZHIHU_APP_SECRET_REF = "ZHIHU_OPENAPI_APP_SECRET";
1735
+ /** 申请发布助手应用凭据的官方入口。 */
1736
+ const ZHIHU_PUBLISHER_URL = "https://www.zhihu.com/playground/zhihu-publisher";
1737
+ /** 成对保存时的挂起标记;它与凭据引用名不同,不会撞上字段的「保存中…」。 */
1738
+ const PAIR_PENDING = "pair";
1739
+ const GRANT_KEY = /ZHIHU_OPENAPI_APP_KEY\s*[:=]\s*"([^"\r\n]+)"/;
1740
+ const GRANT_SECRET = /ZHIHU_OPENAPI_APP_SECRET\s*[:=]\s*"([^"\r\n]+)"/;
1741
+ const GRANT_MARKER = /ZHIHU_OPENAPI_APP_(?:KEY|SECRET)/;
1742
+ /**
1743
+ * 知乎发布助手把授权说明和两项凭据放在同一段文字里。只在两项都用引号写出时拆开;
1744
+ * 单独粘贴的 Key 或 Secret 保持原样,避免把普通文本误存成另一项。
1745
+ */
1746
+ function parseZhihuPublisherGrant(value) {
1747
+ const key = GRANT_KEY.exec(value);
1748
+ const secret = GRANT_SECRET.exec(value);
1749
+ if (!key || !secret) return null;
1750
+ const appKey = key[1].trim();
1751
+ const appSecret = secret[1].trim();
1752
+ if (!appKey || !appSecret || appKey === appSecret) return null;
1753
+ return {
1754
+ appKey,
1755
+ appSecret
1756
+ };
1757
+ }
1758
+ /**
1759
+ * 文字里出现了凭据标记,但没能按受支持的格式读出两项凭据。这样的文本不能当凭据存:
1760
+ * 整段授权说明一旦落进密钥框,格式稍有出入就会变成一个坏凭据,而且要到发布时才报错。
1761
+ */
1762
+ function looksLikeZhihuPublisherGrant(value) {
1763
+ return GRANT_MARKER.test(value);
1764
+ }
1765
+ const FIELD_COPY = [{
1766
+ ref: ZHIHU_APP_SECRET_REF,
1767
+ label: "App Secret",
1768
+ zh: "知乎发布助手的应用密钥",
1769
+ en: "Publisher app secret",
1770
+ hintZh: "知乎发布助手签发的应用密钥。整段授权说明粘贴在这里时,下面会自动填入用户名。",
1771
+ hintEn: "The app secret issued by the Zhihu publisher. Pasting the authorization text here fills in the username below."
1772
+ }, {
1773
+ ref: ZHIHU_APP_KEY_REF,
1774
+ label: "App Key",
1775
+ zh: "知乎主页用户名",
1776
+ en: "Profile username",
1777
+ hintZh: "知乎个人主页地址里的用户名,例如 www.zhihu.com/people/<你的用户名>。这不是密钥,以明文显示。",
1778
+ hintEn: "The username in your Zhihu profile URL, e.g. www.zhihu.com/people/<your username>. This is not a secret and is shown in plain text.",
1779
+ plain: true
1780
+ }];
1781
+ /** 只有 http(s) 可以成为链接;其余一律降级为文本。 */
1782
+ function safeUrl$1(url) {
1783
+ return /^https?:\/\//i.test(url) ? url : null;
1784
+ }
1785
+ function PublisherLink(props) {
1786
+ const url = safeUrl$1(props.url);
1787
+ if (!url) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: props.children });
1788
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
1789
+ className: "zhihu-link",
1790
+ href: url,
1791
+ target: "_blank",
1792
+ rel: "noreferrer noopener",
1793
+ children: props.children
1794
+ });
1795
+ }
1796
+ function ZhihuSyncCredentials(props) {
1797
+ const t = useT();
1798
+ const { credentials } = props;
1799
+ const alive = (0, react.useRef)(false);
1800
+ const [state, setState] = (0, react.useState)({ status: "loading" });
1801
+ const [drafts, setDrafts] = (0, react.useState)({});
1802
+ const [paired, setPaired] = (0, react.useState)(false);
1803
+ const [pending, setPending] = (0, react.useState)(null);
1804
+ const [confirmRef, setConfirmRef] = (0, react.useState)(null);
1805
+ const [failure, setFailure] = (0, react.useState)("");
1806
+ const [note, setNote] = (0, react.useState)("");
1807
+ const views = state.status === "ready" ? state.credentials : {};
1808
+ const writable = (ref) => views[ref]?.writable !== false;
1809
+ (0, react.useEffect)(() => {
1810
+ alive.current = true;
1811
+ return () => {
1812
+ alive.current = false;
1813
+ };
1814
+ }, []);
1815
+ const load = (0, react.useCallback)(async () => {
1816
+ setState((current) => current.status === "ready" ? current : { status: "loading" });
1817
+ try {
1818
+ const result = await credentials.describe({ refs: FIELD_COPY.map((field) => field.ref) });
1819
+ if (!alive.current) return;
1820
+ if (!result.ok) {
1821
+ setState({
1822
+ status: "error",
1823
+ error: result.error.message
1824
+ });
1825
+ return;
1826
+ }
1827
+ setState({
1828
+ status: "ready",
1829
+ credentials: result.value.credentials
1830
+ });
1831
+ } catch (cause) {
1832
+ if (!alive.current) return;
1833
+ setState({
1834
+ status: "error",
1835
+ error: cause instanceof Error ? cause.message : String(cause)
1836
+ });
1837
+ }
1838
+ }, [credentials]);
1839
+ (0, react.useEffect)(() => {
1840
+ load();
1841
+ }, [load]);
1842
+ const save = async (refs) => {
1843
+ if (pending !== null) return;
1844
+ const writes = refs.map((ref) => ({
1845
+ ref,
1846
+ value: (drafts[ref] ?? "").trim()
1847
+ })).filter((item) => item.value && writable(item.ref));
1848
+ if (!writes.length) return;
1849
+ setPending(refs.length > 1 ? PAIR_PENDING : refs[0]);
1850
+ setFailure("");
1851
+ setNote("");
1852
+ try {
1853
+ for (const write of writes) {
1854
+ const result = await credentials.set(write);
1855
+ if (!alive.current) return;
1856
+ if (!result.ok) {
1857
+ setFailure(result.error.message);
1858
+ return;
1859
+ }
1860
+ }
1861
+ setDrafts((current) => {
1862
+ const next = { ...current };
1863
+ for (const write of writes) delete next[write.ref];
1864
+ return next;
1865
+ });
1866
+ setPaired(false);
1867
+ setNote(writes.length > 1 ? ["已保存用户名与密钥。", "Saved the username and the secret."] : ["已保存。", "Saved."]);
1868
+ await load();
1869
+ } catch (cause) {
1870
+ if (!alive.current) return;
1871
+ setFailure(cause instanceof Error ? cause.message : String(cause));
1872
+ } finally {
1873
+ if (alive.current) setPending(null);
1874
+ }
1875
+ };
1876
+ const clear = async (ref) => {
1877
+ setConfirmRef(null);
1878
+ if (pending !== null) return;
1879
+ setPending(ref);
1880
+ setFailure("");
1881
+ setNote("");
1882
+ try {
1883
+ const result = await credentials.unset({ ref });
1884
+ if (!alive.current) return;
1885
+ if (!result.ok) {
1886
+ setFailure(result.error.message);
1887
+ return;
1888
+ }
1889
+ setDrafts((current) => ({
1890
+ ...current,
1891
+ [ref]: ""
1892
+ }));
1893
+ setPaired(false);
1894
+ setNote(["已清除。", "Cleared."]);
1895
+ await load();
1896
+ } catch (cause) {
1897
+ if (!alive.current) return;
1898
+ setFailure(cause instanceof Error ? cause.message : String(cause));
1899
+ } finally {
1900
+ if (alive.current) setPending(null);
1901
+ }
1902
+ };
1903
+ if (state.status === "loading") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
1904
+ className: "dsh-ui-stack",
1905
+ "aria-label": t("知乎发布助手凭据", "Zhihu publisher credentials"),
1906
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1907
+ className: "dsh-ui-loading",
1908
+ role: "status",
1909
+ children: t("正在读取凭据状态…", "Loading credential status…")
1910
+ })
1911
+ });
1912
+ if (state.status === "error") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1913
+ className: "dsh-ui-stack",
1914
+ "aria-label": t("知乎发布助手凭据", "Zhihu publisher credentials"),
1915
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1916
+ className: "dsh-ui-error",
1917
+ role: "alert",
1918
+ children: clientText(state.error, t)
1919
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1920
+ className: "dsh-ui-actions",
1921
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZhihuButton, {
1922
+ onClick: () => void load(),
1923
+ children: t("重试", "Retry")
1924
+ })
1925
+ })]
1926
+ });
1927
+ const confirmed = confirmRef ? FIELD_COPY.find((field) => field.ref === confirmRef) : void 0;
1928
+ const pairReady = paired && (drafts["ZHIHU_OPENAPI_APP_KEY"] ?? "").trim() !== "" && (drafts["ZHIHU_OPENAPI_APP_SECRET"] ?? "").trim() !== "";
1929
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1930
+ className: "dsh-ui-stack",
1931
+ "aria-label": t("知乎发布助手凭据", "Zhihu publisher credentials"),
1932
+ children: [
1933
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
1934
+ className: "dsh-ui-heading",
1935
+ children: t("知乎发布助手", "Zhihu publisher")
1936
+ }),
1937
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
1938
+ className: "dsh-ui-help",
1939
+ children: [
1940
+ t("发布助手使用另一组应用凭据,与搜索用的 Access Secret 无关。把知乎给出的授权说明粘贴到上面的密钥框即可,两项凭据要写成 ZHIHU_OPENAPI_APP_KEY:\"…\" 与 ZHIHU_OPENAPI_APP_SECRET:\"…\" 的形式,用户名会自动填到下面。", "The publisher uses its own app credentials, separate from the search Access Secret. Paste Zhihu’s authorization text into the secret field above — both credentials must be written as ZHIHU_OPENAPI_APP_KEY:\"…\" and ZHIHU_OPENAPI_APP_SECRET:\"…\" — and the username fills in below."),
1941
+ " ",
1942
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PublisherLink, {
1943
+ url: ZHIHU_PUBLISHER_URL,
1944
+ children: t("申请凭据", "Get credentials")
1945
+ })
1946
+ ]
1947
+ }),
1948
+ paired ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1949
+ className: "dsh-ui-hint",
1950
+ children: t("这两项来自同一段授权说明,可以一起保存。", "These two came from the same authorization text and can be saved together.")
1951
+ }) : null,
1952
+ FIELD_COPY.map((field) => {
1953
+ const view = state.credentials[field.ref];
1954
+ const locked = view?.writable === false;
1955
+ const configured = view?.configured === true;
1956
+ const draft = drafts[field.ref] ?? "";
1957
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1958
+ className: "dsh-ui-field",
1959
+ children: [
1960
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1961
+ className: "dsh-ui-label-row",
1962
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1963
+ className: "dsh-ui-label",
1964
+ id: `zhihu-sync-cred-${field.ref}`,
1965
+ children: field.label
1966
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1967
+ className: "dsh-ui-row",
1968
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: locked ? "warning" : configured ? "done" : "idle" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1969
+ className: "dsh-ui-meta",
1970
+ children: locked ? t("由环境变量提供(只读)", "Provided by an environment variable (read-only)") : configured ? t("已保存", "Saved") : t("未配置", "Not set")
1971
+ })]
1972
+ })]
1973
+ }),
1974
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1975
+ className: "dsh-ui-hint",
1976
+ children: t(field.hintZh, field.hintEn)
1977
+ }),
1978
+ renderInput({
1979
+ type: field.plain ? "text" : "password",
1980
+ value: draft,
1981
+ disabled: locked || pending !== null,
1982
+ "aria-label": field.label,
1983
+ "aria-describedby": `zhihu-sync-cred-${field.ref}`,
1984
+ placeholder: locked ? t("由环境变量提供", "Set by an environment variable") : t("粘贴凭据", "Paste the credential"),
1985
+ onChange: (value) => {
1986
+ const grant = parseZhihuPublisherGrant(value);
1987
+ if (grant) {
1988
+ const next = {};
1989
+ if (writable("ZHIHU_OPENAPI_APP_KEY")) next[ZHIHU_APP_KEY_REF] = grant.appKey;
1990
+ if (writable("ZHIHU_OPENAPI_APP_SECRET")) next[ZHIHU_APP_SECRET_REF] = grant.appSecret;
1991
+ setDrafts((current) => ({
1992
+ ...current,
1993
+ ...next
1994
+ }));
1995
+ setPaired(true);
1996
+ setFailure("");
1997
+ setNote(Object.keys(next).length === 2 ? ["已从授权说明读出用户名与密钥。", "Read the username and the secret from the authorization text."] : ["已从授权说明读出一项凭据;另一项当前是只读的,不会被改动。", "Read one credential from the authorization text; the other is read-only and is left alone."]);
1998
+ return;
1999
+ }
2000
+ if (looksLikeZhihuPublisherGrant(value)) {
2001
+ setFailure(["这段文字里有知乎凭据标记,但没能读出 App Key 与 App Secret。请确认两项都写成 ZHIHU_OPENAPI_APP_KEY:\"…\" 与 ZHIHU_OPENAPI_APP_SECRET:\"…\" 的形式。", "This text contains Zhihu credential markers, but no App Key and App Secret could be read from it. Both must be written as ZHIHU_OPENAPI_APP_KEY:\"…\" and ZHIHU_OPENAPI_APP_SECRET:\"…\"."]);
2002
+ setNote("");
2003
+ return;
2004
+ }
2005
+ setDrafts((current) => ({
2006
+ ...current,
2007
+ [field.ref]: value
2008
+ }));
2009
+ if (!value.trim()) setPaired(false);
2010
+ setFailure("");
2011
+ setNote("");
2012
+ }
2013
+ }),
2014
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2015
+ className: "dsh-ui-actions",
2016
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZhihuButton, {
2017
+ variant: "primary",
2018
+ disabled: locked || pending !== null || draft.trim() === "",
2019
+ onClick: () => void save([field.ref]),
2020
+ children: pending === field.ref ? t("保存中…", "Saving…") : t("保存", "Save")
2021
+ }), locked ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZhihuButton, {
2022
+ variant: "danger",
2023
+ disabled: pending !== null || !configured,
2024
+ onClick: () => setConfirmRef(field.ref),
2025
+ children: pending === field.ref ? t("清除中…", "Clearing…") : t("清除", "Clear")
2026
+ })]
2027
+ })
2028
+ ]
2029
+ }, field.ref);
2030
+ }),
2031
+ pairReady ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2032
+ className: "dsh-ui-actions",
2033
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZhihuButton, {
2034
+ variant: "primary",
2035
+ disabled: pending !== null,
2036
+ onClick: () => void save([ZHIHU_APP_KEY_REF, ZHIHU_APP_SECRET_REF]),
2037
+ children: pending === PAIR_PENDING ? t("保存中…", "Saving…") : t("保存两项", "Save both")
2038
+ })
2039
+ }) : null,
2040
+ note ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2041
+ className: "dsh-ui-notice",
2042
+ role: "status",
2043
+ children: clientText(note, t)
2044
+ }) : null,
2045
+ failure ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2046
+ className: "dsh-ui-error",
2047
+ role: "alert",
2048
+ children: clientText(failure, t)
2049
+ }) : null,
2050
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
2051
+ open: confirmed !== void 0,
2052
+ onClose: () => setConfirmRef(null),
2053
+ title: t("清除发布助手凭据?", "Clear the publisher credentials?"),
2054
+ closeLabel: t("关闭", "Close"),
2055
+ className: "zhihu-panel",
2056
+ footer: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2057
+ className: "dsh-ui-actions",
2058
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZhihuButton, {
2059
+ onClick: () => setConfirmRef(null),
2060
+ children: t("取消", "Cancel")
2061
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZhihuButton, {
2062
+ variant: "danger",
2063
+ onClick: () => {
2064
+ if (confirmed) clear(confirmed.ref);
2065
+ },
2066
+ children: t("清除", "Clear")
2067
+ })]
2068
+ }),
2069
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2070
+ className: "dsh-ui-help",
2071
+ children: t("清除后,发布与预览会失败,直到重新填写凭据。", "Publishing and previewing fail until you enter the credentials again.")
2072
+ })
2073
+ })
2074
+ ]
2075
+ });
2076
+ }
2077
+ //#endregion
1730
2078
  //#region src/client.tsx
1731
2079
  const name = "dsh-zhihu-client";
1732
2080
  const inject = [
@@ -1738,14 +2086,9 @@ const inject = [
1738
2086
  ];
1739
2087
  const PACKAGE_NAME = "@klarkxy/dsh-zhihu";
1740
2088
  const SLOT_ORDER = 120;
1741
- const activityDots = () => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1742
- className: "zhihu-dots",
1743
- "aria-hidden": "true",
1744
- children: [
1745
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", {}),
1746
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", {}),
1747
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", {})
1748
- ]
2089
+ const activityText = (children) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.TextShimmer, {
2090
+ active: true,
2091
+ children
1749
2092
  });
1750
2093
  function wrapCredentials(remote) {
1751
2094
  return {
@@ -1961,6 +2304,27 @@ function LinkOrText(props) {
1961
2304
  children: props.label
1962
2305
  });
1963
2306
  }
2307
+ /** 直答的思考过程:官方 DisclosureRow 承载折叠,卡面用契约的 flat 卡片。 */
2308
+ function ReasoningDisclosure(props) {
2309
+ const t = useT();
2310
+ const [open, setOpen] = (0, react.useState)(false);
2311
+ const toggle = (0, react.useCallback)(() => setOpen((value) => !value), []);
2312
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2313
+ className: "zhihu-ask-reasoning dsh-ui-card dsh-ui-card--flat",
2314
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.DisclosureRow, {
2315
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconThinkOutlineRegular, {}),
2316
+ title: t("思考过程", "Reasoning"),
2317
+ open,
2318
+ expandable: true,
2319
+ expandOnRowClick: true,
2320
+ onToggle: toggle,
2321
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2322
+ className: "dsh-ui-help",
2323
+ children: props.reasoning
2324
+ })
2325
+ })
2326
+ });
2327
+ }
1964
2328
  function OutcomeView(props) {
1965
2329
  const { outcome, stale } = props;
1966
2330
  const t = useT();
@@ -1973,16 +2337,7 @@ function OutcomeView(props) {
1973
2337
  let summary = "";
1974
2338
  if (outcome.mode === "ask") {
1975
2339
  summary = t(`直答(${outcome.answer.model || "默认模型"})`, `Direct answer (${outcome.answer.model || "default model"})`);
1976
- body = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [outcome.answer.reasoning ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
1977
- className: "zhihu-ask-reasoning dsh-ui-card dsh-ui-card--flat",
1978
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("summary", {
1979
- className: "dsh-ui-heading",
1980
- children: t("思考过程", "Reasoning")
1981
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1982
- className: "dsh-ui-help",
1983
- children: outcome.answer.reasoning
1984
- })]
1985
- }) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2340
+ body = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [outcome.answer.reasoning ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReasoningDisclosure, { reasoning: outcome.answer.reasoning }) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1986
2341
  className: "zhihu-ask-content dsh-ui-wrap",
1987
2342
  children: outcome.answer.content
1988
2343
  })] });
@@ -2125,7 +2480,7 @@ function CapabilitiesNote(props) {
2125
2480
  function SettingsShell(props) {
2126
2481
  const t = useT();
2127
2482
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2128
- className: "dsh-ui-stack",
2483
+ className: "dsh-ui-form",
2129
2484
  "data-testid": "zhihu-settings",
2130
2485
  "aria-label": t("知乎凭证设置", "Zhihu credential settings"),
2131
2486
  children: [props.children, /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CapabilitiesNote, {})]
@@ -2173,10 +2528,10 @@ function SettingsSection(props) {
2173
2528
  (0, react.useEffect)(() => {
2174
2529
  load();
2175
2530
  }, [load]);
2176
- if (state.status === "loading") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingsShell, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
2531
+ if (state.status === "loading") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingsShell, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2177
2532
  className: "dsh-ui-loading",
2178
2533
  role: "status",
2179
- children: [activityDots(), t("正在读取凭证状态…", "Loading credential status…")]
2534
+ children: activityText(t("正在读取凭证状态…", "Loading credential status…"))
2180
2535
  }) });
2181
2536
  if (state.status === "error") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(SettingsShell, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2182
2537
  className: "dsh-ui-error",
@@ -2294,18 +2649,18 @@ function SettingsSection(props) {
2294
2649
  children: clientText(note, t)
2295
2650
  }) : null,
2296
2651
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2297
- className: "dsh-ui-actions",
2652
+ className: "dsh-ui-form-footer",
2298
2653
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZhihuButton, {
2299
2654
  variant: "danger",
2300
2655
  className: "zhihu-button-danger",
2301
2656
  disabled: busy || keyLocked || !configured,
2302
2657
  onClick: () => setConfirmClear(true),
2303
- children: pending === "clear" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react.Fragment, { children: [activityDots(), t("清除中…", "Clearing…")] }) : t("清除", "Clear")
2658
+ children: pending === "clear" ? activityText(t("清除中…", "Clearing…")) : t("清除", "Clear")
2304
2659
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZhihuButton, {
2305
2660
  variant: "primary",
2306
2661
  disabled: busy || keyLocked || keyValue.length === 0 || draftFailure !== void 0,
2307
2662
  onClick: () => void save(),
2308
- children: pending === "save" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react.Fragment, { children: [activityDots(), t("保存中…", "Saving…")] }) : t("保存", "Save")
2663
+ children: pending === "save" ? activityText(t("保存中…", "Saving…")) : t("保存", "Save")
2309
2664
  })]
2310
2665
  }),
2311
2666
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
@@ -2578,10 +2933,10 @@ function UsageSection(props) {
2578
2933
  if (state.status === "loading") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
2579
2934
  "data-testid": "zhihu-usage",
2580
2935
  "aria-label": sectionLabel,
2581
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
2936
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2582
2937
  className: "dsh-ui-loading",
2583
2938
  role: "status",
2584
- children: [activityDots(), t("正在读取用量…", "Loading usage…")]
2939
+ children: activityText(t("正在读取用量…", "Loading usage…"))
2585
2940
  })
2586
2941
  });
2587
2942
  if (state.status === "error") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
@@ -2761,6 +3116,7 @@ function KnowledgeSection(props) {
2761
3116
  const [baseId, setBaseId] = (0, react.useState)("");
2762
3117
  const [file, setFile] = (0, react.useState)(void 0);
2763
3118
  const [fileKey, setFileKey] = (0, react.useState)(0);
3119
+ const fileInputRef = (0, react.useRef)(null);
2764
3120
  const [busy, setBusy] = (0, react.useState)(false);
2765
3121
  const [note, setNote] = (0, react.useState)(void 0);
2766
3122
  const [failure, setFailure] = (0, react.useState)(void 0);
@@ -2856,10 +3212,10 @@ function KnowledgeSection(props) {
2856
3212
  })
2857
3213
  ]
2858
3214
  }),
2859
- list.status === "loading" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
3215
+ list.status === "loading" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2860
3216
  className: "dsh-ui-loading",
2861
3217
  role: "status",
2862
- children: [activityDots(), t("正在读取知识库列表…", "Loading knowledge bases…")]
3218
+ children: activityText(t("正在读取知识库列表…", "Loading knowledge bases…"))
2863
3219
  }) : null,
2864
3220
  list.status === "error" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2865
3221
  className: "dsh-ui-error",
@@ -2916,6 +3272,8 @@ function KnowledgeSection(props) {
2916
3272
  children: t("参考资料", "Reference file")
2917
3273
  }),
2918
3274
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
3275
+ ref: fileInputRef,
3276
+ tabIndex: -1,
2919
3277
  id: "zhihu-kb-file",
2920
3278
  type: "file",
2921
3279
  accept: KB_ACCEPT,
@@ -2926,10 +3284,9 @@ function KnowledgeSection(props) {
2926
3284
  }, fileKey),
2927
3285
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2928
3286
  className: "dsh-ui-row",
2929
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
2930
- htmlFor: "zhihu-kb-file",
2931
- className: "zhihu-file-trigger",
2932
- "aria-disabled": busy || void 0,
3287
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZhihuButton, {
3288
+ disabled: busy,
3289
+ onClick: () => fileInputRef.current?.click(),
2933
3290
  children: t("选择文件", "Choose file")
2934
3291
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2935
3292
  className: "dsh-ui-meta dsh-ui-wrap",
@@ -2948,7 +3305,7 @@ function KnowledgeSection(props) {
2948
3305
  variant: "primary",
2949
3306
  disabled: busy || !file,
2950
3307
  onClick: () => void upload(),
2951
- children: busy ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react.Fragment, { children: [activityDots(), t("上传中…", "Uploading…")] }) : t("确认上传", "Upload")
3308
+ children: busy ? activityText(t("上传中…", "Uploading…")) : t("确认上传", "Upload")
2952
3309
  })
2953
3310
  }) : null,
2954
3311
  note ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
@@ -3199,7 +3556,7 @@ function ZhihuSettings(props) {
3199
3556
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3200
3557
  className: phase === "loading" ? "dsh-ui-loading" : "dsh-ui-hint",
3201
3558
  role: "status",
3202
- children: phase === "loading" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react.Fragment, { children: [activityDots(), t("正在请求知乎…", "Requesting Zhihu…")] }) : phase === "idle" && mode !== "hot" && !query.trim() ? t("输入关键词后搜索。", "Enter keywords to search.") : null
3559
+ children: phase === "loading" ? activityText(t("正在请求知乎…", "Requesting Zhihu…")) : phase === "idle" && mode !== "hot" && !query.trim() ? t("输入关键词后搜索。", "Enter keywords to search.") : null
3203
3560
  }),
3204
3561
  phase === "error" && failure ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3205
3562
  className: "dsh-ui-error",
@@ -3216,12 +3573,16 @@ function ZhihuSettings(props) {
3216
3573
  }, testOperation)]
3217
3574
  });
3218
3575
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
3219
- className: "zhihu-panel zhihu-settings-embed",
3576
+ className: "zhihu-panel zhihu-settings-embed dsh-ui-page",
3220
3577
  "data-testid": "zhihu-settings-embed",
3221
3578
  "aria-label": t("知乎", "Zhihu"),
3222
3579
  children: [
3223
3580
  tablist,
3224
- panel("settings", /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingsSection, { credentials }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZhihuCallPolicySection, { rpc })] })),
3581
+ panel("settings", /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react.Fragment, { children: [
3582
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingsSection, { credentials }),
3583
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZhihuSyncCredentials, { credentials }),
3584
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZhihuCallPolicySection, { rpc })
3585
+ ] })),
3225
3586
  panel("usage", /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageSection, { rpc })),
3226
3587
  panel("quota", /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZhihuQuotaSection, { rpc })),
3227
3588
  panel("knowledge", /* @__PURE__ */ (0, react_jsx_runtime.jsx)(KnowledgeSection, { rpc })),