mdboard 2.1.0 → 2.1.1

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/index.html CHANGED
@@ -202,12 +202,16 @@ th.sorted .sort-arrow{opacity:1;color:var(--accent)}
202
202
  /* ── Detail Panel — Notion-style ─────────────────────────── */
203
203
  .panel-overlay{position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:99;opacity:0;pointer-events:none;transition:opacity .2s}
204
204
  .panel-overlay.open{opacity:1;pointer-events:auto}
205
- .detail-panel{top: 1rem;right: 1rem;height: calc(100vh - 2rem);border-radius: var(--radius);position:fixed;width:600px;max-width:92vw;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}
205
+ .detail-panel{top: 1rem;right: 1rem;height: calc(100vh - 2rem);border-radius: var(--radius);position:fixed;width:600px;max-width:92vw;background:var(--bg);border-left:1px solid var(--border);z-index:100;transform:translateX(100%);transition:transform .25s ease,top .25s ease,left .25s ease,right .25s ease,width .25s ease,height .25s ease;display:flex;flex-direction:column;overflow:hidden}
206
206
  .detail-panel.open{transform:translateX(0)}
207
+ .detail-panel.expanded{top:50%;left:50%;right:auto;width:900px;height:85vh;border:1px solid var(--border);box-shadow:0 16px 48px rgba(0,0,0,.4)}
208
+ .detail-panel.expanded.open{transform:translate(-50%,-50%)}
207
209
  .panel-header{padding:12px 20px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:12px;flex-shrink:0;background:var(--surface)}
208
210
  .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}
209
211
  .panel-item-id{font-family:var(--mono);font-size:13px;color:var(--text2)}
210
- .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}
212
+ .panel-expand{margin-left:auto;background:none;border:none;color:var(--text2);cursor:pointer;padding:4px 8px;border-radius:var(--radius-sm);line-height:1;display:flex;align-items:center}
213
+ .panel-expand:hover{background:var(--surface2);color:var(--text)}
214
+ .panel-close{background:none;border:none;color:var(--text2);cursor:pointer;font-size:18px;padding:4px 8px;border-radius:var(--radius-sm);line-height:1}
211
215
  .panel-close:hover{background:var(--surface2);color:var(--text)}
212
216
  .panel-title-wrap{padding:20px 24px 8px;flex-shrink:0}
213
217
  .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}
@@ -475,6 +479,7 @@ h4.ce-header{font-size:15px;line-height:1.4}
475
479
  .header{padding:12px 16px}
476
480
  .content{padding:16px}
477
481
  .detail-panel{width:100%;max-width:100%}
482
+ .detail-panel.expanded{width:96vw;height:90vh}
478
483
  .notes-sidebar{width:200px}
479
484
  .notes-header-bar{padding:24px 24px 0}
480
485
  .notes-editorjs-wrap{padding:12px 16px 24px}
