diff --git a/assets/css/base.css b/assets/css/base.css index 11e49eb..82c914a 100644 --- a/assets/css/base.css +++ b/assets/css/base.css @@ -5151,6 +5151,8 @@ body.lt-is-offline .lt-main { margin-top: 2rem; transition: margin-top 0.25s eas .lt-markdown h4, .lt-markdown h5, .lt-markdown h6 { font-size: 0.8rem; color: var(--text-muted); margin: 0.5rem 0 0.25rem; } .lt-markdown p { font-size: 0.82rem; line-height: 1.7; color: var(--text-secondary); margin: 0.5rem 0; } .lt-markdown ul, .lt-markdown ol { padding-left: 1.25rem; margin: 0.5rem 0; } +.lt-markdown ul { list-style: disc; } +.lt-markdown ol { list-style: decimal; } .lt-markdown li { font-size: 0.8rem; color: var(--text-secondary); line-height: 1.6; margin-bottom: 0.2rem; } .lt-markdown ul li::marker { color: var(--accent-cyan); } .lt-markdown ol li::marker { color: var(--accent-orange); } @@ -5167,12 +5169,14 @@ body.lt-is-offline .lt-main { margin-top: 2rem; transition: margin-top 0.25s eas .lt-markdown del { color: var(--text-muted); text-decoration: line-through; } .lt-markdown sub, .lt-markdown sup { font-size: 0.7em; line-height: 0; } .lt-markdown .task-item { list-style: none; margin-left: -1.2em; } +.lt-markdown .fn-ref a { color: var(--accent-cyan); font-size: 0.7em; text-decoration: none; } +.lt-markdown .fn-hr { margin: 1rem 0 0.5rem; } +.lt-markdown .fn-list { font-size: 0.75rem; color: var(--text-muted); list-style: decimal; padding-left: 1.25rem; margin: 0; } +.lt-markdown .fn-item { margin-bottom: 0.2rem; } +.lt-markdown .fn-back { color: var(--accent-cyan); text-decoration: none; font-size: 0.85em; } .lt-markdown .task-cb { margin-right: 0.35em; font-size: 1em; } .lt-markdown .task-done { color: var(--text-muted); text-decoration: line-through; } .lt-markdown .task-todo { color: var(--text-secondary); } -.lt-markdown ol { padding-left: 1.5em; margin: 0.5rem 0; } -.lt-markdown ol li { color: var(--text-secondary); } -.lt-markdown ol li::marker { color: var(--accent-orange); } .lt-markdown table { width: 100%; border-collapse: collapse; font-size: 0.78rem; margin: 0.75rem 0; } .lt-markdown th { background: var(--bg-secondary); color: var(--accent-cyan); padding: 0.4rem 0.6rem; border: 1px solid var(--border-dim); text-align: left; font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.08em; } .lt-markdown td { padding: 0.35rem 0.6rem; border: 1px solid var(--border-dim); color: var(--text-secondary); } diff --git a/assets/js/markdown.js b/assets/js/markdown.js index 33a4a57..d413e11 100644 --- a/assets/js/markdown.js +++ b/assets/js/markdown.js @@ -6,6 +6,23 @@ function parseMarkdown(markdown) { if (!markdown) return ''; + // Footnotes — collect definitions and mark references with placeholders + // (must happen before HTML escaping so tags don't get escaped) + const footnotes = {}; + const footnoteOrder = []; + const fnRefs = []; + markdown = markdown.replace(/^\[\^([^\]]+)\]:\s+(.+)$/gm, function(_, label, text) { + footnotes[label] = text; + return ''; + }); + markdown = markdown.replace(/\[\^([^\]]+)\]/g, function(_, label) { + if (!footnotes[label]) return '[^' + label + ']'; + if (!footnoteOrder.includes(label)) footnoteOrder.push(label); + const n = footnoteOrder.indexOf(label) + 1; + fnRefs.push({ label, n }); + return '%%FNREF' + (fnRefs.length - 1) + '%%'; + }); + let html = markdown; // Escape HTML first to prevent XSS @@ -122,11 +139,28 @@ function parseMarkdown(markdown) { html = html.replace('%%INLINECODE' + i + '%%', code); }); + // Restore footnote reference placeholders + fnRefs.forEach(function(ref, i) { + html = html.replace('%%FNREF' + i + '%%', + '[' + ref.n + ']'); + }); + // Wrap in paragraph if not already wrapped if (!html.startsWith('<')) { html = '

' + html + '

'; } + // Append footnote definitions block + if (footnoteOrder.length) { + html += '
    '; + footnoteOrder.forEach(function(label, i) { + html += '
  1. ' + + parseMarkdown(footnotes[label]).replace(/<\/?p>/g, '') + + '
  2. '; + }); + html += '
'; + } + return html; }