diff --git a/browse/css/tree.css b/browse/css/tree.css index c61552c..a181753 100644 --- a/browse/css/tree.css +++ b/browse/css/tree.css @@ -896,39 +896,37 @@ body { /* ── Front matter editor ────────────────────────────────────────────────── */ .md-fm__body { - /* Body cell owns the textarea; sized by the sidebar's grid row. */ + /* Body cell owns the CodeMirror editor; sized by the sidebar's grid row. */ padding: 0; display: block; overflow: hidden; } -.md-fm__textarea { - width: 100%; +/* Recognised-keys caption under the header (tooltip carries the full list). */ +.md-fm__hint { + padding: 2px 0.6rem 4px; + font-size: 0.72rem; + color: var(--text-muted); + cursor: help; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +/* CodeMirror YAML front-matter editor — fills the body cell + scrolls + internally, matching the .zddc previewer's editor styling. */ +.md-fm__editor, +.md-fm__editor .CodeMirror { height: 100%; - box-sizing: border-box; - margin: 0; - padding: 0.4rem 0.6rem; - border: 0; - background: transparent; - color: var(--text); +} +.md-fm__editor .CodeMirror { font-family: var(--font-mono, ui-monospace, SFMono-Regular, Consolas, monospace); font-size: 0.8rem; line-height: 1.45; - resize: none; - outline: none; - white-space: pre; - overflow: auto; - tab-size: 2; + background: transparent; + color: var(--text); } -.md-fm__textarea::placeholder { - color: var(--text-muted); - font-style: italic; -} -.md-fm__textarea:focus { - background: var(--surface-2, rgba(0, 0, 0, 0.025)); -} -.md-fm__textarea[readonly] { - color: var(--text-muted); - cursor: not-allowed; +.md-fm__editor .CodeMirror-gutters { + background: var(--bg-secondary); + border-right: 1px solid var(--border); } /* Older .md-fm-section / .fm-list / .md-toc-resizer rules were replaced diff --git a/browse/js/preview-markdown.js b/browse/js/preview-markdown.js index b5999b8..839bff5 100644 --- a/browse/js/preview-markdown.js +++ b/browse/js/preview-markdown.js @@ -83,16 +83,22 @@ var fmPlaceholder = null; var fmPlaceholderPromise = null; - // applyFrontMatterPlaceholder sets the textarea placeholder to the server's - // recognised-field hint, in server mode only. Async + best-effort: a failed - // fetch leaves the pane blank (no placeholder), never an error. - function applyFrontMatterPlaceholder(textarea) { + // applyFrontMatterHint populates a greyed caption (+ tooltip) with the + // server's recognised front-matter fields, in server mode only. Async + + // best-effort: a failed fetch leaves the caption hidden, never an error. + // (Replaces the old textarea placeholder — CodeMirror 5 has no built-in + // placeholder without an unvendored add-on. Arbitrary keys stay free.) + function applyFrontMatterHint(el) { var st = window.app && window.app.state; if (!st || st.source !== 'server') return; - if (fmPlaceholder !== null) { - textarea.placeholder = fmPlaceholder; - return; + function paint() { + if (!el.isConnected) return; // user switched files before resolve + if (!fmPlaceholder) { el.style.display = 'none'; return; } + el.textContent = 'ⓘ Recognised front-matter keys (hover) — any other key is allowed'; + el.title = fmPlaceholder; + el.style.display = ''; } + if (fmPlaceholder !== null) { paint(); return; } if (!fmPlaceholderPromise) { fmPlaceholderPromise = fetch('/.api/frontmatter', { headers: { 'Accept': 'application/json' }, @@ -101,11 +107,7 @@ .then(function (j) { fmPlaceholder = (j && j.placeholder) || ''; }) .catch(function () { fmPlaceholder = ''; }); } - fmPlaceholderPromise.then(function () { - // Only apply if this textarea is still in the DOM (user may have - // switched files before the fetch resolved). - if (textarea.isConnected) textarea.placeholder = fmPlaceholder; - }); + fmPlaceholderPromise.then(paint); } // Lightweight YAML front-matter parser. Same envelope as mdedit's: @@ -427,22 +429,19 @@ fmHeader.textContent = 'YAML front matter'; var fmBody = document.createElement('div'); fmBody.className = 'md-side__body md-fm__body'; - var fmTextarea = document.createElement('textarea'); - fmTextarea.className = 'md-fm__textarea'; - fmTextarea.spellcheck = false; - fmTextarea.autocapitalize = 'off'; - fmTextarea.autocomplete = 'off'; - // Placeholder: in server mode, hint the recognised front-matter keys - // (doctype, numbering, …) as greyed text so authors can discover them. - // It's placeholder-only — inserts nothing, vanishes on the first - // keystroke — so arbitrary keys stay free and a file with no front - // matter still renders as a genuinely empty pane. The text is fetched - // from the server (/.api/frontmatter), the single source of truth, so - // it never drifts from what the converter honours. file:// mode shows - // no placeholder (conversion is server-only). - fmTextarea.placeholder = ''; - applyFrontMatterPlaceholder(fmTextarea); - fmBody.appendChild(fmTextarea); + // CodeMirror YAML editor host — mounted with the front-matter value + // once it's computed (sync-on-open) below. Same editor family as the + // .zddc previewer: syntax highlighting, line numbers, lint gutter. + var fmEditorHost = document.createElement('div'); + fmEditorHost.className = 'md-fm__editor'; + fmBody.appendChild(fmEditorHost); + // Recognised-keys hint (server mode): a greyed caption under the header + // whose tooltip carries the full "key: # hint" template from + // /.api/frontmatter. Replaces the old textarea placeholder. + var fmHint = document.createElement('div'); + fmHint.className = 'md-fm__hint'; + fmHint.style.display = 'none'; + applyFrontMatterHint(fmHint); // Rename cue: shown when the author edits an identity field // (tracking_number / revision / status / title) away from the // filename. The filename owns identity, so the cue offers an explicit @@ -459,6 +458,7 @@ + '0.78rem;line-height:1.5;flex-wrap:wrap;align-items:center;gap:6px;' + 'display:none;'; fmSection.appendChild(fmHeader); + fmSection.appendChild(fmHint); fmSection.appendChild(fmWarn); fmSection.appendChild(fmBody); sidebar.appendChild(fmSection); @@ -615,9 +615,32 @@ if (Object.prototype.hasOwnProperty.call(fid, ik)) initialParsed.data[ik] = fid[ik]; } } - fmTextarea.value = stringifyFrontMatter(initialParsed.data); + var syncedFM = stringifyFrontMatter(initialParsed.data); var initialHash = await hashContent(assembleContent(onDiskFM, bodyText)); var writableMode = canSave(node); + + // Front-matter YAML editor — CodeMirror, the same editor family as the + // .zddc previewer (syntax highlighting, line numbers, shared js-yaml + // lint gutter). Replaces the old