-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathnotebook.html
More file actions
21 lines (15 loc) · 33.5 KB
/
Copy pathnotebook.html
File metadata and controls
21 lines (15 loc) · 33.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!doctype html><html lang=en><head><meta charset=UTF-8><meta name=viewport content="width=device-width,initial-scale=1,user-scalable=yes,maximum-scale=5"><title>📝 Notebook</title><style>*{margin:0;padding:0;box-sizing:border-box}body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;background:#f8fafc;color:#334155;height:100vh;overflow:hidden;padding-bottom:70px}.header{background:#fff;display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid #e2e8f0;box-shadow:0 1px 3px rgba(0,0,0,.1);z-index:50;min-height:60px}.header-left{display:flex;align-items:center;gap:12px;flex:1;min-width:0}.header-title{font-size:1.25rem;font-weight:600;color:#1e293b;margin:0;white-space:nowrap}.header-actions{display:flex;align-items:center;gap:8px;flex-wrap:nowrap}.btn-icon{display:flex;align-items:center;justify-content:center;padding:8px;border-radius:6px;min-width:36px;min-height:36px;font-size:1rem;cursor:pointer;transition:.2s;border:none}.encryption-btn{background:#fef3c7;color:#92400e;border:1px solid #f59e0b}.encryption-btn:hover{background:#fde68a}.encryption-btn.active{background:#065f46;color:#fff;border-color:#065f46}.btn{padding:8px 16px;border:none;border-radius:6px;font-size:14px;font-weight:500;cursor:pointer;transition:.2s;display:inline-flex;align-items:center;gap:6px}.btn-primary{background:#3b82f6;color:#fff}.btn-primary:hover{background:#2563eb}.btn-secondary{background:#f1f5f9;color:#475569}.btn-secondary:hover{background:#e2e8f0}.view{position:absolute;top:60px;left:0;right:0;bottom:70px;background:#fff;overflow-y:auto;-webkit-overflow-scrolling:touch;display:none}.view.active{display:block}.nav{position:fixed;bottom:0;left:0;right:0;background:#fff;border-top:1px solid #e2e8f0;z-index:100;box-shadow:0 -2px 8px rgba(0,0,0,.1)}.nav-tabs{display:flex;height:70px;max-width:600px;margin:0 auto}.nav-tab{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;background:0 0;border:none;color:#6b7280;font-size:.875rem;cursor:pointer;transition:.2s;gap:6px;padding:8px}.nav-tab.active{color:#3b82f6;background:#eff6ff}.nav-tab:hover{background:#f8fafc}.nav-icon{font-size:1.4rem}.editor{display:flex;flex-direction:column;height:100%}.editor-header{padding:16px;border-bottom:1px solid #f1f5f9;background:#fff;position:sticky;top:0;z-index:10}.note-title-display{font-size:1.1rem;font-weight:600;color:#1e293b;margin-bottom:12px}.tag-management{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.note-tags{display:flex;flex-wrap:wrap;gap:6px}.tag-item{display:inline-flex;align-items:center;gap:4px;background:#e0f2fe;color:#0369a1;padding:4px 8px;border-radius:12px;font-size:.8rem;border:1px solid #0284c7}.tag-remove{background:0 0;border:none;color:#0369a1;cursor:pointer;font-size:.9rem;padding:0;margin-left:2px;border-radius:50%;width:16px;height:16px;display:flex;align-items:center;justify-content:center}.tag-remove:hover{background:#0284c7;color:#fff}.tags-display{display:flex;flex-wrap:wrap;gap:4px;margin-top:8px}.tag-small{background:#e0f2fe;color:#0369a1;padding:2px 6px;border-radius:8px;font-size:.7rem;border:1px solid #0284c7}.add-tag-btn{background:#10b981;color:#fff;padding:2px 6px;border-radius:8px;font-size:.7rem;border:none;cursor:pointer;display:flex;align-items:center;gap:2px}.add-tag-btn:hover{background:#059669}.editor-toolbar{padding:8px 16px;border-bottom:1px solid #f1f5f9;background:#f8fafc;position:sticky;top:0;z-index:9;overflow-x:auto}.toolbar-scroll{display:flex;gap:4px;min-width:max-content;padding-bottom:4px}.toolbar-group{display:flex;gap:1px;padding:2px;border-radius:6px;background:#fff;border:1px solid #e2e8f0;flex-shrink:0}.toolbar-btn{padding:6px 8px;border:none;background:0 0;border-radius:4px;cursor:pointer;font-size:.8rem;transition:.2s;white-space:nowrap;min-width:32px;display:flex;align-items:center;justify-content:center}.toolbar-btn:hover{background:#f1f5f9;color:#3b82f6}.toolbar-btn.active{background:#3b82f6;color:#fff}.editor-content{flex:1;padding:16px;overflow-y:auto}.editor-textarea{min-height:calc(100vh - 300px);outline:0;line-height:1.6;font-size:1rem;word-wrap:break-word;width:100%;border:none;background:0 0;resize:none}.editor-textarea:empty::before{content:"Start writing your note...";color:#9ca3af}.status-bar{padding:12px 16px;border-top:1px solid #f1f5f9;background:#f8fafc;display:flex;justify-content:space-between;align-items:center;font-size:.8rem;color:#6b7280;position:sticky;bottom:0;z-index:10}.notes-controls{padding:16px;border-bottom:1px solid #e2e8f0;background:#fff;position:sticky;top:0;z-index:10}.search-section{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.search-input{flex:1;min-width:200px;padding:8px 12px;border:1px solid #d1d5db;border-radius:6px;font-size:14px;outline:0;transition:border-color .2s}.search-input:focus{border-color:#3b82f6;box-shadow:0 0 0 3px rgba(59,130,246,.1)}.filter-select{padding:8px 12px;border:1px solid #d1d5db;border-radius:6px;font-size:14px;background:#fff;outline:0;min-width:120px;cursor:pointer}.filter-select:focus{border-color:#3b82f6;box-shadow:0 0 0 3px rgba(59,130,246,.1)}.notes-list{padding:8px 0}.note-item{position:relative;padding:16px;border-bottom:1px solid #f1f5f9;cursor:pointer;transition:background-color .2s}.note-item:active,.note-item:hover{background:#f8fafc}.note-item.active{background:#eff6ff;border-left:4px solid #3b82f6;padding-left:12px}.note-content{padding-right:40px}.note-title{font-size:1rem;font-weight:600;color:#1e293b;margin-bottom:8px;line-height:1.4}.note-preview{font-size:.875rem;color:#6b7280;line-height:1.4;margin-bottom:8px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.note-meta{display:flex;justify-content:space-between;align-items:center;font-size:.75rem;color:#9ca3af}.note-delete{position:absolute;top:12px;right:12px;opacity:1;background:#fef2f2;border:none;color:#dc2626;cursor:pointer;padding:8px;border-radius:4px;font-size:1rem;transition:.2s;z-index:10}.note-delete:hover{background:#fecaca;color:#b91c1c;transform:scale(1.05)}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;height:300px;text-align:center;color:#6b7280;padding:40px 20px}.empty-icon{font-size:3rem;margin-bottom:16px;opacity:.5}.auto-save-indicator{display:flex;align-items:center;gap:6px}.save-dot{width:6px;height:6px;border-radius:50%;background:#10b981}.save-dot.saving{background:#f59e0b;animation:1s infinite pulse}@keyframes pulse{0%,100%{opacity:1}50%{opacity:.5}}.menu-container{position:relative}.menu-dropdown{position:absolute;top:100%;right:0;background:#fff;border:1px solid #e2e8f0;border-radius:8px;box-shadow:0 4px 12px rgba(0,0,0,.15);z-index:1000;min-width:200px;padding:8px 0;margin-top:4px;display:none}.menu-dropdown.show{display:block}.menu-item{display:flex;align-items:center;gap:8px;width:100%;padding:12px 16px;border:none;background:0 0;text-align:left;font-size:14px;color:#374151;cursor:pointer;transition:background-color .2s}.menu-item:hover{background:#f3f4f6}.menu-separator{height:1px;background:#e5e7eb;margin:4px 0}.modal-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,.5);display:none;align-items:center;justify-content:center;z-index:1000;backdrop-filter:blur(2px)}.modal-content{background:#fff;border-radius:12px;max-width:500px;width:90vw;max-height:80vh;overflow-y:auto;box-shadow:0 20px 25px -5px rgba(0,0,0,.1)}.modal-header{padding:24px 24px 16px;border-bottom:1px solid #e2e8f0;display:flex;align-items:center;justify-content:space-between}.modal-header h2{margin:0;font-size:1.5rem;font-weight:600;color:#1e293b}.modal-close{background:0 0;border:none;font-size:24px;cursor:pointer;color:#6b7280;padding:4px;border-radius:6px;width:32px;height:32px;display:flex;align-items:center;justify-content:center}.modal-close:hover{background:#f1f5f9;color:#374151}.modal-body{padding:24px;line-height:1.6}@media (max-width:768px){.header{padding:10px 16px}.header-title{font-size:1.1rem}.header-left{gap:8px}.header-actions{gap:6px}.btn-icon{min-width:32px;min-height:32px;padding:6px;font-size:.9rem}.search-section{flex-direction:column;align-items:stretch}.filter-select,.search-input{min-width:unset}}@media (max-width:480px){.header{padding:8px 12px}.header-title{font-size:1rem}.header-left{gap:6px}.header-actions{gap:4px}.btn-icon{min-width:28px;min-height:28px;padding:4px;font-size:.8rem}.notes-controls{padding:12px}}</style></head><body><div class=header><div class=header-left><h1 class=header-title>📝 Notebook</h1></div><div class=header-actions><button class="btn-icon encryption-btn" onclick=toggleEncryption() id=encryptionBtn title="Toggle encryption">🔒</button><div class=menu-container><button class="btn-icon btn-secondary" onclick=toggleMenu() title=Menu>⚙️</button><div class=menu-dropdown id=menuDropdown><button class=menu-item onclick=checkForUpdates()>🔄 Check for Updates</button><div class=menu-separator></div><button class=menu-item onclick=exportNotes()>📤 Export Notes</button> <button class=menu-item onclick=importNotes()>📥 Import Notes</button><div class=menu-separator></div><button class=menu-item onclick=showAbout()>ℹ️ About</button></div></div><button class="btn-icon btn-primary" onclick=downloadUpdatedFile() title="Save file">💾</button></div></div><input type=checkbox id=encryptionEnabled style=display:none><div class="view active" id=EditorView><div class=editor><div class=editor-header><div class=note-title-display id=noteTitleDisplay>Select a note to edit</div><div class=tag-management><label for=tagSelect style=font-size:.9rem;color:#6b7280;margin-right:8px>Tags:</label> <select class="btn btn-secondary" id=tagSelect onchange=handleTagChange()><option value="">Add tag...</option></select><div class=note-tags id=noteTags></div></div></div><div class=editor-toolbar><div class=toolbar-scroll><div class=toolbar-group><button class=toolbar-btn onclick="ToggleFormat('bold')" data-format=bold title=Bold><strong>B</strong></button> <button class=toolbar-btn onclick="ToggleFormat('italic')" data-format=italic title=Italic><em>I</em></button> <button class=toolbar-btn onclick="ToggleFormat('underline')" data-format=underline title=Underline><u>U</u></button> <button class=toolbar-btn onclick="ToggleFormat('strikeThrough')" data-format=strikeThrough title=Strikethrough><s>S</s></button></div><div class=toolbar-group><button class=toolbar-btn onclick="FormatHeading('h1')" data-format=h1 title="Heading 1">H1</button> <button class=toolbar-btn onclick="FormatHeading('h2')" data-format=h2 title="Heading 2">H2</button> <button class=toolbar-btn onclick="FormatHeading('h3')" data-format=h3 title="Heading 3">H3</button> <button class=toolbar-btn onclick="FormatHeading('p')" data-format=p title=Paragraph>P</button></div><div class=toolbar-group><button class=toolbar-btn onclick="ToggleFormat('insertUnorderedList')" data-format=insertUnorderedList title="Bullet List">• List</button> <button class=toolbar-btn onclick="ToggleFormat('insertOrderedList')" data-format=insertOrderedList title="Numbered List">1. List</button> <button class=toolbar-btn onclick=ToggleCodeFormat() data-format=code title=Code></></button> <button class=toolbar-btn onclick=ToggleQuoteFormat() data-format=quote title=Quote>❝</button></div><div class=toolbar-group><button class=toolbar-btn onclick=CreateLink() data-format=createLink title="Insert Link">🔗</button> <button class=toolbar-btn onclick=InsertImage() title="Insert Image">🖼</button> <button class=toolbar-btn onclick=InsertYouTubeVideo() title="Insert YouTube Video">📹</button> <button class=toolbar-btn onclick=InsertTable() title="Insert Table">📊</button></div><div class=toolbar-group><button class=toolbar-btn onclick=ClearFormatting() title="Clear Formatting">🧹</button> <button class=toolbar-btn onclick=InsertLineBreak() title="Insert Line Break">↵</button></div></div></div><div class=editor-content><div class=editor-textarea contenteditable=true id=Editor></div></div><div class=status-bar><div class=auto-save-indicator><div class=save-dot id=SaveDot></div><span id=SaveStatus>All changes saved</span></div><div id=WordCount>0 words</div></div></div></div><div class=view id=NotesView><div class=notes-controls><div class=search-section><input class=search-input id=searchInput placeholder="Search notes..." onkeyup=searchNotes()> <select class=filter-select id=tagFilter onchange=filterNotesByTag()><option value="">All Tags</option></select> <button class="btn btn-primary" onclick=createNote()>📝 New Note</button></div></div><div class=notes-list id=NotesList></div></div><div class=nav><div class=nav-tabs><button class="nav-tab active" onclick="showView('editor')" data-view=editor><div class=nav-icon>✏️</div><div>Editor</div></button> <button class=nav-tab onclick="showView('notes')" data-view=notes><div class=nav-icon>📄</div><div>Notes</div></button></div></div><div class=modal-overlay id=aboutModal><div class=modal-content><div class=modal-header><h2>About Portable Notebook</h2><button class=modal-close onclick=closeAboutModal()>×</button></div><div class=modal-body id=aboutModalBody></div></div></div><script>// NOTES DATA - DO NOT EDIT MANUALLY
let NOTES_DATA={17346372e5:{title:"Welcome to Notebook! 👋",content:'<h2>Getting Started</h2><p>Welcome to your new note-taking workspace! Here\'s how to get started:</p><ul><li><strong>Create notes:</strong> Click the "New Note" button</li><li><strong>Organize:</strong> Use tags to organize your notes</li><li><strong>Format:</strong> Use the toolbar to style your text</li><li><strong>Encrypt:</strong> Enable notebook encryption for privacy</li></ul><p>Your notes are automatically saved as you type and work completely offline. Happy note-taking! ✨</p>',lastModified:"2024-12-20T00:00:00.000Z",encrypted:!1,tags:["welcome","getting-started"]}};let TAGS_DATA=["welcome","getting-started"];
// END NOTES DATA
const REPO_BASE_URL="https://github.com/TolinSimpson/Portable-Notebook";const CURRENT_VERSION="1.0.0";let notes=NOTES_DATA,tags=TAGS_DATA,currentNote=null,autoSaveTimer=null,pendingChanges=!1,encryptionEnabled=!1,encryptionKey=null;function updateAllViews(){updateNotesList();updateTagSelect();updateNoteDisplay()}async function init(){const e=Object.values(notes).some(e=>e.encrypted);if(e){encryptionEnabled=!0;document.getElementById("encryptionEnabled").checked=!0;document.getElementById("encryptionBtn").classList.add("active");document.getElementById("encryptionBtn").title="Encryption enabled - click to disable";try{const o=prompt("This notebook is encrypted. Enter your password to access it:");if(!o)return alert("Password required to access this notebook."),void 0;await unlockNotebook(o)}catch(e){alert("Invalid password or corrupted data. Cannot access notebook.");return}}showView("editor");updateTagSelect();updateNotesList();setupEventListeners();const t=Object.keys(notes)[0];t&&SelectNote(t)}function setupEventListeners(){const e=document.getElementById("Editor");e.addEventListener("input",()=>{UpdateWordCount();ScheduleAutoSave()});e.addEventListener("keyup",UpdateToolbarState);e.addEventListener("mouseup",UpdateToolbarState);document.addEventListener("click",e=>{const t=document.querySelector(".menu-container"),o=document.getElementById("menuDropdown");t.contains(e.target)||o.classList.remove("show")})}function ScheduleAutoSave(){setSaveStatus("saving");pendingChanges=!0;clearTimeout(autoSaveTimer);autoSaveTimer=setTimeout(()=>{if(currentNote){saveCurrentNote();setSaveStatus("pending")}},1e3)}function setSaveStatus(e){const t=document.getElementById("SaveDot"),o=document.getElementById("SaveStatus");if("saving"===e){t.classList.add("saving");o.innerHTML="Saving..."}else if("pending"===e){t.classList.remove("saving");t.style.background="#f59e0b";o.innerHTML='Changes ready - <button onclick="downloadUpdatedFile()" style="background:none;border:none;color:#3b82f6;cursor:pointer;text-decoration:underline;">Download File</button>'}else{t.classList.remove("saving");t.style.background="#10b981";o.textContent="All changes saved"}}function UpdateWordCount(){const e=document.getElementById("Editor").textContent||"";const t=e.trim()?e.trim().split(/\s+/).length:0;document.getElementById("WordCount").textContent=t+" word"+(1!==t?"s":"")}async function downloadUpdatedFile(){let notesToSave=JSON.parse(JSON.stringify(notes));if(encryptionEnabled&&encryptionKey)for(var[e,t]of Object.entries(notesToSave))t.encrypted&&(t.content=await encryptText(t.content,encryptionKey));const notesDataRegex=/\/\/ NOTES DATA - DO NOT EDIT MANUALLY[\s\S]*?\/\/ END NOTES DATA/;const newNotesData=`// NOTES DATA - DO NOT EDIT MANUALLY
let NOTES_DATA = ${JSON.stringify(notesToSave)};
let TAGS_DATA = ${JSON.stringify(tags)};
// END NOTES DATA`;const htmlContent=document.documentElement.outerHTML.replace(notesDataRegex,newNotesData);const o=new Blob([htmlContent],{type:"text/html"}),n=URL.createObjectURL(o),a=document.createElement("a");a.href=n;a.download="notebook.html";a.setAttribute("download","notebook.html");document.body.appendChild(a);a.click();document.body.removeChild(a);URL.revokeObjectURL(n);pendingChanges=!1;setSaveStatus("saved")}async function saveCurrentNote(){if(currentNote&¬es[currentNote]){const e=notes[currentNote];notes[currentNote]={...e,content:document.getElementById("Editor").innerHTML,lastModified:(new Date).toISOString(),encrypted:encryptionEnabled};updateNotesList()}}function showView(e){document.querySelectorAll(".nav-tab").forEach(e=>e.classList.remove("active"));document.querySelector(`[data-view="${e}"]`).classList.add("active");document.querySelectorAll(".view").forEach(e=>e.classList.remove("active"));document.getElementById(e.charAt(0).toUpperCase()+e.slice(1)+"View").classList.add("active");if("notes"===e){updateNotesList();updateTagFilter()}}function updateNotesList(){const e=document.getElementById("NotesList");const t=document.getElementById("searchInput");const o=document.getElementById("tagFilter");const a=t?t.value.toLowerCase():"";const n=o?o.value:"";let s=Object.entries(notes).map(([e,t])=>({id:e,note:t}));a&&(s=s.filter(({note:e})=>{const t=(e.title||"").toLowerCase();const o=e.content.replace(/<[^>]*>/g,"").toLowerCase();const n=(e.tags||[]).join(" ").toLowerCase();return t.includes(a)||o.includes(a)||n.includes(a)}));n&&(s=s.filter(({note:e})=>(e.tags||[]).includes(n)));s.sort((e,t)=>new Date(t.note.lastModified)-new Date(e.note.lastModified));e.innerHTML=s.length?s.map(({id:e,note:t})=>{const o=t.title||"Untitled Note",n=t.content.replace(/<[^>]*>/g,""),a=n.substring(0,100),s=n.trim()?n.trim().split(/\s+/).length:0,c=new Date(t.lastModified).toLocaleDateString(),r=t.tags||[];const i=r.map(e=>`<span class="tag-small">${e}</span>`).join("")+`<button class="add-tag-btn" onclick="event.stopPropagation();promptAddTag('${e}')" title="Add tag">+ Tag</button>`;return`<div class="note-item ${e===currentNote?"active":""}" onclick="SelectNote('${e}')"><button class="note-delete" onclick="event.stopPropagation();deleteNote('${e}')" title="Delete note">🗑️</button><div class="note-content"><div class="note-title">${o}</div><div class="note-preview">${a}${100<=a.length?"...":""}</div><div class="note-meta"><span>${c}</span><span>${s} word${1!==s?"s":""}</span></div><div class="tags-display">${i}</div></div></div>`}).join(""):'<div class="empty-state"><div class="empty-icon">📝</div><p>No notes yet</p></div>'}function updateTagSelect(){const e=document.getElementById("tagSelect");e&&(e.innerHTML='<option value="">Add tag...</option>'+tags.map(e=>`<option value="${e}">${e}</option>`).join(""))}function updateTagFilter(){const e=document.getElementById("tagFilter");e&&(e.innerHTML='<option value="">All Tags</option>'+tags.map(e=>`<option value="${e}">${e}</option>`).join(""))}function searchNotes(){updateNotesList()}function filterNotesByTag(){updateNotesList()}function handleTagChange(){const e=document.getElementById("tagSelect");const t=e.value;if(t&¤tNote){addTagToNote(currentNote,t);e.value="";updateNoteDisplay();updateNotesList();ScheduleAutoSave()}}function addTagToNote(e,t){if(notes[e]){notes[e].tags||(notes[e].tags=[]);if(!notes[e].tags.includes(t)){notes[e].tags.push(t);if(!tags.includes(t)){tags.push(t);updateTagSelect();updateTagFilter()}}}}function removeTagFromNote(e,t){if(notes[e]&¬es[e].tags){notes[e].tags=notes[e].tags.filter(e=>e!==t);updateNoteDisplay();updateNotesList();ScheduleAutoSave()}}function updateNoteDisplay(){const e=document.getElementById("noteTitleDisplay");const t=document.getElementById("noteTags");if(currentNote&¬es[currentNote]){e.textContent=notes[currentNote].title||"Untitled Note";const o=notes[currentNote].tags||[];t.innerHTML=o.map(e=>`<span class="tag-item">${e}<button class="tag-remove" onclick="removeTagFromNote('${currentNote}','${e}')" title="Remove tag">×</button></span>`).join("")}else{e.textContent="Select a note to edit";t.innerHTML=""}}function promptAddTag(e){const t=prompt("Enter tag name:");if(t&&t.trim()){addTagToNote(e,t.trim());updateNotesList();ScheduleAutoSave()}}function deleteNote(e){if(confirm("Delete this note? This cannot be undone.")){delete notes[e];if(currentNote===e){const t=Object.keys(notes);if(0<t.length)SelectNote(t[0]);else{currentNote=null;document.getElementById("Editor").innerHTML="";updateNoteDisplay()}}updateNotesList();ScheduleAutoSave()}}function SelectNote(e){if(notes[e]){const t=notes[e];currentNote=e;document.getElementById("Editor").innerHTML=t.content;updateAllViews();UpdateWordCount();pendingChanges||setSaveStatus("saved");showView("editor")}}function createNote(){const e=prompt("Enter note title:");if(e&&e.trim()){const t=Date.now().toString();notes[t]={title:e.trim(),content:"",lastModified:(new Date).toISOString(),encrypted:encryptionEnabled,tags:[]};SelectNote(t);updateNotesList();ScheduleAutoSave()}}function ToggleFormat(e){document.getElementById("Editor").focus();document.execCommand(e,!1,null);UpdateToolbarState();ScheduleAutoSave()}function FormatHeading(e){document.getElementById("Editor").focus();document.execCommand("formatBlock",!1,e);UpdateToolbarState();ScheduleAutoSave()}function CreateLink(){document.getElementById("Editor").focus();const e=window.getSelection();if(0<e.rangeCount){const o=e.getRangeAt(0);const n=e.toString();let t=null;if(n){const a=document.createElement("div");a.appendChild(o.cloneContents());t=a.querySelector("a");if(!t){let e=o.commonAncestorContainer;e.nodeType===Node.TEXT_NODE&&(e=e.parentNode);t=e.closest("a")}}else{let e=o.commonAncestorContainer;e.nodeType===Node.TEXT_NODE&&(e=e.parentNode);t=e.closest("a")}if(t)if(n&&t.textContent!==n){const s=t.textContent;const c=document.createTextNode(s);t.parentNode.replaceChild(c,t)}else{const r=t.textContent;const i=document.createTextNode(r);t.parentNode.replaceChild(i,t)}else if(n){const d=prompt("Enter URL:","https://");d&&"https://"!==d&&document.execCommand("insertHTML",!1,`<a href="${d}" target="_blank" rel="noopener noreferrer">${n}</a>`)}else{const l=prompt("Enter URL:","https://");if(l&&"https://"!==l){const u=prompt("Enter link text:");u&&document.execCommand("insertHTML",!1,`<a href="${l}" target="_blank" rel="noopener noreferrer">${u}</a>`)}}}UpdateToolbarState();ScheduleAutoSave()}function InsertImage(){const e=prompt("Enter image URL:","https://");if(e&&"https://"!==e){document.getElementById("Editor").focus();document.execCommand("insertHTML",!1,`<img src="${e}" style="max-width:100%;height:auto;border-radius:4px;margin:8px 0;" alt="Image">`);ScheduleAutoSave()}}function ToggleCodeFormat(){document.getElementById("Editor").focus();const t=window.getSelection();if(0<t.rangeCount){const o=t.getRangeAt(0);const n=t.toString();let e=o.commonAncestorContainer;e.nodeType===Node.TEXT_NODE&&(e=e.parentNode);const a=e.closest("code, pre");if(a){const s=a.textContent;const c=document.createTextNode(s);a.parentNode.replaceChild(c,a);const r=document.createRange();r.selectNodeContents(c);t.removeAllRanges();t.addRange(r)}else if(n){const i=n.includes("\n")||confirm("Create code block? (Cancel for inline code)");const d=i?`<pre style="background:#f4f4f4;padding:12px;border-radius:4px;border-left:4px solid #3b82f6;margin:12px 0;"><code>${n.replace(/</g,"<").replace(/>/g,">")}</code></pre>`:`<code style="background:#f1f5f9;padding:2px 4px;border-radius:3px;font-family:Monaco,monospace;">${n.replace(/</g,"<").replace(/>/g,">")}</code>`;document.execCommand("insertHTML",!1,d)}else{const l=prompt("Enter code:");if(l){const u=l.includes("\n")?`<pre style="background:#f4f4f4;padding:12px;border-radius:4px;border-left:4px solid #3b82f6;margin:12px 0;"><code>${l.replace(/</g,"<").replace(/>/g,">")}</code></pre>`:`<code style="background:#f1f5f9;padding:2px 4px;border-radius:3px;font-family:Monaco,monospace;">${l.replace(/</g,"<").replace(/>/g,">")}</code>`;document.execCommand("insertHTML",!1,u)}}}UpdateToolbarState();ScheduleAutoSave()}function ToggleQuoteFormat(){document.getElementById("Editor").focus();const t=window.getSelection();if(0<t.rangeCount){const o=t.getRangeAt(0);const n=t.toString();let e=o.commonAncestorContainer;e.nodeType===Node.TEXT_NODE&&(e=e.parentNode);const a=e.closest("blockquote");if(a){const s=a.textContent;const c=document.createTextNode(s);a.parentNode.replaceChild(c,a);const r=document.createRange();r.selectNodeContents(c);t.removeAllRanges();t.addRange(r)}else if(n)document.execCommand("insertHTML",!1,`<blockquote style="border-left:4px solid #3b82f6;padding:12px 16px;margin:16px 0;font-style:italic;color:#6b7280;background:#f8fafc;">${n}</blockquote>`);else{const i=prompt("Enter quote:");i&&document.execCommand("insertHTML",!1,`<blockquote style="border-left:4px solid #3b82f6;padding:12px 16px;margin:16px 0;font-style:italic;color:#6b7280;background:#f8fafc;">${i}</blockquote>`)}UpdateToolbarState();ScheduleAutoSave()}}function InsertYouTubeVideo(){const e=prompt("Enter YouTube URL:","https://www.youtube.com/watch?v=");if(e&&"https://www.youtube.com/watch?v="!==e){const t=e.match(/(?:youtube\.com\/watch\?v=|youtu\.be\/)([^&\n?#]+)/);if(t){document.getElementById("Editor").focus();document.execCommand("insertHTML",!1,`<div style="position:relative;width:100%;max-width:560px;height:0;padding-bottom:31.5%;margin:16px auto;"><iframe src="https://www.youtube-nocookie.com/embed/${t[1]}" style="position:absolute;top:0;left:0;width:100%;height:100%;border:none;" allowfullscreen></iframe></div>`);ScheduleAutoSave()}else alert("Invalid YouTube URL.")}}function InsertTable(){const e=parseInt(prompt("Rows:","3")),n=parseInt(prompt("Columns:","3"));if(e&&n&&e<=20&&n<=10){let o='<table style="border-collapse:collapse;width:100%;margin:16px 0;border:1px solid #e2e8f0;">';for(let t=0;t<e;t++){o+="<tr>";for(let e=0;e<n;e++){const a=0===t;o+=`<${a?"th":"td"} style="border:1px solid #e2e8f0;padding:12px;${a?"background:#f8fafc;font-weight:600;":""}}">${a?"Header "+(e+1):""}</${a?"th":"td"}>`}o+="</tr>"}o+="</table>";document.getElementById("Editor").focus();document.execCommand("insertHTML",!1,o);ScheduleAutoSave()}}function ClearFormatting(){const e=document.getElementById("Editor");e.focus();const t=window.getSelection();if(t.isCollapsed){const o=document.createRange();o.selectNodeContents(e);t.removeAllRanges();t.addRange(o)}document.execCommand("removeFormat");document.execCommand("formatBlock",!1,"p");UpdateToolbarState();ScheduleAutoSave()}function InsertLineBreak(){const e=document.getElementById("Editor");e.focus();document.execCommand("insertHTML",!1,"<br>");ScheduleAutoSave()}function UpdateToolbarState(){document.querySelectorAll(".toolbar-btn[data-format]").forEach(t=>{t.classList.remove("active");const e=t.getAttribute("data-format");if(["h1","h2","h3","p"].includes(e)){const o=document.queryCommandValue("formatBlock").toLowerCase();o!==e&&("p"!==e||o)||t.classList.add("active")}else if(["bold","italic","underline","strikeThrough"].includes(e))document.queryCommandState(e)&&t.classList.add("active");else if(["insertUnorderedList","insertOrderedList"].includes(e))document.queryCommandState(e)&&t.classList.add("active");else if("code"===e){const n=window.getSelection();if(0<n.rangeCount){let e=n.getRangeAt(0).commonAncestorContainer;e.nodeType===Node.TEXT_NODE&&(e=e.parentNode);e.closest("code, pre")&&t.classList.add("active")}}else if("quote"===e){const a=window.getSelection();if(0<a.rangeCount){let e=a.getRangeAt(0).commonAncestorContainer;e.nodeType===Node.TEXT_NODE&&(e=e.parentNode);e.closest("blockquote")&&t.classList.add("active")}}else if("createLink"===e){const s=window.getSelection();if(0<s.rangeCount){let e=s.getRangeAt(0).commonAncestorContainer;e.nodeType===Node.TEXT_NODE&&(e=e.parentNode);e.closest("a")&&t.classList.add("active")}}})}async function deriveKey(e,t){const o=new TextEncoder;const n=await crypto.subtle.importKey("raw",o.encode(e),{name:"PBKDF2"},!1,["deriveBits","deriveKey"]);return crypto.subtle.deriveKey({name:"PBKDF2",salt:t,iterations:1e5,hash:"SHA-256"},n,{name:"AES-GCM",length:256},!1,["encrypt","decrypt"])}async function encryptText(e,t){const o=new TextEncoder;const n=crypto.getRandomValues(new Uint8Array(16));const a=crypto.getRandomValues(new Uint8Array(12));const s=await deriveKey(t,n);const c=await crypto.subtle.encrypt({name:"AES-GCM",iv:a},s,o.encode(e));const r=new Uint8Array(n.length+a.length+c.byteLength);r.set(n,0);r.set(a,n.length);r.set(new Uint8Array(c),n.length+a.length);return btoa(String.fromCharCode.apply(null,r))}async function decryptText(e,t){try{const o=new Uint8Array(atob(e).split("").map(e=>e.charCodeAt(0)));const n=o.slice(0,16);const a=o.slice(16,28);const s=o.slice(28);const c=await deriveKey(t,n);const r=await crypto.subtle.decrypt({name:"AES-GCM",iv:a},c,s);return(new TextDecoder).decode(r)}catch(e){throw new Error("Decryption failed")}}async function unlockNotebook(e){for(var[t,o]of Object.entries(notes))if(o.encrypted){o.content=await decryptText(o.content,e);o.encrypted=!1}encryptionKey=e}async function toggleEncryption(){const e=document.getElementById("encryptionEnabled");const t=document.getElementById("encryptionBtn");encryptionEnabled;if(encryptionEnabled){if(confirm("Disable encryption? This removes password protection.")){encryptionEnabled=!1;e.checked=!1;t.classList.remove("active");t.title="Toggle encryption";for(var o of Object.values(notes))o.encrypted=!1;encryptionKey=null;ScheduleAutoSave();alert("Encryption disabled.")}}else{const a=prompt("Enter password for notebook:");if(!a||a.length<4)alert(a?"Password must be at least 4 characters long.":"Password required.");else{encryptionKey=a;encryptionEnabled=!0;e.checked=!0;t.classList.add("active");t.title="Encryption enabled - click to disable";for(var n of Object.values(notes))n.encrypted=!0;ScheduleAutoSave();alert("Encryption enabled!")}}}function toggleMenu(){document.getElementById("menuDropdown").classList.toggle("show")}async function checkForUpdates(){const t=REPO_BASE_URL+"/releases";try{const e=REPO_BASE_URL.replace("https://github.com/","https://api.github.com/repos/");const o=await fetch(e+"/releases/latest");if(!o.ok)throw new Error("GitHub API responded with status: "+o.status);const n=await o.json();const a=n.tag_name.replace(/^v./,"");if(0<compareVersions(a,CURRENT_VERSION)){const s=`🆕 Update Available!
Current version: ${CURRENT_VERSION}
Latest version: ${a}
Release: ${n.name}
Would you like to open the releases page to download the update?`;confirm(s)&&window.open(t,"_blank","noopener,noreferrer")}else alert(`✅ You're up to date!
Current version: ${CURRENT_VERSION}
Latest version: `+a)}catch(e){console.error("Error checking for updates:",e);const c=`❌ Could not check for updates automatically.
Error: ${e.message}
Would you like to open the releases page manually?`;confirm(c)&&window.open(t,"_blank","noopener,noreferrer")}document.getElementById("menuDropdown").classList.remove("show")}function compareVersions(e,t){const o=e.split(".").map(Number);const n=t.split(".").map(Number);const a=Math.max(o.length,n.length);for(;o.length<a;)o.push(0);for(;n.length<a;)n.push(0);for(let e=0;e<a;e++){if(o[e]>n[e])return 1;if(o[e]<n[e])return-1}return 0}function exportNotes(){try{const e={version:"1.0",exportDate:(new Date).toISOString(),notes:notes,tags:tags,encrypted:encryptionEnabled},t=new Blob([JSON.stringify(e,null,2)],{type:"application/json"}),o=URL.createObjectURL(t),n=document.createElement("a");n.href=o;n.download=`notebook-export-${(new Date).toISOString().split("T")[0]}.json`;document.body.appendChild(n);n.click();document.body.removeChild(n);URL.revokeObjectURL(o);alert("Notes exported!")}catch(e){alert("Export error: "+e.message)}document.getElementById("menuDropdown").classList.remove("show")}function importNotes(){const e=document.createElement("input");e.type="file";e.accept=".json";e.onchange=async e=>{const t=e.target.files[0];if(t)try{const o=await t.text(),n=JSON.parse(o);if(!n.notes)throw new Error("Invalid file format");const a=confirm("Replace all notes (OK) or merge (Cancel)?");if(a){notes=n.notes;tags=n.tags||[]}else{Object.assign(notes,n.notes);n.tags&&n.tags.forEach(e=>{tags.includes(e)||tags.push(e)})}currentNote=null;updateTagSelect();updateNotesList();updateNoteDisplay();document.getElementById("Editor").innerHTML="";ScheduleAutoSave();alert("Import successful!")}catch(e){alert("Import error: "+e.message)}};e.click();document.getElementById("menuDropdown").classList.remove("show")}function showAbout(){const e=document.getElementById("aboutModalBody");e.innerHTML=`<div style="background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px;padding:16px;margin:16px 0;"><h3>📝 Portable Notebook Compact v${CURRENT_VERSION}</h3><p><strong>Original Creator:</strong> Tolin Simpson</p><p><strong>Repository:</strong> <a href="${REPO_BASE_URL}" target="_blank">GitHub</a></p><p><strong>License:</strong> <a href="${REPO_BASE_URL}/tree/main?tab=MIT-1-ov-file#readme" target="_blank">MIT</a></p></div>`;document.getElementById("aboutModal").style.display="flex";document.getElementById("menuDropdown").classList.remove("show")}function closeAboutModal(){document.getElementById("aboutModal").style.display="none"}window.addEventListener("load",init);</script></body></html>