dsh-tiddlywiki 0.1.0 → 0.1.2
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/LICENSE +21 -21
- package/README.md +203 -172
- package/cordis.patch.yml +13 -13
- package/lib/client.bundle.js +46 -1
- package/lib/client.js +46 -1
- package/lib/index.js +2152 -2152
- package/package.json +79 -63
- package/src/client/editor-popup.ts +121 -121
- package/src/client/index.ts +76 -76
- package/src/client/note-widget.ts +210 -210
- package/src/client/panel.ts +345 -304
- package/src/client/settings-page.ts +397 -397
- package/src/client/sidebar-entry.ts +148 -148
- package/src/client/state.ts +39 -39
- package/src/client/styles.ts +242 -235
- package/src/client/toast.ts +22 -22
- package/src/host/admin.ts +408 -408
- package/src/host/config.ts +86 -86
- package/src/host/git.ts +218 -218
- package/src/host/routes.ts +233 -233
- package/src/host/seed-notes.ts +62 -62
- package/src/host/tools.ts +254 -254
- package/src/host/tw-api.ts +157 -157
- package/src/host/wiki.ts +287 -287
- package/src/index.ts +331 -331
- package/src/sdk.ts +198 -198
|
@@ -1,210 +1,210 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Floating quick-note widget (design doc §12, D6/D7) — bottom-right, fixed,
|
|
3
|
-
* collapsible, independent of any shell DOM. Lets the human jot drafts /
|
|
4
|
-
* scratch notes while waiting for the AI or drafting the next prompt.
|
|
5
|
-
*
|
|
6
|
-
* Save posts to /dsh-tiddlywiki/note → an independent tiddler (title & tag
|
|
7
|
-
* editable; defaults: timestamp title + config tag, usually "inbox").
|
|
8
|
-
*
|
|
9
|
-
* @module dsh-tiddlywiki/client/note-widget
|
|
10
|
-
*/
|
|
11
|
-
import { toast } from './toast.ts'
|
|
12
|
-
import { openEditorPopup } from './editor-popup.ts'
|
|
13
|
-
|
|
14
|
-
const NOTE_ENDPOINT = '/dsh-tiddlywiki/note'
|
|
15
|
-
const EDIT_ENDPOINT = '/dsh-tiddlywiki/edit'
|
|
16
|
-
const STATUS_ENDPOINT = '/dsh-tiddlywiki/status'
|
|
17
|
-
|
|
18
|
-
function pad(n: number): string {
|
|
19
|
-
return n < 10 ? `0${n}` : String(n)
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/** Default note title: `YYYY-MM-DD HH:mm`. */
|
|
23
|
-
function timestampTitle(date = new Date()): string {
|
|
24
|
-
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
async function fetchDefaultTag(): Promise<string> {
|
|
28
|
-
try {
|
|
29
|
-
const res = await fetch(STATUS_ENDPOINT, { signal: AbortSignal.timeout(5_000) })
|
|
30
|
-
if (!res.ok) return 'inbox'
|
|
31
|
-
const payload = (await res.json()) as { note?: { tag?: string } }
|
|
32
|
-
return payload.note?.tag ?? 'inbox'
|
|
33
|
-
} catch {
|
|
34
|
-
return 'inbox'
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export function mountNoteWidget(): () => void {
|
|
39
|
-
const root = document.createElement('div')
|
|
40
|
-
root.className = 'dsh-tw-note'
|
|
41
|
-
|
|
42
|
-
const card = document.createElement('div')
|
|
43
|
-
card.className = 'dsh-tw-note-card'
|
|
44
|
-
card.hidden = true
|
|
45
|
-
|
|
46
|
-
const head = document.createElement('div')
|
|
47
|
-
head.className = 'dsh-tw-note-head'
|
|
48
|
-
const label = document.createElement('span')
|
|
49
|
-
label.className = 'dsh-tw-note-label'
|
|
50
|
-
label.textContent = '📝 快速笔记'
|
|
51
|
-
const closeBtn = document.createElement('button')
|
|
52
|
-
closeBtn.type = 'button'
|
|
53
|
-
closeBtn.className = 'dsh-tw-note-close'
|
|
54
|
-
closeBtn.title = '收起'
|
|
55
|
-
closeBtn.textContent = '✕'
|
|
56
|
-
head.append(label, closeBtn)
|
|
57
|
-
|
|
58
|
-
const fields = document.createElement('div')
|
|
59
|
-
fields.className = 'dsh-tw-note-fields'
|
|
60
|
-
const titleInput = document.createElement('input')
|
|
61
|
-
titleInput.className = 'dsh-tw-note-title'
|
|
62
|
-
titleInput.placeholder = '标题(默认时间戳)'
|
|
63
|
-
const tagInput = document.createElement('input')
|
|
64
|
-
tagInput.className = 'dsh-tw-note-tag'
|
|
65
|
-
tagInput.placeholder = 'tag(默认 inbox)'
|
|
66
|
-
fields.append(titleInput, tagInput)
|
|
67
|
-
|
|
68
|
-
const textarea = document.createElement('textarea')
|
|
69
|
-
textarea.className = 'dsh-tw-note-text'
|
|
70
|
-
textarea.placeholder = '写点东西…(Ctrl+Enter 保存)'
|
|
71
|
-
|
|
72
|
-
const foot = document.createElement('div')
|
|
73
|
-
foot.className = 'dsh-tw-note-foot'
|
|
74
|
-
const hint = document.createElement('span')
|
|
75
|
-
hint.className = 'dsh-tw-note-hint'
|
|
76
|
-
hint.textContent = 'Ctrl+Enter'
|
|
77
|
-
const edit = document.createElement('button')
|
|
78
|
-
edit.type = 'button'
|
|
79
|
-
edit.className = 'dsh-tw-note-edit'
|
|
80
|
-
edit.title = '保存并在 TiddlyWiki 原生编辑器中打开'
|
|
81
|
-
edit.textContent = '✏️ 在 TW 中编辑'
|
|
82
|
-
const save = document.createElement('button')
|
|
83
|
-
save.type = 'button'
|
|
84
|
-
save.className = 'dsh-tw-note-save'
|
|
85
|
-
save.textContent = '保存'
|
|
86
|
-
foot.append(hint, edit, save)
|
|
87
|
-
|
|
88
|
-
card.append(head, fields, textarea, foot)
|
|
89
|
-
|
|
90
|
-
const toggle = document.createElement('button')
|
|
91
|
-
toggle.type = 'button'
|
|
92
|
-
toggle.className = 'dsh-tw-note-toggle'
|
|
93
|
-
toggle.textContent = '📝 快速笔记'
|
|
94
|
-
|
|
95
|
-
root.append(card, toggle)
|
|
96
|
-
document.body.append(root)
|
|
97
|
-
|
|
98
|
-
let defaultTag = 'inbox'
|
|
99
|
-
let opened = false
|
|
100
|
-
|
|
101
|
-
const resetTitle = (): void => {
|
|
102
|
-
titleInput.value = timestampTitle()
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const open = async (): Promise<void> => {
|
|
106
|
-
card.hidden = false
|
|
107
|
-
opened = true
|
|
108
|
-
resetTitle()
|
|
109
|
-
if (tagInput.value.length === 0) tagInput.value = defaultTag
|
|
110
|
-
textarea.focus()
|
|
111
|
-
// Refresh the configured default tag on each open (cheap, best effort).
|
|
112
|
-
defaultTag = await fetchDefaultTag()
|
|
113
|
-
if (tagInput.value.length === 0) tagInput.value = defaultTag
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const close = (): void => {
|
|
117
|
-
card.hidden = true
|
|
118
|
-
opened = false
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
toggle.addEventListener('click', () => { void (opened ? close() : open()) })
|
|
122
|
-
closeBtn.addEventListener('click', close)
|
|
123
|
-
|
|
124
|
-
const doSave = async (): Promise<void> => {
|
|
125
|
-
const text = textarea.value.trim()
|
|
126
|
-
if (text.length === 0) {
|
|
127
|
-
toast('内容为空,未保存')
|
|
128
|
-
return
|
|
129
|
-
}
|
|
130
|
-
save.disabled = true
|
|
131
|
-
save.textContent = '保存中…'
|
|
132
|
-
try {
|
|
133
|
-
const res = await fetch(NOTE_ENDPOINT, {
|
|
134
|
-
method: 'POST',
|
|
135
|
-
headers: { 'content-type': 'application/json' },
|
|
136
|
-
body: JSON.stringify({
|
|
137
|
-
title: titleInput.value.trim(),
|
|
138
|
-
tag: tagInput.value.trim(),
|
|
139
|
-
text,
|
|
140
|
-
}),
|
|
141
|
-
signal: AbortSignal.timeout(10_000),
|
|
142
|
-
})
|
|
143
|
-
const payload = (await res.json().catch(() => null)) as { ok?: boolean; title?: string; error?: string } | null
|
|
144
|
-
if (!res.ok || payload?.ok !== true) {
|
|
145
|
-
toast(`保存失败:${payload?.error ?? `HTTP ${res.status}`}`)
|
|
146
|
-
return
|
|
147
|
-
}
|
|
148
|
-
textarea.value = ''
|
|
149
|
-
resetTitle()
|
|
150
|
-
toast(`已保存「${payload.title ?? titleInput.value}」`)
|
|
151
|
-
close()
|
|
152
|
-
} catch (err) {
|
|
153
|
-
toast(`保存失败:${err instanceof Error ? err.message : String(err)}`)
|
|
154
|
-
} finally {
|
|
155
|
-
save.disabled = false
|
|
156
|
-
save.textContent = '保存'
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
save.addEventListener('click', () => { void doSave() })
|
|
161
|
-
textarea.addEventListener('keydown', (event) => {
|
|
162
|
-
if ((event.ctrlKey || event.metaKey) && event.key === 'Enter') {
|
|
163
|
-
event.preventDefault()
|
|
164
|
-
void doSave()
|
|
165
|
-
}
|
|
166
|
-
})
|
|
167
|
-
|
|
168
|
-
/** Save (if non-empty) and open the tiddler in TW's native editor. */
|
|
169
|
-
const doEdit = async (): Promise<void> => {
|
|
170
|
-
const title = titleInput.value.trim().length > 0 ? titleInput.value.trim() : timestampTitle()
|
|
171
|
-
const text = textarea.value
|
|
172
|
-
const tag = tagInput.value.trim()
|
|
173
|
-
edit.disabled = true
|
|
174
|
-
edit.textContent = '打开中…'
|
|
175
|
-
try {
|
|
176
|
-
const res = await fetch(EDIT_ENDPOINT, {
|
|
177
|
-
method: 'POST',
|
|
178
|
-
headers: { 'content-type': 'application/json' },
|
|
179
|
-
body: JSON.stringify({ title, tag, text }),
|
|
180
|
-
signal: AbortSignal.timeout(10_000),
|
|
181
|
-
})
|
|
182
|
-
const payload = (await res.json().catch(() => null)) as
|
|
183
|
-
| { ok?: boolean; title?: string; draftTitle?: string; twUrl?: string; error?: string }
|
|
184
|
-
| null
|
|
185
|
-
if (!res.ok || payload?.ok !== true) {
|
|
186
|
-
toast(`打开失败:${payload?.error ?? `HTTP ${res.status}`}`)
|
|
187
|
-
return
|
|
188
|
-
}
|
|
189
|
-
if (typeof payload.twUrl !== 'string' || typeof payload.draftTitle !== 'string') {
|
|
190
|
-
toast('打开失败:服务未返回编辑器地址')
|
|
191
|
-
return
|
|
192
|
-
}
|
|
193
|
-
openEditorPopup(`${payload.twUrl}#${encodeURIComponent(payload.draftTitle)}`, payload.title ?? title)
|
|
194
|
-
toast(`已在弹出窗口打开「${payload.title ?? title}」编辑器`)
|
|
195
|
-
} catch (err) {
|
|
196
|
-
toast(`打开失败:${err instanceof Error ? err.message : String(err)}`)
|
|
197
|
-
} finally {
|
|
198
|
-
edit.disabled = false
|
|
199
|
-
edit.textContent = '✏️ 在 TW 中编辑'
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
edit.addEventListener('click', () => { void doEdit() })
|
|
204
|
-
|
|
205
|
-
return () => {
|
|
206
|
-
root.remove()
|
|
207
|
-
const toastEl = document.querySelector<HTMLElement>('.dsh-tw-toast')
|
|
208
|
-
toastEl?.remove()
|
|
209
|
-
}
|
|
210
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Floating quick-note widget (design doc §12, D6/D7) — bottom-right, fixed,
|
|
3
|
+
* collapsible, independent of any shell DOM. Lets the human jot drafts /
|
|
4
|
+
* scratch notes while waiting for the AI or drafting the next prompt.
|
|
5
|
+
*
|
|
6
|
+
* Save posts to /dsh-tiddlywiki/note → an independent tiddler (title & tag
|
|
7
|
+
* editable; defaults: timestamp title + config tag, usually "inbox").
|
|
8
|
+
*
|
|
9
|
+
* @module dsh-tiddlywiki/client/note-widget
|
|
10
|
+
*/
|
|
11
|
+
import { toast } from './toast.ts'
|
|
12
|
+
import { openEditorPopup } from './editor-popup.ts'
|
|
13
|
+
|
|
14
|
+
const NOTE_ENDPOINT = '/dsh-tiddlywiki/note'
|
|
15
|
+
const EDIT_ENDPOINT = '/dsh-tiddlywiki/edit'
|
|
16
|
+
const STATUS_ENDPOINT = '/dsh-tiddlywiki/status'
|
|
17
|
+
|
|
18
|
+
function pad(n: number): string {
|
|
19
|
+
return n < 10 ? `0${n}` : String(n)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Default note title: `YYYY-MM-DD HH:mm`. */
|
|
23
|
+
function timestampTitle(date = new Date()): string {
|
|
24
|
+
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function fetchDefaultTag(): Promise<string> {
|
|
28
|
+
try {
|
|
29
|
+
const res = await fetch(STATUS_ENDPOINT, { signal: AbortSignal.timeout(5_000) })
|
|
30
|
+
if (!res.ok) return 'inbox'
|
|
31
|
+
const payload = (await res.json()) as { note?: { tag?: string } }
|
|
32
|
+
return payload.note?.tag ?? 'inbox'
|
|
33
|
+
} catch {
|
|
34
|
+
return 'inbox'
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function mountNoteWidget(): () => void {
|
|
39
|
+
const root = document.createElement('div')
|
|
40
|
+
root.className = 'dsh-tw-note'
|
|
41
|
+
|
|
42
|
+
const card = document.createElement('div')
|
|
43
|
+
card.className = 'dsh-tw-note-card'
|
|
44
|
+
card.hidden = true
|
|
45
|
+
|
|
46
|
+
const head = document.createElement('div')
|
|
47
|
+
head.className = 'dsh-tw-note-head'
|
|
48
|
+
const label = document.createElement('span')
|
|
49
|
+
label.className = 'dsh-tw-note-label'
|
|
50
|
+
label.textContent = '📝 快速笔记'
|
|
51
|
+
const closeBtn = document.createElement('button')
|
|
52
|
+
closeBtn.type = 'button'
|
|
53
|
+
closeBtn.className = 'dsh-tw-note-close'
|
|
54
|
+
closeBtn.title = '收起'
|
|
55
|
+
closeBtn.textContent = '✕'
|
|
56
|
+
head.append(label, closeBtn)
|
|
57
|
+
|
|
58
|
+
const fields = document.createElement('div')
|
|
59
|
+
fields.className = 'dsh-tw-note-fields'
|
|
60
|
+
const titleInput = document.createElement('input')
|
|
61
|
+
titleInput.className = 'dsh-tw-note-title'
|
|
62
|
+
titleInput.placeholder = '标题(默认时间戳)'
|
|
63
|
+
const tagInput = document.createElement('input')
|
|
64
|
+
tagInput.className = 'dsh-tw-note-tag'
|
|
65
|
+
tagInput.placeholder = 'tag(默认 inbox)'
|
|
66
|
+
fields.append(titleInput, tagInput)
|
|
67
|
+
|
|
68
|
+
const textarea = document.createElement('textarea')
|
|
69
|
+
textarea.className = 'dsh-tw-note-text'
|
|
70
|
+
textarea.placeholder = '写点东西…(Ctrl+Enter 保存)'
|
|
71
|
+
|
|
72
|
+
const foot = document.createElement('div')
|
|
73
|
+
foot.className = 'dsh-tw-note-foot'
|
|
74
|
+
const hint = document.createElement('span')
|
|
75
|
+
hint.className = 'dsh-tw-note-hint'
|
|
76
|
+
hint.textContent = 'Ctrl+Enter'
|
|
77
|
+
const edit = document.createElement('button')
|
|
78
|
+
edit.type = 'button'
|
|
79
|
+
edit.className = 'dsh-tw-note-edit'
|
|
80
|
+
edit.title = '保存并在 TiddlyWiki 原生编辑器中打开'
|
|
81
|
+
edit.textContent = '✏️ 在 TW 中编辑'
|
|
82
|
+
const save = document.createElement('button')
|
|
83
|
+
save.type = 'button'
|
|
84
|
+
save.className = 'dsh-tw-note-save'
|
|
85
|
+
save.textContent = '保存'
|
|
86
|
+
foot.append(hint, edit, save)
|
|
87
|
+
|
|
88
|
+
card.append(head, fields, textarea, foot)
|
|
89
|
+
|
|
90
|
+
const toggle = document.createElement('button')
|
|
91
|
+
toggle.type = 'button'
|
|
92
|
+
toggle.className = 'dsh-tw-note-toggle'
|
|
93
|
+
toggle.textContent = '📝 快速笔记'
|
|
94
|
+
|
|
95
|
+
root.append(card, toggle)
|
|
96
|
+
document.body.append(root)
|
|
97
|
+
|
|
98
|
+
let defaultTag = 'inbox'
|
|
99
|
+
let opened = false
|
|
100
|
+
|
|
101
|
+
const resetTitle = (): void => {
|
|
102
|
+
titleInput.value = timestampTitle()
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const open = async (): Promise<void> => {
|
|
106
|
+
card.hidden = false
|
|
107
|
+
opened = true
|
|
108
|
+
resetTitle()
|
|
109
|
+
if (tagInput.value.length === 0) tagInput.value = defaultTag
|
|
110
|
+
textarea.focus()
|
|
111
|
+
// Refresh the configured default tag on each open (cheap, best effort).
|
|
112
|
+
defaultTag = await fetchDefaultTag()
|
|
113
|
+
if (tagInput.value.length === 0) tagInput.value = defaultTag
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const close = (): void => {
|
|
117
|
+
card.hidden = true
|
|
118
|
+
opened = false
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
toggle.addEventListener('click', () => { void (opened ? close() : open()) })
|
|
122
|
+
closeBtn.addEventListener('click', close)
|
|
123
|
+
|
|
124
|
+
const doSave = async (): Promise<void> => {
|
|
125
|
+
const text = textarea.value.trim()
|
|
126
|
+
if (text.length === 0) {
|
|
127
|
+
toast('内容为空,未保存')
|
|
128
|
+
return
|
|
129
|
+
}
|
|
130
|
+
save.disabled = true
|
|
131
|
+
save.textContent = '保存中…'
|
|
132
|
+
try {
|
|
133
|
+
const res = await fetch(NOTE_ENDPOINT, {
|
|
134
|
+
method: 'POST',
|
|
135
|
+
headers: { 'content-type': 'application/json' },
|
|
136
|
+
body: JSON.stringify({
|
|
137
|
+
title: titleInput.value.trim(),
|
|
138
|
+
tag: tagInput.value.trim(),
|
|
139
|
+
text,
|
|
140
|
+
}),
|
|
141
|
+
signal: AbortSignal.timeout(10_000),
|
|
142
|
+
})
|
|
143
|
+
const payload = (await res.json().catch(() => null)) as { ok?: boolean; title?: string; error?: string } | null
|
|
144
|
+
if (!res.ok || payload?.ok !== true) {
|
|
145
|
+
toast(`保存失败:${payload?.error ?? `HTTP ${res.status}`}`)
|
|
146
|
+
return
|
|
147
|
+
}
|
|
148
|
+
textarea.value = ''
|
|
149
|
+
resetTitle()
|
|
150
|
+
toast(`已保存「${payload.title ?? titleInput.value}」`)
|
|
151
|
+
close()
|
|
152
|
+
} catch (err) {
|
|
153
|
+
toast(`保存失败:${err instanceof Error ? err.message : String(err)}`)
|
|
154
|
+
} finally {
|
|
155
|
+
save.disabled = false
|
|
156
|
+
save.textContent = '保存'
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
save.addEventListener('click', () => { void doSave() })
|
|
161
|
+
textarea.addEventListener('keydown', (event) => {
|
|
162
|
+
if ((event.ctrlKey || event.metaKey) && event.key === 'Enter') {
|
|
163
|
+
event.preventDefault()
|
|
164
|
+
void doSave()
|
|
165
|
+
}
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
/** Save (if non-empty) and open the tiddler in TW's native editor. */
|
|
169
|
+
const doEdit = async (): Promise<void> => {
|
|
170
|
+
const title = titleInput.value.trim().length > 0 ? titleInput.value.trim() : timestampTitle()
|
|
171
|
+
const text = textarea.value
|
|
172
|
+
const tag = tagInput.value.trim()
|
|
173
|
+
edit.disabled = true
|
|
174
|
+
edit.textContent = '打开中…'
|
|
175
|
+
try {
|
|
176
|
+
const res = await fetch(EDIT_ENDPOINT, {
|
|
177
|
+
method: 'POST',
|
|
178
|
+
headers: { 'content-type': 'application/json' },
|
|
179
|
+
body: JSON.stringify({ title, tag, text }),
|
|
180
|
+
signal: AbortSignal.timeout(10_000),
|
|
181
|
+
})
|
|
182
|
+
const payload = (await res.json().catch(() => null)) as
|
|
183
|
+
| { ok?: boolean; title?: string; draftTitle?: string; twUrl?: string; error?: string }
|
|
184
|
+
| null
|
|
185
|
+
if (!res.ok || payload?.ok !== true) {
|
|
186
|
+
toast(`打开失败:${payload?.error ?? `HTTP ${res.status}`}`)
|
|
187
|
+
return
|
|
188
|
+
}
|
|
189
|
+
if (typeof payload.twUrl !== 'string' || typeof payload.draftTitle !== 'string') {
|
|
190
|
+
toast('打开失败:服务未返回编辑器地址')
|
|
191
|
+
return
|
|
192
|
+
}
|
|
193
|
+
openEditorPopup(`${payload.twUrl}#${encodeURIComponent(payload.draftTitle)}`, payload.title ?? title)
|
|
194
|
+
toast(`已在弹出窗口打开「${payload.title ?? title}」编辑器`)
|
|
195
|
+
} catch (err) {
|
|
196
|
+
toast(`打开失败:${err instanceof Error ? err.message : String(err)}`)
|
|
197
|
+
} finally {
|
|
198
|
+
edit.disabled = false
|
|
199
|
+
edit.textContent = '✏️ 在 TW 中编辑'
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
edit.addEventListener('click', () => { void doEdit() })
|
|
204
|
+
|
|
205
|
+
return () => {
|
|
206
|
+
root.remove()
|
|
207
|
+
const toastEl = document.querySelector<HTMLElement>('.dsh-tw-toast')
|
|
208
|
+
toastEl?.remove()
|
|
209
|
+
}
|
|
210
|
+
}
|