dsh-tiddlywiki 0.28.6 → 0.28.7
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 +1 -0
- package/docs/CHANGELOG.md +1 -0
- package/docs/usage.md +2 -2
- package/lib/client.js +15 -14
- package/package.json +1 -1
- package/src/client/index.ts +6 -13
- package/src/client/quick-note-dock.ts +16 -3
- package/src/client/settings-page.ts +32 -5
- package/src/client/styles.ts +8 -7
- package/src/client/wiki-scope-dock.ts +13 -11
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-tiddlywiki",
|
|
3
3
|
"description": "把 TiddlyWiki 5 变成你和 Agent 共用的本地文档中心(纯文本 + git):你在 DSH 界面里阅读、写作、记日记,Agent 用 15 个 tiddlywiki_* 工具读写同一批笔记;每条笔记都是你能读、能改、能审计、能随时带走的文件——不是自动记忆插件,不做后台抓取。可选一键发布到微信公众号草稿箱。 Turn TiddlyWiki 5 into a document center you and your agent share, in plain text under git — a knowledge base you can read, edit, audit and take with you, not an auto-memory plugin. Mounts via the official dsh plugin system; no DSH source changes.",
|
|
4
|
-
"version": "0.28.
|
|
4
|
+
"version": "0.28.7",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"dsh",
|
package/src/client/index.ts
CHANGED
|
@@ -107,18 +107,11 @@ export function apply(ctx: ClientContextFace): void {
|
|
|
107
107
|
// queue/git-graph 等插件内容都渲染在这里、按纵向 flex 排列,天然不重叠。
|
|
108
108
|
// 由 ui.showQuickNoteDock 配置控制(默认开)。
|
|
109
109
|
//
|
|
110
|
-
//
|
|
111
|
-
//
|
|
112
|
-
//
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
ctx.slots?.register(
|
|
116
|
-
{ name: 'conversation.input.dock', id: 'wiki-scope', order: 4, label: '知识库作用域' },
|
|
117
|
-
createWikiScopeDock(),
|
|
118
|
-
),
|
|
119
|
-
)
|
|
120
|
-
if (removeDock !== undefined) disposers.push(removeDock)
|
|
121
|
-
})
|
|
110
|
+
// 「会话级知识库选择器」(v0.28.0,需求:会话栏上面动态选 wiki)**不再单独占一个
|
|
111
|
+
// 槽位条目**(v0.28.7):dock 是纵向 flex 列,一个条目 = 一整行,所以两个条目
|
|
112
|
+
// 各自量宽度、永远对不齐(作者连报两次)。现在它作为 `scope` 渲染在快速笔记按钮
|
|
113
|
+
// 同一行的前面,只有一个条目、一次测量。组件自己在"可见库 ≤ 1"时返回 null,
|
|
114
|
+
// 所以单库安装这一块 DOM 与以前逐字相同。
|
|
122
115
|
if (ctx.slots !== undefined) {
|
|
123
116
|
void fetchUiConfig().then((cfg) => {
|
|
124
117
|
if (clientDisposed) return
|
|
@@ -128,7 +121,7 @@ export function apply(ctx: ClientContextFace): void {
|
|
|
128
121
|
const removeDock = ctx.slots?.inject('conversation.input.dock', () =>
|
|
129
122
|
ctx.slots?.register(
|
|
130
123
|
{ name: 'conversation.input.dock', id: 'quick-note', order: 8, label: '快速笔记' },
|
|
131
|
-
createQuickNoteDock(widget),
|
|
124
|
+
createQuickNoteDock(widget, createWikiScopeDock()),
|
|
132
125
|
),
|
|
133
126
|
)
|
|
134
127
|
if (removeDock !== undefined) disposers.push(removeDock)
|
|
@@ -28,10 +28,18 @@ import { isEditorPopupOpen, isEditorPopupBlank, closeEditorPopup } from './edito
|
|
|
28
28
|
* (Lexical contenteditable 所在、有边框圆角的可见「输入框」盒子)的右缘,
|
|
29
29
|
* 把横条 paddingRight 设为差值,让按钮右缘与输入框右缘对齐——而不是悬在整列
|
|
30
30
|
* 最右端。注意 dock 槽位会把各条目包在一个容器里、与输入栏是兄弟节点,所以
|
|
31
|
-
*
|
|
31
|
+
* 必须逐级往上爬祖先才能找到输入框。
|
|
32
|
+
*
|
|
33
|
+
* `scope`(v0.28.7)是同一行里渲染在按钮**前面**的元素(知识库选择器)。
|
|
34
|
+
* 为什么合并成一个条目:dock 槽位是纵向 flex 列,**每个条目 = 一整行**,所以
|
|
35
|
+
* "选择器一行、快速笔记一行"永远不可能对齐成一条 —— 各自量各自的宽度,视觉上
|
|
36
|
+
* 就是两行错位(作者连报两次)。合并后只有一个条目、一次测量,两者天然同基线。
|
|
32
37
|
*/
|
|
33
|
-
export function createQuickNoteDock(
|
|
34
|
-
|
|
38
|
+
export function createQuickNoteDock(
|
|
39
|
+
note: NoteWidgetHandle,
|
|
40
|
+
scope?: (props: { sessionId?: string }) => React.ReactElement | null,
|
|
41
|
+
): (props: { sessionId?: string }) => React.ReactElement {
|
|
42
|
+
return function QuickNoteDock(props: { sessionId?: string }) {
|
|
35
43
|
const [open, setOpen] = React.useState(false)
|
|
36
44
|
// 点击行为由 ui.quickNoteMode 决定:native=直达 TW 原生编辑页(弹窗);
|
|
37
45
|
// card=Markdown 卡片。null = 配置尚未加载(点击时按配置实时分发)。
|
|
@@ -71,6 +79,11 @@ export function createQuickNoteDock(note: NoteWidgetHandle): () => React.ReactEl
|
|
|
71
79
|
return React.createElement(
|
|
72
80
|
'div',
|
|
73
81
|
{ ref: wrapRef, className: 'dsh-tw-dock-note' },
|
|
82
|
+
// 知识库选择器排在按钮**前面**(同一行、同一基线)。它的 sessionId 必须
|
|
83
|
+
// 从本条目透传下去 —— 槽位的 scope=session,props 是发给**条目组件**的,
|
|
84
|
+
// 子元素不会自动拿到(v0.28.7:漏了这一步选择器会永远读不到会话 id)。
|
|
85
|
+
// scope 为 undefined 时(单库安装)什么都不渲染,DOM 与以前逐字相同。
|
|
86
|
+
scope?.({ ...(props.sessionId !== undefined ? { sessionId: props.sessionId } : {}) }) ?? null,
|
|
74
87
|
React.createElement(
|
|
75
88
|
'button',
|
|
76
89
|
{
|
|
@@ -186,18 +186,37 @@ function mountSettingsPage(container: HTMLElement): () => void {
|
|
|
186
186
|
const catalogPending: CatalogPending = {}
|
|
187
187
|
/** 首屏是否已成功渲染过内容:决定占位提示与失败时能不能清 body。 */
|
|
188
188
|
let rendered = false
|
|
189
|
+
/**
|
|
190
|
+
* 当前是单库还是多库(v0.28.7)。
|
|
191
|
+
*
|
|
192
|
+
* 为什么要在比 `/status` 更早的地方拿到它:有些配置项**只在单库模式下有意义**
|
|
193
|
+
* (「侧边栏 TW 入口显示名称」是最典型的一个——多库时侧边栏是每个库各占一行、
|
|
194
|
+
* 各用自己 `wikis.json` 里的 label,这个字段改不动任何东西)。`renderConfigSection`
|
|
195
|
+
* 是同步的、按顺序 append 字段,没法在中间 await,所以这里先把模式探好,
|
|
196
|
+
* 再交给它决定要不要渲染那个字段。
|
|
197
|
+
*
|
|
198
|
+
* `undefined` = 还没探测 / 探测失败 —— 按**单库**处理(保守:宁可多显示一个
|
|
199
|
+
* "确实存在且能用"的字段,也不要因为一次请求失败把它藏了,否则单库用户会
|
|
200
|
+
* 莫名其妙找不到设置项)。多库时才显式 'multi'。
|
|
201
|
+
*/
|
|
202
|
+
let rosterMode: string | undefined
|
|
189
203
|
|
|
190
204
|
const refresh = async (): Promise<void> => {
|
|
191
205
|
// 首屏先给占位(v0.24.x):以前 await 期间什么都不画,整块白屏看起来像插件坏了。
|
|
192
206
|
if (!rendered) body.replaceChildren(make('div', 'dsh-tw-settings-muted', '加载中…'))
|
|
193
207
|
try {
|
|
208
|
+
// 与 /status 并行探模式:失败/超时不阻塞配置页渲染(回落成单库的保守视图)。
|
|
209
|
+
const modePromise = fetchJson<WikisView>(WIKI_LIST_ENDPOINT)
|
|
210
|
+
.then((v) => { rosterMode = typeof v?.mode === 'string' ? v.mode : undefined })
|
|
211
|
+
.catch(() => { rosterMode = undefined })
|
|
194
212
|
const state = await fetchJson<AdminState>(withWiki(STATE_ENDPOINT))
|
|
213
|
+
await modePromise
|
|
195
214
|
if (disposed) return
|
|
196
215
|
rendered = true
|
|
197
216
|
loadError.hidden = true
|
|
198
217
|
loadError.replaceChildren()
|
|
199
218
|
renderStatus(statusRow, state, refresh)
|
|
200
|
-
renderMain(body, state, refresh, () => disposed, configState, catalogPending)
|
|
219
|
+
renderMain(body, state, refresh, () => disposed, configState, catalogPending, rosterMode)
|
|
201
220
|
} catch (err) {
|
|
202
221
|
if (disposed) return
|
|
203
222
|
// 不再 body.replaceChildren() / statusRow.replaceChildren():那会把已经渲染出来的
|
|
@@ -288,7 +307,7 @@ function renderStatus(row: HTMLElement, state: AdminState, refresh: () => Promis
|
|
|
288
307
|
}
|
|
289
308
|
|
|
290
309
|
/** Config section: fields bound to effective config, changed-only save. */
|
|
291
|
-
function renderConfigSection(body: HTMLElement, config: Record<string, unknown>, refresh: () => Promise<void>, configState: ConfigRenderState): void {
|
|
310
|
+
function renderConfigSection(body: HTMLElement, config: Record<string, unknown>, refresh: () => Promise<void>, configState: ConfigRenderState, rosterMode?: string): void {
|
|
292
311
|
const section = make('section', 'dsh-tw-settings-section')
|
|
293
312
|
section.append(make('h3', 'dsh-tw-settings-h', '常规配置'))
|
|
294
313
|
const note = (config.note ?? {}) as Record<string, unknown>
|
|
@@ -448,7 +467,15 @@ function renderConfigSection(body: HTMLElement, config: Record<string, unknown>,
|
|
|
448
467
|
{ value: 'native', label: '原生编辑器:直接弹出 TW 原生编辑页(新建/恢复草稿)' },
|
|
449
468
|
{ value: 'card', label: 'Markdown 卡片:弹出现有快速笔记卡片(CodeMirror 编辑器)' },
|
|
450
469
|
])
|
|
451
|
-
|
|
470
|
+
// 「侧边栏 TW 入口显示名称」只在**单库**模式有意义(v0.28.7)。多库时侧边栏是每个库
|
|
471
|
+
// 各占一行、各用自己 `wikis.json` 里的 label,这个字段改不动任何东西——留着只会让人
|
|
472
|
+
// 「改了没生效」。所以多库时**不渲染这个字段**(连字段带保存都不参与),只留一行说明
|
|
473
|
+
// 该去哪儿改。判定用的模式来自 mountSettingsPage 的探测(读不到按单库,见那里的注释)。
|
|
474
|
+
if (rosterMode === 'multi') {
|
|
475
|
+
section.append(make('div', 'dsh-tw-settings-muted', '侧边栏入口:多库模式下每个知识库各占一行、各用自己的「显示名」(在「知识库列表」里改),不再是统一的一个名称,所以这里没有对应配置项。'))
|
|
476
|
+
} else {
|
|
477
|
+
textField('ui.sidebarLabel', '侧边栏 TW 入口显示名称', typeof ui.sidebarLabel === 'string' && ui.sidebarLabel.trim().length > 0 ? ui.sidebarLabel.trim() : 'TiddlyWiki')
|
|
478
|
+
}
|
|
452
479
|
checkField('ui.showPanelStatus', '显示「知识库」按钮里的 TW 面板/重载入口与状态行', ui.showPanelStatus !== false)
|
|
453
480
|
checkField('ui.showSyncButton', '显示「知识库」按钮里的「同步」入口与 git 状态点', ui.showSyncButton !== false)
|
|
454
481
|
checkField('ui.followDshTheme', '嵌入式 TW 跟随 DSH 深浅主题(暗色时自动切深色 palette,不写回 wiki)', ui.followDshTheme !== false)
|
|
@@ -1619,7 +1646,7 @@ interface CatalogPending {
|
|
|
1619
1646
|
themeActive?: string
|
|
1620
1647
|
}
|
|
1621
1648
|
|
|
1622
|
-
function renderMain(body: HTMLElement, state: AdminState, refresh: () => Promise<void>, isDisposed: () => boolean, configState: ConfigRenderState, catalogPending: CatalogPending): void {
|
|
1649
|
+
function renderMain(body: HTMLElement, state: AdminState, refresh: () => Promise<void>, isDisposed: () => boolean, configState: ConfigRenderState, catalogPending: CatalogPending, rosterMode?: string): void {
|
|
1623
1650
|
body.replaceChildren()
|
|
1624
1651
|
// Loud, above everything else: an unparseable config tiddler means the config
|
|
1625
1652
|
// block below shows DEFAULTS that are not actually in effect, and saving is
|
|
@@ -1644,7 +1671,7 @@ function renderMain(body: HTMLElement, state: AdminState, refresh: () => Promise
|
|
|
1644
1671
|
}
|
|
1645
1672
|
if (configState.host === undefined || (serverChanged && !dirty)) {
|
|
1646
1673
|
const host = make('div', 'dsh-tw-settings-confighost')
|
|
1647
|
-
renderConfigSection(host, state.config ?? {}, refresh, configState)
|
|
1674
|
+
renderConfigSection(host, state.config ?? {}, refresh, configState, rosterMode)
|
|
1648
1675
|
configState.host = host
|
|
1649
1676
|
configState.signature = signature
|
|
1650
1677
|
}
|
package/src/client/styles.ts
CHANGED
|
@@ -514,14 +514,15 @@ html[data-dsh-tw-active] .dshDesktopConversationSurface > :not([data-dsh-tw-view
|
|
|
514
514
|
/* ── 会话级知识库选择器(v0.28.0,同一个 conversation.input.dock 槽位)──
|
|
515
515
|
与快速笔记按钮同区、纵向 flex 排列;只在"多于一个可见库"时渲染(组件自己返回
|
|
516
516
|
null),所以单库安装这一整块都不存在。 */
|
|
517
|
-
/* ── 会话级知识库选择器(v0.28.0
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
517
|
+
/* ── 会话级知识库选择器(v0.28.0;v0.28.7 起并入快速笔记那一行)──
|
|
518
|
+
它现在渲染在 .dsh-tw-dock-note **里面**、按钮的前面,所以:
|
|
519
|
+
- 不再 width:100%(那是"独占一整行"的写法,会把按钮挤下去);
|
|
520
|
+
- 不再自己做 padding/justify —— 右对齐与"与 composer 输入框对齐"由父级
|
|
521
|
+
统一负责(见 client/dock-align.ts),一行只测量一次、天然同一基线。
|
|
522
|
+
只在"多于一个可见库"时渲染(组件自己返回 null),单库安装这一块 DOM 不存在。 */
|
|
522
523
|
.dsh-tw-scope-dock {
|
|
523
|
-
display: flex; align-items: center;
|
|
524
|
-
|
|
524
|
+
display: inline-flex; align-items: center; gap: 6px;
|
|
525
|
+
font-size: 12px;
|
|
525
526
|
}
|
|
526
527
|
.dsh-tw-scope-label { opacity: .7; }
|
|
527
528
|
.dsh-tw-scope-select {
|
|
@@ -21,7 +21,6 @@
|
|
|
21
21
|
*/
|
|
22
22
|
import * as React from 'react'
|
|
23
23
|
import { fetchStatus } from './status-cache.ts'
|
|
24
|
-
import { alignDockEntry } from './dock-align.ts'
|
|
25
24
|
import { SESSION_WIKI_ENDPOINT } from './endpoints.ts'
|
|
26
25
|
|
|
27
26
|
interface WikiOption { id: string; label: string; running: boolean }
|
|
@@ -30,8 +29,14 @@ interface WikiOption { id: string; label: string; running: boolean }
|
|
|
30
29
|
interface DockProps { sessionId?: string }
|
|
31
30
|
|
|
32
31
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
32
|
+
* 会话级知识库选择器(v0.28.0)。
|
|
33
|
+
*
|
|
34
|
+
* 它**不是**一个独立的 dock 条目,而是渲染在快速笔记那一行**里面**的元素
|
|
35
|
+
* (v0.28.7):dock 是纵向 flex 列,一个条目 = 一整行,两个条目各自量宽度就永远
|
|
36
|
+
* 对不齐。所以这里返回的是"一行里的一个内联块",宽度由内容决定,不占满整行。
|
|
37
|
+
*
|
|
38
|
+
* 返回 `null` 表示"这一行不需要它"——单库安装(可见库 ≤1)时正是如此,DOM 与
|
|
39
|
+
* 加这个功能之前逐字相同。
|
|
35
40
|
*/
|
|
36
41
|
export function createWikiScopeDock(): (props: DockProps) => React.ReactElement | null {
|
|
37
42
|
return function WikiScopeDock(props: DockProps) {
|
|
@@ -42,14 +47,11 @@ export function createWikiScopeDock(): (props: DockProps) => React.ReactElement
|
|
|
42
47
|
const [busy, setBusy] = React.useState(false)
|
|
43
48
|
const wrapRef = React.useRef<HTMLDivElement | null>(null)
|
|
44
49
|
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
//
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
if (wrap === null) return
|
|
51
|
-
return alignDockEntry(wrap)
|
|
52
|
-
}, [])
|
|
50
|
+
// 注意(v0.28.7):这里**不再**自己调 alignDockEntry。选择器已经从独立 dock 条目
|
|
51
|
+
// 改成渲染在快速笔记那一行**里面**的元素,而那一行整行负责与输入框右缘对齐
|
|
52
|
+
// (quick-note-dock.ts 的 wrapRef)。如果这里再量一次、再补一次右内边距,就是
|
|
53
|
+
// 同一个右缘被补两遍 —— 选择器会被推进去一大截,正是"没和对话框对齐"的复现。
|
|
54
|
+
// 保留 wrapRef 是为了给根元素一个稳定引用做测量/调试锚点,不参与对齐计算。
|
|
53
55
|
|
|
54
56
|
React.useEffect(() => {
|
|
55
57
|
if (sessionId === undefined) return
|