diff --git a/shaarli-pro/css/custom_views.css b/shaarli-pro/css/custom_views.css index c50c168..31b9f6d 100644 --- a/shaarli-pro/css/custom_views.css +++ b/shaarli-pro/css/custom_views.css @@ -265,7 +265,7 @@ body.view-notes .content-container { align-items: center; padding: 12px 16px; cursor: text; - color: var(--text-light, #80868b); + color: var(--note-card-fg, var(--text-light, #80868b)); font-weight: 500; font-size: 1rem; } @@ -282,6 +282,77 @@ body.view-notes .content-container { max-height: 72vh; } +.note-input-expanded-actions { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + margin-top: 2px; + flex-wrap: wrap; +} + +.note-input-expanded .note-formatting-bar, +.note-modal .note-formatting-bar { + width: 100%; + border-radius: 10px; + padding: 6px 8px; +} + +.todo-draft-expanded { + gap: 8px; +} + +.todo-draft-list { + display: flex; + flex-direction: column; + gap: 6px; + max-height: 42vh; + overflow: auto; + padding-right: 2px; +} + +.todo-draft-row { + display: grid; + grid-template-columns: 26px minmax(0, 1fr) 32px; + gap: 8px; + align-items: center; + padding: 2px 0; + border-radius: 8px; +} + +.todo-draft-row.is-checked { + opacity: 0.75; +} + +.todo-draft-row .todo-item-text { + padding: 5px 4px; + font-size: 0.95rem; +} + +.todo-draft-add-btn { + border: none; + background: none; + color: inherit; + opacity: 0.8; + cursor: pointer; + display: inline-flex; + align-items: center; + gap: 8px; + width: fit-content; + border-radius: 8px; + padding: 6px 8px; + margin-left: -2px; +} + +.todo-draft-add-btn:hover { + opacity: 1; + background: rgba(0, 0, 0, 0.08); +} + +[data-theme="dark"] .todo-draft-add-btn:hover { + background: rgba(255, 255, 255, 0.14); +} + .note-input-title { width: 100%; border: 0; @@ -461,16 +532,23 @@ body.view-notes .content-container { gap: 16px; } +.note-input-type-actions { + gap: 6px; +} + .note-input-actions button { background: none; border: none; - cursor: pointer; - color: var(--text-light, #80868b); - padding: 4px; + color: inherit; + opacity: 0.9; + width: 32px; + height: 32px; border-radius: 50%; - display: flex; + display: inline-flex; align-items: center; justify-content: center; + cursor: pointer; + padding: 0; transition: background-color 0.2s, color 0.2s; } @@ -479,6 +557,10 @@ body.view-notes .content-container { color: var(--text-color); } +[data-theme="dark"] .note-input-actions button:hover { + color: #e8eaed; +} + /* Tools (View Toggle) */ .notes-tools { position: absolute; @@ -509,9 +591,7 @@ body.view-notes .content-container { .icon-btn.active { color: var(--primary-color, #202124); - /* or specific active color */ background-color: rgba(136, 136, 136, 0.1); - /* Keep highlight style */ } [data-theme="dark"] .icon-btn { @@ -523,14 +603,33 @@ body.view-notes .content-container { } /* --- LOGIC: Masonry vs List --- */ - -/* Masonry Grid */ .notes-masonry { column-count: 4; column-gap: 16px; width: 100%; } +.notes-masonry .note-card { + display: inline-block; + width: 100%; + margin: 0 0 16px; + break-inside: avoid; + -webkit-column-break-inside: avoid; + page-break-inside: avoid; + column-span: none; + border-radius: 12px; + overflow: hidden; +} + +.note-card { + border-radius: 12px; + overflow: hidden; +} + +.note-card .note-inner { + padding: 14px 16px 12px; +} + @media (max-width: 1200px) { .notes-masonry { column-count: 3; @@ -549,7 +648,6 @@ body.view-notes .content-container { } } -/* List View */ .notes-list-view { display: flex; flex-direction: column; @@ -631,7 +729,16 @@ body.note-modal-open { .note-modal-content { flex: 1; overflow-y: auto; - padding: 0; + padding: 6px 20px 10px; +} + +.note-modal-description-preview { + width: 100%; + min-height: 200px; + max-height: 58vh; + overflow: auto; + cursor: text; + padding: 2px 0; } .note-modal-description-source { @@ -639,7 +746,7 @@ body.note-modal-open { border: none; background: transparent; color: inherit; - padding: 0; + padding: 2px 0; resize: none; min-height: 200px; max-height: 58vh; @@ -662,6 +769,22 @@ body.note-modal-open { display: none; } +.note-modal-overlay .note-modal-description-preview { + display: block; +} + +.note-modal-overlay .note-modal-description-source { + display: none; +} + +.note-modal-overlay.note-modal-editing .note-modal-description-preview { + display: none; +} + +.note-modal-overlay.note-modal-editing .note-modal-description-source { + display: block; +} + .note-modal .note-body * { color: inherit; } @@ -674,270 +797,326 @@ body.note-modal-open { flex-shrink: 0; } - .note-modal-tags.is-empty { - display: none; - } +.note-modal-tags.is-empty { + display: none; +} - .note-modal-tags .note-tag { - display: inline-flex; - align-items: center; - gap: 0.25rem; - background: var(--tag-bg); - color: var(--tag-text); - border-radius: 999px; - padding: 0.25rem 0.5rem; - font-size: 0.75rem; - font-weight: 500; - } +.note-modal-tags .note-tag { + display: inline-flex; + align-items: center; + gap: 0.25rem; + background: var(--tag-bg); + color: var(--tag-text); + border-radius: 999px; + padding: 0.25rem 0.5rem; + font-size: 0.75rem; + font-weight: 500; +} - .note-modal-tags .note-tag-text { - line-height: 1.2; - } +.note-modal-tags .note-tag-text { + line-height: 1.2; +} - .note-modal-tags .note-tag-remove-btn { - display: inline-flex; - align-items: center; - justify-content: center; - width: 1.1rem; - height: 1.1rem; - border: none; - border-radius: 999px; - background: rgba(0, 0, 0, 0.12); - color: currentColor; - cursor: pointer; - padding: 0; - line-height: 1; - font-size: 0.8rem; - } +.note-modal-tags .note-tag-remove-btn { + display: inline-flex; + align-items: center; + justify-content: center; + width: 1.1rem; + height: 1.1rem; + border: none; + border-radius: 999px; + background: rgba(0, 0, 0, 0.12); + color: currentColor; + cursor: pointer; + padding: 0; + line-height: 1; + font-size: 0.8rem; +} - [data-theme="dark"] .note-modal-tags .note-tag-remove-btn { - background: rgba(255, 255, 255, 0.18); - } +[data-theme="dark"] .note-modal-tags .note-tag-remove-btn { + background: rgba(255, 255, 255, 0.18); +} - .note-modal-actions { - display: flex; - align-items: center; - justify-content: space-between; - gap: 6px; - padding: 8px 16px 16px; - background: transparent; - flex-shrink: 0; - flex-wrap: wrap; - } +.note-modal-actions { + display: flex; + align-items: center; + justify-content: space-between; + gap: 6px; + padding: 8px 16px 16px; + background: transparent; + flex-shrink: 0; + flex-wrap: wrap; +} - [data-theme="dark"] .note-modal-actions { - background: transparent; - } +[data-theme="dark"] .note-modal-actions { + background: transparent; +} - .note-modal-actions-left { - display: flex; - align-items: center; - gap: 2px; - flex-wrap: wrap; - } +.note-modal-actions-left { + display: flex; + align-items: center; + gap: 2px; + flex-wrap: wrap; +} - .note-modal-actions-left > button, - .note-modal-actions-left > a, - .note-modal-actions-left > .note-modal-color-picker > button { - background: transparent; - border: none; - color: inherit; - opacity: 0.9; - width: 32px; - height: 32px; - border-radius: 50%; - display: inline-flex; - align-items: center; - justify-content: center; - cursor: pointer; - text-decoration: none; - } +.note-modal-actions-left > button, +.note-modal-actions-left > a, +.note-modal-actions-left > .note-modal-color-picker > button { + background: transparent; + border: none; + color: inherit; + opacity: 0.9; + width: 32px; + height: 32px; + border-radius: 50%; + display: inline-flex; + align-items: center; + justify-content: center; + cursor: pointer; + text-decoration: none; +} - .note-modal-actions-left > button:hover, - .note-modal-actions-left > a:hover, - .note-modal-actions-left > .note-modal-color-picker > button:hover { - opacity: 1; - background: rgba(0, 0, 0, 0.08); - } +.note-modal-actions-left > button:hover, +.note-modal-actions-left > a:hover, +.note-modal-actions-left > .note-modal-color-picker > button:hover { + opacity: 1; + background: rgba(0, 0, 0, 0.08); +} - [data-theme="dark"] .note-modal-actions-left > button:hover, - [data-theme="dark"] .note-modal-actions-left > a:hover, - [data-theme="dark"] .note-modal-actions-left > .note-modal-color-picker > button:hover { - background: rgba(255, 255, 255, 0.14); - } +[data-theme="dark"] .note-modal-actions-left > button:hover, +[data-theme="dark"] .note-modal-actions-left > a:hover, +[data-theme="dark"] .note-modal-actions-left > .note-modal-color-picker > button:hover { + background: rgba(255, 255, 255, 0.14); +} - .note-modal-close-btn { - background: transparent; - border: none; - color: inherit; - font-weight: 600; - border-radius: 8px; - padding: 6px 10px; - cursor: pointer; - } +.note-modal-close-btn { + background: transparent; + border: none; + color: inherit; + font-weight: 600; + border-radius: 8px; + padding: 6px 10px; + cursor: pointer; +} - .note-modal-close-btn:hover { - background: rgba(0, 0, 0, 0.08); - } +.note-modal-close-btn:hover { + background: rgba(0, 0, 0, 0.08); +} - [data-theme="dark"] .note-modal-close-btn:hover { - background: rgba(255, 255, 255, 0.14); - } +[data-theme="dark"] .note-modal-close-btn:hover { + background: rgba(255, 255, 255, 0.14); +} - .note-modal-pin-toggle { - background: transparent; - border: none; - color: inherit; - opacity: 0.85; - width: 34px; - height: 34px; - border-radius: 50%; - display: inline-flex; - align-items: center; - justify-content: center; - cursor: pointer; - flex-shrink: 0; - } +.note-modal-pin-toggle { + background: transparent; + border: none; + color: inherit; + opacity: 0.85; + width: 34px; + height: 34px; + border-radius: 50%; + display: inline-flex; + align-items: center; + justify-content: center; + cursor: pointer; + flex-shrink: 0; +} - .note-modal-pin-toggle:hover, - .note-modal-pin-toggle.active { - opacity: 1; - background-color: rgba(0, 0, 0, 0.08); - } +.note-modal-pin-toggle:hover, +.note-modal-pin-toggle.active { + opacity: 1; + background-color: rgba(0, 0, 0, 0.08); +} - [data-theme="dark"] .note-modal-pin-toggle:hover, - [data-theme="dark"] .note-modal-pin-toggle.active { - background-color: rgba(255, 255, 255, 0.14); - } +[data-theme="dark"] .note-modal-pin-toggle:hover, +[data-theme="dark"] .note-modal-pin-toggle.active { + background-color: rgba(255, 255, 255, 0.14); +} - /* --- CARD STYLING --- */ - .note-card { - background-color: var(--background-secondary, #ffffff); - border: 1px solid rgba(0, 0, 0, 0.12); - border-radius: 8px; - margin-bottom: 16px; - break-inside: avoid; - position: relative; - transition: box-shadow 0.2s cubic-bezier(0.4, 0.0, 0.2, 1), transform 0.2s cubic-bezier(0.4, 0.0, 0.2, 1), border-color 0.2s cubic-bezier(0.4, 0.0, 0.2, 1); - overflow: visible; - color: var(--note-card-fg, #202124); - } +.note-body { + font-size: 0.875rem; + line-height: 1.4; + color: var(--text-color, #202124); + word-wrap: break-word; + display: block; + overflow: hidden; + max-height: 360px; +} - [data-theme="dark"] .note-card { - background-color: #202124; - border-color: #5f6368; - color: #e8eaed; - } +[data-theme="dark"] .note-body { + color: #e8eaed; +} - .note-card:hover { - box-shadow: 0 1px 2px 0 rgba(60, 64, 67, 0.3), 0 1px 3px 1px rgba(60, 64, 67, 0.15); - border-color: transparent; - } +.note-body p, +.note-modal-content p { + margin: 0 0 0.5rem; +} - [data-theme="dark"] .note-card:hover { - background-color: #202124; - box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.6), 0 1px 3px 1px rgba(0, 0, 0, 0.4); - border-color: transparent; - } +.note-body ul, +.note-body ol, +.note-modal-content ul, +.note-modal-content ol { + margin: 0 0 0.5rem 1.2rem; + padding: 0; +} - /* Cover Image */ - .note-cover { - overflow: hidden; - border-radius: 8px 8px 0 0; - } +.note-body li, +.note-modal-content li { + margin: 0 0 0.3rem; +} - .note-cover img { - width: 100%; - height: auto; - display: block; - object-fit: cover; - } +.note-body blockquote, +.note-modal-content blockquote { + margin: 0.2rem 0 0.6rem; + padding: 0.2rem 0.75rem; + border-left: 3px solid rgba(0, 0, 0, 0.22); + opacity: 0.9; +} - /* Inner Content */ - .note-inner { - padding: 12px 16px; - display: flex; - flex-direction: column; - gap: 8px; - position: relative; - z-index: 2; - } +[data-theme="dark"] .note-body blockquote, +[data-theme="dark"] .note-modal-content blockquote { + border-left-color: rgba(255, 255, 255, 0.28); +} - /* Title */ - .note-title { - font-size: 1rem; - font-weight: 500; - margin: 0; - line-height: 1.5rem; - color: var(--text-color, #202124); - } +.note-body code, +.note-modal-content code { + font-family: Consolas, "Courier New", monospace; + background: rgba(0, 0, 0, 0.08); + border-radius: 4px; + padding: 0.05rem 0.35rem; + font-size: 0.86em; +} - [data-theme="dark"] .note-title { - color: #e8eaed; - } +[data-theme="dark"] .note-body code, +[data-theme="dark"] .note-modal-content code { + background: rgba(255, 255, 255, 0.12); +} - /* Body (Truncated) */ - .note-body { - font-size: 0.875rem; - line-height: 1.25rem; - color: var(--text-color, #202124); - word-wrap: break-word; - display: -webkit-box; - -webkit-line-clamp: 12; - line-clamp: 12; - -webkit-box-orient: vertical; - overflow: hidden; - max-height: 300px; - } +.md-code-block { + margin: 0.4rem 0 0.7rem; + background: rgba(0, 0, 0, 0.08); + border-radius: 8px; + overflow: auto; +} - [data-theme="dark"] .note-body { - color: #e8eaed; - } +.md-code-block code { + display: block; + padding: 0.65rem 0.75rem; + background: none; + border-radius: 0; +} - /* Tags */ - .note-tags { - display: flex; - flex-wrap: wrap; - gap: 6px; - margin-top: 4px; - } +[data-theme="dark"] .md-code-block { + background: rgba(255, 255, 255, 0.12); +} - .note-tag { - display: inline-flex; - align-items: center; - gap: 0.25rem; - background: var(--tag-bg, #f1f3f4); - padding: 0.25rem 0.5rem; - border-radius: 999px; - font-size: 0.75rem; - font-weight: 500; - color: var(--tag-text, #3c4043); - } +.md-spacer { + height: 0.35rem; +} - [data-theme="dark"] .note-tag { - background: var(--tag-bg, #3c4043); - color: var(--tag-text, #e8eaed); - } +.md-todo-list { + list-style: none; + margin: 0; + padding: 0; + display: flex; + flex-direction: column; + gap: 0.3rem; +} - .note-tag-text { - line-height: 1.2; - } +.md-todo-item { + display: flex; + align-items: center; + gap: 0.55rem; +} - .note-tag-remove-btn { - display: inline-flex; - align-items: center; - justify-content: center; - width: 1.1rem; - height: 1.1rem; - border: none; - border-radius: 999px; - background: rgba(0, 0, 0, 0.12); - color: currentColor; - cursor: pointer; - padding: 0; - line-height: 1; - font-size: 0.8rem; - } +.md-todo-item.is-checked { + opacity: 0.72; +} + +.md-todo-item.is-checked .md-todo-text { + text-decoration: line-through; +} + +.md-todo-box { + width: 15px; + height: 15px; + border: 2px solid currentColor; + border-radius: 4px; + display: inline-flex; + align-items: center; + justify-content: center; + opacity: 0.7; + flex: 0 0 auto; +} + +.md-todo-box i { + font-size: 13px; + opacity: 0; +} + +.md-todo-item.is-checked .md-todo-box i { + opacity: 1; +} + +@media (max-width: 900px) { + .notes-top-bar { + flex-direction: column; + align-items: stretch; + gap: 10px; + } + + .notes-tools { + position: static; + transform: none; + justify-content: flex-end; + width: 100%; + } +} + +/* Tags */ +.note-tags { + display: flex; + flex-wrap: wrap; + gap: 6px; + margin-top: 4px; +} + +.note-tag { + display: inline-flex; + align-items: center; + gap: 0.25rem; + background: var(--tag-bg, #f1f3f4); + padding: 0.25rem 0.5rem; + border-radius: 999px; + font-size: 0.75rem; + font-weight: 500; + color: var(--tag-text, #3c4043); +} + +[data-theme="dark"] .note-tag { + background: var(--tag-bg, #3c4043); + color: var(--tag-text, #e8eaed); +} + +.note-tag-text { + line-height: 1.2; +} + +.note-tag-remove-btn { + display: inline-flex; + align-items: center; + justify-content: center; + width: 1.1rem; + height: 1.1rem; + border: none; + border-radius: 999px; + background: rgba(0, 0, 0, 0.12); + color: currentColor; + cursor: pointer; + padding: 0; + line-height: 1; + font-size: 0.8rem; +} [data-theme="dark"] .note-tag-remove-btn { background: rgba(255, 255, 255, 0.18); @@ -960,7 +1139,7 @@ body.note-modal-open { gap: 2px; margin-top: 12px; margin-left: -6px; - color: var(--text-light, #5f6368); + color: var(--note-card-fg, currentColor); opacity: 0; transition: opacity 0.2s cubic-bezier(0.4, 0.0, 0.2, 1); position: relative; @@ -969,7 +1148,7 @@ body.note-modal-open { } [data-theme="dark"] .note-hover-actions { - color: #9aa0a6; + color: var(--note-card-fg, currentColor); } /* Show actions on hover */ @@ -1007,7 +1186,7 @@ body.note-modal-open { .note-hover-actions > a:hover, .note-hover-actions > div > button:hover { background-color: rgba(0, 0, 0, 0.08); - color: var(--text-color, #202124); + color: inherit; opacity: 1; } @@ -1015,7 +1194,7 @@ body.note-modal-open { [data-theme="dark"] .note-hover-actions > a:hover, [data-theme="dark"] .note-hover-actions > div > button:hover { background-color: rgba(255, 255, 255, 0.14); - color: #e8eaed; + color: inherit; } .bookmark-palette { @@ -2430,8 +2609,9 @@ body.view-todo .note-card.todo-card .todo-checklist-preview-wrap { -webkit-line-clamp: initial; line-clamp: initial; -webkit-box-orient: initial; - overflow: visible; - max-height: none; + overflow: hidden; + max-height: 360px; + padding-top: 0.25rem; } .todo-checklist-preview { @@ -2445,7 +2625,7 @@ body.view-todo .note-card.todo-card .todo-checklist-preview-wrap { .todo-checklist-preview-item { display: flex; - align-items: center; + align-items: flex-start; gap: 0.6rem; font-size: 0.95rem; line-height: 1.35; @@ -2461,6 +2641,7 @@ body.view-todo .note-card.todo-card .todo-checklist-preview-wrap { justify-content: center; flex: 0 0 auto; opacity: 0.7; + margin-top: 2px; } .todo-checklist-preview-box i { @@ -2468,6 +2649,14 @@ body.view-todo .note-card.todo-card .todo-checklist-preview-wrap { opacity: 0; } +.todo-checklist-preview-text { + flex: 1; + min-width: 0; + overflow-wrap: break-word; + word-wrap: break-word; + word-break: break-word; +} + .todo-checklist-preview-item.is-checked { opacity: 0.7; } diff --git a/shaarli-pro/js/custom_views.js b/shaarli-pro/js/custom_views.js index 5e56696..9f0b0eb 100644 --- a/shaarli-pro/js/custom_views.js +++ b/shaarli-pro/js/custom_views.js @@ -12,13 +12,13 @@ document.addEventListener("DOMContentLoaded", function () { const activeTags = (searchTagsRaw + " " + pathTagRaw).toLowerCase().split(/[\s,]+/).filter(t => t); // Foolproof detection using sidebar active state and DOM rendered tags - const hasNoteActiveMenu = !!document.querySelector('.sidebar-link[aria-label="Notes"].active, .header-nav-link[aria-label="Notes"].active, .sidebar-link[href*="searchtags=note"].active'); + const hasNoteActiveMenu = !!document.querySelector('.sidebar-link[aria-label="Notes"].active, .header-nav-link[aria-label="Notes"].active, .sidebar-link[href*="searchtags=note"].active, .sidebar-link[href*="searchtags=shaarli-note"].active'); const hasTodoActiveMenu = !!document.querySelector('.sidebar-link[aria-label="Mes tâches"].active, .header-nav-link[aria-label="Mes tâches"].active, .sidebar-link[href*="searchtags=shaarli-todo"].active'); const hasArchiveActiveMenu = !!document.querySelector('.sidebar-link[aria-label="Archive"].active, .header-nav-link[aria-label="Archive"].active, .sidebar-link[href*="searchtags=shaarli-archive"].active'); const domChipTags = Array.from(document.querySelectorAll('.search-tag-chip')).map(el => (el.textContent || "").trim().toLowerCase()); - const isNoteView = activeTags.includes("note") || hasNoteActiveMenu || domChipTags.includes("note"); + const isNoteView = activeTags.includes("note") || activeTags.includes("shaarli-note") || hasNoteActiveMenu || domChipTags.includes("note") || domChipTags.includes("shaarli-note"); const isTodoView = activeTags.includes("shaarli-todo") || hasTodoActiveMenu || domChipTags.includes("shaarli-todo"); const isArchiveView = activeTags.includes("shaarli-archive") || hasArchiveActiveMenu || domChipTags.includes("shaarli-archive"); @@ -49,21 +49,205 @@ document.addEventListener("DOMContentLoaded", function () { document.querySelectorAll(".notes-wrapper.todo-wrapper").forEach((el) => el.remove()); }; - // Fonction de rendu Markdown basique pour l'affichage des notes + // Rendu Markdown étendu pour les cartes Notes/Tâches function renderMarkdown(markdown) { if (!markdown) return ""; - let html = markdown - .replace(/&/g, "&") - .replace(//g, ">") - .replace(/^### (.*$)/gim, "

$1

") - .replace(/^## (.*$)/gim, "

$1

") - .replace(/^# (.*$)/gim, "

$1

") - .replace(/\*\*(.*?)\*\*/gim, "$1") - .replace(/\*(.*?)\*/gim, "$1") - .replace(/(.*?)<\/u>/gim, "$1") - .replace(/\n/g, "
"); - return html; + + const normalizeNewlines = String(markdown).replace(/\r\n?/g, "\n"); + const escape = (value) => + String(value) + .replace(/&/g, "&") + .replace(//g, ">"); + + const escapeAttr = (value) => + String(value) + .replace(/&/g, "&") + .replace(/"/g, """) + .replace(//g, ">"); + + const sanitizeUrl = (rawUrl) => { + const url = String(rawUrl || "").trim(); + if (!url) return ""; + if (/^(https?:|mailto:|\/)/i.test(url)) return url; + return ""; + }; + + const renderInline = (input) => { + let out = escape(input || ""); + + const codeTokens = []; + out = out.replace(/`([^`\n]+)`/g, (_, code) => { + const token = `__MD_CODE_${codeTokens.length}__`; + codeTokens.push(`${code}`); + return token; + }); + + out = out.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, (_, alt, rawUrl) => { + const url = sanitizeUrl(rawUrl); + if (!url) return _; + return `${escapeAttr(alt)}`; + }); + + out = out.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_, label, rawUrl) => { + const url = sanitizeUrl(rawUrl); + if (!url) return _; + return `${label}`; + }); + + out = out.replace(/\*\*(.+?)\*\*/g, "$1"); + out = out.replace(/__(.+?)__/g, "$1"); + out = out.replace(/~~(.+?)~~/g, "$1"); + out = out.replace(/\*(.+?)\*/g, "$1"); + out = out.replace(/_(.+?)_/g, "$1"); + out = out.replace(/<u>([\s\S]*?)<\/u>/g, "$1"); + + out = out.replace(/__MD_CODE_(\d+)__/g, (_, idx) => codeTokens[Number(idx)] || ""); + return out; + }; + + const lines = normalizeNewlines.split("\n"); + const html = []; + let inUl = false; + let inOl = false; + let inTodo = false; + let inCode = false; + let codeLang = ""; + let codeLines = []; + + const closeLists = () => { + if (inUl) { + html.push(""); + inUl = false; + } + if (inOl) { + html.push(""); + inOl = false; + } + if (inTodo) { + html.push(""); + inTodo = false; + } + }; + + lines.forEach((line) => { + const trimmed = line.trim(); + + const codeFenceMatch = trimmed.match(/^```\s*([\w-]+)?\s*$/); + if (codeFenceMatch) { + closeLists(); + if (!inCode) { + inCode = true; + codeLang = codeFenceMatch[1] || ""; + codeLines = []; + } else { + const langAttr = codeLang ? ` data-lang="${escapeAttr(codeLang)}"` : ""; + html.push(`
${escape(codeLines.join("\n"))}
`); + inCode = false; + codeLang = ""; + codeLines = []; + } + return; + } + + if (inCode) { + codeLines.push(line); + return; + } + + if (!trimmed) { + closeLists(); + html.push('
'); + return; + } + + const headingMatch = line.match(/^\s*(#{1,6})\s+(.+)$/); + if (headingMatch) { + closeLists(); + const level = headingMatch[1].length; + html.push(`${renderInline(headingMatch[2])}`); + return; + } + + const quoteMatch = line.match(/^\s*>\s?(.*)$/); + if (quoteMatch) { + closeLists(); + html.push(`
${renderInline(quoteMatch[1])}
`); + return; + } + + const todoMatch = line.match(/^\s*[-*]\s+\[( |x|X)\]\s+(.+)$/); + if (todoMatch) { + if (inUl) { + html.push(""); + inUl = false; + } + if (inOl) { + html.push(""); + inOl = false; + } + if (!inTodo) { + html.push('"); + inUl = false; + } + if (inTodo) { + html.push(""); + inTodo = false; + } + if (!inOl) { + html.push("
    "); + inOl = true; + } + html.push(`
  1. ${renderInline(olMatch[1])}
  2. `); + return; + } + + const ulMatch = line.match(/^\s*[-*+]\s+(.+)$/); + if (ulMatch) { + if (inOl) { + html.push("
"); + inOl = false; + } + if (inTodo) { + html.push(""); + inTodo = false; + } + if (!inUl) { + html.push("