Add books parsing function using foliate-js
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
[submodule "packages/foliate-js"]
|
||||
path = packages/foliate-js
|
||||
url = git@github.com:johnfactotum/foliate-js.git
|
||||
@@ -0,0 +1,145 @@
|
||||
import { BookFormat } from '@/types/book';
|
||||
|
||||
Object.groupBy ??= (iterable, callbackfn) => {
|
||||
const obj = Object.create(null);
|
||||
let i = 0;
|
||||
for (const value of iterable) {
|
||||
const key = callbackfn(value, i++);
|
||||
if (key in obj) {
|
||||
obj[key].push(value);
|
||||
} else {
|
||||
obj[key] = [value];
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
};
|
||||
|
||||
Map.groupBy ??= (iterable, callbackfn) => {
|
||||
const map = new Map();
|
||||
let i = 0;
|
||||
for (const value of iterable) {
|
||||
const key = callbackfn(value, i++),
|
||||
list = map.get(key);
|
||||
if (list) {
|
||||
list.push(value);
|
||||
} else {
|
||||
map.set(key, [value]);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
};
|
||||
|
||||
type DocumentFile = File;
|
||||
|
||||
export interface BookDoc {
|
||||
metadata: {
|
||||
title: string;
|
||||
author: string;
|
||||
editor?: string;
|
||||
publisher?: string;
|
||||
};
|
||||
getCover(): Promise<Blob | null>;
|
||||
}
|
||||
|
||||
export class DocumentLoader {
|
||||
private file: DocumentFile;
|
||||
|
||||
constructor(file: DocumentFile) {
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
private async isZip(): Promise<boolean> {
|
||||
const arr = new Uint8Array(await this.file.slice(0, 4).arrayBuffer());
|
||||
return arr[0] === 0x50 && arr[1] === 0x4b && arr[2] === 0x03 && arr[3] === 0x04;
|
||||
}
|
||||
|
||||
private async isPDF(): Promise<boolean> {
|
||||
const arr = new Uint8Array(await this.file.slice(0, 5).arrayBuffer());
|
||||
return (
|
||||
arr[0] === 0x25 && arr[1] === 0x50 && arr[2] === 0x44 && arr[3] === 0x46 && arr[4] === 0x2d
|
||||
);
|
||||
}
|
||||
|
||||
private async makeZipLoader() {
|
||||
const { configure, ZipReader, BlobReader, TextWriter, BlobWriter } = await import(
|
||||
'@zip.js/zip.js'
|
||||
);
|
||||
type Entry = import('@zip.js/zip.js').Entry;
|
||||
configure({ useWebWorkers: false });
|
||||
const reader = new ZipReader(new BlobReader(this.file));
|
||||
const entries = await reader.getEntries();
|
||||
const map = new Map(entries.map((entry) => [entry.filename, entry]));
|
||||
const load =
|
||||
(f: (entry: Entry, type?: string) => Promise<string | Blob> | null) =>
|
||||
(name: string, ...args: [string?]) =>
|
||||
map.has(name) ? f(map.get(name)!, ...args) : null;
|
||||
|
||||
const loadText = load((entry: Entry) =>
|
||||
entry.getData ? entry.getData(new TextWriter()) : null,
|
||||
);
|
||||
const loadBlob = load((entry: Entry, type?: string) =>
|
||||
entry.getData ? entry.getData(new BlobWriter(type!)) : null,
|
||||
);
|
||||
const getSize = (name: string) => map.get(name)?.uncompressedSize ?? 0;
|
||||
|
||||
return { entries, loadText, loadBlob, getSize, sha1: undefined };
|
||||
}
|
||||
|
||||
private isCBZ(): boolean {
|
||||
return this.file.type === 'application/vnd.comicbook+zip' || this.file.name.endsWith('.cbz');
|
||||
}
|
||||
|
||||
private isFB2(): boolean {
|
||||
return this.file.type === 'application/x-fictionbook+xml' || this.file.name.endsWith('.fb2');
|
||||
}
|
||||
|
||||
private isFBZ(): boolean {
|
||||
return (
|
||||
this.file.type === 'application/x-zip-compressed-fb2' ||
|
||||
this.file.name.endsWith('.fb2.zip') ||
|
||||
this.file.name.endsWith('.fbz')
|
||||
);
|
||||
}
|
||||
|
||||
public async open(): Promise<{ book: BookDoc; format: BookFormat }> {
|
||||
let book = null;
|
||||
let format: BookFormat = 'EPUB';
|
||||
if (!this.file.size) {
|
||||
throw new Error('File is empty');
|
||||
}
|
||||
if (await this.isZip()) {
|
||||
const loader = await this.makeZipLoader();
|
||||
const { entries } = loader;
|
||||
|
||||
if (this.isCBZ()) {
|
||||
const { makeComicBook } = await import('foliate-js/comic-book.js');
|
||||
book = makeComicBook(loader, this.file);
|
||||
format = 'CBZ';
|
||||
} else if (this.isFBZ()) {
|
||||
const entry = entries.find((entry) => entry.filename.endsWith('.fb2'));
|
||||
const blob = await loader.loadBlob((entry ?? entries[0]!).filename);
|
||||
const { makeFB2 } = await import('foliate-js/fb2.js');
|
||||
book = await makeFB2(blob);
|
||||
format = 'FBZ';
|
||||
} else {
|
||||
const { EPUB } = await import('foliate-js/epub.js');
|
||||
book = await new EPUB(loader).init();
|
||||
format = 'EPUB';
|
||||
}
|
||||
} else if (await this.isPDF()) {
|
||||
const { makePDF } = await import('foliate-js/pdf.js');
|
||||
book = await makePDF(this.file);
|
||||
format = 'PDF';
|
||||
} else if (await (await import('foliate-js/mobi.js')).isMOBI(this.file)) {
|
||||
const fflate = await import('foliate-js/vendor/fflate.js');
|
||||
const { MOBI } = await import('foliate-js/mobi.js');
|
||||
book = await new MOBI({ unzlib: fflate.unzlibSync }).open(this.file);
|
||||
format = 'MOBI';
|
||||
} else if (this.isFB2()) {
|
||||
const { makeFB2 } = await import('foliate-js/fb2.js');
|
||||
book = await makeFB2(this.file);
|
||||
format = 'FB2';
|
||||
}
|
||||
return { book, format } as { book: BookDoc; format: BookFormat };
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,9 @@
|
||||
"@tauri-apps/plugin-fs": "2.0.0-rc.2",
|
||||
"@tauri-apps/plugin-log": "2.0.0-rc.1",
|
||||
"@tauri-apps/plugin-os": "2.0.0-rc.1",
|
||||
"@zip.js/zip.js": "^2.7.52",
|
||||
"epubjs": "^0.3.93",
|
||||
"foliate-js": "workspace:*",
|
||||
"js-md5": "^0.8.3",
|
||||
"next": "14.2.13",
|
||||
"react": "^18",
|
||||
|
||||
@@ -41,19 +41,42 @@ const LibraryPage = () => {
|
||||
});
|
||||
}, [envConfig, appState]);
|
||||
|
||||
const handleImport = () => {
|
||||
// logic to import books
|
||||
const handleImportBooks = async () => {
|
||||
console.log('Importing books...');
|
||||
const appService = await envConfig.appService();
|
||||
appService.selectFiles('Select Books', ['epub', 'pdf']).then(async (files) => {
|
||||
setLoading(true);
|
||||
for (const file of files) {
|
||||
await dav?.addBook(file, libraryBooks).then(() => {});
|
||||
setLibraryBooks(libraryBooks);
|
||||
}
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='min-h-screen bg-gray-100'>
|
||||
<Navbar onImport={handleImport} />
|
||||
<Navbar onImportBooks={handleImportBooks} />
|
||||
<div className='min-h-screen p-2 pt-16'>
|
||||
<div className='hero-content'>
|
||||
<Spinner loading={loading} />
|
||||
<Bookshelf libraryBooks={libraryBooks} onImport={handleImport} />
|
||||
<Bookshelf libraryBooks={libraryBooks} onImportBooks={handleImportBooks} />
|
||||
</div>
|
||||
{libraryBooks.length === 0 && (
|
||||
<div className='hero min-h-screen'>
|
||||
<div className='hero-content text-neutral-content text-center'>
|
||||
<div className='max-w-md'>
|
||||
<h1 className='mb-5 text-5xl font-bold'>Your Library</h1>
|
||||
<p className='mb-5'>
|
||||
Welcome to your library. You can upload your books here and read them anytime.
|
||||
</p>
|
||||
<button className='btn btn-primary' onClick={handleImportBooks}>
|
||||
Upload Books
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -3,23 +3,18 @@ import Image from 'next/image';
|
||||
import { Book, BooksGroup } from '../types/book';
|
||||
import { FaPlus } from 'react-icons/fa';
|
||||
|
||||
interface BookshelfProps {
|
||||
libraryBooks: Book[];
|
||||
onImport: () => void;
|
||||
}
|
||||
|
||||
type BookshelfItem = Book | BooksGroup;
|
||||
|
||||
const UNGROUPED_NAME = 'ungrouped';
|
||||
|
||||
const MOCK_BOOKS: Book[] = Array.from({ length: 14 }, (_v, k) => ({
|
||||
id: `book-${k}`,
|
||||
format: 'EPUB',
|
||||
title: `Book ${k}`,
|
||||
author: `Author ${k}`,
|
||||
lastUpdated: Date.now() - 1000000 * k,
|
||||
coverImageUrl: `https://placehold.co/800?text=Book+${k}&font=roboto`,
|
||||
}));
|
||||
// const MOCK_BOOKS: Book[] = Array.from({ length: 14 }, (_v, k) => ({
|
||||
// hash: `book-${k}`,
|
||||
// format: 'EPUB',
|
||||
// title: `Book ${k}`,
|
||||
// author: `Author ${k}`,
|
||||
// lastUpdated: Date.now() - 1000000 * k,
|
||||
// coverImageUrl: `https://placehold.co/800?text=Book+${k}&font=roboto`,
|
||||
// }));
|
||||
|
||||
const generateBookshelfItems = (books: Book[]): BookshelfItem[] => {
|
||||
const groups: BooksGroup[] = books.reduce((acc: BooksGroup[], book: Book) => {
|
||||
@@ -43,8 +38,12 @@ const generateBookshelfItems = (books: Book[]): BookshelfItem[] => {
|
||||
return [...ungroupedBooks, ...groupedBooks].sort((a, b) => b.lastUpdated - a.lastUpdated);
|
||||
};
|
||||
|
||||
const Bookshelf: React.FC<BookshelfProps> = ({ libraryBooks, onImport }) => {
|
||||
libraryBooks = [...libraryBooks, ...MOCK_BOOKS];
|
||||
interface BookshelfProps {
|
||||
libraryBooks: Book[];
|
||||
onImportBooks: () => void;
|
||||
}
|
||||
|
||||
const Bookshelf: React.FC<BookshelfProps> = ({ libraryBooks, onImportBooks }) => {
|
||||
const bookshelfItems = generateBookshelfItems(libraryBooks);
|
||||
return (
|
||||
<div>
|
||||
@@ -55,7 +54,7 @@ const Bookshelf: React.FC<BookshelfProps> = ({ libraryBooks, onImport }) => {
|
||||
<div className='grid gap-2'>
|
||||
{'format' in item ? (
|
||||
<div>
|
||||
<div key={(item as Book).id} className='card bg-base-100 w-full shadow-md'>
|
||||
<div key={(item as Book).hash} className='card bg-base-100 w-full shadow-md'>
|
||||
<Image
|
||||
width={10}
|
||||
height={10}
|
||||
@@ -65,13 +64,13 @@ const Bookshelf: React.FC<BookshelfProps> = ({ libraryBooks, onImport }) => {
|
||||
/>
|
||||
</div>
|
||||
<div className='card-body p-0 pt-2'>
|
||||
<h3 className='card-title text-sm'>{(item as Book).title}</h3>
|
||||
<h3 className='card-title line-clamp-1 text-sm'>{(item as Book).title}</h3>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
{(item as BooksGroup).books.map((book) => (
|
||||
<div key={book.id} className='card bg-base-100 w-full shadow-md'>
|
||||
<div key={book.hash} className='card bg-base-100 w-full shadow-md'>
|
||||
<figure>
|
||||
<Image
|
||||
width={10}
|
||||
@@ -94,7 +93,7 @@ const Bookshelf: React.FC<BookshelfProps> = ({ libraryBooks, onImport }) => {
|
||||
<div
|
||||
className='border-1 flex aspect-[28/41] items-center justify-center bg-white'
|
||||
role='button'
|
||||
onClick={onImport}
|
||||
onClick={onImportBooks}
|
||||
>
|
||||
<FaPlus className='size-8' color='gray' />
|
||||
</div>
|
||||
|
||||
@@ -2,10 +2,10 @@ import * as React from 'react';
|
||||
import { FaSearch, FaPlus } from 'react-icons/fa';
|
||||
|
||||
interface NavbarProps {
|
||||
onImport: () => void;
|
||||
onImportBooks: () => void;
|
||||
}
|
||||
|
||||
const Navbar: React.FC<NavbarProps> = ({ onImport }) => {
|
||||
const Navbar: React.FC<NavbarProps> = ({ onImportBooks }) => {
|
||||
return (
|
||||
<div className='fixed top-0 z-10 w-full bg-gray-100 p-6'>
|
||||
<div className='flex items-center justify-between'>
|
||||
@@ -29,7 +29,7 @@ const Navbar: React.FC<NavbarProps> = ({ onImport }) => {
|
||||
className='dropdown-content menu bg-base-100 rounded-box z-[1] w-52 p-2 shadow'
|
||||
>
|
||||
<li>
|
||||
<button onClick={onImport}>From Local File</button>
|
||||
<button onClick={onImportBooks}>From Local File</button>
|
||||
</li>
|
||||
</ul>
|
||||
</span>
|
||||
|
||||
@@ -66,7 +66,7 @@ export const nativeAppService: AppService = {
|
||||
|
||||
return mode === 'text'
|
||||
? (readTextFile(fp, base && { baseDir }) as Promise<string>)
|
||||
: (await readFile(fp, base && { baseDir })).buffer;
|
||||
: ((await readFile(fp, base && { baseDir })).buffer as ArrayBuffer);
|
||||
},
|
||||
async writeFile(path: string, base: BaseDir, content: string | ArrayBuffer) {
|
||||
const { fp, baseDir } = resolvePath(path, base);
|
||||
@@ -188,6 +188,6 @@ export const nativeAppService: AppService = {
|
||||
return books;
|
||||
},
|
||||
generateCoverUrl: (book: Book) => {
|
||||
return convertFileSrc(`${BOOKS_DIR}/${book.id}/cover.png`);
|
||||
return convertFileSrc(`${BOOKS_DIR}/${book.hash}/cover.png`);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
export type BookFormat = 'EPUB' | 'PDF';
|
||||
export type BookFormat = 'EPUB' | 'PDF' | 'MOBI' | 'CBZ' | 'FB2' | 'FBZ';
|
||||
|
||||
export interface Book {
|
||||
id: string;
|
||||
hash: string;
|
||||
format: BookFormat;
|
||||
title: string;
|
||||
author: string;
|
||||
|
||||
Submodule
+1
Submodule packages/foliate-js added at e384aaf6dd
Generated
+36
-3
@@ -50,9 +50,15 @@ importers:
|
||||
'@tauri-apps/plugin-os':
|
||||
specifier: 2.0.0-rc.1
|
||||
version: 2.0.0-rc.1
|
||||
'@zip.js/zip.js':
|
||||
specifier: ^2.7.52
|
||||
version: 2.7.52
|
||||
epubjs:
|
||||
specifier: ^0.3.93
|
||||
version: 0.3.93
|
||||
foliate-js:
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/foliate-js
|
||||
js-md5:
|
||||
specifier: ^0.8.3
|
||||
version: 0.8.3
|
||||
@@ -103,6 +109,15 @@ importers:
|
||||
specifier: ^5
|
||||
version: 5.6.2
|
||||
|
||||
packages/foliate-js:
|
||||
devDependencies:
|
||||
'@eslint/js':
|
||||
specifier: ^9.1.0
|
||||
version: 9.12.0
|
||||
globals:
|
||||
specifier: ^15.0.0
|
||||
version: 15.11.0
|
||||
|
||||
packages:
|
||||
|
||||
'@alloc/quick-lru@5.2.0':
|
||||
@@ -131,6 +146,10 @@ packages:
|
||||
resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==}
|
||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||
|
||||
'@eslint/js@9.12.0':
|
||||
resolution: {integrity: sha512-eohesHH8WFRUprDNyEREgqP6beG6htMeUYeCpkEgBCieCMme5r9zFWjzAJp//9S+Kub4rqE+jXe9Cp1a7IYIIA==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
|
||||
'@humanwhocodes/config-array@0.13.0':
|
||||
resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==}
|
||||
engines: {node: '>=10.10.0'}
|
||||
@@ -448,6 +467,10 @@ packages:
|
||||
resolution: {integrity: sha512-lm2GW5PkosIzccsaZIz7tp8cPADSIlIHWDFTR1N0SzfinhhYgeIQjFMz4rYzanCScr3DqQLeomUDArp6MWKm+g==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
|
||||
'@zip.js/zip.js@2.7.52':
|
||||
resolution: {integrity: sha512-+5g7FQswvrCHwYKNMd/KFxZSObctLSsQOgqBSi0LzwHo3li9Eh1w5cF5ndjQw9Zbr3ajVnd2+XyiX85gAetx1Q==}
|
||||
engines: {bun: '>=0.7.0', deno: '>=1.0.0', node: '>=16.5.0'}
|
||||
|
||||
acorn-jsx@5.3.2:
|
||||
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
|
||||
peerDependencies:
|
||||
@@ -1023,6 +1046,10 @@ packages:
|
||||
resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
globals@15.11.0:
|
||||
resolution: {integrity: sha512-yeyNSjdbyVaWurlwCpcA6XNBrHTMIeDdj0/hnvX/OLJ9ekOXYbLsLinH/MucQyGvNnXhidTdNhTtJaffL2sMfw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
globalthis@1.0.4:
|
||||
resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -1998,6 +2025,8 @@ snapshots:
|
||||
|
||||
'@eslint/js@8.57.1': {}
|
||||
|
||||
'@eslint/js@9.12.0': {}
|
||||
|
||||
'@humanwhocodes/config-array@0.13.0':
|
||||
dependencies:
|
||||
'@humanwhocodes/object-schema': 2.0.3
|
||||
@@ -2284,6 +2313,8 @@ snapshots:
|
||||
|
||||
'@xmldom/xmldom@0.7.13': {}
|
||||
|
||||
'@zip.js/zip.js@2.7.52': {}
|
||||
|
||||
acorn-jsx@5.3.2(acorn@8.12.1):
|
||||
dependencies:
|
||||
acorn: 8.12.1
|
||||
@@ -2766,7 +2797,7 @@ snapshots:
|
||||
eslint: 8.57.1
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1))(eslint@8.57.1)
|
||||
eslint-plugin-import: 2.30.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1)
|
||||
eslint-plugin-import: 2.30.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1)
|
||||
eslint-plugin-jsx-a11y: 6.10.0(eslint@8.57.1)
|
||||
eslint-plugin-react: 7.36.1(eslint@8.57.1)
|
||||
eslint-plugin-react-hooks: 4.6.2(eslint@8.57.1)
|
||||
@@ -2801,7 +2832,7 @@ snapshots:
|
||||
is-bun-module: 1.2.1
|
||||
is-glob: 4.0.3
|
||||
optionalDependencies:
|
||||
eslint-plugin-import: 2.30.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1)
|
||||
eslint-plugin-import: 2.30.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1)
|
||||
transitivePeerDependencies:
|
||||
- '@typescript-eslint/parser'
|
||||
- eslint-import-resolver-node
|
||||
@@ -2819,7 +2850,7 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-plugin-import@2.30.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1):
|
||||
eslint-plugin-import@2.30.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1):
|
||||
dependencies:
|
||||
'@rtsao/scc': 1.1.0
|
||||
array-includes: 3.1.8
|
||||
@@ -3101,6 +3132,8 @@ snapshots:
|
||||
dependencies:
|
||||
type-fest: 0.20.2
|
||||
|
||||
globals@15.11.0: {}
|
||||
|
||||
globalthis@1.0.4:
|
||||
dependencies:
|
||||
define-properties: 1.2.1
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
packages:
|
||||
- 'apps/*'
|
||||
- 'packages/foliate-js'
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"incremental": true,
|
||||
"target": "es2022",
|
||||
"module": "preserve",
|
||||
"allowJs": true,
|
||||
"declaration": true,
|
||||
"strict": true,
|
||||
"moduleResolution": "bundler",
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
},
|
||||
"files": [
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "./packages"
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user