Skip to content

[Feature] Enhanced image viewing#15

Merged
AmanatAliPanhwer merged 3 commits into
mainfrom
feat/11-enhanced-image-viewing
Oct 17, 2025
Merged

[Feature] Enhanced image viewing#15
AmanatAliPanhwer merged 3 commits into
mainfrom
feat/11-enhanced-image-viewing

Conversation

@AmanatAliPanhwer
Copy link
Copy Markdown
Owner

@AmanatAliPanhwer AmanatAliPanhwer commented Oct 17, 2025

📌 Summary

In this pull request, I have added the Lightroom effect for better image viewing. I have also moved some of the CSS and JavaScript to their own files for clarity, and I have written some tests for them. And also fixed a little bug in the image uploading behaviour.

🔧 Type of Change

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

🧩 Related Issues

Closes #11

🧠 Changes Introduced

  • added better image viewing using viewer.js.
  • Moved some inline CSS and JavaScript to their own files for clarity.

📸 video

image-viewing.mp4

✅ Checklist

  • Code follows the project’s style guide
  • Tested locally without errors
  • Commits are descriptive and meaningful

Summary by CodeRabbit

  • New Features

    • Fullscreen post editor mode accessible via URL parameter; submit posts with Shift+Enter.
    • Auto-closing HTML tags in the post editor.
    • Interactive image viewer with zoom and pan capabilities on post pages and homepage.
    • Confirmation flash messages for post deletion operations.
    • Dynamic timestamp field detection for posts.
  • Bug Fixes

    • Improved image upload error handling with automatic fallback to local storage when remote upload fails.
  • Style

    • Added dedicated post page styling with responsive design.
    • Image viewer styling with backdrop and enhanced visual effects.
  • Tests

    • Added comprehensive image viewer functionality tests.

@AmanatAliPanhwer AmanatAliPanhwer added the enhancement New feature or request label Oct 17, 2025
@AmanatAliPanhwer AmanatAliPanhwer linked an issue Oct 17, 2025 that may be closed by this pull request
@vercel
Copy link
Copy Markdown

vercel Bot commented Oct 17, 2025

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

Project Deployment Preview Comments Updated (UTC)
blog Ready Ready Preview Comment Oct 17, 2025 1:17pm

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Oct 17, 2025

Walkthrough

Implemented an image viewer feature with Viewer.js integration, refactored all templates to use layout inheritance, added fullscreen editor mode with auto-closing HTML tags, enhanced image upload error handling, and improved user feedback with flash messages. Also introduced dynamic timestamp field detection.

Changes

Cohort / File(s) Summary
Template Refactoring to Layout Inheritance
templates/index.html, templates/new.html, templates/edit.html, templates/post.html
Converted standalone HTML templates to Jinja2 blocks extending a new base layout.html, removing boilerplate and consolidating styling/script references.
New Base Layout
templates/layout.html
Introduced centralized base template with HTML structure, CDN assets (Viewer.js), static CSS/JS references, and toast message rendering.
Image Viewer Styling
static/css/style.css
Added .viewer-backdrop with blur effects, .viewer-canvas img with rounded corners and shadow, and cursor pointer to all images.
Post Page Styling
static/css/post.css
New stylesheet with responsive post layout, container styling, typography, and responsive adjustments for small screens.
Image Viewer & Filter Toggle
static/js/script.js
Added toggleFilter() function and Viewer.js initialization with click handlers for gallery images on DOMContentLoaded.
Fullscreen Editor Mode
static/js/editor.js
New file implementing fullscreen toggle, auto-closing HTML tags, Shift+Enter submission (POST /new), Escape to exit, and URL parameter handling.
Fullscreen Post Creation
static/js/new.js
New file with identical fullscreen editor features, auto-closing tags, and Shift+Enter submission for the new post form.
Backend Image Upload & Flash Messages
app.py
Replaced statusCode with status check for 409 conflicts in image upload handlers, added local fallback on non-409 errors, introduced resolve_timestamp_field() for dynamic timestamp detection, and added flash messages to delete_post.
Test Coverage Updates
tests/test_blog.py, tests/test_edit_post.py
Updated image selectors from "img" to ".image" class in assertion logic.
Image Viewer E2E Tests
tests/test_viewer.py
New test module with helper create_test_post_with_image() and tests for viewer on homepage/post page and zoom functionality via scroll events.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Browser as Browser/<br/>JS
    participant App as Flask App
    participant Storage as Remote Storage

    User->>Browser: Click image in post
    activate Browser
    Browser->>Browser: Viewer.js initializes<br/>modal overlay
    Browser->>Browser: Apply .viewer-backdrop<br/>and .viewer-canvas styles
    deactivate Browser
    
    Note over Browser: Modal appears with<br/>image full-screen
    
    User->>Browser: Scroll wheel (ctrl+key)
    activate Browser
    Browser->>Browser: Zoom image via<br/>Viewer.js zoom API
    deactivate Browser
    
    User->>Browser: Click outside / Press Esc
    activate Browser
    Browser->>Browser: Close modal,<br/>remove overlay
    deactivate Browser
