From df7424d0f122ce2517bc5fdaf64659d38d02c26f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jakub=20R=C5=AF=C5=BEi=C4=8Dka?=
<97810333+thejakubruzicka@users.noreply.github.com>
Date: Tue, 20 May 2025 20:14:43 +0200
Subject: [PATCH] Update script.js
---
script.js | 440 +++++++++++++++++++++++++++++-------------------------
1 file changed, 239 insertions(+), 201 deletions(-)
diff --git a/script.js b/script.js
index 921cd7b..532cd5b 100644
--- a/script.js
+++ b/script.js
@@ -1,210 +1,248 @@
-document.addEventListener('DOMContentLoaded', () => {
- const notesList = document.getElementById('notesList');
- const addNoteFab = document.getElementById('addNoteFab');
- const noteDialog = document.getElementById('noteDialog');
- const noteForm = document.getElementById('noteForm');
- const noteIdInput = document.getElementById('noteIdInput');
- const noteTitleInput = document.getElementById('noteTitleInput');
- const noteContentInput = document.getElementById('noteContentInput');
+// Define variables in a scope accessible by functions
+let notesList, addNoteFab, noteDialog, noteForm, noteIdInput, noteTitleInput, noteContentInput;
+let themeToggleBtn, body;
+let notes = [];
+let currentEditingId = null;
- const themeToggleBtn = document.getElementById('themeToggleBtn');
- const body = document.body;
+async function initializeApp() {
+ // List of MWC components your app uses directly
+ const mwcComponents = [
+ 'md-icon', 'md-fab', 'md-dialog', 'md-filled-button', 'md-text-button',
+ 'md-icon-button', 'md-filled-text-field', 'md-outlined-text-field', 'md-elevation'
+ ];
- let notes = [];
- let currentEditingId = null;
+ try {
+ // Wait for all essential MWC components to be defined
+ await Promise.all(mwcComponents.map(comp => customElements.whenDefined(comp)));
+ console.log("All MWC components are ready.");
+ } catch (error) {
+ console.error("Error waiting for MWC components:", error);
+ document.body.classList.add('loaded'); // Show body anyway
+ const errorDiv = document.createElement('div');
+ errorDiv.innerHTML = `
+
Error loading application components. The page might not work correctly.
+ Please try refreshing. If the problem persists, check your browser console for details.
+ `;
+ errorDiv.style.color = "red";
+ errorDiv.style.textAlign = "center";
+ errorDiv.style.padding = "20px";
+ document.body.prepend(errorDiv);
+ return; // Stop further initialization if critical components failed
+ }
+
+ // --- Assign DOM elements AFTER MWC components are ready and upgraded ---
+ notesList = document.getElementById('notesList');
+ addNoteFab = document.getElementById('addNoteFab');
+ noteDialog = document.getElementById('noteDialog');
+ noteForm = document.getElementById('noteForm');
+ noteIdInput = document.getElementById('noteIdInput');
+ noteTitleInput = document.getElementById('noteTitleInput');
+ noteContentInput = document.getElementById('noteContentInput');
+ themeToggleBtn = document.getElementById('themeToggleBtn');
+ body = document.body;
// --- Theming ---
- function applyTheme(theme) { // theme is 'light' or 'dark'
- body.classList.remove('light-theme', 'dark-theme');
- body.classList.add(theme + '-theme'); // e.g., 'dark-theme'
- localStorage.setItem('mnotes-theme', theme);
+ // Ensure applyTheme is called after themeToggleBtn and body are defined.
+ loadInitialTheme(); // This will call applyTheme
- const icon = themeToggleBtn.querySelector('md-icon');
- if (theme === 'dark') {
- icon.textContent = 'light_mode';
- themeToggleBtn.ariaLabel = "Activate light theme";
- } else {
- icon.textContent = 'dark_mode';
- themeToggleBtn.ariaLabel = "Activate dark theme";
- }
- }
-
- function toggleTheme() {
- const currentTheme = localStorage.getItem('mnotes-theme') || (body.classList.contains('dark-theme') ? 'dark' : 'light');
- applyTheme(currentTheme === 'light' ? 'dark' : 'light');
- }
-
- function loadInitialTheme() {
- const savedTheme = localStorage.getItem('mnotes-theme');
- if (savedTheme) {
- applyTheme(savedTheme);
- } else {
- // Prefer system theme if no preference saved
- if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
- applyTheme('dark');
- } else {
- applyTheme('light'); // Default to light
- }
- }
- }
+ // --- Event Listeners (Attach now that elements are MWC-upgraded) ---
themeToggleBtn.addEventListener('click', toggleTheme);
-
- // --- Data Persistence ---
- function loadNotes() {
- const storedNotes = localStorage.getItem('material-notes');
- notes = storedNotes ? JSON.parse(storedNotes) : [];
- renderNotes();
- }
-
- function saveNotes() {
- localStorage.setItem('material-notes', JSON.stringify(notes));
- }
-
- // --- Rendering Notes ---
- function renderNotes() {
- notesList.innerHTML = ''; // Clear existing notes
- if (notes.length === 0) {
- const p = document.createElement('p');
- p.textContent = "No notes yet. Click the + button to add one!";
- notesList.appendChild(p);
- return;
- }
-
- notes.sort((a, b) => new Date(b.lastModified) - new Date(a.lastModified)); // Sort by most recently modified
-
- notes.forEach(note => {
- const noteCard = document.createElement('div');
- noteCard.className = 'note-card';
-
- const elevation = document.createElement('md-elevation'); // Add MWC elevation
- noteCard.appendChild(elevation);
-
- const title = document.createElement('h3');
- title.textContent = note.title;
-
- const content = document.createElement('p');
- content.textContent = note.content.substring(0, 150) + (note.content.length > 150 ? '...' : '');
-
- const actions = document.createElement('div');
- actions.className = 'note-card-actions';
-
- const editButton = document.createElement('md-icon-button');
- editButton.innerHTML = `edit`;
- editButton.ariaLabel = `Edit note: ${note.title}`;
- editButton.addEventListener('click', (e) => { e.stopPropagation(); openNoteDialog(note); });
-
- const deleteButton = document.createElement('md-icon-button');
- deleteButton.innerHTML = `delete`;
- deleteButton.ariaLabel = `Delete note: ${note.title}`;
- deleteButton.addEventListener('click', (e) => { e.stopPropagation(); deleteNote(note.id); });
-
- actions.appendChild(editButton);
- actions.appendChild(deleteButton);
-
- noteCard.appendChild(title);
- noteCard.appendChild(content);
- noteCard.appendChild(actions);
- notesList.appendChild(noteCard);
- });
- }
-
- // --- Note Operations ---
- function openNoteDialog(note = null) {
- noteForm.reset(); // Clear form fields
- // Reset MWC text field states (e.g. error messages)
- noteTitleInput.errorText = "";
- noteTitleInput.error = false;
- noteContentInput.errorText = "";
- noteContentInput.error = false;
-
- if (note) {
- currentEditingId = note.id;
- noteTitleInput.value = note.title;
- noteContentInput.value = note.content; // Full content for editing
- noteDialog.querySelector('[slot="headline"]').textContent = 'Edit Note';
- } else {
- currentEditingId = null;
- noteDialog.querySelector('[slot="headline"]').textContent = 'Add New Note';
- }
- noteDialog.show();
- }
-
- function handleDialogClose(event) {
- // event.detail.action contains the `value` of the button that closed it
- if (event.detail.action === 'save') {
- const title = noteTitleInput.value.trim();
- const content = noteContentInput.value.trim();
-
- let isValid = true;
- if (!title) {
- noteTitleInput.error = true;
- noteTitleInput.errorText = "Title cannot be empty.";
- isValid = false;
- } else {
- noteTitleInput.error = false;
- noteTitleInput.errorText = "";
- }
- if (!content) {
- noteContentInput.error = true;
- noteContentInput.errorText = "Content cannot be empty.";
- isValid = false;
- } else {
- noteContentInput.error = false;
- noteContentInput.errorText = "";
- }
-
- if (!isValid) {
- // To prevent dialog from closing on invalid native form submission,
- // we'd need to handle the 'submit' event on the form, preventDefault,
- // then manually close if valid.
- // Since `method="dialog"` auto-closes, we re-show if invalid.
- // This is a common pattern if not using full form.requestSubmit().
- noteDialog.show(); // Re-open dialog if validation fails after it auto-closed.
- return;
- }
-
- const now = new Date().toISOString();
- if (currentEditingId) {
- const noteIndex = notes.findIndex(n => n.id === currentEditingId);
- if (noteIndex > -1) {
- notes[noteIndex].title = title;
- notes[noteIndex].content = content;
- notes[noteIndex].lastModified = now;
- }
- } else {
- notes.push({
- id: Date.now().toString(),
- title,
- content,
- createdAt: now,
- lastModified: now
- });
- }
- saveNotes();
- renderNotes();
- }
- // Reset for next time dialog opens
- currentEditingId = null;
- noteForm.reset(); // Ensure form is clean
- noteTitleInput.error = false; noteTitleInput.errorText = "";
- noteContentInput.error = false; noteContentInput.errorText = "";
- }
-
- function deleteNote(id) {
- // Consider using an md-dialog for confirmation for better UX
- // For simplicity, using confirm() for now
- if (confirm('Are you sure you want to delete this note?')) {
- notes = notes.filter(note => note.id !== id);
- saveNotes();
- renderNotes();
- }
- }
-
- // --- Event Listeners ---
addNoteFab.addEventListener('click', () => openNoteDialog());
noteDialog.addEventListener('closed', handleDialogClose);
- // --- Initial Load ---
- loadInitialTheme();
- loadNotes();
-});
+
+ // --- Initial Data Load ---
+ loadNotes(); // This calls renderNotes, which creates more MWC elements
+
+ // --- Make body visible now that everything is set up ---
+ document.body.classList.add('loaded');
+ console.log("MNotes App Initialized and Visible.");
+}
+
+// --- Theming Functions ---
+function loadInitialTheme() {
+ const savedTheme = localStorage.getItem('mnotes-theme');
+ if (savedTheme) {
+ applyTheme(savedTheme);
+ } else {
+ applyTheme(window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
+ }
+}
+
+function applyTheme(theme) { // theme is 'light' or 'dark'
+ if (!body || !themeToggleBtn) {
+ console.warn("Body or themeToggleBtn not ready for applyTheme.");
+ return;
+ }
+ body.classList.remove('light-theme', 'dark-theme');
+ body.classList.add(theme + '-theme');
+ localStorage.setItem('mnotes-theme', theme);
+
+ const iconElement = themeToggleBtn.querySelector('md-icon');
+ if (iconElement) {
+ iconElement.textContent = theme === 'dark' ? 'light_mode' : 'dark_mode';
+ themeToggleBtn.ariaLabel = theme === 'dark' ? "Activate light theme" : "Activate dark theme";
+ }
+}
+
+function toggleTheme() {
+ const currentTheme = body.classList.contains('dark-theme') ? 'dark' : 'light';
+ applyTheme(currentTheme === 'light' ? 'dark' : 'light');
+}
+
+// --- Data Persistence ---
+function loadNotes() {
+ const storedNotes = localStorage.getItem('material-notes');
+ notes = storedNotes ? JSON.parse(storedNotes) : [];
+ renderNotes();
+}
+
+function saveNotes() {
+ localStorage.setItem('material-notes', JSON.stringify(notes));
+}
+
+// --- Rendering Notes ---
+function renderNotes() {
+ if (!notesList) {
+ console.error("notesList element not found during renderNotes. App not fully initialized?");
+ return;
+ }
+ notesList.innerHTML = ''; // Clear existing notes
+ if (notes.length === 0) {
+ const p = document.createElement('p');
+ p.textContent = "No notes yet. Click the + button to add one!";
+ notesList.appendChild(p);
+ return;
+ }
+
+ notes.sort((a, b) => new Date(b.lastModified) - new Date(a.lastModified));
+
+ notes.forEach(note => {
+ const noteCard = document.createElement('div');
+ noteCard.className = 'note-card';
+
+ const elevation = document.createElement('md-elevation');
+ noteCard.appendChild(elevation);
+
+ const title = document.createElement('h3');
+ title.textContent = note.title;
+
+ const content = document.createElement('p');
+ content.textContent = note.content.substring(0, 150) + (note.content.length > 150 ? '...' : '');
+
+ const actions = document.createElement('div');
+ actions.className = 'note-card-actions';
+
+ const editButton = document.createElement('md-icon-button');
+ editButton.innerHTML = `edit`;
+ editButton.ariaLabel = `Edit note: ${note.title}`;
+ editButton.addEventListener('click', (e) => { e.stopPropagation(); openNoteDialog(note); });
+
+ const deleteButton = document.createElement('md-icon-button');
+ deleteButton.innerHTML = `delete`;
+ deleteButton.ariaLabel = `Delete note: ${note.title}`;
+ deleteButton.addEventListener('click', (e) => { e.stopPropagation(); deleteNote(note.id); });
+
+ actions.appendChild(editButton);
+ actions.appendChild(deleteButton);
+
+ noteCard.appendChild(title);
+ noteCard.appendChild(content);
+ noteCard.appendChild(actions);
+ notesList.appendChild(noteCard);
+ });
+}
+
+// --- Note Operations ---
+function openNoteDialog(note = null) {
+ if (!noteForm || !noteTitleInput || !noteContentInput || !noteDialog) {
+ console.error("Dialog elements not found for openNoteDialog.");
+ return;
+ }
+ noteForm.reset();
+ noteTitleInput.error = false; noteTitleInput.errorText = "";
+ noteContentInput.error = false; noteContentInput.errorText = "";
+
+ const headline = noteDialog.querySelector('[slot="headline"]');
+
+ if (note) {
+ currentEditingId = note.id;
+ noteIdInput.value = note.id; // Keep track of ID if needed by form
+ noteTitleInput.value = note.title;
+ noteContentInput.value = note.content;
+ if(headline) headline.textContent = 'Edit Note';
+ } else {
+ currentEditingId = null;
+ noteIdInput.value = '';
+ if(headline) headline.textContent = 'Add New Note';
+ }
+ noteDialog.show();
+}
+
+function handleDialogClose(event) {
+ if (event.detail.action === 'save') { // 'action' is the value of the button that closed the dialog
+ const title = noteTitleInput.value.trim();
+ const content = noteContentInput.value.trim();
+
+ let isValid = true;
+ if (!title) {
+ noteTitleInput.error = true; noteTitleInput.errorText = "Title cannot be empty.";
+ isValid = false;
+ } else {
+ noteTitleInput.error = false; noteTitleInput.errorText = "";
+ }
+ if (!content) {
+ noteContentInput.error = true; noteContentInput.errorText = "Content cannot be empty.";
+ isValid = false;
+ } else {
+ noteContentInput.error = false; noteContentInput.errorText = "";
+ }
+
+ if (!isValid) {
+ // If form is invalid and dialog closed (e.g. via form method="dialog"), reopen it.
+ // Need a slight delay for the dialog to fully process its closing action.
+ setTimeout(() => { if (noteDialog) noteDialog.show(); }, 0);
+ return;
+ }
+
+ const now = new Date().toISOString();
+ if (currentEditingId) {
+ const noteIndex = notes.findIndex(n => n.id === currentEditingId);
+ if (noteIndex > -1) {
+ notes[noteIndex].title = title;
+ notes[noteIndex].content = content;
+ notes[noteIndex].lastModified = now;
+ }
+ } else {
+ notes.push({
+ id: Date.now().toString(), title, content,
+ createdAt: now, lastModified: now
+ });
+ }
+ saveNotes();
+ renderNotes();
+ }
+ // Reset for next time dialog opens, regardless of save or cancel
+ currentEditingId = null;
+ if (noteForm) noteForm.reset();
+ if (noteTitleInput) { noteTitleInput.error = false; noteTitleInput.errorText = ""; }
+ if (noteContentInput) { noteContentInput.error = false; noteContentInput.errorText = ""; }
+}
+
+function deleteNote(id) {
+ // Consider a md-dialog for confirmation for Material consistency
+ if (confirm('Are you sure you want to delete this note?')) {
+ notes = notes.filter(note => note.id !== id);
+ saveNotes();
+ renderNotes();
+ }
+}
+
+// --- Start the application initialization ---
+// This ensures that the script attempts to initialize after the page DOM is loaded
+// and MWC has had a chance to load and start defining components.
+if (document.readyState === 'loading') {
+ document.addEventListener('DOMContentLoaded', initializeApp);
+} else {
+ initializeApp();
+}