756 lines
24 KiB
HTML
756 lines
24 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="ro">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Echo · Notes</title>
|
|
<link rel="stylesheet" href="common.css">
|
|
<script src="https://unpkg.com/lucide@latest/dist/umd/lucide.min.js"></script>
|
|
<script src="swipe-nav.js"></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
|
<style>
|
|
.main {
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
padding: var(--space-5);
|
|
}
|
|
|
|
.page-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: var(--space-6);
|
|
flex-wrap: wrap;
|
|
gap: var(--space-4);
|
|
}
|
|
|
|
.page-title {
|
|
font-size: var(--text-xl);
|
|
font-weight: 600;
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.search-bar {
|
|
width: 300px;
|
|
}
|
|
|
|
/* Date sections (accordion) */
|
|
.date-section {
|
|
margin-bottom: var(--space-3);
|
|
}
|
|
|
|
.date-header {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: var(--space-3) var(--space-4);
|
|
background: var(--bg-surface);
|
|
border: 1px solid rgba(255, 255, 255, 0.25);
|
|
border-radius: var(--radius-md);
|
|
cursor: pointer;
|
|
user-select: none;
|
|
transition: all var(--transition-fast);
|
|
}
|
|
|
|
[data-theme="light"] .date-header {
|
|
border-color: rgba(0, 0, 0, 0.15);
|
|
}
|
|
|
|
.date-header:hover {
|
|
filter: brightness(1.1);
|
|
}
|
|
|
|
/* Section colors */
|
|
[data-section="today"] .date-header {
|
|
background: linear-gradient(135deg, rgba(59, 130, 246, 0.25), rgba(37, 99, 235, 0.15));
|
|
border-left: 3px solid #3b82f6;
|
|
}
|
|
|
|
[data-section="yesterday"] .date-header {
|
|
background: linear-gradient(135deg, rgba(139, 92, 246, 0.25), rgba(124, 58, 237, 0.15));
|
|
border-left: 3px solid #8b5cf6;
|
|
}
|
|
|
|
[data-section="thisWeek"] .date-header {
|
|
background: linear-gradient(135deg, rgba(20, 184, 166, 0.25), rgba(13, 148, 136, 0.15));
|
|
border-left: 3px solid #14b8a6;
|
|
}
|
|
|
|
[data-section="older"] .date-header {
|
|
background: linear-gradient(135deg, rgba(249, 115, 22, 0.25), rgba(234, 88, 12, 0.15));
|
|
border-left: 3px solid #f97316;
|
|
}
|
|
|
|
.date-header-left {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--space-3);
|
|
}
|
|
|
|
.date-header-left svg {
|
|
width: 18px;
|
|
height: 18px;
|
|
color: var(--text-muted);
|
|
transition: transform var(--transition-fast);
|
|
}
|
|
|
|
.date-section.collapsed .date-header-left svg {
|
|
transform: rotate(-90deg);
|
|
}
|
|
|
|
.date-label {
|
|
font-size: var(--text-sm);
|
|
font-weight: 600;
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.date-sublabel {
|
|
font-size: var(--text-xs);
|
|
color: var(--text-muted);
|
|
margin-left: var(--space-2);
|
|
}
|
|
|
|
.date-section.collapsed .date-content {
|
|
display: none;
|
|
}
|
|
|
|
.date-content {
|
|
padding: var(--space-4) 0;
|
|
}
|
|
|
|
/* Notes grid inside sections */
|
|
.notes-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
|
gap: var(--space-4);
|
|
}
|
|
|
|
.note-card {
|
|
background: var(--bg-surface);
|
|
border: 1px solid rgba(255, 255, 255, 0.25);
|
|
border-radius: var(--radius-lg);
|
|
padding: var(--space-4);
|
|
cursor: pointer;
|
|
transition: all var(--transition-base);
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--space-2);
|
|
}
|
|
|
|
[data-theme="light"] .note-card {
|
|
border-color: rgba(0, 0, 0, 0.15);
|
|
}
|
|
|
|
.note-card:hover {
|
|
border-color: var(--accent);
|
|
transform: translateY(-2px);
|
|
box-shadow: var(--shadow-md);
|
|
}
|
|
|
|
.note-title {
|
|
font-size: var(--text-sm);
|
|
font-weight: 600;
|
|
color: var(--text-primary);
|
|
line-height: 1.4;
|
|
}
|
|
|
|
.note-tags {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: var(--space-1);
|
|
}
|
|
|
|
/* Empty section */
|
|
.empty-section {
|
|
padding: var(--space-4);
|
|
text-align: center;
|
|
color: var(--text-muted);
|
|
font-size: var(--text-sm);
|
|
}
|
|
|
|
/* Note viewer overlay */
|
|
.note-viewer {
|
|
display: none;
|
|
position: fixed;
|
|
inset: 0;
|
|
background: rgba(0, 0, 0, 0.8);
|
|
z-index: 200;
|
|
overflow: auto;
|
|
}
|
|
|
|
.note-viewer.active {
|
|
display: block;
|
|
}
|
|
|
|
.note-viewer-content {
|
|
max-width: 800px;
|
|
margin: var(--space-5) auto;
|
|
background: var(--bg-base);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-lg);
|
|
min-height: calc(100vh - var(--space-10));
|
|
}
|
|
|
|
.note-viewer-header {
|
|
padding: var(--space-4) var(--space-5);
|
|
border-bottom: 1px solid var(--border);
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
position: sticky;
|
|
top: 0;
|
|
background: var(--bg-base);
|
|
z-index: 10;
|
|
}
|
|
|
|
.note-viewer-header h2 {
|
|
font-size: var(--text-lg);
|
|
color: var(--text-primary);
|
|
font-weight: 600;
|
|
}
|
|
|
|
.note-viewer-body {
|
|
padding: var(--space-6);
|
|
}
|
|
|
|
/* Markdown */
|
|
.markdown-body {
|
|
color: var(--text-secondary);
|
|
line-height: 1.7;
|
|
}
|
|
|
|
.markdown-body h1 {
|
|
font-size: 1.8rem;
|
|
color: var(--text-primary);
|
|
margin-bottom: var(--space-4);
|
|
padding-bottom: var(--space-3);
|
|
border-bottom: 1px solid var(--border);
|
|
}
|
|
|
|
.markdown-body h2 {
|
|
font-size: 1.3rem;
|
|
color: var(--accent);
|
|
margin-top: var(--space-6);
|
|
margin-bottom: var(--space-3);
|
|
}
|
|
|
|
.markdown-body h3 {
|
|
font-size: 1.1rem;
|
|
color: var(--text-primary);
|
|
margin-top: var(--space-5);
|
|
margin-bottom: var(--space-2);
|
|
}
|
|
|
|
.markdown-body p { margin-bottom: var(--space-4); }
|
|
|
|
.markdown-body ul, .markdown-body ol {
|
|
margin-bottom: var(--space-4);
|
|
padding-left: var(--space-6);
|
|
}
|
|
|
|
.markdown-body li { margin-bottom: var(--space-2); }
|
|
|
|
.markdown-body a {
|
|
color: var(--accent);
|
|
text-decoration: none;
|
|
}
|
|
|
|
.markdown-body a:hover { text-decoration: underline; }
|
|
|
|
.markdown-body code {
|
|
background: var(--bg-surface);
|
|
padding: 2px 6px;
|
|
border-radius: var(--radius-sm);
|
|
font-family: var(--font-mono);
|
|
font-size: 0.9em;
|
|
}
|
|
|
|
.markdown-body pre {
|
|
background: var(--bg-surface);
|
|
padding: var(--space-4);
|
|
border-radius: var(--radius-md);
|
|
overflow-x: auto;
|
|
margin-bottom: var(--space-4);
|
|
}
|
|
|
|
.markdown-body pre code {
|
|
background: none;
|
|
padding: 0;
|
|
}
|
|
|
|
.markdown-body blockquote {
|
|
border-left: 3px solid var(--accent);
|
|
padding-left: var(--space-4);
|
|
color: var(--text-muted);
|
|
margin-bottom: var(--space-4);
|
|
}
|
|
|
|
.markdown-body strong { color: var(--text-primary); }
|
|
|
|
/* Tag filter pills */
|
|
.tag-filter {
|
|
margin-bottom: var(--space-5);
|
|
}
|
|
|
|
.tag-filter-label {
|
|
font-size: var(--text-xs);
|
|
color: var(--text-muted);
|
|
margin-bottom: var(--space-2);
|
|
display: block;
|
|
}
|
|
|
|
.tag-pills {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: var(--space-2);
|
|
}
|
|
|
|
.tag-pill {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: var(--space-1);
|
|
padding: var(--space-2) var(--space-3);
|
|
background: var(--bg-surface);
|
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
border-radius: var(--radius-full);
|
|
font-size: var(--text-sm);
|
|
color: var(--text-secondary);
|
|
cursor: pointer;
|
|
transition: all var(--transition-fast);
|
|
user-select: none;
|
|
}
|
|
|
|
[data-theme="light"] .tag-pill {
|
|
border-color: rgba(0, 0, 0, 0.15);
|
|
}
|
|
|
|
.tag-pill:hover {
|
|
background: var(--bg-surface-hover);
|
|
border-color: var(--accent);
|
|
}
|
|
|
|
.tag-pill.active {
|
|
background: var(--accent);
|
|
border-color: var(--accent);
|
|
color: white;
|
|
}
|
|
|
|
.tag-pill-count {
|
|
font-size: var(--text-xs);
|
|
opacity: 0.7;
|
|
}
|
|
|
|
.clear-filters {
|
|
font-size: var(--text-xs);
|
|
color: var(--text-muted);
|
|
background: none;
|
|
border: none;
|
|
cursor: pointer;
|
|
padding: var(--space-2);
|
|
margin-left: var(--space-2);
|
|
}
|
|
|
|
.clear-filters:hover {
|
|
color: var(--accent);
|
|
}
|
|
|
|
/* No results */
|
|
.no-results {
|
|
text-align: center;
|
|
padding: var(--space-10);
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
.no-results svg {
|
|
width: 48px;
|
|
height: 48px;
|
|
margin-bottom: var(--space-4);
|
|
opacity: 0.5;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.page-header {
|
|
flex-direction: column;
|
|
align-items: stretch;
|
|
}
|
|
|
|
.search-bar { width: 100%; }
|
|
|
|
.notes-grid {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.note-viewer-content {
|
|
margin: 0;
|
|
border-radius: 0;
|
|
min-height: 100vh;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<header class="header">
|
|
<a href="index.html" class="logo">
|
|
<i data-lucide="circle-dot"></i>
|
|
Echo
|
|
</a>
|
|
<nav class="nav">
|
|
<a href="index.html" class="nav-item">
|
|
<i data-lucide="layout-list"></i>
|
|
<span>Tasks</span>
|
|
</a>
|
|
<a href="notes.html" class="nav-item active">
|
|
<i data-lucide="file-text"></i>
|
|
<span>Notes</span>
|
|
</a>
|
|
<a href="files.html" class="nav-item">
|
|
<i data-lucide="folder"></i>
|
|
<span>Files</span>
|
|
</a>
|
|
<a href="grup-sprijin.html" class="nav-item">
|
|
<i data-lucide="heart-handshake"></i>
|
|
<span>Grup</span>
|
|
</a>
|
|
<button class="theme-toggle" onclick="toggleTheme()" title="Schimbă tema">
|
|
<i data-lucide="sun" id="themeIcon"></i>
|
|
</button>
|
|
</nav>
|
|
</header>
|
|
|
|
<main class="main">
|
|
<div class="page-header">
|
|
<h1 class="page-title">Notes</h1>
|
|
<div class="search-bar">
|
|
<input type="text" class="input" id="searchInput" placeholder="Caută în notițe..." oninput="filterNotes()">
|
|
</div>
|
|
</div>
|
|
|
|
<div class="tag-filter">
|
|
<span class="tag-filter-label">Filtrează după tags:</span>
|
|
<div class="tag-pills" id="tagPills"></div>
|
|
</div>
|
|
|
|
<div id="notesContainer">
|
|
<div class="no-results">
|
|
<i data-lucide="loader"></i>
|
|
<p>Se încarcă...</p>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
|
|
<!-- Note viewer overlay -->
|
|
<div class="note-viewer" id="noteViewer" onclick="if(event.target === this) closeNote()">
|
|
<div class="note-viewer-content">
|
|
<div class="note-viewer-header">
|
|
<h2 id="viewerTitle">Titlu</h2>
|
|
<button class="btn btn-ghost" onclick="closeNote()">
|
|
<i data-lucide="x"></i>
|
|
</button>
|
|
</div>
|
|
<div class="note-viewer-body">
|
|
<div id="viewerContent" class="markdown-body"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// Theme
|
|
function initTheme() {
|
|
const saved = localStorage.getItem('theme') || 'dark';
|
|
document.documentElement.setAttribute('data-theme', saved);
|
|
updateThemeIcon(saved);
|
|
}
|
|
|
|
function toggleTheme() {
|
|
const current = document.documentElement.getAttribute('data-theme') || 'dark';
|
|
const next = current === 'dark' ? 'light' : 'dark';
|
|
document.documentElement.setAttribute('data-theme', next);
|
|
localStorage.setItem('theme', next);
|
|
updateThemeIcon(next);
|
|
}
|
|
|
|
function updateThemeIcon(theme) {
|
|
const icon = document.getElementById('themeIcon');
|
|
if (icon) {
|
|
icon.setAttribute('data-lucide', theme === 'dark' ? 'sun' : 'moon');
|
|
lucide.createIcons();
|
|
}
|
|
}
|
|
|
|
initTheme();
|
|
lucide.createIcons();
|
|
|
|
const notesCache = {};
|
|
let notesIndex = [];
|
|
const notesBasePath = "youtube-notes/";
|
|
const indexPath = notesBasePath + "index.json";
|
|
|
|
// Load notes index from JSON
|
|
async function loadNotesIndex() {
|
|
try {
|
|
const response = await fetch(indexPath + '?t=' + Date.now());
|
|
const data = await response.json();
|
|
notesIndex = Array.isArray(data) ? data : (data.notes || []);
|
|
console.log(`Loaded ${notesIndex.length} notes from index.json`);
|
|
} catch (e) {
|
|
console.error('Failed to load notes index:', e);
|
|
notesIndex = [];
|
|
}
|
|
}
|
|
let selectedTags = new Set();
|
|
|
|
// Extract all tags with counts
|
|
function getAllTags() {
|
|
const tagCounts = {};
|
|
notesIndex.forEach(note => {
|
|
note.tags.forEach(tag => {
|
|
tagCounts[tag] = (tagCounts[tag] || 0) + 1;
|
|
});
|
|
});
|
|
// Sort by count descending
|
|
return Object.entries(tagCounts)
|
|
.sort((a, b) => b[1] - a[1])
|
|
.map(([tag, count]) => ({ tag, count }));
|
|
}
|
|
|
|
// Render tag pills
|
|
function renderTagPills() {
|
|
const container = document.getElementById('tagPills');
|
|
const tags = getAllTags();
|
|
|
|
let html = tags.map(({ tag, count }) => `
|
|
<span class="tag-pill ${selectedTags.has(tag) ? 'active' : ''}"
|
|
onclick="toggleTag('${tag}')">
|
|
${tag} <span class="tag-pill-count">(${count})</span>
|
|
</span>
|
|
`).join('');
|
|
|
|
if (selectedTags.size > 0) {
|
|
html += `<button class="clear-filters" onclick="clearTagFilters()">✕ Clear</button>`;
|
|
}
|
|
|
|
container.innerHTML = html;
|
|
}
|
|
|
|
// Toggle tag selection
|
|
function toggleTag(tag) {
|
|
if (selectedTags.has(tag)) {
|
|
selectedTags.delete(tag);
|
|
} else {
|
|
selectedTags.add(tag);
|
|
}
|
|
renderTagPills();
|
|
filterNotes();
|
|
}
|
|
|
|
// Clear all tag filters
|
|
function clearTagFilters() {
|
|
selectedTags.clear();
|
|
renderTagPills();
|
|
filterNotes();
|
|
}
|
|
|
|
// Filter notes by search and tags
|
|
function filterNotes() {
|
|
const query = document.getElementById('searchInput').value.toLowerCase().trim();
|
|
|
|
let filtered = notesIndex;
|
|
|
|
// Filter by selected tags (AND logic)
|
|
if (selectedTags.size > 0) {
|
|
filtered = filtered.filter(note =>
|
|
[...selectedTags].every(tag => note.tags.includes(tag))
|
|
);
|
|
}
|
|
|
|
// Filter by search query
|
|
if (query) {
|
|
filtered = filtered.filter(note => {
|
|
const titleMatch = note.title.toLowerCase().includes(query);
|
|
const tagsMatch = note.tags.some(t => t.toLowerCase().includes(query));
|
|
const contentMatch = (notesCache[note.file] || '').toLowerCase().includes(query);
|
|
return titleMatch || tagsMatch || contentMatch;
|
|
});
|
|
}
|
|
|
|
renderNotesAccordion(filtered);
|
|
}
|
|
|
|
// Group notes by date category
|
|
function groupNotesByDate(notes) {
|
|
const today = new Date();
|
|
today.setHours(0, 0, 0, 0);
|
|
|
|
const yesterday = new Date(today);
|
|
yesterday.setDate(yesterday.getDate() - 1);
|
|
|
|
const weekAgo = new Date(today);
|
|
weekAgo.setDate(weekAgo.getDate() - 7);
|
|
|
|
const groups = {
|
|
today: { label: 'Azi', sublabel: formatDate(today), notes: [], expanded: true },
|
|
yesterday: { label: 'Ieri', sublabel: formatDate(yesterday), notes: [], expanded: false },
|
|
thisWeek: { label: 'Săptămâna aceasta', sublabel: '', notes: [], expanded: false },
|
|
older: { label: 'Mai vechi', sublabel: '', notes: [], expanded: false }
|
|
};
|
|
|
|
notes.forEach(note => {
|
|
const noteDate = new Date(note.date);
|
|
noteDate.setHours(0, 0, 0, 0);
|
|
|
|
if (noteDate.getTime() === today.getTime()) {
|
|
groups.today.notes.push(note);
|
|
} else if (noteDate.getTime() === yesterday.getTime()) {
|
|
groups.yesterday.notes.push(note);
|
|
} else if (noteDate >= weekAgo) {
|
|
groups.thisWeek.notes.push(note);
|
|
} else {
|
|
groups.older.notes.push(note);
|
|
}
|
|
});
|
|
|
|
return groups;
|
|
}
|
|
|
|
function formatDate(date) {
|
|
return date.toLocaleDateString('ro-RO', { day: 'numeric', month: 'short', year: 'numeric' });
|
|
}
|
|
|
|
function renderNotesAccordion(notes = notesIndex) {
|
|
const container = document.getElementById('notesContainer');
|
|
|
|
if (notes.length === 0) {
|
|
container.innerHTML = `
|
|
<div class="no-results">
|
|
<i data-lucide="search-x"></i>
|
|
<p>Nicio notiță găsită</p>
|
|
</div>
|
|
`;
|
|
lucide.createIcons();
|
|
return;
|
|
}
|
|
|
|
const groups = groupNotesByDate(notes);
|
|
let html = '';
|
|
|
|
Object.entries(groups).forEach(([key, group]) => {
|
|
if (group.notes.length === 0) return;
|
|
|
|
const collapsed = group.expanded ? '' : 'collapsed';
|
|
|
|
html += `
|
|
<div class="date-section ${collapsed}" data-section="${key}">
|
|
<div class="date-header" onclick="toggleSection('${key}')">
|
|
<div class="date-header-left">
|
|
<i data-lucide="chevron-down"></i>
|
|
<span class="date-label">${group.label}</span>
|
|
<span class="date-sublabel">${group.sublabel}</span>
|
|
</div>
|
|
<span class="badge">${group.notes.length}</span>
|
|
</div>
|
|
<div class="date-content">
|
|
<div class="notes-grid">
|
|
${group.notes.map(note => renderNoteCard(note)).join('')}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
});
|
|
|
|
container.innerHTML = html;
|
|
lucide.createIcons();
|
|
}
|
|
|
|
function renderNoteCard(note) {
|
|
return `
|
|
<div class="note-card" onclick="openNote('${note.file}')">
|
|
<div class="note-title">${note.title}</div>
|
|
<div class="note-tags">
|
|
${note.tags.map(t => `<span class="tag">${t}</span>`).join('')}
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
function toggleSection(sectionKey) {
|
|
const section = document.querySelector(`[data-section="${sectionKey}"]`);
|
|
section.classList.toggle('collapsed');
|
|
}
|
|
|
|
async function openNote(file) {
|
|
const note = notesIndex.find(n => n.file === file);
|
|
if (!note) return;
|
|
|
|
document.getElementById('viewerTitle').textContent = note.title;
|
|
document.getElementById('viewerContent').innerHTML = '<p style="color: var(--text-muted)">Se încarcă...</p>';
|
|
document.getElementById('noteViewer').classList.add('active');
|
|
document.body.style.overflow = 'hidden';
|
|
|
|
// Update URL
|
|
const noteId = file.replace(/^\d{4}-\d{2}-\d{2}_/, '').replace(/\.md$/, '');
|
|
history.replaceState(null, '', '#' + noteId);
|
|
|
|
try {
|
|
let content = notesCache[file];
|
|
if (!content) {
|
|
const response = await fetch(notesBasePath + file);
|
|
content = await response.text();
|
|
notesCache[file] = content;
|
|
}
|
|
document.getElementById('viewerContent').innerHTML = marked.parse(content);
|
|
} catch (error) {
|
|
document.getElementById('viewerContent').innerHTML = `<p style="color: var(--error)">Eroare: ${error.message}</p>`;
|
|
}
|
|
}
|
|
|
|
function closeNote() {
|
|
document.getElementById('noteViewer').classList.remove('active');
|
|
document.body.style.overflow = '';
|
|
history.replaceState(null, '', window.location.pathname);
|
|
}
|
|
|
|
async function preloadNotes() {
|
|
for (const note of notesIndex) {
|
|
try {
|
|
const response = await fetch(notesBasePath + note.file);
|
|
notesCache[note.file] = await response.text();
|
|
} catch (e) {
|
|
notesCache[note.file] = '';
|
|
}
|
|
}
|
|
}
|
|
|
|
// searchNotes replaced by filterNotes above
|
|
|
|
// Handle hash for deep links
|
|
function checkHash() {
|
|
if (window.location.hash) {
|
|
const id = window.location.hash.slice(1);
|
|
const note = notesIndex.find(n => {
|
|
const noteId = n.file.replace(/^\d{4}-\d{2}-\d{2}_/, '').replace(/\.md$/, '');
|
|
return noteId === id;
|
|
});
|
|
if (note) {
|
|
openNote(note.file);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ESC to close
|
|
document.addEventListener('keydown', e => {
|
|
if (e.key === 'Escape') closeNote();
|
|
});
|
|
|
|
// Init - load index first, then render
|
|
async function init() {
|
|
await loadNotesIndex();
|
|
renderTagPills();
|
|
renderNotesAccordion();
|
|
preloadNotes();
|
|
checkHash();
|
|
}
|
|
init();
|
|
</script>
|
|
</body>
|
|
</html>
|