Loading
sequenceDiagram
    actor User as User
    participant Editor as Fullscreen Editor
    participant Form as Browser Form
    participant App as Flask App

    User->>Editor: Toggle fullscreen<br/>(click button or URL param)
    activate Editor
    Editor->>Editor: Toggle .fullscreen class<br/>on UI elements
    Editor->>Editor: Update URL to ?fullscreen=true
    deactivate Editor
    
    User->>Editor: Type HTML-like tag<br/>(e.g., "<div>")
    activate Editor
    Editor->>Editor: On ">", auto-insert<br/>closing tag "</div>"
    Editor->>Editor: Position cursor<br/>between tags
    deactivate Editor
    
    User->>Editor: Press Shift+Enter
    activate Editor
    Editor->>Form: Gather title & content
    Editor->>App: POST /new with FormData
    deactivate Editor
    
    App-->>Editor: Success response
    activate Editor
    alt Fullscreen mode
        Editor->>Editor: Clear editor
    else Normal mode
        Editor->>Editor: Navigate to /new
    end
    deactivate Editor
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Rationale: The changes span multiple file types (backend, templates, CSS, JS) with mixed complexity. Template refactoring is repetitive but requires verification of block structure consistency across files. Image viewer integration is straightforward Viewer.js usage. Fullscreen editor and error handling logic are moderately complex. Backend timestamp detection and image upload error handling require careful attention. Test additions are straightforward but comprehensive.

Possibly related PRs

  • [Feature] Post editing functionality #13: Directly related—both PRs modify the post-editing backend flow (image upload error handling and flash messages) and refactor edit.html template with fullscreen editor features.

Poem