@@ -2610,10 +2615,10 @@ async function deleteCurrentNote(id) {
2610
2615
  /* ══════════════════════════════════════════════════════════════
2611
2616
  PANEL — Dynamic detail panel from config
2612
2617
  ══════════════════════════════════════════════════════════════ */
2613
- var panelState = { open: false, entityType: null, item: null, isCreate: false, editor: null };
2618
+ var panelState = { open: false, entityType: null, item: null, isCreate: false, editor: null, expanded: false };
2614
2619
 
2615
2620
  async function openPanel(entityType, item) {
2616
- panelState = { open: true, entityType: entityType, item: JSON.parse(JSON.stringify(item)), isCreate: false, editor: null };
2621
+ panelState = { open: true, entityType: entityType, item: JSON.parse(JSON.stringify(item)), isCreate: false, editor: null, expanded: false };
2617
2622
  document.getElementById('detail-panel').classList.add('open');
2618
2623
  document.getElementById('panel-overlay').classList.add('open');
2619
2624
 
@@ -2630,11 +2635,23 @@ async function openPanel(entityType, item) {
2630
2635
 
2631
2636
  function closePanel() {
2632
2637
  if (panelState.editor) { destroyEditor(panelState.editor); panelState.editor = null; }
2633
- panelState = { open: false, entityType: null, item: null, isCreate: false, editor: null };
2634
- document.getElementById('detail-panel').classList.remove('open');
2638
+ panelState = { open: false, entityType: null, item: null, isCreate: false, editor: null, expanded: false };
2639
+ document.getElementById('detail-panel').classList.remove('open', 'expanded');
2635
2640
  document.getElementById('panel-overlay').classList.remove('open');
2636
2641
  }
2637
2642
 
2643
+ function togglePanelExpand() {
2644
+ panelState.expanded = !panelState.expanded;
2645
+ document.getElementById('detail-panel').classList.toggle('expanded', panelState.expanded);
2646
+ var btn = document.getElementById('panel-expand-btn');
2647
+ if (btn) {
2648
+ btn.innerHTML = panelState.expanded
2649
+ ? '<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M14 6h-4V2M2 10h4v4M10 6l4.5-4.5M6 10L1.5 14.5"/></svg>'
2650
+ : '<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M10 2h4v4M6 14H2v-4M14 2L9.5 6.5M2 14l4.5-4.5"/></svg>';
2651
+ btn.title = panelState.expanded ? 'Collapse' : 'Expand';
2652
+ }
2653
+ }
2654
+
2638
2655
  function openCreateDialog(entityType) {
2639
2656
  var fields = getEntityFields(entityType);
2640
2657
  var item = { _isNew: true, title: '' };
@@ -2659,7 +2676,7 @@ function openCreateDialog(entityType) {
2659
2676
  }
2660
2677
  }
2661
2678
 
2662
- panelState = { open: true, entityType: entityType, item: item, isCreate: true, editor: null };
2679
+ panelState = { open: true, entityType: entityType, item: item, isCreate: true, editor: null, expanded: false };
2663
2680
  renderPanel();
2664
2681
  document.getElementById('detail-panel').classList.add('open');
2665
2682
  document.getElementById('panel-overlay').classList.add('open');
@@ -2686,6 +2703,10 @@ function renderPanel() {
2686
2703
  html += '<span class="pill" style="background:' + item._sourceColor + '20;color:' + item._sourceColor + ';font-size:10px">' + escHtml(item._sourceLabel || item._source) + '</span>';
2687
2704
  }
2688
2705
 
2706
+ var expandIcon = panelState.expanded
2707
+ ? '<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M14 6h-4V2M2 10h4v4M10 6l4.5-4.5M6 10L1.5 14.5"/></svg>'
2708
+ : '<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M10 2h4v4M6 14H2v-4M14 2L9.5 6.5M2 14l4.5-4.5"/></svg>';
2709
+ html += '<button class="panel-expand" id="panel-expand-btn" title="' + (panelState.expanded ? 'Collapse' : 'Expand') + '">' + expandIcon + '</button>';
2689
2710
  html += '<button class="panel-close" id="panel-close-btn">&times;</button></div>';
2690
2711
 
2691
2712
  // ── Title ──
@@ -2772,6 +2793,7 @@ function renderPanel() {
2772
2793
 
2773
2794
  // Event listeners
2774
2795
  document.getElementById('panel-close-btn').addEventListener('click', closePanel);
2796
+ document.getElementById('panel-expand-btn').addEventListener('click', togglePanelExpand);
2775
2797
  document.getElementById('panel-cancel-btn').addEventListener('click', closePanel);
2776
2798
  var saveBtn = document.getElementById('panel-save-btn');
2777
2799
  if (saveBtn) saveBtn.addEventListener('click', isCreate ? saveCreatePanel : savePanel);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mdboard",
3
- "version": "2.1.0",
3
+ "version": "2.1.1",
4
4
  "description": "Git-based project management dashboard. Reads markdown files with YAML frontmatter and serves a visual kanban board, table, milestones, and metrics views.",
5
5
  "main": "./src/server/server.js",
6
6
  "bin": {
package/src/core/yaml.js CHANGED
@@ -58,8 +58,21 @@ function parseYaml(text) {
58
58
  continue;
59
59
  }
60
60
 
61
- obj[key] = parseValue(rawValue);
62
- i++;
61
+ // Check for continuation lines (multiline plain scalar)
62
+ let fullValue = rawValue;
63
+ let j = i + 1;
64
+ while (j < lines.length) {
65
+ const nextLine = lines[j];
66
+ if (nextLine.match(/^\s/) && nextLine.trim() !== '' &&
67
+ !nextLine.match(/^\s+-\s/) && !nextLine.match(/^\s+\w[\w.-]*\s*:/)) {
68
+ fullValue += ' ' + nextLine.trim();
69
+ j++;
70
+ } else {
71
+ break;
72
+ }
73
+ }
74
+ obj[key] = parseValue(fullValue);
75
+ i = j;
63
76
  }
64
77
 
65
78
  return obj;
@@ -99,6 +112,29 @@ function serializeValue(v) {
99
112
  return String(v);
100
113
  }
101
114
 
115
+ function wrapPlainScalar(text) {
116
+ // Normalize newlines to spaces
117
+ const normalized = text.replace(/\n/g, ' ').replace(/\s+/g, ' ').trim();
118
+ const words = normalized.split(' ');
119
+ const lines = [];
120
+ let currentLine = '';
121
+
122
+ for (const word of words) {
123
+ if (currentLine === '') {
124
+ currentLine = word;
125
+ } else if (currentLine.length + 1 + word.length > 78) {
126
+ lines.push(currentLine);
127
+ currentLine = word;
128
+ } else {
129
+ currentLine += ' ' + word;
130
+ }
131
+ }
132
+ if (currentLine) lines.push(currentLine);
133
+
134
+ // First line is bare, continuation lines indented with 2 spaces
135
+ return lines.map((l, i) => (i === 0 ? l : ' ' + l)).join('\n');
136
+ }
137
+
102
138
  function serializeYaml(obj) {
103
139
  const lines = [];
104
140
  for (const [key, value] of Object.entries(obj)) {
@@ -124,7 +160,11 @@ function serializeYaml(obj) {
124
160
  }
125
161
  }
126
162
  } else {
127
- lines.push(key + ': ' + serializeValue(value));
163
+ if (typeof value === 'string' && (value.length > 80 || value.indexOf('\n') !== -1)) {
164
+ lines.push(key + ': ' + wrapPlainScalar(value));
165
+ } else {
166
+ lines.push(key + ': ' + serializeValue(value));
167
+ }
128
168
  }
129
169
  }
130
170
  return lines.join('\n');