cyreader 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/bundle/server/dist/config/paths.js +4 -1
  2. package/bundle/server/dist/index.js +1 -1
  3. package/bundle/server/dist/library/scan.js +2 -3
  4. package/bundle/server/dist/library/scan.test.js +8 -8
  5. package/bundle/server/dist/library/service.js +11 -3
  6. package/bundle/server/dist/library/service.test.js +44 -10
  7. package/bundle/server/dist/library/storage.js +76 -0
  8. package/bundle/server/dist/library/storage.test.js +74 -0
  9. package/bundle/server/dist/reading/progress/service.test.js +1 -1
  10. package/bundle/server/dist/reading/recent/service.test.js +1 -1
  11. package/bundle/server/dist/routes/config.test.js +1 -1
  12. package/bundle/server/dist/routes/library.test.js +1 -1
  13. package/bundle/server/dist/routes/reading.test.js +1 -1
  14. package/bundle/web/dist/assets/{_shell-Bm2pxAQP.js → _shell-CQkt24cc.js} +1 -1
  15. package/bundle/web/dist/assets/_shell-N_UfyRoX.js +1 -0
  16. package/bundle/web/dist/assets/{comic._id-Djtr8diE.js → comic._id-CVm6n_JY.js} +1 -1
  17. package/bundle/web/dist/assets/{index-BHhqsr5H.js → index-BS403FO9.js} +2 -2
  18. package/bundle/web/dist/assets/{novel._id-C62VYvcl.js → novel._id-Bgt8GkIZ.js} +1 -1
  19. package/bundle/web/dist/assets/{save-reading-progress-D231ROaQ.js → save-reading-progress-DntE0i4u.js} +1 -1
  20. package/bundle/web/dist/assets/scroll-area-BUlmpLx6.js +1 -0
  21. package/bundle/web/dist/assets/settings-DopKc4yx.js +1 -0
  22. package/bundle/web/dist/assets/{useRouterState-4dM5FKiy.js → useRouterState-DDtlowFh.js} +1 -1
  23. package/bundle/web/dist/index.html +1 -1
  24. package/package.json +1 -1
  25. package/bundle/web/dist/assets/_shell-Cij8gMSv.js +0 -1
  26. package/bundle/web/dist/assets/scroll-area-CyEO8R_G.js +0 -1
  27. package/bundle/web/dist/assets/settings-BRi0XyRg.js +0 -1
