mdboard 1.1.0 → 1.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/bin.js +83 -10
- package/build.js +44 -0
- package/index.html +2344 -134
- package/package.json +8 -6
- package/src/cli/cli.js +362 -0
- package/src/cli/init.js +123 -0
- package/src/cli/status.js +150 -0
- package/src/cli/sync.js +194 -0
- package/src/cli/theme.js +142 -0
- package/src/client/app.js +266 -0
- package/src/client/board.js +157 -0
- package/src/client/core.js +331 -0
- package/src/client/editor.js +318 -0
- package/src/client/history.js +137 -0
- package/src/client/metrics.js +38 -0
- package/src/client/milestones.js +77 -0
- package/src/client/notes.js +183 -0
- package/src/client/overview.js +104 -0
- package/src/client/panel.js +637 -0
- package/src/client/styles.css +471 -0
- package/src/client/table.js +111 -0
- package/src/client/template.html +144 -0
- package/src/client/themes.js +261 -0
- package/src/client/workspace.js +164 -0
- package/src/core/agent-scanner.js +260 -0
- package/{config.js → src/core/config.js} +27 -2
- package/src/core/history.js +130 -0
- package/src/core/scanner.js +611 -0
- package/src/core/workspace.js +220 -0
- package/src/core/yaml.js +133 -0
- package/src/server/api.js +893 -0
- package/src/server/server.js +511 -0
- package/src/server/watcher.js +162 -0
- package/init.js +0 -100
- package/server.js +0 -830
package/index.html
CHANGED
|
@@ -25,9 +25,23 @@ a{color:var(--accent);text-decoration:none}
|
|
|
25
25
|
|
|
26
26
|
/* ── Layout ──────────────────────────────────────────────── */
|
|
27
27
|
.app{display:flex;height:100vh;overflow:hidden}
|
|
28
|
+
.source-rail{width:56px;background:var(--bg);border-right:1px solid var(--border);display:flex;flex-direction:column;align-items:center;padding:12px 0;gap:8px;flex-shrink:0;overflow-y:auto}
|
|
29
|
+
.source-rail:empty{display:none}
|
|
30
|
+
.rail-icon{width:40px;height:40px;border-radius:12px;display:flex;align-items:center;justify-content:center;font-size:18px;cursor:pointer;transition:all .15s;position:relative;color:var(--text2);background:var(--surface2);border:2px solid transparent;flex-shrink:0}
|
|
31
|
+
.rail-icon:hover{border-radius:10px;background:var(--surface);color:var(--text)}
|
|
32
|
+
.rail-icon.active{border-color:var(--accent);border-radius:10px;color:var(--text)}
|
|
33
|
+
.rail-icon.active::before{content:'';position:absolute;left:-14px;top:50%;transform:translateY(-50%);width:4px;height:24px;border-radius:0 4px 4px 0;background:var(--accent)}
|
|
34
|
+
.rail-icon img{width:24px;height:24px;border-radius:4px;object-fit:contain}
|
|
35
|
+
.rail-icon svg{width:20px;height:20px}
|
|
36
|
+
.rail-divider{width:24px;height:2px;background:var(--border);border-radius:1px;flex-shrink:0}
|
|
37
|
+
.rail-icon[data-tooltip]{position:relative}
|
|
38
|
+
.rail-icon[data-tooltip]:hover::after{content:attr(data-tooltip);position:absolute;left:calc(100% + 10px);top:50%;transform:translateY(-50%);background:var(--surface);border:1px solid var(--border);color:var(--text);padding:4px 10px;border-radius:var(--radius-sm);font-size:12px;font-weight:500;white-space:nowrap;z-index:50;pointer-events:none;box-shadow:0 4px 16px rgba(0,0,0,.3)}
|
|
28
39
|
.sidebar{width:var(--sidebar-w);background:var(--surface);border-right:1px solid var(--border);display:flex;flex-direction:column;flex-shrink:0}
|
|
29
|
-
.sidebar-logo{padding:20px 16px 16px;font-
|
|
30
|
-
.sidebar-logo
|
|
40
|
+
.sidebar-logo{padding:20px 16px 16px;font-size:14px;font-weight:700;color:var(--text);cursor:pointer;display:flex;align-items:center;gap:10px;transition:opacity .15s}
|
|
41
|
+
.sidebar-logo:hover{opacity:.8}
|
|
42
|
+
.sidebar-logo-icon{width:28px;height:28px;border-radius:var(--radius-sm);display:flex;align-items:center;justify-content:center;font-size:16px;color:var(--accent);background:var(--accent-dim);flex-shrink:0}
|
|
43
|
+
.sidebar-logo img{width:28px;height:28px;border-radius:var(--radius-sm);object-fit:contain}
|
|
44
|
+
.sidebar-logo-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
31
45
|
#sidebar-nav{flex:1;padding:8px}
|
|
32
46
|
#sidebar-nav a{display:flex;align-items:center;gap:10px;padding:8px 12px;border-radius:var(--radius-sm);color:var(--text2);font-size:13px;font-weight:500;transition:all .15s;border-left:2px solid transparent;margin-bottom:2px}
|
|
33
47
|
#sidebar-nav a:hover{color:var(--text);background:var(--surface2)}
|
|
@@ -39,9 +53,6 @@ a{color:var(--accent);text-decoration:none}
|
|
|
39
53
|
|
|
40
54
|
/* ── Header ──────────────────────────────────────────────── */
|
|
41
55
|
.header{padding:16px 24px;border-bottom:1px solid var(--border);background:var(--surface);display:flex;align-items:center;gap:24px;flex-wrap:wrap}
|
|
42
|
-
.header-title{flex-shrink:0}
|
|
43
|
-
.header-title h1{font-size:18px;font-weight:700;line-height:1.3}
|
|
44
|
-
.header-title p{font-size:12px;color:var(--text2);margin-top:2px}
|
|
45
56
|
.header-section{display:flex;flex-direction:column;gap:4px;min-width:120px}
|
|
46
57
|
.header-section-label{font-size:10px;text-transform:uppercase;letter-spacing:.05em;color:var(--text3);font-weight:600}
|
|
47
58
|
.header-section-value{font-size:13px;font-weight:600}
|
|
@@ -60,6 +71,8 @@ a{color:var(--accent);text-decoration:none}
|
|
|
60
71
|
.content{flex:1;overflow-y:auto;padding:20px 24px}
|
|
61
72
|
.view{display:none}
|
|
62
73
|
.view.active{display:block}
|
|
74
|
+
#view-notes.active{display:flex;flex-direction:column;margin:-20px -24px;height:calc(100% + 40px)}
|
|
75
|
+
#view-notes #notes-container{flex:1;overflow:hidden}
|
|
63
76
|
|
|
64
77
|
/* ── Icons ───────────────────────────────────────────────── */
|
|
65
78
|
.icon{width:16px;height:16px;flex-shrink:0;vertical-align:middle;display:inline-block}
|
|
@@ -163,26 +176,60 @@ th.sorted .sort-arrow{opacity:1;color:var(--accent)}
|
|
|
163
176
|
.health-label{flex:1;font-size:13px;color:var(--text2)}
|
|
164
177
|
.health-val{font-family:var(--mono);font-size:13px;font-weight:600}
|
|
165
178
|
|
|
166
|
-
/* ── Detail Panel
|
|
179
|
+
/* ── Detail Panel — Notion-style ─────────────────────────── */
|
|
167
180
|
.panel-overlay{position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:99;opacity:0;pointer-events:none;transition:opacity .2s}
|
|
168
181
|
.panel-overlay.open{opacity:1;pointer-events:auto}
|
|
169
|
-
.detail-panel{position:fixed;top:0;right:0;width:
|
|
182
|
+
.detail-panel{position:fixed;top:0;right:0;width:600px;max-width:92vw;height:100vh;background:var(--bg);border-left:1px solid var(--border);z-index:100;transform:translateX(100%);transition:transform .25s ease;display:flex;flex-direction:column;overflow:hidden}
|
|
170
183
|
.detail-panel.open{transform:translateX(0)}
|
|
171
|
-
.panel-header{padding:
|
|
184
|
+
.panel-header{padding:12px 20px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:12px;flex-shrink:0;background:var(--surface)}
|
|
172
185
|
.panel-type{font-size:10px;padding:3px 8px;border-radius:var(--radius-sm);background:var(--accent-dim);color:var(--accent);font-weight:700;text-transform:uppercase;letter-spacing:.04em}
|
|
173
186
|
.panel-item-id{font-family:var(--mono);font-size:13px;color:var(--text2)}
|
|
174
187
|
.panel-close{margin-left:auto;background:none;border:none;color:var(--text2);cursor:pointer;font-size:18px;padding:4px 8px;border-radius:var(--radius-sm);line-height:1}
|
|
175
188
|
.panel-close:hover{background:var(--surface2);color:var(--text)}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
.panel-
|
|
179
|
-
.panel-
|
|
180
|
-
.panel-
|
|
181
|
-
.panel-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
.panel-
|
|
185
|
-
.
|
|
189
|
+
|
|
190
|
+
/* Panel title — large Notion-style */
|
|
191
|
+
.panel-title-wrap{padding:20px 24px 8px;flex-shrink:0}
|
|
192
|
+
.panel-title-input{width:100%;font-size:28px;font-weight:700;background:transparent;border:none;color:var(--text);font-family:var(--font);outline:none;padding:0;line-height:1.3}
|
|
193
|
+
.panel-title-input::placeholder{color:var(--text3)}
|
|
194
|
+
.panel-title-input:disabled{opacity:.7}
|
|
195
|
+
|
|
196
|
+
/* Panel properties — compact Notion-style rows */
|
|
197
|
+
.panel-properties{padding:4px 24px 8px;flex-shrink:0;border-bottom:1px solid var(--border)}
|
|
198
|
+
.prop-row{display:flex;align-items:center;padding:4px 0;min-height:32px}
|
|
199
|
+
.prop-label{width:100px;flex-shrink:0;font-size:12px;color:var(--text3);font-weight:500}
|
|
200
|
+
.prop-value{flex:1;display:flex;align-items:center;gap:6px}
|
|
201
|
+
.prop-value select,.prop-value input[type="text"],.prop-value input[type="number"],.prop-value input[type="date"]{background:transparent;border:1px solid transparent;color:var(--text);padding:4px 8px;border-radius:var(--radius-sm);font-family:var(--font);font-size:13px;width:100%}
|
|
202
|
+
.prop-value select:hover,.prop-value input:hover{border-color:var(--border)}
|
|
203
|
+
.prop-value select:focus,.prop-value input:focus{outline:none;border-color:var(--accent);background:var(--surface)}
|
|
204
|
+
.prop-value select:disabled,.prop-value input:disabled{opacity:.6;cursor:default}
|
|
205
|
+
.prop-value select:disabled:hover,.prop-value input:disabled:hover{border-color:transparent}
|
|
206
|
+
.prop-text{font-size:13px;color:var(--text2);padding:4px 8px}
|
|
207
|
+
|
|
208
|
+
/* AI Tag Input */
|
|
209
|
+
.prop-row-divider{margin-top:4px;border-top:1px solid var(--border);padding-top:8px}
|
|
210
|
+
.prop-row-toggle{cursor:pointer;user-select:none}
|
|
211
|
+
.prop-row-toggle:hover{background:var(--surface2);border-radius:var(--radius-sm)}
|
|
212
|
+
.ai-toggle-arrow{display:inline-block;font-size:10px;width:14px;transition:transform .15s}
|
|
213
|
+
.ai-tag-input{flex:1;display:flex;flex-wrap:wrap;align-items:center;gap:4px;padding:2px 4px;min-height:28px;border:1px solid transparent;border-radius:var(--radius-sm);cursor:text;position:relative;transition:border-color .15s}
|
|
214
|
+
.ai-tag-input:hover{border-color:var(--border)}
|
|
215
|
+
.ai-tag-input:focus-within{border-color:var(--accent);background:var(--surface)}
|
|
216
|
+
.ai-tag{display:inline-flex;align-items:center;gap:2px;padding:1px 8px;border-radius:10px;font-size:11px;font-weight:500;white-space:nowrap;background:var(--surface2);color:var(--text2);border:1px solid var(--border)}
|
|
217
|
+
.ai-tag-own{background:var(--accent-dim);color:var(--accent);border-color:var(--accent)}
|
|
218
|
+
.ai-tag-inherited{border-style:dashed;opacity:.6}
|
|
219
|
+
.ai-tag-remove{background:none;border:none;color:inherit;cursor:pointer;font-size:13px;line-height:1;padding:0 0 0 2px;opacity:.6}
|
|
220
|
+
.ai-tag-remove:hover{opacity:1}
|
|
221
|
+
.ai-tag-text{border:none!important;background:transparent!important;padding:2px 4px!important;font-size:12px;min-width:60px;flex:1;outline:none;color:var(--text)}
|
|
222
|
+
.ai-tag-text::placeholder{color:var(--text3);font-size:11px}
|
|
223
|
+
.ai-autocomplete{position:absolute;top:100%;left:0;right:0;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-sm);box-shadow:0 8px 24px rgba(0,0,0,.4);z-index:50;max-height:160px;overflow-y:auto;margin-top:2px;display:none}
|
|
224
|
+
.ai-autocomplete.open{display:block}
|
|
225
|
+
.ai-autocomplete-item{padding:6px 10px;font-size:12px;color:var(--text2);cursor:pointer;transition:background .1s}
|
|
226
|
+
.ai-autocomplete-item:hover,.ai-autocomplete-item.active{background:var(--accent-dim);color:var(--text)}
|
|
227
|
+
|
|
228
|
+
/* Panel content area — fills remaining space */
|
|
229
|
+
.panel-content-area{flex:1;overflow-y:auto;padding:0}
|
|
230
|
+
.panel-editor-zone{padding:16px 24px;min-height:200px}
|
|
231
|
+
|
|
232
|
+
.panel-footer{padding:12px 20px;border-top:1px solid var(--border);display:flex;gap:8px;align-items:center;flex-shrink:0}
|
|
186
233
|
.btn{padding:8px 16px;border-radius:var(--radius-sm);font-size:13px;font-weight:500;cursor:pointer;border:1px solid var(--border);background:var(--surface2);color:var(--text);transition:all .15s;font-family:var(--font)}
|
|
187
234
|
.btn:hover{background:var(--border)}
|
|
188
235
|
.btn-primary{background:var(--accent);border-color:var(--accent);color:#fff}
|
|
@@ -207,31 +254,244 @@ th.sorted .sort-arrow{opacity:1;color:var(--accent)}
|
|
|
207
254
|
.loading-container{padding:20px}
|
|
208
255
|
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.4}}
|
|
209
256
|
|
|
257
|
+
/* ── Workspace: Source Icon (reused in header chips) ──────── */
|
|
258
|
+
.source-icon{width:20px;height:20px;border-radius:var(--radius-sm);display:flex;align-items:center;justify-content:center;font-size:12px;flex-shrink:0;font-weight:700;line-height:1}
|
|
259
|
+
|
|
260
|
+
/* ── Link Chips ──────────────────────────────────────────── */
|
|
261
|
+
.link-chip{display:inline-flex;align-items:center;gap:4px;padding:2px 8px;border-radius:10px;font-size:11px;font-weight:500;cursor:pointer;border:1px solid var(--border);background:var(--surface2);color:var(--text2);transition:all .15s;white-space:nowrap}
|
|
262
|
+
.link-chip:hover{background:var(--border);color:var(--text)}
|
|
263
|
+
.link-chip-dot{width:6px;height:6px;border-radius:50%;flex-shrink:0}
|
|
264
|
+
.link-chips{display:flex;flex-wrap:wrap;gap:4px;margin-top:4px}
|
|
265
|
+
|
|
266
|
+
/* ── CRUD Buttons ────────────────────────────────────────── */
|
|
267
|
+
.btn-sm{padding:5px 12px;font-size:12px;border-radius:var(--radius-sm)}
|
|
268
|
+
.btn-create{background:var(--accent);border-color:var(--accent);color:#fff;font-weight:600}
|
|
269
|
+
.btn-create:hover{opacity:.9}
|
|
270
|
+
.card-readonly{opacity:.75;cursor:default}
|
|
271
|
+
.card-readonly:hover{border-color:var(--border);background:var(--bg)}
|
|
272
|
+
|
|
273
|
+
/* ── Overview: Tracked Milestones ────────────────────────── */
|
|
274
|
+
.tracked-ms{margin-top:12px;padding:12px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm)}
|
|
275
|
+
.tracked-ms-header{font-size:11px;text-transform:uppercase;letter-spacing:.04em;color:var(--text3);font-weight:600;margin-bottom:8px}
|
|
276
|
+
.tracked-ms-item{display:flex;align-items:center;gap:8px;padding:4px 0}
|
|
277
|
+
.tracked-ms-item .dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}
|
|
278
|
+
.tracked-ms-item .progress{flex:1;max-width:120px}
|
|
279
|
+
.tracked-ms-item span{font-size:12px}
|
|
280
|
+
.tracked-ms-pct{font-family:var(--mono);font-weight:600;font-size:12px;min-width:36px;text-align:right}
|
|
281
|
+
|
|
282
|
+
/* ── Notes View — Notion-style ───────────────────────────── */
|
|
283
|
+
.notes-layout{display:flex;height:100%;gap:0}
|
|
284
|
+
.notes-sidebar{width:260px;flex-shrink:0;border-right:1px solid var(--border);display:flex;flex-direction:column;background:var(--surface)}
|
|
285
|
+
.notes-toolbar{padding:10px 12px;display:flex;gap:8px;border-bottom:1px solid var(--border)}
|
|
286
|
+
.notes-toolbar input{flex:1;background:var(--bg);border:1px solid var(--border);color:var(--text);padding:6px 10px;border-radius:var(--radius-sm);font-size:12px;font-family:var(--font)}
|
|
287
|
+
.notes-toolbar input:focus{outline:none;border-color:var(--accent)}
|
|
288
|
+
.notes-list{flex:1;overflow-y:auto}
|
|
289
|
+
.notes-list-item{padding:10px 16px;border-bottom:1px solid var(--border);cursor:pointer;transition:background .15s}
|
|
290
|
+
.notes-list-item:hover{background:var(--surface2)}
|
|
291
|
+
.notes-list-item.active{background:var(--accent-dim);border-left:3px solid var(--accent)}
|
|
292
|
+
.notes-list-title{font-size:13px;font-weight:600;margin-bottom:2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
293
|
+
.notes-list-date{font-size:11px;color:var(--text3);font-family:var(--mono)}
|
|
294
|
+
.notes-list-empty{padding:40px 20px;text-align:center;color:var(--text3);font-size:13px}
|
|
295
|
+
.notes-editor{flex:1;display:flex;flex-direction:column;overflow:hidden;background:var(--bg)}
|
|
296
|
+
.notes-header-bar{padding:32px 48px 0;flex-shrink:0}
|
|
297
|
+
.notes-title-input{width:100%;font-size:32px;font-weight:700;background:transparent;border:none;color:var(--text);font-family:var(--font);outline:none;padding:0;margin-bottom:4px;line-height:1.3}
|
|
298
|
+
.notes-title-input::placeholder{color:var(--text3)}
|
|
299
|
+
.notes-meta{font-size:11px;color:var(--text3);font-family:var(--mono)}
|
|
300
|
+
.notes-editorjs-wrap{flex:1;overflow-y:auto;padding:16px 40px 40px}
|
|
301
|
+
.notes-editor-footer{padding:10px 24px;border-top:1px solid var(--border);display:flex;align-items:center;gap:8px;flex-shrink:0;background:var(--surface)}
|
|
302
|
+
.notes-empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;color:var(--text3);font-size:14px}
|
|
303
|
+
.notes-empty-state kbd{display:inline-block;padding:2px 6px;background:var(--surface2);border:1px solid var(--border);border-radius:3px;font-family:var(--mono);font-size:12px}
|
|
304
|
+
.notes-editor-loading{display:flex;align-items:center;justify-content:center;height:100%;color:var(--text3)}
|
|
305
|
+
|
|
306
|
+
/* ── Editor.js — Dark theme overrides ────────────────────── */
|
|
307
|
+
.codex-editor{color:var(--text);font-family:var(--font)}
|
|
308
|
+
.codex-editor .ce-block__content{max-width:100%;margin:0}
|
|
309
|
+
.codex-editor .ce-toolbar__content{max-width:100%}
|
|
310
|
+
.codex-editor .ce-toolbar__plus{color:var(--text3);background:transparent;border:1px solid var(--border);width:26px;height:26px}
|
|
311
|
+
.codex-editor .ce-toolbar__plus:hover{background:var(--surface2);color:var(--text)}
|
|
312
|
+
.codex-editor .ce-toolbar__plus svg{color:inherit}
|
|
313
|
+
.codex-editor .ce-toolbar__settings-btn{color:var(--text3);background:transparent;width:26px;height:26px}
|
|
314
|
+
.codex-editor .ce-toolbar__settings-btn:hover{background:var(--surface2);color:var(--text)}
|
|
315
|
+
.codex-editor .ce-toolbar__actions{background:transparent}
|
|
316
|
+
/* Inline toolbar */
|
|
317
|
+
.codex-editor .ce-inline-toolbar{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);box-shadow:0 8px 24px rgba(0,0,0,.4)}
|
|
318
|
+
.codex-editor .ce-inline-toolbar__buttons{color:var(--text)}
|
|
319
|
+
.codex-editor .ce-inline-tool{color:var(--text2)}
|
|
320
|
+
.codex-editor .ce-inline-tool:hover{color:var(--text);background:var(--surface2)}
|
|
321
|
+
.codex-editor .ce-inline-tool--active{color:var(--accent)}
|
|
322
|
+
.codex-editor .ce-inline-tool-input{background:var(--bg);color:var(--text);border:none;border-top:1px solid var(--border)}
|
|
323
|
+
/* Conversion toolbar */
|
|
324
|
+
.codex-editor .ce-conversion-toolbar{background:var(--surface);border:1px solid var(--border);box-shadow:0 8px 24px rgba(0,0,0,.4)}
|
|
325
|
+
.codex-editor .ce-conversion-tool{color:var(--text2)}
|
|
326
|
+
.codex-editor .ce-conversion-tool:hover{background:var(--surface2);color:var(--text)}
|
|
327
|
+
.codex-editor .ce-conversion-tool--active{color:var(--accent)}
|
|
328
|
+
.codex-editor .ce-conversion-tool__icon{background:var(--surface2);color:var(--text2)}
|
|
329
|
+
/* Settings panel */
|
|
330
|
+
.codex-editor .ce-settings{background:var(--surface);border:1px solid var(--border);box-shadow:0 8px 24px rgba(0,0,0,.4)}
|
|
331
|
+
.codex-editor .cdx-settings-button{color:var(--text2)}
|
|
332
|
+
.codex-editor .cdx-settings-button:hover{background:var(--surface2);color:var(--text)}
|
|
333
|
+
.codex-editor .cdx-settings-button--active{color:var(--accent)}
|
|
334
|
+
/* Popover (slash commands menu) */
|
|
335
|
+
.ce-popover{background:var(--surface)!important;border:1px solid var(--border)!important;box-shadow:0 8px 24px rgba(0,0,0,.4)!important;color:var(--text)}
|
|
336
|
+
.ce-popover__container{background:var(--surface)!important;border:1px solid var(--border)!important;box-shadow:0 8px 24px rgba(0,0,0,.4)!important}
|
|
337
|
+
.ce-popover-item{color:var(--text2)!important}
|
|
338
|
+
.ce-popover-item:hover{background:var(--surface2)!important;color:var(--text)!important}
|
|
339
|
+
.ce-popover-item--focused{background:var(--surface2)!important;color:var(--text)!important}
|
|
340
|
+
.ce-popover-item__icon{background:var(--surface2)!important;color:var(--text2)!important;border:none!important}
|
|
341
|
+
.ce-popover-item:hover .ce-popover-item__icon{color:var(--text)!important}
|
|
342
|
+
.ce-popover-item__title{color:var(--text)!important}
|
|
343
|
+
.ce-popover-item__secondary-title{color:var(--text3)!important}
|
|
344
|
+
.ce-popover__search{background:transparent!important;border-bottom:1px solid var(--border)!important}
|
|
345
|
+
.ce-popover__search input,.cdx-search-field__input{color:var(--text)!important;background:var(--bg)!important;border:1px solid var(--border)!important;border-radius:var(--radius-sm)!important}
|
|
346
|
+
.cdx-search-field{background:transparent!important}
|
|
347
|
+
.ce-popover__nothing-found-message{color:var(--text3)!important}
|
|
348
|
+
.ce-popover__items::-webkit-scrollbar{width:4px}
|
|
349
|
+
.ce-popover__items::-webkit-scrollbar-track{background:transparent}
|
|
350
|
+
.ce-popover__items::-webkit-scrollbar-thumb{background:var(--border2);border-radius:2px}
|
|
351
|
+
.ce-block{padding:3px 0}
|
|
352
|
+
.ce-paragraph{line-height:1.65;font-size:15px}
|
|
353
|
+
.ce-paragraph[data-placeholder]:empty::before{color:var(--text3)!important}
|
|
354
|
+
.ce-header{font-weight:700}
|
|
355
|
+
h1.ce-header{font-size:28px;line-height:1.2;margin:8px 0 2px}
|
|
356
|
+
h2.ce-header{font-size:22px;line-height:1.25;margin:6px 0 2px}
|
|
357
|
+
h3.ce-header{font-size:18px;line-height:1.3;margin:4px 0 2px}
|
|
358
|
+
h4.ce-header{font-size:15px;line-height:1.4}
|
|
359
|
+
.ce-code__textarea{background:var(--surface)!important;color:var(--text)!important;border:none!important;border-radius:var(--radius-sm)!important;font-family:var(--mono)!important;font-size:13px!important;padding:16px!important;line-height:1.5!important}
|
|
360
|
+
.cdx-list{font-size:15px;line-height:1.65}
|
|
361
|
+
.cdx-list__item{color:var(--text)}
|
|
362
|
+
/* Checklist in list v2 */
|
|
363
|
+
.cdx-checklist{font-size:15px}
|
|
364
|
+
.cdx-checklist__item-checkbox{background:var(--surface2);border:2px solid var(--border2);border-radius:3px}
|
|
365
|
+
.cdx-checklist__item--checked .cdx-checklist__item-checkbox{background:var(--accent);border-color:var(--accent)}
|
|
366
|
+
.cdx-checklist__item--checked .cdx-checklist__item-text{color:var(--text2);text-decoration:line-through}
|
|
367
|
+
/* List v2 checklist style */
|
|
368
|
+
.cdx-list--checklist .cdx-list__item-checkbox{background:var(--surface2);border-color:var(--border2)}
|
|
369
|
+
.cdx-list--checklist .cdx-list__item--checked .cdx-list__item-checkbox{background:var(--accent);border-color:var(--accent)}
|
|
370
|
+
.cdx-list--checklist .cdx-list__item--checked .cdx-list__item-content{color:var(--text2);text-decoration:line-through}
|
|
371
|
+
.cdx-quote{border-left:3px solid var(--accent);padding-left:16px;font-style:italic;color:var(--text2);font-size:15px;line-height:1.65}
|
|
372
|
+
.cdx-quote [contenteditable]{outline:none}
|
|
373
|
+
.cdx-quote__caption{display:none!important}
|
|
374
|
+
.ce-delimiter{line-height:1;padding:12px 0;color:var(--text3);letter-spacing:8px}
|
|
375
|
+
.cdx-marker{background:rgba(91,110,245,.2);padding:2px 0}
|
|
376
|
+
|
|
377
|
+
/* Fallback textarea (when CDN not available) */
|
|
378
|
+
.editor-fallback-textarea{width:100%;min-height:200px;resize:vertical;background:transparent;border:none;color:var(--text);padding:0;font-family:var(--font);font-size:15px;line-height:1.65;outline:none}
|
|
379
|
+
|
|
380
|
+
/* ── Markdown Rendered (readonly) ────────────────────────── */
|
|
381
|
+
.md-rendered{font-size:15px;line-height:1.65;color:var(--text)}
|
|
382
|
+
.md-rendered h1,.md-rendered h2,.md-rendered h3{font-weight:700;margin:16px 0 6px}
|
|
383
|
+
.md-rendered h1{font-size:28px;line-height:1.2}
|
|
384
|
+
.md-rendered h2{font-size:22px;line-height:1.25}
|
|
385
|
+
.md-rendered h3{font-size:18px;line-height:1.3}
|
|
386
|
+
.md-rendered p{margin-bottom:8px}
|
|
387
|
+
.md-rendered ul,.md-rendered ol{padding-left:24px;margin-bottom:8px}
|
|
388
|
+
.md-rendered li{margin-bottom:4px}
|
|
389
|
+
.md-rendered code{background:var(--surface2);padding:2px 6px;border-radius:var(--radius-sm);font-family:var(--mono);font-size:13px}
|
|
390
|
+
.md-rendered pre{background:var(--surface);padding:16px;border-radius:var(--radius-sm);overflow-x:auto;margin-bottom:12px}
|
|
391
|
+
.md-rendered pre code{background:transparent;padding:0}
|
|
392
|
+
.md-rendered blockquote{border-left:3px solid var(--accent);padding-left:16px;color:var(--text2);margin:8px 0;font-style:italic}
|
|
393
|
+
.md-rendered hr{border:none;border-top:1px solid var(--border);margin:20px 0}
|
|
394
|
+
.md-rendered a{color:var(--accent)}
|
|
395
|
+
.md-rendered .md-check{display:flex;align-items:center;gap:8px;padding:2px 0}
|
|
396
|
+
.md-rendered .md-check-box{width:16px;height:16px;border:2px solid var(--border2);border-radius:3px;display:inline-flex;align-items:center;justify-content:center;font-size:11px;flex-shrink:0;color:var(--accent)}
|
|
397
|
+
.md-rendered .md-check-box.checked{background:var(--accent);border-color:var(--accent);color:#fff}
|
|
398
|
+
|
|
399
|
+
/* ── Settings Panel ──────────────────────────────────────── */
|
|
400
|
+
.sidebar-settings{padding:8px}
|
|
401
|
+
.sidebar-settings a{display:flex;align-items:center;gap:10px;padding:8px 12px;border-radius:var(--radius-sm);color:var(--text3);font-size:13px;font-weight:500;transition:all .15s;cursor:pointer}
|
|
402
|
+
.sidebar-settings a:hover{color:var(--text);background:var(--surface2)}
|
|
403
|
+
.sidebar-settings svg{width:16px;height:16px;flex-shrink:0}
|
|
404
|
+
.settings-overlay{position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:199;opacity:0;pointer-events:none;transition:opacity .2s}
|
|
405
|
+
.settings-overlay.open{opacity:1;pointer-events:auto}
|
|
406
|
+
.settings-panel{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%) scale(.95);width:680px;max-width:92vw;max-height:85vh;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius);z-index:200;display:flex;flex-direction:column;opacity:0;pointer-events:none;transition:all .2s ease;box-shadow:0 16px 48px rgba(0,0,0,.4)}
|
|
407
|
+
.settings-panel.open{opacity:1;pointer-events:auto;transform:translate(-50%,-50%) scale(1)}
|
|
408
|
+
.settings-header{padding:16px 20px;border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between;flex-shrink:0}
|
|
409
|
+
.settings-header h2{font-size:16px;font-weight:700}
|
|
410
|
+
.settings-content{flex:1;overflow-y:auto;padding:20px}
|
|
411
|
+
.settings-section h3{font-size:13px;font-weight:600;color:var(--text2);text-transform:uppercase;letter-spacing:.04em;margin-bottom:8px}
|
|
412
|
+
.settings-theme-info{font-size:12px;color:var(--text3);margin-bottom:12px}
|
|
413
|
+
.theme-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:10px}
|
|
414
|
+
.theme-card{background:var(--surface);border:2px solid var(--border);border-radius:var(--radius);padding:10px;cursor:pointer;transition:all .15s}
|
|
415
|
+
.theme-card:hover{border-color:var(--border2)}
|
|
416
|
+
.theme-card.active{border-color:var(--accent)}
|
|
417
|
+
.theme-card-preview{display:flex;gap:3px;height:32px;border-radius:var(--radius-sm);overflow:hidden;margin-bottom:8px}
|
|
418
|
+
.theme-card-preview span{flex:1}
|
|
419
|
+
.theme-card-name{font-size:12px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
420
|
+
.theme-card-type{font-size:10px;color:var(--text3);text-transform:uppercase;letter-spacing:.03em}
|
|
421
|
+
.settings-footer{padding:14px 20px;border-top:1px solid var(--border);display:flex;align-items:center;justify-content:space-between;flex-shrink:0;gap:12px}
|
|
422
|
+
.toggle-label{display:flex;align-items:center;gap:10px;cursor:pointer;font-size:13px;color:var(--text2);user-select:none}
|
|
423
|
+
.toggle-label input{display:none}
|
|
424
|
+
.toggle-switch{position:relative;width:36px;height:20px;background:var(--surface2);border:1px solid var(--border);border-radius:10px;transition:all .2s;flex-shrink:0}
|
|
425
|
+
.toggle-switch::after{content:'';position:absolute;top:2px;left:2px;width:14px;height:14px;background:var(--text3);border-radius:50%;transition:all .2s}
|
|
426
|
+
.toggle-label input:checked+.toggle-switch{background:var(--accent);border-color:var(--accent)}
|
|
427
|
+
.toggle-label input:checked+.toggle-switch::after{left:18px;background:#fff}
|
|
428
|
+
|
|
429
|
+
/* ── Rail Switch Icon ────────────────────────────────────── */
|
|
430
|
+
.rail-switch{background:transparent;color:var(--text3);border:1px dashed var(--border2)}
|
|
431
|
+
.rail-switch:hover{color:var(--text);border-color:var(--accent);background:var(--accent-dim);border-style:solid}
|
|
432
|
+
|
|
433
|
+
/* ── History Modal ──────────────────────────────────────── */
|
|
434
|
+
.history-overlay{position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:199;opacity:0;pointer-events:none;transition:opacity .2s}
|
|
435
|
+
.history-overlay.open{opacity:1;pointer-events:auto}
|
|
436
|
+
.history-modal{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%) scale(.95);width:560px;max-width:92vw;max-height:70vh;background:var(--bg);border:1px solid var(--border);border-radius:12px;z-index:200;display:flex;flex-direction:column;opacity:0;pointer-events:none;transition:all .2s ease;box-shadow:0 16px 48px rgba(0,0,0,.5)}
|
|
437
|
+
.history-modal.open{opacity:1;pointer-events:auto;transform:translate(-50%,-50%) scale(1)}
|
|
438
|
+
.history-header{padding:16px 20px;border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between;flex-shrink:0}
|
|
439
|
+
.history-header h2{font-size:16px;font-weight:700}
|
|
440
|
+
.history-body{flex:1;overflow-y:auto;padding:8px 0}
|
|
441
|
+
.history-loading,.history-empty{padding:40px 20px;text-align:center;color:var(--text3);font-size:13px}
|
|
442
|
+
.history-item{display:flex;align-items:center;gap:12px;padding:10px 20px;cursor:pointer;transition:background .15s}
|
|
443
|
+
.history-item:hover{background:var(--surface2)}
|
|
444
|
+
.history-item-current{background:var(--accent-dim)}
|
|
445
|
+
.history-item-current:hover{background:var(--accent-dim)}
|
|
446
|
+
.history-item-icon{width:36px;height:36px;border-radius:var(--radius);background:var(--surface2);display:flex;align-items:center;justify-content:center;font-size:16px;font-weight:700;color:var(--accent);flex-shrink:0}
|
|
447
|
+
.history-item-current .history-item-icon{background:var(--accent);color:#fff}
|
|
448
|
+
.history-item-info{flex:1;min-width:0}
|
|
449
|
+
.history-item-name{font-size:13px;font-weight:600;display:flex;align-items:center;gap:8px}
|
|
450
|
+
.history-item-path{font-size:11px;color:var(--text3);font-family:var(--mono);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
451
|
+
.history-item-time{font-size:11px;color:var(--text3);font-family:var(--mono);flex-shrink:0;white-space:nowrap}
|
|
452
|
+
.history-current-badge{display:inline-block;padding:1px 8px;border-radius:10px;font-size:10px;font-weight:600;background:var(--accent);color:#fff}
|
|
453
|
+
|
|
210
454
|
/* ── Responsive ──────────────────────────────────────────── */
|
|
211
455
|
@media(max-width:1024px){
|
|
456
|
+
.source-rail{width:48px;padding:8px 0;gap:6px}
|
|
457
|
+
.rail-icon{width:34px;height:34px;font-size:15px;border-radius:10px}
|
|
458
|
+
.rail-icon.active::before{left:-12px;height:18px}
|
|
212
459
|
.sidebar{width:56px}
|
|
213
|
-
.sidebar-logo
|
|
214
|
-
.sidebar-logo{padding:16px 12px;
|
|
460
|
+
.sidebar-logo-text,.sidebar-footer,#sidebar-nav a span{display:none}
|
|
461
|
+
.sidebar-logo{padding:16px 12px;justify-content:center}
|
|
215
462
|
#sidebar-nav a{justify-content:center;padding:10px}
|
|
216
463
|
.header{padding:12px 16px}
|
|
217
464
|
.content{padding:16px}
|
|
218
465
|
.detail-panel{width:100%;max-width:100%}
|
|
466
|
+
#view-notes.active{margin:-16px}
|
|
467
|
+
.notes-sidebar{width:200px}
|
|
468
|
+
.notes-header-bar{padding:24px 24px 0}
|
|
469
|
+
.notes-editorjs-wrap{padding:12px 16px 24px}
|
|
219
470
|
}
|
|
220
471
|
@media(max-width:768px){
|
|
221
472
|
#h-stats{flex-wrap:wrap;gap:12px}
|
|
222
473
|
.header{gap:12px}
|
|
223
474
|
.metrics-grid{grid-template-columns:1fr}
|
|
224
|
-
.
|
|
475
|
+
.notes-sidebar{display:none}
|
|
476
|
+
.notes-header-bar{padding:20px 16px 0}
|
|
477
|
+
.notes-editorjs-wrap{padding:8px 8px 24px}
|
|
225
478
|
}
|
|
226
479
|
</style>
|
|
227
480
|
<style id="dynamic-styles"></style>
|
|
481
|
+
<style id="theme-styles"></style>
|
|
228
482
|
<link id="custom-theme" rel="stylesheet" href="/mdboard.css">
|
|
229
483
|
</head>
|
|
230
484
|
<body>
|
|
231
485
|
<div class="app">
|
|
486
|
+
<!-- Source Rail (Discord/Slack style) -->
|
|
487
|
+
<nav class="source-rail" id="source-rail"></nav>
|
|
488
|
+
|
|
232
489
|
<!-- Sidebar -->
|
|
233
490
|
<aside class="sidebar">
|
|
234
|
-
<div class="sidebar-logo"
|
|
491
|
+
<div class="sidebar-logo" id="sidebar-logo">
|
|
492
|
+
<span class="sidebar-logo-icon" id="sidebar-logo-icon">■</span>
|
|
493
|
+
<span class="sidebar-logo-text" id="sidebar-logo-text">mdboard</span>
|
|
494
|
+
</div>
|
|
235
495
|
<nav id="sidebar-nav">
|
|
236
496
|
<a href="#board" data-view="board" class="active">
|
|
237
497
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/></svg>
|
|
@@ -249,17 +509,23 @@ th.sorted .sort-arrow{opacity:1;color:var(--accent)}
|
|
|
249
509
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M18 20V10M12 20V4M6 20v-6"/></svg>
|
|
250
510
|
<span>Metrics</span>
|
|
251
511
|
</a>
|
|
512
|
+
<a href="#notes" data-view="notes">
|
|
513
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><polyline points="10 9 9 9 8 9"/></svg>
|
|
514
|
+
<span>Notes</span>
|
|
515
|
+
</a>
|
|
252
516
|
</nav>
|
|
517
|
+
<div class="sidebar-settings" id="sidebar-settings">
|
|
518
|
+
<a href="#" id="settings-toggle">
|
|
519
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 01-2.83 2.83l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-4 0v-.09A1.65 1.65 0 009 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83-2.83l.06-.06A1.65 1.65 0 004.68 15a1.65 1.65 0 00-1.51-1H3a2 2 0 010-4h.09A1.65 1.65 0 004.6 9a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 012.83-2.83l.06.06A1.65 1.65 0 009 4.68a1.65 1.65 0 001-1.51V3a2 2 0 014 0v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 012.83 2.83l-.06.06A1.65 1.65 0 0019.4 9a1.65 1.65 0 001.51 1H21a2 2 0 010 4h-.09a1.65 1.65 0 00-1.51 1z"/></svg>
|
|
520
|
+
<span>Settings</span>
|
|
521
|
+
</a>
|
|
522
|
+
</div>
|
|
253
523
|
<div class="sidebar-footer">mdboard</div>
|
|
254
524
|
</aside>
|
|
255
525
|
|
|
256
526
|
<div class="main">
|
|
257
527
|
<!-- Header -->
|
|
258
528
|
<header class="header">
|
|
259
|
-
<div class="header-title">
|
|
260
|
-
<h1 id="h-project-name">Loading...</h1>
|
|
261
|
-
<p id="h-project-desc"></p>
|
|
262
|
-
</div>
|
|
263
529
|
<div class="header-section" id="h-milestone-wrap" style="display:none">
|
|
264
530
|
<span class="header-section-label">Milestone</span>
|
|
265
531
|
<span class="header-section-value" id="h-milestone-name"></span>
|
|
@@ -291,6 +557,12 @@ th.sorted .sort-arrow{opacity:1;color:var(--accent)}
|
|
|
291
557
|
<div id="view-metrics" class="view">
|
|
292
558
|
<div id="metrics-container"></div>
|
|
293
559
|
</div>
|
|
560
|
+
<div id="view-overview" class="view">
|
|
561
|
+
<div id="overview-container"></div>
|
|
562
|
+
</div>
|
|
563
|
+
<div id="view-notes" class="view">
|
|
564
|
+
<div id="notes-container"></div>
|
|
565
|
+
</div>
|
|
294
566
|
</div>
|
|
295
567
|
</div>
|
|
296
568
|
</div>
|
|
@@ -299,7 +571,306 @@ th.sorted .sort-arrow{opacity:1;color:var(--accent)}
|
|
|
299
571
|
<div id="panel-overlay" class="panel-overlay"></div>
|
|
300
572
|
<div id="detail-panel" class="detail-panel"></div>
|
|
301
573
|
|
|
574
|
+
<!-- Settings Panel -->
|
|
575
|
+
<div id="settings-overlay" class="settings-overlay"></div>
|
|
576
|
+
<div id="settings-panel" class="settings-panel">
|
|
577
|
+
<div class="settings-header">
|
|
578
|
+
<h2>Settings</h2>
|
|
579
|
+
<button class="panel-close" id="settings-close">×</button>
|
|
580
|
+
</div>
|
|
581
|
+
<div class="settings-content">
|
|
582
|
+
<div class="settings-section">
|
|
583
|
+
<h3>Theme</h3>
|
|
584
|
+
<p id="settings-theme-info" class="settings-theme-info"></p>
|
|
585
|
+
<div id="theme-grid" class="theme-grid"></div>
|
|
586
|
+
</div>
|
|
587
|
+
</div>
|
|
588
|
+
<div class="settings-footer">
|
|
589
|
+
<label class="toggle-label">
|
|
590
|
+
<input type="checkbox" id="theme-default-toggle">
|
|
591
|
+
<span class="toggle-switch"></span>
|
|
592
|
+
<span>Set as default for all projects</span>
|
|
593
|
+
</label>
|
|
594
|
+
<button class="btn btn-primary" id="settings-save">Save</button>
|
|
595
|
+
</div>
|
|
596
|
+
</div>
|
|
597
|
+
|
|
598
|
+
<!-- History Modal -->
|
|
599
|
+
<div id="history-overlay" class="history-overlay"></div>
|
|
600
|
+
<div id="history-modal" class="history-modal"></div>
|
|
601
|
+
|
|
602
|
+
<script src="https://cdn.jsdelivr.net/npm/@editorjs/editorjs@2/dist/editorjs.umd.js"></script>
|
|
603
|
+
<script src="https://cdn.jsdelivr.net/npm/@editorjs/header@latest/dist/header.umd.js"></script>
|
|
604
|
+
<script src="https://cdn.jsdelivr.net/npm/@editorjs/list@latest/dist/editorjs-list.umd.js"></script>
|
|
605
|
+
<script src="https://cdn.jsdelivr.net/npm/@editorjs/code@latest/dist/code.umd.js"></script>
|
|
606
|
+
<script src="https://cdn.jsdelivr.net/npm/@editorjs/quote@latest/dist/quote.umd.js"></script>
|
|
607
|
+
<script src="https://cdn.jsdelivr.net/npm/@editorjs/delimiter@latest/dist/delimiter.umd.js"></script>
|
|
608
|
+
<script src="https://cdn.jsdelivr.net/npm/@editorjs/marker@latest/dist/marker.umd.js"></script>
|
|
609
|
+
<script src="https://cdn.jsdelivr.net/npm/@editorjs/inline-code@latest/dist/inline-code.umd.js"></script>
|
|
302
610
|
<script>
|
|
611
|
+
/* ══════════════════════════════════════════════════════════════
|
|
612
|
+
THEMES — Predefined color themes for mdboard
|
|
613
|
+
══════════════════════════════════════════════════════════════ */
|
|
614
|
+
var THEMES = {
|
|
615
|
+
'default-dark': {
|
|
616
|
+
name: 'Default Dark', type: 'dark',
|
|
617
|
+
vars: {
|
|
618
|
+
bg:'#0A0A0B',surface:'#141415',surface2:'#1A1A1C',border:'#232326',border2:'#2E2E33',
|
|
619
|
+
text:'#E8E8ED',text2:'#8B8B93',text3:'#5A5A63',
|
|
620
|
+
accent:'#5B6EF5','accent-dim':'rgba(91,110,245,.15)',
|
|
621
|
+
success:'#2EA043','success-dim':'rgba(46,160,67,.15)',
|
|
622
|
+
warning:'#D4A72C','warning-dim':'rgba(212,167,44,.15)',
|
|
623
|
+
danger:'#DA3633','danger-dim':'rgba(218,54,51,.15)',
|
|
624
|
+
purple:'#8B5CF6','purple-dim':'rgba(139,92,246,.15)'
|
|
625
|
+
}
|
|
626
|
+
},
|
|
627
|
+
'linear-dark': {
|
|
628
|
+
name: 'Linear Dark', type: 'dark',
|
|
629
|
+
vars: {
|
|
630
|
+
bg:'#101012',surface:'#1A1A1F',surface2:'#222228',border:'#2C2C35',border2:'#38384A',
|
|
631
|
+
text:'#E2E2E9',text2:'#8E8E9E',text3:'#5C5C6E',
|
|
632
|
+
accent:'#5E6AD2','accent-dim':'rgba(94,106,210,.15)',
|
|
633
|
+
success:'#4DA771','success-dim':'rgba(77,167,113,.15)',
|
|
634
|
+
warning:'#F2C94C','warning-dim':'rgba(242,201,76,.15)',
|
|
635
|
+
danger:'#EB5757','danger-dim':'rgba(235,87,87,.15)',
|
|
636
|
+
purple:'#BB87FC','purple-dim':'rgba(187,135,252,.15)'
|
|
637
|
+
}
|
|
638
|
+
},
|
|
639
|
+
'linear-light': {
|
|
640
|
+
name: 'Linear Light', type: 'light',
|
|
641
|
+
vars: {
|
|
642
|
+
bg:'#F9F9FB',surface:'#FFFFFF',surface2:'#F0F0F4',border:'#E0E0E6',border2:'#D0D0D8',
|
|
643
|
+
text:'#1A1A2E',text2:'#6B6B80',text3:'#9090A0',
|
|
644
|
+
accent:'#5E6AD2','accent-dim':'rgba(94,106,210,.1)',
|
|
645
|
+
success:'#4DA771','success-dim':'rgba(77,167,113,.1)',
|
|
646
|
+
warning:'#D4A017','warning-dim':'rgba(212,160,23,.1)',
|
|
647
|
+
danger:'#EB5757','danger-dim':'rgba(235,87,87,.1)',
|
|
648
|
+
purple:'#9B6DD7','purple-dim':'rgba(155,109,215,.1)'
|
|
649
|
+
}
|
|
650
|
+
},
|
|
651
|
+
'jira-dark': {
|
|
652
|
+
name: 'Jira Dark', type: 'dark',
|
|
653
|
+
vars: {
|
|
654
|
+
bg:'#0D1117',surface:'#161B22',surface2:'#1C2333',border:'#293040',border2:'#344050',
|
|
655
|
+
text:'#DEE4EC',text2:'#8C96A5',text3:'#5A6577',
|
|
656
|
+
accent:'#2684FF','accent-dim':'rgba(38,132,255,.15)',
|
|
657
|
+
success:'#36B37E','success-dim':'rgba(54,179,126,.15)',
|
|
658
|
+
warning:'#FFAB00','warning-dim':'rgba(255,171,0,.15)',
|
|
659
|
+
danger:'#FF5630','danger-dim':'rgba(255,86,48,.15)',
|
|
660
|
+
purple:'#6554C0','purple-dim':'rgba(101,84,192,.15)'
|
|
661
|
+
}
|
|
662
|
+
},
|
|
663
|
+
'jira-light': {
|
|
664
|
+
name: 'Jira Light', type: 'light',
|
|
665
|
+
vars: {
|
|
666
|
+
bg:'#F4F5F7',surface:'#FFFFFF',surface2:'#EBECF0',border:'#DFE1E6',border2:'#C1C7D0',
|
|
667
|
+
text:'#172B4D',text2:'#6B778C',text3:'#97A0AF',
|
|
668
|
+
accent:'#0052CC','accent-dim':'rgba(0,82,204,.1)',
|
|
669
|
+
success:'#36B37E','success-dim':'rgba(54,179,126,.1)',
|
|
670
|
+
warning:'#FF991F','warning-dim':'rgba(255,153,31,.1)',
|
|
671
|
+
danger:'#FF5630','danger-dim':'rgba(255,86,48,.1)',
|
|
672
|
+
purple:'#6554C0','purple-dim':'rgba(101,84,192,.1)'
|
|
673
|
+
}
|
|
674
|
+
},
|
|
675
|
+
'catppuccin-mocha': {
|
|
676
|
+
name: 'Catppuccin Mocha', type: 'dark',
|
|
677
|
+
vars: {
|
|
678
|
+
bg:'#1E1E2E',surface:'#262637',surface2:'#313244',border:'#3B3B52',border2:'#45475A',
|
|
679
|
+
text:'#CDD6F4',text2:'#A6ADC8',text3:'#6C7086',
|
|
680
|
+
accent:'#89B4FA','accent-dim':'rgba(137,180,250,.15)',
|
|
681
|
+
success:'#A6E3A1','success-dim':'rgba(166,227,161,.15)',
|
|
682
|
+
warning:'#F9E2AF','warning-dim':'rgba(249,226,175,.15)',
|
|
683
|
+
danger:'#F38BA8','danger-dim':'rgba(243,139,168,.15)',
|
|
684
|
+
purple:'#CBA6F7','purple-dim':'rgba(203,166,247,.15)'
|
|
685
|
+
}
|
|
686
|
+
},
|
|
687
|
+
'catppuccin-latte': {
|
|
688
|
+
name: 'Catppuccin Latte', type: 'light',
|
|
689
|
+
vars: {
|
|
690
|
+
bg:'#EFF1F5',surface:'#FFFFFF',surface2:'#E6E9EF',border:'#CCD0DA',border2:'#BCC0CC',
|
|
691
|
+
text:'#4C4F69',text2:'#6C6F85',text3:'#9CA0B0',
|
|
692
|
+
accent:'#1E66F5','accent-dim':'rgba(30,102,245,.1)',
|
|
693
|
+
success:'#40A02B','success-dim':'rgba(64,160,43,.1)',
|
|
694
|
+
warning:'#DF8E1D','warning-dim':'rgba(223,142,29,.1)',
|
|
695
|
+
danger:'#D20F39','danger-dim':'rgba(210,15,57,.1)',
|
|
696
|
+
purple:'#8839EF','purple-dim':'rgba(136,57,239,.1)'
|
|
697
|
+
}
|
|
698
|
+
},
|
|
699
|
+
'ayu-dark': {
|
|
700
|
+
name: 'Ayu Dark', type: 'dark',
|
|
701
|
+
vars: {
|
|
702
|
+
bg:'#0B0E14',surface:'#0F1219',surface2:'#151920',border:'#1E222A',border2:'#272D38',
|
|
703
|
+
text:'#BFBDB6',text2:'#6C7380',text3:'#4A5060',
|
|
704
|
+
accent:'#E6B450','accent-dim':'rgba(230,180,80,.15)',
|
|
705
|
+
success:'#7FD962','success-dim':'rgba(127,217,98,.15)',
|
|
706
|
+
warning:'#FFB454','warning-dim':'rgba(255,180,84,.15)',
|
|
707
|
+
danger:'#F07178','danger-dim':'rgba(240,113,120,.15)',
|
|
708
|
+
purple:'#D2A6FF','purple-dim':'rgba(210,166,255,.15)'
|
|
709
|
+
}
|
|
710
|
+
},
|
|
711
|
+
'ayu-light': {
|
|
712
|
+
name: 'Ayu Light', type: 'light',
|
|
713
|
+
vars: {
|
|
714
|
+
bg:'#F8F9FA',surface:'#FFFFFF',surface2:'#F0F1F2',border:'#E0E1E2',border2:'#D4D5D6',
|
|
715
|
+
text:'#5C6166',text2:'#787B80',text3:'#ACB0B5',
|
|
716
|
+
accent:'#FF9940','accent-dim':'rgba(255,153,64,.1)',
|
|
717
|
+
success:'#6CBF43','success-dim':'rgba(108,191,67,.1)',
|
|
718
|
+
warning:'#F2AE49','warning-dim':'rgba(242,174,73,.1)',
|
|
719
|
+
danger:'#F07171','danger-dim':'rgba(240,113,113,.1)',
|
|
720
|
+
purple:'#A37ACC','purple-dim':'rgba(163,122,204,.1)'
|
|
721
|
+
}
|
|
722
|
+
},
|
|
723
|
+
'tokyo-night': {
|
|
724
|
+
name: 'Tokyo Night', type: 'dark',
|
|
725
|
+
vars: {
|
|
726
|
+
bg:'#1A1B26',surface:'#1F2335',surface2:'#24283B',border:'#2F3449',border2:'#3B4261',
|
|
727
|
+
text:'#C0CAF5',text2:'#7982A9',text3:'#565F89',
|
|
728
|
+
accent:'#7AA2F7','accent-dim':'rgba(122,162,247,.15)',
|
|
729
|
+
success:'#9ECE6A','success-dim':'rgba(158,206,106,.15)',
|
|
730
|
+
warning:'#E0AF68','warning-dim':'rgba(224,175,104,.15)',
|
|
731
|
+
danger:'#F7768E','danger-dim':'rgba(247,118,142,.15)',
|
|
732
|
+
purple:'#BB9AF7','purple-dim':'rgba(187,154,247,.15)'
|
|
733
|
+
}
|
|
734
|
+
},
|
|
735
|
+
'tokyo-night-storm': {
|
|
736
|
+
name: 'Tokyo Night Storm', type: 'dark',
|
|
737
|
+
vars: {
|
|
738
|
+
bg:'#24283B',surface:'#292E42',surface2:'#2F3549',border:'#3B4261',border2:'#464F78',
|
|
739
|
+
text:'#C0CAF5',text2:'#7982A9',text3:'#565F89',
|
|
740
|
+
accent:'#7AA2F7','accent-dim':'rgba(122,162,247,.15)',
|
|
741
|
+
success:'#9ECE6A','success-dim':'rgba(158,206,106,.15)',
|
|
742
|
+
warning:'#E0AF68','warning-dim':'rgba(224,175,104,.15)',
|
|
743
|
+
danger:'#F7768E','danger-dim':'rgba(247,118,142,.15)',
|
|
744
|
+
purple:'#BB9AF7','purple-dim':'rgba(187,154,247,.15)'
|
|
745
|
+
}
|
|
746
|
+
},
|
|
747
|
+
'notion-light': {
|
|
748
|
+
name: 'Notion Light', type: 'light',
|
|
749
|
+
vars: {
|
|
750
|
+
bg:'#F7F6F3',surface:'#FFFFFF',surface2:'#EEEEEC',border:'#E3E2DE',border2:'#D4D3CF',
|
|
751
|
+
text:'#37352F',text2:'#6B6B6B',text3:'#9B9A97',
|
|
752
|
+
accent:'#2383E2','accent-dim':'rgba(35,131,226,.1)',
|
|
753
|
+
success:'#0F7B0F','success-dim':'rgba(15,123,15,.1)',
|
|
754
|
+
warning:'#CB7B00','warning-dim':'rgba(203,123,0,.1)',
|
|
755
|
+
danger:'#E03E3E','danger-dim':'rgba(224,62,62,.1)',
|
|
756
|
+
purple:'#6940A5','purple-dim':'rgba(105,64,165,.1)'
|
|
757
|
+
}
|
|
758
|
+
},
|
|
759
|
+
'notion-dark': {
|
|
760
|
+
name: 'Notion Dark', type: 'dark',
|
|
761
|
+
vars: {
|
|
762
|
+
bg:'#191919',surface:'#202020',surface2:'#2B2B2B',border:'#363636',border2:'#444444',
|
|
763
|
+
text:'#E3E2DE',text2:'#9B9B9B',text3:'#6B6B6B',
|
|
764
|
+
accent:'#529CCA','accent-dim':'rgba(82,156,202,.15)',
|
|
765
|
+
success:'#4DAB9A','success-dim':'rgba(77,171,154,.15)',
|
|
766
|
+
warning:'#CB7B00','warning-dim':'rgba(203,123,0,.15)',
|
|
767
|
+
danger:'#E03E3E','danger-dim':'rgba(224,62,62,.15)',
|
|
768
|
+
purple:'#9A6DD7','purple-dim':'rgba(154,109,215,.15)'
|
|
769
|
+
}
|
|
770
|
+
},
|
|
771
|
+
'github-dark': {
|
|
772
|
+
name: 'GitHub Dark', type: 'dark',
|
|
773
|
+
vars: {
|
|
774
|
+
bg:'#0D1117',surface:'#161B22',surface2:'#1C2128',border:'#30363D',border2:'#3D444D',
|
|
775
|
+
text:'#E6EDF3',text2:'#8B949E',text3:'#6E7681',
|
|
776
|
+
accent:'#58A6FF','accent-dim':'rgba(88,166,255,.15)',
|
|
777
|
+
success:'#3FB950','success-dim':'rgba(63,185,80,.15)',
|
|
778
|
+
warning:'#D29922','warning-dim':'rgba(210,153,34,.15)',
|
|
779
|
+
danger:'#F85149','danger-dim':'rgba(248,81,73,.15)',
|
|
780
|
+
purple:'#BC8CFF','purple-dim':'rgba(188,140,255,.15)'
|
|
781
|
+
}
|
|
782
|
+
},
|
|
783
|
+
'github-light': {
|
|
784
|
+
name: 'GitHub Light', type: 'light',
|
|
785
|
+
vars: {
|
|
786
|
+
bg:'#F6F8FA',surface:'#FFFFFF',surface2:'#EAEEF2',border:'#D0D7DE',border2:'#BCC3CB',
|
|
787
|
+
text:'#1F2328',text2:'#656D76',text3:'#8C959F',
|
|
788
|
+
accent:'#0969DA','accent-dim':'rgba(9,105,218,.1)',
|
|
789
|
+
success:'#1A7F37','success-dim':'rgba(26,127,55,.1)',
|
|
790
|
+
warning:'#9A6700','warning-dim':'rgba(154,103,0,.1)',
|
|
791
|
+
danger:'#CF222E','danger-dim':'rgba(207,34,46,.1)',
|
|
792
|
+
purple:'#8250DF','purple-dim':'rgba(130,80,223,.1)'
|
|
793
|
+
}
|
|
794
|
+
},
|
|
795
|
+
'dracula': {
|
|
796
|
+
name: 'Dracula', type: 'dark',
|
|
797
|
+
vars: {
|
|
798
|
+
bg:'#1E1F29',surface:'#282A36',surface2:'#2E303E',border:'#3A3C4E',border2:'#44475A',
|
|
799
|
+
text:'#F8F8F2',text2:'#ADB0C0',text3:'#6272A4',
|
|
800
|
+
accent:'#BD93F9','accent-dim':'rgba(189,147,249,.15)',
|
|
801
|
+
success:'#50FA7B','success-dim':'rgba(80,250,123,.15)',
|
|
802
|
+
warning:'#F1FA8C','warning-dim':'rgba(241,250,140,.15)',
|
|
803
|
+
danger:'#FF5555','danger-dim':'rgba(255,85,85,.15)',
|
|
804
|
+
purple:'#FF79C6','purple-dim':'rgba(255,121,198,.15)'
|
|
805
|
+
}
|
|
806
|
+
},
|
|
807
|
+
'nord': {
|
|
808
|
+
name: 'Nord', type: 'dark',
|
|
809
|
+
vars: {
|
|
810
|
+
bg:'#242933',surface:'#2E3440',surface2:'#353C4A',border:'#3B4252',border2:'#434C5E',
|
|
811
|
+
text:'#ECEFF4',text2:'#A5B1C2',text3:'#697688',
|
|
812
|
+
accent:'#88C0D0','accent-dim':'rgba(136,192,208,.15)',
|
|
813
|
+
success:'#A3BE8C','success-dim':'rgba(163,190,140,.15)',
|
|
814
|
+
warning:'#EBCB8B','warning-dim':'rgba(235,203,139,.15)',
|
|
815
|
+
danger:'#BF616A','danger-dim':'rgba(191,97,106,.15)',
|
|
816
|
+
purple:'#B48EAD','purple-dim':'rgba(180,142,173,.15)'
|
|
817
|
+
}
|
|
818
|
+
},
|
|
819
|
+
'solarized-dark': {
|
|
820
|
+
name: 'Solarized Dark', type: 'dark',
|
|
821
|
+
vars: {
|
|
822
|
+
bg:'#002129',surface:'#002B36',surface2:'#073642',border:'#0A4050',border2:'#1A5060',
|
|
823
|
+
text:'#93A1A1',text2:'#839496',text3:'#586E75',
|
|
824
|
+
accent:'#268BD2','accent-dim':'rgba(38,139,210,.15)',
|
|
825
|
+
success:'#859900','success-dim':'rgba(133,153,0,.15)',
|
|
826
|
+
warning:'#B58900','warning-dim':'rgba(181,137,0,.15)',
|
|
827
|
+
danger:'#DC322F','danger-dim':'rgba(220,50,47,.15)',
|
|
828
|
+
purple:'#6C71C4','purple-dim':'rgba(108,113,196,.15)'
|
|
829
|
+
}
|
|
830
|
+
},
|
|
831
|
+
'solarized-light': {
|
|
832
|
+
name: 'Solarized Light', type: 'light',
|
|
833
|
+
vars: {
|
|
834
|
+
bg:'#FDF6E3',surface:'#FFFFFF',surface2:'#EEE8D5',border:'#DDD6C1',border2:'#C9C2AD',
|
|
835
|
+
text:'#586E75',text2:'#657B83',text3:'#93A1A1',
|
|
836
|
+
accent:'#268BD2','accent-dim':'rgba(38,139,210,.1)',
|
|
837
|
+
success:'#859900','success-dim':'rgba(133,153,0,.1)',
|
|
838
|
+
warning:'#B58900','warning-dim':'rgba(181,137,0,.1)',
|
|
839
|
+
danger:'#DC322F','danger-dim':'rgba(220,50,47,.1)',
|
|
840
|
+
purple:'#6C71C4','purple-dim':'rgba(108,113,196,.1)'
|
|
841
|
+
}
|
|
842
|
+
},
|
|
843
|
+
'one-dark': {
|
|
844
|
+
name: 'One Dark', type: 'dark',
|
|
845
|
+
vars: {
|
|
846
|
+
bg:'#1E2127',surface:'#282C34',surface2:'#2C313C',border:'#3A3F4B',border2:'#4B5263',
|
|
847
|
+
text:'#ABB2BF',text2:'#7F848E',text3:'#5C6370',
|
|
848
|
+
accent:'#61AFEF','accent-dim':'rgba(97,175,239,.15)',
|
|
849
|
+
success:'#98C379','success-dim':'rgba(152,195,121,.15)',
|
|
850
|
+
warning:'#E5C07B','warning-dim':'rgba(229,192,123,.15)',
|
|
851
|
+
danger:'#E06C75','danger-dim':'rgba(224,108,117,.15)',
|
|
852
|
+
purple:'#C678DD','purple-dim':'rgba(198,120,221,.15)'
|
|
853
|
+
}
|
|
854
|
+
},
|
|
855
|
+
'gruvbox-dark': {
|
|
856
|
+
name: 'Gruvbox Dark', type: 'dark',
|
|
857
|
+
vars: {
|
|
858
|
+
bg:'#1D2021',surface:'#282828',surface2:'#32302F',border:'#3C3836',border2:'#504945',
|
|
859
|
+
text:'#EBDBB2',text2:'#A89984',text3:'#7C6F64',
|
|
860
|
+
accent:'#FE8019','accent-dim':'rgba(254,128,25,.15)',
|
|
861
|
+
success:'#B8BB26','success-dim':'rgba(184,187,38,.15)',
|
|
862
|
+
warning:'#FABD2F','warning-dim':'rgba(250,189,47,.15)',
|
|
863
|
+
danger:'#FB4934','danger-dim':'rgba(251,73,52,.15)',
|
|
864
|
+
purple:'#D3869B','purple-dim':'rgba(211,134,155,.15)'
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
};
|
|
868
|
+
|
|
869
|
+
var THEME_LIST = Object.keys(THEMES).map(function(id) {
|
|
870
|
+
return { id: id, name: THEMES[id].name, type: THEMES[id].type };
|
|
871
|
+
});
|
|
872
|
+
|
|
873
|
+
|
|
303
874
|
/* ══════════════════════════════════════════════════════════════
|
|
304
875
|
ICON SHAPES — SVG generators by icon name
|
|
305
876
|
══════════════════════════════════════════════════════════════ */
|
|
@@ -371,7 +942,9 @@ function milestoneIcon(status) {
|
|
|
371
942
|
══════════════════════════════════════════════════════════════ */
|
|
372
943
|
var D = {
|
|
373
944
|
config: null, project: null, milestones: [], epics: [], tasks: [],
|
|
374
|
-
sprints: [], allSprints: [], metrics: null, health: null, loaded: false
|
|
945
|
+
sprints: [], allSprints: [], metrics: null, health: null, loaded: false,
|
|
946
|
+
sources: [], activeSource: null, overviewLinks: null, notes: [],
|
|
947
|
+
aiSuggestions: null, theme: null
|
|
375
948
|
};
|
|
376
949
|
|
|
377
950
|
/* ── Helpers ─────────────────────────────────────────────── */
|
|
@@ -421,7 +994,8 @@ async function fetchJson(url) {
|
|
|
421
994
|
|
|
422
995
|
async function patchItem(type, id, updates) {
|
|
423
996
|
try {
|
|
424
|
-
var
|
|
997
|
+
var base = apiBase();
|
|
998
|
+
var r = await fetch(base + '/' + type + '/' + encodeURIComponent(id), {
|
|
425
999
|
method: 'PATCH',
|
|
426
1000
|
headers: { 'Content-Type': 'application/json' },
|
|
427
1001
|
body: JSON.stringify(updates),
|
|
@@ -440,22 +1014,83 @@ async function patchItem(type, id, updates) {
|
|
|
440
1014
|
}
|
|
441
1015
|
}
|
|
442
1016
|
|
|
1017
|
+
/* ── CRUD API helpers ─────────────────────────────────────── */
|
|
1018
|
+
function apiBase() {
|
|
1019
|
+
if (D.activeSource) return '/api/sources/' + encodeURIComponent(D.activeSource);
|
|
1020
|
+
return '/api';
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
async function createItem(collection, data) {
|
|
1024
|
+
try {
|
|
1025
|
+
var url = apiBase() + '/' + collection;
|
|
1026
|
+
var r = await fetch(url, {
|
|
1027
|
+
method: 'POST',
|
|
1028
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1029
|
+
body: JSON.stringify(data),
|
|
1030
|
+
});
|
|
1031
|
+
var result = await r.json();
|
|
1032
|
+
if (result && result.ok) {
|
|
1033
|
+
showToast('Created ' + (result.id || ''), 'success');
|
|
1034
|
+
return result;
|
|
1035
|
+
} else {
|
|
1036
|
+
showToast('Error: ' + (result.error || 'Unknown'), 'error');
|
|
1037
|
+
return null;
|
|
1038
|
+
}
|
|
1039
|
+
} catch (e) {
|
|
1040
|
+
showToast('Error: ' + e.message, 'error');
|
|
1041
|
+
return null;
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
async function deleteItem(collection, id) {
|
|
1046
|
+
try {
|
|
1047
|
+
var url = apiBase() + '/' + collection + '/' + encodeURIComponent(id);
|
|
1048
|
+
var r = await fetch(url, { method: 'DELETE' });
|
|
1049
|
+
var result = await r.json();
|
|
1050
|
+
if (result && result.ok) {
|
|
1051
|
+
showToast('Archived successfully', 'success');
|
|
1052
|
+
return true;
|
|
1053
|
+
} else {
|
|
1054
|
+
showToast('Error: ' + (result.error || 'Unknown'), 'error');
|
|
1055
|
+
return false;
|
|
1056
|
+
}
|
|
1057
|
+
} catch (e) {
|
|
1058
|
+
showToast('Error: ' + e.message, 'error');
|
|
1059
|
+
return false;
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
/* ── Data Loading ────────────────────────────────────────── */
|
|
443
1064
|
async function loadAll() {
|
|
1065
|
+
var base = apiBase();
|
|
444
1066
|
var results = await Promise.all([
|
|
445
|
-
fetchJson('/
|
|
446
|
-
fetchJson('/
|
|
447
|
-
fetchJson('/
|
|
1067
|
+
fetchJson(base + '/project'), fetchJson(base + '/milestones'), fetchJson(base + '/epics'),
|
|
1068
|
+
fetchJson(base + '/tasks'), fetchJson(base + '/sprint'), fetchJson(base + '/metrics'),
|
|
1069
|
+
fetchJson(base + '/health'), fetchJson(base + '/sprints'), fetchJson('/api/config'),
|
|
1070
|
+
fetchJson('/api/sources'), fetchJson(base + '/notes')
|
|
448
1071
|
]);
|
|
449
1072
|
D.project = results[0]; D.milestones = results[1] || []; D.epics = results[2] || [];
|
|
450
1073
|
D.tasks = results[3] || [];
|
|
451
|
-
if (results[4]) D.sprints = [results[4]];
|
|
1074
|
+
if (results[4]) D.sprints = [results[4]]; else D.sprints = [];
|
|
452
1075
|
D.metrics = results[5]; D.health = results[6];
|
|
453
1076
|
D.allSprints = results[7] || [];
|
|
454
1077
|
D.config = results[8];
|
|
1078
|
+
D.sources = results[9] || [];
|
|
1079
|
+
D.notes = results[10] || [];
|
|
455
1080
|
D.loaded = true;
|
|
456
1081
|
}
|
|
457
1082
|
|
|
458
|
-
function refreshData() {
|
|
1083
|
+
function refreshData() {
|
|
1084
|
+
loadAll().then(function() {
|
|
1085
|
+
initFromConfig();
|
|
1086
|
+
renderAll();
|
|
1087
|
+
if (hasWorkspace()) buildSourceRail();
|
|
1088
|
+
});
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
function hasWorkspace() {
|
|
1092
|
+
return D.config && D.config.workspace && D.config.workspace.sources && D.config.workspace.sources.length > 0;
|
|
1093
|
+
}
|
|
459
1094
|
|
|
460
1095
|
/* ── Config-driven initialization ────────────────────────── */
|
|
461
1096
|
function initFromConfig() {
|
|
@@ -531,6 +1166,169 @@ function generateDynamicStyles() {
|
|
|
531
1166
|
if (styleEl) styleEl.textContent = css;
|
|
532
1167
|
}
|
|
533
1168
|
|
|
1169
|
+
/* ══════════════════════════════════════════════════════════════
|
|
1170
|
+
THEME ENGINE
|
|
1171
|
+
══════════════════════════════════════════════════════════════ */
|
|
1172
|
+
function generateThemeCss(themeId) {
|
|
1173
|
+
var theme = THEMES[themeId];
|
|
1174
|
+
if (!theme) return '';
|
|
1175
|
+
var css = '/* mdboard theme: ' + theme.name + ' */\n:root {\n';
|
|
1176
|
+
for (var key in theme.vars) {
|
|
1177
|
+
if (theme.vars.hasOwnProperty(key)) {
|
|
1178
|
+
css += ' --' + key + ': ' + theme.vars[key] + ';\n';
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
css += '}\n';
|
|
1182
|
+
return css;
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
function applyTheme(themeId) {
|
|
1186
|
+
var theme = THEMES[themeId];
|
|
1187
|
+
if (!theme) return;
|
|
1188
|
+
var el = document.getElementById('theme-styles');
|
|
1189
|
+
if (el) el.textContent = generateThemeCss(themeId);
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
function revertThemePreview() {
|
|
1193
|
+
var el = document.getElementById('theme-styles');
|
|
1194
|
+
if (el) el.textContent = '';
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
function reloadCustomCss() {
|
|
1198
|
+
var link = document.getElementById('custom-theme');
|
|
1199
|
+
if (link) link.href = '/mdboard.css?t=' + Date.now();
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
function getActiveTheme() {
|
|
1203
|
+
return D.config && D.config.theme ? D.config.theme : 'default-dark';
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
/* ══════════════════════════════════════════════════════════════
|
|
1207
|
+
WORKSPACE — Source switching & sidebar
|
|
1208
|
+
══════════════════════════════════════════════════════════════ */
|
|
1209
|
+
function renderSidebarLogo() {
|
|
1210
|
+
var p = D.project || {};
|
|
1211
|
+
var logoEl = document.getElementById('sidebar-logo');
|
|
1212
|
+
var iconEl = document.getElementById('sidebar-logo-icon');
|
|
1213
|
+
var textEl = document.getElementById('sidebar-logo-text');
|
|
1214
|
+
|
|
1215
|
+
textEl.textContent = p.name || 'Project';
|
|
1216
|
+
|
|
1217
|
+
if (D.config && D.config.logo) {
|
|
1218
|
+
var logoSrc = D.config.logo.startsWith('http') ? D.config.logo : '/logo';
|
|
1219
|
+
iconEl.innerHTML = '<img src="' + escHtml(logoSrc) + '" alt="">';
|
|
1220
|
+
} else {
|
|
1221
|
+
var initial = (p.name || 'P').charAt(0).toUpperCase();
|
|
1222
|
+
iconEl.textContent = initial;
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
logoEl.onclick = function() {
|
|
1226
|
+
if (hasWorkspace()) {
|
|
1227
|
+
switchSource('overview');
|
|
1228
|
+
}
|
|
1229
|
+
};
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
function buildSourceRail() {
|
|
1233
|
+
var rail = document.getElementById('source-rail');
|
|
1234
|
+
if (!rail) return;
|
|
1235
|
+
rail.innerHTML = '';
|
|
1236
|
+
|
|
1237
|
+
if (!hasWorkspace()) return;
|
|
1238
|
+
|
|
1239
|
+
// Home icon (overview)
|
|
1240
|
+
var home = document.createElement('div');
|
|
1241
|
+
home.className = 'rail-icon' + (D.activeSource === 'overview' ? ' active' : '');
|
|
1242
|
+
home.dataset.source = 'overview';
|
|
1243
|
+
home.setAttribute('data-tooltip', 'Overview');
|
|
1244
|
+
home.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg>';
|
|
1245
|
+
home.onclick = function() { switchSource('overview'); };
|
|
1246
|
+
rail.appendChild(home);
|
|
1247
|
+
|
|
1248
|
+
// Divider
|
|
1249
|
+
var divider = document.createElement('div');
|
|
1250
|
+
divider.className = 'rail-divider';
|
|
1251
|
+
rail.appendChild(divider);
|
|
1252
|
+
|
|
1253
|
+
// Source icons
|
|
1254
|
+
var wsSources = D.config.workspace.sources;
|
|
1255
|
+
for (var i = 0; i < wsSources.length; i++) {
|
|
1256
|
+
var s = wsSources[i];
|
|
1257
|
+
if (s.name === 'overview') continue;
|
|
1258
|
+
|
|
1259
|
+
var color = s.color || 'var(--accent)';
|
|
1260
|
+
var icon = document.createElement('div');
|
|
1261
|
+
icon.className = 'rail-icon' + (D.activeSource === s.name ? ' active' : '');
|
|
1262
|
+
icon.dataset.source = s.name;
|
|
1263
|
+
icon.setAttribute('data-tooltip', s.label || s.name);
|
|
1264
|
+
icon.style.background = color + '20';
|
|
1265
|
+
icon.style.color = color;
|
|
1266
|
+
icon.innerHTML = escHtml(s.icon || s.name.charAt(0).toUpperCase());
|
|
1267
|
+
icon.onclick = (function(name) {
|
|
1268
|
+
return function() { switchSource(name); };
|
|
1269
|
+
})(s.name);
|
|
1270
|
+
rail.appendChild(icon);
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
// Switch project icon (bottom of rail)
|
|
1274
|
+
var switchDivider = document.createElement('div');
|
|
1275
|
+
switchDivider.className = 'rail-divider';
|
|
1276
|
+
switchDivider.style.marginTop = 'auto';
|
|
1277
|
+
rail.appendChild(switchDivider);
|
|
1278
|
+
|
|
1279
|
+
var switchIcon = document.createElement('div');
|
|
1280
|
+
switchIcon.className = 'rail-icon rail-switch';
|
|
1281
|
+
switchIcon.setAttribute('data-tooltip', 'Switch workspace');
|
|
1282
|
+
switchIcon.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M16 3h5v5"/><path d="M21 3l-7 7"/><path d="M8 21H3v-5"/><path d="M3 21l7-7"/></svg>';
|
|
1283
|
+
switchIcon.onclick = function() { openHistoryModal(); };
|
|
1284
|
+
rail.appendChild(switchIcon);
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
function switchSource(sourceName) {
|
|
1288
|
+
if (sourceName === D.activeSource) return;
|
|
1289
|
+
|
|
1290
|
+
// Update active source marker
|
|
1291
|
+
D.activeSource = sourceName;
|
|
1292
|
+
D.loaded = false;
|
|
1293
|
+
|
|
1294
|
+
// Persist selection
|
|
1295
|
+
try { localStorage.setItem('mdboard-source', sourceName); } catch(e) {}
|
|
1296
|
+
|
|
1297
|
+
// Update source rail active state
|
|
1298
|
+
document.querySelectorAll('.rail-icon').forEach(function(el) {
|
|
1299
|
+
el.classList.toggle('active', el.dataset.source === sourceName);
|
|
1300
|
+
});
|
|
1301
|
+
|
|
1302
|
+
// If switching to overview, show overview tabs; otherwise show normal tabs
|
|
1303
|
+
var isOverview = sourceName === 'overview';
|
|
1304
|
+
var viewTabs = document.querySelectorAll('#sidebar-nav a[data-view]');
|
|
1305
|
+
viewTabs.forEach(function(tab) {
|
|
1306
|
+
var view = tab.dataset.view;
|
|
1307
|
+
if (isOverview) {
|
|
1308
|
+
tab.style.display = (view === 'overview' || view === 'metrics') ? '' : 'none';
|
|
1309
|
+
} else {
|
|
1310
|
+
tab.style.display = (view === 'overview') ? 'none' : '';
|
|
1311
|
+
}
|
|
1312
|
+
});
|
|
1313
|
+
|
|
1314
|
+
// Switch to appropriate view
|
|
1315
|
+
if (isOverview) {
|
|
1316
|
+
switchView('overview');
|
|
1317
|
+
} else {
|
|
1318
|
+
var currentView = document.querySelector('.view.active');
|
|
1319
|
+
if (currentView && currentView.id === 'view-overview') {
|
|
1320
|
+
switchView('board');
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
// Reload data for this source
|
|
1325
|
+
loadAll().then(function() {
|
|
1326
|
+
initFromConfig();
|
|
1327
|
+
renderAll();
|
|
1328
|
+
buildSourceRail();
|
|
1329
|
+
});
|
|
1330
|
+
}
|
|
1331
|
+
|
|
534
1332
|
/* ── SSE — Hot Reload ────────────────────────────────────── */
|
|
535
1333
|
function connectSSE() {
|
|
536
1334
|
try {
|
|
@@ -538,6 +1336,28 @@ function connectSSE() {
|
|
|
538
1336
|
src.onmessage = function(e) {
|
|
539
1337
|
var data;
|
|
540
1338
|
try { data = JSON.parse(e.data); } catch { data = {}; }
|
|
1339
|
+
|
|
1340
|
+
if (data.type === 'project-changed') {
|
|
1341
|
+
// Full reload on project switch (from another tab or API call)
|
|
1342
|
+
D.activeSource = null;
|
|
1343
|
+
D.loaded = false;
|
|
1344
|
+
loadAll().then(function() {
|
|
1345
|
+
initFromConfig();
|
|
1346
|
+
if (hasWorkspace()) {
|
|
1347
|
+
buildSourceRail();
|
|
1348
|
+
D.activeSource = null;
|
|
1349
|
+
switchSource('overview');
|
|
1350
|
+
} else {
|
|
1351
|
+
// Hide source rail if no workspace
|
|
1352
|
+
var rail = document.getElementById('source-rail');
|
|
1353
|
+
if (rail) rail.innerHTML = '';
|
|
1354
|
+
renderAll();
|
|
1355
|
+
}
|
|
1356
|
+
renderSidebarLogo();
|
|
1357
|
+
});
|
|
1358
|
+
return;
|
|
1359
|
+
}
|
|
1360
|
+
|
|
541
1361
|
if (data.cssReload) {
|
|
542
1362
|
var link = document.getElementById('custom-theme');
|
|
543
1363
|
if (link) link.href = '/mdboard.css?' + Date.now();
|
|
@@ -549,46 +1369,325 @@ function connectSSE() {
|
|
|
549
1369
|
}
|
|
550
1370
|
|
|
551
1371
|
/* ══════════════════════════════════════════════════════════════
|
|
552
|
-
|
|
1372
|
+
EDITOR.JS WRAPPER — md↔blocks conversion + Notion-like UX
|
|
553
1373
|
══════════════════════════════════════════════════════════════ */
|
|
554
|
-
function renderAll() { renderHeader(); renderBoardFilters(); renderBoard(); renderTableControls(); renderTableBody(); renderMsFilters(); renderMilestones(); renderMetrics(); }
|
|
555
1374
|
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
1375
|
+
/* ── Inline Markdown ↔ HTML ──────────────────────────────── */
|
|
1376
|
+
function inlineMdToHtml(text) {
|
|
1377
|
+
if (!text) return '';
|
|
1378
|
+
text = text.replace(/\*\*(.+?)\*\*/g, '<b>$1</b>');
|
|
1379
|
+
text = text.replace(/\*(.+?)\*/g, '<i>$1</i>');
|
|
1380
|
+
text = text.replace(/`([^`]+)`/g, '<code>$1</code>');
|
|
1381
|
+
text = text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>');
|
|
1382
|
+
return text;
|
|
1383
|
+
}
|
|
560
1384
|
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
1385
|
+
function inlineHtmlToMd(html) {
|
|
1386
|
+
if (!html) return '';
|
|
1387
|
+
var text = html.replace(/<br\s*\/?>/gi, '\n');
|
|
1388
|
+
text = text.replace(/<a[^>]+href="([^"]*)"[^>]*>([^<]*)<\/a>/gi, '[$2]($1)');
|
|
1389
|
+
text = text.replace(/<b>([^<]*)<\/b>/gi, '**$1**');
|
|
1390
|
+
text = text.replace(/<strong>([^<]*)<\/strong>/gi, '**$1**');
|
|
1391
|
+
text = text.replace(/<i>([^<]*)<\/i>/gi, '*$1*');
|
|
1392
|
+
text = text.replace(/<em>([^<]*)<\/em>/gi, '*$1*');
|
|
1393
|
+
text = text.replace(/<code>([^<]*)<\/code>/gi, '`$1`');
|
|
1394
|
+
text = text.replace(/<mark[^>]*>([^<]*)<\/mark>/gi, '==$1==');
|
|
1395
|
+
text = text.replace(/<[^>]+>/g, '');
|
|
1396
|
+
return text;
|
|
1397
|
+
}
|
|
568
1398
|
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
if (
|
|
572
|
-
sw.style.display = '';
|
|
573
|
-
document.getElementById('h-sprint-name').textContent = as.goal || as.id || '';
|
|
574
|
-
var dr = daysRemaining(as.end_date);
|
|
575
|
-
document.getElementById('h-sprint-days').textContent = dr !== null ? (dr >= 0 ? dr + ' days left' : Math.abs(dr) + ' days over') : '';
|
|
576
|
-
} else { sw.style.display = 'none'; }
|
|
1399
|
+
/* ── Markdown → Editor.js Blocks ────────────────────────── */
|
|
1400
|
+
function markdownToBlocks(md) {
|
|
1401
|
+
if (!md || !md.trim()) return [{ type: 'paragraph', data: { text: '' } }];
|
|
577
1402
|
|
|
578
|
-
var
|
|
579
|
-
var
|
|
580
|
-
var
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
1403
|
+
var lines = md.split('\n');
|
|
1404
|
+
var blocks = [];
|
|
1405
|
+
var i = 0;
|
|
1406
|
+
|
|
1407
|
+
while (i < lines.length) {
|
|
1408
|
+
var line = lines[i];
|
|
1409
|
+
|
|
1410
|
+
if (line.trim() === '') { i++; continue; }
|
|
1411
|
+
|
|
1412
|
+
if (/^---\s*$/.test(line.trim()) || /^\*\*\*\s*$/.test(line.trim())) {
|
|
1413
|
+
blocks.push({ type: 'delimiter', data: {} });
|
|
1414
|
+
i++; continue;
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
var hMatch = line.match(/^(#{1,6})\s+(.+)$/);
|
|
1418
|
+
if (hMatch) {
|
|
1419
|
+
var lvl = Math.min(hMatch[1].length, 6);
|
|
1420
|
+
blocks.push({ type: 'header', data: { text: inlineMdToHtml(hMatch[2]), level: lvl } });
|
|
1421
|
+
i++; continue;
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
if (line.trim().startsWith('```')) {
|
|
1425
|
+
var code = [];
|
|
1426
|
+
i++;
|
|
1427
|
+
while (i < lines.length && !lines[i].trim().startsWith('```')) {
|
|
1428
|
+
code.push(lines[i]);
|
|
1429
|
+
i++;
|
|
1430
|
+
}
|
|
1431
|
+
blocks.push({ type: 'code', data: { code: code.join('\n') } });
|
|
1432
|
+
i++; continue;
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
if (line.trim().startsWith('> ')) {
|
|
1436
|
+
var quoteLines = [];
|
|
1437
|
+
while (i < lines.length && lines[i].trim().startsWith('> ')) {
|
|
1438
|
+
quoteLines.push(lines[i].trim().substring(2));
|
|
1439
|
+
i++;
|
|
1440
|
+
}
|
|
1441
|
+
blocks.push({ type: 'quote', data: { text: inlineMdToHtml(quoteLines.join('<br>')), caption: '' } });
|
|
1442
|
+
continue;
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
if (/^\s*-\s+\[[ x]\]\s+/.test(line)) {
|
|
1446
|
+
var checkItems = [];
|
|
1447
|
+
while (i < lines.length && /^\s*-\s+\[[ x]\]\s+/.test(lines[i])) {
|
|
1448
|
+
var cm = lines[i].match(/^\s*-\s+\[([ x])\]\s+(.+)$/);
|
|
1449
|
+
if (cm) checkItems.push({ content: inlineMdToHtml(cm[2]), meta: { checked: cm[1] === 'x' }, items: [] });
|
|
1450
|
+
i++;
|
|
1451
|
+
}
|
|
1452
|
+
blocks.push({ type: 'list', data: { style: 'checklist', items: checkItems } });
|
|
1453
|
+
continue;
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
if (/^\s*[-*]\s+/.test(line) && !/^\s*-\s+\[/.test(line)) {
|
|
1457
|
+
var listItems = [];
|
|
1458
|
+
while (i < lines.length && /^\s*[-*]\s+/.test(lines[i]) && !/^\s*-\s+\[/.test(lines[i])) {
|
|
1459
|
+
var lm = lines[i].match(/^\s*[-*]\s+(.+)$/);
|
|
1460
|
+
if (lm) listItems.push({ content: inlineMdToHtml(lm[1]), items: [] });
|
|
1461
|
+
i++;
|
|
1462
|
+
}
|
|
1463
|
+
blocks.push({ type: 'list', data: { style: 'unordered', items: listItems } });
|
|
1464
|
+
continue;
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
if (/^\s*\d+\.\s+/.test(line)) {
|
|
1468
|
+
var olItems = [];
|
|
1469
|
+
while (i < lines.length && /^\s*\d+\.\s+/.test(lines[i])) {
|
|
1470
|
+
var om = lines[i].match(/^\s*\d+\.\s+(.+)$/);
|
|
1471
|
+
if (om) olItems.push({ content: inlineMdToHtml(om[1]), items: [] });
|
|
1472
|
+
i++;
|
|
1473
|
+
}
|
|
1474
|
+
blocks.push({ type: 'list', data: { style: 'ordered', items: olItems } });
|
|
1475
|
+
continue;
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
var paraLines = [];
|
|
1479
|
+
while (i < lines.length && lines[i].trim() !== '' &&
|
|
1480
|
+
!/^#{1,6}\s/.test(lines[i]) && !/^```/.test(lines[i]) &&
|
|
1481
|
+
!/^>\s/.test(lines[i]) && !/^\s*[-*]\s/.test(lines[i]) &&
|
|
1482
|
+
!/^\s*\d+\.\s/.test(lines[i]) && !/^---\s*$/.test(lines[i].trim()) &&
|
|
1483
|
+
!/^\*\*\*\s*$/.test(lines[i].trim())) {
|
|
1484
|
+
paraLines.push(lines[i]);
|
|
1485
|
+
i++;
|
|
1486
|
+
}
|
|
1487
|
+
if (paraLines.length > 0) {
|
|
1488
|
+
blocks.push({ type: 'paragraph', data: { text: inlineMdToHtml(paraLines.join('<br>')) } });
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
return blocks.length > 0 ? blocks : [{ type: 'paragraph', data: { text: '' } }];
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
/* ── Editor.js Blocks → Markdown ────────────────────────── */
|
|
1496
|
+
function blocksToMarkdown(blocks) {
|
|
1497
|
+
if (!blocks || blocks.length === 0) return '';
|
|
1498
|
+
|
|
1499
|
+
var parts = [];
|
|
1500
|
+
for (var i = 0; i < blocks.length; i++) {
|
|
1501
|
+
var b = blocks[i];
|
|
1502
|
+
switch (b.type) {
|
|
1503
|
+
case 'header':
|
|
1504
|
+
var prefix = '';
|
|
1505
|
+
for (var h = 0; h < (b.data.level || 2); h++) prefix += '#';
|
|
1506
|
+
parts.push(prefix + ' ' + inlineHtmlToMd(b.data.text));
|
|
1507
|
+
break;
|
|
1508
|
+
case 'paragraph':
|
|
1509
|
+
parts.push(inlineHtmlToMd(b.data.text));
|
|
1510
|
+
break;
|
|
1511
|
+
case 'list':
|
|
1512
|
+
var items = b.data.items || [];
|
|
1513
|
+
var listLines = [];
|
|
1514
|
+
for (var li = 0; li < items.length; li++) {
|
|
1515
|
+
var itemText = typeof items[li] === 'string' ? items[li] : (items[li].content || items[li].text || '');
|
|
1516
|
+
if (b.data.style === 'checklist') {
|
|
1517
|
+
var chk = (typeof items[li] === 'object' && items[li].meta && items[li].meta.checked) ? 'x' : ' ';
|
|
1518
|
+
listLines.push('- [' + chk + '] ' + inlineHtmlToMd(itemText));
|
|
1519
|
+
} else if (b.data.style === 'ordered') {
|
|
1520
|
+
listLines.push((li + 1) + '. ' + inlineHtmlToMd(itemText));
|
|
1521
|
+
} else {
|
|
1522
|
+
listLines.push('- ' + inlineHtmlToMd(itemText));
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
parts.push(listLines.join('\n'));
|
|
1526
|
+
break;
|
|
1527
|
+
case 'checklist':
|
|
1528
|
+
var cItems = b.data.items || [];
|
|
1529
|
+
var checkLines = [];
|
|
1530
|
+
for (var ci = 0; ci < cItems.length; ci++) {
|
|
1531
|
+
var check = cItems[ci].checked ? 'x' : ' ';
|
|
1532
|
+
checkLines.push('- [' + check + '] ' + inlineHtmlToMd(cItems[ci].text || cItems[ci].content || ''));
|
|
1533
|
+
}
|
|
1534
|
+
parts.push(checkLines.join('\n'));
|
|
1535
|
+
break;
|
|
1536
|
+
case 'code':
|
|
1537
|
+
parts.push('```\n' + (b.data.code || '') + '\n```');
|
|
1538
|
+
break;
|
|
1539
|
+
case 'quote':
|
|
1540
|
+
var qText = inlineHtmlToMd(b.data.text || '');
|
|
1541
|
+
var qLines = qText.split('\n');
|
|
1542
|
+
var quoteParts = [];
|
|
1543
|
+
for (var qi = 0; qi < qLines.length; qi++) {
|
|
1544
|
+
quoteParts.push('> ' + qLines[qi]);
|
|
1545
|
+
}
|
|
1546
|
+
parts.push(quoteParts.join('\n'));
|
|
1547
|
+
break;
|
|
1548
|
+
case 'delimiter':
|
|
1549
|
+
parts.push('---');
|
|
1550
|
+
break;
|
|
1551
|
+
default:
|
|
1552
|
+
if (b.data && b.data.text) parts.push(inlineHtmlToMd(b.data.text));
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
return parts.join('\n\n');
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
/* ── Editor.js Instance Management ──────────────────────── */
|
|
1559
|
+
function initEditor(holderId, markdown, options) {
|
|
1560
|
+
if (typeof EditorJS === 'undefined') {
|
|
1561
|
+
var el = document.getElementById(holderId);
|
|
1562
|
+
if (el) {
|
|
1563
|
+
var ta = document.createElement('textarea');
|
|
1564
|
+
ta.value = markdown || '';
|
|
1565
|
+
ta.className = 'editor-fallback-textarea';
|
|
1566
|
+
ta.placeholder = (options && options.placeholder) || 'Type / for commands...';
|
|
1567
|
+
ta.id = holderId + '-fallback';
|
|
1568
|
+
el.appendChild(ta);
|
|
1569
|
+
}
|
|
1570
|
+
return { _fallback: true, _holderId: holderId };
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
var opts = options || {};
|
|
1574
|
+
var tools = {};
|
|
1575
|
+
|
|
1576
|
+
if (typeof Header !== 'undefined') {
|
|
1577
|
+
tools.header = {
|
|
1578
|
+
class: Header,
|
|
1579
|
+
inlineToolbar: true,
|
|
1580
|
+
config: { levels: [1, 2, 3, 4], defaultLevel: 2 },
|
|
1581
|
+
};
|
|
1582
|
+
}
|
|
1583
|
+
if (typeof EditorjsList !== 'undefined') tools.list = { class: EditorjsList, inlineToolbar: true, config: { maxLevel: 3 } };
|
|
1584
|
+
if (typeof CodeTool !== 'undefined') tools.code = { class: CodeTool };
|
|
1585
|
+
if (typeof Quote !== 'undefined') tools.quote = { class: Quote, inlineToolbar: true, config: { quotePlaceholder: 'Write a quote...', captionPlaceholder: '' } };
|
|
1586
|
+
if (typeof Delimiter !== 'undefined') tools.delimiter = { class: Delimiter };
|
|
1587
|
+
if (typeof Marker !== 'undefined') tools.marker = { class: Marker };
|
|
1588
|
+
if (typeof InlineCode !== 'undefined') tools.inlineCode = { class: InlineCode };
|
|
1589
|
+
|
|
1590
|
+
var blocks = markdownToBlocks(markdown || '');
|
|
1591
|
+
|
|
1592
|
+
var editor = new EditorJS({
|
|
1593
|
+
holder: holderId,
|
|
1594
|
+
tools: tools,
|
|
1595
|
+
data: { blocks: blocks },
|
|
1596
|
+
placeholder: opts.placeholder || 'Type / for commands...',
|
|
1597
|
+
minHeight: opts.minHeight || 0,
|
|
1598
|
+
autofocus: opts.autofocus || false,
|
|
1599
|
+
onChange: opts.onChange || function() {},
|
|
1600
|
+
defaultBlock: 'paragraph',
|
|
1601
|
+
inlineToolbar: ['bold', 'italic', 'link', 'marker', 'inlineCode'],
|
|
1602
|
+
});
|
|
1603
|
+
|
|
1604
|
+
return editor;
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
async function getEditorMarkdown(editorInstance) {
|
|
1608
|
+
if (!editorInstance) return '';
|
|
1609
|
+
if (editorInstance._fallback) {
|
|
1610
|
+
var ta = document.getElementById(editorInstance._holderId + '-fallback');
|
|
1611
|
+
return ta ? ta.value : '';
|
|
1612
|
+
}
|
|
1613
|
+
try {
|
|
1614
|
+
var data = await editorInstance.save();
|
|
1615
|
+
return blocksToMarkdown(data.blocks);
|
|
1616
|
+
} catch (e) {
|
|
1617
|
+
return '';
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
function destroyEditor(editorInstance) {
|
|
1622
|
+
if (!editorInstance) return;
|
|
1623
|
+
if (editorInstance._fallback) return;
|
|
1624
|
+
if (typeof editorInstance.destroy === 'function') {
|
|
1625
|
+
try { editorInstance.destroy(); } catch (e) { /* ignore */ }
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1629
|
+
/* ── Simple Markdown → HTML Renderer (readonly mode) ──── */
|
|
1630
|
+
function simpleMarkdownRender(md) {
|
|
1631
|
+
if (!md) return '';
|
|
1632
|
+
var lines = md.split('\n');
|
|
1633
|
+
var html = '';
|
|
1634
|
+
var inCode = false;
|
|
1635
|
+
var inList = false;
|
|
1636
|
+
var listType = '';
|
|
1637
|
+
|
|
1638
|
+
for (var i = 0; i < lines.length; i++) {
|
|
1639
|
+
var line = lines[i];
|
|
1640
|
+
|
|
1641
|
+
if (line.trim().startsWith('```')) {
|
|
1642
|
+
if (inCode) { html += '</code></pre>'; inCode = false; }
|
|
1643
|
+
else { html += '<pre><code>'; inCode = true; }
|
|
1644
|
+
continue;
|
|
1645
|
+
}
|
|
1646
|
+
if (inCode) { html += escHtml(line) + '\n'; continue; }
|
|
1647
|
+
|
|
1648
|
+
if (inList && !/^\s*[-*]\s/.test(line) && !/^\s*\d+\.\s/.test(line)) {
|
|
1649
|
+
html += '</' + listType + '>'; inList = false;
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
if (line.trim() === '') { if (!inList) html += '<br>'; continue; }
|
|
1653
|
+
if (/^---\s*$/.test(line.trim())) { html += '<hr>'; continue; }
|
|
1654
|
+
|
|
1655
|
+
var hm = line.match(/^(#{1,6})\s+(.+)$/);
|
|
1656
|
+
if (hm) { html += '<h' + hm[1].length + '>' + inlineMdToHtml(hm[2]) + '</h' + hm[1].length + '>'; continue; }
|
|
1657
|
+
|
|
1658
|
+
if (/^>\s/.test(line)) { html += '<blockquote>' + inlineMdToHtml(line.substring(2)) + '</blockquote>'; continue; }
|
|
1659
|
+
|
|
1660
|
+
if (/^\s*-\s+\[[ x]\]\s/.test(line)) {
|
|
1661
|
+
var chm = line.match(/^\s*-\s+\[([ x])\]\s+(.+)$/);
|
|
1662
|
+
if (chm) {
|
|
1663
|
+
var checked = chm[1] === 'x';
|
|
1664
|
+
html += '<div class="md-check"><span class="md-check-box' + (checked ? ' checked' : '') + '">' + (checked ? '✓' : '') + '</span> ' + inlineMdToHtml(chm[2]) + '</div>';
|
|
1665
|
+
}
|
|
1666
|
+
continue;
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
if (/^\s*[-*]\s+/.test(line)) {
|
|
1670
|
+
if (!inList || listType !== 'ul') { if (inList) html += '</' + listType + '>'; html += '<ul>'; inList = true; listType = 'ul'; }
|
|
1671
|
+
var lmatch = line.match(/^\s*[-*]\s+(.+)$/);
|
|
1672
|
+
if (lmatch) html += '<li>' + inlineMdToHtml(lmatch[1]) + '</li>';
|
|
1673
|
+
continue;
|
|
1674
|
+
}
|
|
1675
|
+
if (/^\s*\d+\.\s+/.test(line)) {
|
|
1676
|
+
if (!inList || listType !== 'ol') { if (inList) html += '</' + listType + '>'; html += '<ol>'; inList = true; listType = 'ol'; }
|
|
1677
|
+
var omatch = line.match(/^\s*\d+\.\s+(.+)$/);
|
|
1678
|
+
if (omatch) html += '<li>' + inlineMdToHtml(omatch[1]) + '</li>';
|
|
1679
|
+
continue;
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1682
|
+
html += '<p>' + inlineMdToHtml(line) + '</p>';
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
if (inCode) html += '</code></pre>';
|
|
1686
|
+
if (inList) html += '</' + listType + '>';
|
|
1687
|
+
return html;
|
|
590
1688
|
}
|
|
591
1689
|
|
|
1690
|
+
|
|
592
1691
|
/* ══════════════════════════════════════════════════════════════
|
|
593
1692
|
BOARD VIEW
|
|
594
1693
|
══════════════════════════════════════════════════════════════ */
|
|
@@ -610,7 +1709,11 @@ function renderBoardFilters() {
|
|
|
610
1709
|
arr.map(function(v) { return '<option value="' + escHtml(v) + '"' + (boardFilters[key] === v ? ' selected' : '') + '>' + escHtml(v) + '</option>'; }).join('') + '</select>';
|
|
611
1710
|
}
|
|
612
1711
|
|
|
613
|
-
|
|
1712
|
+
var createBtn = isSourceWritable() ?
|
|
1713
|
+
'<button class="btn btn-sm btn-create" id="board-create-btn">+ New ' + escHtml(ENTITY_NAMES.task ? ENTITY_NAMES.task.singular : 'Task') + '</button>' : '';
|
|
1714
|
+
|
|
1715
|
+
c.innerHTML = createBtn +
|
|
1716
|
+
'<input type="text" data-filter="search" placeholder="Search cards..." value="' + escHtml(boardFilters.search) + '">' +
|
|
614
1717
|
'<select data-filter="priority"><option value="">All Priorities</option>' +
|
|
615
1718
|
PRIORITIES.map(function(p) { return '<option value="' + p + '"' + (boardFilters.priority === p ? ' selected' : '') + '>' + (PRIORITY_LABELS[p] || p) + '</option>'; }).join('') + '</select>' +
|
|
616
1719
|
opts(ep, 'epic', epicPlural) + opts(ms, 'milestone', msPlural);
|
|
@@ -621,6 +1724,9 @@ function renderBoardFilters() {
|
|
|
621
1724
|
c.querySelectorAll('select[data-filter]').forEach(function(el) {
|
|
622
1725
|
el.addEventListener('change', function() { boardFilters[el.dataset.filter] = el.value; renderBoard(); });
|
|
623
1726
|
});
|
|
1727
|
+
|
|
1728
|
+
var btn = document.getElementById('board-create-btn');
|
|
1729
|
+
if (btn) btn.addEventListener('click', function() { openCreateDialog('tasks'); });
|
|
624
1730
|
}
|
|
625
1731
|
|
|
626
1732
|
function getFilteredBoardTasks() {
|
|
@@ -687,6 +1793,7 @@ function renderCard(f) {
|
|
|
687
1793
|
'<div class="card-meta">' +
|
|
688
1794
|
(f.points != null ? '<span class="pill pill-points">' + f.points + ' pts</span>' : '') +
|
|
689
1795
|
(f.epic ? '<span class="pill pill-epic" style="background:' + ec + '">' + escHtml(f.epic) + '</span>' : '') +
|
|
1796
|
+
(f.ai && Object.keys(f.ai).length > 0 ? '<span class="pill pill-ai" title="AI properties">⚡</span>' : '') +
|
|
690
1797
|
(assigned ? '<span class="card-assigned">' + escHtml(assigned) + '</span>' : '') +
|
|
691
1798
|
'</div></div>';
|
|
692
1799
|
}
|
|
@@ -769,7 +1876,11 @@ function renderTableControls() {
|
|
|
769
1876
|
return '<select data-tf="' + filter + '"><option value="">All ' + label + '</option>' +
|
|
770
1877
|
arr.map(function(v) { return '<option value="' + escHtml(v) + '"' + (tableFilters[filter] === v ? ' selected' : '') + '>' + escHtml(v) + '</option>'; }).join('') + '</select>';
|
|
771
1878
|
}
|
|
772
|
-
|
|
1879
|
+
var createBtn = isSourceWritable() ?
|
|
1880
|
+
'<button class="btn btn-sm btn-create" id="table-create-btn">+ New ' + escHtml(ENTITY_NAMES.task ? ENTITY_NAMES.task.singular : 'Task') + '</button>' : '';
|
|
1881
|
+
|
|
1882
|
+
c.innerHTML = createBtn +
|
|
1883
|
+
'<input type="text" data-tf="search" placeholder="Search ID or title..." value="' + escHtml(tableFilters.search) + '">' +
|
|
773
1884
|
opts(ms, 'milestone', msPlural) + opts(ep, 'epic', epicPlural) + opts(st, 'status', 'Statuses') + opts(sp, 'sprint', sprintPlural) +
|
|
774
1885
|
'<select data-tf="priority"><option value="">All Priorities</option>' +
|
|
775
1886
|
PRIORITIES.map(function(p) { return '<option value="' + p + '"' + (tableFilters.priority === p ? ' selected' : '') + '>' + (PRIORITY_LABELS[p] || p) + '</option>'; }).join('') + '</select>';
|
|
@@ -778,6 +1889,9 @@ function renderTableControls() {
|
|
|
778
1889
|
c.querySelectorAll('select[data-tf]').forEach(function(sel) {
|
|
779
1890
|
sel.addEventListener('change', function() { tableFilters[sel.dataset.tf] = sel.value; renderTableBody(); });
|
|
780
1891
|
});
|
|
1892
|
+
|
|
1893
|
+
var btn = document.getElementById('table-create-btn');
|
|
1894
|
+
if (btn) btn.addEventListener('click', function() { openCreateDialog('tasks'); });
|
|
781
1895
|
}
|
|
782
1896
|
|
|
783
1897
|
function getFilteredTasks() {
|
|
@@ -962,49 +2076,396 @@ function renderMetrics() {
|
|
|
962
2076
|
}
|
|
963
2077
|
|
|
964
2078
|
/* ══════════════════════════════════════════════════════════════
|
|
965
|
-
|
|
2079
|
+
OVERVIEW VIEW
|
|
966
2080
|
══════════════════════════════════════════════════════════════ */
|
|
967
|
-
|
|
2081
|
+
async function renderOverview() {
|
|
2082
|
+
var c = document.getElementById('overview-container');
|
|
2083
|
+
if (!D.loaded) { c.innerHTML = '<div class="loading-container"><div class="skeleton skeleton-card" style="height:200px"></div></div>'; return; }
|
|
968
2084
|
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
2085
|
+
// Fetch overview data
|
|
2086
|
+
var overviewMs = await fetchJson('/api/overview/milestones') || [];
|
|
2087
|
+
var overviewLinks = await fetchJson('/api/overview/links');
|
|
2088
|
+
var overviewMetrics = await fetchJson('/api/overview/metrics');
|
|
2089
|
+
|
|
2090
|
+
D.overviewLinks = overviewLinks;
|
|
2091
|
+
|
|
2092
|
+
var html = '<h2 style="margin-bottom:16px;font-size:16px;font-weight:700">Workspace Overview</h2>';
|
|
2093
|
+
|
|
2094
|
+
// Global Milestones
|
|
2095
|
+
html += '<h3 style="font-size:13px;text-transform:uppercase;letter-spacing:.04em;color:var(--text2);font-weight:600;margin:16px 0 8px">Global Milestones</h3>';
|
|
2096
|
+
if (overviewMs.length > 0) {
|
|
2097
|
+
html += overviewMs.map(function(ms) {
|
|
2098
|
+
var pct = ms.combinedProgress != null ? ms.combinedProgress : (ms.progress || 0);
|
|
2099
|
+
var tracked = ms.tracked || [];
|
|
2100
|
+
var trackedHtml = '';
|
|
2101
|
+
if (tracked.length > 0) {
|
|
2102
|
+
trackedHtml = '<div class="tracked-ms"><div class="tracked-ms-header">Tracked Sub-Milestones</div>' +
|
|
2103
|
+
tracked.map(function(t) {
|
|
2104
|
+
return '<div class="tracked-ms-item"><span class="dot" style="background:' + (t.sourceColor || 'var(--accent)') + '"></span>' +
|
|
2105
|
+
'<span style="font-size:12px;flex:1">' + escHtml(t.title || t.id || '') + '</span>' +
|
|
2106
|
+
'<div class="progress progress-accent" style="width:80px"><div class="progress-fill" style="width:' + t.progress + '%;background:' + (t.sourceColor || 'var(--accent)') + '"></div></div>' +
|
|
2107
|
+
'<span class="tracked-ms-pct">' + t.progress + '%</span></div>';
|
|
2108
|
+
}).join('') + '</div>';
|
|
2109
|
+
}
|
|
2110
|
+
return '<div class="ms-card">' +
|
|
2111
|
+
'<div class="ms-header"><h2>' + milestoneIcon(ms.status) + ' ' + escHtml(ms.title || ms.id || '') + '</h2>' +
|
|
2112
|
+
'<span class="badge ' + badgeClass('badge', ms.status) + '">' + escHtml(ms.status || '') + '</span>' +
|
|
2113
|
+
(ms.deadline ? '<span class="ms-deadline">' + fmtDate(ms.deadline) + '</span>' : '') + '</div>' +
|
|
2114
|
+
'<div class="ms-progress"><div class="progress-label"><span>' + (ms.completedCount || 0) + ' / ' + (ms.featureCount || 0) + ' tasks</span><span>' + pct + '%</span></div>' +
|
|
2115
|
+
'<div class="progress progress-lg progress-success"><div class="progress-fill" style="width:' + pct + '%"></div></div></div>' +
|
|
2116
|
+
trackedHtml + '</div>';
|
|
2117
|
+
}).join('');
|
|
2118
|
+
} else {
|
|
2119
|
+
html += '<div style="color:var(--text3);padding:8px 0">No milestones found across sources.</div>';
|
|
2120
|
+
}
|
|
975
2121
|
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
}
|
|
2122
|
+
// Cross-project links
|
|
2123
|
+
if (overviewLinks && overviewLinks.links && overviewLinks.links.length > 0) {
|
|
2124
|
+
html += '<h3 style="font-size:13px;text-transform:uppercase;letter-spacing:.04em;color:var(--text2);font-weight:600;margin:24px 0 8px">Cross-Project Links</h3>';
|
|
2125
|
+
html += '<div class="metrics-grid">';
|
|
2126
|
+
var linkGroups = {};
|
|
2127
|
+
overviewLinks.links.forEach(function(l) {
|
|
2128
|
+
var key = (l.fromSource || 'unknown');
|
|
2129
|
+
if (!linkGroups[key]) linkGroups[key] = [];
|
|
2130
|
+
linkGroups[key].push(l);
|
|
2131
|
+
});
|
|
2132
|
+
Object.keys(linkGroups).forEach(function(src) {
|
|
2133
|
+
var links = linkGroups[src];
|
|
2134
|
+
html += '<div class="metric-card"><h3>' + escHtml(src) + ' Links</h3>';
|
|
2135
|
+
html += links.map(function(l) {
|
|
2136
|
+
return '<div class="health-row"><span style="font-size:12px;font-family:var(--mono)">' + escHtml(l.from || '') + '</span>' +
|
|
2137
|
+
'<span style="color:var(--text3);margin:0 4px">→</span>' +
|
|
2138
|
+
'<span class="link-chip" data-link="' + escHtml(l.to || '') + '">' +
|
|
2139
|
+
'<span class="link-chip-dot" style="background:var(--accent)"></span>' + escHtml(l.to || '') + '</span></div>';
|
|
2140
|
+
}).join('');
|
|
2141
|
+
html += '</div>';
|
|
2142
|
+
});
|
|
2143
|
+
html += '</div>';
|
|
2144
|
+
}
|
|
981
2145
|
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
2146
|
+
// Source metrics
|
|
2147
|
+
if (overviewMetrics && overviewMetrics.sources) {
|
|
2148
|
+
html += '<h3 style="font-size:13px;text-transform:uppercase;letter-spacing:.04em;color:var(--text2);font-weight:600;margin:24px 0 8px">Source Metrics</h3>';
|
|
2149
|
+
html += '<div class="metrics-grid">';
|
|
2150
|
+
Object.keys(overviewMetrics.sources).forEach(function(key) {
|
|
2151
|
+
var m = overviewMetrics.sources[key];
|
|
2152
|
+
var pct = m.totalTasks > 0 ? Math.round((m.completedTasks / m.totalTasks) * 100) : 0;
|
|
2153
|
+
html += '<div class="metric-card" style="border-left:3px solid ' + (m.color || 'var(--accent)') + '">' +
|
|
2154
|
+
'<h3>' + escHtml(m.label || key) + '</h3>' +
|
|
2155
|
+
'<div class="health-row"><div class="health-label">Tasks</div><div class="health-val">' + m.totalTasks + '</div></div>' +
|
|
2156
|
+
'<div class="health-row"><div class="health-label">Completed</div><div class="health-val">' + m.completedTasks + '</div></div>' +
|
|
2157
|
+
'<div class="health-row"><div class="health-label">Points</div><div class="health-val">' + m.totalPoints + '</div></div>' +
|
|
2158
|
+
'<div class="progress progress-lg progress-success" style="margin-top:8px"><div class="progress-fill" style="width:' + pct + '%;background:' + (m.color || 'var(--success)') + '"></div></div>' +
|
|
2159
|
+
'</div>';
|
|
2160
|
+
});
|
|
2161
|
+
html += '</div>';
|
|
2162
|
+
}
|
|
987
2163
|
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
2164
|
+
c.innerHTML = html;
|
|
2165
|
+
|
|
2166
|
+
// Click handlers for link chips
|
|
2167
|
+
c.querySelectorAll('.link-chip[data-link]').forEach(function(chip) {
|
|
2168
|
+
chip.addEventListener('click', function() {
|
|
2169
|
+
var ref = chip.dataset.link;
|
|
2170
|
+
var parts = ref.split(':');
|
|
2171
|
+
if (parts.length === 2) {
|
|
2172
|
+
switchSource(parts[0]);
|
|
2173
|
+
// After switch, try to find and open the item
|
|
2174
|
+
setTimeout(function() {
|
|
2175
|
+
var task = D.tasks.find(function(t) { return t.id === ref || t.id === parts[1]; });
|
|
2176
|
+
if (task) openPanel('tasks', task);
|
|
2177
|
+
}, 500);
|
|
2178
|
+
}
|
|
2179
|
+
});
|
|
2180
|
+
});
|
|
2181
|
+
}
|
|
992
2182
|
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
2183
|
+
/* ══════════════════════════════════════════════════════════════
|
|
2184
|
+
NOTES VIEW — Notion-style split layout
|
|
2185
|
+
══════════════════════════════════════════════════════════════ */
|
|
2186
|
+
|
|
2187
|
+
var notesState = { notes: [], activeId: null, editor: null, dirty: false };
|
|
2188
|
+
|
|
2189
|
+
function renderNotes() {
|
|
2190
|
+
var container = document.getElementById('notes-container');
|
|
2191
|
+
if (!container) return;
|
|
2192
|
+
|
|
2193
|
+
container.innerHTML =
|
|
2194
|
+
'<div class="notes-layout">' +
|
|
2195
|
+
'<div class="notes-sidebar">' +
|
|
2196
|
+
'<div class="notes-toolbar">' +
|
|
2197
|
+
'<input type="text" placeholder="Search..." id="notes-search">' +
|
|
2198
|
+
'<button class="btn btn-sm btn-create" id="notes-new">+ New</button>' +
|
|
2199
|
+
'</div>' +
|
|
2200
|
+
'<div class="notes-list" id="notes-list"></div>' +
|
|
2201
|
+
'</div>' +
|
|
2202
|
+
'<div class="notes-editor" id="notes-editor-pane">' +
|
|
2203
|
+
'<div class="notes-empty-state">' +
|
|
2204
|
+
'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" style="width:48px;height:48px;opacity:.3;margin-bottom:12px"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>' +
|
|
2205
|
+
'<p>Select a note or create a new one</p>' +
|
|
2206
|
+
'<p style="font-size:12px;margin-top:4px;color:var(--text3)">Use <kbd>/</kbd> for block commands</p>' +
|
|
2207
|
+
'</div>' +
|
|
2208
|
+
'</div>' +
|
|
997
2209
|
'</div>';
|
|
998
2210
|
|
|
999
|
-
|
|
2211
|
+
loadNoteList();
|
|
2212
|
+
|
|
2213
|
+
document.getElementById('notes-new').addEventListener('click', createNewNote);
|
|
2214
|
+
document.getElementById('notes-search').addEventListener('input', function() {
|
|
2215
|
+
renderNoteList(this.value.toLowerCase());
|
|
2216
|
+
});
|
|
2217
|
+
}
|
|
2218
|
+
|
|
2219
|
+
async function loadNoteList() {
|
|
2220
|
+
var base = apiBase();
|
|
2221
|
+
var notes = await fetchJson(base + '/notes');
|
|
2222
|
+
notesState.notes = notes || [];
|
|
2223
|
+
renderNoteList('');
|
|
2224
|
+
}
|
|
2225
|
+
|
|
2226
|
+
function renderNoteList(filter) {
|
|
2227
|
+
var list = document.getElementById('notes-list');
|
|
2228
|
+
if (!list) return;
|
|
2229
|
+
|
|
2230
|
+
var notes = notesState.notes;
|
|
2231
|
+
if (filter) {
|
|
2232
|
+
notes = notes.filter(function(n) {
|
|
2233
|
+
return (n.title || '').toLowerCase().indexOf(filter) !== -1;
|
|
2234
|
+
});
|
|
2235
|
+
}
|
|
2236
|
+
|
|
2237
|
+
notes.sort(function(a, b) {
|
|
2238
|
+
return (b.updated || b.created || '').localeCompare(a.updated || a.created || '');
|
|
2239
|
+
});
|
|
2240
|
+
|
|
2241
|
+
if (notes.length === 0) {
|
|
2242
|
+
list.innerHTML = '<div class="notes-list-empty">No notes yet</div>';
|
|
2243
|
+
return;
|
|
2244
|
+
}
|
|
2245
|
+
|
|
2246
|
+
var html = '';
|
|
2247
|
+
for (var i = 0; i < notes.length; i++) {
|
|
2248
|
+
var n = notes[i];
|
|
2249
|
+
var active = n.id === notesState.activeId ? ' active' : '';
|
|
2250
|
+
html += '<div class="notes-list-item' + active + '" data-id="' + escHtml(n.id) + '">' +
|
|
2251
|
+
'<div class="notes-list-title">' + escHtml(n.title || 'Untitled') + '</div>' +
|
|
2252
|
+
'<div class="notes-list-date">' + escHtml(n.updated || n.created || '') + '</div>' +
|
|
2253
|
+
'</div>';
|
|
2254
|
+
}
|
|
2255
|
+
list.innerHTML = html;
|
|
2256
|
+
|
|
2257
|
+
list.querySelectorAll('.notes-list-item').forEach(function(el) {
|
|
2258
|
+
el.addEventListener('click', function() {
|
|
2259
|
+
selectNote(el.dataset.id);
|
|
2260
|
+
});
|
|
2261
|
+
});
|
|
2262
|
+
}
|
|
2263
|
+
|
|
2264
|
+
async function selectNote(id) {
|
|
2265
|
+
if (notesState.editor) {
|
|
2266
|
+
destroyEditor(notesState.editor);
|
|
2267
|
+
notesState.editor = null;
|
|
2268
|
+
}
|
|
2269
|
+
|
|
2270
|
+
notesState.activeId = id;
|
|
2271
|
+
renderNoteList(document.getElementById('notes-search') ? document.getElementById('notes-search').value.toLowerCase() : '');
|
|
2272
|
+
|
|
2273
|
+
var pane = document.getElementById('notes-editor-pane');
|
|
2274
|
+
if (!pane) return;
|
|
2275
|
+
|
|
2276
|
+
pane.innerHTML = '<div class="notes-editor-loading">Loading...</div>';
|
|
2277
|
+
|
|
2278
|
+
var base = apiBase();
|
|
2279
|
+
var note = await fetchJson(base + '/notes/' + encodeURIComponent(id));
|
|
2280
|
+
if (!note) {
|
|
2281
|
+
pane.innerHTML = '<div class="notes-empty-state"><p>Note not found</p></div>';
|
|
2282
|
+
return;
|
|
2283
|
+
}
|
|
2284
|
+
|
|
2285
|
+
pane.innerHTML =
|
|
2286
|
+
'<div class="notes-header-bar">' +
|
|
2287
|
+
'<input type="text" class="notes-title-input" id="notes-title" placeholder="Untitled" value="' + escHtml(note.title || '') + '">' +
|
|
2288
|
+
'<span class="notes-meta">' + escHtml(note.updated || note.created || '') + '</span>' +
|
|
2289
|
+
'</div>' +
|
|
2290
|
+
'<div class="notes-editorjs-wrap" id="notes-editorjs"></div>' +
|
|
2291
|
+
'<div class="notes-editor-footer">' +
|
|
2292
|
+
'<button class="btn btn-danger btn-sm" id="notes-delete">Delete</button>' +
|
|
2293
|
+
'<span style="flex:1"></span>' +
|
|
2294
|
+
'<button class="btn btn-primary" id="notes-save">Save</button>' +
|
|
2295
|
+
'</div>';
|
|
2296
|
+
|
|
2297
|
+
notesState.editor = initEditor('notes-editorjs', note.content || '', {
|
|
2298
|
+
placeholder: 'Type / for commands...',
|
|
2299
|
+
autofocus: true,
|
|
2300
|
+
onChange: function() { notesState.dirty = true; }
|
|
2301
|
+
});
|
|
2302
|
+
notesState.dirty = false;
|
|
2303
|
+
|
|
2304
|
+
document.getElementById('notes-save').addEventListener('click', saveCurrentNote);
|
|
2305
|
+
document.getElementById('notes-delete').addEventListener('click', function() {
|
|
2306
|
+
deleteCurrentNote(id);
|
|
2307
|
+
});
|
|
2308
|
+
|
|
2309
|
+
// Ctrl/Cmd+S to save
|
|
2310
|
+
pane.addEventListener('keydown', function(e) {
|
|
2311
|
+
if ((e.metaKey || e.ctrlKey) && e.key === 's') {
|
|
2312
|
+
e.preventDefault();
|
|
2313
|
+
saveCurrentNote();
|
|
2314
|
+
}
|
|
2315
|
+
});
|
|
2316
|
+
}
|
|
2317
|
+
|
|
2318
|
+
async function saveCurrentNote() {
|
|
2319
|
+
if (!notesState.activeId) return;
|
|
2320
|
+
|
|
2321
|
+
var titleEl = document.getElementById('notes-title');
|
|
2322
|
+
var title = titleEl ? titleEl.value : '';
|
|
2323
|
+
var content = notesState.editor ? await getEditorMarkdown(notesState.editor) : '';
|
|
2324
|
+
|
|
2325
|
+
var updates = {};
|
|
2326
|
+
var current = notesState.notes.find(function(n) { return n.id === notesState.activeId; });
|
|
2327
|
+
if (title && (!current || title !== current.title)) updates.title = title;
|
|
2328
|
+
updates.content = content;
|
|
2329
|
+
updates.updated = new Date().toISOString().split('T')[0];
|
|
2330
|
+
|
|
2331
|
+
var ok = await patchItem('notes', notesState.activeId, updates);
|
|
2332
|
+
if (ok) {
|
|
2333
|
+
notesState.dirty = false;
|
|
2334
|
+
await loadNoteList();
|
|
2335
|
+
}
|
|
2336
|
+
}
|
|
2337
|
+
|
|
2338
|
+
async function createNewNote() {
|
|
2339
|
+
var result = await createItem('notes', { title: 'Untitled', content: '' });
|
|
2340
|
+
if (result && result.id) {
|
|
2341
|
+
await loadNoteList();
|
|
2342
|
+
selectNote(result.id);
|
|
2343
|
+
}
|
|
2344
|
+
}
|
|
2345
|
+
|
|
2346
|
+
async function deleteCurrentNote(id) {
|
|
2347
|
+
if (!confirm('Delete this note?')) return;
|
|
2348
|
+
var ok = await deleteItem('notes', id);
|
|
2349
|
+
if (ok) {
|
|
2350
|
+
if (notesState.editor) {
|
|
2351
|
+
destroyEditor(notesState.editor);
|
|
2352
|
+
notesState.editor = null;
|
|
2353
|
+
}
|
|
2354
|
+
notesState.activeId = null;
|
|
2355
|
+
await loadNoteList();
|
|
2356
|
+
var pane = document.getElementById('notes-editor-pane');
|
|
2357
|
+
if (pane) {
|
|
2358
|
+
pane.innerHTML =
|
|
2359
|
+
'<div class="notes-empty-state">' +
|
|
2360
|
+
'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" style="width:48px;height:48px;opacity:.3;margin-bottom:12px"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>' +
|
|
2361
|
+
'<p>Select a note or create a new one</p>' +
|
|
2362
|
+
'</div>';
|
|
2363
|
+
}
|
|
2364
|
+
}
|
|
2365
|
+
}
|
|
2366
|
+
|
|
2367
|
+
|
|
2368
|
+
/* ══════════════════════════════════════════════════════════════
|
|
2369
|
+
CRUD HELPERS
|
|
2370
|
+
══════════════════════════════════════════════════════════════ */
|
|
2371
|
+
function isSourceWritable() {
|
|
2372
|
+
if (!hasWorkspace() || !D.activeSource) return true;
|
|
2373
|
+
var src = D.config.workspace.sources.find(function(s) { return s.name === D.activeSource; });
|
|
2374
|
+
return src ? !src.readonly : true;
|
|
2375
|
+
}
|
|
2376
|
+
|
|
2377
|
+
function openCreateDialog(collection) {
|
|
2378
|
+
var item = { _isNew: true };
|
|
2379
|
+
|
|
2380
|
+
if (collection === 'tasks') {
|
|
2381
|
+
var ms = D.milestones.length > 0 ? (D.milestones[0].id || D.milestones[0]._dir || '') : '';
|
|
2382
|
+
var ep = D.epics.length > 0 ? (D.epics[0]._dir || D.epics[0].id || '') : '';
|
|
2383
|
+
item.title = '';
|
|
2384
|
+
item.status = 'backlog';
|
|
2385
|
+
item.priority = '';
|
|
2386
|
+
item.points = null;
|
|
2387
|
+
item.assigned = '';
|
|
2388
|
+
item.sprint = '';
|
|
2389
|
+
item.milestone = ms;
|
|
2390
|
+
item.epic = ep;
|
|
2391
|
+
item.content = '';
|
|
2392
|
+
} else if (collection === 'milestones') {
|
|
2393
|
+
item.title = '';
|
|
2394
|
+
item.status = 'planned';
|
|
2395
|
+
item.deadline = '';
|
|
2396
|
+
item.content = '';
|
|
2397
|
+
} else if (collection === 'epics') {
|
|
2398
|
+
var ms = D.milestones.length > 0 ? (D.milestones[0]._dir || D.milestones[0].id || '') : '';
|
|
2399
|
+
item.title = '';
|
|
2400
|
+
item.status = 'active';
|
|
2401
|
+
item.priority = '';
|
|
2402
|
+
item.milestone = ms;
|
|
2403
|
+
item.content = '';
|
|
2404
|
+
}
|
|
2405
|
+
|
|
2406
|
+
panelState = { open: true, type: collection, item: item, isCreate: true, editor: null };
|
|
2407
|
+
renderPanel();
|
|
2408
|
+
document.getElementById('detail-panel').classList.add('open');
|
|
2409
|
+
document.getElementById('panel-overlay').classList.add('open');
|
|
2410
|
+
}
|
|
2411
|
+
|
|
2412
|
+
/* ══════════════════════════════════════════════════════════════
|
|
2413
|
+
DETAIL PANEL — Notion-style: form top + editor bottom
|
|
2414
|
+
══════════════════════════════════════════════════════════════ */
|
|
2415
|
+
var panelState = { open: false, type: null, item: null, isCreate: false, editor: null };
|
|
2416
|
+
|
|
2417
|
+
function openPanel(type, item) {
|
|
2418
|
+
panelState = { open: true, type: type, item: JSON.parse(JSON.stringify(item)), isCreate: false, editor: null };
|
|
2419
|
+
renderPanel();
|
|
2420
|
+
document.getElementById('detail-panel').classList.add('open');
|
|
2421
|
+
document.getElementById('panel-overlay').classList.add('open');
|
|
2422
|
+
}
|
|
2423
|
+
|
|
2424
|
+
function closePanel() {
|
|
2425
|
+
if (panelState.editor) { destroyEditor(panelState.editor); panelState.editor = null; }
|
|
2426
|
+
panelState = { open: false, type: null, item: null, editor: null };
|
|
2427
|
+
document.getElementById('detail-panel').classList.remove('open');
|
|
2428
|
+
document.getElementById('panel-overlay').classList.remove('open');
|
|
2429
|
+
}
|
|
2430
|
+
|
|
2431
|
+
function renderPanel() {
|
|
2432
|
+
var panel = document.getElementById('detail-panel');
|
|
2433
|
+
var t = panelState.type;
|
|
2434
|
+
var item = panelState.item;
|
|
2435
|
+
var isCreate = panelState.isCreate;
|
|
2436
|
+
if (!item) return;
|
|
1000
2437
|
|
|
1001
|
-
|
|
1002
|
-
html += '<div class="panel-field"><label>Title</label><input type="text" id="p-title" value="' + escHtml(item.title || '') + '"></div>';
|
|
2438
|
+
var isReadonly = !isCreate && item.readonly;
|
|
1003
2439
|
|
|
1004
|
-
|
|
1005
|
-
|
|
2440
|
+
var typeLabel;
|
|
2441
|
+
if (t === 'tasks') typeLabel = ENTITY_NAMES.task ? ENTITY_NAMES.task.singular : 'Task';
|
|
2442
|
+
else if (t === 'epics') typeLabel = ENTITY_NAMES.epic ? ENTITY_NAMES.epic.singular : 'Epic';
|
|
2443
|
+
else typeLabel = ENTITY_NAMES.milestone ? ENTITY_NAMES.milestone.singular : 'Milestone';
|
|
1006
2444
|
|
|
1007
|
-
//
|
|
2445
|
+
// ── Header ──
|
|
2446
|
+
var html = '<div class="panel-header">' +
|
|
2447
|
+
'<span class="panel-type">' + escHtml(isCreate ? 'New ' + typeLabel : typeLabel) + '</span>' +
|
|
2448
|
+
'<span class="panel-item-id">' + escHtml(isCreate ? '' : (item.id || '')) + '</span>';
|
|
2449
|
+
|
|
2450
|
+
if (item.source && item.sourceColor) {
|
|
2451
|
+
html += '<span class="pill" style="background:' + item.sourceColor + '20;color:' + item.sourceColor + ';font-size:10px">' + escHtml(item.sourceLabel || item.source) + '</span>';
|
|
2452
|
+
}
|
|
2453
|
+
|
|
2454
|
+
html += '<button class="panel-close" id="panel-close-btn">×</button></div>';
|
|
2455
|
+
|
|
2456
|
+
// ── Title (large, Notion-style) ──
|
|
2457
|
+
html += '<div class="panel-title-wrap">' +
|
|
2458
|
+
'<input type="text" id="p-title" class="panel-title-input" value="' + escHtml(item.title || '') + '" placeholder="Untitled"' + (isReadonly ? ' disabled' : '') + '>' +
|
|
2459
|
+
'</div>';
|
|
2460
|
+
|
|
2461
|
+
// ── Properties (compact inline) ──
|
|
2462
|
+
html += '<div class="panel-properties">';
|
|
2463
|
+
|
|
2464
|
+
// Properties toggle header
|
|
2465
|
+
html += '<div class="prop-row prop-row-toggle" id="props-toggle"><span class="prop-label" style="font-weight:600"><span class="ai-toggle-arrow" id="props-arrow">▶</span> Properties</span><div class="prop-value"></div></div>';
|
|
2466
|
+
html += '<div class="props-fields-body" id="props-fields-body" style="display:none">';
|
|
2467
|
+
|
|
2468
|
+
// Status
|
|
1008
2469
|
var statusOptions, statusLabels;
|
|
1009
2470
|
if (t === 'tasks' && D.config && D.config.statuses && D.config.statuses.task) {
|
|
1010
2471
|
statusOptions = D.config.statuses.task.map(function(s) { return s.key; });
|
|
@@ -1026,61 +2487,322 @@ function renderPanel() {
|
|
|
1026
2487
|
statusLabels = { planned: 'Planned', active: 'Active', completed: 'Completed', cancelled: 'Cancelled' };
|
|
1027
2488
|
}
|
|
1028
2489
|
|
|
1029
|
-
html += '<div class="
|
|
1030
|
-
'<select id="p-status">' + statusOptions.map(function(s) {
|
|
2490
|
+
html += '<div class="prop-row"><span class="prop-label">Status</span><div class="prop-value"><span id="p-status-icon">' + statusIcon(item.status) + '</span>' +
|
|
2491
|
+
'<select id="p-status"' + (isReadonly ? ' disabled' : '') + '>' + statusOptions.map(function(s) {
|
|
1031
2492
|
return '<option value="' + s + '"' + (item.status === s ? ' selected' : '') + '>' + (statusLabels[s] || s) + '</option>';
|
|
1032
2493
|
}).join('') + '</select></div></div>';
|
|
1033
2494
|
|
|
1034
|
-
// Priority
|
|
1035
2495
|
if (t === 'tasks' || t === 'epics') {
|
|
1036
|
-
html += '<div class="
|
|
1037
|
-
'<select id="p-priority"><option value="">None</option>' + PRIORITIES.map(function(p) {
|
|
2496
|
+
html += '<div class="prop-row"><span class="prop-label">Priority</span><div class="prop-value"><span id="p-priority-icon">' + priorityIcon(item.priority) + '</span>' +
|
|
2497
|
+
'<select id="p-priority"' + (isReadonly ? ' disabled' : '') + '><option value="">None</option>' + PRIORITIES.map(function(p) {
|
|
1038
2498
|
return '<option value="' + p + '"' + (item.priority === p ? ' selected' : '') + '>' + (PRIORITY_LABELS[p] || p) + '</option>';
|
|
1039
2499
|
}).join('') + '</select></div></div>';
|
|
1040
2500
|
}
|
|
1041
2501
|
|
|
1042
|
-
html += '</div>'; // close panel-props
|
|
1043
|
-
|
|
1044
|
-
// Type-specific fields
|
|
1045
2502
|
if (t === 'tasks') {
|
|
1046
|
-
html += '<div class="
|
|
1047
|
-
html += '<div class="panel-field"><label>Points</label><input type="number" id="p-points" min="0" max="100" value="' + (item.points != null ? item.points : '') + '"></div>';
|
|
2503
|
+
html += '<div class="prop-row"><span class="prop-label">Points</span><div class="prop-value"><input type="number" id="p-points" min="0" max="100" value="' + (item.points != null ? item.points : '') + '" placeholder="0"' + (isReadonly ? ' disabled' : '') + '></div></div>';
|
|
1048
2504
|
|
|
1049
2505
|
var assignedVal = Array.isArray(item.assigned) ? item.assigned.join(', ') : (item.assigned || '');
|
|
1050
|
-
html += '<div class="
|
|
1051
|
-
html += '</div>';
|
|
2506
|
+
html += '<div class="prop-row"><span class="prop-label">Assigned</span><div class="prop-value"><input type="text" id="p-assigned" value="' + escHtml(assignedVal) + '" placeholder="Unassigned"' + (isReadonly ? ' disabled' : '') + '></div></div>';
|
|
1052
2507
|
|
|
1053
|
-
html += '<div class="
|
|
1054
|
-
// Sprint
|
|
1055
|
-
html += '<div class="panel-field"><label>Sprint</label><select id="p-sprint"><option value="">None</option>' +
|
|
2508
|
+
html += '<div class="prop-row"><span class="prop-label">Sprint</span><div class="prop-value"><select id="p-sprint"' + (isReadonly ? ' disabled' : '') + '><option value="">None</option>' +
|
|
1056
2509
|
D.allSprints.map(function(s) { return '<option value="' + escHtml(s.id || '') + '"' + (item.sprint === s.id ? ' selected' : '') + '>' + escHtml(s.id || '') + '</option>'; }).join('') +
|
|
1057
|
-
'</select></div>';
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
2510
|
+
'</select></div></div>';
|
|
2511
|
+
|
|
2512
|
+
if (isCreate) {
|
|
2513
|
+
html += '<div class="prop-row"><span class="prop-label">Milestone</span><div class="prop-value"><select id="p-milestone">' +
|
|
2514
|
+
D.milestones.map(function(m) { return '<option value="' + escHtml(m._dir || m.id || '') + '"' + (item.milestone === (m._dir || m.id) ? ' selected' : '') + '>' + escHtml(m.title || m.id || '') + '</option>'; }).join('') +
|
|
2515
|
+
'</select></div></div>';
|
|
2516
|
+
html += '<div class="prop-row"><span class="prop-label">Epic</span><div class="prop-value"><select id="p-epic-select">' +
|
|
2517
|
+
D.epics.map(function(e) { return '<option value="' + escHtml(e._dir || e.id || '') + '"' + (item.epic === (e._dir || e.id) ? ' selected' : '') + '>' + escHtml(e.title || e.id || '') + '</option>'; }).join('') +
|
|
2518
|
+
'</select></div></div>';
|
|
2519
|
+
} else {
|
|
2520
|
+
html += '<div class="prop-row"><span class="prop-label">Epic</span><div class="prop-value"><span class="prop-text">' + escHtml(item.epic || 'None') + '</span></div></div>';
|
|
2521
|
+
}
|
|
1062
2522
|
}
|
|
1063
2523
|
|
|
1064
2524
|
if (t === 'milestones') {
|
|
1065
|
-
html += '<div class="
|
|
2525
|
+
html += '<div class="prop-row"><span class="prop-label">Deadline</span><div class="prop-value"><input type="date" id="p-deadline" value="' + fmtDate(item.deadline) + '"' + (isReadonly ? ' disabled' : '') + '></div></div>';
|
|
1066
2526
|
}
|
|
1067
2527
|
|
|
1068
|
-
|
|
1069
|
-
|
|
2528
|
+
if (isCreate && (t === 'epics' || t === 'milestones')) {
|
|
2529
|
+
if (t === 'epics') {
|
|
2530
|
+
html += '<div class="prop-row"><span class="prop-label">Milestone</span><div class="prop-value"><select id="p-milestone">' +
|
|
2531
|
+
D.milestones.map(function(m) { return '<option value="' + escHtml(m._dir || m.id || '') + '">' + escHtml(m.title || m.id || '') + '</option>'; }).join('') +
|
|
2532
|
+
'</select></div></div>';
|
|
2533
|
+
}
|
|
2534
|
+
}
|
|
1070
2535
|
|
|
1071
|
-
|
|
2536
|
+
// Links
|
|
2537
|
+
if (t === 'tasks' && !isCreate && item.links && Array.isArray(item.links) && item.links.length > 0) {
|
|
2538
|
+
html += '<div class="prop-row"><span class="prop-label">Links</span><div class="prop-value"><div class="link-chips">';
|
|
2539
|
+
item.links.forEach(function(link) {
|
|
2540
|
+
var parts = String(link).split(':');
|
|
2541
|
+
var srcName = parts.length === 2 ? parts[0] : null;
|
|
2542
|
+
var srcColor = 'var(--accent)';
|
|
2543
|
+
if (srcName && D.config && D.config.workspace && D.config.workspace.sources) {
|
|
2544
|
+
var src = D.config.workspace.sources.find(function(s) { return s.name === srcName; });
|
|
2545
|
+
if (src && src.color) srcColor = src.color;
|
|
2546
|
+
}
|
|
2547
|
+
html += '<span class="link-chip" data-link="' + escHtml(String(link)) + '">' +
|
|
2548
|
+
'<span class="link-chip-dot" style="background:' + srcColor + '"></span>' +
|
|
2549
|
+
escHtml(String(link)) + '</span>';
|
|
2550
|
+
});
|
|
2551
|
+
html += '</div></div></div>';
|
|
2552
|
+
}
|
|
1072
2553
|
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
2554
|
+
// Reverse links
|
|
2555
|
+
if (t === 'tasks' && !isCreate && D.overviewLinks && D.overviewLinks.reverseLinks && item.id) {
|
|
2556
|
+
var reverseRefs = D.overviewLinks.reverseLinks[item.id] || [];
|
|
2557
|
+
if (reverseRefs.length > 0) {
|
|
2558
|
+
html += '<div class="prop-row"><span class="prop-label">Referenced By</span><div class="prop-value"><div class="link-chips">';
|
|
2559
|
+
reverseRefs.forEach(function(ref) {
|
|
2560
|
+
var srcColor = ref.sourceColor || 'var(--accent)';
|
|
2561
|
+
html += '<span class="link-chip" data-link="' + escHtml(ref.from || '') + '">' +
|
|
2562
|
+
'<span class="link-chip-dot" style="background:' + srcColor + '"></span>' +
|
|
2563
|
+
escHtml(ref.from || '') + '</span>';
|
|
2564
|
+
});
|
|
2565
|
+
html += '</div></div></div>';
|
|
2566
|
+
}
|
|
2567
|
+
}
|
|
2568
|
+
|
|
2569
|
+
html += '</div>'; // close props-fields-body
|
|
2570
|
+
|
|
2571
|
+
// ── AI Properties — integrated as prop-rows ──
|
|
2572
|
+
var aiData = item.ai || {};
|
|
2573
|
+
var aiOwnData = item.aiOwn || {};
|
|
2574
|
+
var aiFieldDefs = [
|
|
2575
|
+
{ key: 'skills', label: 'Skills', icon: '<svg class="icon icon-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2L2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/></svg>' },
|
|
2576
|
+
{ key: 'agents', label: 'Agents', icon: '<svg class="icon icon-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="11" width="18" height="10" rx="2"/><circle cx="12" cy="5" r="3"/><circle cx="9" cy="16" r="1" fill="currentColor"/><circle cx="15" cy="16" r="1" fill="currentColor"/></svg>' },
|
|
2577
|
+
{ key: 'mcps', label: 'MCPs', icon: '<svg class="icon icon-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 4h16v16H4z"/><path d="M9 9h6v6H9z"/><path d="M9 1v3M15 1v3M9 20v3M15 20v3M1 9h3M1 15h3M20 9h3M20 15h3"/></svg>' },
|
|
2578
|
+
{ key: 'commands', label: 'Commands', icon: '<svg class="icon icon-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/></svg>' },
|
|
2579
|
+
{ key: 'context', label: 'Context', icon: '<svg class="icon icon-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>' }
|
|
2580
|
+
];
|
|
2581
|
+
var aiTotalCount = 0;
|
|
2582
|
+
for (var ai = 0; ai < aiFieldDefs.length; ai++) {
|
|
2583
|
+
var arr = aiData[aiFieldDefs[ai].key];
|
|
2584
|
+
if (arr) aiTotalCount += arr.length;
|
|
2585
|
+
}
|
|
2586
|
+
var showAiSection = aiTotalCount > 0 || !isReadonly;
|
|
2587
|
+
if (showAiSection) {
|
|
2588
|
+
// AI divider row — clickable toggle
|
|
2589
|
+
html += '<div class="prop-row prop-row-divider prop-row-toggle" id="ai-toggle"><span class="prop-label" style="font-weight:600"><span class="ai-toggle-arrow" id="ai-arrow">▶</span> AI</span><div class="prop-value">';
|
|
2590
|
+
if (aiTotalCount > 0) html += '<span class="pill" style="font-size:10px;padding:1px 6px">' + aiTotalCount + '</span>';
|
|
2591
|
+
html += '</div></div>';
|
|
2592
|
+
html += '<div class="ai-fields-body" id="ai-fields-body" style="display:none">';
|
|
2593
|
+
for (var ag = 0; ag < aiFieldDefs.length; ag++) {
|
|
2594
|
+
var def = aiFieldDefs[ag];
|
|
2595
|
+
var resolved = aiData[def.key] || [];
|
|
2596
|
+
var own = aiOwnData[def.key] || [];
|
|
2597
|
+
var inherited = resolved.filter(function(v) { return own.indexOf(v) === -1; });
|
|
2598
|
+
if (isReadonly && resolved.length === 0) continue;
|
|
2599
|
+
html += '<div class="prop-row"><span class="prop-label">' + def.icon + ' ' + escHtml(def.label) + '</span><div class="prop-value">';
|
|
2600
|
+
if (isReadonly) {
|
|
2601
|
+
// Readonly: just show tags
|
|
2602
|
+
for (var ci = 0; ci < resolved.length; ci++) {
|
|
2603
|
+
var isOwn = own.indexOf(resolved[ci]) !== -1;
|
|
2604
|
+
html += '<span class="ai-tag' + (isOwn ? ' ai-tag-own' : ' ai-tag-inherited') + '">' + escHtml(resolved[ci]) + '</span>';
|
|
2605
|
+
}
|
|
2606
|
+
} else {
|
|
2607
|
+
// Editable: tag input container
|
|
2608
|
+
html += '<div class="ai-tag-input" data-ai-field="' + def.key + '">';
|
|
2609
|
+
// Inherited tags (dashed, no remove)
|
|
2610
|
+
for (var ii = 0; ii < inherited.length; ii++) {
|
|
2611
|
+
html += '<span class="ai-tag ai-tag-inherited">' + escHtml(inherited[ii]) + '</span>';
|
|
2612
|
+
}
|
|
2613
|
+
// Own tags (accent, with remove button)
|
|
2614
|
+
for (var oi = 0; oi < own.length; oi++) {
|
|
2615
|
+
html += '<span class="ai-tag ai-tag-own"><span class="ai-tag-val">' + escHtml(own[oi]) + '</span><button class="ai-tag-remove" type="button">×</button></span>';
|
|
2616
|
+
}
|
|
2617
|
+
// Text input for adding
|
|
2618
|
+
html += '<input type="text" class="ai-tag-text" placeholder="Add...">';
|
|
2619
|
+
// Autocomplete dropdown
|
|
2620
|
+
html += '<div class="ai-autocomplete"></div>';
|
|
2621
|
+
html += '</div>';
|
|
2622
|
+
}
|
|
2623
|
+
html += '</div></div>';
|
|
2624
|
+
}
|
|
2625
|
+
html += '</div>'; // close ai-fields-body
|
|
2626
|
+
}
|
|
2627
|
+
|
|
2628
|
+
html += '</div>'; // close panel-properties
|
|
2629
|
+
|
|
2630
|
+
// ── Content Editor (fills remaining space) ──
|
|
2631
|
+
if (isReadonly) {
|
|
2632
|
+
html += '<div class="panel-content-area"><div id="p-content-editor" class="panel-editor-zone md-rendered">' + simpleMarkdownRender(item.content || '') + '</div></div>';
|
|
2633
|
+
} else {
|
|
2634
|
+
html += '<div class="panel-content-area"><div id="p-content-editor" class="panel-editor-zone"></div></div>';
|
|
2635
|
+
}
|
|
2636
|
+
|
|
2637
|
+
// ── Footer ──
|
|
2638
|
+
html += '<div class="panel-footer">';
|
|
2639
|
+
if (!isCreate && !isReadonly && isSourceWritable()) {
|
|
2640
|
+
html += '<button class="btn btn-danger btn-sm" id="panel-archive-btn">Archive</button>';
|
|
2641
|
+
}
|
|
2642
|
+
html += '<span style="flex:1"></span>';
|
|
2643
|
+
html += '<button class="btn" id="panel-cancel-btn">Cancel</button>';
|
|
2644
|
+
if (!isReadonly) {
|
|
2645
|
+
html += '<button class="btn btn-primary" id="panel-save-btn">' + (isCreate ? 'Create' : 'Save') + '</button>';
|
|
2646
|
+
}
|
|
2647
|
+
html += '</div>';
|
|
1077
2648
|
|
|
1078
2649
|
panel.innerHTML = html;
|
|
1079
2650
|
|
|
2651
|
+
// Initialize Editor.js for content (if not readonly)
|
|
2652
|
+
if (!isReadonly) {
|
|
2653
|
+
panelState.editor = initEditor('p-content-editor', item.content || '', {
|
|
2654
|
+
placeholder: 'Type / for commands, or start writing...',
|
|
2655
|
+
minHeight: 0,
|
|
2656
|
+
});
|
|
2657
|
+
}
|
|
2658
|
+
|
|
1080
2659
|
// Event listeners
|
|
1081
2660
|
document.getElementById('panel-close-btn').addEventListener('click', closePanel);
|
|
1082
2661
|
document.getElementById('panel-cancel-btn').addEventListener('click', closePanel);
|
|
1083
|
-
document.getElementById('panel-save-btn')
|
|
2662
|
+
var saveBtn = document.getElementById('panel-save-btn');
|
|
2663
|
+
if (saveBtn) saveBtn.addEventListener('click', isCreate ? saveCreatePanel : savePanel);
|
|
2664
|
+
|
|
2665
|
+
var archiveBtn = document.getElementById('panel-archive-btn');
|
|
2666
|
+
if (archiveBtn) {
|
|
2667
|
+
archiveBtn.addEventListener('click', function() {
|
|
2668
|
+
if (!confirm('Archive this ' + typeLabel.toLowerCase() + '?')) return;
|
|
2669
|
+
deleteItem(t, item.id).then(function(ok) {
|
|
2670
|
+
if (ok) { closePanel(); refreshData(); }
|
|
2671
|
+
});
|
|
2672
|
+
});
|
|
2673
|
+
}
|
|
2674
|
+
|
|
2675
|
+
// Properties section toggle
|
|
2676
|
+
var propsToggle = document.getElementById('props-toggle');
|
|
2677
|
+
if (propsToggle) {
|
|
2678
|
+
propsToggle.addEventListener('click', function() {
|
|
2679
|
+
var body = document.getElementById('props-fields-body');
|
|
2680
|
+
var arrow = document.getElementById('props-arrow');
|
|
2681
|
+
if (body.style.display === 'none') {
|
|
2682
|
+
body.style.display = '';
|
|
2683
|
+
arrow.innerHTML = '▼';
|
|
2684
|
+
} else {
|
|
2685
|
+
body.style.display = 'none';
|
|
2686
|
+
arrow.innerHTML = '▶';
|
|
2687
|
+
}
|
|
2688
|
+
});
|
|
2689
|
+
}
|
|
2690
|
+
|
|
2691
|
+
// AI section toggle
|
|
2692
|
+
var aiToggle = document.getElementById('ai-toggle');
|
|
2693
|
+
if (aiToggle) {
|
|
2694
|
+
aiToggle.addEventListener('click', function() {
|
|
2695
|
+
var body = document.getElementById('ai-fields-body');
|
|
2696
|
+
var arrow = document.getElementById('ai-arrow');
|
|
2697
|
+
if (body.style.display === 'none') {
|
|
2698
|
+
body.style.display = '';
|
|
2699
|
+
arrow.innerHTML = '▼';
|
|
2700
|
+
} else {
|
|
2701
|
+
body.style.display = 'none';
|
|
2702
|
+
arrow.innerHTML = '▶';
|
|
2703
|
+
}
|
|
2704
|
+
});
|
|
2705
|
+
}
|
|
2706
|
+
|
|
2707
|
+
// AI tag input event listeners
|
|
2708
|
+
panel.querySelectorAll('.ai-tag-input').forEach(function(container) {
|
|
2709
|
+
var field = container.dataset.aiField;
|
|
2710
|
+
var textInput = container.querySelector('.ai-tag-text');
|
|
2711
|
+
var dropdown = container.querySelector('.ai-autocomplete');
|
|
2712
|
+
if (!textInput || !dropdown) return;
|
|
2713
|
+
|
|
2714
|
+
function getOwnValues() {
|
|
2715
|
+
var vals = [];
|
|
2716
|
+
container.querySelectorAll('.ai-tag-own .ai-tag-val').forEach(function(el) {
|
|
2717
|
+
vals.push(el.textContent);
|
|
2718
|
+
});
|
|
2719
|
+
return vals;
|
|
2720
|
+
}
|
|
2721
|
+
|
|
2722
|
+
function getAllValues() {
|
|
2723
|
+
var vals = [];
|
|
2724
|
+
container.querySelectorAll('.ai-tag').forEach(function(el) {
|
|
2725
|
+
var valEl = el.querySelector('.ai-tag-val');
|
|
2726
|
+
vals.push(valEl ? valEl.textContent : el.textContent);
|
|
2727
|
+
});
|
|
2728
|
+
return vals;
|
|
2729
|
+
}
|
|
2730
|
+
|
|
2731
|
+
function addTag(value) {
|
|
2732
|
+
var v = value.trim();
|
|
2733
|
+
if (!v) return;
|
|
2734
|
+
var existing = getAllValues();
|
|
2735
|
+
if (existing.indexOf(v) !== -1) return;
|
|
2736
|
+
var tag = document.createElement('span');
|
|
2737
|
+
tag.className = 'ai-tag ai-tag-own';
|
|
2738
|
+
tag.innerHTML = '<span class="ai-tag-val">' + escHtml(v) + '</span><button class="ai-tag-remove" type="button">×</button>';
|
|
2739
|
+
tag.querySelector('.ai-tag-remove').addEventListener('click', function() { tag.remove(); });
|
|
2740
|
+
container.insertBefore(tag, textInput);
|
|
2741
|
+
textInput.value = '';
|
|
2742
|
+
closeDropdown();
|
|
2743
|
+
}
|
|
2744
|
+
|
|
2745
|
+
function closeDropdown() {
|
|
2746
|
+
dropdown.classList.remove('open');
|
|
2747
|
+
dropdown.innerHTML = '';
|
|
2748
|
+
}
|
|
2749
|
+
|
|
2750
|
+
function showDropdown() {
|
|
2751
|
+
var suggestions = D.aiSuggestions ? (D.aiSuggestions[field] || []) : [];
|
|
2752
|
+
var present = getAllValues();
|
|
2753
|
+
var query = textInput.value.trim().toLowerCase();
|
|
2754
|
+
var filtered = suggestions.filter(function(s) {
|
|
2755
|
+
return present.indexOf(s) === -1 && (!query || s.toLowerCase().indexOf(query) !== -1);
|
|
2756
|
+
});
|
|
2757
|
+
if (filtered.length === 0) { closeDropdown(); return; }
|
|
2758
|
+
dropdown.innerHTML = '';
|
|
2759
|
+
filtered.forEach(function(s) {
|
|
2760
|
+
var item = document.createElement('div');
|
|
2761
|
+
item.className = 'ai-autocomplete-item';
|
|
2762
|
+
item.textContent = s;
|
|
2763
|
+
item.addEventListener('mousedown', function(e) {
|
|
2764
|
+
e.preventDefault();
|
|
2765
|
+
addTag(s);
|
|
2766
|
+
});
|
|
2767
|
+
dropdown.appendChild(item);
|
|
2768
|
+
});
|
|
2769
|
+
dropdown.classList.add('open');
|
|
2770
|
+
}
|
|
2771
|
+
|
|
2772
|
+
// Click on × to remove own tag
|
|
2773
|
+
container.querySelectorAll('.ai-tag-remove').forEach(function(btn) {
|
|
2774
|
+
btn.addEventListener('click', function() { btn.closest('.ai-tag').remove(); });
|
|
2775
|
+
});
|
|
2776
|
+
|
|
2777
|
+
// Click on container focuses input
|
|
2778
|
+
container.addEventListener('click', function(e) {
|
|
2779
|
+
if (e.target === container) textInput.focus();
|
|
2780
|
+
});
|
|
2781
|
+
|
|
2782
|
+
// Input typing → show autocomplete
|
|
2783
|
+
textInput.addEventListener('input', showDropdown);
|
|
2784
|
+
textInput.addEventListener('focus', showDropdown);
|
|
2785
|
+
|
|
2786
|
+
// Enter → add tag
|
|
2787
|
+
textInput.addEventListener('keydown', function(e) {
|
|
2788
|
+
if (e.key === 'Enter') {
|
|
2789
|
+
e.preventDefault();
|
|
2790
|
+
if (textInput.value.trim()) addTag(textInput.value);
|
|
2791
|
+
} else if (e.key === 'Backspace' && textInput.value === '') {
|
|
2792
|
+
// Remove last own tag
|
|
2793
|
+
var ownTags = container.querySelectorAll('.ai-tag-own');
|
|
2794
|
+
if (ownTags.length > 0) ownTags[ownTags.length - 1].remove();
|
|
2795
|
+
} else if (e.key === 'Escape') {
|
|
2796
|
+
closeDropdown();
|
|
2797
|
+
textInput.blur();
|
|
2798
|
+
}
|
|
2799
|
+
});
|
|
2800
|
+
|
|
2801
|
+
// Close dropdown on blur
|
|
2802
|
+
textInput.addEventListener('blur', function() {
|
|
2803
|
+
setTimeout(closeDropdown, 150);
|
|
2804
|
+
});
|
|
2805
|
+
});
|
|
1084
2806
|
|
|
1085
2807
|
// Live icon updates
|
|
1086
2808
|
var statusSel = document.getElementById('p-status');
|
|
@@ -1097,6 +2819,31 @@ function renderPanel() {
|
|
|
1097
2819
|
if (iconEl) iconEl.innerHTML = priorityIcon(prioritySel.value);
|
|
1098
2820
|
});
|
|
1099
2821
|
}
|
|
2822
|
+
|
|
2823
|
+
// Ctrl/Cmd+S to save
|
|
2824
|
+
panel.addEventListener('keydown', function(e) {
|
|
2825
|
+
if ((e.metaKey || e.ctrlKey) && e.key === 's') {
|
|
2826
|
+
e.preventDefault();
|
|
2827
|
+
if (isCreate) saveCreatePanel();
|
|
2828
|
+
else savePanel();
|
|
2829
|
+
}
|
|
2830
|
+
});
|
|
2831
|
+
|
|
2832
|
+
// Link chip click handlers
|
|
2833
|
+
panel.querySelectorAll('.link-chip[data-link]').forEach(function(chip) {
|
|
2834
|
+
chip.addEventListener('click', function() {
|
|
2835
|
+
var ref = chip.dataset.link;
|
|
2836
|
+
var parts = ref.split(':');
|
|
2837
|
+
if (parts.length === 2 && hasWorkspace()) {
|
|
2838
|
+
closePanel();
|
|
2839
|
+
switchSource(parts[0]);
|
|
2840
|
+
setTimeout(function() {
|
|
2841
|
+
var task = D.tasks.find(function(t) { return t.id === ref || t.id === parts[1]; });
|
|
2842
|
+
if (task) openPanel('tasks', task);
|
|
2843
|
+
}, 500);
|
|
2844
|
+
}
|
|
2845
|
+
});
|
|
2846
|
+
});
|
|
1100
2847
|
}
|
|
1101
2848
|
|
|
1102
2849
|
async function savePanel() {
|
|
@@ -1140,8 +2887,30 @@ async function savePanel() {
|
|
|
1140
2887
|
if (deadlineEl && deadlineEl.value !== fmtDate(item.deadline)) updates.deadline = deadlineEl.value || null;
|
|
1141
2888
|
}
|
|
1142
2889
|
|
|
1143
|
-
|
|
1144
|
-
|
|
2890
|
+
if (panelState.editor) {
|
|
2891
|
+
var newContent = await getEditorMarkdown(panelState.editor);
|
|
2892
|
+
if (newContent !== (item.content || '')) updates.content = newContent;
|
|
2893
|
+
}
|
|
2894
|
+
|
|
2895
|
+
// AI properties — read from tag inputs
|
|
2896
|
+
var aiTagInputs = document.querySelectorAll('.ai-tag-input[data-ai-field]');
|
|
2897
|
+
if (aiTagInputs.length > 0) {
|
|
2898
|
+
var aiObj = {};
|
|
2899
|
+
aiTagInputs.forEach(function(container) {
|
|
2900
|
+
var field = container.dataset.aiField;
|
|
2901
|
+
var vals = [];
|
|
2902
|
+
container.querySelectorAll('.ai-tag-own .ai-tag-val').forEach(function(el) {
|
|
2903
|
+
var v = el.textContent.trim();
|
|
2904
|
+
if (v) vals.push(v);
|
|
2905
|
+
});
|
|
2906
|
+
if (vals.length > 0) aiObj[field] = vals;
|
|
2907
|
+
});
|
|
2908
|
+
if (Object.keys(aiObj).length > 0) {
|
|
2909
|
+
updates.ai = aiObj;
|
|
2910
|
+
} else if (item.aiOwn && Object.keys(item.aiOwn).length > 0) {
|
|
2911
|
+
updates.ai = null;
|
|
2912
|
+
}
|
|
2913
|
+
}
|
|
1145
2914
|
|
|
1146
2915
|
if (Object.keys(updates).length === 0) {
|
|
1147
2916
|
showToast('No changes to save', 'success');
|
|
@@ -1156,22 +2925,290 @@ async function savePanel() {
|
|
|
1156
2925
|
}
|
|
1157
2926
|
}
|
|
1158
2927
|
|
|
2928
|
+
async function saveCreatePanel() {
|
|
2929
|
+
var t = panelState.type;
|
|
2930
|
+
if (!t) return;
|
|
2931
|
+
|
|
2932
|
+
var data = {};
|
|
2933
|
+
|
|
2934
|
+
var titleEl = document.getElementById('p-title');
|
|
2935
|
+
if (titleEl) data.title = titleEl.value || 'Untitled';
|
|
2936
|
+
|
|
2937
|
+
var statusEl = document.getElementById('p-status');
|
|
2938
|
+
if (statusEl) data.status = statusEl.value;
|
|
2939
|
+
|
|
2940
|
+
if (t === 'tasks' || t === 'epics') {
|
|
2941
|
+
var priorityEl = document.getElementById('p-priority');
|
|
2942
|
+
if (priorityEl && priorityEl.value) data.priority = priorityEl.value;
|
|
2943
|
+
}
|
|
2944
|
+
|
|
2945
|
+
if (t === 'tasks') {
|
|
2946
|
+
var pointsEl = document.getElementById('p-points');
|
|
2947
|
+
if (pointsEl && pointsEl.value) data.points = Number(pointsEl.value);
|
|
2948
|
+
|
|
2949
|
+
var assignedEl = document.getElementById('p-assigned');
|
|
2950
|
+
if (assignedEl && assignedEl.value.trim()) data.assigned = assignedEl.value.trim();
|
|
2951
|
+
|
|
2952
|
+
var sprintEl = document.getElementById('p-sprint');
|
|
2953
|
+
if (sprintEl && sprintEl.value) data.sprint = sprintEl.value;
|
|
2954
|
+
|
|
2955
|
+
var milestoneEl = document.getElementById('p-milestone');
|
|
2956
|
+
if (milestoneEl) data.milestone = milestoneEl.value;
|
|
2957
|
+
|
|
2958
|
+
var epicEl = document.getElementById('p-epic-select');
|
|
2959
|
+
if (epicEl) data.epic = epicEl.value;
|
|
2960
|
+
}
|
|
2961
|
+
|
|
2962
|
+
if (t === 'epics' || t === 'milestones') {
|
|
2963
|
+
var milestoneEl = document.getElementById('p-milestone');
|
|
2964
|
+
if (milestoneEl) data.milestone = milestoneEl.value;
|
|
2965
|
+
}
|
|
2966
|
+
|
|
2967
|
+
if (t === 'milestones') {
|
|
2968
|
+
var deadlineEl = document.getElementById('p-deadline');
|
|
2969
|
+
if (deadlineEl && deadlineEl.value) data.deadline = deadlineEl.value;
|
|
2970
|
+
}
|
|
2971
|
+
|
|
2972
|
+
if (panelState.editor) {
|
|
2973
|
+
var editorContent = await getEditorMarkdown(panelState.editor);
|
|
2974
|
+
if (editorContent) data.content = editorContent;
|
|
2975
|
+
}
|
|
2976
|
+
|
|
2977
|
+
// AI properties — read from tag inputs
|
|
2978
|
+
var aiTagInputs = document.querySelectorAll('.ai-tag-input[data-ai-field]');
|
|
2979
|
+
if (aiTagInputs.length > 0) {
|
|
2980
|
+
var aiObj = {};
|
|
2981
|
+
aiTagInputs.forEach(function(container) {
|
|
2982
|
+
var field = container.dataset.aiField;
|
|
2983
|
+
var vals = [];
|
|
2984
|
+
container.querySelectorAll('.ai-tag-own .ai-tag-val').forEach(function(el) {
|
|
2985
|
+
var v = el.textContent.trim();
|
|
2986
|
+
if (v) vals.push(v);
|
|
2987
|
+
});
|
|
2988
|
+
if (vals.length > 0) aiObj[field] = vals;
|
|
2989
|
+
});
|
|
2990
|
+
if (Object.keys(aiObj).length > 0) data.ai = aiObj;
|
|
2991
|
+
}
|
|
2992
|
+
|
|
2993
|
+
var result = await createItem(t, data);
|
|
2994
|
+
if (result) {
|
|
2995
|
+
closePanel();
|
|
2996
|
+
refreshData();
|
|
2997
|
+
}
|
|
2998
|
+
}
|
|
2999
|
+
|
|
1159
3000
|
// Close panel on overlay click or Escape
|
|
1160
3001
|
document.getElementById('panel-overlay').addEventListener('click', closePanel);
|
|
1161
3002
|
document.addEventListener('keydown', function(e) {
|
|
1162
3003
|
if (e.key === 'Escape' && panelState.open) closePanel();
|
|
1163
3004
|
});
|
|
1164
3005
|
|
|
3006
|
+
|
|
3007
|
+
/* ══════════════════════════════════════════════════════════════
|
|
3008
|
+
HISTORY — Project switch modal
|
|
3009
|
+
══════════════════════════════════════════════════════════════ */
|
|
3010
|
+
|
|
3011
|
+
function openHistoryModal() {
|
|
3012
|
+
var overlay = document.getElementById('history-overlay');
|
|
3013
|
+
var modal = document.getElementById('history-modal');
|
|
3014
|
+
if (!overlay || !modal) return;
|
|
3015
|
+
|
|
3016
|
+
overlay.classList.add('open');
|
|
3017
|
+
modal.classList.add('open');
|
|
3018
|
+
modal.innerHTML = '<div class="history-header"><h2>Switch Project</h2><button class="panel-close" id="history-close">×</button></div><div class="history-body"><div class="history-loading">Loading...</div></div>';
|
|
3019
|
+
|
|
3020
|
+
document.getElementById('history-close').onclick = closeHistoryModal;
|
|
3021
|
+
overlay.onclick = closeHistoryModal;
|
|
3022
|
+
|
|
3023
|
+
fetchJson('/api/history').then(function(entries) {
|
|
3024
|
+
renderHistoryList(entries || []);
|
|
3025
|
+
});
|
|
3026
|
+
}
|
|
3027
|
+
|
|
3028
|
+
function closeHistoryModal() {
|
|
3029
|
+
var overlay = document.getElementById('history-overlay');
|
|
3030
|
+
var modal = document.getElementById('history-modal');
|
|
3031
|
+
if (overlay) overlay.classList.remove('open');
|
|
3032
|
+
if (modal) modal.classList.remove('open');
|
|
3033
|
+
}
|
|
3034
|
+
|
|
3035
|
+
function renderHistoryList(entries) {
|
|
3036
|
+
var modal = document.getElementById('history-modal');
|
|
3037
|
+
if (!modal) return;
|
|
3038
|
+
|
|
3039
|
+
var body = modal.querySelector('.history-body');
|
|
3040
|
+
if (!body) return;
|
|
3041
|
+
|
|
3042
|
+
if (entries.length === 0) {
|
|
3043
|
+
body.innerHTML = '<div class="history-empty">No projects in history yet.<br>Open mdboard from a project directory to add it.</div>';
|
|
3044
|
+
return;
|
|
3045
|
+
}
|
|
3046
|
+
|
|
3047
|
+
// Sort: current first, then by lastOpened descending
|
|
3048
|
+
entries.sort(function(a, b) {
|
|
3049
|
+
if (a.isCurrent && !b.isCurrent) return -1;
|
|
3050
|
+
if (!a.isCurrent && b.isCurrent) return 1;
|
|
3051
|
+
return new Date(b.lastOpened) - new Date(a.lastOpened);
|
|
3052
|
+
});
|
|
3053
|
+
|
|
3054
|
+
var html = '';
|
|
3055
|
+
for (var i = 0; i < entries.length; i++) {
|
|
3056
|
+
var e = entries[i];
|
|
3057
|
+
var initial = (e.name || 'P').charAt(0).toUpperCase();
|
|
3058
|
+
var relTime = timeAgo(e.lastOpened);
|
|
3059
|
+
var currentClass = e.isCurrent ? ' history-item-current' : '';
|
|
3060
|
+
var currentBadge = e.isCurrent ? '<span class="history-current-badge">Current</span>' : '';
|
|
3061
|
+
|
|
3062
|
+
html += '<div class="history-item' + currentClass + '" data-path="' + escHtml(e.path) + '">' +
|
|
3063
|
+
'<div class="history-item-icon">' + escHtml(initial) + '</div>' +
|
|
3064
|
+
'<div class="history-item-info">' +
|
|
3065
|
+
'<div class="history-item-name">' + escHtml(e.name) + currentBadge + '</div>' +
|
|
3066
|
+
'<div class="history-item-path">' + escHtml(e.path) + '</div>' +
|
|
3067
|
+
'</div>' +
|
|
3068
|
+
'<div class="history-item-time">' + escHtml(relTime) + '</div>' +
|
|
3069
|
+
'</div>';
|
|
3070
|
+
}
|
|
3071
|
+
|
|
3072
|
+
body.innerHTML = html;
|
|
3073
|
+
|
|
3074
|
+
// Attach click handlers
|
|
3075
|
+
var items = body.querySelectorAll('.history-item');
|
|
3076
|
+
for (var j = 0; j < items.length; j++) {
|
|
3077
|
+
items[j].addEventListener('click', function() {
|
|
3078
|
+
var itemPath = this.getAttribute('data-path');
|
|
3079
|
+
if (this.classList.contains('history-item-current')) return;
|
|
3080
|
+
switchProject(itemPath);
|
|
3081
|
+
});
|
|
3082
|
+
}
|
|
3083
|
+
}
|
|
3084
|
+
|
|
3085
|
+
function switchProject(projectPath) {
|
|
3086
|
+
var body = document.querySelector('#history-modal .history-body');
|
|
3087
|
+
if (body) body.innerHTML = '<div class="history-loading">Switching project...</div>';
|
|
3088
|
+
|
|
3089
|
+
fetch('/api/history/switch', {
|
|
3090
|
+
method: 'POST',
|
|
3091
|
+
headers: { 'Content-Type': 'application/json' },
|
|
3092
|
+
body: JSON.stringify({ path: projectPath }),
|
|
3093
|
+
})
|
|
3094
|
+
.then(function(r) { return r.json(); })
|
|
3095
|
+
.then(function(data) {
|
|
3096
|
+
if (data && data.ok) {
|
|
3097
|
+
closeHistoryModal();
|
|
3098
|
+
// Reload everything — the SSE event will also trigger for other tabs
|
|
3099
|
+
D.activeSource = null;
|
|
3100
|
+
D.loaded = false;
|
|
3101
|
+
loadAll().then(function() {
|
|
3102
|
+
initFromConfig();
|
|
3103
|
+
if (hasWorkspace()) {
|
|
3104
|
+
buildSourceRail();
|
|
3105
|
+
D.activeSource = null;
|
|
3106
|
+
switchSource('overview');
|
|
3107
|
+
} else {
|
|
3108
|
+
renderAll();
|
|
3109
|
+
renderSidebarLogo();
|
|
3110
|
+
// Hide source rail if no workspace
|
|
3111
|
+
var rail = document.getElementById('source-rail');
|
|
3112
|
+
if (rail) rail.innerHTML = '';
|
|
3113
|
+
}
|
|
3114
|
+
});
|
|
3115
|
+
showToast('Switched to ' + (data.name || 'project'), 'success');
|
|
3116
|
+
} else {
|
|
3117
|
+
showToast('Error: ' + (data.error || 'Switch failed'), 'error');
|
|
3118
|
+
closeHistoryModal();
|
|
3119
|
+
}
|
|
3120
|
+
})
|
|
3121
|
+
.catch(function(err) {
|
|
3122
|
+
showToast('Error: ' + err.message, 'error');
|
|
3123
|
+
closeHistoryModal();
|
|
3124
|
+
});
|
|
3125
|
+
}
|
|
3126
|
+
|
|
3127
|
+
function checkAutoOpenHistory() {
|
|
3128
|
+
if (D.config && D.config.hasProject === false) {
|
|
3129
|
+
openHistoryModal();
|
|
3130
|
+
}
|
|
3131
|
+
}
|
|
3132
|
+
|
|
3133
|
+
function timeAgo(dateStr) {
|
|
3134
|
+
if (!dateStr) return '';
|
|
3135
|
+
var now = Date.now();
|
|
3136
|
+
var then = new Date(dateStr).getTime();
|
|
3137
|
+
var diff = Math.floor((now - then) / 1000);
|
|
3138
|
+
if (diff < 60) return 'just now';
|
|
3139
|
+
if (diff < 3600) return Math.floor(diff / 60) + 'm ago';
|
|
3140
|
+
if (diff < 86400) return Math.floor(diff / 3600) + 'h ago';
|
|
3141
|
+
if (diff < 604800) return Math.floor(diff / 86400) + 'd ago';
|
|
3142
|
+
return new Date(dateStr).toLocaleDateString();
|
|
3143
|
+
}
|
|
3144
|
+
|
|
3145
|
+
|
|
3146
|
+
/* ══════════════════════════════════════════════════════════════
|
|
3147
|
+
RENDER
|
|
3148
|
+
══════════════════════════════════════════════════════════════ */
|
|
3149
|
+
function renderAll() {
|
|
3150
|
+
renderHeader(); renderBoardFilters(); renderBoard(); renderTableControls(); renderTableBody(); renderMsFilters(); renderMilestones(); renderMetrics();
|
|
3151
|
+
// Render overview if active
|
|
3152
|
+
var overviewView = document.getElementById('view-overview');
|
|
3153
|
+
if (overviewView && overviewView.classList.contains('active')) {
|
|
3154
|
+
renderOverview();
|
|
3155
|
+
}
|
|
3156
|
+
}
|
|
3157
|
+
|
|
3158
|
+
function renderHeader() {
|
|
3159
|
+
renderSidebarLogo();
|
|
3160
|
+
|
|
3161
|
+
var mw = document.getElementById('h-milestone-wrap');
|
|
3162
|
+
var am = D.milestones.find(function(m) { return m.status === 'active'; });
|
|
3163
|
+
if (am) {
|
|
3164
|
+
mw.style.display = '';
|
|
3165
|
+
document.getElementById('h-milestone-name').textContent = am.title || am.id || '';
|
|
3166
|
+
document.getElementById('h-milestone-progress').style.width = (am.progress || 0) + '%';
|
|
3167
|
+
} else { mw.style.display = 'none'; }
|
|
3168
|
+
|
|
3169
|
+
var sw = document.getElementById('h-sprint-wrap');
|
|
3170
|
+
var as = D.sprints.find(function(s) { return s.status === 'active'; });
|
|
3171
|
+
if (as) {
|
|
3172
|
+
sw.style.display = '';
|
|
3173
|
+
document.getElementById('h-sprint-name').textContent = as.goal || as.id || '';
|
|
3174
|
+
var dr = daysRemaining(as.end_date);
|
|
3175
|
+
document.getElementById('h-sprint-days').textContent = dr !== null ? (dr >= 0 ? dr + ' days left' : Math.abs(dr) + ' days over') : '';
|
|
3176
|
+
} else { sw.style.display = 'none'; }
|
|
3177
|
+
|
|
3178
|
+
var taskPlural = ENTITY_NAMES.task ? ENTITY_NAMES.task.plural : 'Tasks';
|
|
3179
|
+
var total = D.tasks.length;
|
|
3180
|
+
var done = D.tasks.filter(function(f) { return f.status === COMPLETED_STATUS; }).length;
|
|
3181
|
+
var inProgStatus = (D.config && D.config.statuses && D.config.statuses.task) ?
|
|
3182
|
+
((D.config.statuses.task.find(function(s) { return s.icon === 'half-circle'; }) || {}).key || 'in-progress') : 'in-progress';
|
|
3183
|
+
var inProg = D.tasks.filter(function(f) { return f.status === inProgStatus; }).length;
|
|
3184
|
+
var vel = D.health && D.health.velocity != null ? D.health.velocity : (total ? Math.round(done / total * 100) : 0);
|
|
3185
|
+
document.getElementById('h-stats').innerHTML =
|
|
3186
|
+
'<div class="stat"><span class="stat-val">' + total + '</span><span class="stat-label">' + escHtml(taskPlural) + '</span></div>' +
|
|
3187
|
+
'<div class="stat"><span class="stat-val" style="color:var(--success)">' + done + '</span><span class="stat-label">Done</span></div>' +
|
|
3188
|
+
'<div class="stat"><span class="stat-val" style="color:var(--warning)">' + inProg + '</span><span class="stat-label">In Progress</span></div>' +
|
|
3189
|
+
'<div class="stat"><span class="stat-val" style="color:var(--accent)">' + vel + '%</span><span class="stat-label">Velocity</span></div>';
|
|
3190
|
+
}
|
|
3191
|
+
|
|
1165
3192
|
/* ══════════════════════════════════════════════════════════════
|
|
1166
3193
|
NAVIGATION
|
|
1167
3194
|
══════════════════════════════════════════════════════════════ */
|
|
1168
3195
|
function switchView(name) {
|
|
1169
3196
|
document.querySelectorAll('.view').forEach(function(v) { v.classList.remove('active'); });
|
|
1170
|
-
document.querySelectorAll('#sidebar-nav a').forEach(function(a) { a.classList.remove('active'); });
|
|
3197
|
+
document.querySelectorAll('#sidebar-nav a[data-view]').forEach(function(a) { a.classList.remove('active'); });
|
|
1171
3198
|
var t = document.getElementById('view-' + name);
|
|
1172
3199
|
if (t) t.classList.add('active');
|
|
1173
3200
|
var l = document.querySelector('#sidebar-nav a[data-view="' + name + '"]');
|
|
1174
3201
|
if (l) l.classList.add('active');
|
|
3202
|
+
// Persist view selection
|
|
3203
|
+
try { localStorage.setItem('mdboard-view', name); } catch(e) {}
|
|
3204
|
+
// Trigger overview rendering when switching to it
|
|
3205
|
+
if (name === 'overview' && D.loaded) {
|
|
3206
|
+
renderOverview();
|
|
3207
|
+
}
|
|
3208
|
+
// Trigger notes rendering when switching to it
|
|
3209
|
+
if (name === 'notes' && D.loaded) {
|
|
3210
|
+
renderNotes();
|
|
3211
|
+
}
|
|
1175
3212
|
}
|
|
1176
3213
|
|
|
1177
3214
|
document.getElementById('sidebar-nav').addEventListener('click', function(e) {
|
|
@@ -1183,12 +3220,124 @@ document.getElementById('sidebar-nav').addEventListener('click', function(e) {
|
|
|
1183
3220
|
});
|
|
1184
3221
|
|
|
1185
3222
|
function handleHash() {
|
|
1186
|
-
var hash = window.location.hash.replace('#', '')
|
|
1187
|
-
|
|
3223
|
+
var hash = window.location.hash.replace('#', '');
|
|
3224
|
+
if (!hash) {
|
|
3225
|
+
try { hash = localStorage.getItem('mdboard-view') || ''; } catch(e) {}
|
|
3226
|
+
}
|
|
3227
|
+
hash = hash || 'board';
|
|
3228
|
+
var valid = ['board','table','milestones','metrics','overview','notes'];
|
|
1188
3229
|
switchView(valid.indexOf(hash) !== -1 ? hash : 'board');
|
|
1189
3230
|
}
|
|
1190
3231
|
window.addEventListener('hashchange', handleHash);
|
|
1191
3232
|
|
|
3233
|
+
/* ══════════════════════════════════════════════════════════════
|
|
3234
|
+
SETTINGS PANEL
|
|
3235
|
+
══════════════════════════════════════════════════════════════ */
|
|
3236
|
+
var _settingsState = { selectedTheme: null, originalTheme: null };
|
|
3237
|
+
|
|
3238
|
+
function openSettings() {
|
|
3239
|
+
var active = getActiveTheme();
|
|
3240
|
+
_settingsState.originalTheme = active;
|
|
3241
|
+
_settingsState.selectedTheme = null;
|
|
3242
|
+
document.getElementById('settings-overlay').classList.add('open');
|
|
3243
|
+
document.getElementById('settings-panel').classList.add('open');
|
|
3244
|
+
var toggle = document.getElementById('theme-default-toggle');
|
|
3245
|
+
if (toggle) toggle.checked = false;
|
|
3246
|
+
renderThemeGrid();
|
|
3247
|
+
}
|
|
3248
|
+
|
|
3249
|
+
function closeSettings() {
|
|
3250
|
+
if (_settingsState.selectedTheme) {
|
|
3251
|
+
revertThemePreview();
|
|
3252
|
+
_settingsState.selectedTheme = null;
|
|
3253
|
+
}
|
|
3254
|
+
document.getElementById('settings-overlay').classList.remove('open');
|
|
3255
|
+
document.getElementById('settings-panel').classList.remove('open');
|
|
3256
|
+
}
|
|
3257
|
+
|
|
3258
|
+
function renderThemeGrid() {
|
|
3259
|
+
var grid = document.getElementById('theme-grid');
|
|
3260
|
+
var info = document.getElementById('settings-theme-info');
|
|
3261
|
+
var active = getActiveTheme();
|
|
3262
|
+
var selected = _settingsState.selectedTheme;
|
|
3263
|
+
|
|
3264
|
+
if (info) {
|
|
3265
|
+
var displayId = selected || active;
|
|
3266
|
+
var displayTheme = THEMES[displayId];
|
|
3267
|
+
info.textContent = 'Active: ' + (THEMES[active] ? THEMES[active].name : active) +
|
|
3268
|
+
(selected && selected !== active ? ' → Preview: ' + (displayTheme ? displayTheme.name : selected) : '');
|
|
3269
|
+
}
|
|
3270
|
+
|
|
3271
|
+
var html = '';
|
|
3272
|
+
THEME_LIST.forEach(function(t) {
|
|
3273
|
+
var theme = THEMES[t.id];
|
|
3274
|
+
var v = theme.vars;
|
|
3275
|
+
var isSelected = selected ? t.id === selected : t.id === active;
|
|
3276
|
+
var cls = isSelected ? ' active' : '';
|
|
3277
|
+
html += '<div class="theme-card' + cls + '" data-theme="' + t.id + '">' +
|
|
3278
|
+
'<div class="theme-card-preview">' +
|
|
3279
|
+
'<span style="background:' + v.bg + '"></span>' +
|
|
3280
|
+
'<span style="background:' + v.surface + '"></span>' +
|
|
3281
|
+
'<span style="background:' + v.accent + '"></span>' +
|
|
3282
|
+
'<span style="background:' + v.success + '"></span>' +
|
|
3283
|
+
'<span style="background:' + v.warning + '"></span>' +
|
|
3284
|
+
'</div>' +
|
|
3285
|
+
'<div class="theme-card-name">' + escHtml(theme.name) + '</div>' +
|
|
3286
|
+
'<div class="theme-card-type">' + theme.type + '</div>' +
|
|
3287
|
+
'</div>';
|
|
3288
|
+
});
|
|
3289
|
+
grid.innerHTML = html;
|
|
3290
|
+
}
|
|
3291
|
+
|
|
3292
|
+
function saveTheme() {
|
|
3293
|
+
var themeId = _settingsState.selectedTheme;
|
|
3294
|
+
if (!themeId) {
|
|
3295
|
+
closeSettings();
|
|
3296
|
+
return;
|
|
3297
|
+
}
|
|
3298
|
+
var toggle = document.getElementById('theme-default-toggle');
|
|
3299
|
+
var setDefault = toggle ? toggle.checked : false;
|
|
3300
|
+
var css = generateThemeCss(themeId);
|
|
3301
|
+
|
|
3302
|
+
fetch('/api/theme', {
|
|
3303
|
+
method: 'POST',
|
|
3304
|
+
headers: { 'Content-Type': 'application/json' },
|
|
3305
|
+
body: JSON.stringify({ themeId: themeId, css: css, setDefault: setDefault })
|
|
3306
|
+
}).then(function(r) { return r.json(); }).then(function(data) {
|
|
3307
|
+
if (data && data.ok) {
|
|
3308
|
+
revertThemePreview();
|
|
3309
|
+
reloadCustomCss();
|
|
3310
|
+
D.config.theme = themeId;
|
|
3311
|
+
_settingsState.selectedTheme = null;
|
|
3312
|
+
showToast('Theme saved: ' + (THEMES[themeId] ? THEMES[themeId].name : themeId), 'success');
|
|
3313
|
+
closeSettings();
|
|
3314
|
+
} else {
|
|
3315
|
+
showToast('Error: ' + (data.error || 'Unknown'), 'error');
|
|
3316
|
+
}
|
|
3317
|
+
}).catch(function(e) {
|
|
3318
|
+
showToast('Error: ' + e.message, 'error');
|
|
3319
|
+
});
|
|
3320
|
+
}
|
|
3321
|
+
|
|
3322
|
+
document.getElementById('settings-toggle').addEventListener('click', function(e) {
|
|
3323
|
+
e.preventDefault();
|
|
3324
|
+
openSettings();
|
|
3325
|
+
});
|
|
3326
|
+
|
|
3327
|
+
document.getElementById('settings-close').addEventListener('click', closeSettings);
|
|
3328
|
+
document.getElementById('settings-overlay').addEventListener('click', closeSettings);
|
|
3329
|
+
|
|
3330
|
+
document.getElementById('theme-grid').addEventListener('click', function(e) {
|
|
3331
|
+
var card = e.target.closest('.theme-card');
|
|
3332
|
+
if (!card) return;
|
|
3333
|
+
var themeId = card.dataset.theme;
|
|
3334
|
+
_settingsState.selectedTheme = themeId;
|
|
3335
|
+
applyTheme(themeId);
|
|
3336
|
+
renderThemeGrid();
|
|
3337
|
+
});
|
|
3338
|
+
|
|
3339
|
+
document.getElementById('settings-save').addEventListener('click', saveTheme);
|
|
3340
|
+
|
|
1192
3341
|
/* ══════════════════════════════════════════════════════════════
|
|
1193
3342
|
INIT
|
|
1194
3343
|
══════════════════════════════════════════════════════════════ */
|
|
@@ -1196,8 +3345,69 @@ window.addEventListener('hashchange', handleHash);
|
|
|
1196
3345
|
handleHash();
|
|
1197
3346
|
await loadAll();
|
|
1198
3347
|
initFromConfig();
|
|
3348
|
+
|
|
3349
|
+
// Sidebar logo always shows project name
|
|
3350
|
+
renderSidebarLogo();
|
|
3351
|
+
|
|
3352
|
+
// Workspace detection: build source rail + restore last source
|
|
3353
|
+
if (hasWorkspace()) {
|
|
3354
|
+
// Create overview tab (hidden by default)
|
|
3355
|
+
var overviewTab = document.querySelector('#sidebar-nav a[data-view="overview"]');
|
|
3356
|
+
if (!overviewTab) {
|
|
3357
|
+
var nav = document.getElementById('sidebar-nav');
|
|
3358
|
+
var metricsLink = document.querySelector('#sidebar-nav a[data-view="metrics"]');
|
|
3359
|
+
var overviewLink = document.createElement('a');
|
|
3360
|
+
overviewLink.href = '#overview';
|
|
3361
|
+
overviewLink.dataset.view = 'overview';
|
|
3362
|
+
overviewLink.style.display = 'none';
|
|
3363
|
+
overviewLink.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="12" cy="12" r="9"/><path d="M12 3v18M3 12h18"/></svg><span>Overview</span>';
|
|
3364
|
+
if (metricsLink && metricsLink.nextSibling) {
|
|
3365
|
+
nav.insertBefore(overviewLink, metricsLink.nextSibling);
|
|
3366
|
+
} else {
|
|
3367
|
+
nav.appendChild(overviewLink);
|
|
3368
|
+
}
|
|
3369
|
+
}
|
|
3370
|
+
|
|
3371
|
+
// Restore last source or default to overview
|
|
3372
|
+
var savedSource = null;
|
|
3373
|
+
try { savedSource = localStorage.getItem('mdboard-source'); } catch(e) {}
|
|
3374
|
+
var validSource = savedSource && (savedSource === 'overview' || D.config.workspace.sources.some(function(s) { return s.name === savedSource; }));
|
|
3375
|
+
var initialSource = validSource ? savedSource : 'overview';
|
|
3376
|
+
|
|
3377
|
+
buildSourceRail();
|
|
3378
|
+
// switchSource skips if same as current, so set activeSource first then trigger
|
|
3379
|
+
D.activeSource = null;
|
|
3380
|
+
switchSource(initialSource);
|
|
3381
|
+
// Wait for switchSource data reload before continuing
|
|
3382
|
+
await loadAll();
|
|
3383
|
+
initFromConfig();
|
|
3384
|
+
}
|
|
3385
|
+
|
|
1199
3386
|
renderAll();
|
|
1200
3387
|
connectSSE();
|
|
3388
|
+
|
|
3389
|
+
// Auto-open history modal if no project
|
|
3390
|
+
checkAutoOpenHistory();
|
|
3391
|
+
|
|
3392
|
+
// Keyboard shortcut: Ctrl/Cmd+K to open history modal
|
|
3393
|
+
document.addEventListener('keydown', function(e) {
|
|
3394
|
+
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
|
|
3395
|
+
e.preventDefault();
|
|
3396
|
+
openHistoryModal();
|
|
3397
|
+
}
|
|
3398
|
+
});
|
|
3399
|
+
|
|
3400
|
+
// Load AI suggestions for autocomplete
|
|
3401
|
+
fetchJson('/api/ai-suggestions').then(function(data) {
|
|
3402
|
+
D.aiSuggestions = data || { skills: [], agents: [], mcps: [], commands: [], context: [] };
|
|
3403
|
+
});
|
|
3404
|
+
|
|
3405
|
+
// Load overview links in background for reverse link display
|
|
3406
|
+
if (hasWorkspace()) {
|
|
3407
|
+
fetchJson('/api/overview/links').then(function(data) {
|
|
3408
|
+
D.overviewLinks = data;
|
|
3409
|
+
});
|
|
3410
|
+
}
|
|
1201
3411
|
})();
|
|
1202
3412
|
</script>
|
|
1203
3413
|
</body>
|