@@ -5051,13 +5185,17 @@ var e=function(t,n){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Arr
// preview-markdown.js — markdown plugin for the browse preview pane.
// Click a .md / .markdown file in the tree → instantiate Toast UI
-// editor inside the right pane. Save (Ctrl+S) writes back via PUT
-// when the file came from a server URL; FS-API and zip-virtual files
-// are read-only for now (toolbar shows a hint).
+// editor inside the right pane, alongside a TOC pane on the right.
+// Save (Ctrl+S) writes back via:
+// - PUT to the file's server URL when in server mode, or
+// - FileSystemWritableFileStream when in FS-API mode (local folder
+// picker). Both paths set dirty=false + a status timestamp on
+// success.
+// zip-virtual files are read-only — the save button stays disabled.
//
-// Toast UI Editor is loaded from mdedit's bundled vendor file in the
-// browse build (see browse/build.sh). window.toastui is available
-// synchronously when this module runs.
+// Toast UI Editor is bundled (shared/vendor/toastui-editor-all.min.js)
+// and is available synchronously as window.toastui by the time this
+// module runs.
(function () {
'use strict';
@@ -5068,11 +5206,10 @@ var e=function(t,n){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Arr
.replace(/>/g, '>').replace(/"/g, '"');
}
- var currentInstance = null; // { editor, container, dirty, node, hash }
+ var currentInstance = null; // { editor, container, dirty, node, hash, tocEl }
- // Compute SHA-256 hex of a string for a quick "is this content
- // different from what was loaded?" check. Used to decide whether
- // the save button should be active. Not used for integrity.
+ // Compute SHA-256 hex of a string for a "is this content different
+ // from what we loaded?" check. Used to enable/disable Save.
async function hashContent(text) {
if (!window.crypto || !window.crypto.subtle) return null;
var enc = new TextEncoder().encode(text);
@@ -5092,6 +5229,152 @@ var e=function(t,n){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Arr
currentInstance = null;
}
+ // ── TOC (table of contents) ─────────────────────────────────────────────
+ //
+ // Ported from mdedit/js/toc.js, condensed: parse markdown for ATX-style
+ // headings, build a flat hierarchical list, click jumps the editor to
+ // the heading's line. We track WYSIWYG vs markdown mode and route the
+ // scroll behaviour to whichever pane is visible.
+
+ function parseHeadings(content) {
+ var headings = [];
+ var lines = content.split('\n');
+ for (var i = 0; i < lines.length; i++) {
+ var m = lines[i].match(/^(#{1,6})\s+(.+)$/);
+ if (!m) continue;
+ var text = m[2].trim()
+ .replace(/\\(.)/g, '$1')
+ .replace(/\*\*(.*?)\*\*/g, '$1')
+ .replace(/\*(.*?)\*/g, '$1')
+ .replace(/`(.*?)`/g, '$1')
+ .replace(/\[(.*?)\]\(.*?\)/g, '$1')
+ .replace(/~~(.*?)~~/g, '$1')
+ .trim();
+ headings.push({ level: m[1].length, text: text, lineIndex: i });
+ }
+ return headings;
+ }
+
+ function scrollEditorToHeading(editor, heading) {
+ try {
+ var els = editor.getEditorElements();
+ if (editor.isWysiwygMode && editor.isWysiwygMode()) {
+ var ww = els.wwEditor;
+ if (!ww) return;
+ var hs = ww.querySelectorAll('h1, h2, h3, h4, h5, h6');
+ for (var i = 0; i < hs.length; i++) {
+ if (hs[i].textContent.trim() === heading.text) {
+ var top = hs[i].getBoundingClientRect().top - ww.getBoundingClientRect().top;
+ ww.scrollTop = top - 10;
+ flashHeading(hs[i]);
+ return;
+ }
+ }
+ } else {
+ var line = heading.lineIndex + 1;
+ try { editor.setSelection([line, 1], [line, 1]); } catch (_) { /* ignore */ }
+ var preview = els.mdPreview;
+ if (!preview) return;
+ var phs = preview.querySelectorAll('h1, h2, h3, h4, h5, h6');
+ for (var j = 0; j < phs.length; j++) {
+ if (phs[j].textContent.trim() === heading.text) {
+ var ptop = phs[j].getBoundingClientRect().top - preview.getBoundingClientRect().top;
+ preview.scrollTop = ptop - 10;
+ flashHeading(phs[j]);
+ return;
+ }
+ }
+ }
+ } catch (e) { /* swallow; click was best-effort */ }
+ }
+
+ function flashHeading(el) {
+ if (!el) return;
+ el.style.transition = 'background-color 0.3s ease';
+ el.style.backgroundColor = 'var(--primary-light)';
+ setTimeout(function () {
+ el.style.backgroundColor = '';
+ setTimeout(function () { el.style.transition = ''; }, 300);
+ }, 1200);
+ }
+
+ function renderToc(tocEl, content, editor) {
+ if (!tocEl) return;
+ var headings = parseHeadings(content);
+ if (!content.trim()) {
+ tocEl.innerHTML = '