(function () { "use strict"; // Detect Node (fs) availability (Electron / Node) and provide a browser fallback let nodeFS = false; let fsModule = null; let pathModule = null; let dataFilePath = null; const browserDataUrl = "assets/js/data/doxa.json"; // relative to index.html try { fsModule = require("fs"); pathModule = require("path"); nodeFS = true; dataFilePath = pathModule.resolve(__dirname, "data", "doxa.json"); } catch (err) { nodeFS = false; } // In-memory state window.notes = window.notes || []; window.currentNoteIndex = window.currentNoteIndex || null; let modalCurrentIndex = null; function log() { if (console && console.log) console.log.apply(console, arguments); } // Theme handling: store preference in localStorage and toggle body.dark const THEME_KEY = "doxa_theme"; let systemMediaQuery = null; let systemMediaListener = null; function applyTheme(theme) { try { // remove previous system listener if any if (systemMediaQuery && systemMediaListener) { try { if (systemMediaQuery.removeEventListener) systemMediaQuery.removeEventListener("change", systemMediaListener); else systemMediaQuery.removeListener(systemMediaListener); } catch (e) {} systemMediaQuery = null; systemMediaListener = null; } if (theme === "dark") { document.body.classList.add("dark"); } else if (theme === "light") { document.body.classList.remove("dark"); } else { // system: apply based on current system setting and listen for changes try { const mql = window.matchMedia("(prefers-color-scheme: dark)"); if (mql.matches) document.body.classList.add("dark"); else document.body.classList.remove("dark"); systemMediaQuery = mql; systemMediaListener = function (e) { if (e.matches) document.body.classList.add("dark"); else document.body.classList.remove("dark"); const btn = document.getElementById("themeToggle"); if (btn) { const currentSys = e.matches ? "tmavý" : "světlý"; btn.setAttribute( "aria-label", t('themeAriaSystem', { current: t(e.matches ? 'themeCurrentDark' : 'themeCurrentLight') }) ); } }; if (mql.addEventListener) mql.addEventListener("change", systemMediaListener); else mql.addListener(systemMediaListener); } catch (e) { // fallback document.body.classList.remove("dark"); } } } catch (e) { /* ignore */ } const btn = document.getElementById("themeToggle"); if (btn) { let icon = "🌓"; let aria = t('themeAriaSystem', { current: t(document.body.classList.contains("dark") ? 'themeCurrentDark' : 'themeCurrentLight') }); if (theme === "dark") { icon = "🌙"; aria = t('themeAriaDark'); } else if (theme === "light") { icon = "☀️"; aria = t('themeAriaLight'); } else { const cur = document.body.classList.contains("dark") ? t('themeCurrentDark') : t('themeCurrentLight'); icon = "🌓"; aria = t('themeAriaSystem', { current: cur }); } btn.textContent = icon; btn.setAttribute("aria-label", aria); } } function detectSystemTheme() { try { return window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"; } catch (e) { return "light"; } } function getStoredTheme() { try { return localStorage.getItem(THEME_KEY); } catch (e) { return null; } } function setStoredTheme(value) { try { localStorage.setItem(THEME_KEY, value); } catch (e) {} } function initTheme() { const stored = getStoredTheme(); const theme = stored !== null ? stored : "system"; applyTheme(theme); } function toggleTheme() { const stored = getStoredTheme() || "system"; const order = ["light", "dark", "system"]; let idx = order.indexOf(stored); if (idx === -1) idx = 2; const next = order[(idx + 1) % order.length]; setStoredTheme(next); applyTheme(next); } // end theme handling // --- Markdown editor helpers (toolbar + preview) --- let mdEngine = null; function ensureMarkdown() { if (mdEngine) return mdEngine; if (window.markdownit) { try { mdEngine = window.markdownit({ html: false, linkify: true, typographer: true, }); } catch (e) { mdEngine = null; } return mdEngine; } // Fallback simple markdown renderer (safe, offline) function escapeHtml(str) { if (str == null) return ""; return String(str) .replace(/&/g, "&") .replace(//g, ">") .replace(/\"/g, """) .replace(/'/g, "'"); } function escapeAttr(str) { return escapeHtml(str).replace(/\"/g, """); } function inlineTokenize(s) { const tokens = []; function push(html) { const id = "%%T" + tokens.length + "%%"; tokens.push(html); return id; } // images s = s.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, (m, alt, url) => push( '' + escapeHtml(alt) + '', ), ); // links s = s.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (m, text, url) => push('' + escapeHtml(text) + ""), ); // inline code s = s.replace(/`([^`]+)`/g, (m, code) => push("" + escapeHtml(code) + ""), ); // bold s = s.replace(/\*\*(.+?)\*\*/g, (m, t) => push("" + escapeHtml(t) + ""), ); // italic s = s.replace(/\*(.+?)\*/g, (m, t) => push("" + escapeHtml(t) + ""), ); // strike s = s.replace(/~~(.+?)~~/g, (m, t) => push("" + escapeHtml(t) + ""), ); // escape remaining text let escaped = escapeHtml(s); // replace placeholders tokens.forEach((html, i) => { escaped = escaped.replace("%%T" + i + "%%", html); }); return escaped; } function simpleMarkdownRender(src) { if (!src) return ""; const lines = String(src).replace(/\r\n/g, "\n").split("\n"); let i = 0, out = ""; const n = lines.length; while (i < n) { let line = lines[i]; // code block if (/^```/.test(line)) { const lang = line.slice(3).trim(); let code = ""; i++; while (i < n && !/^```/.test(lines[i])) { code += lines[i] + "\n"; i++; } i++; // skip closing out += "
" + escapeHtml(code) + "
\n"; continue; } // heading const h = line.match(/^\s*(#{1,6})\s+(.*)$/); if (h) { out += "" + inlineTokenize(h[2]) + "\n"; i++; continue; } // hr if (/^\s*-{3,}\s*$/.test(line)) { out += "
"; i++; continue; } // table detection: line with | and next line with - or : if ( line.indexOf("|") !== -1 && i + 1 < n && /[-:\s|]+/.test(lines[i + 1]) && lines[i + 1].indexOf("-") !== -1 ) { const headerParts = line .split("|") .map((s) => s.trim()) .filter((s) => s.length > 0); const j = i + 2; const rows = []; let k = j; while (k < n && lines[k].indexOf("|") !== -1) { rows.push(lines[k]); k++; } out += "" + headerParts .map((hp) => "") .join("") + ""; if (rows.length) { out += "" + rows .map((r) => { const cells = r .split("|") .map((c) => c.trim()) .filter((_, idx) => idx > 0 || true); return ( "" + cells .map((c) => "") .join("") + "" ); }) .join("") + ""; } out += "
" + inlineTokenize(hp) + "
" + inlineTokenize(c) + "
\n"; i = k; continue; } // unordered list if (/^\s*[-*]\s+/.test(line)) { out += "\n"; continue; } // ordered list if (/^\s*\d+\.\s+/.test(line)) { out += "
    "; while (i < n && /^\s*\d+\.\s+/.test(lines[i])) { const m = lines[i].replace(/^\s*\d+\.\s+/, ""); out += "
  1. " + inlineTokenize(m) + "
  2. "; i++; } out += "
\n"; continue; } // blockquote if (/^\s*>\s?/.test(line)) { let quote = ""; while (i < n && /^\s*>\s?/.test(lines[i])) { quote += lines[i].replace(/^\s*>\s?/, "") + "\n"; i++; } out += "
" + inlineTokenize(quote.trim()) + "
\n"; continue; } // paragraph (collect until blank) if (/^\s*$/.test(line)) { i++; continue; } let para = line; i++; while ( i < n && !/^\s*$/.test(lines[i]) && !/^(#{1,6})\s+/.test(lines[i]) && !/^```/.test(lines[i]) && lines[i].indexOf("|") === -1 && !/^\s*[-*]\s+/.test(lines[i]) && !/^\s*\d+\.\s+/.test(lines[i]) && !/^\s*>\s?/.test(lines[i]) ) { para += "\n" + lines[i]; i++; } out += "

" + inlineTokenize(para.trim()) + "

\n"; } return out; } mdEngine = { render: simpleMarkdownRender }; return mdEngine; } function sanitizeHtml(html) { if (window.DOMPurify && window.DOMPurify.sanitize) return window.DOMPurify.sanitize(html); return html; } function renderPreviewFor(textareaEl, previewEl) { if (!textareaEl || !previewEl) return; const md = ensureMarkdown(); if (!md) { previewEl.innerText = t('previewUnavailable'); return; } const raw = textareaEl.value || ""; let html = ""; try { html = md.render(raw); } catch (e) { html = "
" + String(raw) + "
"; } html = sanitizeHtml(html); previewEl.innerHTML = html; // syntax highlight if available if (window.hljs && window.hljs.highlightElement) { previewEl.querySelectorAll("pre code").forEach((block) => { try { window.hljs.highlightElement(block); } catch (e) {} }); } } function scheduleRender(textareaEl, previewEl, delay = 200) { if (!previewEl) return; try { if (previewEl._renderTimer) clearTimeout(previewEl._renderTimer); } catch (e) {} previewEl._renderTimer = setTimeout(() => { try { renderPreviewFor(textareaEl, previewEl); } catch (e) {} previewEl._renderTimer = null; }, delay); } // Live preview storage key and helpers const LIVE_PREVIEW_KEY = "doxa_livepreview"; function getStoredLive() { try { return localStorage.getItem(LIVE_PREVIEW_KEY) === "1"; } catch (e) { return false; } } function setStoredLive(v) { try { localStorage.setItem(LIVE_PREVIEW_KEY, v ? "1" : "0"); } catch (e) {} } function applyLiveStateGlobally() { const on = getStoredLive(); const mainLiveBtn = document.getElementById("livePreviewToggle"); const mainPreview = document.getElementById("notePreview"); const mainTextarea = document.getElementById("noteInput"); if (mainLiveBtn) { if (on) mainLiveBtn.classList.add("active"); else mainLiveBtn.classList.remove("active"); } if (mainPreview) { mainPreview.classList.add("hidden"); mainPreview.setAttribute("aria-hidden", "true"); } if (on && mainPreview && mainTextarea) { try { scheduleRender(mainTextarea, mainPreview, 0); } catch (e) {} } // modal (if present) const modalLiveBtn = document.getElementById("livePreviewToggleModal"); const modalPreview = document.getElementById("modalNotePreview"); const modalTextarea = document.getElementById("modalNoteContent"); if (modalLiveBtn) { if (on) modalLiveBtn.classList.add("active"); else modalLiveBtn.classList.remove("active"); } if (on && modalPreview && modalTextarea) { try { scheduleRender(modalTextarea, modalPreview, 0); } catch (e) {} } } const AUTOSAVE_DELAY = 1000; let autosaveTimer = null; function scheduleAutosave() { try { if (autosaveTimer) clearTimeout(autosaveTimer); } catch (e) {} autosaveTimer = setTimeout(() => { try { saveNotesToFile(false); } catch (err) { /* ignore */ } const statusEl = document.getElementById("saveStatus"); if (statusEl) { statusEl.textContent = t('statusSaved'); try { if (window._saveStatusTimeout) clearTimeout(window._saveStatusTimeout); } catch (e) {} window._saveStatusTimeout = setTimeout(() => { statusEl.textContent = ""; }, 1500); } }, AUTOSAVE_DELAY); } function wrapSelection(textarea, before, after) { if (!textarea) return; const start = textarea.selectionStart; const end = textarea.selectionEnd; const value = textarea.value; const selected = value.slice(start, end) || ""; const newValue = value.slice(0, start) + before + selected + (after !== undefined ? after : before) + value.slice(end); textarea.value = newValue; textarea.focus(); const cursorStart = start + before.length; textarea.selectionStart = cursorStart; textarea.selectionEnd = cursorStart + selected.length; // trigger input event so autosave and model update run try { textarea.dispatchEvent(new Event("input", { bubbles: true })); } catch (e) { try { textarea.dispatchEvent(new Event("input")); } catch (e) {} } } function insertCodeBlock(textarea) { if (!textarea) return; const start = textarea.selectionStart; const end = textarea.selectionEnd; const value = textarea.value; const selected = value.slice(start, end) || ""; if (!selected) { const insert = "```\n\n```"; textarea.value = value.slice(0, start) + insert + value.slice(end); textarea.focus(); const cursor = start + 4; // after ```\n textarea.selectionStart = cursor; textarea.selectionEnd = cursor; try { textarea.dispatchEvent(new Event("input", { bubbles: true })); } catch (e) { try { textarea.dispatchEvent(new Event("input")); } catch (e) {} } } else { const newValue = value.slice(0, start) + "```\n" + selected + "\n```" + value.slice(end); textarea.value = newValue; textarea.focus(); textarea.selectionStart = start + 4; textarea.selectionEnd = start + 4 + selected.length; try { textarea.dispatchEvent(new Event("input", { bubbles: true })); } catch (e) { try { textarea.dispatchEvent(new Event("input")); } catch (e) {} } } } function insertLink(textarea) { if (!textarea) return; const start = textarea.selectionStart; const end = textarea.selectionEnd; const value = textarea.value; const selected = value.slice(start, end) || "odkaz"; const url = prompt(t('promptUrl'), "https://"); if (!url) return; const newValue = value.slice(0, start) + "[" + selected + "](" + url + ")" + value.slice(end); textarea.value = newValue; textarea.focus(); const cursorStart = start + 1; textarea.selectionStart = cursorStart; textarea.selectionEnd = cursorStart + selected.length; try { textarea.dispatchEvent(new Event("input", { bubbles: true })); } catch (e) { try { textarea.dispatchEvent(new Event("input")); } catch (e) {} } } // --- end markdown editor helpers --- function getLineInfo(textarea) { const value = textarea.value; const start = textarea.selectionStart; const end = textarea.selectionEnd; const lineStart = value.lastIndexOf("\n", Math.max(0, start - 1)) + 1; let nextNewline = value.indexOf("\n", end); if (nextNewline === -1) nextNewline = value.length; const segment = value.slice(lineStart, nextNewline); const lines = segment.split("\n"); return { value, start, end, lineStart, lineEnd: nextNewline, lines }; } function insertHeading(textarea, level) { if (!textarea) return; const info = getLineInfo(textarea); const lines = info.lines; const prefix = "#".repeat(level) + " "; const specificRegex = new RegExp("^\\s*" + "#".repeat(level) + "\\s+"); const anyHeadingRegex = /^\s*#{1,6}\s+/; const allHaveSpecific = lines.every((l) => specificRegex.test(l)); let newLines; if (allHaveSpecific) newLines = lines.map((l) => l.replace(specificRegex, "")); else newLines = lines.map((l) => prefix + l.replace(anyHeadingRegex, "")); const newSegment = newLines.join("\n"); textarea.value = info.value.slice(0, info.lineStart) + newSegment + info.value.slice(info.lineEnd); textarea.selectionStart = info.lineStart; textarea.selectionEnd = info.lineStart + newSegment.length; textarea.focus(); try { textarea.dispatchEvent(new Event("input", { bubbles: true })); } catch (e) { try { textarea.dispatchEvent(new Event("input")); } catch (e) {} } } function toggleUnorderedList(textarea) { if (!textarea) return; const info = getLineInfo(textarea); const lines = info.lines; const allHave = lines.every((l) => /^\s*([-*])\s+/.test(l)); let newLines; if (allHave) newLines = lines.map((l) => l.replace(/^\s*([-*])\s+/, "")); else newLines = lines.map((l) => l.trim() === "" ? "- " : "- " + l.replace(/^\s*/, ""), ); const newSegment = newLines.join("\n"); textarea.value = info.value.slice(0, info.lineStart) + newSegment + info.value.slice(info.lineEnd); textarea.selectionStart = info.lineStart; textarea.selectionEnd = info.lineStart + newSegment.length; textarea.focus(); try { textarea.dispatchEvent(new Event("input", { bubbles: true })); } catch (e) { try { textarea.dispatchEvent(new Event("input")); } catch (e) {} } } function toggleOrderedList(textarea) { if (!textarea) return; const info = getLineInfo(textarea); const lines = info.lines; const allHave = lines.every((l) => /^\s*\d+\.\s+/.test(l)); let newLines; if (allHave) newLines = lines.map((l) => l.replace(/^\s*\d+\.\s+/, "")); else newLines = lines.map((l, i) => l.trim() === "" ? i + 1 + ". " : i + 1 + ". " + l.replace(/^\s*/, ""), ); const newSegment = newLines.join("\n"); textarea.value = info.value.slice(0, info.lineStart) + newSegment + info.value.slice(info.lineEnd); textarea.selectionStart = info.lineStart; textarea.selectionEnd = info.lineStart + newSegment.length; textarea.focus(); try { textarea.dispatchEvent(new Event("input", { bubbles: true })); } catch (e) { try { textarea.dispatchEvent(new Event("input")); } catch (e) {} } } function toggleBlockquote(textarea) { if (!textarea) return; const info = getLineInfo(textarea); const lines = info.lines; const allHave = lines.every((l) => /^\s*>\s?/.test(l)); let newLines; if (allHave) newLines = lines.map((l) => l.replace(/^\s*>\s?/, "")); else newLines = lines.map((l) => l.trim() === "" ? "> " : "> " + l.replace(/^\s*/, ""), ); const newSegment = newLines.join("\n"); textarea.value = info.value.slice(0, info.lineStart) + newSegment + info.value.slice(info.lineEnd); textarea.selectionStart = info.lineStart; textarea.selectionEnd = info.lineStart + newSegment.length; textarea.focus(); try { textarea.dispatchEvent(new Event("input", { bubbles: true })); } catch (e) { try { textarea.dispatchEvent(new Event("input")); } catch (e) {} } } function insertHr(textarea) { if (!textarea) return; const pos = textarea.selectionStart; const value = textarea.value; const insert = "\n---\n"; textarea.value = value.slice(0, pos) + insert + value.slice(pos); textarea.selectionStart = pos + insert.length; textarea.selectionEnd = textarea.selectionStart; textarea.focus(); try { textarea.dispatchEvent(new Event("input", { bubbles: true })); } catch (e) { try { textarea.dispatchEvent(new Event("input")); } catch (e) {} } } // --- Ostatní helpers pro markdown zůstávají --- function insertImage(textarea) { if (!textarea) return; const start = textarea.selectionStart; const end = textarea.selectionEnd; const value = textarea.value; const selected = value.slice(start, end) || ""; const url = prompt(t('promptImageUrl'), "https://"); if (!url) return; const alt = selected || prompt(t('promptImageAlt'), ""); const insert = "![" + (alt || "") + "](" + url + ")"; textarea.value = value.slice(0, start) + insert + value.slice(end); textarea.selectionStart = start + insert.length; textarea.selectionEnd = textarea.selectionStart; textarea.focus(); try { textarea.dispatchEvent(new Event("input", { bubbles: true })); } catch (e) { try { textarea.dispatchEvent(new Event("input")); } catch (e) {} } } function toggleTaskList(textarea) { if (!textarea) return; const info = getLineInfo(textarea); const lines = info.lines; const allHave = lines.every((l) => /^\s*-\s*\[[ xX]\]\s+/.test(l)); let newLines; if (allHave) newLines = lines.map((l) => l.replace(/^\s*-\s*\[[ xX]\]\s+/, "")); else newLines = lines.map((l) => l.trim() === "" ? "- [ ] " : "- [ ] " + l.replace(/^\s*/, ""), ); const newSegment = newLines.join("\n"); textarea.value = info.value.slice(0, info.lineStart) + newSegment + info.value.slice(info.lineEnd); textarea.selectionStart = info.lineStart; textarea.selectionEnd = info.lineStart + newSegment.length; textarea.focus(); try { textarea.dispatchEvent(new Event("input", { bubbles: true })); } catch (e) { try { textarea.dispatchEvent(new Event("input")); } catch (e) {} } } function insertTable(textarea) { if (!textarea) return; const colsStr = prompt(t('promptTableCols'), "2"); if (!colsStr) return; const rowsStr = prompt(t('promptTableRows'), "2"); if (!rowsStr) return; const cols = Math.max(1, parseInt(colsStr, 10) || 2); const rows = Math.max(0, parseInt(rowsStr, 10) || 2); // build header const headers = []; const aligns = []; for (let c = 1; c <= cols; c++) { headers.push(t('tableHeaderDefault', { index: c })); aligns.push("---"); } const headerLine = "| " + headers.join(" | ") + " |\n"; const alignLine = "| " + aligns.join(" | ") + " |\n"; let body = ""; for (let r = 0; r < rows; r++) { const cells = []; for (let c = 0; c < cols; c++) cells.push(""); body += "| " + cells.join(" | ") + " |\n"; } const insert = headerLine + alignLine + body; const pos = textarea.selectionStart || textarea.value.length; textarea.value = textarea.value.slice(0, pos) + insert + textarea.value.slice(pos); textarea.selectionStart = pos + insert.length; textarea.selectionEnd = textarea.selectionStart; textarea.focus(); try { textarea.dispatchEvent(new Event("input", { bubbles: true })); } catch (e) { try { textarea.dispatchEvent(new Event("input")); } catch (e) {} } } function togglePreview(textareaId, previewId) { const textarea = document.getElementById(textareaId); const preview = document.getElementById(previewId); const toggleBtn = document.getElementById("togglePreviewMain") || document.getElementById("togglePreviewModal"); if (!preview) return; const isHidden = preview.classList.contains("hidden"); if (isHidden) { scheduleRender(textarea, preview, 0); preview.classList.remove("hidden"); preview.setAttribute("aria-hidden", "false"); textarea.classList.add("hidden"); if (toggleBtn) toggleBtn.classList.add("active"); } else { preview.classList.add("hidden"); preview.setAttribute("aria-hidden", "true"); textarea.classList.remove("hidden"); if (toggleBtn) toggleBtn.classList.remove("active"); } } function attachToolbar(toolbarEl, textareaEl, previewEl) { if (!toolbarEl || !textareaEl) return; toolbarEl.querySelectorAll("button[data-action]").forEach((btn) => { btn.addEventListener("click", (e) => { const action = btn.getAttribute("data-action"); e.preventDefault(); switch (action) { case "bold": wrapSelection(textareaEl, "**", "**"); break; case "italic": wrapSelection(textareaEl, "*", "*"); break; case "code": wrapSelection(textareaEl, "`", "`"); break; case "codeblock": insertCodeBlock(textareaEl); break; case "link": insertLink(textareaEl); break; case "h1": insertHeading(textareaEl, 1); break; case "h2": insertHeading(textareaEl, 2); break; case "h3": insertHeading(textareaEl, 3); break; case "ul": toggleUnorderedList(textareaEl); break; case "ol": toggleOrderedList(textareaEl); break; case "quote": toggleBlockquote(textareaEl); break; case "hr": insertHr(textareaEl); break; case "image": insertImage(textareaEl); break; case "strike": wrapSelection(textareaEl, "~~", "~~"); break; case "task": toggleTaskList(textareaEl); break; case "table": insertTable(textareaEl); break; } }); }); const previewBtn = toolbarEl.querySelector("#togglePreviewMain") || toolbarEl.querySelector("#togglePreviewModal"); if (previewBtn) { previewBtn.addEventListener("click", (e) => { e.preventDefault(); togglePreview(textareaEl.id, previewEl.id); }); } } function loadNotes() { if (nodeFS) { try { if (!fsModule.existsSync(dataFilePath)) { fsModule.mkdirSync(pathModule.dirname(dataFilePath), { recursive: true, }); fsModule.writeFileSync( dataFilePath, JSON.stringify({ notes: [] }, null, 2), "utf8", ); } const raw = fsModule.readFileSync(dataFilePath, "utf8"); const data = raw ? JSON.parse(raw) : {}; window.notes = data.notes || []; if (!window.notes || window.notes.length === 0) window.currentNoteIndex = null; } catch (err) { console.error("Chyba při čtení datového souboru:", err); window.notes = window.notes || []; } // OPRAVA/SANACE: Zajištění přítomnosti pole tags a folder u všech načtených poznámek if (Array.isArray(window.notes)) { window.notes.forEach(note => { if (!note.tags) note.tags = []; if (!note.folder) note.folder = ""; }); } updateNotesList(); if (window.currentNoteIndex != null) switchNotes(window.currentNoteIndex); return; } // Browser fallback try { const saved = localStorage.getItem("doxa_notes_v1"); if (saved) { const parsed = JSON.parse(saved); window.notes = parsed.notes || []; // OPRAVA/SANACE v LocalStorage: tags + folder if (Array.isArray(window.notes)) { window.notes.forEach(note => { if (!note.tags) note.tags = []; if (!note.folder) note.folder = ""; }); } updateNotesList(); if (window.currentNoteIndex != null) switchNotes(window.currentNoteIndex); return; } } catch (err) { console.warn("Chyba při načítání z localStorage:", err); } fetch(browserDataUrl) .then((resp) => { if (!resp.ok) { window.notes = []; updateNotesList(); return; } return resp .json() .then((data) => { window.notes = data.notes || []; // OPRAVA/SANACE ve statickém JSON souboru: tags + folder if (Array.isArray(window.notes)) { window.notes.forEach(note => { if (!note.tags) note.tags = []; if (!note.folder) note.folder = ""; }); } updateNotesList(); }) .catch((err) => { console.error("Chyba při parsování JSON ze souboru:", err); window.notes = []; updateNotesList(); }); }) .catch((err) => { console.warn("Nelze načíst statický soubor:", err); window.notes = []; updateNotesList(); }); } function saveNotesToFile(triggerDownload = true) { const payload = { notes: window.notes || [] }; const json = JSON.stringify(payload, null, 2); if (nodeFS) { try { const dir = pathModule.dirname(dataFilePath); if (!fsModule.existsSync(dir)) { fsModule.mkdirSync(dir, { recursive: true }); } fsModule.writeFileSync(dataFilePath, json, "utf8"); log("Data uložena do", dataFilePath); if (triggerDownload) alert(t('notesSavedAlert')); } catch (err) { alert(t('saveErrorAlert', { message: (err.message || err) })); } return; } try { localStorage.setItem("doxa_notes_v1", json); } catch (err) { console.warn("Nepodařilo se uložit do localStorage:", err); } if (triggerDownload) { const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(json); const downloadAnchor = document.createElement("a"); downloadAnchor.setAttribute("href", dataStr); downloadAnchor.setAttribute("download", "doxa.json"); document.body.appendChild(downloadAnchor); downloadAnchor.click(); downloadAnchor.remove(); } } function reorderNotes(fromIndex, toIndex) { if (fromIndex === toIndex) return; if (!Array.isArray(window.notes)) return; const selectedId = window.notes[window.currentNoteIndex] ? window.notes[window.currentNoteIndex].id : null; const item = window.notes.splice(fromIndex, 1)[0]; let insertAt = toIndex; if (toIndex > fromIndex) insertAt = toIndex - 1; if (insertAt < 0) insertAt = 0; if (insertAt > window.notes.length) insertAt = window.notes.length; window.notes.splice(insertAt, 0, item); if (selectedId != null) { const newIndex = window.notes.findIndex((n) => n.id === selectedId); window.currentNoteIndex = newIndex === -1 ? null : newIndex; } saveNotesToFile(false); updateNotesList(); } function moveNoteUp(index) { if (index <= 0) return; reorderNotes(index, index - 1); } function moveNoteDown(index) { if (index < 0 || index >= window.notes.length - 1) return; reorderNotes(index, index + 2); } function deleteNote(index) { if (!window.notes || index < 0 || index >= window.notes.length) return; const note = window.notes[index]; if (!confirm(t('confirmDeleteNote', { noteName: note.name }))) return; const selectedId = window.notes[window.currentNoteIndex] ? window.notes[window.currentNoteIndex].id : null; window.notes.splice(index, 1); if (selectedId != null && selectedId === note.id) { if (window.notes.length === 0) { window.currentNoteIndex = null; document.getElementById("noteTitle").value = ""; document.getElementById("noteInput").value = ""; if (document.getElementById("noteTags")) document.getElementById("noteTags").value = ""; } else { const newIndex = Math.min(index, window.notes.length - 1); window.currentNoteIndex = newIndex; switchNotes(window.currentNoteIndex); } } else if ( window.currentNoteIndex != null && window.currentNoteIndex > index ) { window.currentNoteIndex -= 1; } saveNotesToFile(false); updateNotesList(); } function ensureModal() { if (document.getElementById("editModalOverlay")) return; const overlay = document.createElement("div"); overlay.id = "editModalOverlay"; overlay.className = "modal-overlay hidden"; overlay.innerHTML = ` `; document.body.appendChild(overlay); const modalToolbar = overlay.querySelector("#modalEditorToolbar"); const modalTextarea = overlay.querySelector("#modalNoteContent"); const modalPreview = overlay.querySelector("#modalNotePreview"); if (modalToolbar && modalTextarea) attachToolbar(modalToolbar, modalTextarea, modalPreview); const modalLiveBtn = overlay.querySelector("#livePreviewToggleModal"); if (modalLiveBtn) { modalLiveBtn.addEventListener("click", () => { const cur = getStoredLive(); setStoredLive(!cur); applyLiveStateGlobally(); }); if (getStoredLive()) modalLiveBtn.classList.add("active"); } const modalTitleInput = overlay.querySelector("#modalNoteTitle"); if (modalTitleInput) { modalTitleInput.addEventListener("input", () => { if ( modalCurrentIndex != null && Array.isArray(window.notes) && window.notes[modalCurrentIndex] ) { const idx = modalCurrentIndex; window.notes[idx].name = modalTitleInput.value; window.notes[idx].modified_at = new Date().toISOString(); try { updateNotesList(); } catch (e) {} scheduleAutosave(); } }); } if (modalTextarea && modalPreview) { modalTextarea.addEventListener("input", () => { if ( modalCurrentIndex != null && Array.isArray(window.notes) && window.notes[modalCurrentIndex] ) { const idx = modalCurrentIndex; window.notes[idx].content = modalTextarea.value; window.notes[idx].modified_at = new Date().toISOString(); try { updateNotesList(); } catch (e) {} scheduleAutosave(); } if (!modalPreview.classList.contains("hidden")) scheduleRender(modalTextarea, modalPreview); }); modalTextarea.addEventListener("keydown", (e) => { if ((e.ctrlKey || e.metaKey) && !e.shiftKey) { const k = (e.key || "").toLowerCase(); if (k === "b") { e.preventDefault(); wrapSelection(modalTextarea, "**", "**"); } if (k === "i") { e.preventDefault(); wrapSelection(modalTextarea, "*", "*"); } if (k === "`") { e.preventDefault(); wrapSelection(modalTextarea, "`", "`"); } } }); } overlay.querySelector(".modal-close").addEventListener("click", closeModal); overlay .querySelector("#modalCancelBtn") .addEventListener("click", closeModal); overlay.querySelector("#modalSaveBtn").addEventListener("click", () => { const title = document.getElementById("modalNoteTitle").value.trim(); const content = document.getElementById("modalNoteContent").value; if (!title) return alert(t('alertNoTitle')); if (modalCurrentIndex == null) return closeModal(); const note = window.notes[modalCurrentIndex]; note.name = title; note.content = content; note.modified_at = new Date().toISOString(); saveNotesToFile(false); updateNotesList(); switchNotes(modalCurrentIndex); closeModal(); }); overlay.addEventListener("click", (e) => { if (e.target === overlay) closeModal(); }); } function openEditModal(index) { if (!window.notes || index < 0 || index >= window.notes.length) return; ensureModal(); modalCurrentIndex = index; const overlay = document.getElementById("editModalOverlay"); const note = window.notes[index]; document.getElementById("modalNoteTitle").value = note.name || ""; document.getElementById("modalNoteContent").value = note.content || ""; overlay.classList.remove("hidden"); setTimeout(() => document.getElementById("modalNoteTitle").focus(), 50); } function closeModal() { modalCurrentIndex = null; const overlay = document.getElementById("editModalOverlay"); if (overlay) overlay.classList.add("hidden"); } function updateNotesList() { const notesContainer = document.getElementById("notesContainer"); if (!notesContainer) return; notesContainer.innerHTML = ""; const filterText = document.getElementById("searchTagInput")?.value.trim().toLowerCase() || ""; const folderFilter = document.getElementById("folderFilter")?.value || "all"; // naplnit select se složkami (unikátní, nenulové) a aktualizovat datalist pro pole složky try { const select = document.getElementById("folderFilter"); const datalist = document.getElementById("foldersList"); if (select) { const folders = Array.from(new Set((window.notes || []).map(n => (n.folder || "").trim()).filter(f => f))); const current = select.value || "all"; select.innerHTML = `` + folders.map(f => ``).join(""); if (folders.indexOf(current) !== -1) select.value = current; else select.value = current === "all" ? "all" : current; // update datalist options if (datalist) { datalist.innerHTML = folders.map(f => ``).join(""); } } } catch (e) {} if (!window.notes || window.notes.length === 0) { const empty = document.createElement("div"); empty.textContent = t('noNotes'); empty.className = "no-notes"; notesContainer.appendChild(empty); return; } let renderedCount = 0; window.notes.forEach((note, index) => { // filtrovat podle hledaného textu (název, obsah, tagy, složka) if (filterText) { const ft = filterText; const inTitle = note.name && note.name.toLowerCase().includes(ft); const inContent = note.content && note.content.toLowerCase().includes(ft); const inTags = note.tags && note.tags.some(t => t.toLowerCase().includes(ft)); const inFolderName = note.folder && note.folder.toLowerCase().includes(ft); if (!(inTitle || inContent || inTags || inFolderName)) return; } // filtr podle složky (select) if (folderFilter && folderFilter !== "all") { const nf = (note.folder || "").trim(); if (nf !== folderFilter) return; } renderedCount++; const el = document.createElement("div"); el.className = "note-item"; el.setAttribute("draggable", "true"); el.dataset.index = index; const left = document.createElement("div"); left.className = "note-left"; left.addEventListener("click", () => switchNotes(index)); const title = document.createElement("div"); title.className = "note-title"; title.textContent = note.name || t('noteNameDefault', { index: index + 1 }); const preview = document.createElement("div"); preview.className = "note-preview"; let previewText = (note.content || "").replace(/\n/g, " ").slice(0, 80); preview.textContent = previewText; left.appendChild(title); left.appendChild(preview); if (note.tags && note.tags.length > 0) { const tagsDiv = document.createElement("div"); tagsDiv.className = "note-preview-tags"; tagsDiv.style.fontSize = "0.75rem"; tagsDiv.style.color = "var(--muted)"; tagsDiv.style.marginTop = "4px"; tagsDiv.style.fontFamily = "monospace"; tagsDiv.textContent = "🏷️ " + note.tags.join(", "); left.appendChild(tagsDiv); } if (note.folder && note.folder.trim()) { const folderDiv = document.createElement("div"); folderDiv.className = "note-preview-folder"; folderDiv.style.fontSize = "0.75rem"; folderDiv.style.color = "var(--muted)"; folderDiv.style.marginTop = "4px"; folderDiv.style.fontFamily = "monospace"; const iconSpan = document.createElement("span"); iconSpan.className = "folder-icon"; iconSpan.textContent = "📁"; const nameSpan = document.createElement("span"); nameSpan.className = "folder-name"; nameSpan.textContent = note.folder; folderDiv.appendChild(iconSpan); folderDiv.appendChild(nameSpan); left.appendChild(folderDiv); } const controls = document.createElement("div"); controls.className = "note-controls"; const upBtn = document.createElement("button"); upBtn.className = "note-btn move-up"; upBtn.title = t('noteMoveUp'); upBtn.textContent = "↑"; upBtn.addEventListener("click", (e) => { e.stopPropagation(); moveNoteUp(index); }); const downBtn = document.createElement("button"); downBtn.className = "note-btn move-down"; downBtn.title = t('noteMoveDown'); downBtn.textContent = "↓"; downBtn.addEventListener("click", (e) => { e.stopPropagation(); moveNoteDown(index); }); const editBtn = document.createElement("button"); editBtn.className = "note-btn edit"; editBtn.title = t('noteEditModal'); editBtn.textContent = "✎"; editBtn.addEventListener("click", (e) => { e.stopPropagation(); openEditModal(index); }); const delBtn = document.createElement("button"); delBtn.className = "note-btn delete"; delBtn.title = t('noteDelete'); delBtn.textContent = "🗑"; delBtn.addEventListener("click", (e) => { e.stopPropagation(); deleteNote(index); }); controls.appendChild(upBtn); controls.appendChild(downBtn); controls.appendChild(editBtn); controls.appendChild(delBtn); el.appendChild(left); el.appendChild(controls); if (index === window.currentNoteIndex) el.classList.add("active"); el.addEventListener("dragstart", (ev) => { ev.dataTransfer.setData("text/plain", String(index)); ev.dataTransfer.effectAllowed = "move"; el.classList.add("dragging"); }); el.addEventListener("dragend", () => { el.classList.remove("dragging"); document .querySelectorAll(".note-item.drag-over") .forEach((n) => n.classList.remove("drag-over", "drag-after")); }); el.addEventListener("dragover", (ev) => { ev.preventDefault(); const rect = el.getBoundingClientRect(); const offset = ev.clientY - rect.top; const after = offset > rect.height / 2; el.classList.add("drag-over"); if (after) el.classList.add("drag-after"); else el.classList.remove("drag-after"); ev.dataTransfer.dropEffect = "move"; }); el.addEventListener("dragleave", () => { el.classList.remove("drag-over", "drag-after"); }); el.addEventListener("drop", (ev) => { ev.preventDefault(); el.classList.remove("drag-over", "drag-after"); const from = parseInt(ev.dataTransfer.getData("text/plain"), 10); if (Number.isNaN(from)) return; const rect = el.getBoundingClientRect(); const offset = ev.clientY - rect.top; const insertAfter = offset > rect.height / 2; let to = index + (insertAfter ? 1 : 0); if (to < 0) to = 0; if (to > window.notes.length) to = window.notes.length; reorderNotes(from, to); }); notesContainer.appendChild(el); }); if (renderedCount === 0 && filterText) { const emptyFilter = document.createElement("div"); emptyFilter.textContent = t('noNotesForFilter'); emptyFilter.className = "no-notes"; notesContainer.appendChild(emptyFilter); } } function switchNotes(index) { if (!window.notes || index < 0 || index >= window.notes.length) return; window.currentNoteIndex = index; const note = window.notes[index]; const titleEl = document.getElementById("noteTitle"); const input = document.getElementById("noteInput"); const tagsInput = document.getElementById("noteTags"); const preview = document.getElementById("notePreview"); const toggleBtn = document.getElementById("togglePreviewMain"); if (titleEl) titleEl.value = note.name || ""; if (input) input.value = note.content || ""; if (tagsInput) { tagsInput.value = note.tags ? note.tags.join(", ") : ""; } const noteFolderInput = document.getElementById("noteFolder"); if (noteFolderInput) noteFolderInput.value = note.folder || ""; if (preview) { preview.classList.add("hidden"); preview.setAttribute("aria-hidden", "true"); } if (input) input.classList.remove("hidden"); if (toggleBtn) toggleBtn.classList.remove("active"); updateNotesList(); } function addNewNote() { const defaultName = t('noteNameDefault', { index: (window.notes?.length || 0) + 1 }); const name = prompt(t('promptNewNote'), defaultName); if (name === null) return; const newNote = { id: Date.now(), name: name.trim() || defaultName, content: "", tags: [], folder: "", created_at: new Date().toISOString(), }; window.notes = window.notes || []; window.notes.push(newNote); window.currentNoteIndex = window.notes.length - 1; saveNotesToFile(false); updateNotesList(); switchNotes(window.currentNoteIndex); } function importujPoznamky() { const input = document.createElement("input"); input.type = "file"; input.accept = ".json,application/json"; input.addEventListener("change", (ev) => { const file = ev.target.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = (e) => { try { const parsed = JSON.parse(e.target.result); if (Array.isArray(parsed)) { window.notes = parsed; } else if (Array.isArray(parsed.notes)) { window.notes = parsed.notes; } else { alert(t('importInvalidJson')); return; } // OPRAVA/SANACE při importu: Každá poznámka musí mít pole tags a folder if (Array.isArray(window.notes)) { window.notes.forEach(note => { if (!note.tags) note.tags = []; if (!note.folder) note.folder = ""; }); } window.currentNoteIndex = window.notes.length ? 0 : null; saveNotesToFile(false); updateNotesList(); if (window.currentNoteIndex !== null) switchNotes(window.currentNoteIndex); } catch (err) { alert(t('importReadError', { message: err.message })); } }; reader.readAsText(file); }); input.click(); } function ulozPoznamku() { saveNotesToFile(true); } function saveCurrentNote() { const titleEl = document.getElementById("noteTitle"); const input = document.getElementById("noteInput"); const tagsInput = document.getElementById("noteTags"); const title = titleEl ? titleEl.value.trim() : ""; const content = input ? input.value : ""; // OPRAVA: Zaručíme vytvoření validního pole [] i v případě absence textu const tags = tagsInput && tagsInput.value.trim() !== "" ? tagsInput.value.split(",").map(t => t.trim()).filter(t => t.length > 0) : []; // Rozpoznání záložky složky z obsahu: řádek začínající "folder: Název" let detectedFolder = ""; try { const m = content.match(/(^|\n)\s*folder:\s*(.+)/i); if (m && m[2]) detectedFolder = m[2].trim(); } catch (e) {} // Pokud je explicitní pole noteFolder vyplněné, má přednost const explicitFolderInput = document.getElementById("noteFolder"); const explicitFolder = explicitFolderInput && explicitFolderInput.value.trim() ? explicitFolderInput.value.trim() : null; if (!title) { alert(t('alertNoTitle')); return; } if (window.currentNoteIndex == null) { const newNote = { id: Date.now(), name: title, content, tags, folder: explicitFolder !== null ? explicitFolder : detectedFolder || "", created_at: new Date().toISOString(), }; window.notes.push(newNote); window.currentNoteIndex = window.notes.length - 1; } else { const note = window.notes[window.currentNoteIndex]; note.name = title; note.content = content; note.tags = tags; // explicitFolder má prioritu před detekcí v obsahu if (explicitFolder !== null) note.folder = explicitFolder; else if (detectedFolder) note.folder = detectedFolder; note.modified_at = new Date().toISOString(); } saveNotesToFile(false); updateNotesList(); switchNotes(window.currentNoteIndex); } function exportToMarkdown() { const titleEl = document.getElementById("noteTitle"); const contentEl = document.getElementById("noteInput"); const title = titleEl ? titleEl.value.trim() : "Poznámka"; const content = contentEl ? contentEl.value : ""; if (!title && !content) { alert(t('exportEmptyAlert')); return; } const markdown = title ? `# ${title}\n\n${content}` : content; const filename = title ? `${title.replace(/[^a-zA-Z0-9-_]/g, "_")}.md` : "export.md"; const blob = new Blob([markdown], { type: "text/markdown;charset=utf-8" }); const downloadAnchor = document.createElement("a"); downloadAnchor.setAttribute("href", URL.createObjectURL(blob)); downloadAnchor.setAttribute("download", filename); document.body.appendChild(downloadAnchor); downloadAnchor.click(); downloadAnchor.remove(); } // Slash command functionality const SLASH_COMMANDS = [ { cmd: "/date", i18nKey: "slashDate", insert: () => { const lang = localStorage.getItem('doxa_lang') || 'cs'; const locale = lang === 'cs' ? 'cs-CZ' : 'en-US'; return new Date().toLocaleDateString(locale); } }, { cmd: "/time", i18nKey: "slashTime", insert: () => { const lang = localStorage.getItem('doxa_lang') || 'cs'; const locale = lang === 'cs' ? 'cs-CZ' : 'en-US'; return new Date().toLocaleTimeString(locale, { hour: '2-digit', minute: '2-digit' }); } }, { cmd: "/code", i18nKey: "slashCode", insert: () => "`kód`" }, { cmd: "/codeblock", i18nKey: "slashCodeblock", insert: () => "```\nkód\n```" }, { cmd: "/link", i18nKey: "slashLink", insert: () => "text" }, { cmd: "/image", i18nKey: "slashImage", insert: () => "!alt" }, { cmd: "/table", i18nKey: "slashTable", insert: () => "| Sloupec 1 | Sloupec 2 |\n|-----------|----------|\n| Data 1 | Data 2 |" }, { cmd: "/quote", i18nKey: "slashQuote", insert: () => "> Citace" }, { cmd: "/hr", i18nKey: "slashHr", insert: () => "---" }, { cmd: "/task", i18nKey: "slashTask", insert: () => "- [ ] Úkol" }, { cmd: "/list", i18nKey: "slashList", insert: () => "- Položka" }, { cmd: "/h1", i18nKey: "slashH1", insert: () => "# Nadpis" }, { cmd: "/h2", i18nKey: "slashH2", insert: () => "## Nadpis" }, { cmd: "/h3", i18nKey: "slashH3", insert: () => "### Nadpis" }, ]; let slashCommandState = { active: false, selectedIndex: 0, filteredCommands: [], startPos: 0, query: "", }; function hideSlashMenu() { const menu = document.getElementById("slashCommandMenu"); if (menu) { menu.classList.add("hidden"); menu.innerHTML = ""; } slashCommandState.active = false; } function showSlashMenu(textarea, query) { const menu = document.getElementById("slashCommandMenu"); if (!menu) return; const filtered = SLASH_COMMANDS.filter(cmd => cmd.cmd.toLowerCase().includes(query.toLowerCase()) || t(cmd.i18nKey).toLowerCase().includes(query.toLowerCase()) ); slashCommandState.filteredCommands = filtered; slashCommandState.selectedIndex = 0; slashCommandState.query = query; if (filtered.length === 0) { hideSlashMenu(); return; } menu.innerHTML = filtered.map((cmd, idx) => `
${t(cmd.i18nKey)}
` ).join(""); // Umístit menu pod kurzorem v textareu const textarea_rect = textarea.getBoundingClientRect(); const scrollX = window.scrollX || window.pageXOffset; const scrollY = window.scrollY || window.pageYOffset; // Přibližná výška jedného řádku textu const lineHeight = parseInt(window.getComputedStyle(textarea).lineHeight); const lineCount = textarea.value.substring(0, textarea.selectionStart).split('\n').length; // Pozice kurzoru přibližně let top = textarea_rect.top + scrollY + (lineCount * lineHeight) + 4; let left = textarea_rect.left + scrollX; menu.style.position = "fixed"; menu.style.top = top + "px"; menu.style.left = left + "px"; menu.style.zIndex = "1000"; // Ujistit se, že menu nezůstane mimo viditelnou oblast menu.classList.remove("hidden"); setTimeout(() => { const menuRect = menu.getBoundingClientRect(); // Pokud je menu mimo dolní okraj, posunout nahoru if (menuRect.bottom > window.innerHeight) { top = window.innerHeight - menuRect.height - 10 + scrollY; menu.style.top = Math.max(scrollY, top) + "px"; } // Pokud je menu mimo pravý okraj, posunout doleva if (menuRect.right > window.innerWidth) { left = window.innerWidth - menuRect.width - 10 + scrollX; menu.style.left = Math.max(scrollX, left) + "px"; } }, 0); slashCommandState.active = true; menu.querySelectorAll(".slash-command-item").forEach((item, idx) => { item.addEventListener("click", () => selectSlashCommand(textarea, idx)); item.addEventListener("mouseover", () => { document.querySelectorAll(".slash-command-item").forEach(i => i.classList.remove("active")); item.classList.add("active"); slashCommandState.selectedIndex = idx; }); }); } function selectSlashCommand(textarea, index) { const cmd = slashCommandState.filteredCommands[index]; if (!cmd) return; const text = textarea.value; const pos = textarea.selectionStart; const lastSlashPos = text.lastIndexOf("/", pos - 1); if (lastSlashPos === -1) return; const before = text.substring(0, lastSlashPos); const after = text.substring(pos); const insertText = cmd.insert(); textarea.value = before + insertText + after; textarea.selectionStart = textarea.selectionEnd = before.length + insertText.length; textarea.focus(); if (window.currentNoteIndex != null && Array.isArray(window.notes)) { window.notes[window.currentNoteIndex].content = textarea.value; scheduleAutosave(); } hideSlashMenu(); } function initSlashCommands(textarea) { if (!textarea) return; // Slash commands are handled in the main input and keydown listeners } function updateSlashMenuSelection() { document.querySelectorAll(".slash-command-item").forEach((item, idx) => { if (idx === slashCommandState.selectedIndex) { item.classList.add("active"); item.scrollIntoView({ block: "nearest" }); } else { item.classList.remove("active"); } }); } // Export functions to global scope window.importujPoznamky = importujPoznamky; window.addNewNote = addNewNote; window.switchNotes = switchNotes; window.ulozPoznamku = ulozPoznamku; window.saveNotesToFile = saveNotesToFile; window.saveCurrentNote = saveCurrentNote; window.exportToMarkdown = exportToMarkdown; window.deleteNote = deleteNote; window.moveNoteUp = moveNoteUp; window.moveNoteDown = moveNoteDown; window.openEditModal = openEditModal; window.toggleTheme = toggleTheme; window.applyTheme = applyTheme; // Expose a function to re-render UI parts that depend on translated text function updateDynamicUIText() { applyTheme(getStoredTheme() || "system"); updateNotesList(); // If slash menu is open, redraw it if (slashCommandState.active) showSlashMenu(document.getElementById("noteInput"), slashCommandState.query); } window.updateDynamicUIText = updateDynamicUIText; document.addEventListener("DOMContentLoaded", () => { const newNoteBtn = document.getElementById("newNoteButton"); if (newNoteBtn) newNoteBtn.addEventListener("click", addNewNote); const themeBtn = document.getElementById("themeToggle"); if (themeBtn) themeBtn.addEventListener("click", toggleTheme); const toolbar = document.getElementById("editorToolbar"); const textarea = document.getElementById("noteInput"); const preview = document.getElementById("notePreview"); if (preview) { preview.classList.add("hidden"); preview.setAttribute("aria-hidden", "true"); } if (toolbar && textarea) attachToolbar(toolbar, textarea, preview); if (textarea) { textarea.addEventListener("input", () => { if ( window.currentNoteIndex != null && Array.isArray(window.notes) && window.notes[window.currentNoteIndex] ) { const idx = window.currentNoteIndex; window.notes[idx].content = textarea.value; window.notes[idx].modified_at = new Date().toISOString(); try { updateNotesList(); } catch (e) {} scheduleAutosave(); } if (preview && !preview.classList.contains("hidden")) { scheduleRender(textarea, preview); } // Handle slash commands const text = textarea.value; const pos = textarea.selectionStart; const lastSlashPos = text.lastIndexOf("/", pos - 1); if (lastSlashPos === -1 || text[lastSlashPos] === "\n") { hideSlashMenu(); } else { const query = text.substring(lastSlashPos, pos); const beforeSlash = lastSlashPos === 0 || text[lastSlashPos - 1] === "\n"; if (query.length > 0 && beforeSlash) { showSlashMenu(textarea, query); } else { hideSlashMenu(); } } }); textarea.addEventListener("keydown", (e) => { // Handle slash command navigation if (slashCommandState.active) { if (e.key === "Escape") { e.preventDefault(); hideSlashMenu(); return; } else if (e.key === "ArrowDown") { e.preventDefault(); slashCommandState.selectedIndex = (slashCommandState.selectedIndex + 1) % slashCommandState.filteredCommands.length; updateSlashMenuSelection(); return; } else if (e.key === "ArrowUp") { e.preventDefault(); slashCommandState.selectedIndex = (slashCommandState.selectedIndex - 1 + slashCommandState.filteredCommands.length) % slashCommandState.filteredCommands.length; updateSlashMenuSelection(); return; } else if (e.key === "Enter" || e.key === "Tab") { e.preventDefault(); selectSlashCommand(textarea, slashCommandState.selectedIndex); return; } } // Handle keyboard shortcuts if ((e.ctrlKey || e.metaKey) && !e.shiftKey) { const k = (e.key || "").toLowerCase(); if (k === "b") { e.preventDefault(); wrapSelection(textarea, "**", "**"); } if (k === "i") { e.preventDefault(); wrapSelection(textarea, "*", "*"); } if (k === "`") { e.preventDefault(); wrapSelection(textarea, "`", "`"); } } }); } const titleInput = document.getElementById("noteTitle"); if (titleInput) { titleInput.addEventListener("input", () => { if ( window.currentNoteIndex != null && Array.isArray(window.notes) && window.notes[window.currentNoteIndex] ) { const idx = window.currentNoteIndex; window.notes[idx].name = titleInput.value; window.notes[idx].modified_at = new Date().toISOString(); try { updateNotesList(); } catch (e) {} scheduleAutosave(); } }); } const tagsInput = document.getElementById("noteTags"); if (tagsInput) { // create suggestions container let tagSug = document.createElement("div"); tagSug.className = "suggestions hidden"; tagSug.id = "tagSuggestions"; tagSug.style.minWidth = "220px"; tagsInput.parentNode?.appendChild(tagSug); let tagSelected = -1; function getUniqueTags() { const all = (window.notes || []).flatMap(n => (n.tags || [])); return Array.from(new Set(all)).filter(t => t && t.trim()).sort((a,b)=>a.localeCompare(b)); } function showTagSuggestionsFor(prefix) { const list = getUniqueTags().filter(t => t.toLowerCase().startsWith(prefix.toLowerCase()) ); const container = document.getElementById("tagSuggestions"); if (!container) return; container.innerHTML = ""; tagSelected = -1; if (!list.length) { container.classList.add("hidden"); return; } list.forEach((t, i)=>{ const it = document.createElement("div"); it.className = "suggestion-item"; it.textContent = t; it.dataset.value = t; it.addEventListener("mousedown", (ev)=>{ ev.preventDefault(); // prevent blur applyTagSuggestion(t); }); container.appendChild(it); }); container.classList.remove("hidden"); } function hideTagSuggestions() { const c = document.getElementById("tagSuggestions"); if (c) c.classList.add("hidden"); tagSelected = -1; } function applyTagSuggestion(value) { const el = tagsInput; const cur = el.value; const parts = cur.split(","); const last = parts.pop() || ""; const prefix = last.includes(" ") ? last : last; // keep simple parts.push(" " + value.trim()); const newVal = parts.map(p=>p.trim()).filter(p=>p).join(", "); el.value = newVal; el.dispatchEvent(new Event("input", { bubbles: true })); hideTagSuggestions(); el.focus(); } tagsInput.addEventListener("input", (e)=>{ const v = tagsInput.value; const parts = v.split(","); const last = parts.pop() || ""; const q = last.trim(); if (q.length === 0) { hideTagSuggestions(); return; } showTagSuggestionsFor(q); // update stored tags on change (existing behavior) if ( window.currentNoteIndex != null && Array.isArray(window.notes) && window.notes[window.currentNoteIndex] ) { const idx = window.currentNoteIndex; window.notes[idx].tags = tagsInput.value .split(",") .map(t => t.trim()) .filter(t => t.length > 0); window.notes[idx].modified_at = new Date().toISOString(); try { updateNotesList(); } catch (e) {} scheduleAutosave(); } }); tagsInput.addEventListener("keydown", (e)=>{ const container = document.getElementById("tagSuggestions"); if (!container || container.classList.contains("hidden")) return; const items = Array.from(container.querySelectorAll('.suggestion-item')); if (e.key === 'ArrowDown') { e.preventDefault(); tagSelected = Math.min(items.length-1, tagSelected+1); updateTagActive(items); } else if (e.key === 'ArrowUp') { e.preventDefault(); tagSelected = Math.max(0, tagSelected-1); updateTagActive(items); } else if (e.key === 'Enter' || e.key === 'Tab') { e.preventDefault(); if (tagSelected >=0 && items[tagSelected]) applyTagSuggestion(items[tagSelected].dataset.value); } else if (e.key === 'Escape') { hideTagSuggestions(); } }); function updateTagActive(items){ items.forEach((it, idx)=>{ it.classList.toggle('active', idx === tagSelected); if (idx === tagSelected) it.scrollIntoView({block:'nearest'}); }); } tagsInput.addEventListener('blur', ()=>{ setTimeout(()=>hideTagSuggestions(), 150); }); } // Složka (ručně) const noteFolderInput = document.getElementById("noteFolder"); if (noteFolderInput) { noteFolderInput.addEventListener("input", () => { if ( window.currentNoteIndex != null && Array.isArray(window.notes) && window.notes[window.currentNoteIndex] ) { const idx = window.currentNoteIndex; window.notes[idx].folder = noteFolderInput.value.trim(); window.notes[idx].modified_at = new Date().toISOString(); try { updateNotesList(); } catch (e) {} scheduleAutosave(); } }); } const searchTagInput = document.getElementById("searchTagInput"); if (searchTagInput) { searchTagInput.addEventListener("input", () => { try { updateNotesList(); } catch (e) {} }); } const folderFilterEl = document.getElementById("folderFilter"); if (folderFilterEl) { folderFilterEl.addEventListener("change", () => { try { updateNotesList(); } catch (e) {} }); } // Close slash menu when clicking outside textarea document.addEventListener("click", (e) => { if (e.target !== textarea) { hideSlashMenu(); } }); initTheme(); loadNotes(); }); })();