Archived
feat: customizable mobile reading experience
- Backend: Chapter, ReadingProgress, ReadingSettings models - Backend: Chapter API (TOC + content), progress tracking, settings CRUD - Frontend: ReadingPage with chapter navigation - Frontend: TableOfContents drawer - Frontend: ReadingSettingsPanel (theme, font, size, orientation) - Frontend: Custom hooks for settings, chapters, progress tracking - CSS: Mobile-first reading view with sepia/dark/light/paper themes - Route: /reader/:bookId reading view from book detail page - Docs: 001-customizable-mobile-reading-experience.md
This commit is contained in:
@@ -18,4 +18,71 @@ class Book(models.Model):
|
||||
ordering = ["title"]
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.title
|
||||
return self.title
|
||||
|
||||
|
||||
class Chapter(models.Model):
|
||||
"""A chapter within a book, containing the text/markdown content."""
|
||||
|
||||
book = models.ForeignKey(
|
||||
Book,
|
||||
on_delete=models.CASCADE,
|
||||
related_name="chapters",
|
||||
db_index=True,
|
||||
)
|
||||
title = models.CharField(max_length=512)
|
||||
number = models.PositiveIntegerField()
|
||||
content = models.TextField(blank=True, default="")
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
db_table = "books_chapter"
|
||||
verbose_name = "Chapter"
|
||||
verbose_name_plural = "Chapters"
|
||||
ordering = ["book", "number"]
|
||||
constraints = [
|
||||
models.UniqueConstraint(
|
||||
fields=["book", "number"],
|
||||
name="uq_book_chapter_number",
|
||||
)
|
||||
]
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.book.title} — Ch. {self.number}: {self.title}"
|
||||
|
||||
|
||||
class ReadingProgress(models.Model):
|
||||
"""Tracks a user's reading progress within a book."""
|
||||
|
||||
user = models.ForeignKey(
|
||||
"users.User",
|
||||
on_delete=models.CASCADE,
|
||||
related_name="reading_progress",
|
||||
db_index=True,
|
||||
)
|
||||
book = models.ForeignKey(
|
||||
Book,
|
||||
on_delete=models.CASCADE,
|
||||
related_name="reading_progress",
|
||||
db_index=True,
|
||||
)
|
||||
current_chapter = models.PositiveIntegerField(default=1)
|
||||
current_position = models.PositiveIntegerField(default=0)
|
||||
percentage = models.FloatField(default=0.0)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
db_table = "books_reading_progress"
|
||||
verbose_name = "Reading Progress"
|
||||
verbose_name_plural = "Reading Progress"
|
||||
ordering = ["-updated_at"]
|
||||
constraints = [
|
||||
models.UniqueConstraint(
|
||||
fields=["user", "book"],
|
||||
name="uq_reading_progress_user_book",
|
||||
)
|
||||
]
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.user} — {self.book.title} ({self.percentage:.0f}%)"
|
||||
Reference in New Issue
Block a user