@dsh-ft/dsh-fft-settings 0.5.1 → 0.6.0
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.d.ts +8 -1
- package/lib/client.js +327 -2
- package/lib/client.js.map +1 -1
- package/lib/features/api-key/card.js +2 -2
- package/lib/features/api-key/contract.d.ts +13 -0
- package/lib/features/api-key/contract.js +13 -0
- package/lib/features/free-key-notice/card.d.ts +53 -0
- package/lib/features/free-key-notice/card.js +137 -0
- package/lib/features/free-key-notice/decision.d.ts +40 -0
- package/lib/features/free-key-notice/decision.js +37 -0
- package/lib/features/free-key-notice/index.d.ts +7 -0
- package/lib/features/free-key-notice/index.js +10 -0
- package/lib/features/free-key-notice/storage.d.ts +34 -0
- package/lib/features/free-key-notice/storage.js +58 -0
- package/lib/features/free-key-notice/styles.d.ts +11 -0
- package/lib/features/free-key-notice/styles.js +27 -0
- package/lib/guidance.d.ts +6 -1
- package/lib/guidance.js +6 -1
- package/package.json +3 -1
- package/src/client.tsx +10 -1
- package/src/features/api-key/card.tsx +2 -2
- package/src/features/api-key/contract.ts +13 -0
- package/src/features/free-key-notice/card.tsx +172 -0
- package/src/features/free-key-notice/decision.ts +47 -0
- package/src/features/free-key-notice/index.ts +30 -0
- package/src/features/free-key-notice/storage.ts +62 -0
- package/src/features/free-key-notice/styles.ts +27 -0
- package/src/guidance.ts +6 -1
package/lib/client.d.ts
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
import { type ApiKeyCardContext } from './features/api-key/card.js';
|
|
2
2
|
export * from './core/shell.js';
|
|
3
|
+
export { FREE_KEY_NOTICE_ID, FREE_KEY_NOTICE_SLOT, isViewIdle, shouldShowNotice, type FreeKeyNoticeSessionState } from './features/free-key-notice/decision.js';
|
|
4
|
+
export { readNoticeSeen, writeNoticeSeen, type NoticeFlagStorage } from './features/free-key-notice/storage.js';
|
|
3
5
|
/** Browser context this plugin's own client half requires. */
|
|
4
6
|
export type FtshareSettingsClientContext = ApiKeyCardContext;
|
|
5
7
|
export declare const inject: string[];
|
|
6
8
|
export declare function apply(ctx: FtshareSettingsClientContext): void;
|
|
7
|
-
/**
|
|
9
|
+
/**
|
|
10
|
+
* Contribute the settings section, its cards, and the frame-wide notice.
|
|
11
|
+
*
|
|
12
|
+
* The notice deliberately does not live in the settings page: it has to reach
|
|
13
|
+
* users who never open settings, so it takes a seat in `shell.overlay`.
|
|
14
|
+
*/
|
|
8
15
|
export declare function registerFtshareSettings(ctx: FtshareSettingsClientContext): void;
|
package/lib/client.js
CHANGED
|
@@ -6,6 +6,19 @@ window.__ModuleLoader__.load({
|
|
|
6
6
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
7
|
let react = require("react");
|
|
8
8
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
9
|
+
/**
|
|
10
|
+
* 申请/购买 API Key 的页面。`utm_source=dsh-fft` 标记来源为本插件,站点据此归因;
|
|
11
|
+
* 卡片与一次性提示共用这一条地址,不要在别处另写一份。
|
|
12
|
+
*/
|
|
13
|
+
const FTSHARE_API_KEY_APPLY_URL = "https://ftai.chat/me/profile?tab=api-key&utm_source=dsh-fft";
|
|
14
|
+
/**
|
|
15
|
+
* 设备级「已看过免费 Key 提示」标记的 localStorage 键。
|
|
16
|
+
*
|
|
17
|
+
* 带版本号:改一次文案就把版本加一,让所有用户重新看一次,不需要让用户清缓存。
|
|
18
|
+
* 设备级语义(重启、升级都保留,跨 workspace/窗口共享)由桌面版主窗口的
|
|
19
|
+
* `dsh-app://app/` origin 私有存储提供,不写宿主的配置文件。
|
|
20
|
+
*/
|
|
21
|
+
const FREE_KEY_NOTICE_STORAGE_KEY = "dsh-fft.free-key-notice.v1";
|
|
9
22
|
const FTSHARE_CREDENTIAL_EVENTS = ["credentials/reference-updated"];
|
|
10
23
|
/** Local syntax validation, never an assertion of upstream validity. */
|
|
11
24
|
function normalizeApiKey(input) {
|
|
@@ -363,7 +376,7 @@ window.__ModuleLoader__.load({
|
|
|
363
376
|
children: "重新读取状态"
|
|
364
377
|
}),
|
|
365
378
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
366
|
-
href:
|
|
379
|
+
href: FTSHARE_API_KEY_APPLY_URL,
|
|
367
380
|
target: "_blank",
|
|
368
381
|
rel: "noreferrer",
|
|
369
382
|
children: "获取 API Key ↗"
|
|
@@ -415,6 +428,306 @@ window.__ModuleLoader__.load({
|
|
|
415
428
|
]
|
|
416
429
|
})] });
|
|
417
430
|
}
|
|
431
|
+
// src/features/free-key-notice/storage.ts
|
|
432
|
+
/**
|
|
433
|
+
* 取浏览器存储;拿不到(没有 window、被禁用)时返回 undefined。
|
|
434
|
+
* @returns 可用存储,或 undefined。
|
|
435
|
+
*/
|
|
436
|
+
function browserNoticeStorage() {
|
|
437
|
+
try {
|
|
438
|
+
const storage = globalThis.localStorage;
|
|
439
|
+
return storage === void 0 ? void 0 : {
|
|
440
|
+
getItem: (key) => storage.getItem(key),
|
|
441
|
+
setItem: (key, value) => {
|
|
442
|
+
storage.setItem(key, value);
|
|
443
|
+
}
|
|
444
|
+
};
|
|
445
|
+
} catch (_blocked) {
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* 读标记。任何异常与空值都算「没看过」。
|
|
451
|
+
* @param storage - 目标存储;缺失视为没看过。
|
|
452
|
+
* @param key - 标记键。
|
|
453
|
+
* @returns 是否已看过。
|
|
454
|
+
*/
|
|
455
|
+
function readNoticeSeen(storage, key) {
|
|
456
|
+
if (storage === void 0) return false;
|
|
457
|
+
try {
|
|
458
|
+
return storage.getItem(key) === "1";
|
|
459
|
+
} catch (_blocked) {
|
|
460
|
+
return false;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* 写标记。写失败不抛:提示已经显示过了,这次没记下来只是下次会再弹一次。
|
|
465
|
+
* @param storage - 目标存储;缺失时什么都不做。
|
|
466
|
+
* @param key - 标记键。
|
|
467
|
+
* @returns 是否写入成功。
|
|
468
|
+
*/
|
|
469
|
+
function writeNoticeSeen(storage, key) {
|
|
470
|
+
if (storage === void 0) return false;
|
|
471
|
+
try {
|
|
472
|
+
storage.setItem(key, "1");
|
|
473
|
+
return true;
|
|
474
|
+
} catch (_blocked) {
|
|
475
|
+
return false;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
// src/features/free-key-notice/decision.ts
|
|
479
|
+
/**
|
|
480
|
+
* 「这条一次性提示该不该弹」的判定。
|
|
481
|
+
*
|
|
482
|
+
* 与渲染分开:判定是纯函数、零依赖,能在 Node 测试里直接钉住;渲染层只管把
|
|
483
|
+
* 模态框摆出去。三条输入里任何一条没准备好都按「不弹」处理——这是刻意的,
|
|
484
|
+
* 一次没弹只是少一次提醒,弹错一次就是打扰。
|
|
485
|
+
*/
|
|
486
|
+
/** 宿主 `shell.overlay` 槽名;这个 feature 只往这一个座位贡献。 */
|
|
487
|
+
const FREE_KEY_NOTICE_SLOT = "shell.overlay";
|
|
488
|
+
/** 条目 id:必须自己的,占用宿主的会顶掉别人的浮层。 */
|
|
489
|
+
const FREE_KEY_NOTICE_ID = "ftshare.free-key-notice";
|
|
490
|
+
/**
|
|
491
|
+
* 主视图是否停在一个空会话上(没有内容会被弹窗挡住)。
|
|
492
|
+
* @param state - 会话列表快照。
|
|
493
|
+
* @returns 是否可以安全地弹出模态框。
|
|
494
|
+
*/
|
|
495
|
+
function isViewIdle(state) {
|
|
496
|
+
if (state.phase !== "ready") return false;
|
|
497
|
+
const main = Object.values(state.byId).find((session) => (session.retainedBy?.mainView ?? 0) > 0);
|
|
498
|
+
return main === void 0 || main.blank;
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* 是否应当显示提示。
|
|
502
|
+
* - 已看过:不弹(设备级一次);
|
|
503
|
+
* - Key 已配置、或状态还读不到:不弹(读不到时宁可少弹);
|
|
504
|
+
* - 主视图被有内容的会话占着:不弹,等它空下来。
|
|
505
|
+
* @param facts - 三个输入。
|
|
506
|
+
* @returns 是否显示。
|
|
507
|
+
*/
|
|
508
|
+
function shouldShowNotice(facts) {
|
|
509
|
+
if (facts.seen) return false;
|
|
510
|
+
if (facts.configured !== false) return false;
|
|
511
|
+
return facts.viewIdle;
|
|
512
|
+
}
|
|
513
|
+
// src/features/free-key-notice/styles.ts
|
|
514
|
+
/**
|
|
515
|
+
* 一次性提示的样式:固定在左下角的非阻塞卡片。
|
|
516
|
+
*
|
|
517
|
+
* 不盖遮罩、不抢焦点——这一层(宿主的 `shell.overlay`)本身可穿透点击,
|
|
518
|
+
* 条目只有在这张卡片范围内才接收指针事件,所以卡片以外的界面照常可用。
|
|
519
|
+
* 位置用 `fixed` 而不是依赖外层布局:浮层是 `inset:0` 的绝对定位层,
|
|
520
|
+
* 条目自己决定停在哪里。
|
|
521
|
+
*
|
|
522
|
+
* 颜色全走 `--dsw-alias-*` 与 `--ft-*` 变量,深色/浅色两套调色板共用一份。
|
|
523
|
+
*/
|
|
524
|
+
const FREE_KEY_NOTICE_CSS = `
|
|
525
|
+
.ftshare-free-key-notice { position:fixed; left:24px; bottom:24px; z-index:20; box-sizing:border-box; width:min(320px, calc(100vw - 48px)); padding:16px; border:0.5px solid var(--dsw-alias-border-l2,rgba(0,0,0,.1)); border-radius:var(--dsw-radius-xl,20px); background:var(--dsw-alias-bg-layer-2,#fff); color:var(--dsw-alias-label-primary,#0f1115); box-shadow:var(--dsw-elevation-prominent,0 3px 8px rgba(0,0,0,.04), 0 0 20px rgba(0,0,0,.05)); animation:ftshareNoticeIn .2s ease; }
|
|
526
|
+
.ftshare-free-key-notice * { box-sizing:border-box; }
|
|
527
|
+
.ftshare-free-key-notice .ftshare-free-key-head { display:flex; align-items:flex-start; justify-content:space-between; gap:8px; }
|
|
528
|
+
.ftshare-free-key-notice strong { font-size:14px; font-weight:500; line-height:22px; }
|
|
529
|
+
.ftshare-free-key-notice .ftshare-free-key-close { flex:none; width:22px; height:22px; display:inline-flex; align-items:center; justify-content:center; border:none; border-radius:var(--dsw-radius-sm,8px); background:transparent; color:var(--dsw-alias-label-secondary,#61666b); font:inherit; font-size:12px; line-height:1; cursor:pointer; }
|
|
530
|
+
.ftshare-free-key-notice .ftshare-free-key-close:hover { background:var(--dsw-alias-bg-layer-3,#f9fafb); }
|
|
531
|
+
.ftshare-free-key-notice p { margin:6px 0 14px; font-size:13px; line-height:1.6; color:var(--dsw-alias-label-secondary,#61666b); }
|
|
532
|
+
.ftshare-free-key-notice .ftshare-free-key-actions { display:flex; gap:8px; }
|
|
533
|
+
.ftshare-free-key-notice .ftshare-free-key-actions :is(button,a) { min-height:30px; display:inline-flex; align-items:center; justify-content:center; border-radius:var(--dsw-radius-md,12px); border:0.5px solid var(--dsw-alias-border-l4,rgba(0,0,0,.16)); padding:0 12px; font:inherit; font-size:13px; background:transparent; color:inherit; text-decoration:none; cursor:pointer; }
|
|
534
|
+
/* 主入口用品牌黄的半透明底:保留 FT 的色,又不至于在左下角变成一块醒目的亮黄。
|
|
535
|
+
文字仍取正文主色(不是压在上面的深黑字),70% 在深浅两套调色板下都能读。 */
|
|
536
|
+
.ftshare-free-key-notice .ftshare-free-key-actions .primary { flex:1; background:color-mix(in srgb,var(--ft-accent,#f0c433) 70%,transparent); border-color:var(--ft-accent,#f0c433); color:var(--dsw-alias-label-primary,#0f1115); font-weight:500; }
|
|
537
|
+
.ftshare-free-key-notice .ftshare-free-key-actions .primary:hover { background:color-mix(in srgb,var(--ft-accent,#f0c433) 82%,transparent); }
|
|
538
|
+
@keyframes ftshareNoticeIn { from { opacity:0; transform:translateY(6px); } to { opacity:1; transform:none; } }
|
|
539
|
+
@media (prefers-reduced-motion: reduce) { .ftshare-free-key-notice { animation:none; } }
|
|
540
|
+
`;
|
|
541
|
+
// src/features/free-key-notice/card.tsx
|
|
542
|
+
/**
|
|
543
|
+
* 一次性提示:还没配置 API Key 的用户,去获取免费的 FTShare Key。
|
|
544
|
+
*
|
|
545
|
+
* 三个设计决定:
|
|
546
|
+
*
|
|
547
|
+
* 1. **左下角、不阻塞**。它是提示,不是必须处理的阻断:不盖遮罩、不抢焦点、
|
|
548
|
+
* 不打断正在发生的事,12 秒后自己淡出(鼠标悬停时暂停计时)。用 `Modal`
|
|
549
|
+
* 会遮住整个界面,代价太大。
|
|
550
|
+
* 2. **只在确认「没配 Key」时出现**。凭据状态异步读来,没读到时一律不出现,
|
|
551
|
+
* 否则配了 Key 的用户每次启动都会被闪一下。
|
|
552
|
+
* 3. **设备级只出现一次**。首次出现就写 `localStorage` 标记(见 `./storage.ts`),
|
|
553
|
+
* 不等用户点按钮——否则自动消失后标记不落盘,下次启动又会打扰。
|
|
554
|
+
*
|
|
555
|
+
* 挂载在宿主框架级的 `shell.overlay` 槽上(`ui-layout` 的 AppFrame 常驻渲染),
|
|
556
|
+
* 不在设置页里:要触达的恰恰是从不打开设置的用户。该图层本身可穿透点击,
|
|
557
|
+
* 条目只在自己的卡片范围内接收指针事件。
|
|
558
|
+
*/
|
|
559
|
+
/** 停留时长:足够读完一句话并决定要不要去获取,又不至于一直挂在界面上。 */
|
|
560
|
+
const FREE_KEY_NOTICE_AUTO_DISMISS_MS = 12e3;
|
|
561
|
+
/**
|
|
562
|
+
* 把异步的 `configured` 摊成 uSES 快照。只在有订阅者时读一次,订阅清理时停止。
|
|
563
|
+
* @param deps - 读函数与订阅函数。
|
|
564
|
+
* @returns uSES 快照源。
|
|
565
|
+
*/
|
|
566
|
+
function useConfigured(deps) {
|
|
567
|
+
const source = (0, react.useMemo)(() => {
|
|
568
|
+
let value;
|
|
569
|
+
let stop;
|
|
570
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
571
|
+
const publish = () => {
|
|
572
|
+
for (const listener of [...listeners]) listener();
|
|
573
|
+
};
|
|
574
|
+
const load = () => {
|
|
575
|
+
deps.readConfigured().then((next) => {
|
|
576
|
+
value = next;
|
|
577
|
+
publish();
|
|
578
|
+
}, () => {});
|
|
579
|
+
};
|
|
580
|
+
return {
|
|
581
|
+
getSnapshot: () => value,
|
|
582
|
+
subscribe: (listener) => {
|
|
583
|
+
listeners.add(listener);
|
|
584
|
+
if (stop === void 0) {
|
|
585
|
+
load();
|
|
586
|
+
const off = deps.subscribeConfigured(load);
|
|
587
|
+
stop = () => {
|
|
588
|
+
off();
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
return () => {
|
|
592
|
+
listeners.delete(listener);
|
|
593
|
+
if (listeners.size === 0) {
|
|
594
|
+
stop?.();
|
|
595
|
+
stop = void 0;
|
|
596
|
+
}
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
};
|
|
600
|
+
}, [deps]);
|
|
601
|
+
return (0, react.useSyncExternalStore)(source.subscribe, source.getSnapshot, source.getSnapshot);
|
|
602
|
+
}
|
|
603
|
+
/**
|
|
604
|
+
* 提示本体。
|
|
605
|
+
* @param props - 依赖注入,以及宿主通过槽位交给每个条目的标准份额。
|
|
606
|
+
* @returns 左下角卡片,或 null。
|
|
607
|
+
*/
|
|
608
|
+
function FreeKeyNotice({ deps, useSessions }) {
|
|
609
|
+
const [seen, setSeen] = (0, react.useState)(() => readNoticeSeen(deps.storage, FREE_KEY_NOTICE_STORAGE_KEY));
|
|
610
|
+
const [visible, setVisible] = (0, react.useState)(true);
|
|
611
|
+
const configured = useConfigured(deps);
|
|
612
|
+
const viewIdle = useSessions(isViewIdle);
|
|
613
|
+
const show = visible && shouldShowNotice({
|
|
614
|
+
seen,
|
|
615
|
+
configured,
|
|
616
|
+
viewIdle
|
|
617
|
+
});
|
|
618
|
+
(0, react.useEffect)(() => {
|
|
619
|
+
if (!show || seen) return;
|
|
620
|
+
writeNoticeSeen(deps.storage, FREE_KEY_NOTICE_STORAGE_KEY);
|
|
621
|
+
setSeen(true);
|
|
622
|
+
}, [
|
|
623
|
+
show,
|
|
624
|
+
seen,
|
|
625
|
+
deps.storage
|
|
626
|
+
]);
|
|
627
|
+
const deadline = (0, react.useRef)(void 0);
|
|
628
|
+
const timer = (0, react.useRef)(void 0);
|
|
629
|
+
const dismiss = (0, react.useCallback)(() => {
|
|
630
|
+
setVisible(false);
|
|
631
|
+
}, []);
|
|
632
|
+
const hold = (0, react.useCallback)(() => {
|
|
633
|
+
if (timer.current !== void 0) clearTimeout(timer.current);
|
|
634
|
+
timer.current = void 0;
|
|
635
|
+
deadline.current = void 0;
|
|
636
|
+
}, []);
|
|
637
|
+
const resume = (0, react.useCallback)(() => {
|
|
638
|
+
if (deadline.current === void 0) return;
|
|
639
|
+
const remaining = Math.max(0, deadline.current - Date.now());
|
|
640
|
+
timer.current = setTimeout(dismiss, remaining);
|
|
641
|
+
}, [dismiss]);
|
|
642
|
+
(0, react.useEffect)(() => {
|
|
643
|
+
if (!show) return;
|
|
644
|
+
deadline.current = Date.now() + FREE_KEY_NOTICE_AUTO_DISMISS_MS;
|
|
645
|
+
timer.current = setTimeout(dismiss, FREE_KEY_NOTICE_AUTO_DISMISS_MS);
|
|
646
|
+
return () => {
|
|
647
|
+
if (timer.current !== void 0) clearTimeout(timer.current);
|
|
648
|
+
timer.current = void 0;
|
|
649
|
+
deadline.current = void 0;
|
|
650
|
+
};
|
|
651
|
+
}, [show, dismiss]);
|
|
652
|
+
if (!show) return null;
|
|
653
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: FREE_KEY_NOTICE_CSS }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("aside", {
|
|
654
|
+
className: "ftshare-free-key-notice",
|
|
655
|
+
role: "status",
|
|
656
|
+
"aria-live": "polite",
|
|
657
|
+
onMouseEnter: hold,
|
|
658
|
+
onMouseLeave: resume,
|
|
659
|
+
children: [
|
|
660
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
661
|
+
className: "ftshare-free-key-head",
|
|
662
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: "免费获取 Key" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
663
|
+
type: "button",
|
|
664
|
+
className: "ftshare-free-key-close",
|
|
665
|
+
"aria-label": "关闭",
|
|
666
|
+
onClick: dismiss,
|
|
667
|
+
children: "✕"
|
|
668
|
+
})]
|
|
669
|
+
}),
|
|
670
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: "海量金融数据,一次配置长期使用" }),
|
|
671
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
672
|
+
className: "ftshare-free-key-actions",
|
|
673
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
674
|
+
className: "primary",
|
|
675
|
+
href: deps.applyUrl,
|
|
676
|
+
target: "_blank",
|
|
677
|
+
rel: "noreferrer",
|
|
678
|
+
onClick: dismiss,
|
|
679
|
+
children: "去获取"
|
|
680
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
681
|
+
type: "button",
|
|
682
|
+
onClick: dismiss,
|
|
683
|
+
children: "知道了"
|
|
684
|
+
})]
|
|
685
|
+
})
|
|
686
|
+
]
|
|
687
|
+
})] });
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
* 浏览器侧装配:把凭据通道、真实 localStorage 接到组件上。
|
|
691
|
+
* `useSessions` 是宿主交给 `shell.overlay` 条目的标准份额,从 props 透传而不是自造。
|
|
692
|
+
* @param ctx - 卡片那套上下文(slots + 凭据失效事件)。
|
|
693
|
+
* @returns 槽位组件工厂。
|
|
694
|
+
*/
|
|
695
|
+
function createFreeKeyNotice(ctx) {
|
|
696
|
+
const api = createApiKeyClient(createSettingsTransport());
|
|
697
|
+
const subscribeConfigured = (listener) => {
|
|
698
|
+
const disposers = FTSHARE_CREDENTIAL_EVENTS.map((event) => ctx.remote.$on(event, (ref) => {
|
|
699
|
+
if (ref === "FTSHARE_API_KEY") listener();
|
|
700
|
+
}));
|
|
701
|
+
disposers.push(ctx.on("connection/reset", listener));
|
|
702
|
+
return () => {
|
|
703
|
+
for (const dispose of disposers) dispose();
|
|
704
|
+
};
|
|
705
|
+
};
|
|
706
|
+
const deps = {
|
|
707
|
+
readConfigured: async () => (await api.describe()).configured,
|
|
708
|
+
subscribeConfigured,
|
|
709
|
+
storage: browserNoticeStorage(),
|
|
710
|
+
applyUrl: FTSHARE_API_KEY_APPLY_URL
|
|
711
|
+
};
|
|
712
|
+
return ({ useSessions }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FreeKeyNotice, {
|
|
713
|
+
deps,
|
|
714
|
+
useSessions
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
// src/features/free-key-notice/index.ts
|
|
718
|
+
/**
|
|
719
|
+
* 注册一次性提示。
|
|
720
|
+
* @param ctx - 客户端上下文,需带 `slots` 服务。
|
|
721
|
+
* @returns 幂等 disposer。
|
|
722
|
+
*/
|
|
723
|
+
function registerFreeKeyNotice(ctx) {
|
|
724
|
+
const component = createFreeKeyNotice(ctx);
|
|
725
|
+
return ctx.slots.inject(FREE_KEY_NOTICE_SLOT, () => ctx.slots.register({
|
|
726
|
+
name: FREE_KEY_NOTICE_SLOT,
|
|
727
|
+
id: FREE_KEY_NOTICE_ID,
|
|
728
|
+
order: 100
|
|
729
|
+
}, component));
|
|
730
|
+
}
|
|
418
731
|
// src/features/logs/contract.ts
|
|
419
732
|
/**
|
|
420
733
|
* `logs` 命名空间的共享契约:只有常量与类型,浏览器半边可以安全导入。
|
|
@@ -788,17 +1101,29 @@ window.__ModuleLoader__.load({
|
|
|
788
1101
|
registerFtshareSettings(ctx);
|
|
789
1102
|
applyBrandTokens(ctx);
|
|
790
1103
|
}
|
|
791
|
-
/**
|
|
1104
|
+
/**
|
|
1105
|
+
* Contribute the settings section, its cards, and the frame-wide notice.
|
|
1106
|
+
*
|
|
1107
|
+
* The notice deliberately does not live in the settings page: it has to reach
|
|
1108
|
+
* users who never open settings, so it takes a seat in `shell.overlay`.
|
|
1109
|
+
*/
|
|
792
1110
|
function registerFtshareSettings(ctx) {
|
|
793
1111
|
registerSettingsSection(ctx);
|
|
794
1112
|
registerApiKeyCard(ctx);
|
|
795
1113
|
registerLogsCard(ctx);
|
|
1114
|
+
registerFreeKeyNotice(ctx);
|
|
796
1115
|
}
|
|
1116
|
+
exports.FREE_KEY_NOTICE_ID = FREE_KEY_NOTICE_ID;
|
|
1117
|
+
exports.FREE_KEY_NOTICE_SLOT = FREE_KEY_NOTICE_SLOT;
|
|
797
1118
|
exports.FtshareSettingsSection = FtshareSettingsSection;
|
|
798
1119
|
exports.apply = apply;
|
|
799
1120
|
exports.inject = inject;
|
|
1121
|
+
exports.isViewIdle = isViewIdle;
|
|
1122
|
+
exports.readNoticeSeen = readNoticeSeen;
|
|
800
1123
|
exports.registerFtshareSettings = registerFtshareSettings;
|
|
801
1124
|
exports.registerSettingsSection = registerSettingsSection;
|
|
1125
|
+
exports.shouldShowNotice = shouldShowNotice;
|
|
1126
|
+
exports.writeNoticeSeen = writeNoticeSeen;
|
|
802
1127
|
return module.exports;
|
|
803
1128
|
}
|
|
804
1129
|
});
|