🐰 A rabbit hops through image galleries so bright,
With zoomed-in views and fullscreen delight!
Auto-closing tags in the editor's embrace,
Templates inherit grace from layout's base.
Flash messages bloom—errors handled with care,
The burrow's blog now flourishes fair! 🌼

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Out of Scope Changes Check ⚠️ Warning The PR includes several changes not directly related to the image viewing feature described in issue #11. In app.py, changes to implement dynamic timestamp field detection via resolve_timestamp_field() appear unrelated to image viewing and are not mentioned in the PR description. Similarly, the editor.js and new.js files introduce fullscreen editing functionality with auto-closing HTML tags and Shift+Enter submission, which extends beyond simply moving existing CSS/JavaScript to separate files as described. While template refactoring to use a base layout.html may provide supporting infrastructure for the image viewer, the timestamp detection system in app.py represents a distinct feature addition unconnected to the PR's stated objectives.
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The title "[Feature] Enhanced image viewing" directly aligns with the primary objective of the pull request and the linked issue #11. It clearly and concisely communicates the main focus—adding an image viewing feature—without being vague or overly broad. The title accurately reflects the core changeset, which implements a lightbox/modal gallery system using Viewer.js.
Linked Issues Check ✅ Passed The PR successfully implements the core requirements from issue #11: Viewer.js is integrated via CDN in layout.html to make images clickable and display them in a full-screen overlay modal; CSS styling in style.css provides the dark backdrop with blur effects and rounded corners; JavaScript in script.js initializes Viewer instances with zoom capability; tests in test_viewer.py verify zoom and viewer functionality work as expected. The implementation covers clickable images, modal display, background effects, rounded corners, zoom functionality via scroll gestures, multiple close mechanisms (supported by Viewer.js), responsive design, and acceptable performance through the optimized library.
Description Check ✅ Passed The PR description follows the repository template structure and includes all required sections: a clear summary explaining the Lightroom-style image viewing addition, marked type of change (bug fix, new feature, code refactor, UI/UX enhancement), linked issue reference (#11), changes introduced list, visual evidence (video), and a completed checklist. While the "Changes Introduced" section is somewhat brief, it adequately conveys the key modifications, and the optional "Additional Notes" section is not critical for approval.
✨ 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/11-enhanced-image-viewing

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.

@AmanatAliPanhwer AmanatAliPanhwer merged commit ccb43ac into main Oct 17, 2025
2 of 4 checks passed
@AmanatAliPanhwer AmanatAliPanhwer deleted the feat/11-enhanced-image-viewing branch October 17, 2025 13:20
Copy link
Copy Markdown
Contributor

@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: 6

Caution

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

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

37-37: Fix typo in event listener.

Line 37 has a typo: "DOMContentLaded" should be "DOMContentLoaded". This means the checkSession function is never called on page load.

Apply this diff:

-document.addEventListener("DOMContentLaded", checkSession);
+document.addEventListener("DOMContentLoaded", checkSession);
app.py (1)

343-357: Fix unsafe exception attribute access.

Line 346 attempts to access int(e.status) on a generic exception, but Python's base Exception class doesn't have a status attribute. This will raise an AttributeError if the Supabase exception doesn't have this attribute.

Apply this defensive fix:

                     except Exception as e:
                         print(f"Error uploading image to Superbase: {e}") 
                         try:
-                            if int(e.status) == 409:
+                            if hasattr(e, 'status') and int(e.status) == 409:
                                 image_url = f"{SUPABASE_URL}/storage/v1/object/public/{BLOG_IMAGES_BUCKET}/{filename}"
                             else:
                                 os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
                                 local_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
                                 with open(local_path, 'wb') as out_f:
                                     out_f.write(file_bytes)
                                 image_url = f"/static/uploads/{filename}"
                                 print(f"Saved image locally to {local_path}; using {image_url} as image URL")
                         except Exception as e2:
                             print(f"Failed to save image locally: {e2}")
                             image_url = None
🧹 Nitpick comments (7)
static/js/new.js (1)

49-51: Clarify popup display logic.

The popup is shown only when entering fullscreen (line 50), but the timeout to hide it (lines 59-61) runs unconditionally. This means the popup briefly appears and disappears even when exiting fullscreen, which may not be the intended behavior.

Consider moving the timeout inside the conditional:

     if (editor.classList.contains("full-screen")){
-    popup.classList.add('show');
+        popup.classList.add('show');
+        setTimeout(() => {
+            popup.classList.remove('show');
+        }, 1500);
     }
     if (container.classList.contains("full-screen")) {
         window.history.pushState({}, '', '?fullscreen=true');
     } else {
         window.history.pushState({}, '', window.location.pathname);
     }

-
-    setTimeout(() => {
-        popup.classList.remove('show');
-    }, 1500);
 }
static/css/style.css (1)

622-624: Consider scoping cursor: pointer to clickable images only.

Setting cursor: pointer on all img elements (line 622) may give misleading affordances if some images are not interactive (e.g., logos, icons, decorative images).

Consider scoping the cursor to images within galleries or with a specific class:

