mdboard 2.0.0 → 2.1.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/index.html CHANGED
@@ -223,6 +223,14 @@ th.sorted .sort-arrow{opacity:1;color:var(--accent)}
223
223
  .prop-value select:disabled,.prop-value input:disabled{opacity:.6;cursor:default}
224
224
  .prop-value select:disabled:hover,.prop-value input:disabled:hover{border-color:transparent}
225
225
  .prop-text{font-size:13px;color:var(--text2);padding:4px 8px}
226
+ .prop-longtext{width:100%}
227
+ .prop-longtext-preview{display:flex;align-items:center;gap:6px;cursor:pointer;padding:4px 8px;border-radius:var(--radius-sm);transition:background .15s;min-height:28px}
228
+ .prop-longtext-preview:hover{background:var(--surface2)}
229
+ .prop-longtext-text{flex:1;font-size:13px;color:var(--text2);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
230
+ .prop-longtext-toggle{font-size:9px;color:var(--text3);flex-shrink:0;transition:transform .15s}
231
+ .prop-longtext-editor{width:100%;min-height:80px;resize:vertical;background:var(--surface);border:1px solid var(--border);color:var(--text);padding:8px;border-radius:var(--radius-sm);font-family:var(--font);font-size:13px;line-height:1.5;outline:none;margin-top:4px;transition:border-color .2s}
232
+ .prop-longtext-editor:focus{border-color:var(--accent)}
233
+ .prop-longtext-editor:disabled{opacity:.6;cursor:default}
226
234
  .prop-icon{flex-shrink:0}
227
235
  .prop-row-toggle{cursor:pointer;user-select:none}
228
236
  .prop-row-toggle:hover{background:var(--surface2);border-radius:var(--radius-sm)}
@@ -1156,6 +1164,12 @@ function renderFieldPill(item, entityType, field) {
1156
1164
  }
1157
1165
  }
1158
1166
 
1167
+ if (fieldDef && fieldDef.type === 'longtext') {
1168
+ var ltText = String(value);
1169
+ var ltTrunc = ltText.length > 50 ? ltText.substring(0, 50) + '...' : ltText;
1170
+ return '<span class="pill" style="background:var(--surface2);color:var(--text2)">' + escHtml(ltTrunc) + '</span>';
1171
+ }
1172
+
1159
1173
  if (fieldDef && fieldDef.type === 'number' && value != null) {
1160
1174
  return '<span class="pill pill-points">' + value + (fieldDef.label === 'Points' ? ' pts' : '') + '</span>';
1161
1175
  }
@@ -1185,6 +1199,12 @@ function renderFieldCell(item, entityType, col) {
1185
1199
  statusIcon(entityType, value) + ' ' + escHtml(getFieldLabel(entityType, col, value)) + '</span>';
1186
1200
  }
1187
1201
 
