diff --git a/app/Http/Controllers/ComicController.php b/app/Http/Controllers/ComicController.php
index bcd6971..625af43 100644
--- a/app/Http/Controllers/ComicController.php
+++ b/app/Http/Controllers/ComicController.php
@@ -77,15 +77,9 @@ class ComicController extends Controller
]);
}
- public function index(Request $request): Response
- {
- $params = [];
- if ($request->has('tag')) {
- $params['theme'] = $request->get('tag');
- }
-
- $comics = $this->copyManga->comics(30, $request->header('offset', 0), $request->get('top', 'all'), $params);
-
+ // Internal function for Upsert comics to db
+ protected function comicsUpsert($comics): void
+ {
// Prep the array for upsert
$comicsUpsertArray = [];
$authorsUpsertArray = [];
@@ -98,7 +92,7 @@ class ComicController extends Controller
'alias' => '{}',
'description' => '',
'cover' => $comic['cover'],
- 'upstream_updated_at' => $comic['datetime_updated'],
+ 'upstream_updated_at' => $comic['datetime_updated'] ?? null,
];
foreach ($comic['author'] as $author) {
@@ -122,6 +116,17 @@ class ComicController extends Controller
$comicObj->authors()->sync($authorObj);
}
}
+ }
+
+ public function index(Request $request): Response
+ {
+ $params = [];
+ if ($request->has('tag')) {
+ $params['theme'] = $request->get('tag');
+ }
+
+ $comics = $this->copyManga->comics(30, $request->header('offset', 0), $request->get('top', 'all'), $params);
+ $this->comicsUpsert($comics);
return Inertia::render('Comic/Index', [
'comics' => $comics,
@@ -129,18 +134,57 @@ class ComicController extends Controller
]);
}
+ public function author(Request $request, string $author): Response
+ {
+ $params = [];
+ $params['author'] = $author;
+
+ $comics = $this->copyManga->comics(30, $request->header('offset', 0), $request->get('top', 'all'), $params);
+ $this->comicsUpsert($comics);
+
+ return Inertia::render('Comic/Index', [
+ 'comics' => $comics,
+ 'offset' => $request->header('offset', 0)
+ ]);
+ }
+
+ public function search(Request $request, string $search): Response
+ {
+ $comics = $this->copyManga->search($search, 30, $request->header('offset', 0));
+
+ // Seacrh API is limited, no upsert
+
+ return Inertia::render('Comic/Index', [
+ 'comics' => $comics,
+ 'offset' => $request->header('offset', 0)
+ ]);
+ }
+
public function chapters(Request $request, string $pathword = ''): Response
{
$comic = $this->copyManga->comic($pathword);
$chapters = $this->copyManga->chapters($pathword, 200, 0, [], $request->get('group', 'default'));
// Get the comic object and fill other parameters
- $comicObject = Comic::where('pathword', $pathword)->first();
- $comicObject->uuid = $comic['comic']['uuid'];
- $comicObject->alias = explode(',', $comic['comic']['alias']);
- $comicObject->description = $comic['comic']['brief'];
- $comicObject->metadata = $comic;
- $comicObject->save();
+ try {
+ $comicObject = Comic::where('pathword', $pathword)->firstOrFail();
+ $comicObject->uuid = $comic['comic']['uuid'];
+ $comicObject->alias = explode(',', $comic['comic']['alias']);
+ $comicObject->description = $comic['comic']['brief'];
+ $comicObject->metadata = $comic;
+ $comicObject->save();
+ } catch (ModelNotFoundException $e) {
+ $comicObject = Comic::create([
+ 'name' => $comic['comic']['name'],
+ 'pathword' => $comic['comic']['path_word'],
+ 'cover' => $comic['comic']['cover'],
+ 'upstream_updated_at' => $comic['comic']['datetime_updated'],
+ 'uuid' => $comic['comic']['uuid'],
+ 'alias' => explode(',', $comic['comic']['alias']),
+ 'description' => $comic['comic']['brief'],
+ 'metadata' => $comic
+ ]);
+ }
// Get the authors and update the pathword
foreach ($comic['comic']['author'] as $author) {
diff --git a/app/Models/Comic.php b/app/Models/Comic.php
index f3c9c9f..0866af3 100644
--- a/app/Models/Comic.php
+++ b/app/Models/Comic.php
@@ -8,6 +8,18 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
class Comic extends Model
{
+
+ protected $fillable = [
+ 'pathword',
+ 'uuid',
+ 'name',
+ 'alias',
+ 'description',
+ 'cover',
+ 'upstream_updated_at',
+ 'metadata',
+ ];
+
protected function casts(): array
{
return [
diff --git a/app/Remote/CopyManga.php b/app/Remote/CopyManga.php
index 8d2eb44..9b626c9 100644
--- a/app/Remote/CopyManga.php
+++ b/app/Remote/CopyManga.php
@@ -209,9 +209,33 @@ class CopyManga
$parameters['limit'] = $limit;
$parameters['offset'] = $offset;
$parameters['top'] = $top;
+ //OPTIONS
+ // https://api.mangacopy.com/api/v3/comics?format=json&platform=1&q=x&limit=30&offset=0&top=all"
+ // https://api.mangacopy.com/api/v3/search/comic?platform=1&q=x&limit=20&offset=0&q_type=&_update=true
+
return $this->execute($this->buildUrl("comics", $parameters));
}
+ /**
+ * Search comic by name
+ *
+ * @param string $item
+ * @param int $limit
+ * @param int $offset
+ * @return mixed|string
+ * @throws GuzzleException
+ */
+ public function search(string $item = '', int $limit = 28, int $offset = 0)
+ {
+ $parameters['q'] = $item;
+ $parameters['q_type'] = "";
+ $parameters['_update'] = true;
+ $parameters['limit'] = $limit;
+ $parameters['offset'] = $offset;
+
+ return $this->execute($this->buildUrl("search/comic", $parameters));
+ }
+
/**
* Get comic info
*
diff --git a/resources/js/Pages/Comic/Chapters.jsx b/resources/js/Pages/Comic/Chapters.jsx
index dfcbf4a..74f0ea5 100644
--- a/resources/js/Pages/Comic/Chapters.jsx
+++ b/resources/js/Pages/Comic/Chapters.jsx
@@ -113,7 +113,11 @@ export default function Chapters({ auth, comic, chapters, histories }) {
| Authors |
- { comic.comic.author.map(a => { a.name }) } |
+ { comic.comic.author.map(a => (
+
+ { a.name }
+
+ ) ) } |
| Description |
diff --git a/resources/js/Pages/Comic/Index.jsx b/resources/js/Pages/Comic/Index.jsx
index 3330f87..d09b252 100644
--- a/resources/js/Pages/Comic/Index.jsx
+++ b/resources/js/Pages/Comic/Index.jsx
@@ -12,7 +12,7 @@ import { useToast } from '@/hooks/use-toast.js';
export default function Index({ comics, offset, auth }) {
- const url = new URL(window.location).searchParams;
+ const url = new URL(window.location); //searchParams
const [favourites, setFavourites] = useState(auth.user.favourites);
const { toast } = useToast();
@@ -43,7 +43,11 @@ export default function Index({ comics, offset, auth }) {
{ props.name }
- { props.author.map(a => { a.name }) }
+ { props.author.map(a => (
+
+ { a.name }
+ )
+ ) }
@@ -65,11 +69,11 @@ export default function Index({ comics, offset, auth }) {
{ parseInt(offset) !== 0 &&
-
+
}
-
+
diff --git a/resources/js/Pages/Comic/Read.jsx b/resources/js/Pages/Comic/Read.jsx
index c0758c4..b5ba5f2 100644
--- a/resources/js/Pages/Comic/Read.jsx
+++ b/resources/js/Pages/Comic/Read.jsx
@@ -18,7 +18,7 @@ export default function Read({ auth, comic, chapter }) {
const validReadingModes = ['rtl', 'utd'];
const [readingMode, setReadingMode] = useState('rtl'); // rtl, utd
- const [isTwoPagesPerScreen, setIsTwoPagePerScreen] = useState(false);
+ const [isTwoPagesPerScreen, setIsTwoPagePerScreen] = useState(false); // TODO
const [currentImage, setCurrentImage] = useState(1);
const windowSize = useWindowSize();
@@ -228,13 +228,13 @@ export default function Read({ auth, comic, chapter }) {
let visibleImageIndex = 0;
// Determine which image is visible based on scroll position
- images.forEach((image, index) => {
+ images.forEach((image, i) => {
const imageTop = image.offsetTop; // Distance from top of the container
const imageBottom = imageTop + image.offsetHeight;
// Check if the image is in the visible area
if (containerScrollTop + 80 >= imageTop && containerScrollTop < imageBottom) {
- visibleImageIndex = index;
+ visibleImageIndex = i;
}
});
@@ -242,12 +242,9 @@ export default function Read({ auth, comic, chapter }) {
setCurrentImage(visibleImageIndex + 1);
};
- const throttledHandleScroll = throttle(handleScroll, 100); // Throttle for performance
+ const throttledHandleScroll = throttle(handleScroll, 1000); // Throttle for performance
ref.current.addEventListener("scroll", throttledHandleScroll);
- // Initial check for visible image
- handleScroll();
-
return () => {
if (ref.current) {
ref.current.removeEventListener("scroll", throttledHandleScroll);
diff --git a/resources/js/components/ui/app-sidebar.jsx b/resources/js/components/ui/app-sidebar.jsx
index 97e0916..558a891 100644
--- a/resources/js/components/ui/app-sidebar.jsx
+++ b/resources/js/components/ui/app-sidebar.jsx
@@ -1,18 +1,26 @@
-import { Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupContent, SidebarGroupLabel, SidebarMenu, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem } from '@/components/ui/sidebar';
-import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from '@/components/ui/dropdown-menu';
+import { useState } from 'react';
+import { Link, router, usePage } from '@inertiajs/react';
+
+import { BadgeCheck, ChevronsUpDown, Star, History, ChevronDown, LogOut, Search, Book } from 'lucide-react';
-import { BadgeCheck, ChevronsUpDown, Star, History, ChevronDown, LogOut, ChevronRight } from 'lucide-react';
-import { Link, usePage } from '@inertiajs/react';
import { Avatar, AvatarFallback } from '@/components/ui/avatar';
-import {
- Collapsible,
- CollapsibleContent,
- CollapsibleTrigger,
-} from "@/components/ui/collapsible"
+import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
+import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from '@/components/ui/dropdown-menu';
+import { Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarInput, SidebarMenu, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem } from '@/components/ui/sidebar';
export function AppSidebar({ auth }) {
const { tags } = usePage().props;
+ const [search, setSearch] = useState('');
+
+ const searchOnSubmitHandler = (e) => {
+ e.preventDefault();
+ // Visit the search page
+ router.get(route('comics.search', [search]), {}, {
+
+ });
+ }
+
const SidebarItem = (props) => {
const searchParams = new URL(window.location).searchParams;
const isActive = (!searchParams.has(props.query) && props.name === 'All') || (searchParams.has(props.query) && searchParams.get(props.query) === props.path_word);
@@ -27,8 +35,9 @@ export function AppSidebar({ auth }) {
return (
- { props.name }
+ setSearch("") }>
+ { props.name }
+
{ props.count && { props.count } }
@@ -38,6 +47,32 @@ export function AppSidebar({ auth }) {
return (
+
+
+
+
+
+
+
+
+
+ Comic
+ 0.0.0
+
+
+
+
+
+
+
+
+
+
+
@@ -48,7 +83,7 @@ export function AppSidebar({ auth }) {
-
+
{ tags.theme.map(item => ) }
diff --git a/routes/web.php b/routes/web.php
index 7996e8c..cf135d1 100644
--- a/routes/web.php
+++ b/routes/web.php
@@ -8,6 +8,9 @@ use Inertia\Inertia;
// Auth protected routes
Route::controller(ComicController::class)->middleware('auth')->name('comics.')->group(function () {
Route::get('/', 'index')->name('index');
+ Route::get('/author/{author}', 'author')->name('author');
+ Route::get('/search/{search}', 'search')->name('search');
+
Route::get('/comic/{pathword}/{uuid}', 'read')->name('read');
Route::get('/comic/{pathword}', 'chapters')->name('chapters');
Route::get('/tags', 'tags')->name('tags');