dsh-tiddlywiki 0.1.2 → 0.2.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 +7 -4
- package/lib/client.bundle.js +208 -18
- package/lib/client.js +208 -18
- package/lib/index.js +2263 -2152
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/client/note-widget.ts +373 -210
- package/src/client/settings-page.ts +3 -0
- package/src/client/styles.ts +43 -0
- package/src/host/config.ts +6 -0
- package/src/host/routes.ts +49 -10
- package/src/host/seed-notes.ts +74 -14
- package/src/index.ts +22 -7
package/src/client/styles.ts
CHANGED
|
@@ -24,6 +24,21 @@ const CSS_TEXT = `
|
|
|
24
24
|
.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; }
|
|
25
25
|
.dsh-tw-entry svg { flex: none; }
|
|
26
26
|
.dsh-tw-entry .dsh-tw-entry-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
27
|
+
/* Collapsed rail: the shell narrows the sidebar to a 56px icon rail (dual
|
|
28
|
+
signals — the frame's data-sidebar-collapsed + the sidebar root's hashed
|
|
29
|
+
*_collapsed class, same doctrine as dsh-taskboard 0.4.3). Mirror the
|
|
30
|
+
native 36×36 icon-button geometry: center the icon, hide the label, and
|
|
31
|
+
scale it up to match the shell's 18px rail icons. */
|
|
32
|
+
[data-sidebar-collapsed] [data-dsh-tw-entry],
|
|
33
|
+
[class*="_collapsed"] [data-dsh-tw-entry] {
|
|
34
|
+
width: 36px; height: 36px; min-width: 36px;
|
|
35
|
+
margin: 0 0 12px; padding: 0;
|
|
36
|
+
justify-content: center; gap: 0; text-align: center;
|
|
37
|
+
}
|
|
38
|
+
[data-sidebar-collapsed] [data-dsh-tw-entry] .dsh-tw-entry-label,
|
|
39
|
+
[class*="_collapsed"] [data-dsh-tw-entry] .dsh-tw-entry-label { display: none; }
|
|
40
|
+
[data-sidebar-collapsed] [data-dsh-tw-entry] svg,
|
|
41
|
+
[class*="_collapsed"] [data-dsh-tw-entry] svg { width: 18px; height: 18px; }
|
|
27
42
|
|
|
28
43
|
/* ── center-column panel (fixed overlay, JS-pinned to the column rect) ── */
|
|
29
44
|
.dsh-tw-view {
|
|
@@ -139,6 +154,34 @@ html[data-dsh-tw-active] .dshDesktopConversationSurface > :not([data-dsh-tw-view
|
|
|
139
154
|
}
|
|
140
155
|
.dsh-tw-note-edit:hover:not(:disabled) { filter: brightness(.96); }
|
|
141
156
|
.dsh-tw-note-edit:disabled { opacity: .55; cursor: default; }
|
|
157
|
+
/* ── Multi-tag chip editor (quick-note tags) ─────────────────────────────── */
|
|
158
|
+
.dsh-tw-note-tags { position: relative; flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 4px; }
|
|
159
|
+
.dsh-tw-note-chips { display: flex; flex-wrap: wrap; gap: 4px; }
|
|
160
|
+
.dsh-tw-note-tagchip {
|
|
161
|
+
display: inline-flex; align-items: center; gap: 4px;
|
|
162
|
+
padding: 2px 6px 2px 8px; border-radius: 999px; font-size: 11px; line-height: 1.4;
|
|
163
|
+
background: color-mix(in srgb, var(--dsw-alias-brand-primary, #3e63dd) 10%, var(--dsw-alias-bg-layer-1, #fff));
|
|
164
|
+
border: 1px solid color-mix(in srgb, var(--dsw-alias-brand-primary, #3e63dd) 30%, transparent);
|
|
165
|
+
color: var(--dsw-alias-label-primary, #222);
|
|
166
|
+
}
|
|
167
|
+
.dsh-tw-note-tagchip-x {
|
|
168
|
+
cursor: pointer; font-size: 12px; line-height: 1; padding: 0 2px; border-radius: 50%;
|
|
169
|
+
color: var(--dsw-alias-label-secondary, #888);
|
|
170
|
+
}
|
|
171
|
+
.dsh-tw-note-tagchip-x:hover { color: var(--dsw-alias-state-error-primary, #d13b3b); }
|
|
172
|
+
.dsh-tw-note-taginput { width: 100%; box-sizing: border-box; }
|
|
173
|
+
.dsh-tw-note-tagsuggest {
|
|
174
|
+
position: absolute; left: 0; right: 0; top: calc(100% + 2px); z-index: 60;
|
|
175
|
+
max-height: 168px; overflow-y: auto; border-radius: 8px; padding: 4px;
|
|
176
|
+
background: var(--dsw-alias-bg-layer-2, #fff); color: var(--dsw-alias-label-primary, #222);
|
|
177
|
+
border: 1px solid var(--dsw-alias-border-l2, rgba(0,0,0,.18));
|
|
178
|
+
box-shadow: var(--dsw-shadow-lv2, 0 4px 16px rgba(0,0,0,.16));
|
|
179
|
+
font-size: 12px;
|
|
180
|
+
}
|
|
181
|
+
.dsh-tw-note-tagsuggest-item {
|
|
182
|
+
padding: 6px 9px; border-radius: 6px; cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
183
|
+
}
|
|
184
|
+
.dsh-tw-note-tagsuggest-item:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,.12)); }
|
|
142
185
|
/* ── Native-editor popup iframe (quick-note "在 TW 中编辑") ──────────────── */
|
|
143
186
|
.dsh-tw-editor-popup {
|
|
144
187
|
position: fixed; left: 0; right: 0; top: 0; bottom: 0; margin: auto;
|
package/src/host/config.ts
CHANGED
|
@@ -20,6 +20,12 @@ export const CONFIG_TIDDLER = '$:/plugins/dsh-tiddlywiki/config'
|
|
|
20
20
|
export interface PluginConfigShape {
|
|
21
21
|
note?: { tag?: string }
|
|
22
22
|
git?: { autoCommit?: boolean; debounceMs?: number; remote?: string; branch?: string }
|
|
23
|
+
ui?: {
|
|
24
|
+
/** 是否在界面右下角显示「快速笔记」悬浮按钮(默认 true)。 */
|
|
25
|
+
showQuickNote?: boolean
|
|
26
|
+
/** 是否启用「侧边移到左侧.css」默认 CSS;被删除后随启动补丁还原(默认 true)。 */
|
|
27
|
+
sidebarLeftCss?: boolean
|
|
28
|
+
}
|
|
23
29
|
uiLanguage?: string
|
|
24
30
|
[key: string]: unknown
|
|
25
31
|
}
|
package/src/host/routes.ts
CHANGED
|
@@ -39,6 +39,7 @@ export interface RouteDeps {
|
|
|
39
39
|
git: GitFace
|
|
40
40
|
autoCommit: () => void
|
|
41
41
|
noteDefaults: () => { tag: string }
|
|
42
|
+
uiDefaults: () => { showQuickNote: boolean }
|
|
42
43
|
getWikiPath: () => string
|
|
43
44
|
}
|
|
44
45
|
|
|
@@ -86,10 +87,10 @@ export async function openInTwEditor(
|
|
|
86
87
|
client: TiddlyWebClient,
|
|
87
88
|
title: string,
|
|
88
89
|
text: string,
|
|
89
|
-
|
|
90
|
+
tags: string[],
|
|
90
91
|
): Promise<{ title: string; draftTitle: string }> {
|
|
91
92
|
if (text.trim().length > 0) {
|
|
92
|
-
await client.put({ title, text, tags
|
|
93
|
+
await client.put({ title, text, tags })
|
|
93
94
|
}
|
|
94
95
|
// Draft content: the provided text, else the existing tiddler's content.
|
|
95
96
|
let draftText = text
|
|
@@ -115,6 +116,22 @@ export async function openInTwEditor(
|
|
|
115
116
|
return { title, draftTitle }
|
|
116
117
|
}
|
|
117
118
|
|
|
119
|
+
/** Resolve note tags from the request body: `tags` array wins, then the
|
|
120
|
+
* legacy single `tag` string, then the configured default tag. */
|
|
121
|
+
function resolveTags(
|
|
122
|
+
body: { tag?: unknown; tags?: unknown },
|
|
123
|
+
defaultTag: string,
|
|
124
|
+
): string[] {
|
|
125
|
+
if (Array.isArray(body.tags)) {
|
|
126
|
+
const tags = body.tags.filter((t): t is string => typeof t === 'string' && t.trim().length > 0).map((t) => t.trim())
|
|
127
|
+
if (tags.length > 0) return tags
|
|
128
|
+
}
|
|
129
|
+
if (typeof body.tag === 'string' && body.tag.trim().length > 0) {
|
|
130
|
+
return body.tag.trim().split(/\s+/).filter(Boolean)
|
|
131
|
+
}
|
|
132
|
+
return [defaultTag]
|
|
133
|
+
}
|
|
134
|
+
|
|
118
135
|
export function registerRoutes(ctx: { webServer: WebServerFace }, deps: RouteDeps): () => void {
|
|
119
136
|
const handleStatus = async (_req: IncomingMessage, res: ServerResponse): Promise<void> => {
|
|
120
137
|
const view = deps.server.status()
|
|
@@ -124,12 +141,12 @@ export function registerRoutes(ctx: { webServer: WebServerFace }, deps: RouteDep
|
|
|
124
141
|
} catch {
|
|
125
142
|
gitSummary = null
|
|
126
143
|
}
|
|
127
|
-
json(res, { ok: true, ...view, git: gitSummary, note: { tag: deps.noteDefaults().tag } })
|
|
144
|
+
json(res, { ok: true, ...view, git: gitSummary, note: { tag: deps.noteDefaults().tag }, ui: deps.uiDefaults() })
|
|
128
145
|
}
|
|
129
146
|
|
|
130
147
|
const handleNote = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {
|
|
131
148
|
try {
|
|
132
|
-
const body = JSON.parse(await readBody(req)) as { title?: unknown; tag?: unknown; text?: unknown }
|
|
149
|
+
const body = JSON.parse(await readBody(req)) as { title?: unknown; tag?: unknown; tags?: unknown; text?: unknown }
|
|
133
150
|
const text = typeof body.text === 'string' && body.text.trim().length > 0 ? body.text.trim() : null
|
|
134
151
|
if (text === null) {
|
|
135
152
|
json(res, { ok: false, error: 'text is required' }, 400)
|
|
@@ -141,10 +158,10 @@ export function registerRoutes(ctx: { webServer: WebServerFace }, deps: RouteDep
|
|
|
141
158
|
return
|
|
142
159
|
}
|
|
143
160
|
const title = typeof body.title === 'string' && body.title.trim().length > 0 ? body.title.trim() : timestampTitle()
|
|
144
|
-
const
|
|
145
|
-
await client.put({ title, text, tags
|
|
161
|
+
const tags = resolveTags(body, deps.noteDefaults().tag)
|
|
162
|
+
await client.put({ title, text, tags })
|
|
146
163
|
deps.autoCommit()
|
|
147
|
-
json(res, { ok: true, title, tag, text })
|
|
164
|
+
json(res, { ok: true, title, tag: tags.join(' '), tags, text })
|
|
148
165
|
} catch (err) {
|
|
149
166
|
json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 500)
|
|
150
167
|
}
|
|
@@ -152,16 +169,16 @@ export function registerRoutes(ctx: { webServer: WebServerFace }, deps: RouteDep
|
|
|
152
169
|
|
|
153
170
|
const handleEdit = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {
|
|
154
171
|
try {
|
|
155
|
-
const body = JSON.parse(await readBody(req)) as { title?: unknown; tag?: unknown; text?: unknown }
|
|
172
|
+
const body = JSON.parse(await readBody(req)) as { title?: unknown; tag?: unknown; tags?: unknown; text?: unknown }
|
|
156
173
|
const client = deps.getClient()
|
|
157
174
|
if (client === undefined) {
|
|
158
175
|
json(res, { ok: false, error: 'wiki service is not running' }, 503)
|
|
159
176
|
return
|
|
160
177
|
}
|
|
161
178
|
const title = typeof body.title === 'string' && body.title.trim().length > 0 ? body.title.trim() : timestampTitle()
|
|
162
|
-
const
|
|
179
|
+
const tags = resolveTags(body, deps.noteDefaults().tag)
|
|
163
180
|
const text = typeof body.text === 'string' ? body.text : ''
|
|
164
|
-
const result = await openInTwEditor(client, title, text,
|
|
181
|
+
const result = await openInTwEditor(client, title, text, tags)
|
|
165
182
|
deps.autoCommit()
|
|
166
183
|
json(res, { ok: true, ...result, twUrl: deps.server.url })
|
|
167
184
|
} catch (err) {
|
|
@@ -169,6 +186,27 @@ export function registerRoutes(ctx: { webServer: WebServerFace }, deps: RouteDep
|
|
|
169
186
|
}
|
|
170
187
|
}
|
|
171
188
|
|
|
189
|
+
/** Distinct non-system tags for the quick-note tag autocomplete. */
|
|
190
|
+
const handleTags = async (_req: IncomingMessage, res: ServerResponse): Promise<void> => {
|
|
191
|
+
const client = deps.getClient()
|
|
192
|
+
if (client === undefined) {
|
|
193
|
+
json(res, { ok: false, error: 'wiki service is not running' }, 503)
|
|
194
|
+
return
|
|
195
|
+
}
|
|
196
|
+
try {
|
|
197
|
+
const items = await client.list(undefined, false)
|
|
198
|
+
const tags = new Set<string>()
|
|
199
|
+
for (const item of items) {
|
|
200
|
+
for (const tag of item.tags ?? []) {
|
|
201
|
+
if (tag.length > 0 && !tag.startsWith('$:/')) tags.add(tag)
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
json(res, { ok: true, tags: [...tags].sort((a, b) => a.localeCompare(b, 'zh')) })
|
|
205
|
+
} catch (err) {
|
|
206
|
+
json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 500)
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
172
210
|
const handleRestart = async (_req: IncomingMessage, res: ServerResponse): Promise<void> => {
|
|
173
211
|
try {
|
|
174
212
|
await deps.server.restart()
|
|
@@ -212,6 +250,7 @@ export function registerRoutes(ctx: { webServer: WebServerFace }, deps: RouteDep
|
|
|
212
250
|
ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/status`, handler: (req, res) => { void handleStatus(req, res) } }),
|
|
213
251
|
ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/note`, handler: (req, res) => { void handleNote(req, res) } }),
|
|
214
252
|
ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/edit`, handler: (req, res) => { void handleEdit(req, res) } }),
|
|
253
|
+
ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/tags`, handler: (req, res) => { void handleTags(req, res) } }),
|
|
215
254
|
ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/restart`, handler: (req, res) => { void handleRestart(req, res) } }),
|
|
216
255
|
ctx.webServer.register({ kind: 'prefix', path: `${ROUTE_PREFIX}/api`, handler: (req, res) => { void handleApiProxy(req, res) } }),
|
|
217
256
|
]
|
package/src/host/seed-notes.ts
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Built-in doc note for the plugin (design doc §14)
|
|
3
|
-
* "how to use dsh-tiddlywiki" note that is seeded into the wiki on first run.
|
|
2
|
+
* Built-in doc note + optional UI-patch seeds for the plugin (design doc §14).
|
|
4
3
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* Two seeds, deliberately different lifetimes:
|
|
5
|
+
*
|
|
6
|
+
* 1. DOC NOTE (`dsh-tiddlywiki 插件说明`) — ONE-SHOT: seeded only the first
|
|
7
|
+
* time this plugin runs on a wiki (guarded by a `seed-doc-note` marker
|
|
8
|
+
* tiddler). After that the note is the user's own content: deleting it and
|
|
9
|
+
* restarting dsh web does NOT recreate it.
|
|
10
|
+
* 2. SIDEBAR-LEFT CSS (`侧边移到左侧.css`) — PATCH-REPAIR: written whenever
|
|
11
|
+
* the wiki lacks it while the `ui.sidebarLeftCss` option is enabled
|
|
12
|
+
* (default on). The user may delete it; a restart with the option still on
|
|
13
|
+
* re-seeds it (the "补丁选项"). Turning the option off stops re-seeding.
|
|
8
14
|
*
|
|
9
15
|
* @module dsh-tiddlywiki/host/seed-notes
|
|
10
16
|
*/
|
|
@@ -16,6 +22,9 @@ export const DOC_NOTE_TITLE = 'dsh-tiddlywiki 插件说明'
|
|
|
16
22
|
/** Tag that makes the note easy to find via `tiddlywiki_search tag=docs`. */
|
|
17
23
|
export const DOC_NOTE_TAG = 'docs'
|
|
18
24
|
|
|
25
|
+
/** One-time marker: its presence means "the note was offered once — hands off". */
|
|
26
|
+
export const SEED_MARKER_TITLE = '$:/plugins/dsh-tiddlywiki/seed-doc-note'
|
|
27
|
+
|
|
19
28
|
/** The note body, TiddlyWiki wiki-text. */
|
|
20
29
|
export const DOC_NOTE_TEXT = `! dsh-tiddlywiki 插件说明
|
|
21
30
|
|
|
@@ -25,9 +34,9 @@ export const DOC_NOTE_TEXT = `! dsh-tiddlywiki 插件说明
|
|
|
25
34
|
|
|
26
35
|
* **5 个 agent 工具**:\`tiddlywiki_search\`(检索)/ \`tiddlywiki_get\`(读)/ \`tiddlywiki_put\`(写)/ \`tiddlywiki_delete\`(删)/ \`tiddlywiki_git_sync\`(git 同步)。
|
|
27
36
|
* **TW 编辑器面板**:侧边栏「TiddlyWiki」按钮 → 在界面中央打开完整版 TW 编辑器。
|
|
28
|
-
* **快速笔记**:右下角悬浮「📝
|
|
37
|
+
* **快速笔记**:右下角悬浮「📝 快速笔记」写随手记(可多选/自动补全 tag),\`Ctrl+Enter\` 保存;点「✏️ 在 TW 中编辑」会弹出独立小窗用 TW 原生编辑器编辑。
|
|
29
38
|
* **git 同步**:写入自动防抖 commit(默认 60 秒);手动 \`tiddlywiki_git_sync action=sync\` 做 pull → commit → push。
|
|
30
|
-
* **设置页**:DSH 设置 → 「TiddlyWiki
|
|
39
|
+
* **设置页**:DSH 设置 → 「TiddlyWiki 知识库」管理插件/主题/语言与运行配置(含「快速笔记」显示开关、侧边栏移左 CSS 补丁开关)。
|
|
31
40
|
|
|
32
41
|
!! 知识库纪律(三条)
|
|
33
42
|
|
|
@@ -42,21 +51,72 @@ export const DOC_NOTE_TEXT = `! dsh-tiddlywiki 插件说明
|
|
|
42
51
|
|
|
43
52
|
!! 说明
|
|
44
53
|
|
|
45
|
-
*
|
|
54
|
+
* 本笔记由插件在**首次启动**时自动写入(一次性:只写一次)。删除后重启 dsh web **不会自动恢复**——它从此归你所有。
|
|
55
|
+
* 「\`侧边移到左侧.css\`」是插件默认安装的界面补丁(把 TW 右侧栏移到左边);可在设置页关闭「侧边栏移左 CSS」开关来停用补丁还原。
|
|
46
56
|
* 更多细节见插件仓库 README。`
|
|
47
57
|
|
|
48
58
|
/**
|
|
49
|
-
* Seed the doc note
|
|
50
|
-
*
|
|
59
|
+
* Seed the doc note exactly once per wiki. A marker tiddler records that the
|
|
60
|
+
* note has been offered; from then on the note is user-owned and is never
|
|
61
|
+
* re-created (deleting it survives restarts). Returns whether a note was
|
|
62
|
+
* written this call. Never throws.
|
|
51
63
|
*/
|
|
52
64
|
export async function seedDocNote(client: TiddlyWebClient): Promise<boolean> {
|
|
65
|
+
const marker = await client.get(SEED_MARKER_TITLE).catch(() => undefined)
|
|
66
|
+
if (marker !== undefined) return false
|
|
53
67
|
const existing = await client.get(DOC_NOTE_TITLE).catch(() => undefined)
|
|
68
|
+
let wrote = false
|
|
69
|
+
if (existing === undefined) {
|
|
70
|
+
await client.put({
|
|
71
|
+
title: DOC_NOTE_TITLE,
|
|
72
|
+
text: DOC_NOTE_TEXT,
|
|
73
|
+
type: 'text/vnd.tiddlywiki',
|
|
74
|
+
tags: [DOC_NOTE_TAG],
|
|
75
|
+
})
|
|
76
|
+
wrote = true
|
|
77
|
+
}
|
|
78
|
+
// Record the offer regardless, so an existing note (upgrade from an older
|
|
79
|
+
// create-if-missing version) also becomes user-owned from here on.
|
|
80
|
+
await client
|
|
81
|
+
.put({ title: SEED_MARKER_TITLE, text: 'seeded-once', type: 'text/plain', tags: [] })
|
|
82
|
+
.catch(() => undefined)
|
|
83
|
+
return wrote
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Sidebar-left CSS tiddler: moves TW's sidebar to the LEFT (mirror of the
|
|
87
|
+
* `centralised` default), so dsh-better-sidebar's top-right toggle cluster
|
|
88
|
+
* no longer sits over TW's own right sidebar. A normal tiddler (tag
|
|
89
|
+
* `$:/tags/Stylesheet`, type text/css) — same pattern as hand-made
|
|
90
|
+
* `编辑界面微调:小标题·大编辑器.css`, easy to find and delete. */
|
|
91
|
+
export const SIDEBAR_LEFT_CSS_TITLE = '侧边移到左侧.css'
|
|
92
|
+
|
|
93
|
+
/** The stylesheet body. Hardcoded 960px breakpoint (vanilla default
|
|
94
|
+
* sidebarbreakpoint) — plain text/css tiddlers don't process `{{...}}`. */
|
|
95
|
+
export const SIDEBAR_LEFT_CSS = `/* 由 dsh-tiddlywiki 插件默认安装:把 TW 右侧栏镜像到左侧(与 centralised 主题默认对称)。
|
|
96
|
+
删除后,只要设置页「侧边栏移左 CSS」补丁开关开着,插件会在下次启动时重建本 tiddler。 */
|
|
97
|
+
@media (min-width: 960px) {
|
|
98
|
+
html .tc-sidebar-scrollable {
|
|
99
|
+
left: 0 !important;
|
|
100
|
+
right: 50% !important;
|
|
101
|
+
margin-left: 0 !important;
|
|
102
|
+
margin-right: 343px !important;
|
|
103
|
+
}
|
|
104
|
+
}`
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Patch-repair the sidebar-left CSS while the option is enabled: write it
|
|
108
|
+
* whenever it is missing (fresh installs and deleted-by-user both re-seed).
|
|
109
|
+
* Returns whether it was written. Never throws.
|
|
110
|
+
*/
|
|
111
|
+
export async function seedSidebarLeftCss(client: TiddlyWebClient, enabled: boolean): Promise<boolean> {
|
|
112
|
+
if (!enabled) return false
|
|
113
|
+
const existing = await client.get(SIDEBAR_LEFT_CSS_TITLE).catch(() => undefined)
|
|
54
114
|
if (existing !== undefined) return false
|
|
55
115
|
await client.put({
|
|
56
|
-
title:
|
|
57
|
-
text:
|
|
58
|
-
type: 'text/
|
|
59
|
-
tags: [
|
|
116
|
+
title: SIDEBAR_LEFT_CSS_TITLE,
|
|
117
|
+
text: SIDEBAR_LEFT_CSS,
|
|
118
|
+
type: 'text/css',
|
|
119
|
+
tags: ['$:/tags/Stylesheet'],
|
|
60
120
|
})
|
|
61
121
|
return true
|
|
62
122
|
}
|
package/src/index.ts
CHANGED
|
@@ -25,7 +25,7 @@ import { AutoCommitter, GitFace } from './host/git.ts'
|
|
|
25
25
|
import { registerRoutes, type WebServerFace } from './host/routes.ts'
|
|
26
26
|
import { ConfigStore, deepMerge, type PluginConfigShape } from './host/config.ts'
|
|
27
27
|
import { registerAdminRoutes, ensureLanguage, resolveTwRoot, type AdminDeps } from './host/admin.ts'
|
|
28
|
-
import { seedDocNote, DOC_NOTE_TITLE } from './host/seed-notes.ts'
|
|
28
|
+
import { seedDocNote, seedSidebarLeftCss, DOC_NOTE_TITLE } from './host/seed-notes.ts'
|
|
29
29
|
import { TiddlyWebClient } from './host/tw-api.ts'
|
|
30
30
|
import { registerTiddlywikiTools, type ToolsDeps } from './host/tools.ts'
|
|
31
31
|
import { PATH_PREFIX, WikiServer, type WikiServerOptions } from './host/wiki.ts'
|
|
@@ -42,7 +42,7 @@ export { AutoCommitter, GitFace, PATH_PREFIX, TiddlyWebClient, WikiServer, dshHo
|
|
|
42
42
|
export { ConfigStore, deepMerge } from './host/config.ts'
|
|
43
43
|
export { openInTwEditor } from './host/routes.ts'
|
|
44
44
|
export { registerAdminRoutes, resolveTwRoot, readWikiInfo, writeWikiInfo, bundledCatalog, ensureLanguage, normalizeThemes } from './host/admin.ts'
|
|
45
|
-
export { seedDocNote, DOC_NOTE_TITLE, DOC_NOTE_TAG, DOC_NOTE_TEXT } from './host/seed-notes.ts'
|
|
45
|
+
export { seedDocNote, seedSidebarLeftCss, DOC_NOTE_TITLE, DOC_NOTE_TAG, DOC_NOTE_TEXT, SIDEBAR_LEFT_CSS_TITLE, SIDEBAR_LEFT_CSS } from './host/seed-notes.ts'
|
|
46
46
|
export type { PluginConfigShape } from './host/config.ts'
|
|
47
47
|
export type { GitStatusView } from './host/git.ts'
|
|
48
48
|
export type { Tiddler } from './host/tw-api.ts'
|
|
@@ -55,6 +55,7 @@ export interface TiddlywikiConfig {
|
|
|
55
55
|
port?: number
|
|
56
56
|
git?: { autoCommit?: boolean; debounceMs?: number; remote?: string; branch?: string }
|
|
57
57
|
note?: { tag?: string }
|
|
58
|
+
ui?: { showQuickNote?: boolean; sidebarLeftCss?: boolean }
|
|
58
59
|
auth?: { username?: string; password?: string }
|
|
59
60
|
}
|
|
60
61
|
|
|
@@ -75,6 +76,7 @@ interface ResolvedConfig {
|
|
|
75
76
|
port: number
|
|
76
77
|
git: { autoCommit: boolean; debounceMs: number; remote: string; branch: string }
|
|
77
78
|
note: { tag: string }
|
|
79
|
+
ui: { showQuickNote: boolean; sidebarLeftCss: boolean }
|
|
78
80
|
auth: { username?: string; password?: string }
|
|
79
81
|
}
|
|
80
82
|
|
|
@@ -84,6 +86,7 @@ const DEFAULTS: ResolvedConfig = {
|
|
|
84
86
|
port: 0,
|
|
85
87
|
git: { autoCommit: true, debounceMs: 60_000, remote: '', branch: 'main' },
|
|
86
88
|
note: { tag: 'inbox' },
|
|
89
|
+
ui: { showQuickNote: true, sidebarLeftCss: true },
|
|
87
90
|
auth: { username: '', password: '' },
|
|
88
91
|
}
|
|
89
92
|
|
|
@@ -163,6 +166,7 @@ export function apply(ctx: HostCtx, rawConfig: TiddlywikiConfig = {}): void {
|
|
|
163
166
|
port: rawConfig.port ?? DEFAULTS.port,
|
|
164
167
|
git: { ...DEFAULTS.git, ...(rawConfig.git ?? {}) },
|
|
165
168
|
note: { ...DEFAULTS.note, ...(rawConfig.note ?? {}) },
|
|
169
|
+
ui: { ...DEFAULTS.ui, ...(rawConfig.ui ?? {}) },
|
|
166
170
|
auth: { ...DEFAULTS.auth, ...(rawConfig.auth ?? {}) },
|
|
167
171
|
}
|
|
168
172
|
const wikiPath = join(config.wikiRoot, config.wiki)
|
|
@@ -171,12 +175,16 @@ export function apply(ctx: HostCtx, rawConfig: TiddlywikiConfig = {}): void {
|
|
|
171
175
|
// Runtime-editable config (settings page): the cordis `config:` block is the
|
|
172
176
|
// BASE; a config tiddler ($:/plugins/dsh-tiddlywiki/config) written by the
|
|
173
177
|
// settings page overlays it. Effective values come from configStore.get().
|
|
174
|
-
const configStore = new ConfigStore({ note: config.note, git: config.git } satisfies PluginConfigShape)
|
|
178
|
+
const configStore = new ConfigStore({ note: config.note, git: config.git, ui: config.ui } satisfies PluginConfigShape)
|
|
175
179
|
const eff = (): PluginConfigShape => configStore.get()
|
|
176
180
|
const effectiveNoteTag = (): string => {
|
|
177
181
|
const tag = eff().note?.tag
|
|
178
182
|
return typeof tag === 'string' && tag.trim().length > 0 ? tag : config.note.tag
|
|
179
183
|
}
|
|
184
|
+
const effectiveUi = (): { showQuickNote: boolean; sidebarLeftCss: boolean } => ({
|
|
185
|
+
showQuickNote: eff().ui?.showQuickNote !== false,
|
|
186
|
+
sidebarLeftCss: eff().ui?.sidebarLeftCss !== false,
|
|
187
|
+
})
|
|
180
188
|
|
|
181
189
|
const disposers: Array<() => void> = []
|
|
182
190
|
const disposeAll = (): void => {
|
|
@@ -265,13 +273,19 @@ export function apply(ctx: HostCtx, rawConfig: TiddlywikiConfig = {}): void {
|
|
|
265
273
|
try {
|
|
266
274
|
await server.start()
|
|
267
275
|
await configStore.load(client())
|
|
268
|
-
//
|
|
269
|
-
// wiki gets the plugin guide
|
|
276
|
+
// Seeds (run after the effective config is loaded):
|
|
277
|
+
// - doc note: ONE-SHOT — a fresh wiki gets the plugin guide; deleting
|
|
278
|
+
// it afterwards survives restarts (see seedDocNote).
|
|
279
|
+
// - sidebar-left CSS: PATCH-REPAIR while ui.sidebarLeftCss is on —
|
|
280
|
+
// re-seeds when missing (fresh install OR user deleted it).
|
|
270
281
|
try {
|
|
271
282
|
const seedClient = client()
|
|
272
|
-
if (seedClient !== undefined)
|
|
283
|
+
if (seedClient !== undefined) {
|
|
284
|
+
await seedDocNote(seedClient)
|
|
285
|
+
await seedSidebarLeftCss(seedClient, effectiveUi().sidebarLeftCss)
|
|
286
|
+
}
|
|
273
287
|
} catch (err) {
|
|
274
|
-
console.warn('[dsh-tiddlywiki] seeding doc note:', err)
|
|
288
|
+
console.warn('[dsh-tiddlywiki] seeding doc note / css:', err)
|
|
275
289
|
}
|
|
276
290
|
// Apply the configured UI language (e.g. "zh-Hans"): enable the bundled
|
|
277
291
|
// language plugin in tiddlywiki.info.languages + restart once so TW loads
|
|
@@ -307,6 +321,7 @@ export function apply(ctx: HostCtx, rawConfig: TiddlywikiConfig = {}): void {
|
|
|
307
321
|
git,
|
|
308
322
|
autoCommit: () => committer?.touch(),
|
|
309
323
|
noteDefaults: () => ({ tag: effectiveNoteTag() }),
|
|
324
|
+
uiDefaults: () => effectiveUi(),
|
|
310
325
|
getWikiPath: () => wikiPath,
|
|
311
326
|
})
|
|
312
327
|
const adminDeps: AdminDeps = {
|