Chore: Restructure Repository for SvelteKit

This commit is contained in:
Youwes09
2026-05-22 04:04:59 -05:00
parent bf071dcfc7
commit 8cef74bb98
266 changed files with 5093 additions and 396 deletions
@@ -0,0 +1,55 @@
import type { Extension } from "@types/index";
export type Filter = "installed" | "available" | "updates" | "all";
export type Panel = null | "apk" | "repos";
export function baseName(name: string): string {
return name.replace(/\s*\([A-Z0-9-]{2,10}\)\s*$/, "").trim();
}
export function matchesFilter(ext: Extension, filter: Filter): boolean {
if (filter === "installed") return ext.isInstalled;
if (filter === "available") return !ext.isInstalled;
if (filter === "updates") return ext.hasUpdate;
return true;
}
export interface ExtensionGroup {
base: string;
primary: Extension;
variants: Extension[];
}
export function groupExtensions(
extensions: Extension[],
preferredLang: string | undefined,
): ExtensionGroup[] {
const map = new Map<string, Extension[]>();
for (const ext of extensions) {
const key = baseName(ext.name);
if (!map.has(key)) map.set(key, []);
map.get(key)!.push(ext);
}
return Array.from(map.entries()).map(([base, all]) => {
const primary =
all.find((v) => v.lang === preferredLang) ??
all.find((v) => v.lang === "en") ??
all[0];
return { base, primary, variants: all.filter((v) => v.pkgName !== primary.pkgName) };
});
}
export function validateUrl(url: string, ext?: string): string | null {
if (!url.startsWith("http://") && !url.startsWith("https://"))
return "URL must start with http:// or https://";
if (ext && !url.endsWith(ext))
return `URL must point to a ${ext} file`;
return null;
}
export const FILTERS: { id: Filter; label: string }[] = [
{ id: "installed", label: "Installed" },
{ id: "available", label: "Available" },
{ id: "updates", label: "Updates" },
{ id: "all", label: "All" },
];
@@ -0,0 +1,56 @@
export interface LibraryManga {
id: number;
title: string;
thumbnailUrl: string;
unreadCount: number;
downloadCount: number;
source: { id: string; displayName: string };
}
export interface SourceLibrary {
sourceId: string;
displayName: string;
manga: LibraryManga[];
}
export type SourceNode = {
id: string;
displayName: string;
isConfigurable: boolean;
extension: { pkgName: string };
};
export function libraryByExtension(
libraryManga: LibraryManga[],
sources: SourceNode[],
pkgName: string,
): SourceLibrary[] {
const pkgSources = sources.filter(s => s.extension?.pkgName === pkgName);
const sourceIds = new Set(pkgSources.map(s => s.id));
const bySource = new Map<string, LibraryManga[]>();
for (const src of pkgSources) bySource.set(src.id, []);
for (const m of libraryManga) {
if (sourceIds.has(m.source.id)) bySource.get(m.source.id)!.push(m);
}
return pkgSources
.map(src => ({ sourceId: src.id, displayName: src.displayName, manga: bySource.get(src.id)! }))
.filter(g => g.manga.length > 0);
}
export function libraryCountByPkg(
libraryManga: LibraryManga[],
sources: SourceNode[],
): Record<string, number> {
const sourceIdToPkg = new Map<string, string>();
for (const s of sources) {
if (s.extension?.pkgName) sourceIdToPkg.set(s.id, s.extension.pkgName);
}
const counts: Record<string, number> = {};
for (const m of libraryManga) {
const pkg = sourceIdToPkg.get(m.source.id);
if (pkg) counts[pkg] = (counts[pkg] ?? 0) + 1;
}
return counts;
}