Skip to content

Conversation

@bhavish00007
Copy link

@bhavish00007 bhavish00007 commented Sep 9, 2025

💡 Summary

This PR improves the JavaScript functionality in the SimpleBlog project by adding:

  • 🌙 Theme toggle with localStorage to remember user preference.
  • 📱 Mobile nav auto-closes when a link is clicked.
  • 📧 Newsletter form email validation with alert on invalid input.
  • 📄 Load More button to reveal more blog posts gradually.

These changes improve user experience, accessibility, and interactivity of the blog site.


✅ Tested on latest Chrome and Firefox
✅ No existing functionality was broken

Summary by CodeRabbit

  • New Features
    • Dark/Light theme toggle with preference saved across sessions.
    • Mobile navigation now auto-closes when a menu link is selected.
    • Newsletter form email validation prevents submission of invalid addresses with a user alert.
    • “Load more” for blog cards: shows a limited set initially, reveals additional posts on demand, and hides the button when all are visible.

@coderabbitai
Copy link

coderabbitai bot commented Sep 9, 2025

Walkthrough

Single-file JavaScript update adding mobile nav close-on-click, theme toggle with localStorage persistence, newsletter email validation, and “load more” blog cards behavior; minor refactor converting a function expression to an arrow function.

Changes

Cohort / File(s) Summary of changes
Navigation behavior
assets/js/script.js
Converted navToggleFunc to an arrow function; added listeners to mobile nav links to remove the active class on click.
Theme toggle & persistence
assets/js/script.js
Added body, savedTheme, and theme button handling to apply and persist light/dark theme via localStorage across all theme buttons.
Newsletter validation
assets/js/script.js
Added newsletterForm and submit handler to validate email; prevents submission and alerts on invalid input.
Blog cards “load more”
assets/js/script.js
Added blogCards, loadMoreBtn, and showCards to reveal cards incrementally; hides load-more when all cards are shown; removed prior per-theme-btn loop in favor of this structure.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant Burger as Burger/Menu Buttons
  participant Script as script.js
  participant DOM as DOM

  User->>Burger: Click
  Burger->>Script: navToggleFunc()
  Script->>DOM: Toggle .active on nav
  note over DOM: Mobile nav links also close nav on click
Loading
sequenceDiagram
  autonumber
  actor User
  participant ThemeBtn as Theme Button(s)
  participant Script as script.js
  participant DOM as document.body
  participant Store as localStorage

  Script->>Store: getItem('theme')
  alt saved theme exists
    Script->>DOM: Apply saved theme classes
  else no saved theme
    Script->>DOM: Keep default classes
  end
  User->>ThemeBtn: Click
  ThemeBtn->>Script: toggleTheme()
  Script->>DOM: Toggle dark/light classes
  Script->>Store: setItem('theme', currentTheme)
Loading
sequenceDiagram
  autonumber
  actor User
  participant Form as Newsletter Form
  participant Script as script.js
  participant DOM as Email Input

  User->>Form: Submit
  Form->>Script: onSubmit()
  Script->>DOM: Validate email format
  alt invalid
    Script-->>User: alert("Invalid email")
    Script->>Form: preventDefault()
  else valid
    Script->>Form: allow submission
  end
Loading
sequenceDiagram
  autonumber
  actor User
  participant Button as Load More Button
  participant Script as script.js
  participant Cards as Blog Cards (NodeList)

  Script->>Cards: showCards(initialCount)
  User->>Button: Click "Load more"
  Button->>Script: showCards(nextCount)
  Script->>Cards: Reveal additional hidden cards
  alt all cards shown
    Script->>Button: Hide button
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Pre-merge checks (3 passed)

✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The current title clearly and concisely summarizes the primary JavaScript enhancements—theme persistence, mobile navigation auto-close, and email validation—which represent the main functionality changes in the PR without extraneous detail or file listings.
Description Check ✅ Passed The pull request description directly outlines each of the JavaScript improvements—theme toggle with localStorage, mobile nav auto-close, newsletter form validation, and load-more functionality—demonstrating clear relevance to the changeset.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.

