mdboard 1.2.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 +44 -16
- package/build.js +44 -0
- package/index.html +1835 -216
- package/package.json +7 -10
- 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/{scanner.js → src/core/scanner.js} +141 -21
- package/{yaml.js → src/core/yaml.js} +5 -1
- package/{api.js → src/server/api.js} +150 -9
- package/{server.js → src/server/server.js} +105 -32
- package/{watcher.js → src/server/watcher.js} +40 -9
- package/init.js +0 -109
- /package/{workspace.js → src/core/workspace.js} +0 -0
package/index.html
CHANGED
|
@@ -71,6 +71,8 @@ a{color:var(--accent);text-decoration:none}
|
|
|
71
71
|
.content{flex:1;overflow-y:auto;padding:20px 24px}
|
|
72
72
|
.view{display:none}
|
|
73
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}
|
|
74
76
|
|
|
75
77
|
/* ── Icons ───────────────────────────────────────────────── */
|
|
76
78
|
.icon{width:16px;height:16px;flex-shrink:0;vertical-align:middle;display:inline-block}
|
|
@@ -174,26 +176,60 @@ th.sorted .sort-arrow{opacity:1;color:var(--accent)}
|
|
|
174
176
|
.health-label{flex:1;font-size:13px;color:var(--text2)}
|
|
175
177
|
.health-val{font-family:var(--mono);font-size:13px;font-weight:600}
|
|
176
178
|
|
|
177
|
-
/* ── Detail Panel
|
|
179
|
+
/* ── Detail Panel — Notion-style ─────────────────────────── */
|
|
178
180
|
.panel-overlay{position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:99;opacity:0;pointer-events:none;transition:opacity .2s}
|
|
179
181
|
.panel-overlay.open{opacity:1;pointer-events:auto}
|
|
180
|
-
.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}
|
|
181
183
|
.detail-panel.open{transform:translateX(0)}
|
|
182
|
-
.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)}
|
|
183
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}
|
|
184
186
|
.panel-item-id{font-family:var(--mono);font-size:13px;color:var(--text2)}
|
|
185
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}
|
|
186
188
|
.panel-close:hover{background:var(--surface2);color:var(--text)}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
.panel-
|
|
190
|
-
.panel-
|
|
191
|
-
.panel-
|
|
192
|
-
.panel-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
.panel-
|
|
196
|
-
.
|
|
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}
|
|
197
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)}
|
|
198
234
|
.btn:hover{background:var(--border)}
|
|
199
235
|
.btn-primary{background:var(--accent);border-color:var(--accent);color:#fff}
|
|
@@ -243,6 +279,178 @@ th.sorted .sort-arrow{opacity:1;color:var(--accent)}
|
|
|
243
279
|
.tracked-ms-item span{font-size:12px}
|
|
244
280
|
.tracked-ms-pct{font-family:var(--mono);font-weight:600;font-size:12px;min-width:36px;text-align:right}
|
|
245
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
|
+
|
|
246
454
|
/* ── Responsive ──────────────────────────────────────────── */
|
|
247
455
|
@media(max-width:1024px){
|
|
248
456
|
.source-rail{width:48px;padding:8px 0;gap:6px}
|
|
@@ -255,15 +463,22 @@ th.sorted .sort-arrow{opacity:1;color:var(--accent)}
|
|
|
255
463
|
.header{padding:12px 16px}
|
|
256
464
|
.content{padding:16px}
|
|
257
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}
|
|
258
470
|
}
|
|
259
471
|
@media(max-width:768px){
|
|
260
472
|
#h-stats{flex-wrap:wrap;gap:12px}
|
|
261
473
|
.header{gap:12px}
|
|
262
474
|
.metrics-grid{grid-template-columns:1fr}
|
|
263
|
-
.
|
|
475
|
+
.notes-sidebar{display:none}
|
|
476
|
+
.notes-header-bar{padding:20px 16px 0}
|
|
477
|
+
.notes-editorjs-wrap{padding:8px 8px 24px}
|
|
264
478
|
}
|
|
265
479
|
</style>
|
|
266
480
|
<style id="dynamic-styles"></style>
|
|
481
|
+
<style id="theme-styles"></style>
|
|
267
482
|
<link id="custom-theme" rel="stylesheet" href="/mdboard.css">
|
|
268
483
|
</head>
|
|
269
484
|
<body>
|
|
@@ -294,7 +509,17 @@ th.sorted .sort-arrow{opacity:1;color:var(--accent)}
|
|
|
294
509
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M18 20V10M12 20V4M6 20v-6"/></svg>
|
|
295
510
|
<span>Metrics</span>
|
|
296
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>
|
|
297
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>
|
|
298
523
|
<div class="sidebar-footer">mdboard</div>
|
|
299
524
|
</aside>
|
|
300
525
|
|
|
@@ -335,6 +560,9 @@ th.sorted .sort-arrow{opacity:1;color:var(--accent)}
|
|
|
335
560
|
<div id="view-overview" class="view">
|
|
336
561
|
<div id="overview-container"></div>
|
|
337
562
|
</div>
|
|
563
|
+
<div id="view-notes" class="view">
|
|
564
|
+
<div id="notes-container"></div>
|
|
565
|
+
</div>
|
|
338
566
|
</div>
|
|
339
567
|
</div>
|
|
340
568
|
</div>
|
|
@@ -343,7 +571,306 @@ th.sorted .sort-arrow{opacity:1;color:var(--accent)}
|
|
|
343
571
|
<div id="panel-overlay" class="panel-overlay"></div>
|
|
344
572
|
<div id="detail-panel" class="detail-panel"></div>
|
|
345
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>
|
|
346
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
|
+
|
|
347
874
|
/* ══════════════════════════════════════════════════════════════
|
|
348
875
|
ICON SHAPES — SVG generators by icon name
|
|
349
876
|
══════════════════════════════════════════════════════════════ */
|
|
@@ -416,7 +943,8 @@ function milestoneIcon(status) {
|
|
|
416
943
|
var D = {
|
|
417
944
|
config: null, project: null, milestones: [], epics: [], tasks: [],
|
|
418
945
|
sprints: [], allSprints: [], metrics: null, health: null, loaded: false,
|
|
419
|
-
sources: [], activeSource: null, overviewLinks: null
|
|
946
|
+
sources: [], activeSource: null, overviewLinks: null, notes: [],
|
|
947
|
+
aiSuggestions: null, theme: null
|
|
420
948
|
};
|
|
421
949
|
|
|
422
950
|
/* ── Helpers ─────────────────────────────────────────────── */
|
|
@@ -539,7 +1067,7 @@ async function loadAll() {
|
|
|
539
1067
|
fetchJson(base + '/project'), fetchJson(base + '/milestones'), fetchJson(base + '/epics'),
|
|
540
1068
|
fetchJson(base + '/tasks'), fetchJson(base + '/sprint'), fetchJson(base + '/metrics'),
|
|
541
1069
|
fetchJson(base + '/health'), fetchJson(base + '/sprints'), fetchJson('/api/config'),
|
|
542
|
-
fetchJson('/api/sources')
|
|
1070
|
+
fetchJson('/api/sources'), fetchJson(base + '/notes')
|
|
543
1071
|
]);
|
|
544
1072
|
D.project = results[0]; D.milestones = results[1] || []; D.epics = results[2] || [];
|
|
545
1073
|
D.tasks = results[3] || [];
|
|
@@ -548,6 +1076,7 @@ async function loadAll() {
|
|
|
548
1076
|
D.allSprints = results[7] || [];
|
|
549
1077
|
D.config = results[8];
|
|
550
1078
|
D.sources = results[9] || [];
|
|
1079
|
+
D.notes = results[10] || [];
|
|
551
1080
|
D.loaded = true;
|
|
552
1081
|
}
|
|
553
1082
|
|
|
@@ -637,6 +1166,43 @@ function generateDynamicStyles() {
|
|
|
637
1166
|
if (styleEl) styleEl.textContent = css;
|
|
638
1167
|
}
|
|
639
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
|
+
|
|
640
1206
|
/* ══════════════════════════════════════════════════════════════
|
|
641
1207
|
WORKSPACE — Source switching & sidebar
|
|
642
1208
|
══════════════════════════════════════════════════════════════ */
|
|
@@ -703,6 +1269,19 @@ function buildSourceRail() {
|
|
|
703
1269
|
})(s.name);
|
|
704
1270
|
rail.appendChild(icon);
|
|
705
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);
|
|
706
1285
|
}
|
|
707
1286
|
|
|
708
1287
|
function switchSource(sourceName) {
|
|
@@ -757,6 +1336,28 @@ function connectSSE() {
|
|
|
757
1336
|
src.onmessage = function(e) {
|
|
758
1337
|
var data;
|
|
759
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
|
+
|
|
760
1361
|
if (data.cssReload) {
|
|
761
1362
|
var link = document.getElementById('custom-theme');
|
|
762
1363
|
if (link) link.href = '/mdboard.css?' + Date.now();
|
|
@@ -768,51 +1369,325 @@ function connectSSE() {
|
|
|
768
1369
|
}
|
|
769
1370
|
|
|
770
1371
|
/* ══════════════════════════════════════════════════════════════
|
|
771
|
-
|
|
1372
|
+
EDITOR.JS WRAPPER — md↔blocks conversion + Notion-like UX
|
|
772
1373
|
══════════════════════════════════════════════════════════════ */
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
1374
|
+
|
|
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
|
+
}
|
|
1384
|
+
|
|
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
|
+
}
|
|
1398
|
+
|
|
1399
|
+
/* ── Markdown → Editor.js Blocks ────────────────────────── */
|
|
1400
|
+
function markdownToBlocks(md) {
|
|
1401
|
+
if (!md || !md.trim()) return [{ type: 'paragraph', data: { text: '' } }];
|
|
1402
|
+
|
|
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
|
+
}
|
|
779
1490
|
}
|
|
1491
|
+
|
|
1492
|
+
return blocks.length > 0 ? blocks : [{ type: 'paragraph', data: { text: '' } }];
|
|
780
1493
|
}
|
|
781
1494
|
|
|
782
|
-
|
|
783
|
-
|
|
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
|
+
}
|
|
784
1557
|
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
if (
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
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
|
+
}
|
|
792
1572
|
|
|
793
|
-
var
|
|
794
|
-
var
|
|
795
|
-
if (as) {
|
|
796
|
-
sw.style.display = '';
|
|
797
|
-
document.getElementById('h-sprint-name').textContent = as.goal || as.id || '';
|
|
798
|
-
var dr = daysRemaining(as.end_date);
|
|
799
|
-
document.getElementById('h-sprint-days').textContent = dr !== null ? (dr >= 0 ? dr + ' days left' : Math.abs(dr) + ' days over') : '';
|
|
800
|
-
} else { sw.style.display = 'none'; }
|
|
1573
|
+
var opts = options || {};
|
|
1574
|
+
var tools = {};
|
|
801
1575
|
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
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;
|
|
814
1688
|
}
|
|
815
1689
|
|
|
1690
|
+
|
|
816
1691
|
/* ══════════════════════════════════════════════════════════════
|
|
817
1692
|
BOARD VIEW
|
|
818
1693
|
══════════════════════════════════════════════════════════════ */
|
|
@@ -918,6 +1793,7 @@ function renderCard(f) {
|
|
|
918
1793
|
'<div class="card-meta">' +
|
|
919
1794
|
(f.points != null ? '<span class="pill pill-points">' + f.points + ' pts</span>' : '') +
|
|
920
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>' : '') +
|
|
921
1797
|
(assigned ? '<span class="card-assigned">' + escHtml(assigned) + '</span>' : '') +
|
|
922
1798
|
'</div></div>';
|
|
923
1799
|
}
|
|
@@ -1199,51 +2075,6 @@ function renderMetrics() {
|
|
|
1199
2075
|
'<div class="metric-card"><h3>Project Health</h3>' + qualHtml + '</div></div>';
|
|
1200
2076
|
}
|
|
1201
2077
|
|
|
1202
|
-
/* ══════════════════════════════════════════════════════════════
|
|
1203
|
-
CRUD HELPERS
|
|
1204
|
-
══════════════════════════════════════════════════════════════ */
|
|
1205
|
-
function isSourceWritable() {
|
|
1206
|
-
if (!hasWorkspace() || !D.activeSource) return true; // legacy mode = writable
|
|
1207
|
-
var src = D.config.workspace.sources.find(function(s) { return s.name === D.activeSource; });
|
|
1208
|
-
return src ? !src.readonly : true;
|
|
1209
|
-
}
|
|
1210
|
-
|
|
1211
|
-
function openCreateDialog(collection) {
|
|
1212
|
-
var item = { _isNew: true };
|
|
1213
|
-
|
|
1214
|
-
if (collection === 'tasks') {
|
|
1215
|
-
// Pre-fill milestone and epic from available ones
|
|
1216
|
-
var ms = D.milestones.length > 0 ? (D.milestones[0].id || D.milestones[0]._dir || '') : '';
|
|
1217
|
-
var ep = D.epics.length > 0 ? (D.epics[0]._dir || D.epics[0].id || '') : '';
|
|
1218
|
-
item.title = '';
|
|
1219
|
-
item.status = 'backlog';
|
|
1220
|
-
item.priority = '';
|
|
1221
|
-
item.points = null;
|
|
1222
|
-
item.assigned = '';
|
|
1223
|
-
item.sprint = '';
|
|
1224
|
-
item.milestone = ms;
|
|
1225
|
-
item.epic = ep;
|
|
1226
|
-
item.content = '';
|
|
1227
|
-
} else if (collection === 'milestones') {
|
|
1228
|
-
item.title = '';
|
|
1229
|
-
item.status = 'planned';
|
|
1230
|
-
item.deadline = '';
|
|
1231
|
-
item.content = '';
|
|
1232
|
-
} else if (collection === 'epics') {
|
|
1233
|
-
var ms = D.milestones.length > 0 ? (D.milestones[0]._dir || D.milestones[0].id || '') : '';
|
|
1234
|
-
item.title = '';
|
|
1235
|
-
item.status = 'active';
|
|
1236
|
-
item.priority = '';
|
|
1237
|
-
item.milestone = ms;
|
|
1238
|
-
item.content = '';
|
|
1239
|
-
}
|
|
1240
|
-
|
|
1241
|
-
panelState = { open: true, type: collection, item: item, isCreate: true };
|
|
1242
|
-
renderPanel();
|
|
1243
|
-
document.getElementById('detail-panel').classList.add('open');
|
|
1244
|
-
document.getElementById('panel-overlay').classList.add('open');
|
|
1245
|
-
}
|
|
1246
|
-
|
|
1247
2078
|
/* ══════════════════════════════════════════════════════════════
|
|
1248
2079
|
OVERVIEW VIEW
|
|
1249
2080
|
══════════════════════════════════════════════════════════════ */
|
|
@@ -1350,70 +2181,304 @@ async function renderOverview() {
|
|
|
1350
2181
|
}
|
|
1351
2182
|
|
|
1352
2183
|
/* ══════════════════════════════════════════════════════════════
|
|
1353
|
-
|
|
2184
|
+
NOTES VIEW — Notion-style split layout
|
|
1354
2185
|
══════════════════════════════════════════════════════════════ */
|
|
1355
|
-
var panelState = { open: false, type: null, item: null, isCreate: false };
|
|
1356
2186
|
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
document.getElementById('
|
|
1361
|
-
|
|
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>' +
|
|
2209
|
+
'</div>';
|
|
2210
|
+
|
|
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
|
+
});
|
|
1362
2217
|
}
|
|
1363
2218
|
|
|
1364
|
-
function
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
2219
|
+
async function loadNoteList() {
|
|
2220
|
+
var base = apiBase();
|
|
2221
|
+
var notes = await fetchJson(base + '/notes');
|
|
2222
|
+
notesState.notes = notes || [];
|
|
2223
|
+
renderNoteList('');
|
|
1368
2224
|
}
|
|
1369
2225
|
|
|
1370
|
-
function
|
|
1371
|
-
var
|
|
1372
|
-
|
|
1373
|
-
var item = panelState.item;
|
|
1374
|
-
var isCreate = panelState.isCreate;
|
|
1375
|
-
if (!item) return;
|
|
2226
|
+
function renderNoteList(filter) {
|
|
2227
|
+
var list = document.getElementById('notes-list');
|
|
2228
|
+
if (!list) return;
|
|
1376
2229
|
|
|
1377
|
-
var
|
|
2230
|
+
var notes = notesState.notes;
|
|
2231
|
+
if (filter) {
|
|
2232
|
+
notes = notes.filter(function(n) {
|
|
2233
|
+
return (n.title || '').toLowerCase().indexOf(filter) !== -1;
|
|
2234
|
+
});
|
|
2235
|
+
}
|
|
1378
2236
|
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
else typeLabel = ENTITY_NAMES.milestone ? ENTITY_NAMES.milestone.singular : 'Milestone';
|
|
2237
|
+
notes.sort(function(a, b) {
|
|
2238
|
+
return (b.updated || b.created || '').localeCompare(a.updated || a.created || '');
|
|
2239
|
+
});
|
|
1383
2240
|
|
|
1384
|
-
|
|
1385
|
-
'<
|
|
1386
|
-
|
|
2241
|
+
if (notes.length === 0) {
|
|
2242
|
+
list.innerHTML = '<div class="notes-list-empty">No notes yet</div>';
|
|
2243
|
+
return;
|
|
2244
|
+
}
|
|
1387
2245
|
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
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>';
|
|
1391
2254
|
}
|
|
2255
|
+
list.innerHTML = html;
|
|
1392
2256
|
|
|
1393
|
-
|
|
2257
|
+
list.querySelectorAll('.notes-list-item').forEach(function(el) {
|
|
2258
|
+
el.addEventListener('click', function() {
|
|
2259
|
+
selectNote(el.dataset.id);
|
|
2260
|
+
});
|
|
2261
|
+
});
|
|
2262
|
+
}
|
|
1394
2263
|
|
|
1395
|
-
|
|
2264
|
+
async function selectNote(id) {
|
|
2265
|
+
if (notesState.editor) {
|
|
2266
|
+
destroyEditor(notesState.editor);
|
|
2267
|
+
notesState.editor = null;
|
|
2268
|
+
}
|
|
1396
2269
|
|
|
1397
|
-
|
|
1398
|
-
|
|
2270
|
+
notesState.activeId = id;
|
|
2271
|
+
renderNoteList(document.getElementById('notes-search') ? document.getElementById('notes-search').value.toLowerCase() : '');
|
|
1399
2272
|
|
|
1400
|
-
|
|
1401
|
-
|
|
2273
|
+
var pane = document.getElementById('notes-editor-pane');
|
|
2274
|
+
if (!pane) return;
|
|
1402
2275
|
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
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;
|
|
2437
|
+
|
|
2438
|
+
var isReadonly = !isCreate && item.readonly;
|
|
2439
|
+
|
|
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';
|
|
2444
|
+
|
|
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
|
|
2469
|
+
var statusOptions, statusLabels;
|
|
2470
|
+
if (t === 'tasks' && D.config && D.config.statuses && D.config.statuses.task) {
|
|
2471
|
+
statusOptions = D.config.statuses.task.map(function(s) { return s.key; });
|
|
2472
|
+
statusLabels = {};
|
|
2473
|
+
D.config.statuses.task.forEach(function(s) { statusLabels[s.key] = s.label; });
|
|
2474
|
+
} else if (t === 'epics' && D.config && D.config.statuses && D.config.statuses.epic) {
|
|
2475
|
+
statusOptions = D.config.statuses.epic.map(function(s) { return s.key; });
|
|
2476
|
+
statusLabels = {};
|
|
2477
|
+
D.config.statuses.epic.forEach(function(s) { statusLabels[s.key] = s.label; });
|
|
2478
|
+
} else if (t === 'milestones' && D.config && D.config.statuses && D.config.statuses.milestone) {
|
|
2479
|
+
statusOptions = D.config.statuses.milestone.map(function(s) { return s.key; });
|
|
2480
|
+
statusLabels = {};
|
|
2481
|
+
D.config.statuses.milestone.forEach(function(s) { statusLabels[s.key] = s.label; });
|
|
1417
2482
|
} else if (t === 'tasks') {
|
|
1418
2483
|
statusOptions = STATUSES;
|
|
1419
2484
|
statusLabels = STATUS_LABELS;
|
|
@@ -1422,116 +2487,175 @@ function renderPanel() {
|
|
|
1422
2487
|
statusLabels = { planned: 'Planned', active: 'Active', completed: 'Completed', cancelled: 'Cancelled' };
|
|
1423
2488
|
}
|
|
1424
2489
|
|
|
1425
|
-
html += '<div class="
|
|
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>' +
|
|
1426
2491
|
'<select id="p-status"' + (isReadonly ? ' disabled' : '') + '>' + statusOptions.map(function(s) {
|
|
1427
2492
|
return '<option value="' + s + '"' + (item.status === s ? ' selected' : '') + '>' + (statusLabels[s] || s) + '</option>';
|
|
1428
2493
|
}).join('') + '</select></div></div>';
|
|
1429
2494
|
|
|
1430
|
-
// Priority
|
|
1431
2495
|
if (t === 'tasks' || t === 'epics') {
|
|
1432
|
-
html += '<div class="
|
|
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>' +
|
|
1433
2497
|
'<select id="p-priority"' + (isReadonly ? ' disabled' : '') + '><option value="">None</option>' + PRIORITIES.map(function(p) {
|
|
1434
2498
|
return '<option value="' + p + '"' + (item.priority === p ? ' selected' : '') + '>' + (PRIORITY_LABELS[p] || p) + '</option>';
|
|
1435
2499
|
}).join('') + '</select></div></div>';
|
|
1436
2500
|
}
|
|
1437
2501
|
|
|
1438
|
-
html += '</div>'; // close panel-props
|
|
1439
|
-
|
|
1440
|
-
// Type-specific fields
|
|
1441
2502
|
if (t === 'tasks') {
|
|
1442
|
-
html += '<div class="
|
|
1443
|
-
html += '<div class="panel-field"><label>Points</label><input type="number" id="p-points" min="0" max="100" value="' + (item.points != null ? item.points : '') + '"' + (isReadonly ? ' disabled' : '') + '></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>';
|
|
1444
2504
|
|
|
1445
2505
|
var assignedVal = Array.isArray(item.assigned) ? item.assigned.join(', ') : (item.assigned || '');
|
|
1446
|
-
html += '<div class="
|
|
1447
|
-
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>';
|
|
1448
2507
|
|
|
1449
|
-
html += '<div class="
|
|
1450
|
-
// Sprint
|
|
1451
|
-
html += '<div class="panel-field"><label>Sprint</label><select id="p-sprint"' + (isReadonly ? ' disabled' : '') + '><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>' +
|
|
1452
2509
|
D.allSprints.map(function(s) { return '<option value="' + escHtml(s.id || '') + '"' + (item.sprint === s.id ? ' selected' : '') + '>' + escHtml(s.id || '') + '</option>'; }).join('') +
|
|
1453
|
-
'</select></div>';
|
|
2510
|
+
'</select></div></div>';
|
|
1454
2511
|
|
|
1455
2512
|
if (isCreate) {
|
|
1456
|
-
|
|
1457
|
-
html += '<div class="panel-field"><label>Milestone</label><select id="p-milestone">' +
|
|
2513
|
+
html += '<div class="prop-row"><span class="prop-label">Milestone</span><div class="prop-value"><select id="p-milestone">' +
|
|
1458
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('') +
|
|
1459
|
-
'</select></div>';
|
|
1460
|
-
html += '</div>'
|
|
1461
|
-
html += '<div class="panel-field"><label>Epic</label><select id="p-epic-select">' +
|
|
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">' +
|
|
1462
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('') +
|
|
1463
|
-
'</select></div>';
|
|
2518
|
+
'</select></div></div>';
|
|
1464
2519
|
} else {
|
|
1465
|
-
|
|
1466
|
-
html += '<div class="panel-field"><label>Epic</label><input type="text" id="p-epic" value="' + escHtml(item.epic || '') + '" disabled style="opacity:.6" title="Epic is determined by file location"></div>';
|
|
1467
|
-
html += '</div>';
|
|
1468
|
-
}
|
|
1469
|
-
|
|
1470
|
-
// Links rendering
|
|
1471
|
-
if (!isCreate && item.links && Array.isArray(item.links) && item.links.length > 0) {
|
|
1472
|
-
html += '<div class="panel-field"><label>Links</label><div class="link-chips">';
|
|
1473
|
-
item.links.forEach(function(link) {
|
|
1474
|
-
var parts = String(link).split(':');
|
|
1475
|
-
var srcName = parts.length === 2 ? parts[0] : null;
|
|
1476
|
-
var srcColor = 'var(--accent)';
|
|
1477
|
-
if (srcName && D.config && D.config.workspace && D.config.workspace.sources) {
|
|
1478
|
-
var src = D.config.workspace.sources.find(function(s) { return s.name === srcName; });
|
|
1479
|
-
if (src && src.color) srcColor = src.color;
|
|
1480
|
-
}
|
|
1481
|
-
html += '<span class="link-chip" data-link="' + escHtml(String(link)) + '">' +
|
|
1482
|
-
'<span class="link-chip-dot" style="background:' + srcColor + '"></span>' +
|
|
1483
|
-
escHtml(String(link)) + '</span>';
|
|
1484
|
-
});
|
|
1485
|
-
html += '</div></div>';
|
|
1486
|
-
}
|
|
1487
|
-
|
|
1488
|
-
// Reverse links
|
|
1489
|
-
if (!isCreate && D.overviewLinks && D.overviewLinks.reverseLinks && item.id) {
|
|
1490
|
-
var reverseRefs = D.overviewLinks.reverseLinks[item.id] || [];
|
|
1491
|
-
if (reverseRefs.length > 0) {
|
|
1492
|
-
html += '<div class="panel-field"><label>Referenced By</label><div class="link-chips">';
|
|
1493
|
-
reverseRefs.forEach(function(ref) {
|
|
1494
|
-
var srcColor = ref.sourceColor || 'var(--accent)';
|
|
1495
|
-
html += '<span class="link-chip" data-link="' + escHtml(ref.from || '') + '">' +
|
|
1496
|
-
'<span class="link-chip-dot" style="background:' + srcColor + '"></span>' +
|
|
1497
|
-
escHtml(ref.from || '') + '</span>';
|
|
1498
|
-
});
|
|
1499
|
-
html += '</div></div>';
|
|
1500
|
-
}
|
|
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>';
|
|
1501
2521
|
}
|
|
1502
2522
|
}
|
|
1503
2523
|
|
|
1504
2524
|
if (t === 'milestones') {
|
|
1505
|
-
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>';
|
|
1506
2526
|
}
|
|
1507
2527
|
|
|
1508
2528
|
if (isCreate && (t === 'epics' || t === 'milestones')) {
|
|
1509
2529
|
if (t === 'epics') {
|
|
1510
|
-
html += '<div class="
|
|
2530
|
+
html += '<div class="prop-row"><span class="prop-label">Milestone</span><div class="prop-value"><select id="p-milestone">' +
|
|
1511
2531
|
D.milestones.map(function(m) { return '<option value="' + escHtml(m._dir || m.id || '') + '">' + escHtml(m.title || m.id || '') + '</option>'; }).join('') +
|
|
1512
|
-
'</select></div>';
|
|
2532
|
+
'</select></div></div>';
|
|
2533
|
+
}
|
|
2534
|
+
}
|
|
2535
|
+
|
|
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
|
+
}
|
|
2553
|
+
|
|
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>';
|
|
1513
2566
|
}
|
|
1514
2567
|
}
|
|
1515
2568
|
|
|
1516
|
-
//
|
|
1517
|
-
|
|
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
|
+
}
|
|
1518
2627
|
|
|
1519
|
-
html += '</div>'; // close panel-
|
|
2628
|
+
html += '</div>'; // close panel-properties
|
|
1520
2629
|
|
|
1521
|
-
//
|
|
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 ──
|
|
1522
2638
|
html += '<div class="panel-footer">';
|
|
1523
2639
|
if (!isCreate && !isReadonly && isSourceWritable()) {
|
|
1524
|
-
html += '<button class="btn btn-danger" id="panel-archive-btn">Archive</button>';
|
|
2640
|
+
html += '<button class="btn btn-danger btn-sm" id="panel-archive-btn">Archive</button>';
|
|
1525
2641
|
}
|
|
1526
2642
|
html += '<span style="flex:1"></span>';
|
|
1527
2643
|
html += '<button class="btn" id="panel-cancel-btn">Cancel</button>';
|
|
1528
2644
|
if (!isReadonly) {
|
|
1529
|
-
html += '<button class="btn btn-primary" id="panel-save-btn">' + (isCreate ? 'Create' : 'Save
|
|
2645
|
+
html += '<button class="btn btn-primary" id="panel-save-btn">' + (isCreate ? 'Create' : 'Save') + '</button>';
|
|
1530
2646
|
}
|
|
1531
2647
|
html += '</div>';
|
|
1532
2648
|
|
|
1533
2649
|
panel.innerHTML = html;
|
|
1534
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
|
+
|
|
1535
2659
|
// Event listeners
|
|
1536
2660
|
document.getElementById('panel-close-btn').addEventListener('click', closePanel);
|
|
1537
2661
|
document.getElementById('panel-cancel-btn').addEventListener('click', closePanel);
|
|
@@ -1541,13 +2665,145 @@ function renderPanel() {
|
|
|
1541
2665
|
var archiveBtn = document.getElementById('panel-archive-btn');
|
|
1542
2666
|
if (archiveBtn) {
|
|
1543
2667
|
archiveBtn.addEventListener('click', function() {
|
|
1544
|
-
if (!confirm('Archive this ' + typeLabel.toLowerCase() + '?
|
|
2668
|
+
if (!confirm('Archive this ' + typeLabel.toLowerCase() + '?')) return;
|
|
1545
2669
|
deleteItem(t, item.id).then(function(ok) {
|
|
1546
2670
|
if (ok) { closePanel(); refreshData(); }
|
|
1547
2671
|
});
|
|
1548
2672
|
});
|
|
1549
2673
|
}
|
|
1550
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
|
+
});
|
|
2806
|
+
|
|
1551
2807
|
// Live icon updates
|
|
1552
2808
|
var statusSel = document.getElementById('p-status');
|
|
1553
2809
|
if (statusSel) {
|
|
@@ -1564,6 +2820,15 @@ function renderPanel() {
|
|
|
1564
2820
|
});
|
|
1565
2821
|
}
|
|
1566
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
|
+
|
|
1567
2832
|
// Link chip click handlers
|
|
1568
2833
|
panel.querySelectorAll('.link-chip[data-link]').forEach(function(chip) {
|
|
1569
2834
|
chip.addEventListener('click', function() {
|
|
@@ -1622,8 +2887,30 @@ async function savePanel() {
|
|
|
1622
2887
|
if (deadlineEl && deadlineEl.value !== fmtDate(item.deadline)) updates.deadline = deadlineEl.value || null;
|
|
1623
2888
|
}
|
|
1624
2889
|
|
|
1625
|
-
|
|
1626
|
-
|
|
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
|
+
}
|
|
1627
2914
|
|
|
1628
2915
|
if (Object.keys(updates).length === 0) {
|
|
1629
2916
|
showToast('No changes to save', 'success');
|
|
@@ -1682,8 +2969,26 @@ async function saveCreatePanel() {
|
|
|
1682
2969
|
if (deadlineEl && deadlineEl.value) data.deadline = deadlineEl.value;
|
|
1683
2970
|
}
|
|
1684
2971
|
|
|
1685
|
-
|
|
1686
|
-
|
|
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
|
+
}
|
|
1687
2992
|
|
|
1688
2993
|
var result = await createItem(t, data);
|
|
1689
2994
|
if (result) {
|
|
@@ -1698,6 +3003,192 @@ document.addEventListener('keydown', function(e) {
|
|
|
1698
3003
|
if (e.key === 'Escape' && panelState.open) closePanel();
|
|
1699
3004
|
});
|
|
1700
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
|
+
|
|
1701
3192
|
/* ══════════════════════════════════════════════════════════════
|
|
1702
3193
|
NAVIGATION
|
|
1703
3194
|
══════════════════════════════════════════════════════════════ */
|
|
@@ -1714,6 +3205,10 @@ function switchView(name) {
|
|
|
1714
3205
|
if (name === 'overview' && D.loaded) {
|
|
1715
3206
|
renderOverview();
|
|
1716
3207
|
}
|
|
3208
|
+
// Trigger notes rendering when switching to it
|
|
3209
|
+
if (name === 'notes' && D.loaded) {
|
|
3210
|
+
renderNotes();
|
|
3211
|
+
}
|
|
1717
3212
|
}
|
|
1718
3213
|
|
|
1719
3214
|
document.getElementById('sidebar-nav').addEventListener('click', function(e) {
|
|
@@ -1730,11 +3225,119 @@ function handleHash() {
|
|
|
1730
3225
|
try { hash = localStorage.getItem('mdboard-view') || ''; } catch(e) {}
|
|
1731
3226
|
}
|
|
1732
3227
|
hash = hash || 'board';
|
|
1733
|
-
var valid = ['board','table','milestones','metrics','overview'];
|
|
3228
|
+
var valid = ['board','table','milestones','metrics','overview','notes'];
|
|
1734
3229
|
switchView(valid.indexOf(hash) !== -1 ? hash : 'board');
|
|
1735
3230
|
}
|
|
1736
3231
|
window.addEventListener('hashchange', handleHash);
|
|
1737
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
|
+
|
|
1738
3341
|
/* ══════════════════════════════════════════════════════════════
|
|
1739
3342
|
INIT
|
|
1740
3343
|
══════════════════════════════════════════════════════════════ */
|
|
@@ -1783,6 +3386,22 @@ window.addEventListener('hashchange', handleHash);
|
|
|
1783
3386
|
renderAll();
|
|
1784
3387
|
connectSSE();
|
|
1785
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
|
+
|
|
1786
3405
|
// Load overview links in background for reverse link display
|
|
1787
3406
|
if (hasWorkspace()) {
|
|
1788
3407
|
fetchJson('/api/overview/links').then(function(data) {
|