dsh-tiddlywiki 0.28.2 → 0.28.4
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 +2 -0
- package/docs/CHANGELOG.md +2 -0
- package/lib/client.js +52 -28
- package/lib/index.js +63 -3
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/client/dock-align.ts +121 -0
- package/src/client/knowledge-fab.ts +38 -4
- package/src/client/note-widget.ts +10 -1
- package/src/client/quick-note-dock.ts +5 -80
- package/src/client/settings-page.ts +45 -1
- package/src/client/sidebar-entry.ts +112 -19
- package/src/client/status-cache.ts +2 -0
- package/src/client/styles.ts +25 -1
- package/src/client/wiki-icon.ts +65 -0
- package/src/client/wiki-scope-dock.ts +12 -1
- package/src/host/seed-notes.ts +22 -0
- package/src/host/wiki-registry.ts +47 -0
- package/src/index.ts +4 -0
package/src/client/styles.ts
CHANGED
|
@@ -23,7 +23,20 @@ const CSS_TEXT = `
|
|
|
23
23
|
cursor: pointer; text-align: left;
|
|
24
24
|
}
|
|
25
25
|
.dsh-tw-entry:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,.12)); color: var(--dsw-alias-label-primary, inherit); }
|
|
26
|
+
/* 每库自定义图标(v0.28.4):emoji 走 textContent,尺寸与 SVG 图标对齐。 */
|
|
27
|
+
.dsh-tw-entry-icon.dsh-tw-entry-emoji {
|
|
28
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
29
|
+
width: 14px; height: 14px; font-size: 13px; line-height: 1;
|
|
30
|
+
}
|
|
31
|
+
.dsh-tw-settings-icon-select {
|
|
32
|
+
border: 1px solid var(--dsw-alias-border-l2, rgba(0,0,0,.18));
|
|
33
|
+
background: var(--dsw-alias-bg-layer-2, #fff); color: inherit; font: inherit; font-size: 12px;
|
|
34
|
+
padding: 3px 8px; border-radius: 999px; max-width: 180px;
|
|
35
|
+
}
|
|
26
36
|
.dsh-tw-entry[data-active="true"] { background: var(--dsw-alias-interactive-bg-active, rgba(128,128,128,.18)); color: var(--dsw-alias-label-primary, inherit); font-weight: 500; }
|
|
37
|
+
/* 多库(v0.28.2):每库一个入口行,当前焦点库加一条左侧标线,和「面板正打开哪个库」区分开
|
|
38
|
+
—— 面板是同一个,但焦点库决定它加载谁。 */
|
|
39
|
+
.dsh-tw-entry[data-focus="true"] { box-shadow: inset 2px 0 0 0 var(--dsw-alias-brand-primary, #3e63dd); }
|
|
27
40
|
.dsh-tw-entry svg { flex: none; }
|
|
28
41
|
.dsh-tw-entry .dsh-tw-entry-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
29
42
|
/* Collapsed rail: the shell narrows the sidebar to a 56px icon rail (dual
|
|
@@ -501,8 +514,13 @@ html[data-dsh-tw-active] .dshDesktopConversationSurface > :not([data-dsh-tw-view
|
|
|
501
514
|
/* ── 会话级知识库选择器(v0.28.0,同一个 conversation.input.dock 槽位)──
|
|
502
515
|
与快速笔记按钮同区、纵向 flex 排列;只在"多于一个可见库"时渲染(组件自己返回
|
|
503
516
|
null),所以单库安装这一整块都不存在。 */
|
|
517
|
+
/* ── 会话级知识库选择器(v0.28.0,同一个 conversation.input.dock 槽位)──
|
|
518
|
+
与快速笔记按钮同区、纵向 flex 排列;只在"多于一个可见库"时渲染(组件自己返回
|
|
519
|
+
null),所以单库安装这一整块都不存在。
|
|
520
|
+
justify-content: flex-end 与 JS 测得的 paddingRight 一起把右缘对齐 composer
|
|
521
|
+
输入框(见 client/dock-align.ts)—— 与 .dsh-tw-dock-note 同一套做法。 */
|
|
504
522
|
.dsh-tw-scope-dock {
|
|
505
|
-
display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
|
|
523
|
+
display: flex; align-items: center; justify-content: flex-end; gap: 8px; flex-wrap: wrap;
|
|
506
524
|
padding: 4px 8px 0; box-sizing: border-box; width: 100%; font-size: 12px;
|
|
507
525
|
}
|
|
508
526
|
.dsh-tw-scope-label { opacity: .7; }
|
|
@@ -515,6 +533,11 @@ html[data-dsh-tw-active] .dshDesktopConversationSurface > :not([data-dsh-tw-view
|
|
|
515
533
|
.dsh-tw-scope-note { opacity: .75; }
|
|
516
534
|
/* 快速笔记卡片的目标库选择(v0.28.0,R7):只在多库时出现。 */
|
|
517
535
|
.dsh-tw-note-wiki { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; }
|
|
536
|
+
/* ⚠️ hidden 属性在 CSS 里只是 display:none,**任何显式 display 都能盖掉它** ——
|
|
537
|
+
上面那条 display:inline-flex 就盖掉了,于是单库模式下这个空下拉照样显示出来
|
|
538
|
+
(作者 2026-09-28 报障:"有个写入下拉,但是没有选项")。显式尊重 hidden,
|
|
539
|
+
并把这一条写在后面(同优先级靠后者生效)。 */
|
|
540
|
+
.dsh-tw-note-wiki[hidden] { display: none; }
|
|
518
541
|
.dsh-tw-note-wiki-label { opacity: .7; }
|
|
519
542
|
.dsh-tw-note-wiki-select {
|
|
520
543
|
border: 1px solid var(--dsw-alias-border-l2, rgba(0,0,0,.18));
|
|
@@ -522,6 +545,7 @@ html[data-dsh-tw-active] .dshDesktopConversationSurface > :not([data-dsh-tw-view
|
|
|
522
545
|
padding: 2px 6px; border-radius: 999px; max-width: 220px;
|
|
523
546
|
}
|
|
524
547
|
.dsh-tw-note-wiki-hint { font-size: 12px; opacity: .75; }
|
|
548
|
+
.dsh-tw-note-wiki-hint[hidden] { display: none; }
|
|
525
549
|
|
|
526
550
|
/* ── input-dock quick-note button(聊天输入框上方,conversation.input.dock)──
|
|
527
551
|
该槽位渲染为「输入框上方的全宽条目」纵向 flex 排列(todo/cost-meter/goal/
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-wiki entry icons (v0.28.4).
|
|
3
|
+
*
|
|
4
|
+
* A wiki's icon is part of its identity, stored next to `label` in the control
|
|
5
|
+
* file (`wikis.json`). A value is either one of {@link ICON_SVG}'s names — a
|
|
6
|
+
* curated set of hand-written 16×16 stroke SVGs that inherit `currentColor`, so
|
|
7
|
+
* they look right in both DSH themes with no assets to ship — or a short emoji
|
|
8
|
+
* literal (📚 / 💼 / 🏠), which costs nothing and tells four wikis apart at a
|
|
9
|
+
* glance.
|
|
10
|
+
*
|
|
11
|
+
* The host validates the same shape (`normalizeWikiIcon`); this module is only
|
|
12
|
+
* the rendering side, and it must never throw on a value it does not recognise —
|
|
13
|
+
* an unknown string falls back to the default icon rather than breaking the row.
|
|
14
|
+
*
|
|
15
|
+
* @module dsh-tiddlywiki/client/wiki-icon
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/** The default icon: a wiki page with a TiddlyWiki-style "T". */
|
|
19
|
+
export const DEFAULT_ICON_SVG =
|
|
20
|
+
'<svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 2.5h8a1 1 0 0 1 1 1v9a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1v-9a1 1 0 0 1 1-1z"/><path d="M6 6h4M6 8.5h2.5"/></svg>'
|
|
21
|
+
|
|
22
|
+
/** The curated name → SVG map. Names match `WIKI_ICON_NAMES` on the host. */
|
|
23
|
+
export const ICON_SVG: Record<string, string> = {
|
|
24
|
+
book: '<svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3.5 3h9v10h-9z"/><path d="M6.5 3v10"/><path d="M8.5 6h2.5M8.5 8.5h2"/></svg>',
|
|
25
|
+
briefcase: '<svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2.5 5.5h11v7h-11z"/><path d="M6 5.5V4a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v1.5"/><path d="M2.5 8.5h11"/></svg>',
|
|
26
|
+
home: '<svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2.5 7.5 8 3l5.5 4.5"/><path d="M4 7v6h8V7"/></svg>',
|
|
27
|
+
notebook: '<svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 2.5h7.5v11H4z"/><path d="M6 2.5v11"/><path d="M8 6h2M8 8.5h2"/></svg>',
|
|
28
|
+
flask: '<svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6.5 2.5v4L3.5 12a1 1 0 0 0 .9 1.5h7.2A1 1 0 0 0 12.5 12L9.5 6.5v-4"/><path d="M5.5 2.5h5"/><path d="M4.8 10h6.4"/></svg>',
|
|
29
|
+
globe: '<svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="8" cy="8" r="5.5"/><path d="M2.5 8h11"/><path d="M8 2.5c1.6 1.6 2.4 3.5 2.4 5.5S9.6 12 8 13.5C6.4 12 5.6 10 5.6 8s.8-3.9 2.4-5.5z"/></svg>',
|
|
30
|
+
star: '<svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m8 2.5 1.7 3.6 3.8.5-2.8 2.7.7 3.9L8 11.4l-3.4 1.8.7-3.9-2.8-2.7 3.8-.5z"/></svg>',
|
|
31
|
+
archive: '<svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2.5 3.5h11v3h-11z"/><path d="M3.5 6.5v6h9v-6"/><path d="M6.5 9h3"/></svg>',
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** The curated names, in picker order (mirrors the host's `WIKI_ICON_NAMES`). */
|
|
35
|
+
export const ICON_NAMES: readonly string[] = Object.keys(ICON_SVG)
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Is this value a name we ship an SVG for? Anything else is treated as an emoji
|
|
39
|
+
* literal by {@link applyWikiIcon}.
|
|
40
|
+
*/
|
|
41
|
+
export function isIconName(value: string): boolean {
|
|
42
|
+
return Object.prototype.hasOwnProperty.call(ICON_SVG, value)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Put `icon` into `el` (an `.dsh-tw-entry-icon` span), falling back to the
|
|
47
|
+
* default. A name sets innerHTML to our own SVG; anything else is set as TEXT
|
|
48
|
+
* content, never HTML — the value comes from a file a user can edit, so it must
|
|
49
|
+
* not be able to inject markup into the sidebar.
|
|
50
|
+
*/
|
|
51
|
+
export function applyWikiIcon(el: HTMLElement, icon: string | undefined): void {
|
|
52
|
+
if (icon !== undefined && isIconName(icon)) {
|
|
53
|
+
el.innerHTML = ICON_SVG[icon] ?? DEFAULT_ICON_SVG
|
|
54
|
+
el.classList.remove('dsh-tw-entry-emoji')
|
|
55
|
+
return
|
|
56
|
+
}
|
|
57
|
+
if (icon !== undefined && icon.trim().length > 0) {
|
|
58
|
+
// textContent, deliberately: a control-file string must never be markup.
|
|
59
|
+
el.textContent = icon.trim()
|
|
60
|
+
el.classList.add('dsh-tw-entry-emoji')
|
|
61
|
+
return
|
|
62
|
+
}
|
|
63
|
+
el.innerHTML = DEFAULT_ICON_SVG
|
|
64
|
+
el.classList.remove('dsh-tw-entry-emoji')
|
|
65
|
+
}
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
*/
|
|
22
22
|
import * as React from 'react'
|
|
23
23
|
import { fetchStatus } from './status-cache.ts'
|
|
24
|
+
import { alignDockEntry } from './dock-align.ts'
|
|
24
25
|
import { SESSION_WIKI_ENDPOINT } from './endpoints.ts'
|
|
25
26
|
|
|
26
27
|
interface WikiOption { id: string; label: string; running: boolean }
|
|
@@ -39,6 +40,16 @@ export function createWikiScopeDock(): (props: DockProps) => React.ReactElement
|
|
|
39
40
|
const [scope, setScope] = React.useState('')
|
|
40
41
|
const [note, setNote] = React.useState<string | undefined>(undefined)
|
|
41
42
|
const [busy, setBusy] = React.useState(false)
|
|
43
|
+
const wrapRef = React.useRef<HTMLDivElement | null>(null)
|
|
44
|
+
|
|
45
|
+
// 与 composer 输入框右缘对齐(v0.28.2):dock 槽位渲染成输入框的**兄弟节点**,
|
|
46
|
+
// 不做这一步这一行就悬在整列最右端(作者报障:"没有和输入框对齐")。测量逻辑
|
|
47
|
+
// 与快速笔记共用一份 —— client/dock-align.ts。
|
|
48
|
+
React.useLayoutEffect(() => {
|
|
49
|
+
const wrap = wrapRef.current
|
|
50
|
+
if (wrap === null) return
|
|
51
|
+
return alignDockEntry(wrap)
|
|
52
|
+
}, [])
|
|
42
53
|
|
|
43
54
|
React.useEffect(() => {
|
|
44
55
|
if (sessionId === undefined) return
|
|
@@ -91,7 +102,7 @@ export function createWikiScopeDock(): (props: DockProps) => React.ReactElement
|
|
|
91
102
|
|
|
92
103
|
return React.createElement(
|
|
93
104
|
'div',
|
|
94
|
-
{ className: 'dsh-tw-scope-dock' },
|
|
105
|
+
{ ref: wrapRef, className: 'dsh-tw-scope-dock' },
|
|
95
106
|
React.createElement('span', { className: 'dsh-tw-scope-label' }, '知识库'),
|
|
96
107
|
React.createElement(
|
|
97
108
|
'select',
|
package/src/host/seed-notes.ts
CHANGED
|
@@ -64,6 +64,28 @@ const DOC_NOTE_TAIL = `* **TW 编辑器面板**:侧边栏「TiddlyWiki」按
|
|
|
64
64
|
* **设置页**:DSH 设置 → 「TiddlyWiki 知识库」管理插件/主题/语言与运行配置(含「知识库」按钮相关显示开关)。
|
|
65
65
|
* **注入给 Agent 的提示词可配置**(设置页「系统提示词」区块):默认**精简版**只约定同步纪律/标签/链接格式(工具参数由工具 schema 提供,不重复);可切完整版(附参数索引)、追加自定义规范(\`extra\`)、整段替换(\`override\`)或整体停用。保存即生效,无需重启 dsh web;点「查看当前注入文本」可预览下一步实际注入的全文。
|
|
66
66
|
|
|
67
|
+
!! 多知识库(v0.28.0 起)
|
|
68
|
+
|
|
69
|
+
同一个 dsh web 里可以**同时跑多个知识库**,每个库独立配置、独立 git,各自一个 TW 子进程。
|
|
70
|
+
|
|
71
|
+
* **现在有几个库?**右下角「知识库」按钮的菜单会列出全部(● 是当前打开的那个);输入框上方也会有「知识库」下拉,用来选**本会话**让 Agent 用哪个库(单选,选择会被记住)。
|
|
72
|
+
* **加一个库**:DSH 设置 → 「TiddlyWiki 知识库」→ **知识库列表** → 填根目录 + 文件夹名 →「添加知识库」。目录不存在会自动初始化一个全新库;已存在的目录直接接管。
|
|
73
|
+
* **让某个库对 Agent 隐身**:知识库列表里点该行「对 Agent 隐身」。隐身 = Agent 永远碰不到它(不进检索、不进工具、不进会话选择器),**你自己照常用**——适合只读语料、书籍扫描、导入归档。
|
|
74
|
+
* **每个库一套设置**:笔记标签、语言、主题、提示词都在该库自己的配置里。设置页顶部会显示「**配置作用域**:…」,点某一行「配置」即可切换在编辑哪个库。
|
|
75
|
+
* **git 按仓库生效**:多个库可以共用一个仓库(例如两个库是同一仓库下的两个子目录),也可以各自独立仓库。共用仓库时只有一个自动提交者,pull 后只重启内容真的变了的库。
|
|
76
|
+
* **想拆分现有的大库**:见设置页文档或仓库里的 \`docs/wiki-split.md\`——那里有一段可以直接复制给 Agent 的话,它会先只读体检、再问你几个问题、出方案等你确认后才动手。
|
|
77
|
+
|
|
78
|
+
!! 从旧版本升级上来(重要)
|
|
79
|
+
|
|
80
|
+
**升级后不会自动变成多库**,仍然是单库模式(\`mode: single\`),行为与升级前完全一致。
|
|
81
|
+
|
|
82
|
+
* **想继续用单库**:什么都不用做。设置页里「知识库位置(可切换)」只在单库模式出现,用法和以前一样。
|
|
83
|
+
* **想开始用多库**:去设置页「知识库列表」添加第二个库,再把「运行模式」切到**多库**。此后清单里的库都会可用(标了「开局自启」的随 DSH 启动,其余在你打开或选中它时启动)。
|
|
84
|
+
* **控制文件在哪**:\`$DSH_HOME/dsh-tiddlywiki/wikis.json\`(在每本 wiki **之外**,所以它不会随某个库被搬走)。模式、默认库、库清单都在里面;也可以在设置页里改,效果一样。
|
|
85
|
+
* **旧的「知识库位置」指针文件**(\`location.json\`)在单库模式下仍然是权威的——多库模式下它不再参与,改由清单决定。
|
|
86
|
+
* **每个库要有自己的 \`tiddlywiki.info\`**:把一个大库拆成多个目录时,每个目录都要有一个(缺了 TW 认不出它是库)。从旧库整体搬过来的目录通常已经有了。
|
|
87
|
+
* **拆分大库的注意**:书籍/语料这类只读内容建议**独立仓库 + 对 Agent 隐身**;工作与个人笔记可以共用一个仓库、按子目录划分。搬运用 \`git mv\` 保留历史,别用\"复制粘贴再删\"。
|
|
88
|
+
|
|
67
89
|
!! 知识库纪律(四条)
|
|
68
90
|
|
|
69
91
|
1. 开工先 \`tiddlywiki_git_sync action=pull\`(rebase + autostash,真冲突会自动 abort 并报文件)。
|
|
@@ -108,6 +108,50 @@ export interface WikiEntry {
|
|
|
108
108
|
agentVisible: boolean
|
|
109
109
|
/** Start its TW child process at boot (default false; the default wiki is true). */
|
|
110
110
|
autostart: boolean
|
|
111
|
+
/**
|
|
112
|
+
* Sidebar/entry icon (v0.28.4). Either a NAME from {@link WIKI_ICON_NAMES}
|
|
113
|
+
* (a curated set of built-in SVGs) or a short EMOJI literal.
|
|
114
|
+
*
|
|
115
|
+
* Why free-form-but-tiny instead of a fixed enum: a curated set covers the
|
|
116
|
+
* common cases without shipping a picker that needs its own assets, while an
|
|
117
|
+
* emoji lets someone tell four wikis apart at a glance (📚 / 💼 / 🏠) with
|
|
118
|
+
* nothing to download or maintain.
|
|
119
|
+
*
|
|
120
|
+
* It lives in the control file next to `label` because it is part of a wiki's
|
|
121
|
+
* IDENTITY — the same reason `label` is here and not in a tiddler. Absent =
|
|
122
|
+
* the built-in default icon (so upgrades see no change).
|
|
123
|
+
*/
|
|
124
|
+
icon?: string
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Built-in icon names (the picker's curated set). Deliberately small: each is a
|
|
129
|
+
* hand-written 16×16 stroke SVG inheriting `currentColor`, so it renders in both
|
|
130
|
+
* DSH themes with no asset pipeline.
|
|
131
|
+
*/
|
|
132
|
+
export const WIKI_ICON_NAMES = ['book', 'briefcase', 'home', 'notebook', 'flask', 'globe', 'star', 'archive'] as const
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Normalise an icon value: a known name, or a SHORT emoji-ish string, else
|
|
136
|
+
* undefined (= use the default).
|
|
137
|
+
*
|
|
138
|
+
* The length cap matters: the value can end up as text content in the sidebar,
|
|
139
|
+
* and an unbounded string would let a control file wreck the layout. Emoji are
|
|
140
|
+
* 1–2 code points; 8 UTF-16 units fits a ZWJ cluster without being a place to
|
|
141
|
+
* hide a paragraph.
|
|
142
|
+
*/
|
|
143
|
+
export function normalizeWikiIcon(value: unknown): string | undefined {
|
|
144
|
+
if (typeof value !== 'string') return undefined
|
|
145
|
+
const trimmed = value.trim()
|
|
146
|
+
if (trimmed.length === 0) return undefined
|
|
147
|
+
if ((WIKI_ICON_NAMES as readonly string[]).includes(trimmed)) return trimmed
|
|
148
|
+
if (trimmed.length > 8) return undefined
|
|
149
|
+
// A control character would be invisible or break the row.
|
|
150
|
+
for (const ch of trimmed) {
|
|
151
|
+
const code = ch.codePointAt(0) ?? 0
|
|
152
|
+
if (code < 0x20 || code === 0x7f) return undefined
|
|
153
|
+
}
|
|
154
|
+
return trimmed
|
|
111
155
|
}
|
|
112
156
|
|
|
113
157
|
/** The whole registry: the mode, a non-empty list, and the default-wiki pointer. */
|
|
@@ -268,6 +312,9 @@ export function normalizeEntry(input: unknown): { entry?: WikiEntry; error?: str
|
|
|
268
312
|
name: normalized.location.name,
|
|
269
313
|
agentVisible: input.agentVisible !== false,
|
|
270
314
|
autostart: input.autostart === true,
|
|
315
|
+
// 图标可选:只在给了合法值时才带上,这样"没设过"的库在 JSON 里保持干净
|
|
316
|
+
// (也保证既有清单重新序列化后逐字不变)。
|
|
317
|
+
...(normalizeWikiIcon(input.icon) !== undefined ? { icon: normalizeWikiIcon(input.icon) } : {}),
|
|
271
318
|
},
|
|
272
319
|
}
|
|
273
320
|
}
|
package/src/index.ts
CHANGED
|
@@ -187,12 +187,14 @@ export {
|
|
|
187
187
|
findEntry,
|
|
188
188
|
findPathConflicts,
|
|
189
189
|
normalizeEntry,
|
|
190
|
+
normalizeWikiIcon,
|
|
190
191
|
normalizeWikiId,
|
|
191
192
|
readRegistry,
|
|
192
193
|
removeWiki,
|
|
193
194
|
singleEntryRegistry,
|
|
194
195
|
upsertWiki,
|
|
195
196
|
validateRegistry,
|
|
197
|
+
WIKI_ICON_NAMES,
|
|
196
198
|
writeRegistry,
|
|
197
199
|
type RegistryReadResult,
|
|
198
200
|
type RegistryValidation,
|
|
@@ -1133,6 +1135,8 @@ export function apply(ctx: HostCtx, rawConfig: TiddlywikiConfig = {}): void {
|
|
|
1133
1135
|
autostart: entry.autostart,
|
|
1134
1136
|
running: runtime !== undefined,
|
|
1135
1137
|
path: entryPath(entry),
|
|
1138
|
+
// 每库图标(v0.28.4):侧边栏入口与设置页选择器共用这个来源。
|
|
1139
|
+
icon: entry.icon,
|
|
1136
1140
|
}
|
|
1137
1141
|
}),
|
|
1138
1142
|
}
|