2025-11-05 16:48:41 +00:00
<!DOCTYPE html>
< html lang = "en" >
< head >
< meta charset = "UTF-8" >
< meta name = "viewport" content = "width=device-width, initial-scale=1.0" >
< title > NoteDiscovery< / title >
<!-- Favicon -->
< link rel = "icon" type = "image/svg+xml" href = "/static/favicon.svg" >
< link rel = "icon" type = "image/png" sizes = "32x32" href = "/static/favicon.svg" >
< link rel = "icon" type = "image/png" sizes = "16x16" href = "/static/favicon.svg" >
< link rel = "apple-touch-icon" href = "/static/logo.svg" >
<!-- Tailwind CSS from CDN -->
< script src = "https://cdn.tailwindcss.com" > < / script >
<!-- Alpine.js -->
< script defer src = "https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js" > < / script >
<!-- Marked.js for Markdown parsing -->
< script src = "https://cdn.jsdelivr.net/npm/marked/marked.min.js" > < / script >
2025-11-11 13:51:55 +00:00
<!-- MathJax for LaTeX math rendering -->
< script >
MathJax = {
tex: {
inlineMath: [['$', '$']],
displayMath: [['$$', '$$']],
processEscapes: true,
processEnvironments: true
},
options: {
skipHtmlTags: ['script', 'noscript', 'style', 'textarea', 'pre', 'code']
},
startup: {
pageReady: () => {
return MathJax.startup.defaultPageReady();
}
}
};
< / script >
< script src = "https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js" > < / script >
2025-11-05 16:48:41 +00:00
<!-- Highlight.js for code syntax highlighting -->
< link rel = "stylesheet" id = "highlight-theme" href = "" >
< script src = "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js" > < / script >
< script src = "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/csharp.min.js" > < / script >
< script src = "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/python.min.js" > < / script >
< script src = "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/bash.min.js" > < / script >
< script src = "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/shell.min.js" > < / script >
< script src = "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/powershell.min.js" > < / script >
< script src = "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/javascript.min.js" > < / script >
< script src = "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/typescript.min.js" > < / script >
< script src = "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/json.min.js" > < / script >
< script src = "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/sql.min.js" > < / script >
< script src = "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/yaml.min.js" > < / script >
< script src = "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/css.min.js" > < / script >
< script src = "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/http.min.js" > < / script >
2025-11-17 16:29:00 +00:00
<!-- Mermaid.js for diagram rendering -->
< script type = "module" >
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs';
2025-11-17 16:37:29 +00:00
// Note: Mermaid is initialized dynamically by app.js renderMermaid() based on current theme
2025-11-17 16:29:00 +00:00
window.mermaid = mermaid;
< / script >
2025-11-05 16:48:41 +00:00
<!-- Theme styles will be loaded dynamically -->
< link rel = "stylesheet" id = "theme-stylesheet" href = "" >
<!-- Custom styles -->
< style >
[x-cloak] { display: none !important; }
/* Theme-aware styles using CSS variables */
body {
background-color: var(--bg-primary, #ffffff);
color: var(--text-primary, #111827);
}
.bg-themed {
background-color: var(--bg-primary);
}
.bg-themed-secondary {
background-color: var(--bg-secondary);
}
.text-themed {
color: var(--text-primary);
}
.text-themed-secondary {
color: var(--text-secondary);
}
.border-themed {
border-color: var(--border-primary);
}
.bg-accent {
background-color: var(--accent-primary);
}
.bg-accent:hover {
background-color: var(--accent-hover);
}
.text-accent {
color: var(--accent-primary);
}
/* Markdown preview styling */
.markdown-preview {
line-height: 1.8;
color: var(--text-primary);
font-size: 16px;
}
/* Remove top margin from first element */
.markdown-preview > *:first-child {
margin-top: 0 !important;
}
/* Headings */
.markdown-preview h1 {
font-size: 2.25rem;
font-weight: 700;
margin-top: 2rem;
margin-bottom: 1rem;
color: var(--text-primary);
border-bottom: 2px solid var(--border-primary);
padding-bottom: 0.5rem;
}
.markdown-preview h2 {
font-size: 1.875rem;
font-weight: 700;
margin-top: 1.75rem;
margin-bottom: 0.875rem;
color: var(--text-primary);
border-bottom: 1px solid var(--border-primary);
padding-bottom: 0.3rem;
}
.markdown-preview h3 {
font-size: 1.5rem;
font-weight: 600;
margin-top: 1.5rem;
margin-bottom: 0.75rem;
color: var(--text-primary);
}
.markdown-preview h4 {
font-size: 1.25rem;
font-weight: 600;
margin-top: 1.25rem;
margin-bottom: 0.625rem;
color: var(--text-primary);
}
.markdown-preview h5 {
font-size: 1.125rem;
font-weight: 600;
margin-top: 1rem;
margin-bottom: 0.5rem;
color: var(--text-primary);
}
.markdown-preview h6 {
font-size: 1rem;
font-weight: 600;
margin-top: 1rem;
margin-bottom: 0.5rem;
color: var(--text-secondary);
}
/* Paragraphs and text */
.markdown-preview p {
margin-bottom: 1rem;
line-height: 1.7;
}
.markdown-preview strong {
font-weight: 600;
color: var(--text-primary);
}
.markdown-preview em {
font-style: italic;
}
/* Lists */
.markdown-preview ul, .markdown-preview ol {
margin-bottom: 1rem;
margin-left: 1.5rem;
padding-left: 0.5rem;
}
.markdown-preview ul { list-style-type: disc; }
.markdown-preview ol { list-style-type: decimal; }
.markdown-preview li {
margin-bottom: 0.5rem;
line-height: 1.6;
}
.markdown-preview li > ul,
.markdown-preview li > ol {
margin-top: 0.5rem;
margin-bottom: 0.5rem;
}
/* Inline code */
.markdown-preview code:not(pre code) {
background-color: var(--bg-tertiary);
color: var(--accent-primary);
padding: 0.2rem 0.4rem;
border-radius: 0.25rem;
font-size: 0.875rem;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-weight: 500;
}
/* Code blocks */
.markdown-preview pre {
background-color: var(--bg-tertiary);
margin-bottom: 1.5rem;
border-radius: 0.5rem;
overflow-x: auto;
border: 1px solid var(--border-primary);
}
.markdown-preview pre code {
background: transparent;
padding: 1rem;
display: block;
font-size: 0.875rem;
line-height: 1.6;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
}
/* Blockquotes */
.markdown-preview blockquote {
border-left: 4px solid var(--accent-primary);
padding-left: 1rem;
margin: 1.5rem 0;
font-style: italic;
color: var(--text-secondary);
background-color: var(--bg-tertiary);
padding: 1rem;
padding-left: 1rem;
border-radius: 0.25rem;
}
.markdown-preview blockquote p {
margin-bottom: 0;
}
/* Links */
.markdown-preview a {
color: var(--accent-primary);
text-decoration: underline;
text-decoration-color: transparent;
transition: text-decoration-color 0.2s;
cursor: pointer;
}
.markdown-preview a:hover {
text-decoration-color: var(--accent-primary);
}
/* Internal links (not starting with http/https) get a special style */
.markdown-preview a:not([href^="http"]):not([href^="https"]):not([href^="//"]):not([href^="mailto:"]) {
color: var(--accent-primary);
font-weight: 500;
}
.markdown-preview a:not([href^="http"]):not([href^="https"]):not([href^="//"]):not([href^="mailto:"]):before {
content: "→ ";
font-size: 0.9em;
opacity: 0.7;
}
/* Tables */
.markdown-preview table {
width: 100%;
margin-bottom: 1.5rem;
border-collapse: collapse;
border: 1px solid var(--border-primary);
}
.markdown-preview th, .markdown-preview td {
border: 1px solid var(--border-primary);
padding: 0.75rem 1rem;
text-align: left;
}
.markdown-preview th {
background-color: var(--bg-tertiary);
font-weight: 600;
color: var(--text-primary);
}
.markdown-preview tr:nth-child(even) {
background-color: var(--bg-secondary);
}
/* Horizontal rule */
.markdown-preview hr {
border: none;
border-top: 2px solid var(--border-primary);
margin: 2rem 0;
}
/* Images */
.markdown-preview img {
max-width: 100%;
height: auto;
border-radius: 0.5rem;
margin: 1rem 0;
}
/* Task lists */
.markdown-preview input[type="checkbox"] {
margin-right: 0.5rem;
}
2025-11-11 13:51:55 +00:00
/* Math/LaTeX styling */
.markdown-preview .MathJax {
color: var(--text-primary);
overflow-x: auto;
overflow-y: hidden;
}
/* Display math (block) */
.markdown-preview mjx-container[display="true"] {
margin: 1.5rem 0;
overflow-x: auto;
}
/* Inline math */
.markdown-preview mjx-container[display="false"] {
margin: 0 0.2rem;
}
2025-11-17 16:29:00 +00:00
/* Mermaid diagram styling - minimal, let Mermaid handle its own rendering */
.markdown-preview .mermaid-rendered svg {
max-width: 100%;
height: auto;
}
2025-11-05 16:48:41 +00:00
/* Editor styling */
.editor-textarea {
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
tab-size: 2;
background-color: var(--bg-primary) !important;
color: var(--text-primary) !important;
}
/* Link drop zone cursor */
.editor-textarea.link-drop-target {
cursor: copy !important;
}
/* Folder tree styling */
.folder-item {
user-select: none;
2025-11-18 09:03:47 +00:00
transition: all 0.2s ease;
2025-11-05 16:48:41 +00:00
}
.folder-item:hover {
background-color: var(--bg-hover);
}
.folder-expanded {
font-weight: 600;
}
.note-item {
padding-left: 1.5rem;
2025-11-18 09:03:47 +00:00
transition: all 0.2s ease;
}
/* Drag and drop improvements */
.note-item[draggable="true"],
.folder-item[draggable="true"] {
cursor: grab;
}
.note-item[draggable="true"]:active,
.folder-item[draggable="true"]:active {
cursor: grabbing;
}
/* Smooth transitions for drag states */
.folder-item,
.note-item {
transition: opacity 0.2s ease, transform 0.15s ease, background-color 0.15s ease, border-color 0.15s ease;
}
/* Better drop zone visibility */
.folder-item:has(+ .folder-contents) {
position: relative;
2025-11-05 16:48:41 +00:00
}
.hover-buttons {
transition: opacity 0.2s;
}
.folder-item:hover .hover-buttons {
opacity: 1 !important;
pointer-events: auto !important;
}
/* Note delete button on hover */
.note-item:hover .note-delete-btn {
opacity: 1 !important;
}
/* Folder hierarchy lines (Obsidian-style) */
.folder-contents {
position: relative;
padding-left: 1rem;
margin-left: 0.5rem;
}
.folder-contents::before {
content: '';
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 1px;
2025-11-09 09:17:09 +00:00
background-color: var(--text-tertiary);
2025-11-05 16:48:41 +00:00
opacity: 0.4;
}
/* Scrollbar styling */
.custom-scrollbar::-webkit-scrollbar,
.editor-textarea::-webkit-scrollbar,
.markdown-preview::-webkit-scrollbar {
width: 12px;
height: 12px;
}
.custom-scrollbar::-webkit-scrollbar-track,
.editor-textarea::-webkit-scrollbar-track,
.markdown-preview::-webkit-scrollbar-track {
background: var(--bg-tertiary);
}
.custom-scrollbar::-webkit-scrollbar-thumb,
.editor-textarea::-webkit-scrollbar-thumb,
.markdown-preview::-webkit-scrollbar-thumb {
background: var(--border-secondary);
border-radius: 6px;
border: 2px solid var(--bg-tertiary);
}
.custom-scrollbar::-webkit-scrollbar-thumb:hover,
.editor-textarea::-webkit-scrollbar-thumb:hover,
.markdown-preview::-webkit-scrollbar-thumb:hover {
background: var(--text-tertiary);
}
/* Firefox scrollbar */
.custom-scrollbar,
.editor-textarea,
.markdown-preview {
scrollbar-width: thin;
scrollbar-color: var(--border-secondary) var(--bg-tertiary);
}
/* Resize handle */
.resize-handle {
position: relative;
user-select: none;
flex-shrink: 0;
}
.resize-handle:hover {
background-color: var(--accent-primary) !important;
}
.resize-handle:active {
background-color: var(--accent-hover) !important;
}
2025-11-09 09:17:09 +00:00
/* Split view resize handle */
.split-resize-handle {
position: relative;
user-select: none;
flex-shrink: 0;
}
.split-resize-handle:hover {
background-color: var(--accent-primary) !important;
}
.split-resize-handle:active {
background-color: var(--accent-hover) !important;
}
2025-11-05 16:48:41 +00:00
/* Enhanced Shell/Bash Syntax Highlighting */
.markdown-preview pre code.language-shell .hljs-meta,
.markdown-preview pre code.language-bash .hljs-meta,
.markdown-preview pre code.language-sh .hljs-meta {
color: #7c3aed !important; /* Purple for prompts like $ # */
font-weight: 600;
}
.markdown-preview pre code.language-shell .hljs-built_in,
.markdown-preview pre code.language-bash .hljs-built_in,
.markdown-preview pre code.language-sh .hljs-built_in {
color: #10b981 !important; /* Green for built-in commands */
font-weight: 500;
}
.markdown-preview pre code.language-shell .hljs-string,
.markdown-preview pre code.language-bash .hljs-string,
.markdown-preview pre code.language-sh .hljs-string {
color: #f59e0b !important; /* Orange for strings */
}
.markdown-preview pre code.language-shell .hljs-variable,
.markdown-preview pre code.language-bash .hljs-variable,
.markdown-preview pre code.language-sh .hljs-variable {
color: #06b6d4 !important; /* Cyan for variables */
font-weight: 500;
}
.markdown-preview pre code.language-shell .hljs-comment,
.markdown-preview pre code.language-bash .hljs-comment,
.markdown-preview pre code.language-sh .hljs-comment {
color: #6b7280 !important; /* Gray for comments */
font-style: italic;
}
.markdown-preview pre code.language-shell .hljs-keyword,
.markdown-preview pre code.language-bash .hljs-keyword,
.markdown-preview pre code.language-sh .hljs-keyword {
color: #ec4899 !important; /* Pink for keywords like if, then, else */
font-weight: 600;
}
.markdown-preview pre code.language-shell .hljs-number,
.markdown-preview pre code.language-bash .hljs-number,
.markdown-preview pre code.language-sh .hljs-number {
color: #8b5cf6 !important; /* Purple for numbers */
}
.markdown-preview pre code.language-shell .hljs-literal,
.markdown-preview pre code.language-bash .hljs-literal,
.markdown-preview pre code.language-sh .hljs-literal {
color: #3b82f6 !important; /* Blue for literals like true, false */
font-weight: 500;
}
/* Enhanced PowerShell Syntax Highlighting */
.markdown-preview pre code.language-powershell .hljs-built_in,
.markdown-preview pre code.language-ps1 .hljs-built_in,
.markdown-preview pre code.language-ps .hljs-built_in {
color: #10b981 !important; /* Green for cmdlets like Get-Process, Set-Location */
font-weight: 600;
}
.markdown-preview pre code.language-powershell .hljs-literal,
.markdown-preview pre code.language-ps1 .hljs-literal,
.markdown-preview pre code.language-ps .hljs-literal {
color: #3b82f6 !important; /* Blue for $true, $false, $null */
font-weight: 600;
}
.markdown-preview pre code.language-powershell .hljs-variable,
.markdown-preview pre code.language-ps1 .hljs-variable,
.markdown-preview pre code.language-ps .hljs-variable {
color: #06b6d4 !important; /* Cyan for $variables */
font-weight: 500;
}
.markdown-preview pre code.language-powershell .hljs-string,
.markdown-preview pre code.language-ps1 .hljs-string,
.markdown-preview pre code.language-ps .hljs-string {
color: #f59e0b !important; /* Orange for strings */
}
.markdown-preview pre code.language-powershell .hljs-keyword,
.markdown-preview pre code.language-ps1 .hljs-keyword,
.markdown-preview pre code.language-ps .hljs-keyword {
color: #ec4899 !important; /* Pink for keywords like if, else, foreach */
font-weight: 600;
}
.markdown-preview pre code.language-powershell .hljs-params,
.markdown-preview pre code.language-ps1 .hljs-params,
.markdown-preview pre code.language-ps .hljs-params {
color: #8b5cf6 !important; /* Purple for -Parameters */
font-weight: 500;
}
.markdown-preview pre code.language-powershell .hljs-type,
.markdown-preview pre code.language-ps1 .hljs-type,
.markdown-preview pre code.language-ps .hljs-type {
color: #14b8a6 !important; /* Teal for [Types] */
font-weight: 500;
}
.markdown-preview pre code.language-powershell .hljs-comment,
.markdown-preview pre code.language-ps1 .hljs-comment,
.markdown-preview pre code.language-ps .hljs-comment {
color: #6b7280 !important; /* Gray for comments */
font-style: italic;
}
.markdown-preview pre code.language-powershell .hljs-number,
.markdown-preview pre code.language-ps1 .hljs-number,
.markdown-preview pre code.language-ps .hljs-number {
color: #8b5cf6 !important; /* Purple for numbers */
}
.markdown-preview pre code.language-powershell .hljs-operator,
.markdown-preview pre code.language-ps1 .hljs-operator,
.markdown-preview pre code.language-ps .hljs-operator {
color: #f472b6 !important; /* Pink for operators */
}
2025-11-13 14:09:26 +00:00
/* Mobile Responsive Styles */
@media (max-width: 768px) {
/* Hide sidebar by default on mobile */
.mobile-sidebar {
position: fixed;
left: -100%;
top: 0;
height: 100vh;
width: 80% !important;
max-width: 300px !important;
z-index: 1000;
transition: left 0.3s ease;
}
/* Show sidebar when open */
.mobile-sidebar-open {
left: 0;
}
/* Mobile overlay */
.mobile-overlay {
position: fixed;
inset: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 999;
display: none;
}
.mobile-overlay-visible {
display: block;
}
/* Hamburger button */
.mobile-menu-button {
display: flex !important;
}
/* Hide desktop resize handle on mobile */
.sidebar-resize-handle,
.resize-handle {
display: none;
}
/* Make editor take full width on mobile */
.mobile-editor {
width: 100% !important;
}
/* Stack view modes vertically on mobile */
.mobile-view-toggle {
flex-direction: column;
gap: 0.25rem;
}
/* Smaller buttons on mobile */
.mobile-toolbar button {
padding: 0.5rem;
font-size: 0.875rem;
}
/* Smaller text input on mobile */
.mobile-toolbar input {
min-width: 100px !important;
max-width: 200px !important;
font-size: 1rem !important;
}
/* Hide split view on mobile (not enough space) */
.mobile-hide-split {
display: none !important;
}
/* Make toolbar wrap on very small screens */
.mobile-toolbar {
flex-wrap: wrap;
gap: 0.5rem;
}
/* Larger touch targets */
.note-item,
.folder-item {
padding: 0.75rem !important;
min-height: 44px;
}
}
/* Hide mobile menu button on desktop */
@media (min-width: 769px) {
.mobile-menu-button {
display: none !important;
}
}
2025-11-05 16:48:41 +00:00
< / style >
< / head >
< body x-data = "noteApp()" x-init = "init()" style = "background-color: var(--bg-primary);" >
2025-11-13 14:09:26 +00:00
<!-- Mobile Overlay -->
< div
@click="mobileSidebarOpen = false"
:class="{'mobile-overlay': true, 'mobile-overlay-visible': mobileSidebarOpen}"
x-show="mobileSidebarOpen"
>< / div >
2025-11-05 16:48:41 +00:00
<!-- Main Container -->
< div class = "flex h-screen overflow-hidden" >
<!-- Sidebar -->
2025-11-13 14:09:26 +00:00
< div
class="flex flex-col mobile-sidebar"
:class="{'mobile-sidebar-open': mobileSidebarOpen}"
:style="'width: ' + sidebarWidth + 'px; background-color: var(--bg-secondary); border-right: 1px solid var(--border-primary); min-width: 200px; max-width: 600px;'"
>
2025-11-05 16:48:41 +00:00
<!-- Header -->
< div class = "p-4 border-b" style = "border-color: var(--border-primary);" >
< div class = "flex items-center gap-2 mb-3" >
< img src = "/static/logo.svg" alt = "NoteDiscovery Logo" class = "w-8 h-8" >
< h1 class = "text-xl font-bold" style = "color: var(--text-primary);" x-text = "appName" > < / h1 >
< / div >
<!-- Theme Selector -->
< div class = "mb-3" >
< label class = "text-xs mb-1 block" style = "color: var(--text-tertiary);" > Theme< / label >
< select
x-model="currentTheme"
@change="setTheme(currentTheme)"
class="w-full px-3 py-2 text-sm rounded border focus:outline-none focus:ring-2 focus:ring-opacity-50"
style="background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border-primary); cursor: pointer;"
>
< template x-for = "theme in availableThemes" :key = "theme.id" >
< option :value = "theme.id" x-text = "theme.name" > < / option >
< / template >
< / select >
< / div >
2025-11-14 13:34:19 +00:00
< p class = "text-xs mb-3" style = "color: var(--text-secondary);" x-text = "appTagline" > < / p >
<!-- Logout Button (only show if auth is enabled) -->
< div x-data = "{ authEnabled: false }" x-init = "fetch('/api/config').then(r => r.json()).then(d => authEnabled = d.security?.enabled || false)" >
< a x-show = "authEnabled"
href="/logout"
class="flex items-center justify-center gap-2 px-3 py-2 text-sm rounded transition-colors"
style="background-color: var(--bg-secondary); color: var(--text-secondary); border: 1px solid var(--border-primary);"
onmouseover="this.style.backgroundColor='var(--bg-hover)'; this.style.color='var(--text-primary)'"
onmouseout="this.style.backgroundColor='var(--bg-secondary)'; this.style.color='var(--text-secondary)'">
< span > 🔒< / span >
< span > Logout< / span >
< / a >
< / div >
2025-11-05 16:48:41 +00:00
< / div >
<!-- Search -->
< div class = "p-3 border-b" style = "border-color: var(--border-primary);" >
< div class = "relative" >
< input
type="text"
x-model="searchQuery"
@input="searchNotes()"
placeholder="Search notes..."
class="w-full px-3 py-2 pl-8 pr-8 text-sm rounded-lg focus:outline-none focus:ring-2"
style="background-color: var(--bg-primary); color: var(--text-primary); border: 1px solid var(--border-primary);"
>
<!-- Search Icon -->
< svg class = "absolute left-2 top-2.5 w-4 h-4" style = "color: var(--text-tertiary);" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" >
< path stroke-linecap = "round" stroke-linejoin = "round" stroke-width = "2" d = "M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" > < / path >
< / svg >
<!-- Clear Button -->
< button
x-show="searchQuery.length > 0"
@click="searchQuery = ''; searchResults = []; currentSearchHighlight = ''; clearSearchHighlights();"
class="absolute right-2 top-2.5 w-4 h-4 flex items-center justify-center rounded-full hover:bg-gray-300 dark:hover:bg-gray-600 transition-colors"
style="color: var(--text-tertiary);"
title="Clear search"
>
< svg class = "w-3 h-3" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" >
< path stroke-linecap = "round" stroke-linejoin = "round" stroke-width = "2" d = "M6 18L18 6M6 6l12 12" > < / path >
< / svg >
< / button >
< / div >
<!-- Match Navigation (shown when there are matches in current note) -->
< div
x-show="totalMatches > 0"
class="flex items-center justify-between mt-2 px-2 py-1 rounded text-xs"
style="background-color: var(--bg-secondary); color: var(--text-secondary);"
>
< span x-text = "`Match ${currentMatchIndex + 1} of ${totalMatches}`" > < / span >
< div class = "flex gap-1" >
< button
@click="previousMatch()"
class="px-2 py-1 rounded hover:bg-opacity-80 transition-colors"
style="background-color: var(--bg-primary); color: var(--text-primary);"
title="Previous match (Shift+F3)"
>
< svg class = "w-3 h-3" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" >
< path stroke-linecap = "round" stroke-linejoin = "round" stroke-width = "2" d = "M15 19l-7-7 7-7" > < / path >
< / svg >
< / button >
< button
@click="nextMatch()"
class="px-2 py-1 rounded hover:bg-opacity-80 transition-colors"
style="background-color: var(--bg-primary); color: var(--text-primary);"
title="Next match (F3)"
>
< svg class = "w-3 h-3" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" >
< path stroke-linecap = "round" stroke-linejoin = "round" stroke-width = "2" d = "M9 5l7 7-7 7" > < / path >
< / svg >
< / button >
< / div >
< / div >
< / div >
2025-11-17 13:20:57 +00:00
<!-- New Item Dropdown -->
< div class = "p-3 border-b relative" style = "border-color: var(--border-primary);" >
2025-11-05 16:48:41 +00:00
< button
2025-11-17 13:20:57 +00:00
@click="toggleNewDropdown()"
class="w-full px-4 py-2 text-sm font-medium text-white rounded-lg focus:outline-none focus:ring-2 flex items-center justify-center gap-2"
2025-11-05 16:48:41 +00:00
style="background-color: var(--accent-primary);"
onmouseover="this.style.backgroundColor='var(--accent-hover)'"
onmouseout="this.style.backgroundColor='var(--accent-primary)'"
>
2025-11-17 13:20:57 +00:00
< span > + New< / span >
< svg class = "w-4 h-4" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" >
< path stroke-linecap = "round" stroke-linejoin = "round" stroke-width = "2" d = "M19 9l-7 7-7-7" > < / path >
< / svg >
2025-11-05 16:48:41 +00:00
< / button >
2025-11-17 13:20:57 +00:00
<!-- Dropdown Menu -->
< div
x-show="showNewDropdown"
@click.outside="closeDropdown()"
x-transition:enter="transition ease-out duration-100"
x-transition:enter-start="transform opacity-0 scale-95"
x-transition:enter-end="transform opacity-100 scale-100"
x-transition:leave="transition ease-in duration-75"
x-transition:leave-start="transform opacity-100 scale-100"
x-transition:leave-end="transform opacity-0 scale-95"
class="absolute left-3 right-3 mt-2 rounded-lg shadow-lg z-50"
style="background-color: var(--bg-secondary); border: 1px solid var(--border-primary);"
2025-11-05 16:48:41 +00:00
>
2025-11-17 13:20:57 +00:00
< div class = "py-1" >
< button
@click="createNote()"
class="w-full px-4 py-2.5 text-sm text-left flex items-center gap-3 transition-colors"
style="color: var(--text-primary);"
onmouseover="this.style.backgroundColor='var(--bg-hover)'"
onmouseout="this.style.backgroundColor='transparent'"
>
< span class = "text-lg" > 📝< / span >
< span > New Note< / span >
< / button >
< button
@click="createFolder()"
class="w-full px-4 py-2.5 text-sm text-left flex items-center gap-3 transition-colors"
style="color: var(--text-primary);"
onmouseover="this.style.backgroundColor='var(--bg-hover)'"
onmouseout="this.style.backgroundColor='transparent'"
>
< span class = "text-lg" > 📁< / span >
< span > New Folder< / span >
< / button >
< / div >
< / div >
2025-11-05 16:48:41 +00:00
< / div >
<!-- Notes List -->
< div class = "flex-1 overflow-y-auto custom-scrollbar" >
< template x-if = "searchResults.length > 0" >
< div class = "p-2" >
< div class = "text-xs px-2 py-1" style = "color: var(--text-tertiary);" > Search Results< / div >
< template x-for = "note in searchResults" :key = "note.path" >
< div
@click="loadNote(note.path, true, searchQuery)"
class="px-3 py-2 mb-1 text-sm rounded cursor-pointer"
:style="currentNote === note.path ? 'background-color: var(--accent-light); color: var(--accent-primary);' : 'color: var(--text-primary);'"
onmouseover="if(this.style.backgroundColor !== 'var(--accent-light)') this.style.backgroundColor='var(--bg-hover)'"
onmouseout="if(this.style.backgroundColor !== 'var(--accent-light)') this.style.backgroundColor='transparent'"
>
< div class = "font-medium truncate" x-text = "note.name" > < / div >
< div class = "text-xs truncate" style = "color: var(--text-tertiary);" x-text = "note.folder || 'Root'" > < / div >
< / div >
< / template >
< / div >
< / template >
< template x-if = "searchResults.length === 0" >
< div class = "p-2" >
< div class = "text-xs px-2 py-1 mb-2" style = "color: var(--text-tertiary);" >
< div class = "flex items-center justify-between mb-1" >
< span > Folders & Notes< / span >
< div class = "flex gap-1" >
< button
@click="expandAllFolders()"
class="text-xs px-2 py-0.5 rounded hover:bg-opacity-20"
style="color: var(--text-secondary);"
title="Expand all folders"
>⊞< / button >
< button
@click="collapseAllFolders()"
class="text-xs px-2 py-0.5 rounded hover:bg-opacity-20"
style="color: var(--text-secondary);"
title="Collapse all folders"
>⊟< / button >
< / div >
< / div >
< div class = "text-xs" style = "color: var(--text-tertiary); opacity: 0.8;" >
💡 Drag=Move | < kbd style = "padding: 1px 4px; border-radius: 3px; background: var(--bg-tertiary); font-size: 10px;" > Ctrl< / kbd > +Drag=Link
< / div >
< / div >
<!-- Root drop zone - only visible when dragging -->
< div
x-show="draggedNote || draggedFolder"
2025-11-18 09:03:47 +00:00
x-transition
@dragover.prevent="dragOverFolder = '';"
@dragenter.prevent="dragOverFolder = '';"
@dragleave="dragOverFolder = null;"
2025-11-05 16:48:41 +00:00
@drop="onFolderDrop('')"
2025-11-18 09:14:47 +00:00
class="mb-2 px-2 py-1.5 rounded border-2 border-dashed transition-all"
2025-11-18 09:03:47 +00:00
:class="dragOverFolder === '' ? 'bg-accent-light' : ''"
2025-11-18 09:14:47 +00:00
:style="dragOverFolder === '' ? 'border-color: var(--accent-primary);' : 'border-color: var(--border-primary);'"
2025-11-05 16:48:41 +00:00
>
2025-11-18 09:14:47 +00:00
< div class = "text-xs text-center font-medium flex items-center justify-center gap-1"
2025-11-18 09:03:47 +00:00
:style="dragOverFolder === '' ? 'color: var(--accent-primary);' : 'color: var(--text-secondary);'">
2025-11-18 09:14:47 +00:00
< span > 📂 Root< / span >
2025-11-05 16:48:41 +00:00
< / div >
< / div >
2025-11-08 11:23:07 +00:00
<!-- Folders (rendered recursively for unlimited nesting) -->
2025-11-05 16:48:41 +00:00
< template x-for = "[folderKey, folder] in Object.entries(folderTree).filter(([k]) => k !== '__root__').sort((a, b) => a[1].name.toLowerCase().localeCompare(b[1].name.toLowerCase()))" :key = "folderKey" >
2025-11-08 11:28:31 +00:00
< div x-html = "renderFolderRecursive(folder, 0, true)" > < / div >
2025-11-05 16:48:41 +00:00
< / template >
<!-- Root notes (no folder) - shown after folders -->
< template x-for = "note in (folderTree.__root__ && folderTree.__root__.notes ? folderTree.__root__.notes : [])" :key = "note.path" >
< div
draggable="true"
@dragstart="onNoteDragStart(note.path, $event)"
@dragend="onNoteDragEnd()"
@click="loadNote(note.path)"
class="note-item px-3 py-2 mb-1 text-sm rounded relative"
:style="(currentNote === note.path ? 'background-color: var(--accent-light); color: var(--accent-primary);' : 'color: var(--text-primary);') + (draggedNote || draggedFolder ? ' cursor: grabbing;' : ' cursor: pointer;')"
@mouseover="if(currentNote !== note.path) $el.style.backgroundColor='var(--bg-hover)'"
@mouseout="if(currentNote !== note.path) $el.style.backgroundColor='transparent'"
>
< span class = "truncate" x-text = "note.name" > < / span >
< button
@click.stop="deleteNote(note.path, note.name)"
class="note-delete-btn absolute right-2 top-1/2 transform -translate-y-1/2 px-1 py-0.5 text-xs rounded hover:brightness-110 transition-opacity"
style="opacity: 0; color: var(--error);"
title="Delete note"
>
< svg class = "w-4 h-4" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" >
< path stroke-linecap = "round" stroke-linejoin = "round" stroke-width = "2" d = "M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" > < / path >
< / svg >
< / button >
< / div >
< / template >
<!-- Empty state -->
< template x-if = "notes.length === 0 && allFolders.length === 0" >
< div class = "px-3 py-4 text-sm text-center" style = "color: var(--text-tertiary);" >
No notes or folders yet.< br > Create your first note or folder!
< / div >
< / template >
< / div >
< / template >
< / div >
<!-- Footer -->
< div class = "p-3 border-t" style = "border-color: var(--border-primary);" >
< div class = "text-xs text-center" style = "color: var(--text-tertiary);" >
< span x-text = "notes.length" > < / span > notes
< / div >
< / div >
< / div >
<!-- Resize Handle -->
< div
@mousedown="startResize($event)"
class="resize-handle"
style="width: 4px; cursor: col-resize; background-color: transparent; transition: background-color 0.2s;"
@mouseover="$el.style.backgroundColor='var(--accent-primary)'"
@mouseout="if(!isResizing) $el.style.backgroundColor='transparent'"
>< / div >
<!-- Drag Mode Indicator (floating) -->
< div
x-show="draggedNote || draggedNoteForLink"
class="fixed top-4 left-1/2 transform -translate-x-1/2 z-50 px-4 py-2 rounded-lg shadow-lg"
:style="'background-color: var(--accent-primary); color: white; font-weight: 600;'"
style="pointer-events: none;"
>
< span x-show = "draggedNoteForLink" > 🔗 Link Mode - Drop in editor to create link< / span >
< span x-show = "draggedNote && !draggedNoteForLink" > 📦 Move Mode - Drop on folder to move< / span >
< / div >
<!-- Main Content Area -->
< div class = "flex-1 flex flex-col overflow-hidden" >
< template x-if = "!currentNote" >
<!-- Welcome Screen -->
< div class = "flex-1 flex items-center justify-center" style = "background-color: var(--bg-primary);" >
< div class = "text-center" >
< svg class = "mx-auto h-16 w-16 mb-4" style = "color: var(--text-tertiary);" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" >
< path stroke-linecap = "round" stroke-linejoin = "round" stroke-width = "2" d = "M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" > < / path >
< / svg >
< h2 class = "text-2xl font-bold mb-2" style = "color: var(--text-primary);" x-text = "appName" > < / h2 >
< p class = "mb-6" style = "color: var(--text-secondary);" x-text = "appTagline" > < / p >
< button
2025-11-17 13:24:40 +00:00
@click="createNote()"
2025-11-05 16:48:41 +00:00
class="px-6 py-3 text-sm font-medium text-white rounded-lg"
style="background-color: var(--accent-primary);"
onmouseover="this.style.backgroundColor='var(--accent-hover)'"
onmouseout="this.style.backgroundColor='var(--accent-primary)'"
>
Create Your First Note
< / button >
< / div >
< / div >
< / template >
< template x-if = "currentNote" >
<!-- Editor Area -->
< div class = "flex-1 flex flex-col overflow-hidden" style = "background-color: var(--bg-primary);" >
<!-- Toolbar -->
2025-11-13 14:09:26 +00:00
< div class = "px-4 py-3 flex items-center justify-between mobile-toolbar" style = "background-color: var(--bg-secondary); border-bottom: 1px solid var(--border-primary);" >
2025-11-05 16:48:41 +00:00
< div class = "flex items-center space-x-2" >
2025-11-13 14:09:26 +00:00
<!-- Mobile Menu Button -->
< button
@click="mobileSidebarOpen = !mobileSidebarOpen"
class="mobile-menu-button p-2 rounded-lg"
style="color: var(--text-primary); display: none;"
onmouseover="this.style.backgroundColor='var(--bg-hover)'"
onmouseout="this.style.backgroundColor='transparent'"
title="Toggle sidebar"
>
< svg class = "w-6 h-6" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" >
< path stroke-linecap = "round" stroke-linejoin = "round" stroke-width = "2" d = "M4 6h16M4 12h16M4 18h16" > < / path >
< / svg >
< / button >
2025-11-05 16:48:41 +00:00
< input
type="text"
x-model="currentNoteName"
@blur="renameNote()"
class="text-xl font-semibold border-none focus:outline-none focus:ring-2 rounded px-3 py-1.5"
2025-11-13 14:09:26 +00:00
style="background-color: var(--bg-primary); color: var(--text-primary); border: 1px solid var(--border-primary); min-width: 150px; max-width: 300px;"
2025-11-05 16:48:41 +00:00
>
<!-- Undo Button -->
< button
@click="undo()"
:disabled="undoHistory.length < = 1"
class="p-2 rounded-lg"
:style="undoHistory.length < = 1 ? 'color: var(--text-tertiary); opacity: 0.4; cursor: not-allowed;' : 'color: var(--text-secondary);'"
onmouseover="if(!this.disabled) this.style.backgroundColor='var(--bg-hover)'"
onmouseout="this.style.backgroundColor='transparent'"
title="Undo (Ctrl+Z)"
>
< svg class = "w-5 h-5" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" >
< path stroke-linecap = "round" stroke-linejoin = "round" stroke-width = "2" d = "M3 10h10a8 8 0 018 8v2M3 10l6 6m-6-6l6-6" > < / path >
< / svg >
< / button >
<!-- Redo Button -->
< button
@click="redo()"
:disabled="redoHistory.length === 0"
class="p-2 rounded-lg"
:style="redoHistory.length === 0 ? 'color: var(--text-tertiary); opacity: 0.4; cursor: not-allowed;' : 'color: var(--text-secondary);'"
onmouseover="if(!this.disabled) this.style.backgroundColor='var(--bg-hover)'"
onmouseout="this.style.backgroundColor='transparent'"
title="Redo (Ctrl+Y)"
>
< svg class = "w-5 h-5" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" >
< path stroke-linecap = "round" stroke-linejoin = "round" stroke-width = "2" d = "M21 10h-10a8 8 0 00-8 8v2m18-10l-6 6m6-6l-6-6" > < / path >
< / svg >
< / button >
<!-- Delete Button -->
< button
@click="deleteCurrentNote()"
class="p-2 rounded-lg"
style="color: var(--error);"
onmouseover="this.style.backgroundColor='var(--bg-hover)'"
onmouseout="this.style.backgroundColor='transparent'"
title="Delete note"
>
< svg class = "w-5 h-5" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" >
< path stroke-linecap = "round" stroke-linejoin = "round" stroke-width = "2" d = "M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" > < / path >
< / svg >
< / button >
< span x-show = "isSaving" class = "text-xs" style = "color: var(--text-secondary);" > Saving...< / span >
< span x-show = "!isSaving && lastSaved" class = "text-xs" style = "color: var(--success);" > ✓ Saved< / span >
< / div >
< div class = "flex items-center space-x-2" >
<!-- View Toggle -->
< div class = "flex rounded-lg p-1" style = "background-color: var(--bg-tertiary);" >
< button
@click="viewMode = 'edit'"
2025-11-13 14:09:26 +00:00
class="px-2 md:px-3 py-1 text-xs md:text-sm rounded transition"
2025-11-05 16:48:41 +00:00
:style="viewMode === 'edit' ? 'background-color: var(--accent-primary); color: white;' : 'color: var(--text-secondary);'"
>
Edit
< / button >
< button
@click="viewMode = 'split'"
2025-11-13 14:09:26 +00:00
class="mobile-hide-split px-2 md:px-3 py-1 text-xs md:text-sm rounded transition"
2025-11-05 16:48:41 +00:00
:style="viewMode === 'split' ? 'background-color: var(--accent-primary); color: white;' : 'color: var(--text-secondary);'"
>
Split
< / button >
< button
@click="viewMode = 'preview'"
2025-11-13 14:09:26 +00:00
class="px-2 md:px-3 py-1 text-xs md:text-sm rounded transition"
2025-11-05 16:48:41 +00:00
:style="viewMode === 'preview' ? 'background-color: var(--accent-primary); color: white;' : 'color: var(--text-secondary);'"
>
Preview
< / button >
< / div >
2025-11-12 10:16:12 +00:00
<!-- Export HTML Button -->
< button
x-show="currentNote"
@click="exportToHTML()"
2025-11-13 14:09:26 +00:00
class="px-2 md:px-3 py-1 md:py-1.5 text-xs md:text-sm rounded transition hover:opacity-80"
2025-11-12 10:16:12 +00:00
style="background-color: var(--accent-secondary); color: var(--text-primary);"
title="Export as HTML"
>
2025-11-13 14:09:26 +00:00
< span class = "hidden md:inline" > 📄 Export HTML< / span >
< span class = "md:hidden" > 📄< / span >
2025-11-12 10:16:12 +00:00
< / button >
2025-11-05 16:48:41 +00:00
< / div >
< / div >
<!-- Editor/Preview -->
2025-11-09 09:17:09 +00:00
< div class = "flex-1 flex relative" style = "min-height: 0;" >
2025-11-05 16:48:41 +00:00
<!-- Editor -->
< div
x-show="viewMode === 'edit' || viewMode === 'split'"
class="flex flex-col"
style="min-height: 0;"
2025-11-09 09:17:09 +00:00
:style="viewMode === 'split' ? `width: ${editorWidth}%;` : 'width: 100%;'"
2025-11-05 16:48:41 +00:00
>
< textarea
x-model="noteContent"
@input="autoSave()"
@drop="onEditorDrop($event)"
@dragover.prevent="onEditorDragOver($event)"
@dragenter="onEditorDragEnter($event)"
@dragleave="onEditorDragLeave($event)"
:class="'flex-1 w-full p-6 resize-none focus:outline-none editor-textarea' + (draggedNoteForLink ? ' link-drop-target' : '')"
:style="'background-color: var(--bg-primary); color: var(--text-primary);' + (draggedNoteForLink ? ' outline: 2px dashed var(--accent-primary); outline-offset: -2px; box-shadow: 0 0 20px var(--accent-light);' : '')"
:placeholder="draggedNoteForLink ? '💡 Drop here to create link at cursor position...' : 'Start writing in markdown...'"
>< / textarea >
< / div >
2025-11-09 09:17:09 +00:00
<!-- Split view resize handle -->
< div
x-show="viewMode === 'split'"
@mousedown="startSplitResize($event)"
class="split-resize-handle"
2025-11-11 14:50:33 +00:00
style="width: 6px; cursor: col-resize; background: linear-gradient(90deg, transparent 0%, var(--border-secondary) 50%, transparent 100%); transition: all 0.2s; position: relative; z-index: 10; opacity: 0.5;"
@mouseover="$el.style.opacity='1'; $el.style.background='var(--accent-primary)'"
@mouseout="if(!isResizingSplit) { $el.style.opacity='0.5'; $el.style.background='linear-gradient(90deg, transparent 0%, var(--border-secondary) 50%, transparent 100%)' }"
2025-11-09 09:17:09 +00:00
>< / div >
2025-11-05 16:48:41 +00:00
<!-- Preview -->
< div
x-show="viewMode === 'preview' || viewMode === 'split'"
class="overflow-y-auto overflow-x-hidden custom-scrollbar"
style="background-color: var(--bg-primary); min-height: 0;"
2025-11-09 09:17:09 +00:00
:style="viewMode === 'split' ? `width: ${100 - editorWidth}%;` : 'width: 100%;'"
2025-11-05 16:48:41 +00:00
>
< div
class="p-6 markdown-preview"
x-html="renderedMarkdown"
@click="handleInternalLink($event)"
>< / div >
< / div >
< / div >
<!-- Stats Status Bar (only shows if plugin enabled) -->
< div x-show = "statsPluginEnabled && currentNote && noteStats"
class="border-t"
style="background-color: var(--bg-secondary); border-color: var(--border-primary);">
<!-- Collapsed State (default) -->
< div x-show = "!statsExpanded"
@click="statsExpanded = true"
class="px-4 py-2 flex items-center justify-between cursor-pointer hover:bg-opacity-80 transition-colors"
onmouseover="this.style.backgroundColor='var(--bg-hover)'"
onmouseout="this.style.backgroundColor='transparent'">
< div class = "flex items-center space-x-4 text-sm" style = "color: var(--text-secondary);" >
< span class = "font-medium" > 📊< / span >
< span x-text = "noteStats ? noteStats.words.toLocaleString() + ' words' : ''" > < / span >
< span > ·< / span >
< span x-text = "noteStats ? '~' + noteStats.reading_time_minutes + 'm read' : ''" > < / span >
< template x-if = "noteStats && noteStats.links > 0" >
< span >
· < span x-text = "noteStats.links + ' links'" > < / span >
< / span >
< / template >
< template x-if = "noteStats && noteStats.tasks.total > 0" >
< span >
· < span x-text = "noteStats.tasks.completed + '/' + noteStats.tasks.total + ' tasks'" > < / span >
< / span >
< / template >
< / div >
< span class = "text-xs" style = "color: var(--text-tertiary);" > ▼ Click for details< / span >
< / div >
<!-- Expanded State -->
< div x-show = "statsExpanded"
class="px-4 py-3"
style="background-color: var(--bg-tertiary);">
< div class = "flex items-center justify-between mb-3" >
< h3 class = "font-semibold text-sm" style = "color: var(--text-primary);" > 📊 Note Statistics< / h3 >
< button
@click="statsExpanded = false"
class="text-xs px-2 py-1 rounded hover:bg-opacity-80"
style="color: var(--text-secondary); background-color: var(--bg-hover);">
▲ Collapse
< / button >
< / div >
< div class = "grid grid-cols-2 md:grid-cols-4 gap-3 text-sm" >
<!-- Basic Stats -->
< div >
< div class = "text-xs" style = "color: var(--text-tertiary);" > Words< / div >
< div class = "font-semibold" style = "color: var(--text-primary);" x-text = "noteStats ? noteStats.words.toLocaleString() : '0'" > < / div >
< / div >
< div >
< div class = "text-xs" style = "color: var(--text-tertiary);" > Characters< / div >
< div class = "font-semibold" style = "color: var(--text-primary);" x-text = "noteStats ? noteStats.characters.toLocaleString() : '0'" > < / div >
< / div >
< div >
< div class = "text-xs" style = "color: var(--text-tertiary);" > Reading Time< / div >
< div class = "font-semibold" style = "color: var(--text-primary);" x-text = "noteStats ? '~' + noteStats.reading_time_minutes + ' min' : '0 min'" > < / div >
< / div >
< div >
< div class = "text-xs" style = "color: var(--text-tertiary);" > Lines< / div >
< div class = "font-semibold" style = "color: var(--text-primary);" x-text = "noteStats ? noteStats.lines.toLocaleString() : '0'" > < / div >
< / div >
<!-- Links -->
< template x-if = "noteStats && noteStats.links > 0" >
< div >
< div class = "text-xs" style = "color: var(--text-tertiary);" > Links< / div >
< div class = "font-semibold" style = "color: var(--text-primary);" >
< span x-text = "noteStats.links" > < / span >
< span class = "text-xs font-normal" style = "color: var(--text-tertiary);" >
(< span x-text = "noteStats.internal_links" > < / span > internal)
< / span >
< / div >
< / div >
< / template >
<!-- Headings -->
< template x-if = "noteStats && noteStats.headings.total > 0" >
< div >
< div class = "text-xs" style = "color: var(--text-tertiary);" > Headings< / div >
< div class = "font-semibold" style = "color: var(--text-primary);" >
< span x-text = "noteStats.headings.total" > < / span >
< span class = "text-xs font-normal" style = "color: var(--text-tertiary);" >
(H1: < span x-text = "noteStats.headings.h1" > < / span > , H2: < span x-text = "noteStats.headings.h2" > < / span > )
< / span >
< / div >
< / div >
< / template >
<!-- Code Blocks -->
< template x-if = "noteStats && noteStats.code_blocks > 0" >
< div >
< div class = "text-xs" style = "color: var(--text-tertiary);" > Code Blocks< / div >
< div class = "font-semibold" style = "color: var(--text-primary);" x-text = "noteStats.code_blocks" > < / div >
< / div >
< / template >
<!-- Tasks -->
< template x-if = "noteStats && noteStats.tasks.total > 0" >
< div >
< div class = "text-xs" style = "color: var(--text-tertiary);" > Tasks< / div >
< div class = "font-semibold" style = "color: var(--text-primary);" >
< span x-text = "noteStats.tasks.completed + '/' + noteStats.tasks.total" > < / span >
< span class = "text-xs font-normal" style = "color: var(--text-tertiary);" >
(< span x-text = "noteStats.tasks.completion_rate" > < / span > %)
< / span >
< / div >
< / div >
< / template >
<!-- Images -->
< template x-if = "noteStats && noteStats.images > 0" >
< div >
< div class = "text-xs" style = "color: var(--text-tertiary);" > Images< / div >
< div class = "font-semibold" style = "color: var(--text-primary);" x-text = "noteStats.images" > < / div >
< / div >
< / template >
< / div >
< / div >
< / div >
< / div >
< / template >
< / div >
< / div >
< script src = "/static/app.js" > < / script >
< / body >
< / html >