Poem

I tap my paw—nav slides with grace,
A flick of ears—dark turns to lace.
I sniff the form, no spam today!
More posts to nibble, load away.
In burrows of code I hop and store,
Theme crumbs saved—to browse some more. 🐇✨

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (2)
assets/js/script.js (2)

15-21: Close-on-click: add a null-safe guard; consider delegation (optional)

Current handler will throw if nav is null. Add a safe guard; delegation reduces N listeners.

-mobileNavLinks.forEach(link => {
-  link.addEventListener('click', () => {
-    nav.classList.remove('active');
-  });
-});
+mobileNavLinks.forEach(link => {
+  link.addEventListener('click', () => {
+    nav?.classList.remove('active');
+  });
+});

Alternative (delegation; replace the block above):

nav?.addEventListener('click', (e) => {
  if (e.target.closest('.nav-link')) nav.classList.remove('active');
});

46-58: Avoid toggle drift; compute next theme and set classes explicitly

Prevents states where both classes are present or out of sync with button classes.

-  btn.addEventListener('click', () => {
-    const isDark = body.classList.toggle('dark-theme');
-    body.classList.toggle('light-theme');
-
-    themeBtn.forEach(btn => {
-      btn.classList.toggle('dark');
-      btn.classList.toggle('light');
-    });
-
-    localStorage.setItem('theme', isDark ? 'dark' : 'light');
-  });
+  btn.addEventListener('click', () => {
+    const isDark = !body.classList.contains('dark-theme');
+    body.classList.toggle('dark-theme', isDark);
+    body.classList.toggle('light-theme', !isDark);
+
+    themeBtn.forEach(b => {
+      b.classList.toggle('dark', isDark);
+      b.classList.toggle('light', !isDark);
+    });
+
+    try { localStorage.setItem('theme', isDark ? 'dark' : 'light'); } catch (_) {}
+  });
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 74af4e1 and 55f4277.

📒 Files selected for processing (1)
  • assets/js/script.js (1 hunks)

Comment on lines +8 to 13
const navToggleFunc = () => {
nav.classList.toggle('active');
};

navMenuBtn.addEventListener('click', navToggleFunc);
navCloseBtn.addEventListener('click', navToggleFunc);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Guard potential nulls for nav elements to avoid runtime errors

If any of .mobile-nav, .nav-menu-btn, or .nav-close-btn is missing on a page, the current code can throw at runtime. Minimal hardening below.

-const navToggleFunc = () => {
-  nav.classList.toggle('active');
-};
+const navToggleFunc = () => {
+  nav?.classList.toggle('active');
+};

-navMenuBtn.addEventListener('click', navToggleFunc);
-navCloseBtn.addEventListener('click', navToggleFunc);
+navMenuBtn?.addEventListener('click', navToggleFunc);
+navCloseBtn?.addEventListener('click', navToggleFunc);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const navToggleFunc = () => {
nav.classList.toggle('active');
};
navMenuBtn.addEventListener('click', navToggleFunc);
navCloseBtn.addEventListener('click', navToggleFunc);
const navToggleFunc = () => {
nav?.classList.toggle('active');
};
navMenuBtn?.addEventListener('click', navToggleFunc);
navCloseBtn?.addEventListener('click', navToggleFunc);
🤖 Prompt for AI Agents
In assets/js/script.js around lines 8 to 13, the code assumes elements
.mobile-nav, .nav-menu-btn, and .nav-close-btn always exist which can throw if
any are missing; add null guards: check that nav, navMenuBtn, and navCloseBtn
are non-null before using them (either return early if nav is missing or wrap
nav.classList.toggle in a conditional/optional chaining), and only call
addEventListener when the corresponding button element exists so event wiring is
skipped safely on pages without those elements.

