+ |
+
+ mdi-check-circle
+ {{ chapter.name }}
+
+ |
{{ chapter.wordCount.total }} |
{{ chapter.wordCount.unique }} |
{{ chapter.wordCount.highlighted }} |
@@ -56,8 +70,8 @@
diff --git a/resources/js/themes.js b/resources/js/themes.js
index b96516e7..703426eb 100644
--- a/resources/js/themes.js
+++ b/resources/js/themes.js
@@ -4,7 +4,7 @@ export default {
foreground: '#FFFFFF',
navigation: '#FFFFFF',
primary: '#AB8875',
-
+
gray: '#E9EAEC',
gray2: '#E4E4E4',
gray3: '#F0F0F0',
@@ -14,7 +14,7 @@ export default {
info: '#057CBC',
success: '#3DCF59',
warning: '#FFA73C',
-
+
text: '#333333',
textDark: '#333333',
@@ -22,6 +22,8 @@ export default {
highlightedWordText: '#333333',
highlightedWordBackground: '#71EB7A',
newWordBackground: '#ffD08B',
+ chapterReadBorder: '#8BC68F',
+ chapterLastReadBackground: '#E8F5E9',
},
dark: {
background: '#1C1B20',
@@ -41,7 +43,9 @@ export default {
readerWordSelection: '#B6B6B6',
highlightedWordText: '#121212',
highlightedWordBackground: '#49A74F',
- newWordBackground: '#ffD08B'
+ newWordBackground: '#ffD08B',
+ chapterReadBorder: '#4A8B4F',
+ chapterLastReadBackground: '#1B3A1D',
},
eink: {
name: '#000000',
@@ -59,7 +63,7 @@ export default {
info: '#057CBC',
success: '#000000',
warning: '#000000',
-
+
text: '#000000',
textDark: '#000000',
@@ -67,5 +71,7 @@ export default {
highlightedWordText: '#FFFFFF',
highlightedWordBackground: '#000000',
newWordBackground: '#000000',
+ chapterReadBorder: '#888888',
+ chapterLastReadBackground: '#F0F0F0',
},
-}
\ No newline at end of file
+}
diff --git a/resources/sass/Library/BookChapters.scss b/resources/sass/Library/BookChapters.scss
index 333256a1..af85777f 100644
--- a/resources/sass/Library/BookChapters.scss
+++ b/resources/sass/Library/BookChapters.scss
@@ -1,4 +1,13 @@
.book-chapters {
+ .chapter-read {
+ border-left: 4px solid var(--v-chapterReadBorder-base);
+ }
+
+ .chapter-last-read {
+ border-left: 4px solid var(--v-success-base);
+ background-color: var(--v-chapterLastReadBackground-base) !important;
+ }
+
.highlighted-words {
background-color: var(--v-highlightedWordBackground-base);
color: var(--v-highlightedWordText-base);
@@ -25,4 +34,4 @@
.importing-text {
color: var(--v-warning-base);
}
-}
\ No newline at end of file
+}
diff --git a/resources/sass/TextReader/TextReaderChapterList.scss b/resources/sass/TextReader/TextReaderChapterList.scss
index 9a0357c1..7815bdea 100644
--- a/resources/sass/TextReader/TextReaderChapterList.scss
+++ b/resources/sass/TextReader/TextReaderChapterList.scss
@@ -1,4 +1,13 @@
#text-reader-chapter-list {
+ tr.chapter-read {
+ border-left: 4px solid var(--v-chapterReadBorder-base);
+ }
+
+ tr.chapter-last-read {
+ border-left: 4px solid var(--v-success-base);
+ background-color: var(--v-chapterLastReadBackground-base) !important;
+ }
+
span.highlighted{
display: block;
margin: auto;
@@ -48,4 +57,4 @@
display: none;
}
}
-}
\ No newline at end of file
+}
diff --git a/tests/Feature/ChapterReadTrackingTest.php b/tests/Feature/ChapterReadTrackingTest.php
new file mode 100644
index 00000000..f1adedeb
--- /dev/null
+++ b/tests/Feature/ChapterReadTrackingTest.php
@@ -0,0 +1,112 @@
+user = User::factory()->create();
+ $this->book = Book::factory()->create([
+ 'user_id' => $this->user->id,
+ ]);
+ }
+
+ public function test_chapters_endpoint_returns_read_count(): void
+ {
+ Chapter::factory()->create([
+ 'user_id' => $this->user->id,
+ 'book_id' => $this->book->id,
+ 'read_count' => 3,
+ ]);
+
+ $response = $this->actingAs($this->user)->post('/chapters', [
+ 'bookId' => $this->book->id,
+ ]);
+
+ $response->assertStatus(200);
+ $data = $response->json();
+ $this->assertArrayHasKey('chapters', $data);
+ $this->assertCount(1, $data['chapters']);
+ $this->assertEquals(3, $data['chapters'][0]['read_count']);
+ }
+
+ public function test_chapters_endpoint_returns_updated_at(): void
+ {
+ Chapter::factory()->create([
+ 'user_id' => $this->user->id,
+ 'book_id' => $this->book->id,
+ ]);
+
+ $response = $this->actingAs($this->user)->post('/chapters', [
+ 'bookId' => $this->book->id,
+ ]);
+
+ $response->assertStatus(200);
+ $data = $response->json();
+ $this->assertArrayHasKey('updated_at', $data['chapters'][0]);
+ $this->assertNotNull($data['chapters'][0]['updated_at']);
+ }
+
+ public function test_finish_chapter_increments_read_count(): void
+ {
+ $chapter = Chapter::factory()->create([
+ 'user_id' => $this->user->id,
+ 'book_id' => $this->book->id,
+ 'read_count' => 0,
+ ]);
+
+ $response = $this->actingAs($this->user)->post('/chapters/finish', [
+ 'chapterId' => $chapter->id,
+ 'autoMoveWordsToKnown' => false,
+ 'uniqueWords' => [],
+ 'autoLevelUpWords' => false,
+ 'leveledUpWords' => [],
+ 'leveledUpPhrases' => [],
+ 'language' => 'english',
+ ]);
+
+ $response->assertStatus(200);
+
+ $chapter->refresh();
+ $this->assertEquals(1, $chapter->read_count);
+ }
+
+ public function test_finish_chapter_updates_timestamp(): void
+ {
+ $chapter = Chapter::factory()->create([
+ 'user_id' => $this->user->id,
+ 'book_id' => $this->book->id,
+ 'read_count' => 0,
+ 'updated_at' => now()->subDay(),
+ ]);
+
+ $originalTimestamp = $chapter->updated_at;
+
+ $this->actingAs($this->user)->post('/chapters/finish', [
+ 'chapterId' => $chapter->id,
+ 'autoMoveWordsToKnown' => false,
+ 'uniqueWords' => [],
+ 'autoLevelUpWords' => false,
+ 'leveledUpWords' => [],
+ 'leveledUpPhrases' => [],
+ 'language' => 'english',
+ ]);
+
+ $chapter->refresh();
+ $this->assertTrue($chapter->updated_at->gt($originalTimestamp));
+ }
+}