1202
+ if (fieldDef && fieldDef.type === 'longtext') {
1203
+ var ltText = String(value);
1204
+ var ltTrunc = ltText.length > 50 ? ltText.substring(0, 50) + '...' : ltText;
1205
+ return '<span style="color:var(--text2)">' + escHtml(ltTrunc) + '</span>';
1206
+ }
1207
+
1188
1208
  if (fieldDef && fieldDef.type === 'list') {
1189
1209
  var arr = Array.isArray(value) ? value : [value];
1190
1210
  return escHtml(arr.join(', '));
@@ -2592,11 +2612,20 @@ async function deleteCurrentNote(id) {
2592
2612
  ══════════════════════════════════════════════════════════════ */
2593
2613
  var panelState = { open: false, entityType: null, item: null, isCreate: false, editor: null };
2594
2614
 
2595
- function openPanel(entityType, item) {
2615
+ async function openPanel(entityType, item) {
2596
2616
  panelState = { open: true, entityType: entityType, item: JSON.parse(JSON.stringify(item)), isCreate: false, editor: null };
2597
- renderPanel();
2598
2617
  document.getElementById('detail-panel').classList.add('open');
2599
2618
  document.getElementById('panel-overlay').classList.add('open');
2619
+
2620
+ // Fetch full item with content from single endpoint
2621
+ var plural = getEntityPlural(entityType);
2622
+ var base = apiBase();
2623
+ var full = await fetchJson(base + '/' + plural + '/' + encodeURIComponent(item.id));
2624
+ if (full && full.content !== undefined) {
2625
+ panelState.item.content = full.content;
2626
+ }
2627
+
2628
+ renderPanel();
2600
2629
  }
2601
2630
 
2602
2631
  function closePanel() {
@@ -2773,6 +2802,38 @@ function renderPanel() {
2773
2802
  });
2774
2803
  }
2775
2804
 
2805
+ // Longtext expand/collapse
2806
+ panel.querySelectorAll('.prop-longtext-preview').forEach(function(prev) {
2807
+ prev.addEventListener('click', function() {
2808
+ var wrap = prev.parentNode;
2809
+ var textarea = wrap.querySelector('.prop-longtext-editor');
2810
+ var toggle = prev.querySelector('.prop-longtext-toggle');
2811
+ if (textarea.style.display === 'none') {
2812
+ textarea.style.display = '';
2813
+ prev.style.display = 'none';
2814
+ textarea.style.height = 'auto';
2815
+ textarea.style.height = Math.max(80, textarea.scrollHeight) + 'px';
2816
+ textarea.focus();
2817
+ }
2818
+ });
2819
+ });
2820
+ panel.querySelectorAll('.prop-longtext-editor').forEach(function(ta) {
2821
+ ta.addEventListener('blur', function() {
2822
+ var wrap = ta.parentNode;
2823
+ var prev = wrap.querySelector('.prop-longtext-preview');
2824
+ var textSpan = prev.querySelector('.prop-longtext-text');
2825
+ var val = ta.value || '';
2826
+ var truncated = val.length > 80 ? val.substring(0, 80) + '...' : (val || 'Empty');
2827
+ textSpan.textContent = truncated;
2828
+ ta.style.display = 'none';
2829
+ prev.style.display = '';
2830
+ });
2831
+ ta.addEventListener('input', function() {
2832
+ ta.style.height = 'auto';
2833
+ ta.style.height = Math.max(80, ta.scrollHeight) + 'px';
2834
+ });
2835
+ });
2836
+
2776
2837
  // Live icon updates for enum fields
2777
2838
  panel.querySelectorAll('select[id^="p-"]').forEach(function(sel) {
2778
2839
  sel.addEventListener('change', function() {
@@ -2827,6 +2888,18 @@ function renderPropRow(name, field, value, entityType, isReadonly) {
2827
2888
  html += '<input type="text" id="p-' + name + '" value="' + escHtml(value || '') + '" placeholder=""' + (isReadonly ? ' disabled' : '') + '>';
2828
2889
  break;
2829
2890
 
2891
+ case 'longtext':
2892
+ var ltVal = value || '';
2893
+ var ltTruncated = ltVal.length > 80 ? ltVal.substring(0, 80) + '...' : ltVal;
2894
+ html += '<div class="prop-longtext" id="p-lt-wrap-' + name + '">';
2895
+ html += '<div class="prop-longtext-preview" id="p-lt-preview-' + name + '" title="Click to expand">';
2896
+ html += '<span class="prop-longtext-text">' + escHtml(ltTruncated || 'Empty') + '</span>';
2897
+ html += '<span class="prop-longtext-toggle">&#9660;</span>';
2898
+ html += '</div>';
2899
+ html += '<textarea id="p-' + name + '" class="prop-longtext-editor" style="display:none" placeholder="Enter text..."' + (isReadonly ? ' disabled' : '') + '>' + escHtml(ltVal) + '</textarea>';
2900
+ html += '</div>';
2901
+ break;
2902
+
2830
2903
  case 'list':
2831
2904
  var listVal = Array.isArray(value) ? value.join(', ') : (value || '');
2832
2905
  html += '<input type="text" id="p-' + name + '" value="' + escHtml(listVal) + '" placeholder="Comma-separated"' + (isReadonly ? ' disabled' : '') + '>';
@@ -2944,6 +3017,8 @@ function readFieldValue(el, fieldDef) {
2944
3017
  return el.value.trim() || null;
2945
3018
  case 'date':
2946
3019
  return el.value || null;
3020
+ case 'longtext':
3021
+ return el.value || '';
2947
3022
  case 'enum':
2948
3023
  case 'string':
2949
3024
  case 'text':
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mdboard",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
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": {
@@ -48,8 +48,8 @@
48
48
  ],
49
49
  "default": "big"
50
50
  },
51
- "problem": { "type": "text", "label": "Problem" },
52
- "solution": { "type": "text", "label": "Solution" },
51
+ "problem": { "type": "longtext", "label": "Problem" },
52
+ "solution": { "type": "longtext", "label": "Solution" },
53
53
  "no_gos": { "type": "list", "label": "No-gos" }
54
54
  }
55
55
  },
@@ -115,8 +115,8 @@
115
115
  ],
116
116
  "default": "big"
117
117
  },
118
- "problem": { "type": "text", "label": "Problem" },
119
- "solution": { "type": "text", "label": "Solution" },
118
+ "problem": { "type": "longtext", "label": "Problem" },
119
+ "solution": { "type": "longtext", "label": "Solution" },
120
120
  "no_gos": { "type": "list", "label": "No-gos" }
121
121
  }
122
122
  },