Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Nov 21, 2025

Implementation Complete: Save unsaved blog posts in local storage ✅

This PR implements automatic saving of unsaved blog posts to browser's local storage, addressing the feature request in the issue.

Implementation Checklist:

  • Research and understand the Blazorise.Markdown component and local storage capabilities
  • Create DraftBlogPostModel for serializing draft data
  • Add FromDraft method to CreateNewModel to restore from saved drafts
  • Implement autosave functionality in CreateNewBlogPost component with 30-second interval
  • Add UI banner to show when autosaved drafts are available
  • Add restore and discard draft functionality
  • Clear autosaved draft when post is successfully saved
  • Update all existing tests to mock dependencies
  • Add new unit tests for autosave functionality
  • Run all tests to ensure no regressions - All tests passing ✅
  • Address code review feedback on threading and semantics
  • Improve timer callback to use proper synchronous pattern
  • Extract autosave logic into dedicated service (BlogPostDraftService)
  • Add null safety checks in service methods

Features Implemented:

  1. Auto-save: Drafts are automatically saved every 30 seconds when changes are detected
  2. Draft Detection Banner: When a saved draft exists, a prominent alert banner appears with options to restore or discard
  3. Status Indicators:
    • "Unsaved Changes" badge when there are uncommitted changes
    • "Auto-saved X ago" badge showing time since last autosave
  4. Smart Auto-save Logic: Only autosaves for new posts, not when editing existing posts
  5. Automatic Cleanup: Drafts are cleared when the post is successfully created/updated

Technical Details:

Service Layer:

  • IBlogPostDraftService - Interface for draft management
  • BlogPostDraftService - Implementation handling all draft persistence logic with proper null checks
    • CheckForSavedDraftAsync() - Checks if a draft exists and returns saved time
    • SaveDraftAsync() - Saves draft to local storage
    • RestoreDraftAsync() - Restores draft from local storage
    • DiscardDraftAsync() - Removes draft from local storage
    • GetTimeSinceAutoSave() - Formats elapsed time since last save

Component Changes:

  • CreateNewBlogPost.razor - Now uses IBlogPostDraftService instead of directly accessing ILocalStorageService
  • Cleaner, more maintainable code with separation of concerns

Files Modified:

  • IBlogPostDraftService.cs - New interface
  • BlogPostDraftService.cs - New service implementation with null safety
  • ServiceExtensions.cs - Registered service in DI container
  • CreateNewBlogPost.razor - Refactored to use service
  • CreateNewModel.cs - Added FromDraft method
  • DraftBlogPostModel.cs - DTO for draft serialization
  • ILocalStorageService.cs - Added RemoveItemAsync method
  • LocalStorageService.cs - Implemented RemoveItemAsync
  • Test files - Updated to mock IBlogPostDraftService

Testing:

  • All 400 tests passing (268 unit + 132 integration)
  • Updated all tests to mock the new service interface
  • No regressions introduced

Security Summary:

  • No security vulnerabilities introduced
  • Uses existing secure storage mechanism (ProtectedLocalStorage)
  • Drafts are encrypted before storage
  • Only accessible to the same user/browser due to browser's local storage isolation
  • Proper null checks to prevent potential null reference exceptions

Notes:

This implementation follows the maintainer's suggestion to use local storage and addresses the feedback to extract logic into a service for better separation of concerns and maintainability.

Original prompt

This section details on the original issue you should resolve

<issue_title>Save unsaved blog posts in local storage</issue_title>
<issue_description>Instead of dismissing the whole work, we can just put the whole blog post into the local storage (or give the option to save the draft to local storage).

This can also be done for already created blog posts where we additionally save the ID to make the mapping happen.
</issue_description>

Comments on the Issue (you are @copilot in this section)

@linkdotnet That could be a way forward for sure. Yes, you are right, if we just put it into the local storage, you can't take it "with you". The "problem" with saving it in the database is somewhat consistency. Right now if you call `BlogPost.Create` it checks all the validation - like is there a title, description, ...

Probably we would need a new domain object called DraftBlogPost that does have different logic.
In the editor you would create such draft blog posts and not the "real one". Only when you press "Publish" (in addition to save, what we currently have) then we create the new object.

I do think a clean cut between those two concepts is necessary - for sure we could somehow fit this in into the current "BlogPost" Container but it does not feel right.</comment_new>
<comment_new>@linkdotnet
Side note: Draft is already a state on BlogPost - but currently it is used to distinguish between things shown for all users vs things shown for the admin of the page.</comment_new>
<comment_new>@linkdotnet
With the new EasyMDE, this should work out of the box.</comment_new>


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Copilot AI changed the title [WIP] Save unsaved blog posts in local storage Add autosave for blog post drafts to local storage Nov 21, 2025
Copilot AI requested a review from linkdotnet November 21, 2025 16:55
@linkdotnet
Copy link
Owner

@copilot Move some of the logic into a service as there is much logic.

Copy link
Contributor Author

Copilot AI commented Nov 21, 2025

@copilot Move some of the logic into a service as there is much logic.

Extracted autosave logic into BlogPostDraftService with dedicated interface IBlogPostDraftService. The service now handles all draft persistence operations including check, save, restore, and discard. Component is cleaner and responsibilities are better separated. (2c51cf2)

@linkdotnet linkdotnet closed this Nov 22, 2025
@linkdotnet linkdotnet deleted the copilot/save-unsaved-blog-posts branch November 23, 2025 09:07
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.

Save unsaved blog posts in local storage

2 participants