dsh-notes-board 0.0.0-stage → 0.3.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/LICENSE +21 -0
- package/NOTICE.md +21 -0
- package/README.md +163 -2
- package/client.js +1253 -0
- package/cordis.patch.yml +6 -0
- package/icon.svg +16 -0
- package/index.js +28 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +79 -4
- package/scripts/build-client.mjs +175 -0
- package/scripts/test-board.mjs +202 -0
- package/src/core/notes.js +486 -0
- package/src/core/tools.js +221 -0
- package/src/host/board.js +184 -0
- package/src/host/notes.js +250 -0
- package/src/host/skills.js +48 -0
- package/src/panel.js +294 -0
- package/vendor/board.html +1779 -0
- package/vendor/write-notes-like-deepseek/README.md +323 -0
- package/vendor/write-notes-like-deepseek/package.json +21 -0
- package/vendor/write-notes-like-deepseek/scripts/export-assets.sh +40 -0
- package/vendor/write-notes-like-deepseek/scripts/import-dsh-notes.ts +125 -0
- package/vendor/write-notes-like-deepseek/scripts/test-skill-install.mjs +63 -0
- package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/SKILL.md +195 -0
- package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/assets/agent-notes-board.html +1779 -0
- package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/references/archiving.md +109 -0
- package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/references/classification.md +33 -0
- package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/references/note-format.md +69 -0
- package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/references/prose-checklist.md +51 -0
- package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/references/quality-gate.md +58 -0
- package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/references/simplification-checklist.md +26 -0
- package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/references/verification.md +34 -0
- package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/references/when-to-write.md +83 -0
- package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/scripts/agent-note-tree.ts +75 -0
- package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/scripts/archive-agent-note.ts +167 -0
- package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/scripts/build-board.ts +235 -0
- package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/scripts/check-note-anchors.ts +103 -0
- package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/scripts/verify-agent-note-format.ts +158 -0
- package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/scripts/verify-agent-note-tree.ts +56 -0
- package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/scripts/verify-archived-agent-notes.ts +127 -0
- package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/templates/implemented.md +32 -0
- package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/templates/proposed.md +38 -0
- package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/templates/rejected.md +27 -0
- package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/templates/verify-notes.yml +38 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-notes-board · 把随包的 write-notes-like-deepseek 技能挂给 agent
|
|
3
|
+
*
|
|
4
|
+
* 技能本体(判定红线、六分类、implemented 骨架、references / templates / 7 个 .ts 脚本)
|
|
5
|
+
* 随 vendor/ 一起发布,note_verify 用的三个校验器也在那里。这里只做一件事:用
|
|
6
|
+
* @deepseek-ai/dsh-skill-filesystem 起一个独立 provider 指向这份目录,让「怎么写笔记」
|
|
7
|
+
* 和「强制结账的钩子」来自同一个包,不再依赖 ~/.dsh/skills 下的手工安装。
|
|
8
|
+
*
|
|
9
|
+
* Why a second provider instead of the default filesystem one: the default provider
|
|
10
|
+
* scans the user's home skill roots, which this package cannot populate on install.
|
|
11
|
+
* A bundled provider makes the skill travel with the plugin and disappear with it.
|
|
12
|
+
*
|
|
13
|
+
* @deepseek-ai/dsh-skill-filesystem 是宿主自带的包(与 dsh-tools / dsh-llm 同类),
|
|
14
|
+
* 因此列为可选 peerDependency,不在 dependencies 里。
|
|
15
|
+
*/
|
|
16
|
+
import { fileURLToPath } from 'node:url'
|
|
17
|
+
import * as skillFilesystem from '@deepseek-ai/dsh-skill-filesystem'
|
|
18
|
+
|
|
19
|
+
/** Provider name on `ctx.skills`; must not collide with DSH's own `filesystem` or `openviking`. */
|
|
20
|
+
export const SKILL_PROVIDER_NAME = 'agent-notes'
|
|
21
|
+
|
|
22
|
+
/** Directory holding skill folders: vendor/write-notes-like-deepseek/skills/<skill>/SKILL.md */
|
|
23
|
+
export const SKILLS_DIR = fileURLToPath(new URL('../../vendor/write-notes-like-deepseek/skills', import.meta.url))
|
|
24
|
+
|
|
25
|
+
export function skillsConfig() {
|
|
26
|
+
return {
|
|
27
|
+
providerName: SKILL_PROVIDER_NAME,
|
|
28
|
+
// 只提供随包技能,不去扫 ~/.dsh/skills、~/.claude/skills 这些默认根,
|
|
29
|
+
// 否则同一份技能会被默认 provider 和本 provider 各索引一次。
|
|
30
|
+
includeDefaultRoots: false,
|
|
31
|
+
bundledSkillDir: SKILLS_DIR,
|
|
32
|
+
// 打包内容只在升级时变;监听器在 Windows 上还会拖住目录替换。
|
|
33
|
+
watch: false,
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function mountNotesSkills(ctx) {
|
|
38
|
+
if (typeof ctx?.plugin !== 'function') {
|
|
39
|
+
ctx?.logger?.info?.('notes-board: skills provider not mounted (ctx.plugin unavailable)')
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
return ctx.plugin(skillFilesystem, skillsConfig())
|
|
44
|
+
} catch (error) {
|
|
45
|
+
ctx?.logger?.warn?.(`notes-board: skills provider failed to mount: ${String(error)}`)
|
|
46
|
+
return
|
|
47
|
+
}
|
|
48
|
+
}
|
package/src/panel.js
ADDED
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
/* dsh-notes-board · 面板外壳(源文件,请在 src/panel.js 里改)
|
|
2
|
+
*
|
|
3
|
+
* client.js 是生成物:scripts/build-client.mjs 读取 vendor/board.html(原版看板模板),
|
|
4
|
+
* 把它的 <style> / <body> / <script> 注入本文件的三处占位符后输出。
|
|
5
|
+
* 改造点全部集中在 build-client.mjs 的 PATCHES 列表里,原版逻辑一行没重写。
|
|
6
|
+
*/
|
|
7
|
+
window.__ModuleLoader__.load({
|
|
8
|
+
id: 'dsh-notes-board',
|
|
9
|
+
factory(require) {
|
|
10
|
+
const React = require('react')
|
|
11
|
+
const h = React.createElement
|
|
12
|
+
const { useState, useEffect, useRef, useCallback } = React
|
|
13
|
+
|
|
14
|
+
const PANEL_ID = 'dsh-notes-board'
|
|
15
|
+
const API = '/api/dsh-notes-board'
|
|
16
|
+
const STORE_KEY = 'dsh-notes-board:workspace'
|
|
17
|
+
|
|
18
|
+
/* 由构建脚本注入:原版看板的 CSS / 正文骨架 / 外链字体 */
|
|
19
|
+
const BOARD_CSS = /*__BOARD_CSS__*/ ''
|
|
20
|
+
const BOARD_HTML = /*__BOARD_HTML__*/ ''
|
|
21
|
+
const BOARD_FONT = /*__BOARD_FONT__*/ ''
|
|
22
|
+
|
|
23
|
+
const SHELL_CSS = `
|
|
24
|
+
.nbx-shell{display:flex;flex-direction:column;height:100%;min-height:0;background:var(--dsw-alias-bg-base);color:var(--dsw-alias-label-primary);font-size:12px}
|
|
25
|
+
.nbx-bar{display:flex;align-items:center;gap:10px;padding:8px 14px;border-bottom:1px solid var(--dsw-alias-border-l1);flex:0 0 auto}
|
|
26
|
+
.nbx-title{font-size:13px;font-weight:650;letter-spacing:.2px}
|
|
27
|
+
.nbx-select{appearance:none;background:var(--dsw-alias-bg-layer-1);color:inherit;border:1px solid var(--dsw-alias-border-l1);border-radius:7px;padding:4px 24px 4px 9px;font-size:12px;font-family:inherit;cursor:pointer;max-width:260px}
|
|
28
|
+
.nbx-select:hover{border-color:var(--dsw-alias-border-l2)}
|
|
29
|
+
.nbx-btn{background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);border:1px solid var(--dsw-alias-border-l1);border-radius:7px;padding:4px 9px;font-size:12px;font-family:inherit;cursor:pointer}
|
|
30
|
+
.nbx-btn:hover{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-border-l2)}
|
|
31
|
+
.nbx-btn[disabled]{opacity:.5;cursor:default}
|
|
32
|
+
.nbx-state{color:var(--dsw-alias-label-secondary);font-size:11.5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
33
|
+
.nbx-err{color:var(--dsw-alias-state-error-primary)}
|
|
34
|
+
.nbx-grow{flex:1 1 auto}
|
|
35
|
+
.nbx-host{flex:1 1 auto;min-height:0;position:relative;display:block}
|
|
36
|
+
`
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 把原版看板挂进一个 shadow root。
|
|
40
|
+
* root: ShadowRoot(原版脚本里的 document.getElementById / querySelector 全部被改写到它上面)
|
|
41
|
+
* host: { onRefresh?: () => void }
|
|
42
|
+
*/
|
|
43
|
+
function mountBoard(root, host) {
|
|
44
|
+
const cleanups = []
|
|
45
|
+
const byId = (id) => root.getElementById(id)
|
|
46
|
+
const onRoot = (type, fn, opts) => {
|
|
47
|
+
root.addEventListener(type, fn, opts)
|
|
48
|
+
cleanups.push(() => root.removeEventListener(type, fn, opts))
|
|
49
|
+
}
|
|
50
|
+
const onWin = (type, fn, opts) => {
|
|
51
|
+
window.addEventListener(type, fn, opts)
|
|
52
|
+
cleanups.push(() => window.removeEventListener(type, fn, opts))
|
|
53
|
+
}
|
|
54
|
+
const setInterval = (fn, ms) => {
|
|
55
|
+
const id = window.setInterval(fn, ms)
|
|
56
|
+
cleanups.push(() => window.clearInterval(id))
|
|
57
|
+
return id
|
|
58
|
+
}
|
|
59
|
+
const panelRoute = {
|
|
60
|
+
_h: '',
|
|
61
|
+
get hash() {
|
|
62
|
+
return this._h ? '#' + this._h : ''
|
|
63
|
+
},
|
|
64
|
+
replaceState(_state, _title, url) {
|
|
65
|
+
this._h = String(url || '').replace(/^#/, '')
|
|
66
|
+
},
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* ===== 以下为原版看板脚本(构建时注入,未重写) ===== */
|
|
70
|
+
/*__BOARD_SCRIPT__*/
|
|
71
|
+
/* ===== 原版看板脚本结束 ===== */
|
|
72
|
+
|
|
73
|
+
/** 宿主把工作区的 .agents/notes 原文喂进来,走原版 parseNote / ingestDataset */
|
|
74
|
+
function ingestFiles(files) {
|
|
75
|
+
const notesMap = new Map()
|
|
76
|
+
for (const file of files || []) {
|
|
77
|
+
const cleanRel = String(file.path || '').replace(/\.zh\.md$/, '.md')
|
|
78
|
+
const parts = cleanRel.split('/')
|
|
79
|
+
if (parts.length >= 3 && LIFECYCLES.includes(parts[0])) notesMap.set(cleanRel, String(file.raw || ''))
|
|
80
|
+
}
|
|
81
|
+
const slugToId = new Map()
|
|
82
|
+
for (const rel of notesMap.keys()) {
|
|
83
|
+
const slug = rel.split('/').pop().replace(/\.md$/, '')
|
|
84
|
+
slugToId.set(slug, rel)
|
|
85
|
+
slugToId.set(rel, rel)
|
|
86
|
+
}
|
|
87
|
+
const notes = []
|
|
88
|
+
for (const [rel, text] of notesMap.entries()) notes.push(parseNote(text, rel, slugToId))
|
|
89
|
+
ingestDataset(notes)
|
|
90
|
+
return notes.length
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
const boot = startMission()
|
|
95
|
+
if (boot && typeof boot.catch === 'function') {
|
|
96
|
+
boot.catch((error) => console.error('[dsh-notes-board] startMission failed', error))
|
|
97
|
+
}
|
|
98
|
+
} catch (error) {
|
|
99
|
+
console.error('[dsh-notes-board] startMission failed', error)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
ingest: ingestFiles,
|
|
104
|
+
/* 测试用:scripts/test-board.mjs 用它验证懒渲染 */
|
|
105
|
+
switchTab: switchDeckTab,
|
|
106
|
+
dispose() {
|
|
107
|
+
cleanups.splice(0).forEach((fn) => {
|
|
108
|
+
try {
|
|
109
|
+
fn()
|
|
110
|
+
} catch {
|
|
111
|
+
/* 卸载期单个清理失败不影响其余 */
|
|
112
|
+
}
|
|
113
|
+
})
|
|
114
|
+
},
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/* ── 面板 ─────────────────────────────────────────────── */
|
|
119
|
+
|
|
120
|
+
function NotesBoard() {
|
|
121
|
+
const [workspaces, setWorkspaces] = useState([])
|
|
122
|
+
const [selected, setSelected] = useState('')
|
|
123
|
+
const [state, setState] = useState('正在读取工作区清单…')
|
|
124
|
+
const [busy, setBusy] = useState(false)
|
|
125
|
+
const [failed, setFailed] = useState(false)
|
|
126
|
+
const hostRef = useRef(null)
|
|
127
|
+
const boardRef = useRef(null)
|
|
128
|
+
const abortRef = useRef(null)
|
|
129
|
+
|
|
130
|
+
const fetchJson = async (url, signal) => {
|
|
131
|
+
const response = await fetch(url, { cache: 'no-store', signal })
|
|
132
|
+
const payload = await response.json().catch(() => null)
|
|
133
|
+
if (!response.ok || !payload || payload.ok === false) {
|
|
134
|
+
throw new Error((payload && payload.message) || `请求失败 (${response.status})`)
|
|
135
|
+
}
|
|
136
|
+
return payload
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
useEffect(() => {
|
|
140
|
+
const hostEl = hostRef.current
|
|
141
|
+
if (!hostEl) return undefined
|
|
142
|
+
const shadow = hostEl.shadowRoot || hostEl.attachShadow({ mode: 'open' })
|
|
143
|
+
shadow.innerHTML =
|
|
144
|
+
'<style>' + BOARD_CSS + '</style>' + BOARD_FONT + '<div class="nbx-stage">' + BOARD_HTML + '</div>'
|
|
145
|
+
let mounted = null
|
|
146
|
+
try {
|
|
147
|
+
mounted = mountBoard(shadow, {})
|
|
148
|
+
boardRef.current = mounted
|
|
149
|
+
} catch (error) {
|
|
150
|
+
console.error('[dsh-notes-board] mount failed', error)
|
|
151
|
+
setState('看板挂载失败:' + (error && error.message ? error.message : error))
|
|
152
|
+
setFailed(true)
|
|
153
|
+
}
|
|
154
|
+
return () => {
|
|
155
|
+
if (mounted) mounted.dispose()
|
|
156
|
+
boardRef.current = null
|
|
157
|
+
shadow.innerHTML = ''
|
|
158
|
+
}
|
|
159
|
+
}, [])
|
|
160
|
+
|
|
161
|
+
useEffect(() => {
|
|
162
|
+
let alive = true
|
|
163
|
+
fetchJson(`${API}/workspaces`)
|
|
164
|
+
.then((data) => {
|
|
165
|
+
if (!alive) return
|
|
166
|
+
const list = data.workspaces || []
|
|
167
|
+
setWorkspaces(list)
|
|
168
|
+
const remembered = window.localStorage.getItem(STORE_KEY) || ''
|
|
169
|
+
const preferred = list.find((w) => w.id === remembered) || list.find((w) => w.hasNotes) || list[0]
|
|
170
|
+
setSelected(preferred ? preferred.id : '')
|
|
171
|
+
if (!preferred) setState('没有可用工作区')
|
|
172
|
+
})
|
|
173
|
+
.catch((error) => {
|
|
174
|
+
if (!alive) return
|
|
175
|
+
setFailed(true)
|
|
176
|
+
setState('读取工作区失败:' + (error.message || error))
|
|
177
|
+
})
|
|
178
|
+
return () => {
|
|
179
|
+
alive = false
|
|
180
|
+
}
|
|
181
|
+
}, [])
|
|
182
|
+
|
|
183
|
+
const load = useCallback((workspaceId) => {
|
|
184
|
+
if (!workspaceId) return
|
|
185
|
+
if (abortRef.current) abortRef.current.abort()
|
|
186
|
+
const controller = new AbortController()
|
|
187
|
+
abortRef.current = controller
|
|
188
|
+
setBusy(true)
|
|
189
|
+
setFailed(false)
|
|
190
|
+
setState('正在扫描 .agents/notes…')
|
|
191
|
+
fetchJson(`${API}/scan?workspace=${encodeURIComponent(workspaceId)}`, controller.signal)
|
|
192
|
+
.then((data) => {
|
|
193
|
+
const count = boardRef.current ? boardRef.current.ingest(data.files || []) : 0
|
|
194
|
+
const label = (data.workspace && data.workspace.title) || '工作区'
|
|
195
|
+
setBusy(false)
|
|
196
|
+
const cut = data.truncated ? `(已达安全阀 ${data.safetyLimit} 篇,结果被截断)` : ''
|
|
197
|
+
setState(data.exists ? `${label} · ${count} 篇笔记${cut}` : `${label} 下还没有 .agents/notes`)
|
|
198
|
+
setFailed(!!data.truncated)
|
|
199
|
+
const shadow = hostRef.current && hostRef.current.shadowRoot
|
|
200
|
+
const beacon = shadow && shadow.getElementById('beacon-status')
|
|
201
|
+
const radar = shadow && shadow.getElementById('radar-indicator')
|
|
202
|
+
if (beacon) beacon.textContent = data.exists ? `宿主直连 · ${count} 篇` : '该工作区没有笔记'
|
|
203
|
+
if (radar) radar.className = data.exists ? 'radar-dot active' : 'radar-dot'
|
|
204
|
+
})
|
|
205
|
+
.catch((error) => {
|
|
206
|
+
if (error && error.name === 'AbortError') return
|
|
207
|
+
setBusy(false)
|
|
208
|
+
setFailed(true)
|
|
209
|
+
setState('扫描失败:' + (error.message || error))
|
|
210
|
+
})
|
|
211
|
+
}, [])
|
|
212
|
+
|
|
213
|
+
useEffect(() => {
|
|
214
|
+
if (!selected) return
|
|
215
|
+
window.localStorage.setItem(STORE_KEY, selected)
|
|
216
|
+
load(selected)
|
|
217
|
+
}, [selected, load])
|
|
218
|
+
|
|
219
|
+
useEffect(() => () => abortRef.current && abortRef.current.abort(), [])
|
|
220
|
+
|
|
221
|
+
return h(
|
|
222
|
+
'div',
|
|
223
|
+
{ className: 'nbx-shell' },
|
|
224
|
+
h('style', null, SHELL_CSS),
|
|
225
|
+
h(
|
|
226
|
+
'div',
|
|
227
|
+
{ className: 'nbx-bar' },
|
|
228
|
+
h('span', { className: 'nbx-title' }, '笔记看板'),
|
|
229
|
+
h(
|
|
230
|
+
'select',
|
|
231
|
+
{
|
|
232
|
+
className: 'nbx-select',
|
|
233
|
+
value: selected,
|
|
234
|
+
onChange: (event) => setSelected(event.target.value),
|
|
235
|
+
},
|
|
236
|
+
workspaces.length
|
|
237
|
+
? workspaces.map((w) =>
|
|
238
|
+
h('option', { key: w.id, value: w.id }, `${w.title}${w.hasNotes ? '' : '(无笔记)'}`),
|
|
239
|
+
)
|
|
240
|
+
: h('option', { value: '' }, '没有可用工作区'),
|
|
241
|
+
),
|
|
242
|
+
h('button', {
|
|
243
|
+
className: 'nbx-btn',
|
|
244
|
+
disabled: !selected || busy,
|
|
245
|
+
onClick: () => load(selected),
|
|
246
|
+
}, busy ? '刷新中…' : '刷新'),
|
|
247
|
+
h('span', { className: 'nbx-grow' }),
|
|
248
|
+
h('span', { className: 'nbx-state' + (failed ? ' nbx-err' : '') }, state),
|
|
249
|
+
),
|
|
250
|
+
h('div', { className: 'nbx-host', ref: hostRef }),
|
|
251
|
+
)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/* ── 侧栏图标 ─────────────────────────────────────────── */
|
|
255
|
+
|
|
256
|
+
function PanelIcon(props) {
|
|
257
|
+
const size = (props && props.size) || 20
|
|
258
|
+
const active = !!(props && props.active)
|
|
259
|
+
return h(
|
|
260
|
+
'svg',
|
|
261
|
+
{
|
|
262
|
+
width: size,
|
|
263
|
+
height: size,
|
|
264
|
+
viewBox: '0 0 24 24',
|
|
265
|
+
fill: 'none',
|
|
266
|
+
stroke: 'currentColor',
|
|
267
|
+
strokeWidth: active ? 2 : 1.7,
|
|
268
|
+
strokeLinecap: 'round',
|
|
269
|
+
strokeLinejoin: 'round',
|
|
270
|
+
'aria-hidden': true,
|
|
271
|
+
style: { display: 'block' },
|
|
272
|
+
},
|
|
273
|
+
h('path', { d: 'M6 3.5h9.5L19 7v13.5H6z' }),
|
|
274
|
+
h('path', { d: 'M15 3.5V7h4' }),
|
|
275
|
+
h('path', { d: 'M9 11h7M9 14.5h7M9 18h4' }),
|
|
276
|
+
)
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return {
|
|
280
|
+
inject: ['slots'],
|
|
281
|
+
/* 测试用出口:scripts/test-board.mjs 直接拿它跑桩 DOM 冒烟 */
|
|
282
|
+
mountBoard,
|
|
283
|
+
apply(ctx) {
|
|
284
|
+
ctx.slots.inject('main', () => ctx.slots.register({ name: 'main', key: PANEL_ID }, NotesBoard))
|
|
285
|
+
ctx.slots.inject('sidebar.panellist', () =>
|
|
286
|
+
ctx.slots.register(
|
|
287
|
+
{ name: 'sidebar.panellist', id: PANEL_ID, order: 20, label: '笔记看板' },
|
|
288
|
+
PanelIcon,
|
|
289
|
+
),
|
|
290
|
+
)
|
|
291
|
+
},
|
|
292
|
+
}
|
|
293
|
+
},
|
|
294
|
+
})
|