├── src ├── vite-env.d.ts ├── db │ ├── schema │ │ ├── index.ts │ │ ├── book.ts │ │ ├── collections.ts │ │ ├── goals.ts │ │ └── stats.ts │ ├── services │ │ ├── books.services.ts │ │ ├── goals.services.ts │ │ ├── collections.services.ts │ │ └── stats.services.ts │ └── index.ts ├── lib │ ├── utils.ts │ ├── types │ │ ├── history.ts │ │ ├── epub.ts │ │ ├── settings.ts │ │ └── pdf.ts │ ├── helpers │ │ ├── time.ts │ │ ├── history.ts │ │ ├── settings.ts │ │ └── pdf.ts │ └── constants.ts ├── main.tsx ├── components │ ├── ui │ │ ├── skeleton.tsx │ │ ├── spinner.tsx │ │ ├── label.tsx │ │ ├── separator.tsx │ │ ├── textarea.tsx │ │ ├── progress.tsx │ │ ├── kbd.tsx │ │ ├── input.tsx │ │ ├── switch.tsx │ │ ├── badge.tsx │ │ ├── alert.tsx │ │ ├── tooltip.tsx │ │ ├── button.tsx │ │ ├── card.tsx │ │ ├── table.tsx │ │ ├── dialog.tsx │ │ ├── drawer.tsx │ │ └── item.tsx │ ├── ErrorBoundary.tsx │ ├── BookCardSkeleton.tsx │ ├── theme-provider.tsx │ ├── collections │ │ ├── CollectionsGrid.tsx │ │ ├── CollectionsHeader.tsx │ │ └── CollectionDetail.tsx │ ├── TestReader.tsx │ ├── BookCard.tsx │ ├── PdfReaderHeader.tsx │ ├── dialogs │ │ ├── BookSummaryDialog.tsx │ │ └── SettingsDialog.tsx │ ├── DailyReadingSummary.tsx │ ├── Heatmap.tsx │ ├── PdfReader.tsx │ └── BookSelector.tsx ├── stores │ ├── useStatsStore.ts │ ├── useSettingsStore.ts │ ├── useHistoryStore.ts │ └── useReaderStore.ts ├── router.tsx ├── hooks │ ├── useBookCover.ts │ └── useReadingTimer.ts ├── App.tsx ├── App.css ├── pages │ ├── Home.tsx │ ├── Collections.tsx │ └── Library.tsx ├── assets │ └── react.svg └── layouts │ └── Root.tsx ├── src-tauri ├── build.rs ├── migrations │ ├── 0004_numerous_vermin.sql │ ├── 0002_vengeful_owl.sql │ ├── 0001_nappy_earthquake.sql │ ├── 0003_conscious_boomerang.sql │ ├── 0005_optimal_prism.sql │ ├── meta │ │ ├── _journal.json │ │ ├── 0000_snapshot.json │ │ └── 0001_snapshot.json │ └── 0000_modern_omega_flight.sql ├── icons │ ├── 32x32.png │ ├── icon.icns │ ├── icon.ico │ ├── icon.png │ ├── 128x128.png │ ├── 128x128@2x.png │ ├── StoreLogo.png │ ├── Square30x30Logo.png │ ├── Square44x44Logo.png │ ├── Square71x71Logo.png │ ├── Square89x89Logo.png │ ├── Square107x107Logo.png │ ├── Square142x142Logo.png │ ├── Square150x150Logo.png │ ├── Square284x284Logo.png │ └── Square310x310Logo.png ├── .gitignore ├── src │ ├── main.rs │ └── lib.rs ├── capabilities │ ├── desktop.json │ └── default.json ├── tauri.conf.json └── Cargo.toml ├── public ├── fonts │ ├── Lexend.ttf │ ├── Roboto.ttf │ ├── Comfortaa.ttf │ ├── Helvetica.ttf │ ├── SegoeUI.ttf │ └── RobotoCondensed.ttf ├── vite.svg ├── epub.svg ├── tauri.svg └── pdf.svg ├── screenshots ├── ketav1.png ├── ketav2.png ├── ketav3.png ├── ketav4.png ├── ketav5.png └── ketav6.png ├── target └── rust-analyzer │ └── flycheck0 │ └── stderr ├── .vscode └── extensions.json ├── tsconfig.node.json ├── .gitignore ├── index.html ├── drizzle.config.ts ├── components.json ├── tsconfig.json ├── vite.config.ts ├── README.md ├── package.json └── ROADMAP.MD /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src-tauri/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | tauri_build::build() 3 | } 4 | -------------------------------------------------------------------------------- /src-tauri/migrations/0004_numerous_vermin.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `books` ADD `pages` integer; -------------------------------------------------------------------------------- /src-tauri/migrations/0002_vengeful_owl.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `books` ADD `file_name` text NOT NULL; -------------------------------------------------------------------------------- /public/fonts/Lexend.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/henacodes/ketav/HEAD/public/fonts/Lexend.ttf -------------------------------------------------------------------------------- /public/fonts/Roboto.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/henacodes/ketav/HEAD/public/fonts/Roboto.ttf -------------------------------------------------------------------------------- /screenshots/ketav1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/henacodes/ketav/HEAD/screenshots/ketav1.png -------------------------------------------------------------------------------- /screenshots/ketav2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/henacodes/ketav/HEAD/screenshots/ketav2.png -------------------------------------------------------------------------------- /screenshots/ketav3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/henacodes/ketav/HEAD/screenshots/ketav3.png -------------------------------------------------------------------------------- /screenshots/ketav4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/henacodes/ketav/HEAD/screenshots/ketav4.png -------------------------------------------------------------------------------- /screenshots/ketav5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/henacodes/ketav/HEAD/screenshots/ketav5.png -------------------------------------------------------------------------------- /screenshots/ketav6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/henacodes/ketav/HEAD/screenshots/ketav6.png -------------------------------------------------------------------------------- /src-tauri/migrations/0001_nappy_earthquake.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `books` ADD `cover_image_path` text; -------------------------------------------------------------------------------- /public/fonts/Comfortaa.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/henacodes/ketav/HEAD/public/fonts/Comfortaa.ttf -------------------------------------------------------------------------------- /public/fonts/Helvetica.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/henacodes/ketav/HEAD/public/fonts/Helvetica.ttf -------------------------------------------------------------------------------- /public/fonts/SegoeUI.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/henacodes/ketav/HEAD/public/fonts/SegoeUI.ttf -------------------------------------------------------------------------------- /src-tauri/icons/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/henacodes/ketav/HEAD/src-tauri/icons/32x32.png -------------------------------------------------------------------------------- /src-tauri/icons/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/henacodes/ketav/HEAD/src-tauri/icons/icon.icns -------------------------------------------------------------------------------- /src-tauri/icons/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/henacodes/ketav/HEAD/src-tauri/icons/icon.ico -------------------------------------------------------------------------------- /src-tauri/icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/henacodes/ketav/HEAD/src-tauri/icons/icon.png -------------------------------------------------------------------------------- /src-tauri/icons/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/henacodes/ketav/HEAD/src-tauri/icons/128x128.png -------------------------------------------------------------------------------- /src-tauri/icons/128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/henacodes/ketav/HEAD/src-tauri/icons/128x128@2x.png -------------------------------------------------------------------------------- /src-tauri/icons/StoreLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/henacodes/ketav/HEAD/src-tauri/icons/StoreLogo.png -------------------------------------------------------------------------------- /target/rust-analyzer/flycheck0/stderr: -------------------------------------------------------------------------------- 1 | Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.40s 2 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["tauri-apps.tauri-vscode", "rust-lang.rust-analyzer"] 3 | } 4 | -------------------------------------------------------------------------------- /public/fonts/RobotoCondensed.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/henacodes/ketav/HEAD/public/fonts/RobotoCondensed.ttf -------------------------------------------------------------------------------- /src-tauri/icons/Square30x30Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/henacodes/ketav/HEAD/src-tauri/icons/Square30x30Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square44x44Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/henacodes/ketav/HEAD/src-tauri/icons/Square44x44Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square71x71Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/henacodes/ketav/HEAD/src-tauri/icons/Square71x71Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square89x89Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/henacodes/ketav/HEAD/src-tauri/icons/Square89x89Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square107x107Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/henacodes/ketav/HEAD/src-tauri/icons/Square107x107Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square142x142Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/henacodes/ketav/HEAD/src-tauri/icons/Square142x142Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square150x150Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/henacodes/ketav/HEAD/src-tauri/icons/Square150x150Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square284x284Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/henacodes/ketav/HEAD/src-tauri/icons/Square284x284Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square310x310Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/henacodes/ketav/HEAD/src-tauri/icons/Square310x310Logo.png -------------------------------------------------------------------------------- /src/db/schema/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./book"; 2 | export * from "./stats"; 3 | export * from "./goals"; 4 | export * from "./collections"; 5 | -------------------------------------------------------------------------------- /src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { clsx, type ClassValue } from "clsx" 2 | import { twMerge } from "tailwind-merge" 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | -------------------------------------------------------------------------------- /src-tauri/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Generated by Tauri 6 | # will have schema files for capabilities auto-completion 7 | /gen/schemas 8 | -------------------------------------------------------------------------------- /src-tauri/src/main.rs: -------------------------------------------------------------------------------- 1 | // Prevents additional console window on Windows in release, DO NOT REMOVE!! 2 | #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] 3 | 4 | fn main() { 5 | ketav_lib::run() 6 | } 7 | -------------------------------------------------------------------------------- /src/lib/types/history.ts: -------------------------------------------------------------------------------- 1 | export type LastOpenedPage = Record; 2 | 3 | export interface History { 4 | // last opened pages of different pdf books 5 | lastOpenedPages: LastOpenedPage[]; 6 | lastOpenedBookFileName: string; 7 | } 8 | -------------------------------------------------------------------------------- /src-tauri/capabilities/desktop.json: -------------------------------------------------------------------------------- 1 | { 2 | "identifier": "desktop-capability", 3 | "platforms": [ 4 | "macOS", 5 | "windows", 6 | "linux" 7 | ], 8 | "windows": [ 9 | "main" 10 | ], 11 | "permissions": [ 12 | "autostart:default" 13 | ] 14 | } -------------------------------------------------------------------------------- /src/lib/types/epub.ts: -------------------------------------------------------------------------------- 1 | import type { EpubMetadata } from "epubix"; 2 | import { Epub } from "epubix"; 3 | export type SupportedEbooks = "epub"; 4 | 5 | export type LibraryEpub = EpubMetadata & { 6 | fileName: string; 7 | }; 8 | 9 | export type OpenEpub = { 10 | metadata: LibraryEpub; 11 | book: Epub; 12 | }; 13 | -------------------------------------------------------------------------------- /src/main.tsx: -------------------------------------------------------------------------------- 1 | 2 | import ReactDOM from "react-dom/client"; 3 | import { router } from "./router.tsx"; 4 | import { RouterProvider } from "react-router/dom"; 5 | import "./index.css"; 6 | 7 | const rootEl = document.getElementById("root") as HTMLElement; 8 | ReactDOM.createRoot(rootEl).render(); 9 | -------------------------------------------------------------------------------- /src-tauri/migrations/0003_conscious_boomerang.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE `daily_reading_progress` ( 2 | `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, 3 | `goal_id` integer, 4 | `date` text NOT NULL, 5 | `minutes_read` integer NOT NULL, 6 | FOREIGN KEY (`goal_id`) REFERENCES `daily_reading_goal`(`id`) ON UPDATE no action ON DELETE cascade 7 | ); 8 | -------------------------------------------------------------------------------- /src/components/ui/skeleton.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils" 2 | 3 | function Skeleton({ className, ...props }: React.ComponentProps<"div">) { 4 | return ( 5 |
10 | ) 11 | } 12 | 13 | export { Skeleton } 14 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true, 8 | "baseUrl": ".", 9 | "paths": { 10 | "@/*": ["./src/*"] 11 | } 12 | }, 13 | "include": ["vite.config.ts"] 14 | } 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /src/lib/types/settings.ts: -------------------------------------------------------------------------------- 1 | export type AlignmentOptions = "left" | "center" | "justify"; 2 | export type FontFamilyOptions = 3 | | "Helvetica" 4 | | "Lexend" 5 | | "SegoeUI" 6 | | "Robot" 7 | | "RobotoCondensed" 8 | | "Comfortaa"; 9 | 10 | export interface Settings { 11 | libraryFolderPath: string; 12 | textAlignment: AlignmentOptions; 13 | fontFamily: FontFamilyOptions; 14 | } 15 | -------------------------------------------------------------------------------- /src/lib/types/pdf.ts: -------------------------------------------------------------------------------- 1 | export type PdfMetadata = { 2 | title?: string | null; 3 | author?: string | null; 4 | pages?: number | null; 5 | // other pdf metadata you might extract 6 | fileName?: string; 7 | }; 8 | 9 | export type OpenPdf = { 10 | type: "pdf"; 11 | metadata: PdfMetadata; 12 | // the raw bytes (Uint8Array) of the PDF; PdfReader will consume this 13 | fileBytes: Uint8Array; 14 | }; 15 | -------------------------------------------------------------------------------- /src/components/ui/spinner.tsx: -------------------------------------------------------------------------------- 1 | import { Loader2Icon } from "lucide-react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | function Spinner({ className, ...props }: React.ComponentProps<"svg">) { 6 | return ( 7 | 13 | ) 14 | } 15 | 16 | export { Spinner } 17 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Tauri + React + Typescript 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /drizzle.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "drizzle-kit"; 2 | 3 | 4 | // the sqlite file resides in the tauri app data directory 5 | // i had no choice but to hardcode it for now 6 | 7 | export default defineConfig({ 8 | out: "./src-tauri/migrations", 9 | schema: "./src/db/schema/index.ts", 10 | dialect: "sqlite", 11 | dbCredentials: { 12 | url: `file:/home/kirakos/.config/com.kirakos.ketav/ketav-local.db`, 13 | }, 14 | }); 15 | -------------------------------------------------------------------------------- /src/db/services/books.services.ts: -------------------------------------------------------------------------------- 1 | import { db } from ".."; 2 | import { books, InsertBook } from "../schema"; 3 | 4 | export async function registerBook(input: InsertBook) { 5 | try { 6 | await db.insert(books).values({ ...input }); 7 | } catch (error) { 8 | console.log("Failed to register a book: ", error); 9 | } 10 | } 11 | 12 | export async function fetchAllDbBooks() { 13 | const res = await db.select().from(books); 14 | 15 | return res; 16 | } 17 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": false, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "", 8 | "css": "src/index.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "iconLibrary": "lucide", 14 | "aliases": { 15 | "components": "@/components", 16 | "utils": "@/lib/utils", 17 | "ui": "@/components/ui", 18 | "lib": "@/lib", 19 | "hooks": "@/hooks" 20 | }, 21 | "registries": {} 22 | } 23 | -------------------------------------------------------------------------------- /src/db/schema/book.ts: -------------------------------------------------------------------------------- 1 | import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core"; 2 | import { InferInsertModel, InferSelectModel } from "drizzle-orm"; 3 | export const books = sqliteTable("books", { 4 | id: integer("id").primaryKey({ autoIncrement: true }), 5 | bookId: text("book_id").unique().notNull(), 6 | title: text("title").notNull(), 7 | author: text("author").notNull(), 8 | coverImagePath: text("cover_image_path"), 9 | fileName: text("file_name").notNull(), 10 | pages: integer("pages"), 11 | }); 12 | 13 | export type Book = InferSelectModel; 14 | export type InsertBook = InferInsertModel; 15 | -------------------------------------------------------------------------------- /src-tauri/migrations/0005_optimal_prism.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE `collection_books` ( 2 | `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, 3 | `collection_id` integer NOT NULL, 4 | `book_id` integer NOT NULL, 5 | FOREIGN KEY (`collection_id`) REFERENCES `collections`(`id`) ON UPDATE no action ON DELETE cascade, 6 | FOREIGN KEY (`book_id`) REFERENCES `books`(`id`) ON UPDATE no action ON DELETE cascade 7 | ); 8 | --> statement-breakpoint 9 | CREATE TABLE `collections` ( 10 | `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, 11 | `name` text NOT NULL, 12 | `description` text 13 | ); 14 | --> statement-breakpoint 15 | CREATE UNIQUE INDEX `collections_name_unique` ON `collections` (`name`); -------------------------------------------------------------------------------- /src/lib/helpers/time.ts: -------------------------------------------------------------------------------- 1 | import { format } from "date-fns"; 2 | 3 | export function formatMinutesToTime(totalMinutes: number): string { 4 | if (totalMinutes <= 0) return "0 mins"; 5 | 6 | const hours = Math.floor(totalMinutes / 60); 7 | const minutes = totalMinutes % 60; 8 | 9 | if (hours > 0 && minutes > 0) { 10 | return `${hours} hr${hours > 1 ? "s" : ""} ${minutes} min${ 11 | minutes > 1 ? "s" : "" 12 | }`; 13 | } else if (hours > 0) { 14 | return `${hours} hr${hours > 1 ? "s" : ""}`; 15 | } else { 16 | return `${minutes} min${minutes > 1 ? "s" : ""}`; 17 | } 18 | } 19 | 20 | export function today() { 21 | return format(new Date(), "yyyy-MM-dd"); 22 | } 23 | -------------------------------------------------------------------------------- /src/lib/helpers/history.ts: -------------------------------------------------------------------------------- 1 | import { load } from "@tauri-apps/plugin-store"; 2 | import { History, LastOpenedPage } from "../types/history"; 3 | import { STORE_KEYS } from "../constants"; 4 | 5 | export async function getHistory(): Promise { 6 | const store = await load("history.json"); 7 | 8 | let lastOpenedBookFileName = await store.get<{ fileName: string }>( 9 | STORE_KEYS.lastOpenedBook 10 | ); 11 | 12 | let result = await store.get<{ lastOpenedPages: LastOpenedPage[] }>( 13 | STORE_KEYS.lastOpenedPage 14 | ); 15 | 16 | return { 17 | lastOpenedBookFileName: lastOpenedBookFileName?.fileName || "", 18 | lastOpenedPages: result?.lastOpenedPages || [], 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /src/components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as LabelPrimitive from "@radix-ui/react-label" 3 | 4 | import { cn } from "@/lib/utils" 5 | 6 | function Label({ 7 | className, 8 | ...props 9 | }: React.ComponentProps) { 10 | return ( 11 | 19 | ) 20 | } 21 | 22 | export { Label } 23 | -------------------------------------------------------------------------------- /src/lib/constants.ts: -------------------------------------------------------------------------------- 1 | import { AlignmentOptions, FontFamilyOptions } from "./types/settings"; 2 | 3 | export const STORE_KEYS = { 4 | libraryFolderPath: "library-folder-path", 5 | fontFamily: "font-family", 6 | textAlignment: "text-alignment", 7 | lastOpenedBook: "last-opened-book", 8 | lastOpenedPage: "last-opened-page", 9 | }; 10 | 11 | // default store values 12 | export const DEFAULT_LIBRARY_FOLDER_PATH = "books"; 13 | export const DEFAULT_TEXT_ALIGNMENT: AlignmentOptions = "justify"; 14 | export const DEFAULT_FONT_FAMILY: FontFamilyOptions = "Helvetica"; 15 | 16 | export const DATABASE_NAME = "ketav-local.db"; 17 | export const THEME_STORAGE_KEY = "vite-ui-theme"; 18 | 19 | export const BOOK_COVER_IMAGE_FOLDER = "covers"; 20 | -------------------------------------------------------------------------------- /src/components/ui/separator.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as SeparatorPrimitive from "@radix-ui/react-separator" 3 | 4 | import { cn } from "@/lib/utils" 5 | 6 | function Separator({ 7 | className, 8 | orientation = "horizontal", 9 | decorative = true, 10 | ...props 11 | }: React.ComponentProps) { 12 | return ( 13 | 23 | ) 24 | } 25 | 26 | export { Separator } 27 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true, 22 | 23 | /* For Shadcn/tailwind */ 24 | "baseUrl": ".", 25 | "paths": { 26 | "@/*": ["./src/*"] 27 | } 28 | }, 29 | "include": ["src"], 30 | "references": [{ "path": "./tsconfig.node.json" }] 31 | } 32 | -------------------------------------------------------------------------------- /src/components/ui/textarea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | function Textarea({ className, ...props }: React.ComponentProps<"textarea">) { 6 | return ( 7 |