Comment on lines +28 to +43
// Apply saved theme on load
const savedTheme = localStorage.getItem('theme');
if (savedTheme === 'dark') {
body.classList.add('dark-theme');
body.classList.remove('light-theme');
themeBtn.forEach(btn => {
btn.classList.add('dark');
btn.classList.remove('light');
});
} else {
body.classList.add('light-theme');
themeBtn.forEach(btn => {
btn.classList.add('light');
btn.classList.remove('dark');
});
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Theme persistence: storage safety + exclusive classes + better default

  • localStorage access can throw in some contexts; wrap in try/catch.
  • Ensure exclusivity by removing the opposite theme in both branches.
  • Optional: default to system preference when no saved theme.
-// Apply saved theme on load
-const savedTheme = localStorage.getItem('theme');
+// Apply saved theme on load (guard storage access)
+let savedTheme;
+try { savedTheme = localStorage.getItem('theme'); } catch (_) { savedTheme = null; }
+const prefersDark = window.matchMedia?.('(prefers-color-scheme: dark)').matches;

-if (savedTheme === 'dark') {
+if (savedTheme === 'dark' || (!savedTheme && prefersDark)) {
   body.classList.add('dark-theme');
   body.classList.remove('light-theme');
   themeBtn.forEach(btn => {
     btn.classList.add('dark');
     btn.classList.remove('light');
   });
 } else {
   body.classList.add('light-theme');
+  body.classList.remove('dark-theme');
   themeBtn.forEach(btn => {
     btn.classList.add('light');
     btn.classList.remove('dark');
   });
 }

Also guard setItem:

-    localStorage.setItem('theme', isDark ? 'dark' : 'light');
+    try { localStorage.setItem('theme', isDark ? 'dark' : 'light'); } catch (_) {}

Also applies to: 56-56

Comment on lines +61 to +74
// ===== Newsletter Form Validation =====
const newsletterForm = document.querySelector('.newsletter form');

if (newsletterForm) {
newsletterForm.addEventListener('submit', function (e) {
const emailInput = this.querySelector('input[name="email"]');
const email = emailInput.value.trim();

if (!email || !email.includes('@')) {
e.preventDefault(); // Stop form submission
alert('Please enter a valid email address.');
}
});
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Email validation: use HTML5 validity and null guards

includes('@') is too weak and emailInput may be null. Use the browser’s validator when possible.

-  newsletterForm.addEventListener('submit', function (e) {
-    const emailInput = this.querySelector('input[name="email"]');
-    const email = emailInput.value.trim();
-
-    if (!email || !email.includes('@')) {
-      e.preventDefault(); // Stop form submission
-      alert('Please enter a valid email address.');
-    }
-  });
+  newsletterForm.addEventListener('submit', function (e) {
+    const emailInput = this.querySelector('input[type="email"], input[name="email"]');
+    const email = emailInput?.value.trim() || '';
+    const isValid = emailInput
+      ? (emailInput.type === 'email' ? emailInput.checkValidity() : /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email))
+      : false;
+    if (!isValid) {
+      e.preventDefault();
+      alert('Please enter a valid email address.');
+      emailInput?.focus();
+    }
+  });

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In assets/js/script.js around lines 61 to 74, the current validation uses
emailInput without guarding for null and only checks for '@', which is weak;
update the handler to first guard that emailInput exists, then rely on the
browser’s HTML5 validation by ensuring the input is treated as type="email" (or
check its type) and use emailInput.checkValidity() or emailInput.validity.valid
(and emailInput.reportValidity() to show the built-in message) after trimming
the value; if invalid call e.preventDefault() and optionally
setCustomValidity('Please enter a valid email address') before reportValidity(),
clearing custom validity when valid.

Comment on lines 21 to 100
// ===== Load More Blog Cards =====
const blogCards = document.querySelectorAll('.blog-card');
const loadMoreBtn = document.querySelector('.load-more');

themeBtn[i].addEventListener('click', function () {
if (blogCards.length && loadMoreBtn) {
let visibleCards = 3;

// toggle `light-theme` & `dark-theme` class from `body`
// when clicked `theme-btn`
document.body.classList.toggle('light-theme');
document.body.classList.toggle('dark-theme');
const showCards = count => {
blogCards.forEach((card, i) => {
card.style.display = i < count ? 'block' : 'none';
});
};

for (let i = 0; i < themeBtn.length; i++) {
showCards(visibleCards);

// When the `theme-btn` is clicked,
// it toggles classes between `light` & `dark` for all `theme-btn`.
themeBtn[i].classList.toggle('light');
themeBtn[i].classList.toggle('dark');
loadMoreBtn.addEventListener('click', () => {
visibleCards += 3;
showCards(visibleCards);

if (visibleCards >= blogCards.length) {
loadMoreBtn.style.display = 'none';
}

})

}
});
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Load more: avoid forcing display:block; use hidden and hide the button initially

Inline display: block can break grid/flex/card layouts. hidden is simpler and layout-safe. Also hide the button if all cards are already visible.

   const showCards = count => {
     blogCards.forEach((card, i) => {
-      card.style.display = i < count ? 'block' : 'none';
+      card.hidden = i >= count;
     });
   };

   showCards(visibleCards);
+  if (blogCards.length <= visibleCards) {
+    loadMoreBtn.hidden = true;
+  }

   loadMoreBtn.addEventListener('click', () => {
     visibleCards += 3;
     showCards(visibleCards);

-    if (visibleCards >= blogCards.length) {
-      loadMoreBtn.style.display = 'none';
-    }
+    if (visibleCards >= blogCards.length) {
+      loadMoreBtn.hidden = true;
+    }
   });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// ===== Load More Blog Cards =====
const blogCards = document.querySelectorAll('.blog-card');
const loadMoreBtn = document.querySelector('.load-more');
themeBtn[i].addEventListener('click', function () {
if (blogCards.length && loadMoreBtn) {
let visibleCards = 3;
// toggle `light-theme` & `dark-theme` class from `body`
// when clicked `theme-btn`
document.body.classList.toggle('light-theme');
document.body.classList.toggle('dark-theme');
const showCards = count => {
blogCards.forEach((card, i) => {
card.style.display = i < count ? 'block' : 'none';
});
};
for (let i = 0; i < themeBtn.length; i++) {
showCards(visibleCards);
// When the `theme-btn` is clicked,
// it toggles classes between `light` & `dark` for all `theme-btn`.
themeBtn[i].classList.toggle('light');
themeBtn[i].classList.toggle('dark');
loadMoreBtn.addEventListener('click', () => {
visibleCards += 3;
showCards(visibleCards);
if (visibleCards >= blogCards.length) {
loadMoreBtn.style.display = 'none';
}
})
}
\ No newline at end of file
});
}
// ===== Load More Blog Cards =====
const blogCards = document.querySelectorAll('.blog-card');
const loadMoreBtn = document.querySelector('.load-more');
if (blogCards.length && loadMoreBtn) {
let visibleCards = 3;
const showCards = count => {
blogCards.forEach((card, i) => {
card.hidden = i >= count;
});
};
showCards(visibleCards);
if (blogCards.length <= visibleCards) {
loadMoreBtn.hidden = true;
}
loadMoreBtn.addEventListener('click', () => {
visibleCards += 3;
showCards(visibleCards);
if (visibleCards >= blogCards.length) {
loadMoreBtn.hidden = true;
}
});
}
🤖 Prompt for AI Agents
In assets/js/script.js around lines 77 to 100, the load-more logic is using
inline style.display = 'block'/'none' which can break layout; change it to use
the element.hidden property to show/hide cards and the button. Specifically,
replace any card.style.display toggles with card.hidden = false/true based on
index < count, and replace loadMoreBtn.style.display = 'none' with
loadMoreBtn.hidden = true; also set loadMoreBtn.hidden = true initially if
visibleCards >= blogCards.length so the button is hidden when all cards are
already visible.

@bhavish00007
Copy link
Author

@codingstella can u please check my pr request and give review..

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant