@cowwo/dsh-icon-custom 0.16.0 → 0.18.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/README.md +22 -7
- package/README.zh.md +22 -7
- package/client/client.js +783 -37
- package/lib/index.js +52 -0
- package/lib/running.js +32 -0
- package/lib/typert.host.js +72 -0
- package/package.json +3 -3
package/lib/index.js
CHANGED
|
@@ -40,6 +40,7 @@ import {
|
|
|
40
40
|
normalizeUnreadConfig,
|
|
41
41
|
pruneSeen
|
|
42
42
|
} from './unread.js'
|
|
43
|
+
import { normalizeRunningConfig } from './running.js'
|
|
43
44
|
|
|
44
45
|
const MAX_BYTES = 10 * 1024 * 1024 // 10 MB
|
|
45
46
|
// Normalized mime -> safe extension. CUR (mouse cursor) is accepted as ICO.
|
|
@@ -338,6 +339,11 @@ export class FaviconCustomService extends TypertRemoteService {
|
|
|
338
339
|
// survives a cleared browser profile and means the same thing on every
|
|
339
340
|
// device, while each browser keeps its own copy as cache + offline queue.
|
|
340
341
|
this.seenPath = join(this.storageDir, 'unread-seen.json')
|
|
342
|
+
// The "show a yellow mark for every session that is running" switch. Its own
|
|
343
|
+
// file and its own endpoint pair: the running FACT is official (the browser
|
|
344
|
+
// list already carries each session's `running`), so nothing about it is
|
|
345
|
+
// stored here — only whether to show the projection of it.
|
|
346
|
+
this.runningPath = join(this.storageDir, 'running.json')
|
|
341
347
|
// { activeId, icons: { [id]: { id, mime, ext, name, size, savedAt, rev, pwa, logo } } }
|
|
342
348
|
this.library = { activeId: null, icons: {} }
|
|
343
349
|
// The active icon's loaded state: { rev, mime, ext, name, size, savedAt, pwa, logo, bytes } | null
|
|
@@ -346,12 +352,16 @@ export class FaviconCustomService extends TypertRemoteService {
|
|
|
346
352
|
// Kept in its own file so a settings change can never corrupt the icon
|
|
347
353
|
// library index.
|
|
348
354
|
this.unread = normalizeUnreadConfig(null)
|
|
355
|
+
// Running-mark switch: whether the yellow projection of the official
|
|
356
|
+
// `running` fact is drawn. See lib/running.js.
|
|
357
|
+
this.running = normalizeRunningConfig(null)
|
|
349
358
|
// Host-side watermark: { [sessionId]: endAt } — see lib/unread.js.
|
|
350
359
|
this.seen = {}
|
|
351
360
|
// Platform PWA manifest (raw + parsed), read once at startup.
|
|
352
361
|
this.baseManifest = loadBaseManifest()
|
|
353
362
|
this._load()
|
|
354
363
|
this._loadUnread()
|
|
364
|
+
this._loadRunning()
|
|
355
365
|
this._loadUnreadSeen()
|
|
356
366
|
// "How did this session's last turn end" as an official session
|
|
357
367
|
// projection. Optional registration on purpose: this plugin is a display
|
|
@@ -450,6 +460,19 @@ export class FaviconCustomService extends TypertRemoteService {
|
|
|
450
460
|
return writeFile(this.unreadPath, JSON.stringify(this.unread, null, 2))
|
|
451
461
|
}
|
|
452
462
|
|
|
463
|
+
/** Load the running-mark switch (missing or corrupt file = default: on). */
|
|
464
|
+
_loadRunning() {
|
|
465
|
+
try {
|
|
466
|
+
const raw = JSON.parse(readFileSync(this.runningPath).toString('utf8'))
|
|
467
|
+
this.running = normalizeRunningConfig(raw)
|
|
468
|
+
} catch { /* no persisted switch yet: keep the default */ }
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/** Persist the running-mark switch (best effort, advisory file). */
|
|
472
|
+
_saveRunning() {
|
|
473
|
+
return writeFile(this.runningPath, JSON.stringify(this.running, null, 2))
|
|
474
|
+
}
|
|
475
|
+
|
|
453
476
|
/**
|
|
454
477
|
* Load the host-side watermark.
|
|
455
478
|
*
|
|
@@ -516,6 +539,35 @@ export class FaviconCustomService extends TypertRemoteService {
|
|
|
516
539
|
return this._unreadRuleView()
|
|
517
540
|
}
|
|
518
541
|
|
|
542
|
+
/**
|
|
543
|
+
* typert endpoint: read the running-mark switch.
|
|
544
|
+
*
|
|
545
|
+
* Its own endpoint pair, not a field on the unread rule: the two features are
|
|
546
|
+
* independent (a red dot says "something ended and you have not looked", a
|
|
547
|
+
* yellow mark says "something is running right now"), and neither switch may
|
|
548
|
+
* drag the other's storage or response shape along. There is deliberately no
|
|
549
|
+
* `running` field here — who is running is the official host's answer and
|
|
550
|
+
* travels in the ordinary session list.
|
|
551
|
+
* @returns `{ enabled }`.
|
|
552
|
+
*/
|
|
553
|
+
async getRunningRule() {
|
|
554
|
+
return { ...this.running }
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* typert endpoint: flip the running-mark switch.
|
|
559
|
+
* @param request - `{ enabled: boolean }`; anything else falls back to the default.
|
|
560
|
+
* @returns the stored switch.
|
|
561
|
+
*/
|
|
562
|
+
async setRunningRule(request) {
|
|
563
|
+
if (request === null || typeof request !== 'object') {
|
|
564
|
+
throw new Error('缺少 enabled 参数')
|
|
565
|
+
}
|
|
566
|
+
this.running = normalizeRunningConfig(request)
|
|
567
|
+
await this._saveRunning().catch(() => {}) // switch file is advisory
|
|
568
|
+
return { ...this.running }
|
|
569
|
+
}
|
|
570
|
+
|
|
519
571
|
/**
|
|
520
572
|
* typert endpoint: merge this browser's "seen" watermarks into the Host's copy.
|
|
521
573
|
*
|
package/lib/running.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-icon-custom · 「进行中黄点」的开关(宿主半,纯函数)
|
|
3
|
+
*
|
|
4
|
+
* 这里**没有**任何"谁在跑"的事实,一条也没有——那是官方的:
|
|
5
|
+
* 宿主 `ctx.agents.get(id)?.status === 'running'` 经 `api-session/status`
|
|
6
|
+
* 事件实时推到浏览器,客户端会话列表里本来就带着每个会话的 `running`。
|
|
7
|
+
* 这个插件只把那件事**多投影几次**(黄点/黄数字/黄 favicon 角标)。
|
|
8
|
+
*
|
|
9
|
+
* 所以这个文件只有一个布尔开关,和一条纪律:
|
|
10
|
+
* **不落盘"进行中",只落盘"要不要显示"。** 进程重启后冷会话按官方口径
|
|
11
|
+
* 永远是 `running:false`,不会留下一个永远亮着的假黄点;而开关是偏好,
|
|
12
|
+
* 应该跨设备、跨重启跟着你,所以它归宿主,和红点规则并列但互不牵连
|
|
13
|
+
* (见 docs/adr/0010-running-mark-is-a-second-projection.md)。
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/** 默认:显示黄点。 */
|
|
17
|
+
export const RUNNING_MARK_DEFAULT = true
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 把任意输入夹成 `{ enabled }`。
|
|
21
|
+
*
|
|
22
|
+
* 只认严格的 `true`/`false`;缺项、脏值、null 一律回落默认——一个读不懂的
|
|
23
|
+
* 开关值不该把一整套标记的可见性变成猜谜。
|
|
24
|
+
* @param input - `{ enabled? }`,可以是脏的、缺项的、null
|
|
25
|
+
* @returns `{ enabled }` —— 永远完整
|
|
26
|
+
*/
|
|
27
|
+
export function normalizeRunningConfig(input) {
|
|
28
|
+
const source = input !== null && typeof input === 'object' ? input : {}
|
|
29
|
+
return {
|
|
30
|
+
enabled: typeof source.enabled === 'boolean' ? source.enabled : RUNNING_MARK_DEFAULT
|
|
31
|
+
}
|
|
32
|
+
}
|
package/lib/typert.host.js
CHANGED
|
@@ -89,6 +89,21 @@ const unreadSeenSchema = z.object({
|
|
|
89
89
|
seen: z.record(z.string(), z.number())
|
|
90
90
|
})
|
|
91
91
|
|
|
92
|
+
/**
|
|
93
|
+
* 进行中黄点的开关。
|
|
94
|
+
*
|
|
95
|
+
* 这里**没有**"谁在跑"的字段,一条都没有:那是官方的 `SessionSummary.running`,
|
|
96
|
+
* 由官方宿主实时推给浏览器,插件只读不存。宿主这一侧只有"要不要显示"这一个偏好,
|
|
97
|
+
* 和红点规则并列、互不牵连(自己的端点对、自己的文件)。
|
|
98
|
+
*/
|
|
99
|
+
const runningRuleSchema = z.object({
|
|
100
|
+
enabled: z.boolean()
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
const setRunningRuleRequestSchema = z.object({
|
|
104
|
+
enabled: z.boolean().optional()
|
|
105
|
+
})
|
|
106
|
+
|
|
92
107
|
const setIconResultSchema = statusSchema
|
|
93
108
|
|
|
94
109
|
export const TYPERT = {
|
|
@@ -238,6 +253,45 @@ export const TYPERT = {
|
|
|
238
253
|
},
|
|
239
254
|
sourceLocation: { file: 'lib/index.js', line: 1, column: 1 }
|
|
240
255
|
},
|
|
256
|
+
{
|
|
257
|
+
id: 'dsh-icon-custom#iconCustom/getRunningRule',
|
|
258
|
+
service: 'iconCustom',
|
|
259
|
+
namespace: 'iconCustom',
|
|
260
|
+
method: 'getRunningRule',
|
|
261
|
+
invocation: { kind: 'direct' },
|
|
262
|
+
parameters: [],
|
|
263
|
+
result: {
|
|
264
|
+
mode: 'strict',
|
|
265
|
+
typeSymbol: 'dsh-icon-custom#iconCustom/getRunningRule:result',
|
|
266
|
+
create: () => runningRuleSchema
|
|
267
|
+
},
|
|
268
|
+
sourceLocation: { file: 'lib/index.js', line: 1, column: 1 }
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
id: 'dsh-icon-custom#iconCustom/setRunningRule',
|
|
272
|
+
service: 'iconCustom',
|
|
273
|
+
namespace: 'iconCustom',
|
|
274
|
+
method: 'setRunningRule',
|
|
275
|
+
invocation: { kind: 'direct' },
|
|
276
|
+
parameters: [
|
|
277
|
+
{
|
|
278
|
+
name: 'request',
|
|
279
|
+
wire: 'request',
|
|
280
|
+
source: 'json',
|
|
281
|
+
codec: {
|
|
282
|
+
mode: 'strict',
|
|
283
|
+
typeSymbol: 'dsh-icon-custom#iconCustom/setRunningRule:request',
|
|
284
|
+
create: () => setRunningRuleRequestSchema
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
],
|
|
288
|
+
result: {
|
|
289
|
+
mode: 'strict',
|
|
290
|
+
typeSymbol: 'dsh-icon-custom#iconCustom/setRunningRule:result',
|
|
291
|
+
create: () => runningRuleSchema
|
|
292
|
+
},
|
|
293
|
+
sourceLocation: { file: 'lib/index.js', line: 1, column: 1 }
|
|
294
|
+
},
|
|
241
295
|
{
|
|
242
296
|
id: 'dsh-icon-custom#iconCustom/setUnreadSeen',
|
|
243
297
|
service: 'iconCustom',
|
|
@@ -345,6 +399,24 @@ export const TYPERT = {
|
|
|
345
399
|
summary: "Merge the seen watermark.",
|
|
346
400
|
jsDoc: "/** Merge the seen watermark. */",
|
|
347
401
|
tags: []
|
|
402
|
+
},
|
|
403
|
+
{
|
|
404
|
+
kind: 'method',
|
|
405
|
+
name: 'getRunningRule',
|
|
406
|
+
signature: 'async getRunningRule(): Promise<{ enabled: boolean }>',
|
|
407
|
+
description: "Read the running-mark switch (yellow mark for sessions that are running right now).",
|
|
408
|
+
summary: "Read the running-mark switch.",
|
|
409
|
+
jsDoc: "/** Read the running-mark switch. */",
|
|
410
|
+
tags: []
|
|
411
|
+
},
|
|
412
|
+
{
|
|
413
|
+
kind: 'method',
|
|
414
|
+
name: 'setRunningRule',
|
|
415
|
+
signature: 'async setRunningRule(request: { enabled?: boolean }): Promise<{ enabled: boolean }>',
|
|
416
|
+
description: "Flip the running-mark switch. The running fact itself is official and is never stored here.",
|
|
417
|
+
summary: "Set the running-mark switch.",
|
|
418
|
+
jsDoc: "/** Set the running-mark switch. */",
|
|
419
|
+
tags: []
|
|
348
420
|
}
|
|
349
421
|
],
|
|
350
422
|
types: []
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cowwo/dsh-icon-custom",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "自定义浏览器标签页图标:在设置页上传 SVG/PNG/ICO,立即生效并持久保存,自动适配安卓/iOS/平板 PWA
|
|
3
|
+
"version": "0.18.0",
|
|
4
|
+
"description": "自定义浏览器标签页图标:在设置页上传 SVG/PNG/ICO,立即生效并持久保存,自动适配安卓/iOS/平板 PWA 图标,显示未读红点与进行中黄点(工作区数字/会话行/左上角/标签页),并可把未读数显示为系统应用图标角标(Windows 任务栏 / macOS Dock,含通知权限申请)。· Custom browser tab favicon: upload SVG/PNG/ICO from the settings page, applies instantly and persists, auto-sizes PWA icons for Android/iOS/tablets, shows unread red dots and running yellow marks (workspace number / session row / top-left / tab icon), and projects the unread count onto the system app-icon badge (Windows taskbar / macOS Dock, notifications permission included).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"scripts": {
|
|
9
|
-
"test": "node test/app-badge.mjs && node test/unread-panel.mjs && node test/unread-clear.mjs && node test/unread-rows.mjs && node test/unread-seen.mjs && node test/unread-host-sync.mjs"
|
|
9
|
+
"test": "node test/app-badge.mjs && node test/notification-permission.mjs && node test/unread-panel.mjs && node test/unread-clear.mjs && node test/unread-rows.mjs && node test/unread-seen.mjs && node test/unread-host-sync.mjs && node test/running-mark.mjs"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"jimp": "^1.6.1",
|