@@ -16,6 +16,9 @@ export function getRecentFilePath() {
16
16
  export function getProgressFilePath() {
17
17
  return path.join(getConfigDir(), 'progress.json');
18
18
  }
19
- export function getLibraryCacheFilePath() {
19
+ export function getLibraryFilePath() {
20
+ return path.join(getConfigDir(), 'library.json');
21
+ }
22
+ export function getLegacyLibraryCacheFilePath() {
20
23
  return path.join(getConfigDir(), 'library-cache.json');
21
24
  }
@@ -7,7 +7,7 @@ import { ProgressService } from './reading/progress/service.js';
7
7
  import { RecentService } from './reading/recent/service.js';
8
8
  async function main() {
9
9
  const configService = await ConfigService.create();
10
- const libraryService = new LibraryService(() => configService.get());
10
+ const libraryService = await LibraryService.create(() => configService.get());
11
11
  const recentService = await RecentService.create(libraryService);
12
12
  const progressService = await ProgressService.create(libraryService);
13
13
  const app = createApp(configService, libraryService, recentService, progressService);
@@ -1,7 +1,7 @@
1
1
  import { readFile, readdir, stat } from 'node:fs/promises';
2
2
  import path from 'node:path';
3
3
  import sharp from 'sharp';
4
- import { readLibraryScanCache, writeLibraryScanCache } from './cache.js';
4
+ import { readLibrary } from './storage.js';
5
5
  import { generateComicCoverIfMissing, resolveComicCoverFilename } from './cover.js';
6
6
  import { isComicCoverFile, isHiddenName, isImageFile, isTxtFile, sortByNaturalName, } from './files.js';
7
7
  import { makeLocalItemId } from './id.js';
@@ -165,12 +165,11 @@ async function scanComicsInDirectory(directoryId, root, cacheByPath) {
165
165
  return comics.filter((comic) => comic !== null);
166
166
  }
167
167
  export async function scanLibrary(config) {
168
- const cache = await readLibraryScanCache();
168
+ const cache = await readLibrary();
169
169
  const cacheByPath = new Map(cache.items.map((item) => [item.path, item]));
170
170
  const novelResults = await Promise.all(config.novelDirectories.map((directory) => scanNovelsInDirectory(directory.id, directory.path, config.gbkNovelHandling, cacheByPath)));
171
171
  const comicResults = await Promise.all(config.comicDirectories.map((directory) => scanComicsInDirectory(directory.id, directory.path, cacheByPath)));
172
172
  const items = [...novelResults.flat(), ...comicResults.flat()];
173
- await writeLibraryScanCache({ items });
174
173
  return items;
175
174
  }
176
175
  export async function listComicPages(comicDir) {
@@ -3,9 +3,9 @@ import { tmpdir } from 'node:os';
3
3
  import path from 'node:path';
4
4
  import sharp from 'sharp';
5
5
  import { afterEach, beforeEach, describe, expect, it } from 'vitest';
6
- import { getLibraryCacheFilePath } from '../config/paths.js';
7
6
  import { COMIC_COVER_FILENAME } from './files.js';
8
7
  import { listComicPages, scanLibrary } from './scan.js';
8
+ import { readLibrary, writeLibrary } from './storage.js';
9
9
  import { writeTestImage } from './test-images.js';
10
10
  describe('scanLibrary', () => {
11
11
  let tempDir;
@@ -237,7 +237,7 @@ describe('scanLibrary', () => {
237
237
  pages: [{ filename: 'cover.jpeg', width: 320, height: 480 }],
238
238
  });
239
239
  });
240
- it('writes cache with comic page dimensions and reuses cached comic directories', async () => {
240
+ it('reuses cached comic directories from persisted library', async () => {
241
241
  const comicRoot = path.join(tempDir, 'comics');
242
242
  const mangaDir = path.join(comicRoot, 'manga-cached');
243
243
  await mkdir(mangaDir, { recursive: true });
@@ -253,6 +253,7 @@ describe('scanLibrary', () => {
253
253
  coverFilename: COMIC_COVER_FILENAME,
254
254
  addedAt: expect.any(String),
255
255
  });
256
+ await writeLibrary({ items: first });
256
257
  await rm(path.join(mangaDir, '1.jpg'));
257
258
  await rm(path.join(mangaDir, COMIC_COVER_FILENAME));
258
259
  const second = await scanLibrary({
@@ -262,8 +263,6 @@ describe('scanLibrary', () => {
262
263
  });
263
264
  expect(second).toEqual(first);
264
265
  await expect(access(path.join(mangaDir, COMIC_COVER_FILENAME))).rejects.toThrow();
265
- const cache = JSON.parse(await readFile(getLibraryCacheFilePath(), 'utf-8'));
266
- expect(cache.items).toHaveLength(1);
267
266
  });
268
267
  it('reuses cached novels without rechecking encoding', async () => {
269
268
  const novelRoot = path.join(tempDir, 'novels');
@@ -275,6 +274,7 @@ describe('scanLibrary', () => {
275
274
  comicDirectories: [],
276
275
  gbkNovelHandling: 'ignore',
277
276
  });
277
+ await writeLibrary({ items: first });
278
278
  await writeFile(filePath, Buffer.from([0xd6, 0xd0, 0xce, 0xc4]));
279
279
  const second = await scanLibrary({
280
280
  novelDirectories: [{ id: 'novel-root', path: novelRoot }],
@@ -283,25 +283,25 @@ describe('scanLibrary', () => {
283
283
  });
284
284
  expect(second).toEqual(first);
285
285
  });
286
- it('removes deleted entries from cache after scanning', async () => {
286
+ it('removes deleted entries after scanning persisted library', async () => {
287
287
  const novelRoot = path.join(tempDir, 'novels');
288
288
  const filePath = path.join(novelRoot, 'book.txt');
289
289
  await mkdir(novelRoot);
290
290
  await writeFile(filePath, 'content');
291
- await scanLibrary({
291
+ const first = await scanLibrary({
292
292
  novelDirectories: [{ id: 'novel-root', path: novelRoot }],
293
293
  comicDirectories: [],
294
294
  gbkNovelHandling: 'ignore',
295
295
  });
296
+ await writeLibrary({ items: first });
296
297
  await rm(filePath);
297
298
  const items = await scanLibrary({
298
299
  novelDirectories: [{ id: 'novel-root', path: novelRoot }],
299
300
  comicDirectories: [],
300
301
  gbkNovelHandling: 'ignore',
301
302
  });
302
- const cache = JSON.parse(await readFile(getLibraryCacheFilePath(), 'utf-8'));
303
303
  expect(items).toHaveLength(0);
304
- expect(cache.items).toHaveLength(0);
304
+ expect((await readLibrary()).items).toHaveLength(1);
305
305
  });
306
306
  });
307
307
  describe('listComicPages', () => {
@@ -2,6 +2,7 @@ import { attachProgressToDetail, attachProgressToListItem } from '../reading/pro
2
2
  import { buildComicImageUrl } from './comic-url.js';
3
3
  import { NotFoundError, ScanInProgressError } from './errors.js';
4
4
  import { scanLibrary } from './scan.js';
5
+ import { readLibrary, writeLibrary } from './storage.js';
5
6
  export function toListItemBase(item) {
6
7
  if (item.type === 'novel') {
7
8
  return {
@@ -43,10 +44,15 @@ function toItemDetailBase(item) {
43
44
  }
44
45
  export class LibraryService {
45
46
  getConfig;
46
- items = [];
47
+ items;
47
48
  scanning = false;
48
- constructor(getConfig) {
49
+ constructor(getConfig, items) {
49
50
  this.getConfig = getConfig;
51
+ this.items = items;
52
+ }
53
+ static async create(getConfig) {
54
+ const library = await readLibrary();
55
+ return new LibraryService(getConfig, library.items);
50
56
  }
51
57
  getState(progressMap = {}) {
52
58
  return {
@@ -67,7 +73,9 @@ export class LibraryService {
67
73
  }
68
74
  this.scanning = true;
69
75
  try {
70
- this.items = await scanLibrary(this.getConfig());
76
+ const items = await scanLibrary(this.getConfig());
77
+ await writeLibrary({ items });
78
+ this.items = items;
71
79
  }
72
80
  finally {
73
81
  this.scanning = false;
@@ -2,14 +2,21 @@ import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
2
2
  import { tmpdir } from 'node:os';
3
3
  import path from 'node:path';
4
4
  import { afterEach, beforeEach, describe, expect, it } from 'vitest';
5
+ import { getLibraryFilePath } from '../config/paths.js';
5
6
  import { NotFoundError, ScanInProgressError } from './errors.js';
6
7
  import { LibraryService } from './service.js';
8
+ import { readLibrary } from './storage.js';
7
9
  import { writeTestImage } from './test-images.js';
8
10
  describe('LibraryService', () => {
9
11
  let tempDir;
10
12
  let previousConfigDir;
11
13
  let novelRoot;
12
14
  let comicRoot;
15
+ const config = () => ({
16
+ novelDirectories: [{ id: 'novel-root', path: novelRoot }],
17
+ comicDirectories: [{ id: 'comic-root', path: comicRoot }],
18
+ gbkNovelHandling: 'ignore',
19
+ });
13
20
  beforeEach(async () => {
14
21
  previousConfigDir = process.env.CYREADER_CONFIG_DIR;
15
22
  tempDir = await mkdtemp(path.join(tmpdir(), 'cyreader-service-'));
@@ -30,14 +37,33 @@ describe('LibraryService', () => {
30
37
  await rm(tempDir, { recursive: true, force: true });
31
38
  });
32
39
  function createService() {
33
- return new LibraryService(() => ({
34
- novelDirectories: [{ id: 'novel-root', path: novelRoot }],
35
- comicDirectories: [{ id: 'comic-root', path: comicRoot }],
36
- gbkNovelHandling: 'ignore',
37
- }));
40
+ return LibraryService.create(config);
38
41
  }
42
+ it('returns persisted items before scan', async () => {
43
+ const scanned = await createService();
44
+ await scanned.scan();
45
+ const preloaded = await createService();
46
+ expect(preloaded.getState()).toEqual({
47
+ scanning: false,
48
+ items: scanned.getState().items,
49
+ });
50
+ });
51
+ it('keeps persisted items visible while scan is in progress', async () => {
52
+ const service = await createService();
53
+ await service.scan();
54
+ const before = service.getState().items;
55
+ await writeFile(path.join(novelRoot, 'newbook.txt'), 'more content');
56
+ const scanPromise = service.scan();
57
+ expect(service.getState()).toEqual({
58
+ scanning: true,
59
+ items: before,
60
+ });
61
+ await scanPromise;
62
+ expect(service.getState().scanning).toBe(false);
63
+ expect(service.getState().items).toHaveLength(2);
64
+ });
39
65
  it('populates list items after scan', async () => {
40
- const service = createService();
66
+ const service = await createService();
41
67
  await service.scan();
42
68
  expect(service.getState()).toEqual({
43
69
  scanning: false,
@@ -52,14 +78,22 @@ describe('LibraryService', () => {
52
78
  ],
53
79
  });
54
80
  });
81
+ it('writes library.json after scan', async () => {
82
+ const service = await createService();
83
+ await service.scan();
84
+ const library = await readLibrary();
85
+ expect(library.items).toHaveLength(1);
86
+ expect(library.items[0]).toMatchObject({ type: 'novel', title: 'book' });
87
+ expect(getLibraryFilePath()).toContain('library.json');
88
+ });
55
89
  it('rejects concurrent scan', async () => {
56
- const service = createService();
90
+ const service = await createService();
57
91
  const firstScan = service.scan();
58
92
  await expect(service.scan()).rejects.toBeInstanceOf(ScanInProgressError);
59
93
  await firstScan;
60
94
  });
61
95
  it('returns novel detail for a known id', async () => {
62
- const service = createService();
96
+ const service = await createService();
63
97
  await service.scan();
64
98
  const novel = service.getState().items[0];
65
99
  expect(service.getItemDetail(novel.id)).toEqual({
@@ -77,7 +111,7 @@ describe('LibraryService', () => {
77
111
  await mkdir(mangaDir);
78
112
  await writeTestImage(path.join(mangaDir, '2.jpg'), 300, 400);
79
113
  await writeTestImage(path.join(mangaDir, '1.jpg'), 300, 400);
80
- const service = createService();
114
+ const service = await createService();
81
115
  await service.scan();
82
116
  const comic = service.getState().items.find((item) => item.type === 'comic');
83
117
  expect(comic).toBeDefined();
@@ -100,7 +134,7 @@ describe('LibraryService', () => {
100
134
  });
101
135
  });
102
136
  it('throws when detail is requested for unknown id', async () => {
103
- const service = createService();
137
+ const service = await createService();
104
138
  await service.scan();
105
139
  expect(() => service.getItemDetail('missing')).toThrow(NotFoundError);
106
140
  });
@@ -0,0 +1,76 @@
1
+ import { access, mkdir, readFile, writeFile } from 'node:fs/promises';
2
+ import { z } from 'zod';
3
+ import { getConfigDir, getLegacyLibraryCacheFilePath, getLibraryFilePath, } from '../config/paths.js';
4
+ const comicPageSchema = z.object({
5
+ filename: z.string().min(1),
6
+ width: z.number().int().positive(),
7
+ height: z.number().int().positive(),
8
+ });
9
+ const novelItemSchema = z.object({
10
+ id: z.string().min(1),
11
+ type: z.literal('novel'),
12
+ title: z.string(),
13
+ path: z.string().min(1),
14
+ addedAt: z.string().datetime(),
15
+ directoryId: z.string().min(1),
16
+ filename: z.string().min(1),
17
+ });
18
+ const comicItemSchema = z.object({
19
+ id: z.string().min(1),
20
+ type: z.literal('comic'),
21
+ title: z.string(),
22
+ path: z.string().min(1),
23
+ addedAt: z.string().datetime(),
24
+ directoryId: z.string().min(1),
25
+ pages: z.array(comicPageSchema),
26
+ coverFilename: z.string().nullable(),
27
+ });
28
+ const librarySchema = z.object({
29
+ items: z.array(z.discriminatedUnion('type', [novelItemSchema, comicItemSchema])),
30
+ });
31
+ async function fileExists(filePath) {
32
+ try {
33
+ await access(filePath);
34
+ return true;
35
+ }
36
+ catch {
37
+ return false;
38
+ }
39
+ }
40
+ export function createEmptyLibrary() {
41
+ return { items: [] };
42
+ }
43
+ export function parseLibrary(data) {
44
+ const parsed = librarySchema.safeParse(data);
45
+ if (!parsed.success) {
46
+ return createEmptyLibrary();
47
+ }
48
+ return { items: parsed.data.items };
49
+ }
50
+ async function readLibraryFile(filePath) {
51
+ if (!(await fileExists(filePath))) {
52
+ return createEmptyLibrary();
53
+ }
54
+ let data;
55
+ try {
56
+ data = JSON.parse(await readFile(filePath, 'utf-8'));
57
+ }
58
+ catch {
59
+ return createEmptyLibrary();
60
+ }
61
+ return parseLibrary(data);
62
+ }
63
+ export async function readLibrary() {
64
+ const libraryFile = getLibraryFilePath();
65
+ if (await fileExists(libraryFile)) {
66
+ return readLibraryFile(libraryFile);
67
+ }
68
+ return readLibraryFile(getLegacyLibraryCacheFilePath());
69
+ }
70
+ export async function writeLibrary(library) {
71
+ const configDir = getConfigDir();
72
+ const libraryFile = getLibraryFilePath();
73
+ const normalized = parseLibrary(library);
74
+ await mkdir(configDir, { recursive: true });
75
+ await writeFile(libraryFile, `${JSON.stringify(normalized, null, 2)}\n`, 'utf-8');
76
+ }
@@ -0,0 +1,74 @@
1
+ import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
2
+ import { tmpdir } from 'node:os';
3
+ import path from 'node:path';
4
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
5
+ import { getLegacyLibraryCacheFilePath, getLibraryFilePath } from '../config/paths.js';
6
+ import { createEmptyLibrary, parseLibrary, readLibrary, writeLibrary } from './storage.js';
7
+ describe('library storage', () => {
8
+ let tempDir;
9
+ let previousConfigDir;
10
+ beforeEach(async () => {
11
+ previousConfigDir = process.env.CYREADER_CONFIG_DIR;
12
+ tempDir = await mkdtemp(path.join(tmpdir(), 'cyreader-storage-'));
13
+ process.env.CYREADER_CONFIG_DIR = tempDir;
14
+ });
15
+ afterEach(async () => {
16
+ if (previousConfigDir === undefined) {
17
+ delete process.env.CYREADER_CONFIG_DIR;
18
+ }
19
+ else {
20
+ process.env.CYREADER_CONFIG_DIR = previousConfigDir;
21
+ }
22
+ await rm(tempDir, { recursive: true, force: true });
23
+ });
24
+ it('returns empty library when no file exists', async () => {
25
+ expect(await readLibrary()).toEqual(createEmptyLibrary());
26
+ });
27
+ it('reads library.json when present', async () => {
28
+ const library = {
29
+ items: [
30
+ {
31
+ id: 'novel-1',
32
+ type: 'novel',
33
+ title: 'book',
34
+ path: '/tmp/book.txt',
35
+ addedAt: '2024-01-01T00:00:00.000Z',
36
+ directoryId: 'novel-root',
37
+ filename: 'book.txt',
38
+ },
39
+ ],
40
+ };
41
+ await writeLibrary(library);
42
+ expect(await readLibrary()).toEqual(library);
43
+ expect(await readFile(getLibraryFilePath(), 'utf-8')).toContain('"novel-1"');
44
+ });
45
+ it('falls back to legacy library-cache.json', async () => {
46
+ const legacy = {
47
+ items: [
48
+ {
49
+ id: 'novel-legacy',
50
+ type: 'novel',
51
+ title: 'legacy',
52
+ path: '/tmp/legacy.txt',
53
+ addedAt: '2024-01-01T00:00:00.000Z',
54
+ directoryId: 'novel-root',
55
+ filename: 'legacy.txt',
56
+ },
57
+ ],
58
+ };
59
+ await writeFile(getLegacyLibraryCacheFilePath(), `${JSON.stringify(legacy, null, 2)}\n`, 'utf-8');
60
+ expect(await readLibrary()).toEqual(legacy);
61
+ });
62
+ it('prefers library.json over legacy cache file', async () => {
63
+ await writeFile(getLegacyLibraryCacheFilePath(), `${JSON.stringify({ items: [{ id: 'legacy' }] }, null, 2)}\n`, 'utf-8');
64
+ await writeLibrary(createEmptyLibrary());
65
+ expect(await readLibrary()).toEqual(createEmptyLibrary());
66
+ });
67
+ it('returns empty library for invalid JSON', async () => {
68
+ await writeFile(getLibraryFilePath(), '{ invalid', 'utf-8');
69
+ expect(await readLibrary()).toEqual(createEmptyLibrary());
70
+ });
71
+ it('returns empty library for invalid schema', async () => {
72
+ expect(parseLibrary({ items: [{ id: 'x', type: 'unknown' }] })).toEqual(createEmptyLibrary());
73
+ });
74
+ });
@@ -30,7 +30,7 @@ describe('ProgressService', () => {
30
30
  await writeTestImage(path.join(mangaDir, '1.jpg'), 300, 400);
31
31
  await writeTestImage(path.join(mangaDir, '2.jpg'), 300, 400);
32
32
  await writeTestImage(path.join(mangaDir, '3.jpg'), 300, 400);
33
- libraryService = new LibraryService(() => ({
33
+ libraryService = await LibraryService.create(() => ({
34
34
  novelDirectories: [{ id: 'novel-root', path: novelRoot }],
35
35
  comicDirectories: [{ id: 'comic-root', path: comicRoot }],
36
36
  gbkNovelHandling: 'ignore',
@@ -21,7 +21,7 @@ describe('RecentService', () => {
21
21
  novelRoot = path.join(tempDir, 'novels');
22
22
  await mkdir(novelRoot);
23
23
  await Promise.all(Array.from({ length: MAX_RECENT + 2 }, (_, index) => writeFile(path.join(novelRoot, `book-${index}.txt`), 'content')));
24
- libraryService = new LibraryService(() => ({
24
+ libraryService = await LibraryService.create(() => ({
25
25
  novelDirectories: [{ id: 'novel-root', path: novelRoot }],
26
26
  comicDirectories: [],
27
27
  gbkNovelHandling: 'ignore',
@@ -21,7 +21,7 @@ describe('config routes', () => {
21
21
  tempDir = await mkdtemp(path.join(tmpdir(), 'cyreader-routes-'));
22
22
  process.env.CYREADER_CONFIG_DIR = tempDir;
23
23
  configService = await ConfigService.create();
24
- libraryService = new LibraryService(() => configService.get());
24
+ libraryService = await LibraryService.create(() => configService.get());
25
25
  const recentService = await RecentService.create(libraryService);
26
26
  const progressService = await ProgressService.create(libraryService);
27
27
  app = createApp(configService, libraryService, recentService, progressService);
@@ -34,7 +34,7 @@ describe('library routes', () => {
34
34
  novelDirectories: [{ id: 'novel-root', path: novelRoot }],
35
35
  comicDirectories: [{ id: 'comic-root', path: comicRoot }],
36
36
  });
37
- libraryService = new LibraryService(() => configService.get());
37
+ libraryService = await LibraryService.create(() => configService.get());
38
38
  const recentService = await RecentService.create(libraryService);
39
39
  const progressService = await ProgressService.create(libraryService);
40
40
  app = createApp(configService, libraryService, recentService, progressService);
@@ -30,7 +30,7 @@ describe('reading routes', () => {
30
30
  novelDirectories: [{ id: 'novel-root', path: novelRoot }],
31
31
  comicDirectories: [],
32
32
  });
33
- libraryService = new LibraryService(() => configService.get());
33
+ libraryService = await LibraryService.create(() => configService.get());
34
34
  await libraryService.scan();
35
35
  recentService = await RecentService.create(libraryService);
36
36
  const progressService = await ProgressService.create(libraryService);
@@ -1,4 +1,4 @@
1
- import{Y as e,a as t,b as n,c as r,f as i,g as a,h as o,i as s,l as c,p as l,s as u,t as d,u as f,x as p}from"./utils-ChkMmPzE.js";import{a as m,i as h,r as g}from"./query-keys-CbbgiZx4.js";import{i as _,r as ee,t as te}from"./use-library-Bh7Bunn1.js";import{t as ne}from"./useNavigate-B29ssGbr.js";import{t as v}from"./useRouterState-4dM5FKiy.js";import{a as re,t as ie}from"./library-utils-BRrejTkM.js";import{a as ae,i as oe,o as se,s as ce,t as le}from"./shell-context-Buw4m44t.js";import{a as ue,f as de,i as fe,m as y,p as pe,r as me,v as he,y as ge}from"./index-BHhqsr5H.js";var _e=a(`bookmark`,[[`path`,{d:`M17 3a2 2 0 0 1 2 2v15a1 1 0 0 1-1.496.868l-4.512-2.578a2 2 0 0 0-1.984 0l-4.512 2.578A1 1 0 0 1 5 20V5a2 2 0 0 1 2-2z`,key:`oz39mx`}]]),ve=a(`circle-check-big`,[[`path`,{d:`M21.801 10A10 10 0 1 1 17 3.335`,key:`yps3ct`}],[`path`,{d:`m9 11 3 3L22 4`,key:`1pflzl`}]]),ye=a(`circle-plus`,[[`circle`,{cx:`12`,cy:`12`,r:`10`,key:`1mglay`}],[`path`,{d:`M8 12h8`,key:`1wcyev`}],[`path`,{d:`M12 8v8`,key:`napkw2`}]]),be=a(`layers`,[[`path`,{d:`M12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83z`,key:`zw3jo`}],[`path`,{d:`M2 12a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 12`,key:`1wduqc`}],[`path`,{d:`M2 17a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 17`,key:`kqbvx6`}]]),xe=a(`library`,[[`path`,{d:`m16 6 4 14`,key:`ji33uf`}],[`path`,{d:`M12 6v14`,key:`1n7gus`}],[`path`,{d:`M8 8v12`,key:`1gg7y9`}],[`path`,{d:`M4 4v16`,key:`6qkkli`}]]),Se=a(`settings`,[[`path`,{d:`M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915`,key:`1i5ecw`}],[`circle`,{cx:`12`,cy:`12`,r:`3`,key:`1v7zrd`}]]),b=e(p(),1),x=n(),S=`Dialog`,[C,Ce]=c(S),[we,w]=C(S),T=e=>{let{__scopeDialog:n,children:r,open:i,defaultOpen:a,onOpenChange:o,modal:c=!0}=e,l=b.useRef(null),u=b.useRef(null),[d,f]=t({prop:i,defaultProp:a??!1,onChange:o,caller:S});return(0,x.jsx)(we,{scope:n,triggerRef:l,contentRef:u,contentId:s(),titleId:s(),descriptionId:s(),open:d,onOpenChange:f,onOpenToggle:b.useCallback(()=>f(e=>!e),[f]),modal:c,children:r})};T.displayName=S;var E=`DialogTrigger`,Te=b.forwardRef((e,t)=>{let{__scopeDialog:n,...r}=e,i=w(E,n),a=o(t,i.triggerRef);return(0,x.jsx)(f.button,{type:`button`,"aria-haspopup":`dialog`,"aria-expanded":i.open,"aria-controls":i.open?i.contentId:void 0,"data-state":V(i.open),...r,ref:a,onClick:u(e.onClick,i.onOpenToggle)})});Te.displayName=E;var D=`DialogPortal`,[Ee,O]=C(D,{forceMount:void 0}),k=e=>{let{__scopeDialog:t,forceMount:n,children:r,container:i}=e,a=w(D,t);return(0,x.jsx)(Ee,{scope:t,forceMount:n,children:b.Children.map(r,e=>(0,x.jsx)(y,{present:n||a.open,children:(0,x.jsx)(de,{asChild:!0,container:i,children:e})}))})};k.displayName=D;var A=`DialogOverlay`,j=b.forwardRef((e,t)=>{let n=O(A,e.__scopeDialog),{forceMount:r=n.forceMount,...i}=e,a=w(A,e.__scopeDialog);return a.modal?(0,x.jsx)(y,{present:r||a.open,children:(0,x.jsx)(Oe,{...i,ref:t})}):null});j.displayName=A;var De=l(`DialogOverlay.RemoveScroll`),Oe=b.forwardRef((e,t)=>{let{__scopeDialog:n,...r}=e,i=w(A,n);return(0,x.jsx)(ae,{as:De,allowPinchZoom:!0,shards:[i.contentRef],children:(0,x.jsx)(f.div,{"data-state":V(i.open),...r,ref:t,style:{pointerEvents:`auto`,...r.style}})})}),M=`DialogContent`,N=b.forwardRef((e,t)=>{let n=O(M,e.__scopeDialog),{forceMount:r=n.forceMount,...i}=e,a=w(M,e.__scopeDialog);return(0,x.jsx)(y,{present:r||a.open,children:a.modal?(0,x.jsx)(ke,{...i,ref:t}):(0,x.jsx)(Ae,{...i,ref:t})})});N.displayName=M;var ke=b.forwardRef((e,t)=>{let n=w(M,e.__scopeDialog),r=b.useRef(null),i=o(t,n.contentRef,r);return b.useEffect(()=>{let e=r.current;if(e)return oe(e)},[]),(0,x.jsx)(P,{...e,ref:i,trapFocus:n.open,disableOutsidePointerEvents:n.open,onCloseAutoFocus:u(e.onCloseAutoFocus,e=>{e.preventDefault(),n.triggerRef.current?.focus()}),onPointerDownOutside:u(e.onPointerDownOutside,e=>{let t=e.detail.originalEvent,n=t.button===0&&t.ctrlKey===!0;(t.button===2||n)&&e.preventDefault()}),onFocusOutside:u(e.onFocusOutside,e=>e.preventDefault())})}),Ae=b.forwardRef((e,t)=>{let n=w(M,e.__scopeDialog),r=b.useRef(!1),i=b.useRef(!1);return(0,x.jsx)(P,{...e,ref:t,trapFocus:!1,disableOutsidePointerEvents:!1,onCloseAutoFocus:t=>{e.onCloseAutoFocus?.(t),t.defaultPrevented||(r.current||n.triggerRef.current?.focus(),t.preventDefault()),r.current=!1,i.current=!1},onInteractOutside:t=>{e.onInteractOutside?.(t),t.defaultPrevented||(r.current=!0,t.detail.originalEvent.type===`pointerdown`&&(i.current=!0));let a=t.target;n.triggerRef.current?.contains(a)&&t.preventDefault(),t.detail.originalEvent.type===`focusin`&&i.current&&t.preventDefault()}})}),P=b.forwardRef((e,t)=>{let{__scopeDialog:n,trapFocus:r,onOpenAutoFocus:i,onCloseAutoFocus:a,...s}=e,c=w(M,n),l=b.useRef(null),u=o(t,l);return se(),(0,x.jsxs)(x.Fragment,{children:[(0,x.jsx)(ce,{asChild:!0,loop:!0,trapped:r,onMountAutoFocus:i,onUnmountAutoFocus:a,children:(0,x.jsx)(pe,{role:`dialog`,id:c.contentId,"aria-describedby":c.descriptionId,"aria-labelledby":c.titleId,"data-state":V(c.open),...s,ref:u,onDismiss:()=>c.onOpenChange(!1)})}),(0,x.jsxs)(x.Fragment,{children:[(0,x.jsx)(Me,{titleId:c.titleId}),(0,x.jsx)(Pe,{contentRef:l,descriptionId:c.descriptionId})]})]})}),F=`DialogTitle`,I=b.forwardRef((e,t)=>{let{__scopeDialog:n,...r}=e,i=w(F,n);return(0,x.jsx)(f.h2,{id:i.titleId,...r,ref:t})});I.displayName=F;var L=`DialogDescription`,R=b.forwardRef((e,t)=>{let{__scopeDialog:n,...r}=e,i=w(L,n);return(0,x.jsx)(f.p,{id:i.descriptionId,...r,ref:t})});R.displayName=L;var z=`DialogClose`,B=b.forwardRef((e,t)=>{let{__scopeDialog:n,...r}=e,i=w(z,n);return(0,x.jsx)(f.button,{type:`button`,...r,ref:t,onClick:u(e.onClick,()=>i.onOpenChange(!1))})});B.displayName=z;function V(e){return e?`open`:`closed`}var H=`DialogTitleWarning`,[je,U]=r(H,{contentName:M,titleName:F,docsSlug:`dialog`}),Me=({titleId:e})=>{let t=U(H),n=`\`${t.contentName}\` requires a \`${t.titleName}\` for the component to be accessible for screen reader users.
1
+ import{Y as e,a as t,b as n,c as r,f as i,g as a,h as o,i as s,l as c,p as l,s as u,t as d,u as f,x as p}from"./utils-ChkMmPzE.js";import{a as m,i as h,r as g}from"./query-keys-CbbgiZx4.js";import{i as _,r as ee,t as te}from"./use-library-Bh7Bunn1.js";import{t as ne}from"./useNavigate-B29ssGbr.js";import{t as v}from"./useRouterState-DDtlowFh.js";import{a as re,t as ie}from"./library-utils-BRrejTkM.js";import{a as ae,i as oe,o as se,s as ce,t as le}from"./shell-context-Buw4m44t.js";import{a as ue,f as de,i as fe,m as y,p as pe,r as me,v as he,y as ge}from"./index-BS403FO9.js";var _e=a(`bookmark`,[[`path`,{d:`M17 3a2 2 0 0 1 2 2v15a1 1 0 0 1-1.496.868l-4.512-2.578a2 2 0 0 0-1.984 0l-4.512 2.578A1 1 0 0 1 5 20V5a2 2 0 0 1 2-2z`,key:`oz39mx`}]]),ve=a(`circle-check-big`,[[`path`,{d:`M21.801 10A10 10 0 1 1 17 3.335`,key:`yps3ct`}],[`path`,{d:`m9 11 3 3L22 4`,key:`1pflzl`}]]),ye=a(`circle-plus`,[[`circle`,{cx:`12`,cy:`12`,r:`10`,key:`1mglay`}],[`path`,{d:`M8 12h8`,key:`1wcyev`}],[`path`,{d:`M12 8v8`,key:`napkw2`}]]),be=a(`layers`,[[`path`,{d:`M12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83z`,key:`zw3jo`}],[`path`,{d:`M2 12a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 12`,key:`1wduqc`}],[`path`,{d:`M2 17a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 17`,key:`kqbvx6`}]]),xe=a(`library`,[[`path`,{d:`m16 6 4 14`,key:`ji33uf`}],[`path`,{d:`M12 6v14`,key:`1n7gus`}],[`path`,{d:`M8 8v12`,key:`1gg7y9`}],[`path`,{d:`M4 4v16`,key:`6qkkli`}]]),Se=a(`settings`,[[`path`,{d:`M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915`,key:`1i5ecw`}],[`circle`,{cx:`12`,cy:`12`,r:`3`,key:`1v7zrd`}]]),b=e(p(),1),x=n(),S=`Dialog`,[C,Ce]=c(S),[we,w]=C(S),T=e=>{let{__scopeDialog:n,children:r,open:i,defaultOpen:a,onOpenChange:o,modal:c=!0}=e,l=b.useRef(null),u=b.useRef(null),[d,f]=t({prop:i,defaultProp:a??!1,onChange:o,caller:S});return(0,x.jsx)(we,{scope:n,triggerRef:l,contentRef:u,contentId:s(),titleId:s(),descriptionId:s(),open:d,onOpenChange:f,onOpenToggle:b.useCallback(()=>f(e=>!e),[f]),modal:c,children:r})};T.displayName=S;var E=`DialogTrigger`,Te=b.forwardRef((e,t)=>{let{__scopeDialog:n,...r}=e,i=w(E,n),a=o(t,i.triggerRef);return(0,x.jsx)(f.button,{type:`button`,"aria-haspopup":`dialog`,"aria-expanded":i.open,"aria-controls":i.open?i.contentId:void 0,"data-state":V(i.open),...r,ref:a,onClick:u(e.onClick,i.onOpenToggle)})});Te.displayName=E;var D=`DialogPortal`,[Ee,O]=C(D,{forceMount:void 0}),k=e=>{let{__scopeDialog:t,forceMount:n,children:r,container:i}=e,a=w(D,t);return(0,x.jsx)(Ee,{scope:t,forceMount:n,children:b.Children.map(r,e=>(0,x.jsx)(y,{present:n||a.open,children:(0,x.jsx)(de,{asChild:!0,container:i,children:e})}))})};k.displayName=D;var A=`DialogOverlay`,j=b.forwardRef((e,t)=>{let n=O(A,e.__scopeDialog),{forceMount:r=n.forceMount,...i}=e,a=w(A,e.__scopeDialog);return a.modal?(0,x.jsx)(y,{present:r||a.open,children:(0,x.jsx)(Oe,{...i,ref:t})}):null});j.displayName=A;var De=l(`DialogOverlay.RemoveScroll`),Oe=b.forwardRef((e,t)=>{let{__scopeDialog:n,...r}=e,i=w(A,n);return(0,x.jsx)(ae,{as:De,allowPinchZoom:!0,shards:[i.contentRef],children:(0,x.jsx)(f.div,{"data-state":V(i.open),...r,ref:t,style:{pointerEvents:`auto`,...r.style}})})}),M=`DialogContent`,N=b.forwardRef((e,t)=>{let n=O(M,e.__scopeDialog),{forceMount:r=n.forceMount,...i}=e,a=w(M,e.__scopeDialog);return(0,x.jsx)(y,{present:r||a.open,children:a.modal?(0,x.jsx)(ke,{...i,ref:t}):(0,x.jsx)(Ae,{...i,ref:t})})});N.displayName=M;var ke=b.forwardRef((e,t)=>{let n=w(M,e.__scopeDialog),r=b.useRef(null),i=o(t,n.contentRef,r);return b.useEffect(()=>{let e=r.current;if(e)return oe(e)},[]),(0,x.jsx)(P,{...e,ref:i,trapFocus:n.open,disableOutsidePointerEvents:n.open,onCloseAutoFocus:u(e.onCloseAutoFocus,e=>{e.preventDefault(),n.triggerRef.current?.focus()}),onPointerDownOutside:u(e.onPointerDownOutside,e=>{let t=e.detail.originalEvent,n=t.button===0&&t.ctrlKey===!0;(t.button===2||n)&&e.preventDefault()}),onFocusOutside:u(e.onFocusOutside,e=>e.preventDefault())})}),Ae=b.forwardRef((e,t)=>{let n=w(M,e.__scopeDialog),r=b.useRef(!1),i=b.useRef(!1);return(0,x.jsx)(P,{...e,ref:t,trapFocus:!1,disableOutsidePointerEvents:!1,onCloseAutoFocus:t=>{e.onCloseAutoFocus?.(t),t.defaultPrevented||(r.current||n.triggerRef.current?.focus(),t.preventDefault()),r.current=!1,i.current=!1},onInteractOutside:t=>{e.onInteractOutside?.(t),t.defaultPrevented||(r.current=!0,t.detail.originalEvent.type===`pointerdown`&&(i.current=!0));let a=t.target;n.triggerRef.current?.contains(a)&&t.preventDefault(),t.detail.originalEvent.type===`focusin`&&i.current&&t.preventDefault()}})}),P=b.forwardRef((e,t)=>{let{__scopeDialog:n,trapFocus:r,onOpenAutoFocus:i,onCloseAutoFocus:a,...s}=e,c=w(M,n),l=b.useRef(null),u=o(t,l);return se(),(0,x.jsxs)(x.Fragment,{children:[(0,x.jsx)(ce,{asChild:!0,loop:!0,trapped:r,onMountAutoFocus:i,onUnmountAutoFocus:a,children:(0,x.jsx)(pe,{role:`dialog`,id:c.contentId,"aria-describedby":c.descriptionId,"aria-labelledby":c.titleId,"data-state":V(c.open),...s,ref:u,onDismiss:()=>c.onOpenChange(!1)})}),(0,x.jsxs)(x.Fragment,{children:[(0,x.jsx)(Me,{titleId:c.titleId}),(0,x.jsx)(Pe,{contentRef:l,descriptionId:c.descriptionId})]})]})}),F=`DialogTitle`,I=b.forwardRef((e,t)=>{let{__scopeDialog:n,...r}=e,i=w(F,n);return(0,x.jsx)(f.h2,{id:i.titleId,...r,ref:t})});I.displayName=F;var L=`DialogDescription`,R=b.forwardRef((e,t)=>{let{__scopeDialog:n,...r}=e,i=w(L,n);return(0,x.jsx)(f.p,{id:i.descriptionId,...r,ref:t})});R.displayName=L;var z=`DialogClose`,B=b.forwardRef((e,t)=>{let{__scopeDialog:n,...r}=e,i=w(z,n);return(0,x.jsx)(f.button,{type:`button`,...r,ref:t,onClick:u(e.onClick,()=>i.onOpenChange(!1))})});B.displayName=z;function V(e){return e?`open`:`closed`}var H=`DialogTitleWarning`,[je,U]=r(H,{contentName:M,titleName:F,docsSlug:`dialog`}),Me=({titleId:e})=>{let t=U(H),n=`\`${t.contentName}\` requires a \`${t.titleName}\` for the component to be accessible for screen reader users.
2
2
 
3
3
  If you want to hide the \`${t.titleName}\`, you can wrap it with our VisuallyHidden component.
4
4
 
@@ -0,0 +1 @@
1
+ import{Y as e,b as t,f as n,g as r,t as i,x as a}from"./utils-ChkMmPzE.js";import{a as o,i as s,o as c,r as l,t as u}from"./query-keys-CbbgiZx4.js";import{t as d}from"./useNavigate-B29ssGbr.js";import{a as f,c as p,i as m,n as h,o as g,r as _,s as v,t as y}from"./scroll-area-BUlmpLx6.js";import{a as ee,i as te,n as ne,r as re}from"./library-utils-BRrejTkM.js";import{n as b,r as x}from"./shell-context-Buw4m44t.js";import{a as S,i as C,t as w}from"./reading-BozmJNi1.js";import{a as T,i as E}from"./reading-progress-C5bkK-vF.js";var D=r(`clock`,[[`circle`,{cx:`12`,cy:`12`,r:`10`,key:`1mglay`}],[`path`,{d:`M12 6v6l4 2`,key:`mmk7yg`}]]),O=r(`layout-grid`,[[`rect`,{width:`7`,height:`7`,x:`3`,y:`3`,rx:`1`,key:`1g98yp`}],[`rect`,{width:`7`,height:`7`,x:`14`,y:`3`,rx:`1`,key:`6d4xhi`}],[`rect`,{width:`7`,height:`7`,x:`14`,y:`14`,rx:`1`,key:`nxv5o0`}],[`rect`,{width:`7`,height:`7`,x:`3`,y:`14`,rx:`1`,key:`1bb6yr`}]]),k=r(`list`,[[`path`,{d:`M3 5h.01`,key:`18ugdj`}],[`path`,{d:`M3 12h.01`,key:`nlz23k`}],[`path`,{d:`M3 19h.01`,key:`noohij`}],[`path`,{d:`M8 5h13`,key:`1pao27`}],[`path`,{d:`M8 12h13`,key:`1za7za`}],[`path`,{d:`M8 19h13`,key:`m83p4d`}]]),A=r(`search-x`,[[`path`,{d:`m13.5 8.5-5 5`,key:`1cs55j`}],[`path`,{d:`m8.5 8.5 5 5`,key:`a8mexj`}],[`circle`,{cx:`11`,cy:`11`,r:`8`,key:`4ej97u`}],[`path`,{d:`m21 21-4.3-4.3`,key:`1qie3q`}]]),j=r(`search`,[[`path`,{d:`m21 21-4.34-4.34`,key:`14j7rj`}],[`circle`,{cx:`11`,cy:`11`,r:`8`,key:`4ej97u`}]]),M=e(a(),1),N=t();function P({className:e,...t}){return(0,N.jsx)(`div`,{"data-slot":`empty`,className:i(`flex w-full min-w-0 flex-1 flex-col items-center justify-center gap-4 rounded-xl border-dashed p-6 text-center text-balance`,e),...t})}function F({className:e,...t}){return(0,N.jsx)(`div`,{"data-slot":`empty-header`,className:i(`flex max-w-sm flex-col items-center gap-2`,e),...t})}var I=o(`mb-2 flex shrink-0 items-center justify-center [&_svg]:pointer-events-none [&_svg]:shrink-0`,{variants:{variant:{default:`bg-transparent`,icon:`flex size-8 shrink-0 items-center justify-center rounded-lg bg-muted text-foreground [&_svg:not([class*='size-'])]:size-4`}},defaultVariants:{variant:`default`}});function L({className:e,variant:t=`default`,...n}){return(0,N.jsx)(`div`,{"data-slot":`empty-icon`,"data-variant":t,className:i(I({variant:t,className:e})),...n})}function R({className:e,...t}){return(0,N.jsx)(`div`,{"data-slot":`empty-title`,className:i(`font-heading text-sm font-medium tracking-tight`,e),...t})}function z({className:e,...t}){return(0,N.jsx)(`div`,{"data-slot":`empty-description`,className:i(`text-sm/relaxed text-muted-foreground [&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary`,e),...t})}var ie=o(`group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!`,{variants:{variant:{default:`bg-primary text-primary-foreground [a]:hover:bg-primary/80`,secondary:`bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80`,destructive:`bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20`,outline:`border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground`,ghost:`hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50`,link:`text-primary underline-offset-4 hover:underline`}},defaultVariants:{variant:`default`}});function B({className:e,variant:t=`default`,asChild:r=!1,...a}){return(0,N.jsx)(r?n:`span`,{"data-slot":`badge`,"data-variant":t,className:i(ie({variant:t}),e),...a})}var V=`/novel-default-cover.png`;function H(e){return e.type===`comic`?e.cover:V}function U(e,t){if(t.type===`novel`){e({to:`/read/novel/$id`,params:{id:t.id},state:{readerTitle:t.title}});return}e({to:`/read/comic/$id`,params:{id:t.id},state:{readerTitle:t.title}})}var W=`h-[180px]`,G=`h-16`,K=`h-[244px]`,q={reading:`bg-indigo-500 shadow-[0_0_6px_rgba(99,102,241,0.4)]`,unread:`bg-green-500 shadow-[0_0_6px_rgba(34,197,94,0.4)]`,completed:`bg-muted-foreground`};function J({item:e,view:t}){let n=d(),r=()=>{U(n,e)},a=E(e.progress),o=T(e);return t===`list`?(0,N.jsxs)(`button`,{type:`button`,onClick:r,className:`flex h-14 w-full items-stretch overflow-hidden rounded-md border border-[#25252a] bg-[#1a1a20] text-left transition-colors hover:border-[#3a3a44]`,children:[(0,N.jsx)(`div`,{className:`relative h-full w-10 shrink-0 overflow-hidden bg-muted`,children:(0,N.jsx)(`img`,{src:H(e),alt:``,className:`absolute inset-0 block size-full object-cover`,loading:`lazy`})}),(0,N.jsxs)(`div`,{className:`flex min-w-0 flex-1 items-center gap-4 px-3.5`,children:[(0,N.jsx)(`div`,{className:`min-w-0 flex-1 truncate text-xs font-semibold text-[#e2e2e8]`,children:e.title}),(0,N.jsx)(`div`,{className:`min-w-[100px]`,children:(0,N.jsx)(`div`,{className:`h-[3px] w-full overflow-hidden rounded-full bg-[#2a2a32]`,children:(0,N.jsx)(`div`,{className:i(`h-full rounded-full`,a===`unread`?`bg-muted-foreground`:`bg-indigo-500`),style:{width:`${o}%`}})})})]})]}):(0,N.jsxs)(`button`,{type:`button`,onClick:r,className:i(`group flex w-full flex-col overflow-hidden rounded-lg border border-[#25252a] bg-[#1a1a20] text-left transition-all hover:-translate-y-px hover:border-[#3a3a44]`,K),children:[(0,N.jsxs)(`div`,{className:i(`relative w-full shrink-0 overflow-hidden bg-muted`,W),children:[(0,N.jsx)(`img`,{src:H(e),alt:``,className:`absolute inset-0 block size-full object-cover`,loading:`lazy`}),(0,N.jsx)(B,{variant:`secondary`,className:`absolute top-1.5 left-1.5 border-0 bg-black/60 px-1.5 py-0 text-[10px] text-[#c2c2ca] backdrop-blur-sm`,children:e.type===`novel`?`小说`:`漫画`}),(0,N.jsx)(`span`,{className:i(`absolute top-1.5 right-1.5 size-2 rounded-full`,q[a])})]}),(0,N.jsxs)(`div`,{className:i(`flex shrink-0 flex-col justify-between p-2.5`,G),children:[(0,N.jsx)(`div`,{className:`truncate text-xs leading-4 font-semibold text-[#e2e2e8]`,children:e.title}),(0,N.jsx)(`div`,{className:`h-[3px] w-full overflow-hidden rounded-full bg-[#2a2a32]`,children:(0,N.jsx)(`div`,{className:i(`h-full rounded-full`,a===`unread`?`bg-muted-foreground`:`bg-indigo-500`),style:{width:`${o}%`}})})]})]})}function Y({items:e,view:t,filterLabel:n}){return e.length===0?(0,N.jsx)(P,{className:`border py-16`,children:(0,N.jsxs)(F,{children:[(0,N.jsx)(L,{variant:`icon`,children:(0,N.jsx)(A,{})}),(0,N.jsx)(R,{children:`未找到匹配的作品`}),(0,N.jsx)(z,{children:`尝试调整搜索词或筛选条件`})]})}):(0,N.jsxs)(`div`,{className:`flex flex-col gap-1.5`,children:[(0,N.jsx)(`div`,{className:`flex items-center justify-between py-2`,children:(0,N.jsxs)(`div`,{className:`text-xs text-muted-foreground`,children:[n,` · `,e.length,` 部作品`]})}),t===`list`?(0,N.jsxs)(`div`,{className:`mb-1.5 hidden grid-cols-[40px_1fr_1fr_80px_100px] gap-4 border-b px-3.5 py-1.5 text-[11px] font-semibold tracking-wide text-muted-foreground uppercase md:grid`,children:[(0,N.jsx)(`div`,{}),(0,N.jsx)(`div`,{children:`作品`}),(0,N.jsx)(`div`,{children:`作者`}),(0,N.jsx)(`div`,{className:`text-right`,children:`进度`}),(0,N.jsx)(`div`,{className:`text-right`,children:`状态`})]}):null,(0,N.jsx)(`div`,{className:i(t===`grid`?`grid grid-cols-[repeat(auto-fill,minmax(140px,1fr))] gap-3.5`:`flex flex-col gap-1.5`),children:e.map(e=>(0,N.jsx)(J,{item:e,view:t},e.id))})]})}var X=`h-8 border-[#2a2a32] bg-[#1a1a20] text-xs text-[#9f9fad] hover:bg-[#23232a] hover:text-[#d5d5dd]`;function ae({query:e,onQueryChange:t,sort:n,onSortChange:r,view:a,onViewChange:o,scanning:c=!1}){return(0,N.jsxs)(`header`,{className:`flex h-14 shrink-0 items-center gap-3 border-b px-5`,children:[(0,N.jsxs)(`div`,{className:`relative max-w-[520px] flex-1`,children:[(0,N.jsx)(j,{className:`pointer-events-none absolute top-1/2 left-2.5 size-3.5 -translate-y-1/2 text-muted-foreground`}),(0,N.jsx)(x,{value:e,onChange:e=>t(e.target.value),placeholder:`搜索作品、作者...`,className:`h-[34px] border-[#2a2a32] bg-[#1a1a20] pl-8 text-[13px] text-[#e2e2e8] placeholder:text-[#6b6b78] focus-visible:border-indigo-500`}),e?(0,N.jsx)(s,{type:`button`,variant:`ghost`,size:`icon-xs`,className:`absolute top-1/2 right-1.5 -translate-y-1/2`,onClick:()=>t(``),children:(0,N.jsx)(ee,{})}):null]}),(0,N.jsxs)(`div`,{className:`ml-auto flex items-center gap-3`,children:[c?(0,N.jsxs)(`div`,{className:`flex h-8 items-center gap-1.5 text-xs text-muted-foreground`,children:[(0,N.jsx)(p,{className:`size-3.5 animate-spin`}),`扫描中...`]}):null,(0,N.jsxs)(h,{value:n,onValueChange:e=>r(e),children:[(0,N.jsx)(g,{size:`sm`,className:i(X,`h-8 min-w-[108px] px-2.5`),children:(0,N.jsx)(v,{})}),(0,N.jsx)(_,{children:(0,N.jsxs)(m,{children:[(0,N.jsx)(f,{value:`addedAt`,children:`最近添加`}),(0,N.jsx)(f,{value:`name`,children:`名称`})]})})]}),(0,N.jsxs)(C,{type:`single`,value:a,onValueChange:e=>{e&&o(e)},variant:`outline`,size:`sm`,className:`gap-0.5`,children:[(0,N.jsx)(S,{value:`grid`,"aria-label":`网格视图`,className:i(X,`px-2 data-[state=on]:border-[#3e3e52] data-[state=on]:bg-[#2a2a3a] data-[state=on]:text-[#e2e2e8]`),children:(0,N.jsx)(O,{})}),(0,N.jsx)(S,{value:`list`,"aria-label":`列表视图`,className:i(X,`px-2 data-[state=on]:border-[#3e3e52] data-[state=on]:bg-[#2a2a3a] data-[state=on]:text-[#e2e2e8]`),children:(0,N.jsx)(k,{})})]})]})]})}function oe(){let e=c({queryKey:u.recent,queryFn:w});return{items:e.data?.items??[],loading:e.isPending,error:e.error?e.error instanceof Error?e.error.message:`Failed to load recent reading`:null}}function se(){let{filter:e}=b(),{items:t,loading:n}=oe(),r=(0,M.useMemo)(()=>e===`novel`||e===`comic`?t.filter(t=>t.type===e):t,[e,t]);return(0,N.jsxs)(`section`,{className:`mb-2`,children:[(0,N.jsxs)(`div`,{className:`flex items-center gap-2 py-4 text-[13px] font-semibold text-foreground/80`,children:[(0,N.jsx)(D,{className:`size-3.5 text-indigo-500`}),`最近阅读`]}),n?(0,N.jsx)(`div`,{className:`grid grid-cols-[repeat(auto-fill,minmax(140px,1fr))] gap-3.5 pb-2`,children:Array.from({length:4},(e,t)=>(0,N.jsxs)(`div`,{className:i(`flex flex-col overflow-hidden rounded-lg border border-[#25252a] bg-[#1a1a20]`,K),children:[(0,N.jsx)(l,{className:i(`w-full shrink-0 rounded-none`,W)}),(0,N.jsxs)(`div`,{className:i(`flex flex-col justify-between p-2.5`,G),children:[(0,N.jsx)(l,{className:`h-3 w-4/5`}),(0,N.jsx)(l,{className:`h-[3px] w-full`})]})]},t))}):null,!n&&r.length===0?(0,N.jsx)(`div`,{className:`pb-2 text-xs text-muted-foreground`,children:`暂无阅读记录`}):null,!n&&r.length>0?(0,N.jsx)(`div`,{className:`grid grid-cols-[repeat(auto-fill,minmax(140px,1fr))] gap-3.5 pb-2`,children:r.map(e=>(0,N.jsx)(J,{item:e,view:`grid`},e.id))}):null]})}var Z=`cyreader:library-sort`,Q=`addedAt`,ce=new Set([`name`,`addedAt`]);function le(e){return typeof e==`string`&&ce.has(e)?e:Q}function $(){if(typeof window>`u`)return Q;try{let e=window.localStorage.getItem(Z);return e?le(JSON.parse(e)):Q}catch{return Q}}function ue(e){if(!(typeof window>`u`))try{window.localStorage.setItem(Z,JSON.stringify(e))}catch{}}function de(){let{filter:e,items:t,scanning:n}=b(),[r,i]=(0,M.useState)(``),[a,o]=(0,M.useState)(`grid`),[s,c]=(0,M.useState)(()=>$()),l=(0,M.useMemo)(()=>te(ne(t,e,r),s),[t,e,r,s]);return(0,N.jsxs)(`div`,{className:`flex h-full min-h-0 flex-col`,children:[(0,N.jsx)(ae,{query:r,onQueryChange:i,sort:s,onSortChange:e=>{c(e),ue(e)},view:a,onViewChange:o,scanning:n}),(0,N.jsx)(y,{className:`min-h-0 flex-1`,children:(0,N.jsxs)(`div`,{className:`px-5 pb-5`,children:[(0,N.jsx)(se,{}),(0,N.jsx)(Y,{items:l,view:a,filterLabel:re(e)})]})})]})}export{de as component};
@@ -1 +1 @@
1
- import{Y as e,b as t,t as n,x as r}from"./utils-ChkMmPzE.js";import{i,r as a}from"./query-keys-CbbgiZx4.js";import{t as o}from"./useRouterState-4dM5FKiy.js";import{a as s,d as c,i as l,l as u,n as d,r as f,t as p,u as m}from"./save-reading-progress-D231ROaQ.js";import{t as h}from"./settings-2-8mnAuSax.js";import{a as g,i as _}from"./reading-BozmJNi1.js";import{t as v,y}from"./index-BHhqsr5H.js";import{n as b}from"./reading-progress-C5bkK-vF.js";var x=e(r(),1),S=t();function C({open:e,direction:t,onDirectionChange:n}){return e?(0,S.jsx)(`div`,{className:`absolute top-14 right-0 z-20 flex w-80 flex-col gap-4 border-b border-l border-[#25252a] bg-[#0a0a0f] p-4`,children:(0,S.jsxs)(`div`,{className:`flex items-center justify-between gap-3`,children:[(0,S.jsx)(`span`,{className:`text-sm text-[#d4c5a9]`,children:`阅读方向`}),(0,S.jsxs)(_,{type:`single`,value:t,onValueChange:e=>{(e===`ltr`||e===`rtl`)&&n(e)},children:[(0,S.jsx)(g,{value:`ltr`,"aria-label":`从左往右`,children:`左→右`}),(0,S.jsx)(g,{value:`rtl`,"aria-label":`从右往左`,children:`右→左`})]})]})}):null}function w(e){(0,x.useEffect)(()=>{let t=e.current;if(!t)return;let n=!1,r=0,i=0,a=null,o=null,s=0,c=()=>{s=0,o!==null&&(t.scrollLeft=o,o=null)},l=e=>{n&&(n=!1,a=null,s&&=(cancelAnimationFrame(s),0),o=null,t.hasPointerCapture(e)&&t.releasePointerCapture(e),t.style.cursor=`grab`)},u=e=>{e.button===0&&(n=!0,a=e.pointerId,r=e.clientX,i=t.scrollLeft,t.setPointerCapture(e.pointerId),t.style.cursor=`grabbing`)},d=e=>{!n||a!==e.pointerId||(o=i-(e.clientX-r),s||=requestAnimationFrame(c))},f=e=>{l(e.pointerId)};return t.style.cursor=`grab`,t.style.userSelect=`none`,t.addEventListener(`pointerdown`,u),t.addEventListener(`pointermove`,d),t.addEventListener(`pointerup`,f),t.addEventListener(`pointercancel`,f),()=>{s&&cancelAnimationFrame(s),t.removeEventListener(`pointerdown`,u),t.removeEventListener(`pointermove`,d),t.removeEventListener(`pointerup`,f),t.removeEventListener(`pointercancel`,f),t.style.cursor=``,t.style.userSelect=``}},[e])}function T(e,t,n){return`/static/${e}/${encodeURIComponent(t)}/${encodeURIComponent(n)}`}var E=2/3,D=.995;function O(e){return Number.isFinite(e)?Math.min(Math.max(e,0),1):0}function k(e,t){return Math.max(0,e-t)}function A(e,t,n,r=`ltr`){let i=k(t,n);if(i===0)return 0;let a=O(e/i);return r===`rtl`?1-a:a}function j(e,t,n,r=`ltr`){let i=k(t,n),a=O(e);return(r===`rtl`?1-a:a)*i}function M(e,t,n=`ltr`){e.scrollLeft=j(t,e.scrollWidth,e.clientWidth,n)}function N(e,t){return t<=1?0:Math.min(Math.max(e,0),t-1)/(t-1)}function P(e){return O(e)>=D}function F(e,t,n){if(t<=0)return 0;let r=Math.min(Math.max(e,0),t-1);return n===`rtl`?t-1-r:r}function I(e){return e<=0?200:Math.round(e*E)+8}function L(e,t,n){return e<=0||t<=0||n<=0?I(n):Math.round(e/t*n)+8}function R(e,t){return e.reduce((e,n)=>e+L(n.width,n.height,t),0)}function z({directoryId:e,title:t,pages:r,direction:i,initialScrollRatio:a,onScrollProgress:o}){let s=(0,x.useRef)(null),c=(0,x.useRef)(o),l=(0,x.useRef)(a),u=(0,x.useRef)(!0),d=(0,x.useRef)(!1),[f,p]=(0,x.useState)(0);c.current=o;let h=f>0,g=r.length,_=(0,x.useMemo)(()=>I(f),[f]);w(s);let v=(0,x.useCallback)(e=>F(e,g,i),[i,g]),y=m({horizontal:!0,count:g,enabled:h,getScrollElement:()=>s.current,estimateSize:(0,x.useCallback)(e=>{let t=r[v(e)];return t?L(t.width,t.height,f):_},[f,_,v,r]),overscan:4,initialOffset:(0,x.useCallback)(()=>{let e=s.current;if(!e||!h)return 0;let t=R(r,f);return j(l.current,t,e.clientWidth,i)},[f,i,h,r]),getItemKey:e=>r[v(e)]?.filename??e}),b=(0,x.useRef)(y.measure);b.current=y.measure;let C=(0,x.useCallback)(()=>{let e=s.current;e&&(d.current=!0,M(e,l.current,i),window.setTimeout(()=>{d.current=!1},0))},[i]);return(0,x.useLayoutEffect)(()=>{let e=s.current;if(!e)return;let t=()=>{p(e.clientHeight)};t();let n=new ResizeObserver(t);return n.observe(e),()=>{n.disconnect()}},[]),(0,x.useLayoutEffect)(()=>{l.current=a,u.current=!0},[i,a,r,t]),(0,x.useLayoutEffect)(()=>{g===0||!h||(b.current(),u.current&&C())},[C,f,i,h,g]),(0,x.useEffect)(()=>{if(g===0||!h||!u.current)return;let e=window.setTimeout(()=>{C(),u.current=!1},120);return()=>{window.clearTimeout(e)}},[C,a,h,g]),(0,x.useEffect)(()=>{let e=s.current;if(!e||!h)return;let t,n=()=>{t&&clearTimeout(t),t=setTimeout(()=>{if(d.current||u.current)return;let t=A(e.scrollLeft,e.scrollWidth,e.clientWidth,i);c.current(t)},100)};return e.addEventListener(`scroll`,n,{passive:!0}),()=>{e.removeEventListener(`scroll`,n),t&&clearTimeout(t)}},[i,h]),(0,S.jsx)(`div`,{ref:s,className:n(`reader-scrollbar-none h-full overflow-x-auto overflow-y-hidden`),children:(0,S.jsx)(`div`,{style:{width:y.getTotalSize(),height:`100%`,position:`relative`},children:y.getVirtualItems().map(n=>{let i=v(n.index),a=r[i];return a?(0,S.jsx)(`div`,{"data-index":n.index,className:`flex h-full items-center`,style:{position:`absolute`,top:0,left:0,height:`100%`,width:n.size,paddingRight:8,transform:`translateX(${n.start}px)`},children:(0,S.jsx)(`img`,{src:T(e,t,a.filename),alt:`第 ${i+1} 页`,draggable:!1,decoding:`async`,className:`h-full w-auto max-w-none select-none`})},n.key):null})})})}function B(e,t){return e?typeof e.scrollRatio==`number`?O(e.scrollRatio):typeof e.pageIndex==`number`?N(e.pageIndex,t):0:0}function V({id:e}){let t=o({select:e=>e.location.state?.readerTitle}),{data:r,isPending:m,error:g}=u(e),{settings:_,patchSettings:v}=s(`comic`),[w,T]=(0,x.useState)(0),[E,D]=(0,x.useState)(null),[k,A]=(0,x.useState)(!1),j=r&&r.type===`comic`?r:null,M=j?.pages.length??0,N=t??r?.title??`加载中...`,F=g?g instanceof Error?g.message:`加载失败`:null,I=(0,x.useMemo)(()=>b(w),[w]),L=(0,x.useCallback)((t,n)=>{j&&d(e,{type:`comic`,scrollRatio:t,completedAt:n})},[j,e]),R=(0,x.useCallback)(e=>{let t=O(e);T(t),L(t,P(t)?new Date().toISOString():null)},[L]),V=(0,x.useCallback)(e=>{D(w),v({direction:e})},[v,w]);return(0,x.useEffect)(()=>{D(null),T(0)},[e]),(0,x.useEffect)(()=>{if(!j||E!==null)return;let e=B(j.progress,M);D(e),T(e)},[j,M,E]),(0,x.useEffect)(()=>(l(e),()=>{f(e),p(e)}),[e]),(0,S.jsxs)(`div`,{className:`relative flex h-screen flex-col overflow-hidden bg-[#0a0a0f] text-[#d4c5a9]`,children:[(0,S.jsxs)(`header`,{className:`relative z-10 flex h-14 shrink-0 items-center gap-3 border-b border-[#25252a] px-4`,children:[(0,S.jsx)(i,{type:`button`,variant:`ghost`,size:`sm`,asChild:!0,children:(0,S.jsxs)(y,{to:`/`,children:[(0,S.jsx)(c,{"data-icon":`inline-start`}),`返回`]})}),(0,S.jsx)(`div`,{className:`flex-1 truncate text-center text-sm font-medium`,children:N}),(0,S.jsx)(i,{type:`button`,variant:`ghost`,size:`icon-sm`,onClick:()=>A(e=>!e),className:n(k&&`bg-[#25252a]`),children:(0,S.jsx)(h,{})})]}),(0,S.jsx)(C,{open:k,direction:_.direction,onDirectionChange:V}),(0,S.jsxs)(`div`,{className:`relative min-h-0 flex-1`,children:[m&&!j?(0,S.jsx)(`div`,{className:`flex size-full items-center justify-center`,children:(0,S.jsx)(a,{className:`h-[70vh] w-full max-w-3xl`})}):null,F?(0,S.jsx)(`div`,{className:`p-8 text-sm text-red-400`,children:F}):null,!m&&!F&&r&&r.type!==`comic`?(0,S.jsx)(`div`,{className:`p-8 text-sm text-red-400`,children:`作品类型不匹配`}):null,j&&M>0&&E!==null?(0,S.jsx)(z,{directoryId:j.directoryId,title:j.title,pages:j.pages,direction:_.direction,initialScrollRatio:E,onScrollProgress:R},`${e}-${_.direction}`):null,j&&M===0?(0,S.jsx)(`div`,{className:`flex size-full items-center justify-center text-sm text-muted-foreground`,children:`暂无页面`}):null]}),(0,S.jsxs)(`footer`,{className:`flex h-11 shrink-0 items-center justify-center border-t border-[#25252a] text-xs opacity-80`,children:[`已读 `,I,`%`]})]})}function H(){let{id:e}=v.useParams();return(0,S.jsx)(V,{id:e})}export{H as component};
1
+ import{Y as e,b as t,t as n,x as r}from"./utils-ChkMmPzE.js";import{i,r as a}from"./query-keys-CbbgiZx4.js";import{t as o}from"./useRouterState-DDtlowFh.js";import{a as s,d as c,i as l,l as u,n as d,r as f,t as p,u as m}from"./save-reading-progress-DntE0i4u.js";import{t as h}from"./settings-2-8mnAuSax.js";import{a as g,i as _}from"./reading-BozmJNi1.js";import{t as v,y}from"./index-BS403FO9.js";import{n as b}from"./reading-progress-C5bkK-vF.js";var x=e(r(),1),S=t();function C({open:e,direction:t,onDirectionChange:n}){return e?(0,S.jsx)(`div`,{className:`absolute top-14 right-0 z-20 flex w-80 flex-col gap-4 border-b border-l border-[#25252a] bg-[#0a0a0f] p-4`,children:(0,S.jsxs)(`div`,{className:`flex items-center justify-between gap-3`,children:[(0,S.jsx)(`span`,{className:`text-sm text-[#d4c5a9]`,children:`阅读方向`}),(0,S.jsxs)(_,{type:`single`,value:t,onValueChange:e=>{(e===`ltr`||e===`rtl`)&&n(e)},children:[(0,S.jsx)(g,{value:`ltr`,"aria-label":`从左往右`,children:`左→右`}),(0,S.jsx)(g,{value:`rtl`,"aria-label":`从右往左`,children:`右→左`})]})]})}):null}function w(e){(0,x.useEffect)(()=>{let t=e.current;if(!t)return;let n=!1,r=0,i=0,a=null,o=null,s=0,c=()=>{s=0,o!==null&&(t.scrollLeft=o,o=null)},l=e=>{n&&(n=!1,a=null,s&&=(cancelAnimationFrame(s),0),o=null,t.hasPointerCapture(e)&&t.releasePointerCapture(e),t.style.cursor=`grab`)},u=e=>{e.button===0&&(n=!0,a=e.pointerId,r=e.clientX,i=t.scrollLeft,t.setPointerCapture(e.pointerId),t.style.cursor=`grabbing`)},d=e=>{!n||a!==e.pointerId||(o=i-(e.clientX-r),s||=requestAnimationFrame(c))},f=e=>{l(e.pointerId)};return t.style.cursor=`grab`,t.style.userSelect=`none`,t.addEventListener(`pointerdown`,u),t.addEventListener(`pointermove`,d),t.addEventListener(`pointerup`,f),t.addEventListener(`pointercancel`,f),()=>{s&&cancelAnimationFrame(s),t.removeEventListener(`pointerdown`,u),t.removeEventListener(`pointermove`,d),t.removeEventListener(`pointerup`,f),t.removeEventListener(`pointercancel`,f),t.style.cursor=``,t.style.userSelect=``}},[e])}function T(e,t,n){return`/static/${e}/${encodeURIComponent(t)}/${encodeURIComponent(n)}`}var E=2/3,D=.995;function O(e){return Number.isFinite(e)?Math.min(Math.max(e,0),1):0}function k(e,t){return Math.max(0,e-t)}function A(e,t,n,r=`ltr`){let i=k(t,n);if(i===0)return 0;let a=O(e/i);return r===`rtl`?1-a:a}function j(e,t,n,r=`ltr`){let i=k(t,n),a=O(e);return(r===`rtl`?1-a:a)*i}function M(e,t,n=`ltr`){e.scrollLeft=j(t,e.scrollWidth,e.clientWidth,n)}function N(e,t){return t<=1?0:Math.min(Math.max(e,0),t-1)/(t-1)}function P(e){return O(e)>=D}function F(e,t,n){if(t<=0)return 0;let r=Math.min(Math.max(e,0),t-1);return n===`rtl`?t-1-r:r}function I(e){return e<=0?200:Math.round(e*E)+8}function L(e,t,n){return e<=0||t<=0||n<=0?I(n):Math.round(e/t*n)+8}function R(e,t){return e.reduce((e,n)=>e+L(n.width,n.height,t),0)}function z({directoryId:e,title:t,pages:r,direction:i,initialScrollRatio:a,onScrollProgress:o}){let s=(0,x.useRef)(null),c=(0,x.useRef)(o),l=(0,x.useRef)(a),u=(0,x.useRef)(!0),d=(0,x.useRef)(!1),[f,p]=(0,x.useState)(0);c.current=o;let h=f>0,g=r.length,_=(0,x.useMemo)(()=>I(f),[f]);w(s);let v=(0,x.useCallback)(e=>F(e,g,i),[i,g]),y=m({horizontal:!0,count:g,enabled:h,getScrollElement:()=>s.current,estimateSize:(0,x.useCallback)(e=>{let t=r[v(e)];return t?L(t.width,t.height,f):_},[f,_,v,r]),overscan:4,initialOffset:(0,x.useCallback)(()=>{let e=s.current;if(!e||!h)return 0;let t=R(r,f);return j(l.current,t,e.clientWidth,i)},[f,i,h,r]),getItemKey:e=>r[v(e)]?.filename??e}),b=(0,x.useRef)(y.measure);b.current=y.measure;let C=(0,x.useCallback)(()=>{let e=s.current;e&&(d.current=!0,M(e,l.current,i),window.setTimeout(()=>{d.current=!1},0))},[i]);return(0,x.useLayoutEffect)(()=>{let e=s.current;if(!e)return;let t=()=>{p(e.clientHeight)};t();let n=new ResizeObserver(t);return n.observe(e),()=>{n.disconnect()}},[]),(0,x.useLayoutEffect)(()=>{l.current=a,u.current=!0},[i,a,r,t]),(0,x.useLayoutEffect)(()=>{g===0||!h||(b.current(),u.current&&C())},[C,f,i,h,g]),(0,x.useEffect)(()=>{if(g===0||!h||!u.current)return;let e=window.setTimeout(()=>{C(),u.current=!1},120);return()=>{window.clearTimeout(e)}},[C,a,h,g]),(0,x.useEffect)(()=>{let e=s.current;if(!e||!h)return;let t,n=()=>{t&&clearTimeout(t),t=setTimeout(()=>{if(d.current||u.current)return;let t=A(e.scrollLeft,e.scrollWidth,e.clientWidth,i);c.current(t)},100)};return e.addEventListener(`scroll`,n,{passive:!0}),()=>{e.removeEventListener(`scroll`,n),t&&clearTimeout(t)}},[i,h]),(0,S.jsx)(`div`,{ref:s,className:n(`reader-scrollbar-none h-full overflow-x-auto overflow-y-hidden`),children:(0,S.jsx)(`div`,{style:{width:y.getTotalSize(),height:`100%`,position:`relative`},children:y.getVirtualItems().map(n=>{let i=v(n.index),a=r[i];return a?(0,S.jsx)(`div`,{"data-index":n.index,className:`flex h-full items-center`,style:{position:`absolute`,top:0,left:0,height:`100%`,width:n.size,paddingRight:8,transform:`translateX(${n.start}px)`},children:(0,S.jsx)(`img`,{src:T(e,t,a.filename),alt:`第 ${i+1} 页`,draggable:!1,decoding:`async`,className:`h-full w-auto max-w-none select-none`})},n.key):null})})})}function B(e,t){return e?typeof e.scrollRatio==`number`?O(e.scrollRatio):typeof e.pageIndex==`number`?N(e.pageIndex,t):0:0}function V({id:e}){let t=o({select:e=>e.location.state?.readerTitle}),{data:r,isPending:m,error:g}=u(e),{settings:_,patchSettings:v}=s(`comic`),[w,T]=(0,x.useState)(0),[E,D]=(0,x.useState)(null),[k,A]=(0,x.useState)(!1),j=r&&r.type===`comic`?r:null,M=j?.pages.length??0,N=t??r?.title??`加载中...`,F=g?g instanceof Error?g.message:`加载失败`:null,I=(0,x.useMemo)(()=>b(w),[w]),L=(0,x.useCallback)((t,n)=>{j&&d(e,{type:`comic`,scrollRatio:t,completedAt:n})},[j,e]),R=(0,x.useCallback)(e=>{let t=O(e);T(t),L(t,P(t)?new Date().toISOString():null)},[L]),V=(0,x.useCallback)(e=>{D(w),v({direction:e})},[v,w]);return(0,x.useEffect)(()=>{D(null),T(0)},[e]),(0,x.useEffect)(()=>{if(!j||E!==null)return;let e=B(j.progress,M);D(e),T(e)},[j,M,E]),(0,x.useEffect)(()=>(l(e),()=>{f(e),p(e)}),[e]),(0,S.jsxs)(`div`,{className:`relative flex h-screen flex-col overflow-hidden bg-[#0a0a0f] text-[#d4c5a9]`,children:[(0,S.jsxs)(`header`,{className:`relative z-10 flex h-14 shrink-0 items-center gap-3 border-b border-[#25252a] px-4`,children:[(0,S.jsx)(i,{type:`button`,variant:`ghost`,size:`sm`,asChild:!0,children:(0,S.jsxs)(y,{to:`/`,children:[(0,S.jsx)(c,{"data-icon":`inline-start`}),`返回`]})}),(0,S.jsx)(`div`,{className:`flex-1 truncate text-center text-sm font-medium`,children:N}),(0,S.jsx)(i,{type:`button`,variant:`ghost`,size:`icon-sm`,onClick:()=>A(e=>!e),className:n(k&&`bg-[#25252a]`),children:(0,S.jsx)(h,{})})]}),(0,S.jsx)(C,{open:k,direction:_.direction,onDirectionChange:V}),(0,S.jsxs)(`div`,{className:`relative min-h-0 flex-1`,children:[m&&!j?(0,S.jsx)(`div`,{className:`flex size-full items-center justify-center`,children:(0,S.jsx)(a,{className:`h-[70vh] w-full max-w-3xl`})}):null,F?(0,S.jsx)(`div`,{className:`p-8 text-sm text-red-400`,children:F}):null,!m&&!F&&r&&r.type!==`comic`?(0,S.jsx)(`div`,{className:`p-8 text-sm text-red-400`,children:`作品类型不匹配`}):null,j&&M>0&&E!==null?(0,S.jsx)(z,{directoryId:j.directoryId,title:j.title,pages:j.pages,direction:_.direction,initialScrollRatio:E,onScrollProgress:R},`${e}-${_.direction}`):null,j&&M===0?(0,S.jsx)(`div`,{className:`flex size-full items-center justify-center text-sm text-muted-foreground`,children:`暂无页面`}):null]}),(0,S.jsxs)(`footer`,{className:`flex h-11 shrink-0 items-center justify-center border-t border-[#25252a] text-xs opacity-80`,children:[`已读 `,I,`%`]})]})}function H(){let{id:e}=v.useParams();return(0,S.jsx)(V,{id:e})}export{H as component};