-img {
+.gallery img {
     cursor: pointer;
 }

This assumes all clickable images are within .gallery containers, as indicated by the viewer initialization logic in static/js/script.js.

templates/layout.html (1)

11-11: Consider adding defer or async to script.js.

The script at line 11 loads in the <head> without defer or async, which may block HTML parsing and delay page rendering.

Apply this diff to improve page load performance:

-    <script src="{{ url_for('static', filename='js/script.js') }}"></script>
+    <script src="{{ url_for('static', filename='js/script.js') }}" defer></script>

Using defer ensures the script executes after the DOM is parsed, which is appropriate since the script contains DOMContentLoaded listeners.

static/js/editor.js (2)

29-31: Clarify or remove unclear Backspace/Delete handler.

The handler for Backspace and Delete keys simply returns without performing any action or preventing default behavior. If this is intentional for future functionality, add a comment explaining the purpose. Otherwise, remove it.


63-93: Consider using this file only for edit operations.

The Shift+Enter handler submits to /new endpoint (line 73), but this file is loaded by templates/edit.html for editing posts. This creates confusion about the file's purpose. Consider:

  • Renaming to clarify scope (e.g., editor-shared.js)
  • Or splitting edit-specific and new-post-specific logic into separate files
templates/post.html (2)

6-6: Consider moving stylesheet link to base layout.

Loading the post-specific CSS inside the content block is unconventional. While functional, it's better practice to include stylesheets in the <head> section. Consider adding a styles block in layout.html or moving this to the base template's head.


11-16: Move inline styles to CSS file.

Line 14 uses an inline style width: 90%. This should be moved to static/css/post.css for better maintainability and separation of concerns.

In static/css/post.css:

.gallery .image {
    width: 90%;
}

Then remove the inline style:

-        <img src="{{ post.image }}" alt="Post Image" class="image"
-            style="width: 90%" />
+        <img src="{{ post.image }}" alt="Post Image" class="image" />
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9d22ba4 and d906dad.

⛔ Files ignored due to path filters (1)
  • tests/test_viewer_image.png is excluded by !**/*.png
📒 Files selected for processing (14)
  • app.py (2 hunks)
  • static/css/post.css (1 hunks)
  • static/css/style.css (1 hunks)
  • static/js/editor.js (1 hunks)
  • static/js/new.js (1 hunks)
  • static/js/script.js (1 hunks)
  • templates/edit.html (1 hunks)
  • templates/index.html (1 hunks)
  • templates/layout.html (1 hunks)
  • templates/new.html (1 hunks)
  • templates/post.html (1 hunks)
  • tests/test_blog.py (1 hunks)
  • tests/test_edit_post.py (2 hunks)
  • tests/test_viewer.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
tests/test_blog.py (1)
tests/conftest.py (1)
  • page (54-59)
tests/test_viewer.py (2)
tests/conftest.py (3)
  • page (54-59)
  • flask_app_url (16-51)
  • admin_logged_in_page (63-72)
static/js/script.js (2)
  • image (89-89)
  • viewer (90-95)
tests/test_edit_post.py (1)
tests/conftest.py (1)
  • page (54-59)
🪛 Ruff (0.14.0)
app.py

408-408: Do not catch blind exception: Exception

(BLE001)

⏰ 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: build
🔇 Additional comments (16)
tests/test_blog.py (1)

101-101: LGTM! Test selector updated to match new HTML structure.

The change from img to .image class selector aligns with the UI refactor where images now use a container with the .image class.

tests/test_edit_post.py (1)

115-115: LGTM! Test selectors updated consistently.

The changes from img to .image class selector are consistent with the UI refactor and align with the changes in tests/test_blog.py.

Also applies to: 131-131

static/css/style.css (1)

610-620: LGTM! Image viewer styling is well-implemented.

The viewer backdrop and canvas styling effectively uses CSS variables, includes Safari prefixes, and provides a polished visual experience with blur effects and shadows.

static/js/script.js (2)

72-82: LGTM! Filter toggle function works correctly.

The toggleFilter() function appropriately toggles the panel state and updates button text. The implementation is straightforward and functional.


84-103: LGTM! Image viewer initialization is well-structured.

The viewer initialization correctly:

  • Iterates over gallery elements
  • Configures Viewer with appropriate options
  • Prevents default click behavior
  • Shows the viewer on image click
templates/new.html (1)

1-23: LGTM! Clean refactor to layout inheritance.

The template successfully:

  • Extends the base layout
  • Moves inline JavaScript to an external file (static/js/new.js)
  • Maintains all form fields and functionality
  • Loads the script at the end of the content block for proper DOM availability

This improves maintainability and follows best practices for template organization.

templates/layout.html (2)

17-25: LGTM! Toast container implementation is clean.

The toast container correctly:

  • Uses Flask's get_flashed_messages with categories
  • Imports and renders the toast component
  • Provides a fixed position container for notifications

12-13: Verify and regenerate Viewer.js CDN integrity hashes using srihash.org.

The CDNJS supplied SRI hashes do not always match the actual CDN files, as documented in GitHub issue #14124. The template currently uses hardcoded SHA-512 hashes for Viewer.js v1.11.7 (CSS and JS). srihash.org is an easy-to-use tool to generate SRI hashes.

Confirm the integrity hashes are correct before deployment, or the Viewer.js assets may fail to load in browsers with SRI verification enabled.

static/css/post.css (1)

1-58: LGTM! Post page styling is well-structured.

The CSS file:

  • Consistently uses CSS variables from the design system
  • Provides responsive adjustments for mobile screens
  • Includes appropriate shadows and border radius for visual polish
  • Properly styles the .image class referenced by tests
templates/edit.html (1)

1-28: LGTM! Clean template refactoring.

The template correctly extends the base layout and maintains all necessary functionality. The separation of JavaScript into an external file (editor.js) improves maintainability.

app.py (2)

55-75: LGTM! Smart dynamic timestamp detection.

The resolve_timestamp_field() function elegantly handles different timestamp field naming conventions across database schemas. The fallback to 'timestamp' ensures robustness.


407-410: LGTM! Good user feedback additions.

The flash messages provide clear feedback to users about post deletion success or failure. This aligns well with the toast notification system introduced in the layout template.

tests/test_viewer.py (3)

67-107: Creative zoom verification approach!

The bounding box measurement technique for verifying zoom functionality is clever, especially given that the toolbar is disabled. This provides robust verification without relying on UI controls.


1-107: LGTM! Comprehensive viewer test coverage.

The test suite effectively covers:

  • Viewer activation on homepage
  • Viewer activation on post detail page
  • Zoom functionality verification

Tests follow Playwright best practices with proper fixtures, expectations, and cleanup.


8-27: Test image file is present in version control.

The verification confirms that tests/test_viewer_image.png exists at the expected location. The code will function properly without test failures related to missing image files.

templates/index.html (1)

1-73: No issues found—JavaScript functions are properly defined.

Both toggleFilter() and logoutUser() functions referenced in the template's onclick handlers are correctly defined in static/js/script.js. The JavaScript dependencies are satisfied.

Comment thread app.py
image_url = f"{SUPABASE_URL}/storage/v1/object/public/{BLOG_IMAGES_BUCKET}/{filename}"
except Exception as e:
print(f"Error uploading image to Superbase: {e}")
print(f"Error uploading image to Superbase: {e}")
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

Fix typo in error message.

Line 344 contains "Superbase" instead of "Supabase".

-                        print(f"Error uploading image to Superbase: {e}") 
+                        print(f"Error uploading image to Supabase: {e}") 
📝 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
print(f"Error uploading image to Superbase: {e}")
print(f"Error uploading image to Supabase: {e}")
🤖 Prompt for AI Agents
In app.py around line 344, the error message prints "Superbase" instead of
"Supabase"; update the string to "Error uploading image to Supabase: {e}" (fix
the typo) and scan nearby log messages for any other "Superbase" occurrences and
correct them to "Supabase".

Comment thread static/js/editor.js
Comment on lines +10 to +99
editor.addEventListener("keydown", function (event) {
const cursorPos = editor.selectionStart;
const value = editor.value;

// Handle auto-closing only when typing '>'
if (event.key === ">" && cursorPos > 1) {
const beforeCursor = value.substring(0, cursorPos);
const match = beforeCursor.match(/<([a-zA-Z0-9]+)$/);

if (match) {
event.preventDefault();
const tag = match[1];
const closingTag = `>${`</${tag}>`}`;

editor.value = value.substring(0, cursorPos) + closingTag + value.substring(cursorPos);
editor.selectionStart = editor.selectionEnd = cursorPos + 1;
}
}

if (event.key === "Backspace" || event.key === "Delete") {
return;
}
});

function FullScreen() {
const container = document.querySelector(".form");
const button = document.querySelector(".form").querySelector("button");
const full_screen_button = document.querySelector(".full-screen-button");
const form_text_input = document.querySelector(".form-text-input");
const editor = document.getElementById("editor");
const form_file_input = document.querySelector(".form-file-input");
const popup = document.getElementById('popup');

container.classList.toggle("full-screen");
button.classList.toggle("full-screen");
full_screen_button.classList.toggle("full-screen");
form_text_input.classList.toggle("full-screen");
form_file_input.classList.toggle("full-screen");
editor.classList.toggle("full-screen");
if (editor.classList.contains("full-screen")) {
popup.classList.add('show');
}
if (container.classList.contains("full-screen")) {
window.history.pushState({}, '', '?fullscreen=true');
} else {
window.history.pushState({}, '', window.location.pathname);
}


setTimeout(() => {
popup.classList.remove('show');
}, 1500);
}
editor.addEventListener('keydown', function (event) {
if (event.shiftKey && event.key === 'Enter') {
event.preventDefault();

const content = editor.value;
const title = document.querySelector('input[name="title"]').value;
const formData = new FormData();
formData.append('title', title);
formData.append('content', content);

fetch('/new', {
method: 'POST',
body: formData,
})
.then(response => {
if (response.ok) {
console.log('Post submitted successfully.');
if (editor.classList.contains("full-screen")) {
editor.value = '';
} else {
window.location.href = '/new';
}
} else {
alert('Error while submitting the post.');
}
})
.catch(error => {
console.error('Error:', error);
alert('Something went wrong while submitting the post.');
});
}
if (event.key === 'Escape') {
if (editor.classList.contains("full-screen")) {
FullScreen();
}
}
}); No newline at end of file
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.

🛠️ Refactor suggestion | 🟠 Major

Consolidate duplicate event listeners.

Two separate keydown event listeners are attached to the editor element (lines 10 and 63). This creates confusion and potential conflicts. Combine them into a single handler.

Merge both handlers:

 editor.addEventListener("keydown", function (event) {
     const cursorPos = editor.selectionStart;
     const value = editor.value;

     // Handle auto-closing only when typing '>'
     if (event.key === ">" && cursorPos > 1) {
         const beforeCursor = value.substring(0, cursorPos);
         const match = beforeCursor.match(/<([a-zA-Z0-9]+)$/);

         if (match) {
             event.preventDefault();
             const tag = match[1];
             const closingTag = `></${tag}>`;

             editor.value = value.substring(0, cursorPos) + closingTag + value.substring(cursorPos);
             editor.selectionStart = editor.selectionEnd = cursorPos + 1;
         }
     }

-    if (event.key === "Backspace" || event.key === "Delete") {
-        return;
-    }
-});
-
-function FullScreen() {
-    // ... (keep FullScreen function)
-}
-
-editor.addEventListener('keydown', function (event) {
+    // Handle Shift+Enter for post submission
     if (event.shiftKey && event.key === 'Enter') {
         event.preventDefault();

         const content = editor.value;
         const title = document.querySelector('input[name="title"]').value;
         const formData = new FormData();
         formData.append('title', title);
         formData.append('content', content);

         fetch('/new', {
             method: 'POST',
             body: formData,
         })
             .then(response => {
                 if (response.ok) {
                     console.log('Post submitted successfully.');
                     if (editor.classList.contains("full-screen")) {
                         editor.value = '';
                     } else {
                         window.location.href = '/new';
                     }
                 } else {
                     alert('Error while submitting the post.');
                 }
             })
             .catch(error => {
                 console.error('Error:', error);
                 alert('Something went wrong while submitting the post.');
             });
     }
+    
+    // Handle Escape to exit fullscreen
     if (event.key === 'Escape') {
         if (editor.classList.contains("full-screen")) {
             FullScreen();
         }
     }
 });

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

🤖 Prompt for AI Agents
In static/js/editor.js around lines 10 to 99 there are two separate
editor.keydown handlers; merge them into one to avoid conflicts: keep the
auto-closing tag logic (trigger only on key === '>' with the match and
preventDefault, insert closing tag and set caret) but do that after early-return
guards for Backspace/Delete; include the Shift+Enter form submit logic
(preventDefault, build FormData, fetch and handle success/error and clearing
editor only when in full-screen) and the Escape key handling (toggle FullScreen
if in full-screen). Ensure the merged handler reuses the existing editor
variable, preserves event.preventDefault calls where used, maintains
caret/selection updates, and keeps ordering such that special keys
(Backspace/Delete) return early, '>' handling runs before form submission on
Shift+Enter, and Escape handling runs last.

Comment thread static/js/editor.js
Comment on lines +14 to +27
// Handle auto-closing only when typing '>'
if (event.key === ">" && cursorPos > 1) {
const beforeCursor = value.substring(0, cursorPos);
const match = beforeCursor.match(/<([a-zA-Z0-9]+)$/);

if (match) {
event.preventDefault();
const tag = match[1];
const closingTag = `>${`</${tag}>`}`;

editor.value = value.substring(0, cursorPos) + closingTag + value.substring(cursorPos);
editor.selectionStart = editor.selectionEnd = cursorPos + 1;
}
}
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 | 🔴 Critical

Fix syntax error in auto-closing tag logic.

Line 22 contains a malformed template literal. The syntax >${`</${tag}>`}` will result in incorrect string concatenation.

Apply this fix:

-            const closingTag = `>${`</${tag}>`}`;
+            const closingTag = `></${tag}>`;
📝 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
// Handle auto-closing only when typing '>'
if (event.key === ">" && cursorPos > 1) {
const beforeCursor = value.substring(0, cursorPos);
const match = beforeCursor.match(/<([a-zA-Z0-9]+)$/);
if (match) {
event.preventDefault();
const tag = match[1];
const closingTag = `>${`</${tag}>`}`;
editor.value = value.substring(0, cursorPos) + closingTag + value.substring(cursorPos);
editor.selectionStart = editor.selectionEnd = cursorPos + 1;
}
}
// Handle auto-closing only when typing '>'
if (event.key === ">" && cursorPos > 1) {
const beforeCursor = value.substring(0, cursorPos);
const match = beforeCursor.match(/<([a-zA-Z0-9]+)$/);
if (match) {
event.preventDefault();
const tag = match[1];
const closingTag = `></${tag}>`;
editor.value = value.substring(0, cursorPos) + closingTag + value.substring(cursorPos);
editor.selectionStart = editor.selectionEnd = cursorPos + 1;
}
}
🤖 Prompt for AI Agents
static/js/editor.js lines 14-27: The auto-closing tag logic uses a malformed
template literal; replace the broken expression with a proper closing-tag
string. Set closingTag to the closing tag constructed from the captured tag (for
example the string "</" + tag + ">" i.e. </tag>), then insert that string at the
cursor and keep the selection logic as-is.

Comment thread static/js/new.js
if (match) {
event.preventDefault();
const tag = match[1];
const closingTag = `>${`</${tag}>`}`;
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 | 🔴 Critical

Fix the template literal syntax error.

The string concatenation on line 22 has invalid syntax. The closing tag should be a plain string, not nested template literals.

Apply this diff to fix the syntax:

-            const closingTag = `>${`</${tag}>`}`;
+            const closingTag = `></${tag}>`;
🤖 Prompt for AI Agents
In static/js/new.js around line 22 the closingTag assignment uses nested
template literals causing a syntax error; replace it with a single string that
represents the closing HTML tag by interpolating the tag variable in one
template literal (i.e. backticks with </ then the tag then >) or by
concatenating the pieces into a normal string ('</' + tag + '>') so the closing
tag is a valid plain string.

Comment thread static/js/new.js
Comment on lines +34 to +62
function FullScreen() {
const container = document.querySelector(".form");
const button = document.querySelector(".form").querySelector("button");
const full_screen_button = document.querySelector(".full-screen-button");
const form_text_input = document.querySelector(".form-text-input");
const editor = document.getElementById("editor");
const form_file_input = document.querySelector(".form-file-input");
const popup = document.getElementById('popup');

container.classList.toggle("full-screen");
button.classList.toggle("full-screen");
full_screen_button.classList.toggle("full-screen");
form_text_input.classList.toggle("full-screen");
form_file_input.classList.toggle("full-screen");
editor.classList.toggle("full-screen");
if (editor.classList.contains("full-screen")){
popup.classList.add('show');
}
if (container.classList.contains("full-screen")) {
window.history.pushState({}, '', '?fullscreen=true');
} else {
window.history.pushState({}, '', window.location.pathname);
}


setTimeout(() => {
popup.classList.remove('show');
}, 1500);
}
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.

🛠️ Refactor suggestion | 🟠 Major

Add null checks for DOM elements.

The FullScreen() function queries multiple DOM elements without null checks. If any element is missing (e.g., due to template changes or loading order issues), the function will throw runtime errors.

Apply this diff to add defensive checks:

 function FullScreen() {
     const container = document.querySelector(".form");
     const button = document.querySelector(".form").querySelector("button");
     const full_screen_button = document.querySelector(".full-screen-button");
     const form_text_input = document.querySelector(".form-text-input");
     const editor = document.getElementById("editor");
     const form_file_input = document.querySelector(".form-file-input");
     const popup = document.getElementById('popup');
+
+    if (!container || !button || !full_screen_button || !form_text_input || !editor || !form_file_input || !popup) {
+        console.error('FullScreen: Required elements not found');
+        return;
+    }

     container.classList.toggle("full-screen");
📝 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
function FullScreen() {
const container = document.querySelector(".form");
const button = document.querySelector(".form").querySelector("button");
const full_screen_button = document.querySelector(".full-screen-button");
const form_text_input = document.querySelector(".form-text-input");
const editor = document.getElementById("editor");
const form_file_input = document.querySelector(".form-file-input");
const popup = document.getElementById('popup');
container.classList.toggle("full-screen");
button.classList.toggle("full-screen");
full_screen_button.classList.toggle("full-screen");
form_text_input.classList.toggle("full-screen");
form_file_input.classList.toggle("full-screen");
editor.classList.toggle("full-screen");
if (editor.classList.contains("full-screen")){
popup.classList.add('show');
}
if (container.classList.contains("full-screen")) {
window.history.pushState({}, '', '?fullscreen=true');
} else {
window.history.pushState({}, '', window.location.pathname);
}
setTimeout(() => {
popup.classList.remove('show');
}, 1500);
}
function FullScreen() {
const container = document.querySelector(".form");
const button = document.querySelector(".form").querySelector("button");
const full_screen_button = document.querySelector(".full-screen-button");
const form_text_input = document.querySelector(".form-text-input");
const editor = document.getElementById("editor");
const form_file_input = document.querySelector(".form-file-input");
const popup = document.getElementById('popup');
if (!container || !button || !full_screen_button || !form_text_input || !editor || !form_file_input || !popup) {
console.error('FullScreen: Required elements not found');
return;
}
container.classList.toggle("full-screen");
button.classList.toggle("full-screen");
full_screen_button.classList.toggle("full-screen");
form_text_input.classList.toggle("full-screen");
form_file_input.classList.toggle("full-screen");
editor.classList.toggle("full-screen");
if (editor.classList.contains("full-screen")){
popup.classList.add('show');
}
if (container.classList.contains("full-screen")) {
window.history.pushState({}, '', '?fullscreen=true');
} else {
window.history.pushState({}, '', window.location.pathname);
}
setTimeout(() => {
popup.classList.remove('show');
}, 1500);
}
🤖 Prompt for AI Agents
In static/js/new.js around lines 34 to 62, the FullScreen() function calls
querySelector/getElementById and immediately accesses properties on the returned
nodes, which will throw if any element is missing; update the function to guard
every DOM lookup with a null check before toggling classes or calling methods
(e.g., only call classList.toggle/classList.contains and history.pushState when
the element exists), bail out early or skip operations for missing elements, and
ensure the popup timeout is set only if popup was found.

Comment thread static/js/new.js
Comment on lines +63 to +92
editor.addEventListener('keydown', function (event) {
if (event.shiftKey && event.key === 'Enter') {
event.preventDefault();

const content = editor.value;
const title = document.querySelector('input[name="title"]').value;
const formData = new FormData();
formData.append('title', title);
formData.append('content', content);

fetch('/new', {
method: 'POST',
body: formData,
})
.then(response => {
if (response.ok) {
console.log('Post submitted successfully.');
if (editor.classList.contains("full-screen")) {
editor.value = '';
} else {
window.location.href = '/new';
}
} else {
alert('Error while submitting the post.');
}
})
.catch(error => {
console.error('Error:', error);
alert('Something went wrong while submitting the post.');
});
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

Image upload not included in fetch submission.

The Shift+Enter submission (lines 63-92) uses FormData but only appends title and content. The image file input is not included, so users cannot upload images via this shortcut.

Consider whether image upload should be supported via Shift+Enter submission. If yes, apply this diff:

         const content = editor.value;
         const title = document.querySelector('input[name="title"]').value;
+        const imageInput = document.querySelector('input[name="image"]');
         const formData = new FormData();
         formData.append('title', title);
         formData.append('content', content);
+        if (imageInput.files.length > 0) {
+            formData.append('image', imageInput.files[0]);
+        }

If image upload via Shift+Enter is not intended, consider adding user feedback (e.g., a toast notification) to inform users that only text content will be submitted.

📝 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
editor.addEventListener('keydown', function (event) {
if (event.shiftKey && event.key === 'Enter') {
event.preventDefault();
const content = editor.value;
const title = document.querySelector('input[name="title"]').value;
const formData = new FormData();
formData.append('title', title);
formData.append('content', content);
fetch('/new', {
method: 'POST',
body: formData,
})
.then(response => {
if (response.ok) {
console.log('Post submitted successfully.');
if (editor.classList.contains("full-screen")) {
editor.value = '';
} else {
window.location.href = '/new';
}
} else {
alert('Error while submitting the post.');
}
})
.catch(error => {
console.error('Error:', error);
alert('Something went wrong while submitting the post.');
});
editor.addEventListener('keydown', function (event) {
if (event.shiftKey && event.key === 'Enter') {
event.preventDefault();
const content = editor.value;
const title = document.querySelector('input[name="title"]').value;
const imageInput = document.querySelector('input[name="image"]');
const formData = new FormData();
formData.append('title', title);
formData.append('content', content);
if (imageInput.files.length > 0) {
formData.append('image', imageInput.files[0]);
}
fetch('/new', {
method: 'POST',
body: formData,
})
.then(response => {
if (response.ok) {
console.log('Post submitted successfully.');
if (editor.classList.contains("full-screen")) {
editor.value = '';
} else {
window.location.href = '/new';
}
} else {
alert('Error while submitting the post.');
}
})
.catch(error => {
console.error('Error:', error);
alert('Something went wrong while submitting the post.');
});
🤖 Prompt for AI Agents
In static/js/new.js around lines 63-92 the FormData only includes title and
content so image inputs are not uploaded when users press Shift+Enter; fix by
selecting the image file input (e.g., document.querySelector for the file input
by name or id), check if it has files and if so append the file(s) to formData
(append a single file as 'image' or loop append multiple with the same key),
then proceed with fetch; alternatively, if image upload via Shift+Enter is
intentionally unsupported, add a small user-facing notification before
submission to inform users that images will not be uploaded by this shortcut.

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

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature: Enhanced Image Viewing

1 participant