Skip to content

[Feature] implement optimization and load on scroll for posts#31

Merged
AmanatAliPanhwer merged 3 commits into
mainfrom
feat/26-implement-optimization-and-load-on-scroll-for-posts
Nov 23, 2025
Merged

[Feature] implement optimization and load on scroll for posts#31
AmanatAliPanhwer merged 3 commits into
mainfrom
feat/26-implement-optimization-and-load-on-scroll-for-posts

Conversation

@AmanatAliPanhwer

@AmanatAliPanhwer AmanatAliPanhwer commented Nov 23, 2025

Copy link
Copy Markdown
Owner

📌 Summary

Optimize application performance by implementing server-side pagination and a client-side load-on-scroll mechanism for the blog post list. This reduces the initial data load by only fetching the first page of posts and dynamically loading more as the user scrolls.


🔧 Type of Change

  • 🐞 Bug fix
  • 🚀 New feature
  • 🧰 Code refactor
  • 🧾 Documentation update
  • 🎨 UI/UX enhancement

🧩 Related Issues

Closes #26


🧠 Changes Introduced

  • Backend (app.py):
    • Modified the index route to fetch only the first page of posts (limit 10).
    • Created a new /api/posts endpoint to serve subsequent pages via JSON, accepting a page query parameter.
  • Frontend (templates/index.html):
    • Added id="posts-container" to the main posts wrapper for easier DOM manipulation.
    • Added hidden elements for the loading indicator (#loading-indicator) and end-of-posts message (#end-of-posts-message).
  • Client-side Logic (static/js/script.js):
    • Implemented a scroll event listener to detect when the user reaches the bottom of the page.
    • Added loadMorePosts function to fetch data from the new API and append posts dynamically to the DOM.

📸 Screenshots (if applicable)

N/A


✅ Checklist

  • Code follows the project’s style guide
  • Tested locally without errors
  • Documentation updated (if needed)
  • Commits are descriptive and meaningful

🧾 Additional Notes

This implementation significantly improves the initial load time for the homepage by deferring the loading of older posts.

Summary by CodeRabbit

  • New Features

    • Infinite scroll loading of posts with server-driven pagination and per-page limits.
    • Rich video player enhancements: quality selection, speed controls, PiP, fullscreen, and improved per-post video metadata.
  • UI/UX Improvements

    • Loading indicator while fetching more posts and end-of-feed message when complete.
    • Posts include formatted timestamps and embedded video previews.
  • Admin

    • Admin status is now checked so Edit/Delete options appear for authenticated admins.

✏️ Tip: You can customize this high-level summary in your review settings.

@AmanatAliPanhwer AmanatAliPanhwer linked an issue Nov 23, 2025 that may be closed by this pull request
5 tasks
@vercel

vercel Bot commented Nov 23, 2025

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
blog Ready Ready Preview Comment Nov 23, 2025 6:03am

@coderabbitai

coderabbitai Bot commented Nov 23, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

Adds paginated posts API and scroll-based lazy-loading, refactors per-video initialization into initializeVideoPlayer, exposes admin-check endpoint, updates templates and CSS for loader/end states, and augments client script to fetch and append posts with embedded video metadata and admin-aware controls.

Changes

Cohort / File(s) Summary
Backend / API & Pagination
app.py
Added POSTS_PER_PAGE constant and fetch_video_data(video_id) function. New routes: @app.route("/api/posts") returns paginated posts with formatted_timestamp, nested video data and has_next; @app.route("/api/check_admin") returns is_admin. Replaced inline video fetching in view routes and applied per-page limit in home(). admin_inspect now includes is_admin.
Client: Lazy-loading & Post rendering
static/js/script.js, templates/index.html
Added scroll-triggered lazy-loading state (page, hasNext, loading) and fetch flow to /api/posts?page={n}. Renders appended posts (images and video containers), shows #loading-indicator, reveals #end-of-posts-message when done, and queries /api/check_admin to conditionally render Edit/Delete links. Added id="posts-container" and the loader/end-message elements in templates/index.html.
Client: Video player refactor
static/js/video_player.js
Introduced initializeVideoPlayer(container) and moved per-video logic into modular helper functions. Added HLS handling (hls.js conditional), dynamic quality menu, event wiring per container, UI state management (play/pause, progress, volume, quality, speed, fullscreen, PiP), and outside-click/menu behavior. DOMContentLoaded now initializes each container via initializeVideoPlayer().
Styles
static/css/style.css
Appended .loader class and @keyframes l3 to render a rotating masked circular loading animation used by #loading-indicator.

Sequence Diagram

sequenceDiagram
  actor User
  participant Browser
  participant Flask
  participant DB
  participant HLS as HLS.js

  User->>Browser: Open homepage
  Browser->>Flask: GET /
  Flask->>DB: Fetch first POSTS_PER_PAGE posts
  DB-->>Flask: Posts + metadata
  Flask-->>Browser: Render page with initial posts

  Note over Browser: User scrolls near bottom
  Browser->>Browser: scroll listener triggers
  Browser->>Flask: GET /api/posts?page=2
  Flask->>DB: Query posts with LIMIT/OFFSET
  DB-->>Flask: Page posts
  Flask-->>Browser: JSON {posts, has_next}

  alt has_next = true
    Browser->>Browser: Render posts into `#posts-container`
    Browser->>Flask: GET /api/check_admin
    Flask-->>Browser: {is_admin}
    Browser->>Browser: Add Edit/Delete if admin
    alt post has video
      Browser->>Browser: create video container
      Browser->>Browser: call initializeVideoPlayer(container)
      Browser->>HLS: (if needed) load manifest / levels
      HLS-->>Browser: quality levels
      Browser->>Browser: build quality menu & wire controls
    end
  else has_next = false
    Browser->>Browser: show `#end-of-posts-message`
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Review focus areas:
    • app.py: pagination SQL/ORM usage, correctness of has_next computation, security of fetch_video_data() and any user-exposed fields.
    • static/js/script.js: scroll threshold/debouncing, error handling, DOM insertion safety, admin-check call frequency.
    • static/js/video_player.js: event listener lifecycle per container (avoid leaks), HLS integration and fallback handling, quality switching correctness.
    • static/css/style.css & templates/index.html: ensure IDs/classes used by scripts match and loader animation performs acceptably.

Poem

🐰
Hops and bytes in evening light,
Posts arrive as you scroll by night,
Videos wake with nimble play,
Admins peek, then hop away,
Loader spins — the forest cheerfully bright.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.45% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Out of Scope Changes check ❓ Inconclusive Changes include refactored video player logic and new CSS for loader animations that are not explicitly mentioned in issue #26 objectives, though they support the overall implementation. Clarify whether video player refactoring (initializeVideoPlayer, video_player.js reorganization) and loader CSS are directly required for issue #26 or represent scope creep.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main feature: implementing load-on-scroll optimization for posts, which matches the changeset's primary focus.
Description check ✅ Passed The description follows the template structure with all major sections filled: Summary, Type of Change, Related Issues, Changes Introduced, and Checklist. The content adequately explains the implementation.
Linked Issues check ✅ Passed The PR implements all core objectives from issue #26: backend pagination with /api/posts endpoint, frontend scroll detection, loading indicators, dynamic post appending, and end-of-posts handling.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/26-implement-optimization-and-load-on-scroll-for-posts

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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 (7)
static/js/video_player.js (2)

16-33: Harden control element lookups to avoid null dereferences

initializeVideoPlayer assumes all queried controls (playPauseBtn, muteBtn, volumeSlider, fullscreenBtn, etc.) exist; any divergence between HTML markup (including components/video_player.html and the dynamic markup in static/js/script.js) and these selectors will cause immediate runtime errors (e.g., playPauseBtn.querySelector on null, or muteBtn.addEventListener when muteBtn is null).

Consider validating critical elements up front and bailing out (with a clear console warning) if any are missing, or at least guarding subsequent usage:

function initializeVideoPlayer(container) {
-    const playOverlay = container.querySelector(".play-overlay");
-    const playPauseBtn = container.querySelector(".play-pause-btn");
-    const playPauseIcon = playPauseBtn.querySelector(".play-icon");
+    const playOverlay = container.querySelector(".play-overlay");
+    const playPauseBtn = container.querySelector(".play-pause-btn");
+    const playPauseIcon = playPauseBtn && playPauseBtn.querySelector(".play-icon");
    // ...similar for other controls...

+    if (!playOverlay || !playPauseBtn || !playPauseIcon || !progressBar || !muteBtn || !volumeSlider || !fullscreenBtn) {
+        console.warn("Video controls markup incomplete for container:", container);
+        return;
+    }

This prevents a single malformed container from breaking video initialization globally.

Also applies to: 276-301


302-307: Multiple document‑level listeners per player could be centralized later

Each player instance registers its own fullscreenchange (and vendor variants) and document.click handlers. Functionally this works, but on pages with many videos it can add unnecessary overhead and duplication.

In a follow‑up, consider centralizing these document‑level listeners once (e.g., maintaining a registry of players or active menus) rather than per container. Not urgent, but worth keeping in mind if the number of players grows.

Also applies to: 338-345

templates/index.html (1)

51-76: Consider removing nested <a> elements in post markup

The post markup wraps everything in an <a class="post-button"> and also contains inner <a> tags (for the video player wrapper and the Edit/Delete links). This is invalid HTML and can lead to inconsistent click behavior and accessibility issues. The lazy-loaded posts in static/js/script.js mirror this pattern.

When convenient, consider restructuring to have a single outer clickable block (e.g., wrapping the content in a block element with a click handler or using only one anchor per post) and moving Edit/Delete controls outside that main link.

static/js/script.js (3)

150-158: Re‑initialize image viewer for lazily loaded galleries

New posts appended via loadMorePosts can include:

imageHTML = `
    <div class="gallery">
        <img src="${post.image}" alt="Post Image" class="image" loading="lazy">
    </div>
`;

But the existing Viewer.js setup only runs once on DOMContentLoaded over the initial .gallery elements, so newly loaded galleries will not get the enhanced viewer behavior.

Consider extracting the viewer initialization into a reusable function and calling it for newly added galleries, e.g.:

-document.addEventListener('DOMContentLoaded', function () {
-    const galleries = document.querySelectorAll('.gallery');
-    galleries.forEach(gallery => { /* init Viewer */ });
-});
+function initGalleries(root = document) {
+    const galleries = root.querySelectorAll('.gallery');
+    galleries.forEach(gallery => { /* init Viewer */ });
+}
+
+document.addEventListener('DOMContentLoaded', () => initGalleries(document));

And inside loadMorePosts after postsContainer.appendChild(postLink):

+                    initGalleries(postLink);

This keeps UX consistent between initial and lazy‑loaded content.

Also applies to: 224-237


107-125: Add basic null‑safety for loading/end indicators

Within the lazy‑load handler you assume loadingIndicator and endOfPostsMessage exist whenever postsContainer exists:

const loadingIndicator = document.getElementById('loading-indicator');
const endOfPostsMessage = document.getElementById('end-of-posts-message');
// ...
loadingIndicator.style.display = 'flex';

If a future template forgets to include one of these elements but still uses id="posts-container", this will throw a TypeError.

A small defensive guard would make this more robust:

-    if (!postsContainer) return;
+    if (!postsContainer) return;
+
+    if (!loadingIndicator || !endOfPostsMessage) {
+        console.warn("Lazy loading: missing loading or end-of-posts elements");
+    }

And when toggling styles, check for existence before accessing .style. Not urgent, but it hardens the code against template drift.

Also applies to: 254-260


133-148: Consider caching admin status instead of calling /api/check_admin on every page

For each page of posts you do:

const isAdmin = await fetch('/api/check_admin')
    .then(res => res.json())
    .then(resData => resData.is_admin)
    .catch(() => false);

Admin status is session‑scoped and effectively static for the duration of the page, so this extra network roundtrip on every lazy‑load is avoidable.

You could cache it once at the top‑level (or on first call) and reuse:

let cachedIsAdmin = null;

async function getIsAdmin() {
    if (cachedIsAdmin !== null) return cachedIsAdmin;
    try {
        const res = await fetch('/api/check_admin');
        const data = await res.json();
        cachedIsAdmin = !!data.is_admin;
    } catch {
        cachedIsAdmin = false;
    }
    return cachedIsAdmin;
}

// in loadMorePosts
const isAdmin = await getIsAdmin();

This reduces latency and server load, especially when scrolling through many pages.

app.py (1)

773-808: Reuse fetch_video_data in edit_post GET branch to avoid duplication

The edit_post GET logic reimplements video lookup logic that now lives in fetch_video_data:

if post["video_id"]:
    video_id = post["video_id"]
    # repeated Supabase query building videodata dict...

You can simplify and keep behavior consistent by delegating to fetch_video_data:

-        if post["video_id"]:
-            video_id = post["video_id"]
-            try:
-                video_resp = supabase_client.table("videos")...
-                # build videodata
-                post["video"] = videodata
-            except Exception as e:
-                print(f"Error fetching video info for video_id={video_id}: {e}")
+        video_id = post.get("video_id")
+        if video_id:
+            post["video"] = fetch_video_data(video_id)

This removes duplicate Supabase access patterns and centralizes any future changes to video metadata.

Also applies to: 180-201

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between df054d0 and 47ee91c.

📒 Files selected for processing (5)
  • app.py (8 hunks)
  • static/css/style.css (1 hunks)
  • static/js/script.js (1 hunks)
  • static/js/video_player.js (1 hunks)
  • templates/index.html (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
app.py (1)
static/js/script.js (3)
  • data (21-21)
  • data (32-32)
  • page (113-113)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: test
🔇 Additional comments (5)
static/css/style.css (1)

725-742: Loader styles look good and self‑contained

The new .loader and @keyframes l3 are syntactically correct, scoped, and reuse existing theme variables without impacting prior styles. Degradation in browsers lacking mask-composite will be graceful.

templates/index.html (1)

5-5: Markup wiring for lazy loading looks correct

The id="posts-container", #loading-indicator, and #end-of-posts-message additions line up with the selectors used in static/js/script.js and the .loader styles in static/css/style.css. Structurally this should work well with the new infinite scroll behavior.

Also applies to: 79-85

app.py (3)

180-201: Good reuse of fetch_video_data helper

Centralizing video metadata access in fetch_video_data and reusing it in home / view_post reduces duplication and keeps the shape of the video object consistent across views and the API.


246-254: Consistent video data wiring into templates

Using fetch_video_data(video_id) in both home (tuple element) and view_post (post["video"]) ensures templates receive a uniform video structure whether rendered server‑side or via the JSON API. This is a clean improvement over scattered inline queries.

Also applies to: 839-878


975-981: /api/check_admin implementation is straightforward and matches frontend expectations

The new /api/check_admin endpoint cleanly exposes {"is_admin": bool} based on the session, which lines up with how static/js/script.js consumes it to decide whether to render Edit/Delete links for lazy-loaded posts.

Comment thread app.py Outdated
Comment thread static/js/script.js
Comment thread static/js/video_player.js

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
static/js/script.js (1)

84-103: Re-initialize image viewer for lazily loaded .gallery elements

The viewer setup at the top of this file runs only once on DOMContentLoaded, so .gallery elements injected by lazy loading will not get the enhanced viewer behavior.

After appending each postLink, you can initialize viewers for any new galleries in that post, preferably by extracting the existing viewer initialization into a reusable helper:

-                    postsContainer.appendChild(postLink);
+                    postsContainer.appendChild(postLink);
+
+                    // Initialize viewer.js for any new galleries in this post
+                    const newGalleries = postLink.querySelectorAll('.gallery');
+                    newGalleries.forEach(gallery => {
+                        const image = gallery.querySelector('img');
+                        if (!image) return;
+                        const viewer = new Viewer(gallery, {
+                            movable: true,
+                            zoomable: true,
+                            fullscreen: true,
+                            toolbar: false,
+                        });
+                        image.addEventListener('click', (event) => {
+                            event.preventDefault();
+                            event.stopPropagation();
+                            viewer.show();
+                        });
+                    });

You can also DRY this up by moving the viewer initialization logic into a shared function used both on initial load and here.

Also applies to: 150-158, 237-245

🧹 Nitpick comments (4)
static/js/script.js (1)

133-137: Cache admin status instead of calling /api/check_admin on every page fetch

/api/check_admin is called on every loadMorePosts invocation, even though admin status is effectively per-session. You can avoid redundant network calls by caching the value after the first check:

-    let page = 2; // Start with page 2, since page 1 is loaded initially
+    let page = 2; // Start with page 2, since page 1 is loaded initially
     let isLoading = false;
     let hasNext = true;
+    let isAdminCache = null;
...
-                const isAdmin = await fetch('/api/check_admin')
-                    .then(res => res.json())
-                    .then(resData => resData.is_admin)
-                    .catch(() => false);
+                if (isAdminCache === null) {
+                    isAdminCache = await fetch('/api/check_admin')
+                        .then(res => res.json())
+                        .then(resData => resData.is_admin)
+                        .catch(() => false);
+                }
+                const isAdmin = isAdminCache;

This keeps behavior the same while cutting unnecessary requests as the user scrolls.

Also applies to: 246-247

app.py (2)

953-975: Make /api/posts ordering resilient if TIMESTAMP_FIELD is ever unset

/api/posts currently does:

res = supabase_client.table("posts").select(select_fields)\
    .order(TIMESTAMP_FIELD, desc=True)\
    .offset(offset).limit(POSTS_PER_PAGE).execute()

Elsewhere (home(), filter_posts()), you explicitly fall back to ordering by "id" when TIMESTAMP_FIELD is not resolved. Even though resolve_timestamp_field() currently guarantees a non-None value, mirroring that pattern here will make the endpoint more robust to future changes and to schema/probe failures.

For example:

-        res = supabase_client.table("posts").select(select_fields).order(TIMESTAMP_FIELD, desc=True).offset(offset).limit(POSTS_PER_PAGE).execute()
+        order_field = TIMESTAMP_FIELD if TIMESTAMP_FIELD else "id"
+        res = (
+            supabase_client.table("posts")
+            .select(select_fields)
+            .order(order_field, desc=True)
+            .offset(offset)
+            .limit(POSTS_PER_PAGE)
+            .execute()
+        )

You may also want to reuse a small helper for the timestamp formatting logic to keep behavior identical with home()/filter_posts().


953-975: Be aware of N+1 queries when enriching posts with video data

Inside /api/posts, each post with a video_id triggers a separate fetch_video_data(video_id) call. For pages with many video posts, this becomes an N+1 pattern against the videos table.

This is acceptable for low volume, but if you expect many posts per page and frequent scrolling, consider a future optimization that batches video lookups (e.g., collecting all video_ids for the page and fetching them in one query, then mapping by id) while still reusing the fetch_video_data shape.

Also applies to: 968-972

static/js/video_player.js (1)

302-307: Consider de-duplicating global event listeners across video instances

Each call to initializeVideoPlayer adds its own document-level listeners for fullscreen change and global click (to close menus). With many video containers on a long page, these listeners multiply and all run on every relevant event.

Behavior is correct, but you could simplify and reduce overhead by registering these document-level handlers once (outside initializeVideoPlayer) and having them delegate to the active/fullscreen container as needed. Not urgent, but worth considering if the number of videos grows.

Also applies to: 338-345

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 47ee91c and 917d138.

📒 Files selected for processing (3)
  • app.py (8 hunks)
  • static/js/script.js (1 hunks)
  • static/js/video_player.js (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
app.py (1)
static/js/script.js (3)
  • data (21-21)
  • data (32-32)
  • page (113-113)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: test
🔇 Additional comments (4)
static/js/script.js (1)

105-125: Lazy-loading flow and loading state management look solid

The paging state (page, isLoading, hasNext), scroll trigger, and try/finally around the fetch give you a clean, race-free lazy-loading behavior and ensure the loading indicator is always cleared. This aligns well with the backend /api/posts contract and the PR’s performance goals.

Also applies to: 254-260

app.py (2)

98-99: Good consolidation of video data fetching and pagination constant

Using POSTS_PER_PAGE in both home() and /api/posts, combined with the fetch_video_data helper reused in home(), view_post(), and the API, keeps pagination and video metadata consistent across views and the JSON API. This is a nice reduction of duplication and makes it easier to adjust page size or video fields in one place.

Also applies to: 180-199, 232-255, 869-878


885-889: Admin JSON endpoints look consistent and minimally exposed

The updated /admin/inspect response (JSON with is_admin and a masked key) and the new /api/check_admin route cleanly separate admin state probing from the main views. Returning {"is_admin": False, "error": "admin only"} with 403 for unauthenticated calls is sensible, and /api/check_admin exposes only a boolean flag, which is appropriate for the client checks in static/js/script.js.

Also applies to: 923-926, 986-992

static/js/video_player.js (1)

1-4: Per-container initializeVideoPlayer abstraction is well-structured and works for dynamically added videos

The new initializeVideoPlayer(container) plus the DOMContentLoaded bootstrap (and calls from script.js for lazily appended posts) give you a clean, reusable initialization path. The helper functions for controls, HLS/native playback selection, and control visibility keep the logic manageable, and the guard around the Hls global avoids ReferenceErrors when the library is unavailable.

Also applies to: 6-37, 217-275, 276-354

Comment thread static/js/script.js
Comment on lines +108 to +110
const loadingIndicator = document.getElementById('loading-indicator');
const endOfPostsMessage = document.getElementById('end-of-posts-message');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Guard against missing loading/end-of-posts elements to avoid runtime errors

loadingIndicator and endOfPostsMessage are assumed to exist; if the DOM ever omits one (e.g., template reuse), loadingIndicator.style... or endOfPostsMessage.style... will throw and break lazy loading.

You can harden this by bailing out when any of the required nodes is missing:

 document.addEventListener('DOMContentLoaded', () => {
-    const postsContainer = document.getElementById('posts-container');
-    const loadingIndicator = document.getElementById('loading-indicator');
-    const endOfPostsMessage = document.getElementById('end-of-posts-message');
-
-    if (!postsContainer) return;
+    const postsContainer = document.getElementById('posts-container');
+    const loadingIndicator = document.getElementById('loading-indicator');
+    const endOfPostsMessage = document.getElementById('end-of-posts-message');
+
+    if (!postsContainer || !loadingIndicator || !endOfPostsMessage) {
+        return;
+    }

Also applies to: 120-125, 249-252, 258-260

🤖 Prompt for AI Agents
In static/js/script.js around lines 108-110 (and similarly at 120-125, 249-252,
258-260), the code assumes document.getElementById returned non-null for
loadingIndicator and endOfPostsMessage and will throw when accessing .style on a
missing element; fix by null-checking each DOM node after retrieval and bail out
or skip the related logic when either is missing (e.g., return early from the
function or guard conditional before manipulating .style or adding/removing
classes), ensuring all places that reference these elements perform the same
presence checks.

Comment thread static/js/script.js
Comment on lines +133 to +137
if (data.posts && data.posts.length > 0) {
const isAdmin = await fetch('/api/check_admin')
.then(res => res.json())
.then(resData => resData.is_admin)
.catch(() => false);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fix nested <a> tags and admin links inside the main post link

The generated HTML nests <a> tags inside another <a> (adminLinks and the extra <a> around ${videoHTML}), which is invalid HTML and can cause confusing click behavior (e.g., edit/delete links triggering the main post navigation).

Consider keeping the main card as a single link and rendering admin controls outside of it:

-                    let adminLinks = '';
-                    if (isAdmin) {
-                        adminLinks = `
-                            <a style="padding-right: 5px;" href="/edit/${post.id}">Edit</a>
-                            <a href="/delete/${post.id}">Delete</a>
-                        `;
-                    }
+                    let adminLinks = '';
+                    if (isAdmin) {
+                        adminLinks = `
+                            <a style="padding-right: 5px;" href="/edit/${post.id}">Edit</a>
+                            <a href="/delete/${post.id}">Delete</a>
+                        `;
+                    }
...
-                    postLink.innerHTML = `
-                        <a href="/post/${post.id}" class="post-button">
-                            <h1 class="Heding">${post.title}</h1>
-                            <p class="text">${post.content}</p>
-                            ${imageHTML}
-                            <a>
-                            ${videoHTML}
-                            </a>
-                            <small>Posted on ${post.formatted_timestamp || (post.timestamp ? new Date(post.timestamp).toLocaleDateString() : '')}</small>
-                            <br>
-                            ${adminLinks}
-                        </a>
-                    `;
+                    postLink.innerHTML = `
+                        <a href="/post/${post.id}" class="post-button">
+                            <h1 class="Heding">${post.title}</h1>
+                            <p class="text">${post.content}</p>
+                            ${imageHTML}
+                            ${videoHTML}
+                            <small>Posted on ${post.formatted_timestamp || (post.timestamp ? new Date(post.timestamp).toLocaleDateString() : '')}</small>
+                        </a>
+                        <br>
+                        ${adminLinks}
+                    `;

This keeps the entire card clickable while ensuring admin links are independent anchors.

Also applies to: 142-148, 224-236

🤖 Prompt for AI Agents
In static/js/script.js around lines 133-137 (and also address similar patterns
at 142-148 and 224-236), the generated markup currently nests anchor tags
(adminLinks and an extra anchor wrapping videoHTML) inside the main post anchor
which is invalid and causes click conflicts; fix this by keeping the main card
as a single top-level anchor or clickable container and render admin controls
(edit/delete links) outside that anchor: remove any inner <a> wrappers around
videoHTML, move adminLinks out of the main link container (place them as sibling
elements), and if you need the card to remain fully clickable attach a click
handler or wrap non-admin content in the main anchor while ensuring admin
anchors are not inside it so their clicks don't bubble to post navigation.
Ensure identical fixes are applied to the other mentioned line ranges.

@AmanatAliPanhwer AmanatAliPanhwer merged commit aa1523f into main Nov 23, 2025
3 of 4 checks passed
@AmanatAliPanhwer AmanatAliPanhwer deleted the feat/26-implement-optimization-and-load-on-scroll-for-posts branch November 23, 2025 06:12
@AmanatAliPanhwer AmanatAliPanhwer restored the feat/26-implement-optimization-and-load-on-scroll-for-posts branch November 23, 2025 06:12
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.

[Feature] Implement optimization and load-on-scroll for posts

1 participant