(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", `Respektovat systém (aktuálně: ${currentSys})`, ); } }; 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 = "Respektovat systém"; if (theme === "dark") { icon = "🌙"; aria = "Tmavý režim (kliknutím přepnete)"; } else if (theme === "light") { icon = "☀️"; aria = "Světlý režim (kliknutím přepnete)"; } else { const cur = document.body.classList.contains("dark") ? "tmavý" : "světlý"; icon = "🌓"; aria = `Respektovat systém (aktuálně: ${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 = "Preview není dostupný (chybí knihovna)."; 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 (on && mainPreview && mainTextarea) { mainPreview.classList.remove("hidden"); mainPreview.setAttribute("aria-hidden", "false"); 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) { modalPreview.classList.remove("hidden"); modalPreview.setAttribute("aria-hidden", "false"); 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 = "Uloženo"; 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("Zadejte URL (např. https://example.com):", "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) {} } } 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) {} } } 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("Adresa obrázku (URL):", "https://"); if (!url) return; const alt = selected || prompt("Alt text (krátký popis):", ""); 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("Počet sloupců (např. 2):", "2"); if (!colsStr) return; const rowsStr = prompt("Počet řádků (bez hlavičky, např. 2):", "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("Header " + 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); if (!preview) return; const isHidden = preview.classList.contains("hidden"); if (isHidden) { // show + render scheduleRender(textarea, preview, 0); preview.classList.remove("hidden"); preview.setAttribute("aria-hidden", "false"); } else { preview.classList.add("hidden"); preview.setAttribute("aria-hidden", "true"); } } 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; } }); }); // preview toggle button (if present) const previewBtn = toolbarEl.querySelector("#togglePreviewMain") || toolbarEl.querySelector("#togglePreviewModal"); if (previewBtn) { previewBtn.addEventListener("click", (e) => { e.preventDefault(); togglePreview(textareaEl.id, previewEl.id); }); } } // --- end markdown editor helpers --- function loadNotes() { if (nodeFS) { try { // Ensure file exists 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 || []; } updateNotesList(); if (window.currentNoteIndex != null) switchNotes(window.currentNoteIndex); return; } // Browser fallback: try localStorage first, then static JSON file try { const saved = localStorage.getItem("doxa_notes_v1"); if (saved) { const parsed = JSON.parse(saved); window.notes = parsed.notes || []; 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 || []; 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("Poznámky byly uloženy."); } catch (err) { alert("Chyba při ukládání souboru: " + (err.message || err)); } return; } // Browser fallback: save to localStorage 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; // Keep track of selected note id to restore selection after reorder const selectedId = window.notes[window.currentNoteIndex] ? window.notes[window.currentNoteIndex].id : null; // Remove item from array const item = window.notes.splice(fromIndex, 1)[0]; // Adjust insert position because we removed an earlier element 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); // restore selection by id (robust across reorders) 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; // toIndex = index + 2 will place after the next element (because of removal adjustment) reorderNotes(index, index + 2); } function deleteNote(index) { if (!window.notes || index < 0 || index >= window.notes.length) return; const note = window.notes[index]; if (!confirm(`Smazat poznámku "${note.name}"?`)) return; const selectedId = window.notes[window.currentNoteIndex] ? window.notes[window.currentNoteIndex].id : null; window.notes.splice(index, 1); // If selected note deleted, try to select neighbor if (selectedId != null && selectedId === note.id) { if (window.notes.length === 0) { window.currentNoteIndex = null; document.getElementById("noteTitle").value = ""; document.getElementById("noteInput").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 ) { // adjust index of current selection 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); // attach modal toolbar handlers (editor in modal) const modalToolbar = overlay.querySelector("#modalEditorToolbar"); const modalTextarea = overlay.querySelector("#modalNoteContent"); const modalPreview = overlay.querySelector("#modalNotePreview"); if (modalToolbar && modalTextarea) attachToolbar(modalToolbar, modalTextarea, modalPreview); // modal live toggle const modalLiveBtn = overlay.querySelector("#livePreviewToggleModal"); if (modalLiveBtn) { modalLiveBtn.addEventListener("click", () => { const cur = getStoredLive(); setStoredLive(!cur); applyLiveStateGlobally(); }); if (getStoredLive()) modalLiveBtn.classList.add("active"); } // modal title autosave 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", () => { // update model for modal current note 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, "`", "`"); } } }); } // events 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("Zadejte název poznámky."); 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(); }); // click outside modal to close 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"); // focus title 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 = ""; if (!window.notes || window.notes.length === 0) { const empty = document.createElement("div"); empty.textContent = 'Žádné poznámky. Klikněte na "➕ Nová poznámka" pro přidání.'; empty.className = "no-notes"; notesContainer.appendChild(empty); return; } window.notes.forEach((note, index) => { const el = document.createElement("div"); el.className = "note-item"; el.setAttribute("draggable", "true"); el.dataset.index = index; // left clickable area (switches to note) 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 || "Poznámka " + (index + 1); const preview = document.createElement("div"); preview.className = "note-preview"; preview.textContent = (note.content || "") .replace(/\n/g, " ") .slice(0, 80); left.appendChild(title); left.appendChild(preview); // controls const controls = document.createElement("div"); controls.className = "note-controls"; const upBtn = document.createElement("button"); upBtn.className = "note-btn move-up"; upBtn.title = "Přesunout nahoru"; upBtn.textContent = "↑"; upBtn.addEventListener("click", (e) => { e.stopPropagation(); moveNoteUp(index); }); const downBtn = document.createElement("button"); downBtn.className = "note-btn move-down"; downBtn.title = "Přesunout dolů"; downBtn.textContent = "↓"; downBtn.addEventListener("click", (e) => { e.stopPropagation(); moveNoteDown(index); }); const editBtn = document.createElement("button"); editBtn.className = "note-btn edit"; editBtn.title = "Upravit (modal)"; editBtn.textContent = "✎"; editBtn.addEventListener("click", (e) => { e.stopPropagation(); openEditModal(index); }); const delBtn = document.createElement("button"); delBtn.className = "note-btn delete"; delBtn.title = "Smazat poznámku"; 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); // mark active if (index === window.currentNoteIndex) el.classList.add("active"); // drag & drop handlers 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"); // cleanup any drag-over classes document .querySelectorAll(".note-item.drag-over") .forEach((n) => n.classList.remove("drag-over", "drag-after")); }); // show whether drop will be before or after based on mouse position 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); // bounds if (to < 0) to = 0; if (to > window.notes.length) to = window.notes.length; reorderNotes(from, to); }); notesContainer.appendChild(el); }); } 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"); if (titleEl) titleEl.value = note.name || ""; if (input) input.value = note.content || ""; updateNotesList(); } function addNewNote() { const defaultName = "Nová poznámka " + (window.notes && window.notes.length ? window.notes.length + 1 : 1); const name = prompt("Název nové poznámky:", defaultName); if (name === null) return; // user cancelled const newNote = { id: Date.now(), name: name.trim() || defaultName, content: "", created_at: new Date().toISOString(), }; window.notes = window.notes || []; window.notes.push(newNote); window.currentNoteIndex = window.notes.length - 1; saveNotesToFile(false); // persist but don't trigger download 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( 'Neplatný formát JSON. Očekávám pole nebo objekt s polem "notes".', ); return; } window.currentNoteIndex = window.notes.length ? 0 : null; saveNotesToFile(false); // persist (fs/localStorage) but don't trigger download updateNotesList(); if (window.currentNoteIndex !== null) switchNotes(window.currentNoteIndex); } catch (err) { alert("Chyba při čtení JSON: " + err.message); } }; reader.readAsText(file); }); input.click(); } function ulozPoznamku() { // compatibility with old button saveNotesToFile(true); } function saveCurrentNote() { const titleEl = document.getElementById("noteTitle"); const input = document.getElementById("noteInput"); const title = titleEl ? titleEl.value.trim() : ""; const content = input ? input.value : ""; if (!title) { alert("Zadejte název poznámky."); return; } if (window.currentNoteIndex == null) { // create new const newNote = { id: Date.now(), name: title, content, created_at: new Date().toISOString(), }; window.notes.push(newNote); window.currentNoteIndex = window.notes.length - 1; } else { // update existing const note = window.notes[window.currentNoteIndex]; note.name = title; note.content = content; note.modified_at = new Date().toISOString(); } saveNotesToFile(false); updateNotesList(); switchNotes(window.currentNoteIndex); } // Export functions to global scope so HTML onclick handlers work window.importujPoznamky = importujPoznamky; window.addNewNote = addNewNote; window.switchNotes = switchNotes; window.ulozPoznamku = ulozPoznamku; window.saveNotesToFile = saveNotesToFile; window.saveCurrentNote = saveCurrentNote; // Additional exported helpers window.deleteNote = deleteNote; window.moveNoteUp = moveNoteUp; window.moveNoteDown = moveNoteDown; window.openEditModal = openEditModal; window.toggleTheme = toggleTheme; window.applyTheme = applyTheme; 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); // attach main editor toolbar if present const toolbar = document.getElementById("editorToolbar"); const textarea = document.getElementById("noteInput"); const preview = document.getElementById("notePreview"); if (toolbar && textarea) attachToolbar(toolbar, textarea, preview); // live preview toggle const liveBtn = document.getElementById("livePreviewToggle"); if (liveBtn) { liveBtn.addEventListener("click", () => { const cur = getStoredLive(); setStoredLive(!cur); applyLiveStateGlobally(); }); } // apply initial state to main & modal if present applyLiveStateGlobally(); if (textarea) { // input handling: update preview and autosave current note textarea.addEventListener("input", () => { // update model for selected note 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(); // reflect small changes in the list try { updateNotesList(); } catch (e) {} scheduleAutosave(); } // live preview render if visible or live mode enabled const liveMode = getStoredLive(); if (preview && (!preview.classList.contains("hidden") || liveMode)) { if (preview.classList.contains("hidden")) { // show preview when live mode enabled preview.classList.remove("hidden"); preview.setAttribute("aria-hidden", "false"); } scheduleRender(textarea, preview); } }); textarea.addEventListener("keydown", (e) => { 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, "`", "`"); } } }); } // title input autosave 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(); } }); } initTheme(); loadNotes(); }); })();