├── packages ├── epubjs │ ├── .nojekyll │ ├── .npmignore │ ├── test │ │ ├── fixtures │ │ │ ├── alice │ │ │ │ ├── mimetype │ │ │ │ ├── OPS │ │ │ │ │ ├── images │ │ │ │ │ │ ├── title.jpg │ │ │ │ │ │ ├── cover_th.jpg │ │ │ │ │ │ ├── i001_th.jpg │ │ │ │ │ │ ├── i002_th.jpg │ │ │ │ │ │ ├── i003_th.jpg │ │ │ │ │ │ ├── i004_th.jpg │ │ │ │ │ │ ├── i005_th.jpg │ │ │ │ │ │ ├── i006_th.jpg │ │ │ │ │ │ ├── i007_th.jpg │ │ │ │ │ │ ├── i008_th.jpg │ │ │ │ │ │ ├── i009_th.jpg │ │ │ │ │ │ ├── i010_th.jpg │ │ │ │ │ │ ├── i011_th.jpg │ │ │ │ │ │ ├── i012_th.jpg │ │ │ │ │ │ ├── i013_th.jpg │ │ │ │ │ │ ├── i014_th.jpg │ │ │ │ │ │ ├── i015_th.jpg │ │ │ │ │ │ ├── i016_th.jpg │ │ │ │ │ │ ├── i017_th.jpg │ │ │ │ │ │ ├── i018_th.jpg │ │ │ │ │ │ ├── i019_th.jpg │ │ │ │ │ │ ├── i020_th.jpg │ │ │ │ │ │ ├── i022_th.jpg │ │ │ │ │ │ ├── ii021_th.jpg │ │ │ │ │ │ ├── plate01_th.jpg │ │ │ │ │ │ ├── plate02_th.jpg │ │ │ │ │ │ ├── plate03_th.jpg │ │ │ │ │ │ └── plate04_th.jpg │ │ │ │ │ ├── cover.xhtml │ │ │ │ │ ├── titlepage.xhtml │ │ │ │ │ └── toc.xhtml │ │ │ │ └── META-INF │ │ │ │ │ └── container.xml │ │ │ ├── alice.epub │ │ │ └── alice_without_cover.epub │ │ ├── index.html │ │ ├── old │ │ │ └── rendering.js │ │ └── locations.js │ ├── tsconfig.json │ ├── types │ │ ├── tslint.json │ │ ├── utils │ │ │ ├── scrolltype.d.ts │ │ │ ├── request.d.ts │ │ │ ├── constants.d.ts │ │ │ ├── url.d.ts │ │ │ ├── path.d.ts │ │ │ ├── hook.d.ts │ │ │ ├── replacements.d.ts │ │ │ └── queue.d.ts │ │ ├── container.d.ts │ │ ├── epub.d.ts │ │ ├── epubjs-tests.ts │ │ ├── tsconfig.json │ │ ├── index.d.ts │ │ ├── spine.d.ts │ │ ├── pagelist.d.ts │ │ ├── archive.d.ts │ │ ├── themes.d.ts │ │ ├── resources.d.ts │ │ ├── mapping.d.ts │ │ ├── locations.d.ts │ │ ├── store.d.ts │ │ ├── layout.d.ts │ │ ├── navigation.d.ts │ │ └── section.d.ts │ ├── .watchmanconfig │ ├── documentation.yml │ ├── examples │ │ ├── ajax-loader.gif │ │ ├── themes.css │ │ ├── contents.html │ │ └── toc.html │ ├── .gitignore │ ├── .travis.yml │ ├── src │ │ ├── index.js │ │ ├── epub.js │ │ └── container.js │ ├── eslint.config.js │ ├── .babelrc.json │ ├── .jshintrc │ ├── bower.json │ └── license ├── internal │ ├── src │ │ ├── index.ts │ │ └── lib.ts │ ├── tsconfig.json │ └── package.json └── tailwind │ ├── package.json │ └── src │ ├── state.js │ ├── elevation.js │ └── index.js ├── apps ├── api │ ├── src │ │ ├── config │ │ │ ├── __init__.py │ │ │ ├── seed.py │ │ │ ├── app_config.py │ │ │ └── db.py │ │ ├── domain │ │ │ ├── __init__.py │ │ │ ├── book │ │ │ │ ├── __init__.py │ │ │ │ ├── value_objects │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── book_title.py │ │ │ │ │ ├── book_id.py │ │ │ │ │ ├── tennant_id.py │ │ │ │ │ └── book_metadata.py │ │ │ │ ├── entities │ │ │ │ │ └── __init__.py │ │ │ │ ├── repositories │ │ │ │ │ ├── __init__.py │ │ │ │ │ └── book_repository.py │ │ │ │ └── exceptions │ │ │ │ │ ├── __init__.py │ │ │ │ │ └── book_exceptions.py │ │ │ ├── chat │ │ │ │ ├── __init__.py │ │ │ │ ├── entities │ │ │ │ │ └── __init__.py │ │ │ │ ├── exceptions │ │ │ │ │ ├── __init__.py │ │ │ │ │ └── chat_exceptions.py │ │ │ │ ├── repositories │ │ │ │ │ ├── __init__.py │ │ │ │ │ └── chat_repository.py │ │ │ │ └── value_objects │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── chat_title.py │ │ │ │ │ ├── book_id.py │ │ │ │ │ ├── chat_id.py │ │ │ │ │ └── user_id.py │ │ │ ├── annotation │ │ │ │ ├── __init__.py │ │ │ │ ├── entities │ │ │ │ │ └── __init__.py │ │ │ │ ├── repositories │ │ │ │ │ ├── __init__.py │ │ │ │ │ └── annotation_repository.py │ │ │ │ ├── value_objects │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── annotation_cfi.py │ │ │ │ │ ├── annotation_notes.py │ │ │ │ │ ├── annotation_text.py │ │ │ │ │ ├── annotation_id.py │ │ │ │ │ ├── annotation_type.py │ │ │ │ │ └── annotation_color.py │ │ │ │ └── exceptions.py │ │ │ ├── podcast │ │ │ │ ├── __init__.py │ │ │ │ ├── entities │ │ │ │ │ └── __init__.py │ │ │ │ ├── repositories │ │ │ │ │ └── __init__.py │ │ │ │ ├── value_objects │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── language.py │ │ │ │ │ ├── podcast_id.py │ │ │ │ │ └── speaker_role.py │ │ │ │ └── exceptions │ │ │ │ │ └── __init__.py │ │ │ └── message │ │ │ │ ├── entities │ │ │ │ └── __init__.py │ │ │ │ ├── repositories │ │ │ │ ├── __init__.py │ │ │ │ └── message_repository.py │ │ │ │ ├── value_objects │ │ │ │ ├── __init__.py │ │ │ │ ├── message_content.py │ │ │ │ ├── message_id.py │ │ │ │ └── sender_type.py │ │ │ │ ├── exceptions │ │ │ │ ├── __init__.py │ │ │ │ └── message_exceptions.py │ │ │ │ └── __init__.py │ │ ├── infrastructure │ │ │ ├── __init__.py │ │ │ ├── external │ │ │ │ ├── __init__.py │ │ │ │ ├── cloud_tts │ │ │ │ │ └── __init__.py │ │ │ │ ├── gemini │ │ │ │ │ ├── prompts │ │ │ │ │ │ └── __init__.py │ │ │ │ │ └── __init__.py │ │ │ │ ├── epub │ │ │ │ │ ├── __init__.py │ │ │ │ │ └── epub_reader.py │ │ │ │ └── audio │ │ │ │ │ └── __init__.py │ │ │ ├── di │ │ │ │ └── __init__.py │ │ │ ├── postgres │ │ │ │ ├── chat │ │ │ │ │ └── __init__.py │ │ │ │ ├── user │ │ │ │ │ └── __init__.py │ │ │ │ ├── podcast │ │ │ │ │ └── __init__.py │ │ │ │ ├── annotation │ │ │ │ │ └── __init__.py │ │ │ │ ├── book │ │ │ │ │ └── __init__.py │ │ │ │ ├── message │ │ │ │ │ └── __init__.py │ │ │ │ ├── __init__.py │ │ │ │ └── db_util.py │ │ │ ├── memory │ │ │ │ ├── __init__.py │ │ │ │ └── retry_decorator.py │ │ │ └── vector.py │ │ ├── usecase │ │ │ ├── annotation │ │ │ │ └── __init__.py │ │ │ ├── podcast │ │ │ │ ├── __init__.py │ │ │ │ ├── find_podcasts_by_book_id_usecase.py │ │ │ │ ├── podcast_config.py │ │ │ │ └── find_podcast_by_id_usecase.py │ │ │ ├── chat │ │ │ │ ├── __init__.py │ │ │ │ ├── find_chats_by_user_id_usecase.py │ │ │ │ ├── delete_chat_usecase.py │ │ │ │ ├── find_chat_by_id_usecase.py │ │ │ │ ├── find_chats_by_user_id_and_book_id_usecase.py │ │ │ │ ├── create_chat_usecase.py │ │ │ │ └── update_chat_title_usecase.py │ │ │ ├── message │ │ │ │ ├── __init__.py │ │ │ │ ├── find_message_by_id_usecase.py │ │ │ │ ├── find_messages_usecase.py │ │ │ │ └── highlight_searcher.py │ │ │ └── book │ │ │ │ ├── create_book_vector_index_usecase.py │ │ │ │ ├── find_books_usecase.py │ │ │ │ ├── find_book_by_id_usecase.py │ │ │ │ └── __init__.py │ │ ├── presentation │ │ │ └── api │ │ │ │ ├── handlers │ │ │ │ ├── __init__.py │ │ │ │ ├── annotation_api_route_handler.py │ │ │ │ └── rag_api_route_handler.py │ │ │ │ ├── schemas │ │ │ │ ├── __init__.py │ │ │ │ ├── base_schema.py │ │ │ │ ├── annotation_schema.py │ │ │ │ └── chat_schema.py │ │ │ │ ├── converters │ │ │ │ └── __init__.py │ │ │ │ ├── error_messages │ │ │ │ ├── __init__.py │ │ │ │ ├── chat_error_message.py │ │ │ │ ├── message_error_message.py │ │ │ │ └── book_error_message.py │ │ │ │ ├── __init__.py │ │ │ │ └── routes.py │ │ ├── __init__.py │ │ └── main.py │ ├── .python-version │ ├── gcs_fixtures │ │ └── bookwith-bucket │ │ │ └── .gitkeep │ ├── package.json │ ├── index_tenant_mapping.json │ └── docker-compose.yml └── reader │ ├── .gitignore │ ├── src │ ├── components │ │ ├── pages │ │ │ └── index.ts │ │ ├── Reader.tsx │ │ ├── Library.tsx │ │ ├── base │ │ │ ├── GridView.tsx │ │ │ ├── index.ts │ │ │ └── ActionBar.tsx │ │ ├── TextSelectionMenu.tsx │ │ ├── library │ │ │ └── index.ts │ │ ├── textSelection │ │ │ └── index.ts │ │ ├── chat │ │ │ ├── types.ts │ │ │ └── EmptyState.tsx │ │ ├── reader │ │ │ ├── PaneContainer.tsx │ │ │ ├── index.ts │ │ │ ├── Bar.tsx │ │ │ ├── ReaderGridView.tsx │ │ │ ├── eventHandlers.ts │ │ │ ├── ReaderPaneHeader.tsx │ │ │ └── ReaderPaneFooter.tsx │ │ ├── index.ts │ │ ├── viewlets │ │ │ └── PodcastView.tsx │ │ ├── podcast │ │ │ ├── index.ts │ │ │ └── PodcastPane.tsx │ │ ├── Page.tsx │ │ ├── FormattedText.tsx │ │ ├── ui │ │ │ ├── textarea.tsx │ │ │ ├── input.tsx │ │ │ ├── progress.tsx │ │ │ ├── sonner.tsx │ │ │ ├── spinner.tsx │ │ │ ├── badge.tsx │ │ │ └── tooltip.tsx │ │ ├── ErrorBoundary.tsx │ │ └── Button.tsx │ ├── models │ │ ├── index.ts │ │ └── tree.ts │ ├── pages │ │ ├── _.tsx │ │ ├── success.tsx │ │ └── _app.tsx │ ├── types │ │ ├── window.d.ts │ │ ├── podcast.ts │ │ └── loading.ts │ ├── hooks │ │ ├── theme │ │ │ ├── index.ts │ │ │ ├── useTheme.ts │ │ │ ├── useSourceColor.ts │ │ │ └── useColorScheme.ts │ │ ├── loading │ │ │ ├── index.ts │ │ │ ├── useLoadingState.ts │ │ │ └── useProgressManager.ts │ │ ├── useForceRender.ts │ │ ├── useEnv.ts │ │ ├── useList.ts │ │ ├── useAfterMount.ts │ │ ├── useAutoResize.ts │ │ ├── usePress.ts │ │ ├── useAsync.ts │ │ ├── useBoolean.ts │ │ ├── useHover.ts │ │ ├── useAction.ts │ │ ├── useSWR │ │ │ ├── fetcher.ts │ │ │ ├── useLibrary.ts │ │ │ ├── usePodcast.ts │ │ │ └── useChat.ts │ │ ├── useMobile.ts │ │ ├── useMediaQuery.ts │ │ ├── index.ts │ │ ├── useDisablePinchZooming.ts │ │ ├── useTypography.ts │ │ ├── useIntermediateKeyword.ts │ │ ├── useIntermediateChatKeyword.ts │ │ ├── useTranslation.ts │ │ ├── useLoading.ts │ │ └── useLongPress.ts │ ├── lib │ │ └── utils.ts │ ├── utils │ │ ├── mime.ts │ │ ├── platform.ts │ │ ├── epub.ts │ │ ├── state.ts │ │ ├── color.ts │ │ ├── fileUtils.ts │ │ ├── utils.ts │ │ └── annotation.ts │ ├── store │ │ ├── loading │ │ │ ├── atoms.ts │ │ │ ├── index.ts │ │ │ └── selectors.ts │ │ └── loading.ts │ └── constants │ │ ├── audio.ts │ │ └── podcast.ts │ ├── sentry.properties │ ├── public │ ├── icons │ │ ├── 192.png │ │ ├── 512.png │ │ ├── maskable-192.png │ │ └── maskable-512.png │ └── manifest.json │ ├── postcss.config.js │ ├── .prettierignore │ ├── locales │ └── index.ts │ ├── next-env.d.ts │ ├── tsconfig.json │ ├── docker-compose.yml │ ├── .env.example │ ├── web.d.ts │ ├── netlify.toml │ ├── components.json │ ├── eslint.config.js │ ├── sentry.client.config.js │ └── sentry.server.config.js ├── .gitattributes ├── .dockerignore ├── pnpm-workspace.yaml ├── .husky └── pre-commit ├── .npmrc ├── supabase ├── .gitignore └── migrations │ └── 20250713003643_add_language_to_podcast.sql ├── .prettierignore ├── prettier.config.cjs ├── .mcp.json ├── tsconfig.ts.json ├── poetry.lock ├── tsconfig.react.json ├── turbo.json ├── pyproject.toml ├── .pre-commit-config.yaml ├── .github └── ISSUE_TEMPLATE │ ├── feature-request.yml │ └── bug_report.yml ├── tsconfig.next.json ├── tsconfig.json ├── .claude └── settings.local.json ├── .gitignore ├── .vscode └── settings.json └── package.json /packages/epubjs/.nojekyll: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/api/src/config/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/api/src/domain/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/api/.python-version: -------------------------------------------------------------------------------- 1 | 3.13.2 2 | -------------------------------------------------------------------------------- /apps/api/src/domain/book/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/api/src/domain/chat/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/api/src/domain/annotation/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/api/src/domain/podcast/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/api/src/infrastructure/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/api/src/usecase/annotation/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/api/src/usecase/podcast/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/api/gcs_fixtures/bookwith-bucket/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/api/src/domain/chat/entities/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/api/src/domain/chat/exceptions/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/api/src/domain/podcast/entities/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/api/src/infrastructure/external/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/api/src/domain/annotation/entities/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/api/src/domain/book/value_objects/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/api/src/domain/chat/repositories/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/api/src/domain/chat/value_objects/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/api/src/domain/podcast/repositories/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/api/src/domain/podcast/value_objects/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/api/src/presentation/api/handlers/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/api/src/presentation/api/schemas/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/reader/.gitignore: -------------------------------------------------------------------------------- 1 | # Sentry 2 | .sentryclirc 3 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | packages/epubjs/** linguist-vendored 2 | -------------------------------------------------------------------------------- /apps/api/src/domain/annotation/repositories/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/api/src/infrastructure/external/cloud_tts/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/api/src/presentation/api/converters/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /apps/api/src/presentation/api/error_messages/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/epubjs/.npmignore: -------------------------------------------------------------------------------- 1 | books 2 | test 3 | .babelrc 4 | -------------------------------------------------------------------------------- /packages/internal/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib' 2 | -------------------------------------------------------------------------------- /apps/api/src/infrastructure/external/gemini/prompts/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/epubjs/test/fixtures/alice/mimetype: -------------------------------------------------------------------------------- 1 | application/epub+zip -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | **/node_modules 2 | **/dist 3 | **/.next 4 | **/.turbo 5 | -------------------------------------------------------------------------------- /apps/reader/src/components/pages/index.ts: -------------------------------------------------------------------------------- 1 | export * from './settings' 2 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - "apps/*" 3 | - "packages/*" 4 | -------------------------------------------------------------------------------- /packages/epubjs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.ts.json" 3 | } 4 | -------------------------------------------------------------------------------- /apps/api/src/domain/annotation/value_objects/__init__.py: -------------------------------------------------------------------------------- 1 | """アノテーション値オブジェクトモジュール""" 2 | -------------------------------------------------------------------------------- /apps/reader/src/models/index.ts: -------------------------------------------------------------------------------- 1 | export * from './reader' 2 | export * from './tree' 3 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | pnpm lint-staged 5 | -------------------------------------------------------------------------------- /apps/reader/src/pages/_.tsx: -------------------------------------------------------------------------------- 1 | import Index from './index' 2 | 3 | export default Index 4 | -------------------------------------------------------------------------------- /packages/epubjs/types/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "dtslint/dt.json", 3 | "rules": {} 4 | } 5 | -------------------------------------------------------------------------------- /apps/api/src/domain/book/entities/__init__.py: -------------------------------------------------------------------------------- 1 | from src.domain.book.entities.book import Book as Book 2 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | # https://github.com/remix-run/remix/issues/154#issuecomment-978359765 2 | # shamefully-hoist=true 3 | -------------------------------------------------------------------------------- /packages/epubjs/.watchmanconfig: -------------------------------------------------------------------------------- 1 | { 2 | "ignore_dirs": [ 3 | ".git", 4 | "node_modules" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /apps/reader/sentry.properties: -------------------------------------------------------------------------------- 1 | defaults.url=https://sentry.io/ 2 | defaults.org=pacexy 3 | defaults.project=flow 4 | -------------------------------------------------------------------------------- /apps/api/src/infrastructure/external/epub/__init__.py: -------------------------------------------------------------------------------- 1 | from .epub_reader import Chapter 2 | 3 | __all__ = ["Chapter"] 4 | -------------------------------------------------------------------------------- /apps/reader/public/icons/192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shutootaki/bookwith/HEAD/apps/reader/public/icons/192.png -------------------------------------------------------------------------------- /apps/reader/public/icons/512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shutootaki/bookwith/HEAD/apps/reader/public/icons/512.png -------------------------------------------------------------------------------- /supabase/.gitignore: -------------------------------------------------------------------------------- 1 | # Supabase 2 | .branches 3 | .temp 4 | 5 | # dotenvx 6 | .env.keys 7 | .env.local 8 | .env.*.local 9 | -------------------------------------------------------------------------------- /apps/api/src/infrastructure/di/__init__.py: -------------------------------------------------------------------------------- 1 | from src.infrastructure.di.injection import get_book_repository as get_book_repository 2 | -------------------------------------------------------------------------------- /apps/reader/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /apps/api/src/infrastructure/external/gemini/__init__.py: -------------------------------------------------------------------------------- 1 | from .gemini_client import GeminiClient 2 | 3 | __all__ = ["GeminiClient"] 4 | -------------------------------------------------------------------------------- /apps/api/src/presentation/api/__init__.py: -------------------------------------------------------------------------------- 1 | from src.presentation.api.routes import setup_routes 2 | 3 | __all__ = ["setup_routes"] 4 | -------------------------------------------------------------------------------- /apps/reader/public/icons/maskable-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shutootaki/bookwith/HEAD/apps/reader/public/icons/maskable-192.png -------------------------------------------------------------------------------- /apps/reader/public/icons/maskable-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shutootaki/bookwith/HEAD/apps/reader/public/icons/maskable-512.png -------------------------------------------------------------------------------- /packages/epubjs/documentation.yml: -------------------------------------------------------------------------------- 1 | toc: 2 | - ePub 3 | - name: ePubJS 4 | description: | 5 | main entry 6 | - EpubCFI 7 | -------------------------------------------------------------------------------- /packages/epubjs/examples/ajax-loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shutootaki/bookwith/HEAD/packages/epubjs/examples/ajax-loader.gif -------------------------------------------------------------------------------- /packages/epubjs/test/fixtures/alice.epub: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shutootaki/bookwith/HEAD/packages/epubjs/test/fixtures/alice.epub -------------------------------------------------------------------------------- /apps/api/src/domain/message/entities/__init__.py: -------------------------------------------------------------------------------- 1 | from src.domain.message.entities.message import Message 2 | 3 | __all__ = ["Message"] 4 | -------------------------------------------------------------------------------- /apps/api/src/infrastructure/external/audio/__init__.py: -------------------------------------------------------------------------------- 1 | from .audio_processor import AudioProcessor 2 | 3 | __all__ = ["AudioProcessor"] 4 | -------------------------------------------------------------------------------- /packages/epubjs/types/utils/scrolltype.d.ts: -------------------------------------------------------------------------------- 1 | export default function scrollType(): string 2 | 3 | export function createDefiner(): Node 4 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | pnpm-lock.yaml 2 | **/pnpm-lock.yaml 3 | # ビルド生成物を無視 4 | .next 5 | **/.next 6 | build 7 | **/build 8 | dist 9 | **/dist 10 | -------------------------------------------------------------------------------- /apps/api/src/infrastructure/postgres/chat/__init__.py: -------------------------------------------------------------------------------- 1 | from src.infrastructure.postgres.chat.chat_dto import ChatDTO 2 | 3 | __all__ = ["ChatDTO"] 4 | -------------------------------------------------------------------------------- /apps/api/src/infrastructure/postgres/user/__init__.py: -------------------------------------------------------------------------------- 1 | from src.infrastructure.postgres.user.user_dto import UserDTO 2 | 3 | __all__ = ["UserDTO"] 4 | -------------------------------------------------------------------------------- /apps/reader/.prettierignore: -------------------------------------------------------------------------------- 1 | .next 2 | **/.next 3 | build 4 | **/build 5 | dist 6 | **/dist 7 | pnpm-lock.yaml 8 | **/pnpm-lock.yaml 9 | 10 | -------------------------------------------------------------------------------- /apps/reader/src/components/Reader.tsx: -------------------------------------------------------------------------------- 1 | // Re-export the main ReaderGridView component 2 | export { ReaderGridView } from './reader/ReaderGridView' 3 | -------------------------------------------------------------------------------- /supabase/migrations/20250713003643_add_language_to_podcast.sql: -------------------------------------------------------------------------------- 1 | alter table "public"."podcasts" add column "language" character varying(10) not null; 2 | -------------------------------------------------------------------------------- /apps/api/src/domain/book/repositories/__init__.py: -------------------------------------------------------------------------------- 1 | from src.domain.book.repositories.book_repository import BookRepository 2 | 3 | __all__ = ["BookRepository"] 4 | -------------------------------------------------------------------------------- /apps/api/src/infrastructure/postgres/podcast/__init__.py: -------------------------------------------------------------------------------- 1 | from .podcast_repository import PodcastRepositoryImpl 2 | 3 | __all__ = ["PodcastRepositoryImpl"] 4 | -------------------------------------------------------------------------------- /packages/epubjs/test/fixtures/alice_without_cover.epub: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shutootaki/bookwith/HEAD/packages/epubjs/test/fixtures/alice_without_cover.epub -------------------------------------------------------------------------------- /packages/internal/src/lib.ts: -------------------------------------------------------------------------------- 1 | export const str = 'Hello world' 2 | 3 | export function range(n: number) { 4 | return [...new Array(n)].map((_, i) => i) 5 | } 6 | -------------------------------------------------------------------------------- /apps/reader/src/components/Library.tsx: -------------------------------------------------------------------------------- 1 | // Re-export the main LibraryContainer component 2 | export { LibraryContainer as Library } from './library/LibraryContainer' 3 | -------------------------------------------------------------------------------- /apps/reader/src/components/base/GridView.tsx: -------------------------------------------------------------------------------- 1 | import React, { JSX } from 'react' 2 | 3 | export const GridView = (): JSX.Element => { 4 | return
5 | } 6 | -------------------------------------------------------------------------------- /apps/reader/src/types/window.d.ts: -------------------------------------------------------------------------------- 1 | declare global { 2 | interface Window { 3 | podcastSeekFunction?: (time: number) => void 4 | } 5 | } 6 | 7 | export {} 8 | -------------------------------------------------------------------------------- /packages/epubjs/test/fixtures/alice/OPS/images/title.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shutootaki/bookwith/HEAD/packages/epubjs/test/fixtures/alice/OPS/images/title.jpg -------------------------------------------------------------------------------- /packages/internal/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.react.json", 3 | "include": ["."], 4 | "exclude": ["dist", "build", "node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /apps/api/src/__init__.py: -------------------------------------------------------------------------------- 1 | from src.config.db import Base, SessionLocal, engine, get_db, init_db 2 | 3 | __all__ = ["Base", "get_db", "init_db", "engine", "SessionLocal"] 4 | -------------------------------------------------------------------------------- /packages/epubjs/test/fixtures/alice/OPS/images/cover_th.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shutootaki/bookwith/HEAD/packages/epubjs/test/fixtures/alice/OPS/images/cover_th.jpg -------------------------------------------------------------------------------- /packages/epubjs/test/fixtures/alice/OPS/images/i001_th.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shutootaki/bookwith/HEAD/packages/epubjs/test/fixtures/alice/OPS/images/i001_th.jpg -------------------------------------------------------------------------------- /packages/epubjs/test/fixtures/alice/OPS/images/i002_th.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shutootaki/bookwith/HEAD/packages/epubjs/test/fixtures/alice/OPS/images/i002_th.jpg -------------------------------------------------------------------------------- /packages/epubjs/test/fixtures/alice/OPS/images/i003_th.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shutootaki/bookwith/HEAD/packages/epubjs/test/fixtures/alice/OPS/images/i003_th.jpg -------------------------------------------------------------------------------- /packages/epubjs/test/fixtures/alice/OPS/images/i004_th.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shutootaki/bookwith/HEAD/packages/epubjs/test/fixtures/alice/OPS/images/i004_th.jpg -------------------------------------------------------------------------------- /packages/epubjs/test/fixtures/alice/OPS/images/i005_th.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shutootaki/bookwith/HEAD/packages/epubjs/test/fixtures/alice/OPS/images/i005_th.jpg -------------------------------------------------------------------------------- /packages/epubjs/test/fixtures/alice/OPS/images/i006_th.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shutootaki/bookwith/HEAD/packages/epubjs/test/fixtures/alice/OPS/images/i006_th.jpg -------------------------------------------------------------------------------- /packages/epubjs/test/fixtures/alice/OPS/images/i007_th.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shutootaki/bookwith/HEAD/packages/epubjs/test/fixtures/alice/OPS/images/i007_th.jpg -------------------------------------------------------------------------------- /packages/epubjs/test/fixtures/alice/OPS/images/i008_th.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shutootaki/bookwith/HEAD/packages/epubjs/test/fixtures/alice/OPS/images/i008_th.jpg -------------------------------------------------------------------------------- /packages/epubjs/test/fixtures/alice/OPS/images/i009_th.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shutootaki/bookwith/HEAD/packages/epubjs/test/fixtures/alice/OPS/images/i009_th.jpg -------------------------------------------------------------------------------- /packages/epubjs/test/fixtures/alice/OPS/images/i010_th.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shutootaki/bookwith/HEAD/packages/epubjs/test/fixtures/alice/OPS/images/i010_th.jpg -------------------------------------------------------------------------------- /packages/epubjs/test/fixtures/alice/OPS/images/i011_th.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shutootaki/bookwith/HEAD/packages/epubjs/test/fixtures/alice/OPS/images/i011_th.jpg -------------------------------------------------------------------------------- /packages/epubjs/test/fixtures/alice/OPS/images/i012_th.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shutootaki/bookwith/HEAD/packages/epubjs/test/fixtures/alice/OPS/images/i012_th.jpg -------------------------------------------------------------------------------- /packages/epubjs/test/fixtures/alice/OPS/images/i013_th.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shutootaki/bookwith/HEAD/packages/epubjs/test/fixtures/alice/OPS/images/i013_th.jpg -------------------------------------------------------------------------------- /packages/epubjs/test/fixtures/alice/OPS/images/i014_th.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shutootaki/bookwith/HEAD/packages/epubjs/test/fixtures/alice/OPS/images/i014_th.jpg -------------------------------------------------------------------------------- /packages/epubjs/test/fixtures/alice/OPS/images/i015_th.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shutootaki/bookwith/HEAD/packages/epubjs/test/fixtures/alice/OPS/images/i015_th.jpg -------------------------------------------------------------------------------- /packages/epubjs/test/fixtures/alice/OPS/images/i016_th.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shutootaki/bookwith/HEAD/packages/epubjs/test/fixtures/alice/OPS/images/i016_th.jpg -------------------------------------------------------------------------------- /packages/epubjs/test/fixtures/alice/OPS/images/i017_th.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shutootaki/bookwith/HEAD/packages/epubjs/test/fixtures/alice/OPS/images/i017_th.jpg -------------------------------------------------------------------------------- /packages/epubjs/test/fixtures/alice/OPS/images/i018_th.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shutootaki/bookwith/HEAD/packages/epubjs/test/fixtures/alice/OPS/images/i018_th.jpg -------------------------------------------------------------------------------- /packages/epubjs/test/fixtures/alice/OPS/images/i019_th.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shutootaki/bookwith/HEAD/packages/epubjs/test/fixtures/alice/OPS/images/i019_th.jpg -------------------------------------------------------------------------------- /packages/epubjs/test/fixtures/alice/OPS/images/i020_th.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shutootaki/bookwith/HEAD/packages/epubjs/test/fixtures/alice/OPS/images/i020_th.jpg -------------------------------------------------------------------------------- /packages/epubjs/test/fixtures/alice/OPS/images/i022_th.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shutootaki/bookwith/HEAD/packages/epubjs/test/fixtures/alice/OPS/images/i022_th.jpg -------------------------------------------------------------------------------- /packages/epubjs/test/fixtures/alice/OPS/images/ii021_th.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shutootaki/bookwith/HEAD/packages/epubjs/test/fixtures/alice/OPS/images/ii021_th.jpg -------------------------------------------------------------------------------- /apps/api/src/domain/message/repositories/__init__.py: -------------------------------------------------------------------------------- 1 | from src.domain.message.repositories.message_repository import MessageRepository 2 | 3 | __all__ = ["MessageRepository"] 4 | -------------------------------------------------------------------------------- /apps/reader/src/components/TextSelectionMenu.tsx: -------------------------------------------------------------------------------- 1 | // Re-export the main TextSelectionMenu component 2 | export { TextSelectionMenu } from './textSelection/TextSelectionMenu' 3 | -------------------------------------------------------------------------------- /apps/reader/src/hooks/theme/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useBackground' 2 | export * from './useColorScheme' 3 | export * from './useSourceColor' 4 | export * from './useTheme' 5 | -------------------------------------------------------------------------------- /packages/epubjs/test/fixtures/alice/OPS/images/plate01_th.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shutootaki/bookwith/HEAD/packages/epubjs/test/fixtures/alice/OPS/images/plate01_th.jpg -------------------------------------------------------------------------------- /packages/epubjs/test/fixtures/alice/OPS/images/plate02_th.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shutootaki/bookwith/HEAD/packages/epubjs/test/fixtures/alice/OPS/images/plate02_th.jpg -------------------------------------------------------------------------------- /packages/epubjs/test/fixtures/alice/OPS/images/plate03_th.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shutootaki/bookwith/HEAD/packages/epubjs/test/fixtures/alice/OPS/images/plate03_th.jpg -------------------------------------------------------------------------------- /packages/epubjs/test/fixtures/alice/OPS/images/plate04_th.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shutootaki/bookwith/HEAD/packages/epubjs/test/fixtures/alice/OPS/images/plate04_th.jpg -------------------------------------------------------------------------------- /apps/api/src/infrastructure/postgres/annotation/__init__.py: -------------------------------------------------------------------------------- 1 | from src.infrastructure.postgres.annotation.annotation_dto import AnnotationDTO 2 | 3 | __all__ = ["AnnotationDTO"] 4 | -------------------------------------------------------------------------------- /apps/api/src/usecase/chat/__init__.py: -------------------------------------------------------------------------------- 1 | from src.config.db import Base, SessionLocal, engine, get_db, init_db 2 | 3 | __all__ = ["Base", "get_db", "init_db", "engine", "SessionLocal"] 4 | -------------------------------------------------------------------------------- /prettier.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [require('prettier-plugin-tailwindcss')], 3 | singleQuote: true, 4 | semi: false, 5 | trailingComma: 'all', 6 | } 7 | -------------------------------------------------------------------------------- /packages/epubjs/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | */**/.DS_Store 3 | node_modules/ 4 | components 5 | node_modules 6 | bower_components 7 | books 8 | lib 9 | dist 10 | documentation/html 11 | types/*.js -------------------------------------------------------------------------------- /packages/epubjs/types/container.d.ts: -------------------------------------------------------------------------------- 1 | export default class Container { 2 | constructor(containerDocument: Document) 3 | 4 | parse(containerDocument: Document): void 5 | 6 | destroy(): void 7 | } 8 | -------------------------------------------------------------------------------- /.mcp.json: -------------------------------------------------------------------------------- 1 | { 2 | "mcpServers": { 3 | "playwright": { 4 | "type": "stdio", 5 | "command": "npx", 6 | "args": ["-y", "@playwright/mcp@latest"], 7 | "env": {} 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /apps/reader/src/components/base/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ActionBar' 2 | export * from './ContextView' 3 | export * from './DropZone' 4 | export * from './GridView' 5 | export * from './PaneView' 6 | export * from './SplitView' 7 | -------------------------------------------------------------------------------- /packages/tailwind/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@flow/tailwind", 3 | "version": "0.0.0", 4 | "license": "MIT", 5 | "main": "./src/index.js", 6 | "devDependencies": { 7 | "tailwindcss": "^3.2.0" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /apps/reader/src/hooks/loading/index.ts: -------------------------------------------------------------------------------- 1 | export { useLoadingState } from './useLoadingState' 2 | export { useProgressManager } from './useProgressManager' 3 | export { useTaskManager, type UseTaskManagerOptions } from './useTaskManager' 4 | -------------------------------------------------------------------------------- /apps/reader/src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import clsx from 'clsx' 2 | import { ClassValue } from 'clsx' 3 | import { twMerge } from 'tailwind-merge' 4 | 5 | export function cn(...inputs: ClassValue[]) { 6 | return twMerge(clsx(inputs)) 7 | } 8 | -------------------------------------------------------------------------------- /packages/epubjs/types/utils/request.d.ts: -------------------------------------------------------------------------------- 1 | export default function request( 2 | url: string, 3 | type?: string, 4 | withCredentials?: boolean, 5 | headers?: object, 6 | ): Promise 7 | -------------------------------------------------------------------------------- /packages/epubjs/examples/themes.css: -------------------------------------------------------------------------------- 1 | .dark { 2 | background: #000; 3 | color: #fff; 4 | } 5 | 6 | .light { 7 | background: #fff; 8 | color: #000; 9 | } 10 | 11 | .tan { 12 | background: tan; 13 | color: #ccc; 14 | } 15 | -------------------------------------------------------------------------------- /packages/epubjs/types/utils/constants.d.ts: -------------------------------------------------------------------------------- 1 | export const EPUBJS_VERSION: string 2 | 3 | export const DOM_EVENTS: Array 4 | 5 | export const EVENTS: { 6 | [key: string]: { 7 | [key: string]: string 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /apps/reader/locales/index.ts: -------------------------------------------------------------------------------- 1 | import cmn_CN from './cmn-CN' 2 | import en_US from './en-US' 3 | import ja_JP from './ja-JP' 4 | 5 | export default { 6 | 'en-US': en_US, 7 | 'ja-JP': ja_JP, 8 | 'cmn-CN': cmn_CN, 9 | } as const 10 | -------------------------------------------------------------------------------- /apps/api/src/infrastructure/postgres/book/__init__.py: -------------------------------------------------------------------------------- 1 | from src.infrastructure.postgres.book.book_dto import BookDTO 2 | from src.infrastructure.postgres.book.book_repository import BookRepositoryImpl 3 | 4 | __all__ = ["BookDTO", "BookRepositoryImpl"] 5 | -------------------------------------------------------------------------------- /apps/reader/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/pages/api-reference/config/typescript for more information. 6 | -------------------------------------------------------------------------------- /apps/reader/src/hooks/useForceRender.ts: -------------------------------------------------------------------------------- 1 | import { useState, useCallback } from 'react' 2 | 3 | export function useForceRender() { 4 | const [, render] = useState({}) 5 | 6 | return useCallback(() => { 7 | render({}) 8 | }, []) 9 | } 10 | -------------------------------------------------------------------------------- /apps/reader/src/types/podcast.ts: -------------------------------------------------------------------------------- 1 | import { components } from '../lib/openapi-schema/schema' 2 | 3 | export type PodcastResponse = components['schemas']['PodcastResponse'] 4 | 5 | export type PodcastStatus = 'PENDING' | 'PROCESSING' | 'COMPLETED' | 'FAILED' 6 | -------------------------------------------------------------------------------- /apps/reader/src/utils/mime.ts: -------------------------------------------------------------------------------- 1 | export const mapExtToMimes = { 2 | '.epub': ['application/epub+zip', 'application/epub'], 3 | '.zip': [ 4 | 'application/zip', 5 | 'application/zip-compressed', 6 | 'application/x-zip-compressed', 7 | ], 8 | } 9 | -------------------------------------------------------------------------------- /apps/reader/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.next.json", 3 | "compilerOptions": { 4 | "experimentalDecorators": true 5 | }, 6 | "include": ["next-env.d.ts", "web.d.ts", "**/*.ts", "**/*.tsx"], 7 | "exclude": ["node_modules"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/epubjs/.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 'node' 4 | notifications: 5 | email: false 6 | sudo: false 7 | addons: 8 | chrome: stable 9 | cache: 10 | directories: 11 | - node_modules 12 | script: 13 | - npm test 14 | -------------------------------------------------------------------------------- /apps/reader/src/store/loading/atoms.ts: -------------------------------------------------------------------------------- 1 | import { atom } from 'jotai' 2 | 3 | import { LoadingTaskMap } from '../../types/loading' 4 | 5 | /** 6 | * Core atom that holds all loading tasks 7 | */ 8 | export const loadingTasksAtom = atom(new Map()) 9 | -------------------------------------------------------------------------------- /apps/api/src/domain/message/value_objects/__init__.py: -------------------------------------------------------------------------------- 1 | from src.domain.message.value_objects.message_content import MessageContent 2 | from src.domain.message.value_objects.message_id import MessageId 3 | 4 | __all__ = [ 5 | "MessageId", 6 | "MessageContent", 7 | ] 8 | -------------------------------------------------------------------------------- /apps/api/src/infrastructure/memory/__init__.py: -------------------------------------------------------------------------------- 1 | from src.infrastructure.memory.memory_service import MemoryService 2 | from src.infrastructure.memory.memory_vector_store import MemoryVectorStore 3 | 4 | __all__ = [ 5 | "MemoryService", 6 | "MemoryVectorStore", 7 | ] 8 | -------------------------------------------------------------------------------- /apps/api/src/infrastructure/postgres/message/__init__.py: -------------------------------------------------------------------------------- 1 | from src.infrastructure.postgres.message.message_dto import MessageDTO 2 | from src.infrastructure.postgres.message.message_repository import MessageRepositoryImpl 3 | 4 | __all__ = ["MessageDTO", "MessageRepositoryImpl"] 5 | -------------------------------------------------------------------------------- /apps/reader/src/components/library/index.ts: -------------------------------------------------------------------------------- 1 | export { LibraryContainer } from './LibraryContainer' 2 | export { RemoteImportManager as ImportManager } from './RemoteImportManager' 3 | export { SelectionManager } from './SelectionManager' 4 | export { BookGrid } from './BookGrid' 5 | -------------------------------------------------------------------------------- /apps/reader/src/components/textSelection/index.ts: -------------------------------------------------------------------------------- 1 | export { TextSelectionMenu } from './TextSelectionMenu' 2 | export { TextSelectionRenderer } from './TextSelectionRenderer' 3 | export { ActionMenu } from './ActionMenu' 4 | export { AnnotationToolbar } from './AnnotationToolbar' 5 | -------------------------------------------------------------------------------- /apps/reader/src/components/chat/types.ts: -------------------------------------------------------------------------------- 1 | import { components } from '../../lib/openapi-schema/schema' 2 | 3 | export interface Message { 4 | text: components['schemas']['MessageResponse']['content'] 5 | senderType: components['schemas']['MessageResponse']['senderType'] 6 | } 7 | -------------------------------------------------------------------------------- /apps/reader/src/hooks/useEnv.ts: -------------------------------------------------------------------------------- 1 | import { useMobile } from './useMobile' 2 | 3 | export enum Env { 4 | Desktop = 1, 5 | Mobile = 1 << 1, 6 | } 7 | 8 | export function useEnv() { 9 | const mobile = useMobile() 10 | return mobile ? Env.Mobile : Env.Desktop 11 | } 12 | -------------------------------------------------------------------------------- /packages/epubjs/types/epub.d.ts: -------------------------------------------------------------------------------- 1 | import Book, { BookOptions } from './book' 2 | 3 | export default Epub 4 | 5 | declare function Epub( 6 | urlOrData: string | ArrayBuffer, 7 | options?: BookOptions, 8 | ): Book 9 | declare function Epub(options?: BookOptions): Book 10 | -------------------------------------------------------------------------------- /tsconfig.ts.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "TypeScript Library", 4 | "extends": "./tsconfig.json", 5 | "compilerOptions": { 6 | "lib": ["esnext"], 7 | "module": "ESNext", 8 | "target": "ES6" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 2.1.2 and should not be changed by hand. 2 | package = [] 3 | 4 | [metadata] 5 | lock-version = "2.1" 6 | python-versions = ">=3.11,<4.0" 7 | content-hash = "6e476a2a110ac6b4d38bba4321a4054bf79bf7c0f098f4cda47c09e95c0b73b2" 8 | -------------------------------------------------------------------------------- /apps/api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@flow/api", 3 | "private": true, 4 | "type": "module", 5 | "scripts": { 6 | "docker:up": "docker compose up --build --pull always", 7 | "dev": "make run", 8 | "dev:services": "make docker.up", 9 | "lint": "make lint" 10 | } 11 | } -------------------------------------------------------------------------------- /packages/epubjs/types/epubjs-tests.ts: -------------------------------------------------------------------------------- 1 | import ePub, { Book } from '../' 2 | 3 | function testEpub() { 4 | const epub = ePub('https://s3.amazonaws.com/moby-dick/moby-dick.epub') 5 | 6 | const book = new Book('https://s3.amazonaws.com/moby-dick/moby-dick.epub', {}) 7 | } 8 | 9 | testEpub() 10 | -------------------------------------------------------------------------------- /packages/internal/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@flow/internal", 3 | "version": "0.0.0", 4 | "main": "./src/index.ts", 5 | "types": "./src/index.ts", 6 | "license": "MIT", 7 | "devDependencies": { 8 | "@types/react": "17.0.43", 9 | "@types/react-dom": "18.0.0" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /apps/reader/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | 3 | services: 4 | reader: 5 | container_name: reader 6 | build: 7 | context: . 8 | dockerfile: ./Dockerfile 9 | restart: always 10 | ports: 11 | - 3000:3000 12 | env_file: 13 | - ./apps/reader/.env.local 14 | -------------------------------------------------------------------------------- /packages/epubjs/test/fixtures/alice/META-INF/container.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /packages/epubjs/types/utils/url.d.ts: -------------------------------------------------------------------------------- 1 | import Path from './path' 2 | 3 | export default class Url { 4 | constructor(urlString: string, baseString: string) 5 | 6 | path(): Path 7 | 8 | resolve(what: string): string 9 | 10 | relative(what: string): string 11 | 12 | toString(): string 13 | } 14 | -------------------------------------------------------------------------------- /apps/api/src/domain/annotation/exceptions.py: -------------------------------------------------------------------------------- 1 | class AnnotationNotFoundError(Exception): 2 | """指定されたアノテーションが見つからない場合に送出される例外""" 3 | 4 | def __init__(self, annotation_id: str) -> None: 5 | self.annotation_id = annotation_id 6 | super().__init__(f"Annotation with id '{annotation_id}' not found.") 7 | -------------------------------------------------------------------------------- /packages/epubjs/src/index.js: -------------------------------------------------------------------------------- 1 | import Book from './book' 2 | import Contents from './contents' 3 | import ePub from './epub' 4 | import EpubCFI from './epubcfi' 5 | import Layout from './layout' 6 | import Rendition from './rendition' 7 | 8 | export default ePub 9 | export { Book, EpubCFI, Rendition, Contents, Layout } 10 | -------------------------------------------------------------------------------- /apps/api/src/domain/message/value_objects/message_content.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | 4 | @dataclass(frozen=True) 5 | class MessageContent: 6 | value: str 7 | 8 | def __post_init__(self) -> None: 9 | if not self.value: 10 | raise ValueError("Message content cannot be empty") 11 | -------------------------------------------------------------------------------- /apps/api/src/domain/podcast/value_objects/language.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class PodcastLanguage(str, Enum): 5 | EN_US = "en-US" 6 | JA_JP = "ja-JP" 7 | CMN_CN = "cmn-CN" 8 | 9 | @classmethod 10 | def has_value(cls, value: str) -> bool: 11 | return value in cls._value2member_map_ 12 | -------------------------------------------------------------------------------- /tsconfig.react.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "React Library", 4 | "extends": "./tsconfig.json", 5 | "compilerOptions": { 6 | "lib": ["dom", "dom.iterable", "esnext"], 7 | "module": "ESNext", 8 | "target": "ES6", 9 | "jsx": "react-jsx" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "tasks": { 3 | "build": { 4 | "dependsOn": ["^build"], 5 | "outputs": ["dist/**", ".next/**"] 6 | }, 7 | "lint": { 8 | "outputs": [] 9 | }, 10 | "dev": {}, 11 | "dev:services": { 12 | "cache": false, 13 | "persistent": true 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /apps/api/src/domain/annotation/repositories/annotation_repository.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | from src.domain.book.entities.book import Book 4 | 5 | 6 | class AnnotationRepository(ABC): 7 | @abstractmethod 8 | def sync_annotations(self, book: Book) -> None: 9 | """書籍に関連する全てのアノテーションを同期する""" 10 | -------------------------------------------------------------------------------- /apps/api/src/presentation/api/schemas/base_schema.py: -------------------------------------------------------------------------------- 1 | import humps 2 | from pydantic import BaseModel, ConfigDict 3 | 4 | 5 | def to_camel(string: str) -> str: 6 | return humps.camelize(string) 7 | 8 | 9 | class BaseSchemaModel(BaseModel): 10 | model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True) 11 | -------------------------------------------------------------------------------- /apps/reader/.env.example: -------------------------------------------------------------------------------- 1 | # For develop features related to synchronization, 2 | # you should register an app on Dropbox: 3 | # https://www.dropbox.com/developers/apps/create 4 | NEXT_PUBLIC_DROPBOX_CLIENT_ID= 5 | DROPBOX_CLIENT_SECRET= 6 | 7 | NEXT_PUBLIC_WEBSITE_URL=http://localhost:7117 8 | NEXT_PUBLIC_API_BASE_URL=http://localhost:8000 9 | -------------------------------------------------------------------------------- /apps/api/src/domain/chat/value_objects/chat_title.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | 4 | @dataclass(frozen=True) 5 | class ChatTitle: 6 | value: str 7 | 8 | def __post_init__(self) -> None: 9 | if self.value and len(self.value) > 255: 10 | raise ValueError("Chat title must be 255 characters or less") 11 | -------------------------------------------------------------------------------- /apps/reader/src/utils/platform.ts: -------------------------------------------------------------------------------- 1 | import { IS_SERVER } from './utils' 2 | 3 | // https://www.geeksforgeeks.org/how-to-detect-touch-screen-device-using-javascript 4 | export const isTouchScreen = IS_SERVER ? false : 'ontouchstart' in window 5 | export const scale = (value: number, valueInTouchScreen: number) => 6 | isTouchScreen ? valueInTouchScreen : value 7 | -------------------------------------------------------------------------------- /apps/reader/src/hooks/useList.ts: -------------------------------------------------------------------------------- 1 | import useVirtual from 'react-cool-virtual' 2 | 3 | import { scale } from '../utils/platform' 4 | 5 | export const LIST_ITEM_SIZE = scale(24, 32) 6 | export function useList(array: Readonly = []) { 7 | return useVirtual({ 8 | itemCount: array.length, 9 | itemSize: LIST_ITEM_SIZE, 10 | }) 11 | } 12 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "bookwith-monorepo" 3 | version = "0.1.0" 4 | description = "Monorepo for bookwith project" 5 | authors = [] 6 | 7 | [tool.poetry.dependencies] 8 | python = ">=3.11,<4.0" 9 | 10 | [tool.poetry.group.dev.dependencies] 11 | 12 | [build-system] 13 | requires = ["poetry-core>=1.7.0"] 14 | build-backend = "poetry.core.masonry.api" 15 | -------------------------------------------------------------------------------- /apps/api/src/domain/message/exceptions/__init__.py: -------------------------------------------------------------------------------- 1 | from src.domain.message.exceptions.message_exceptions import ( 2 | MessageAlreadyDeletedException, 3 | MessageDeliveryFailedException, 4 | MessageNotFoundException, 5 | ) 6 | 7 | __all__ = [ 8 | "MessageNotFoundException", 9 | "MessageAlreadyDeletedException", 10 | "MessageDeliveryFailedException", 11 | ] 12 | -------------------------------------------------------------------------------- /apps/api/src/domain/book/value_objects/book_title.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | 4 | @dataclass(frozen=True) 5 | class BookTitle: 6 | value: str 7 | 8 | def __post_init__(self) -> None: 9 | if not self.value: 10 | raise ValueError("タイトルは必須です") 11 | if len(self.value) > 100: 12 | raise ValueError("タイトルは100文字以下である必要があります") 13 | -------------------------------------------------------------------------------- /apps/reader/src/hooks/theme/useTheme.ts: -------------------------------------------------------------------------------- 1 | import { Theme } from '@material/material-color-utilities' 2 | import { atom, useAtomValue, useSetAtom } from 'jotai' 3 | 4 | const themeState = atom(undefined) 5 | 6 | export function useTheme() { 7 | return useAtomValue(themeState) 8 | } 9 | 10 | export function useSetTheme() { 11 | return useSetAtom(themeState) 12 | } 13 | -------------------------------------------------------------------------------- /packages/epubjs/eslint.config.js: -------------------------------------------------------------------------------- 1 | import baseConfig from '../../eslint.config.js'; 2 | import tseslint from 'typescript-eslint'; 3 | 4 | export default tseslint.config( 5 | ...baseConfig, 6 | { 7 | files: ['**/*.ts', '**/*.tsx'], 8 | rules: { 9 | '@typescript-eslint/ban-types': 'off', 10 | '@typescript-eslint/no-unused-vars': 'off', 11 | }, 12 | } 13 | ); 14 | -------------------------------------------------------------------------------- /apps/reader/src/hooks/useAfterMount.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react' 2 | 3 | function useMounted() { 4 | const [mounted, setMounted] = useState(false) 5 | useEffect(() => { 6 | setMounted(true) 7 | return () => setMounted(false) 8 | }, []) 9 | return mounted 10 | } 11 | 12 | export function useAfterMount(v: T) { 13 | return useMounted() ? v : null 14 | } 15 | -------------------------------------------------------------------------------- /apps/api/src/presentation/api/error_messages/chat_error_message.py: -------------------------------------------------------------------------------- 1 | class ChatErrorMessage: 2 | CHAT_NOT_FOUND = "チャットが見つかりません" 3 | CHAT_ALREADY_EXISTS = "チャットは既に存在します" 4 | CHAT_VALIDATION_ERROR = "チャットのバリデーションエラーが発生しました" 5 | CHAT_TITLE_TOO_LONG = "チャットタイトルは255文字以内である必要があります" 6 | CHAT_ID_INVALID = "無効なチャットIDです" 7 | USER_ID_INVALID = "無効なユーザーIDです" 8 | BOOK_ID_INVALID = "無効な本IDです" 9 | -------------------------------------------------------------------------------- /apps/reader/src/store/loading.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Loading store - backward compatibility layer 3 | * 4 | * This file re-exports from the new modular structure to maintain 5 | * backward compatibility with existing imports. 6 | * 7 | * @deprecated Consider importing from '@/store/loading' instead 8 | */ 9 | 10 | // Re-export everything from the new modular structure 11 | export * from './loading/index' 12 | -------------------------------------------------------------------------------- /packages/epubjs/types/utils/path.d.ts: -------------------------------------------------------------------------------- 1 | export default class Path { 2 | constructor(pathString: string) 3 | 4 | parse(what: string): object 5 | 6 | isAbsolute(what: string): boolean 7 | 8 | isDirectory(what: string): boolean 9 | 10 | resolve(what: string): string 11 | 12 | relative(what: string): string 13 | 14 | splitPath(filename: string): string 15 | 16 | toString(): string 17 | } 18 | -------------------------------------------------------------------------------- /packages/epubjs/types/utils/hook.d.ts: -------------------------------------------------------------------------------- 1 | interface HooksObject { 2 | [key: string]: Hook 3 | } 4 | 5 | export default class Hook { 6 | constructor(context: any) 7 | 8 | register(func: Function): void 9 | register(arr: Array): void 10 | 11 | deregister(func: Function): void 12 | 13 | trigger(...args: any[]): Promise 14 | 15 | list(): Array 16 | 17 | clear(): void 18 | } 19 | -------------------------------------------------------------------------------- /apps/reader/src/components/reader/PaneContainer.tsx: -------------------------------------------------------------------------------- 1 | import clsx from 'clsx' 2 | import React, { FC, PropsWithChildren } from 'react' 3 | 4 | interface PaneContainerProps { 5 | active: boolean 6 | } 7 | 8 | export const PaneContainer: FC> = ({ 9 | active, 10 | children, 11 | }) => { 12 | return
{children}
13 | } 14 | -------------------------------------------------------------------------------- /apps/reader/src/components/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Annotation' 2 | export * from './base' 3 | export * from './Button' 4 | export * from './ErrorBoundary' 5 | export * from './Form' 6 | export * from './Layout' 7 | export * from './Page' 8 | export * from './pages' 9 | export * from './Reader' 10 | export * from './Row' 11 | export * from './Tab' 12 | export * from './TextSelectionMenu' 13 | export * from './Theme' 14 | -------------------------------------------------------------------------------- /apps/reader/src/components/reader/index.ts: -------------------------------------------------------------------------------- 1 | export { ReaderGridView } from './ReaderGridView' 2 | export { ReaderGroup } from './ReaderGroup' 3 | export { BookPane } from './BookPane' 4 | export { PaneContainer } from './PaneContainer' 5 | export { ReaderPaneHeader } from './ReaderPaneHeader' 6 | export { ReaderPaneFooter } from './ReaderPaneFooter' 7 | export { Bar } from './Bar' 8 | export { handleKeyDown } from './eventHandlers' 9 | -------------------------------------------------------------------------------- /apps/reader/src/components/viewlets/PodcastView.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { PaneView, PaneViewProps } from '../base' 4 | import { PodcastPane } from '../podcast' 5 | 6 | export const PodcastView: React.FC = (props) => { 7 | return ( 8 | 9 |
10 | 11 |
12 |
13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /apps/reader/web.d.ts: -------------------------------------------------------------------------------- 1 | // https://github.com/christianliebel/paint/blob/850a57cd3cc6f6532791abb6d20d9228ceffb74f/types/static.d.ts#L66 2 | // Type declarations for File Handling API 3 | 4 | interface LaunchParams { 5 | files: FileSystemFileHandle[] 6 | } 7 | 8 | interface LaunchQueue { 9 | setConsumer(consumer: (launchParams: LaunchParams) => any): void 10 | } 11 | 12 | interface Window { 13 | launchQueue: LaunchQueue 14 | } 15 | -------------------------------------------------------------------------------- /packages/epubjs/.babelrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "targets": "last 2 Chrome versions, last 2 Safari versions, last 2 ChromeAndroid versions, last 2 iOS versions, last 2 Firefox versions, last 2 Edge versions", 7 | "corejs": 3, 8 | "useBuiltIns": "usage", 9 | "bugfixes": true, 10 | "modules": "auto" 11 | } 12 | ] 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /packages/epubjs/test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Mocha 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /apps/reader/src/hooks/useAutoResize.ts: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react' 2 | 3 | export const useAutoResize = ( 4 | text: string, 5 | textareaRef: { 6 | current: HTMLTextAreaElement | null 7 | }, 8 | ) => { 9 | useEffect(() => { 10 | const textarea = textareaRef.current 11 | if (!textarea) return 12 | textarea.style.height = 'auto' 13 | textarea.style.height = `${textarea.scrollHeight}px` 14 | }, [text, textareaRef]) 15 | } 16 | -------------------------------------------------------------------------------- /apps/reader/src/components/reader/Bar.tsx: -------------------------------------------------------------------------------- 1 | import clsx from 'clsx' 2 | import React, { ComponentProps, FC } from 'react' 3 | 4 | export const Bar: FC> = ({ className, ...props }) => { 5 | return ( 6 |
13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /packages/epubjs/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "browser": true, 3 | "devel": true, 4 | "worker": true, 5 | 6 | "trailing": true, 7 | "strict": false, 8 | 9 | "boss": true, 10 | "funcscope": true, 11 | "globalstrict": true, 12 | "loopfunc": true, 13 | "maxerr": 1000, 14 | "nonstandard": true, 15 | "sub": true, 16 | "validthis": true, 17 | 18 | "globals": { 19 | "_": false, 20 | "define" : false, 21 | "module" : false 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/epubjs/test/fixtures/alice/OPS/cover.xhtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Cover 6 | 7 | 8 | 9 | 10 | Cover Image 11 | 12 | 13 | -------------------------------------------------------------------------------- /apps/api/src/domain/chat/value_objects/book_id.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from uuid import UUID 3 | 4 | 5 | @dataclass(frozen=True) 6 | class BookId: 7 | value: str 8 | 9 | def __post_init__(self) -> None: 10 | if not self.value: 11 | raise ValueError("Book ID is required") 12 | try: 13 | UUID(self.value) 14 | except ValueError: 15 | raise ValueError("Book ID must be a valid UUID") 16 | -------------------------------------------------------------------------------- /apps/api/src/domain/chat/value_objects/chat_id.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from uuid import UUID 3 | 4 | 5 | @dataclass(frozen=True) 6 | class ChatId: 7 | value: str 8 | 9 | def __post_init__(self) -> None: 10 | if not self.value: 11 | raise ValueError("Chat ID is required") 12 | try: 13 | UUID(self.value) 14 | except ValueError: 15 | raise ValueError("Chat ID must be a valid UUID") 16 | -------------------------------------------------------------------------------- /apps/api/src/domain/chat/value_objects/user_id.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from uuid import UUID 3 | 4 | 5 | @dataclass(frozen=True) 6 | class UserId: 7 | value: str 8 | 9 | def __post_init__(self) -> None: 10 | if not self.value: 11 | raise ValueError("User ID is required") 12 | try: 13 | UUID(self.value) 14 | except ValueError: 15 | raise ValueError("User ID must be a valid UUID") 16 | -------------------------------------------------------------------------------- /apps/reader/src/hooks/usePress.ts: -------------------------------------------------------------------------------- 1 | import { useState } from 'react' 2 | 3 | import { useEventListener } from './useEventListener' 4 | 5 | export function usePress(target: React.RefObject | null) { 6 | const [pressed, setPressed] = useState(false) 7 | useEventListener(target?.current, 'mousedown', () => { 8 | setPressed(true) 9 | }) 10 | useEventListener('mouseup', () => { 11 | setPressed(false) 12 | }) 13 | return pressed 14 | } 15 | -------------------------------------------------------------------------------- /packages/epubjs/test/old/rendering.js: -------------------------------------------------------------------------------- 1 | module('Rendering') 2 | /* 3 | asyncTest("Render To", 1, function() { 4 | 5 | var book = ePub("../books/moby-dick/OPS/package.opf"); 6 | var rendition = book.renderTo("qunit-fixture", {width:400, height:600}); 7 | var displayed = rendition.display(0); 8 | 9 | displayed.then(function(){ 10 | equal( $( "iframe", "#qunit-fixture" ).length, 1, "iframe added successfully" ); 11 | start(); 12 | }); 13 | 14 | 15 | }); 16 | */ 17 | -------------------------------------------------------------------------------- /apps/api/src/infrastructure/postgres/__init__.py: -------------------------------------------------------------------------------- 1 | from src.infrastructure.postgres.annotation.annotation_dto import AnnotationDTO 2 | from src.infrastructure.postgres.book.book_dto import BookDTO 3 | from src.infrastructure.postgres.chat.chat_dto import ChatDTO 4 | from src.infrastructure.postgres.message.message_dto import MessageDTO 5 | from src.infrastructure.postgres.user.user_dto import UserDTO 6 | 7 | __all__ = ["UserDTO", "BookDTO", "AnnotationDTO", "ChatDTO", "MessageDTO"] 8 | -------------------------------------------------------------------------------- /apps/reader/netlify.toml: -------------------------------------------------------------------------------- 1 | [build.environment] 2 | NODE_VERSION = "16" 3 | # https://github.com/netlify/build/issues/1633#issuecomment-907246600 4 | NPM_FLAGS = "--version" # prevent Netlify npm install 5 | 6 | [build] 7 | # Set `base` to repo directory in Netlify UI 8 | # base = 'reader' 9 | publish = ".next" 10 | # https://answers.netlify.com/t/using-pnpm-and-pnpm-workspaces/2759 11 | command = "npx pnpm i --store=node_modules/.pnpm-store && npx pnpm -F reader... build" 12 | -------------------------------------------------------------------------------- /apps/reader/src/hooks/useAsync.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef, useState } from 'react' 2 | 3 | export function useAsync( 4 | func: () => Promise | undefined | null, 5 | deps = [], 6 | ) { 7 | const ref = useRef(func) 8 | ref.current = func 9 | const [value, setValue] = useState() 10 | 11 | useEffect(() => { 12 | ref.current()?.then(setValue) 13 | // eslint-disable-next-line react-hooks/exhaustive-deps 14 | }, deps) 15 | 16 | return value 17 | } 18 | -------------------------------------------------------------------------------- /packages/epubjs/types/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "lib": ["es6", "dom"], 5 | "noImplicitAny": true, 6 | "noImplicitThis": true, 7 | "strictNullChecks": true, 8 | "strictFunctionTypes": true, 9 | "baseUrl": "../", 10 | "typeRoots": ["../"], 11 | "types": [], 12 | "noEmit": true, 13 | "forceConsistentCasingInFileNames": true 14 | }, 15 | "files": ["index.d.ts", "epubjs-tests.ts"] 16 | } 17 | -------------------------------------------------------------------------------- /apps/reader/src/hooks/useBoolean.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useState } from 'react' 2 | 3 | export function useBoolean( 4 | initial?: boolean, 5 | ): readonly [boolean | undefined, (v?: any) => void] { 6 | const [state, setState] = useState(initial) 7 | const toggle = useCallback((v?: any) => { 8 | setState((s) => { 9 | if (typeof v === 'boolean') { 10 | return v 11 | } 12 | return !s 13 | }) 14 | }, []) 15 | return [state, toggle] 16 | } 17 | -------------------------------------------------------------------------------- /apps/reader/src/hooks/useHover.ts: -------------------------------------------------------------------------------- 1 | import { useState, RefObject } from 'react' 2 | 3 | import { useEventListener } from './useEventListener' 4 | 5 | export function useHover(target: RefObject | null) { 6 | const [hovered, setHovered] = useState(false) 7 | useEventListener(target?.current, 'mouseenter', () => { 8 | setHovered(true) 9 | }) 10 | useEventListener(target?.current, 'mouseleave', () => { 11 | setHovered(false) 12 | }) 13 | return hovered 14 | } 15 | -------------------------------------------------------------------------------- /apps/api/src/presentation/api/error_messages/message_error_message.py: -------------------------------------------------------------------------------- 1 | class MessageErrorMessage: 2 | MESSAGE_NOT_FOUND = "メッセージが見つかりません。" 3 | MESSAGE_ALREADY_DELETED = "既に削除されたメッセージです。" 4 | MESSAGE_ALREADY_READ = "既に既読のメッセージです。" 5 | MESSAGE_DELIVERY_FAILED = "メッセージの配信に失敗しました。" 6 | MESSAGE_PERMISSION_DENIED = "このメッセージへのアクセス権限がありません。" 7 | MESSAGE_CREATE_FAILED = "メッセージの作成に失敗しました。" 8 | MESSAGE_UPDATE_FAILED = "メッセージの更新に失敗しました。" 9 | MESSAGE_DELETE_FAILED = "メッセージの削除に失敗しました。" 10 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/charliermarsh/ruff-pre-commit 3 | rev: v0.11.5 4 | hooks: 5 | - id: ruff 6 | args: [--fix, --unsafe-fixes] 7 | - id: ruff-format 8 | - repo: https://github.com/python-poetry/poetry 9 | rev: 2.1.2 10 | hooks: 11 | - id: poetry-check 12 | - id: poetry-lock 13 | files: ^(pyproject\.toml|poetry\.lock)$ 14 | default_language_version: 15 | python: python3 16 | minimum_pre_commit_version: 4.0.1 17 | -------------------------------------------------------------------------------- /apps/reader/src/components/podcast/index.ts: -------------------------------------------------------------------------------- 1 | export { AudioPlayer } from './AudioPlayer' 2 | export { AudioControls } from './AudioControls' 3 | export { VolumeControl } from './VolumeControl' 4 | export { SpeedControl } from './SpeedControl' 5 | export { BookPodcastItem } from './BookPodcastItem' 6 | export { LibraryPodcastView } from './LibraryPodcastView' 7 | export { PodcastDetail } from './PodcastDetail' 8 | export { PodcastPane } from './PodcastPane' 9 | export { PodcastScript } from './PodcastScript' 10 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.yml: -------------------------------------------------------------------------------- 1 | name: 🚀 Feature Request 2 | description: Suggest an idea, feature, or enhancement 3 | labels: [enhancement] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Thanks for submitting your idea! It helps make BookWith better. 9 | 10 | - type: textarea 11 | attributes: 12 | label: What feature would you like to see? 13 | description: Please describe what you'd like to happen. 14 | validations: 15 | required: true 16 | -------------------------------------------------------------------------------- /apps/reader/src/hooks/useAction.ts: -------------------------------------------------------------------------------- 1 | import { atom, useAtom, useSetAtom } from 'jotai' 2 | 3 | export type Action = 4 | | 'toc' 5 | | 'chat' 6 | | 'search' 7 | | 'annotation' 8 | | 'typography' 9 | | 'image' 10 | | 'timeline' 11 | | 'theme' 12 | | 'podcast' 13 | 14 | export const actionState = atom(undefined) 15 | 16 | export function useSetAction() { 17 | return useSetAtom(actionState) 18 | } 19 | 20 | export function useAction() { 21 | return useAtom(actionState) 22 | } 23 | -------------------------------------------------------------------------------- /apps/api/src/domain/annotation/value_objects/annotation_cfi.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | 4 | @dataclass(frozen=True) 5 | class AnnotationCfi: 6 | value: str 7 | 8 | def __post_init__(self) -> None: 9 | if not self.value: 10 | raise ValueError("CFI is required") 11 | if not isinstance(self.value, str): 12 | raise ValueError("CFI must be a string") 13 | 14 | @classmethod 15 | def from_string(cls, cfi_str: str) -> "AnnotationCfi": 16 | return cls(cfi_str) 17 | -------------------------------------------------------------------------------- /apps/api/src/domain/annotation/value_objects/annotation_notes.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | 4 | @dataclass(frozen=True) 5 | class AnnotationNotes: 6 | value: str | None 7 | 8 | def __post_init__(self) -> None: 9 | if self.value is not None and not isinstance(self.value, str): 10 | raise ValueError("Annotation notes must be a string") 11 | 12 | @classmethod 13 | def from_string(cls, notes_str: str | None) -> "AnnotationNotes": 14 | """文字列からAnnotationNotesを生成""" 15 | return cls(notes_str) 16 | -------------------------------------------------------------------------------- /packages/epubjs/types/utils/replacements.d.ts: -------------------------------------------------------------------------------- 1 | import Contents from '../contents' 2 | import Section from '../section' 3 | 4 | export function replaceBase(doc: Document, section: Section): void 5 | 6 | export function replaceCanonical(doc: Document, section: Section): void 7 | 8 | export function replaceMeta(doc: Document, section: Section): void 9 | 10 | export function replaceLinks(contents: Contents, fn: Function): void 11 | 12 | export function substitute( 13 | contents: Contents, 14 | urls: string[], 15 | replacements: string[], 16 | ): void 17 | -------------------------------------------------------------------------------- /apps/reader/src/hooks/useSWR/fetcher.ts: -------------------------------------------------------------------------------- 1 | type SWRError = { 2 | status: number 3 | } & Error 4 | 5 | export async function fetcher( 6 | input: RequestInfo, 7 | init?: RequestInit, 8 | ): Promise { 9 | const res = await fetch(input, { 10 | ...init, 11 | headers: { 12 | ...init?.headers, 13 | }, 14 | }) 15 | 16 | if (!res.ok) { 17 | const error = await res.text() 18 | const err = new Error(error) as SWRError 19 | err.status = res.status 20 | throw err 21 | } 22 | 23 | return await res.json() 24 | } 25 | -------------------------------------------------------------------------------- /apps/reader/src/store/loading/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Loading store - modular structure 3 | * 4 | * This file re-exports all loading-related atoms and utilities 5 | * to maintain backward compatibility while providing a clean modular structure. 6 | */ 7 | 8 | // Type definitions 9 | export * from '../../types/loading' 10 | 11 | // Core atoms 12 | export * from './atoms' 13 | 14 | // Selectors (derived atoms) 15 | export * from './selectors' 16 | 17 | // Actions (write-only atoms) 18 | export * from './actions' 19 | 20 | // Utilities 21 | export * from './utils' 22 | -------------------------------------------------------------------------------- /packages/tailwind/src/state.js: -------------------------------------------------------------------------------- 1 | exports.base = { 2 | '--md-sys-state-hover-state-layer-opacity': '0.08', 3 | '--md-sys-state-focus-state-layer-opacity': '0.12', 4 | '--md-sys-state-pressed-state-layer-opacity': '0.12', 5 | '--md-sys-state-dragged-state-layer-opacity': '0.16', 6 | } 7 | 8 | exports.map = { 9 | hover: 'var(--md-sys-state-hover-state-layer-opacity)', 10 | focus: 'var(--md-sys-state-focus-state-layer-opacity)', 11 | pressed: 'var(--md-sys-state-pressed-state-layer-opacity)', 12 | dragged: 'var(--md-sys-state-dragged-state-layer-opacity)', 13 | } 14 | -------------------------------------------------------------------------------- /apps/api/src/domain/message/value_objects/message_id.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | from dataclasses import dataclass 3 | 4 | 5 | @dataclass(frozen=True) 6 | class MessageId: 7 | value: str 8 | 9 | def __post_init__(self) -> None: 10 | if not self.value: 11 | raise ValueError("MessageId value cannot be empty") 12 | 13 | try: 14 | uuid.UUID(self.value) 15 | except ValueError: 16 | raise ValueError("MessageId must be a valid UUID") 17 | 18 | @classmethod 19 | def generate(cls) -> "MessageId": 20 | return cls(str(uuid.uuid4())) 21 | -------------------------------------------------------------------------------- /apps/api/src/domain/podcast/value_objects/podcast_id.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | from dataclasses import dataclass 3 | 4 | 5 | @dataclass(frozen=True) 6 | class PodcastId: 7 | value: str 8 | 9 | def __post_init__(self) -> None: 10 | if not self.value: 11 | raise ValueError("PodcastId value cannot be empty") 12 | 13 | try: 14 | uuid.UUID(self.value) 15 | except ValueError: 16 | raise ValueError("PodcastId must be a valid UUID") 17 | 18 | @classmethod 19 | def generate(cls) -> "PodcastId": 20 | return cls(str(uuid.uuid4())) 21 | -------------------------------------------------------------------------------- /apps/api/src/presentation/api/schemas/annotation_schema.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | from src.domain.annotation.value_objects.annotation_color import AnnotationColorEnum 4 | from src.domain.annotation.value_objects.annotation_type import AnnotationTypeEnum 5 | from src.presentation.api.schemas.base_schema import BaseSchemaModel 6 | 7 | 8 | class AnnotationSchema(BaseSchemaModel): 9 | id: str 10 | book_id: str 11 | 12 | cfi: str 13 | color: AnnotationColorEnum 14 | notes: str | None = None 15 | spine: dict[str, Any] 16 | text: str 17 | type: AnnotationTypeEnum 18 | -------------------------------------------------------------------------------- /apps/api/src/domain/annotation/value_objects/annotation_text.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | 4 | @dataclass(frozen=True) 5 | class AnnotationText: 6 | value: str 7 | 8 | def __post_init__(self) -> None: 9 | if not self.value: 10 | raise ValueError("Annotation text is required") 11 | if not isinstance(self.value, str): 12 | raise ValueError("Annotation text must be a string") 13 | 14 | @classmethod 15 | def from_string(cls, text_str: str) -> "AnnotationText": 16 | """文字列からAnnotationTextを生成""" 17 | return cls(text_str) 18 | -------------------------------------------------------------------------------- /apps/reader/src/hooks/theme/useSourceColor.ts: -------------------------------------------------------------------------------- 1 | import { useCallback } from 'react' 2 | 3 | import { useSettings } from '@flow/reader/utils/state' 4 | 5 | export function useSourceColor() { 6 | const [{ theme }, setSettings] = useSettings() 7 | 8 | const setSourceColor = useCallback( 9 | (source: string) => { 10 | setSettings((prev) => ({ 11 | ...prev, 12 | theme: { 13 | ...prev.theme, 14 | source, 15 | }, 16 | })) 17 | }, 18 | [setSettings], 19 | ) 20 | 21 | return { sourceColor: theme?.source ?? '#0ea5e9', setSourceColor } 22 | } 23 | -------------------------------------------------------------------------------- /apps/reader/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": "tailwind.config.cjs", 8 | "css": "src/pages/styles.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@flow/reader/components", 15 | "utils": "@flow/reader/lib/utils", 16 | "ui": "@flow/reader/components/ui", 17 | "lib": "@flow/reader/lib", 18 | "hooks": "@flow/reader/hooks" 19 | }, 20 | "iconLibrary": "lucide" 21 | } 22 | -------------------------------------------------------------------------------- /apps/reader/src/hooks/useMobile.ts: -------------------------------------------------------------------------------- 1 | import { atom, useAtom } from 'jotai' 2 | import { useEffect } from 'react' 3 | 4 | export const mobileState = atom(undefined) 5 | 6 | let listened = false 7 | 8 | export function useMobile() { 9 | const [mobile, setMobile] = useAtom(mobileState) 10 | 11 | useEffect(() => { 12 | if (listened) return 13 | listened = true 14 | 15 | const mq = window.matchMedia('(max-width: 640px)') 16 | setMobile(mq.matches) 17 | mq.addEventListener('change', (e) => { 18 | setMobile(e.matches) 19 | }) 20 | }, [setMobile]) 21 | 22 | return mobile 23 | } 24 | -------------------------------------------------------------------------------- /tsconfig.next.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "Next.js", 4 | "extends": "./tsconfig.json", 5 | "compilerOptions": { 6 | "target": "es6", 7 | "lib": ["dom", "dom.iterable", "esnext"], 8 | "allowJs": true, 9 | "skipLibCheck": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "noEmit": true, 12 | "incremental": true, 13 | "esModuleInterop": true, 14 | "module": "esnext", 15 | "resolveJsonModule": true, 16 | "isolatedModules": true, 17 | "jsx": "preserve" 18 | }, 19 | "include": ["src", "next-env.d.ts"], 20 | "exclude": ["node_modules"] 21 | } 22 | -------------------------------------------------------------------------------- /apps/api/src/domain/podcast/exceptions/__init__.py: -------------------------------------------------------------------------------- 1 | from .podcast_exceptions import ( 2 | PodcastAlreadyExistsError, 3 | PodcastAudioSynthesisError, 4 | PodcastException, 5 | PodcastGenerationError, 6 | PodcastInvalidStatusError, 7 | PodcastNotFoundError, 8 | PodcastScriptGenerationError, 9 | PodcastStorageError, 10 | ) 11 | 12 | __all__ = [ 13 | "PodcastException", 14 | "PodcastNotFoundError", 15 | "PodcastAlreadyExistsError", 16 | "PodcastGenerationError", 17 | "PodcastInvalidStatusError", 18 | "PodcastScriptGenerationError", 19 | "PodcastAudioSynthesisError", 20 | "PodcastStorageError", 21 | ] 22 | -------------------------------------------------------------------------------- /apps/reader/src/hooks/loading/useLoadingState.ts: -------------------------------------------------------------------------------- 1 | import { useAtomValue } from 'jotai' 2 | 3 | import { isGlobalLoadingAtom, loadingTasksAtom } from '../../store/loading' 4 | 5 | /** 6 | * 基本的なローディング状態を管理するフック 7 | */ 8 | export function useLoadingState(getCurrentTaskId: () => string | null) { 9 | const tasks = useAtomValue(loadingTasksAtom) 10 | const isGlobalLoading = useAtomValue(isGlobalLoadingAtom) 11 | 12 | // このフックインスタンスがローディング中かどうか 13 | const currentTaskId = getCurrentTaskId() 14 | const isLoading = currentTaskId ? tasks.has(currentTaskId) : false 15 | 16 | return { 17 | tasks, 18 | isGlobalLoading, 19 | isLoading, 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /apps/api/src/domain/message/__init__.py: -------------------------------------------------------------------------------- 1 | from src.domain.message.entities import Message 2 | from src.domain.message.exceptions import ( 3 | MessageAlreadyDeletedException, 4 | MessageDeliveryFailedException, 5 | MessageNotFoundException, 6 | ) 7 | from src.domain.message.repositories import MessageRepository 8 | from src.domain.message.value_objects import ( 9 | MessageContent, 10 | MessageId, 11 | ) 12 | 13 | __all__ = [ 14 | "Message", 15 | "MessageNotFoundException", 16 | "MessageAlreadyDeletedException", 17 | "MessageDeliveryFailedException", 18 | "MessageRepository", 19 | "MessageId", 20 | "MessageContent", 21 | ] 22 | -------------------------------------------------------------------------------- /apps/reader/src/hooks/useMediaQuery.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react' 2 | 3 | import { useEventListener } from './useEventListener' 4 | 5 | export function useMediaQuery(query: string) { 6 | const [mql, setMQL] = useState() 7 | const [matches, setMatches] = useState() 8 | 9 | useEffect(() => { 10 | setMQL(window.matchMedia(query)) 11 | }, [query]) 12 | 13 | useEffect(() => { 14 | if (mql) setMatches(mql.matches) 15 | }, [mql]) 16 | 17 | useEventListener(mql as unknown as EventTarget, 'change', (e: Event) => 18 | setMatches((e as MediaQueryListEvent).matches), 19 | ) 20 | 21 | return matches 22 | } 23 | -------------------------------------------------------------------------------- /apps/reader/src/components/Page.tsx: -------------------------------------------------------------------------------- 1 | import clsx from 'clsx' 2 | import { ComponentProps } from 'react' 3 | 4 | interface PageProps extends ComponentProps<'div'> { 5 | headline: string 6 | } 7 | export const Page: React.FC = ({ 8 | className, 9 | children, 10 | headline, 11 | ...props 12 | }) => { 13 | return ( 14 |
15 |

22 | {headline} 23 |

24 | {children} 25 |
26 | ) 27 | } 28 | -------------------------------------------------------------------------------- /apps/api/index_tenant_mapping.json: -------------------------------------------------------------------------------- 1 | { 2 | "BookContent_8eb3eeea": { 3 | "tenant_id": "test_user_id_alice", 4 | "file_name": "alice.epub", 5 | "created_at": "11b137d4-b230-4156-9af2-b4fab5a84ede" 6 | }, 7 | "BookContent_729bd4be": { 8 | "tenant_id": "test_user_id_3a98e39b-173a-48ae-91a5-4d89b729e113", 9 | "file_name": "Fundamental-Accessibility-Tests-Basic-Functionality-v1.0.0.epub", 10 | "created_at": "49f41bfb-90d4-4870-a5f2-0b3b2ddb8623" 11 | }, 12 | "BookContent_6d92e688": { 13 | "tenant_id": "test_user_id_58263899-d6bb-4ec6-905a-9e6e97269e03", 14 | "file_name": "alice.epub", 15 | "created_at": "f949d1e9-55e6-4b91-bfb9-15a3a6952ef4" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/epubjs/types/index.d.ts: -------------------------------------------------------------------------------- 1 | // Type definitions for epubjs 0.3 2 | // Project: https://github.com/futurepress/epub.js#readme 3 | // Definitions by: Fred Chasen 4 | // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped 5 | import Epub from './epub' 6 | 7 | export as namespace ePub 8 | 9 | export default Epub 10 | 11 | export { default as Book } from './book' 12 | export { default as EpubCFI } from './epubcfi' 13 | export { Rendition, Location } from './rendition' 14 | export { default as Contents } from './contents' 15 | export { default as Layout } from './layout' 16 | export { NavItem } from './navigation' 17 | 18 | declare namespace ePub {} 19 | -------------------------------------------------------------------------------- /packages/epubjs/types/spine.d.ts: -------------------------------------------------------------------------------- 1 | import Packaging from './packaging' 2 | import Section from './section' 3 | import Hook from './utils/hook' 4 | 5 | export default class Spine { 6 | constructor() 7 | 8 | hooks: { 9 | serialize: Hook 10 | content: Hook 11 | } 12 | 13 | unpack(_package: Packaging, resolver: Function, canonical: Function): void 14 | 15 | get(target?: string | number): Section 16 | 17 | each(...args: any[]): any 18 | 19 | first(): Section 20 | 21 | last(): Section 22 | 23 | destroy(): void 24 | 25 | private append(section: Section): number 26 | 27 | private prepend(section: Section): number 28 | 29 | private remove(section: Section): number 30 | } 31 | -------------------------------------------------------------------------------- /apps/api/src/domain/chat/exceptions/chat_exceptions.py: -------------------------------------------------------------------------------- 1 | class ChatError(Exception): 2 | pass 3 | 4 | 5 | class ChatNotFoundError(ChatError): 6 | def __init__(self, message: str = "Chat not found") -> None: 7 | self.message = message 8 | super().__init__(self.message) 9 | 10 | 11 | class ChatAlreadyExistsError(ChatError): 12 | def __init__(self, message: str = "Chat already exists") -> None: 13 | self.message = message 14 | super().__init__(self.message) 15 | 16 | 17 | class ChatValidationError(ChatError): 18 | def __init__(self, message: str = "Chat validation failed") -> None: 19 | self.message = message 20 | super().__init__(self.message) 21 | -------------------------------------------------------------------------------- /packages/epubjs/types/pagelist.d.ts: -------------------------------------------------------------------------------- 1 | export interface PageListItem { 2 | href: string 3 | page: string 4 | cfi?: string 5 | packageUrl?: string 6 | } 7 | 8 | export default class Pagelist { 9 | constructor(xml: XMLDocument) 10 | 11 | parse(xml: XMLDocument): Array 12 | 13 | pageFromCfi(cfi: string): number 14 | 15 | cfiFromPage(pg: string | number): string 16 | 17 | pageFromPercentage(percent: number): number 18 | 19 | percentageFromPage(pg: number): number 20 | 21 | destroy(): void 22 | 23 | private parseNav(navHtml: Node): Array 24 | 25 | private item(item: Node): PageListItem 26 | 27 | private process(pageList: Array): void 28 | } 29 | -------------------------------------------------------------------------------- /apps/reader/src/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export * from './theme' 2 | export * from './useAction' 3 | export * from './useAsync' 4 | export * from './useDisablePinchZooming' 5 | export * from './useEnv' 6 | export * from './useForceRender' 7 | export * from './useSWR/useLibrary' 8 | export * from './useList' 9 | export * from './useMobile' 10 | export * from './useTextSelection' 11 | export * from './useTranslation' 12 | export * from './useTypography' 13 | export * from './useAfterMount' 14 | export * from './useBoolean' 15 | export * from './useEventListener' 16 | export * from './useHover' 17 | export * from './useMediaQuery' 18 | export * from './usePress' 19 | export * from './podcast/usePodcastActions' 20 | export * from './podcast/useAudioControls' 21 | -------------------------------------------------------------------------------- /packages/epubjs/types/utils/queue.d.ts: -------------------------------------------------------------------------------- 1 | import { defer } from './core' 2 | 3 | export interface QueuedTask { 4 | task: any | Task 5 | args: any[] 6 | deferred: any // should be defer, but not working 7 | promise: Promise 8 | } 9 | 10 | export default class Queue { 11 | constructor(context: any) 12 | 13 | enqueue(func: Promise | Function, ...args: any[]): Promise 14 | 15 | dequeue(): Promise 16 | 17 | dump(): void 18 | 19 | run(): Promise 20 | 21 | flush(): Promise 22 | 23 | clear(): void 24 | 25 | length(): number 26 | 27 | pause(): void 28 | 29 | stop(): void 30 | } 31 | 32 | declare class Task { 33 | constructor(task: any, args: any[], context: any) 34 | } 35 | -------------------------------------------------------------------------------- /apps/api/src/domain/book/value_objects/book_id.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | from dataclasses import dataclass 3 | 4 | 5 | @dataclass(frozen=True) 6 | class BookId: 7 | value: str 8 | 9 | def __post_init__(self) -> None: 10 | if not self.value: 11 | raise ValueError("BookIdは必須です") 12 | 13 | if not self._is_valid_uuid(self.value): 14 | raise ValueError("BookIdは有効なUUID形式である必要があります") 15 | 16 | @staticmethod 17 | def _is_valid_uuid(val: str) -> bool: 18 | try: 19 | uuid.UUID(str(val)) 20 | return True 21 | except ValueError: 22 | return False 23 | 24 | @classmethod 25 | def generate(cls) -> "BookId": 26 | return cls(str(uuid.uuid4())) 27 | -------------------------------------------------------------------------------- /apps/reader/src/components/FormattedText.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | function cleanUpText(str: string, strict = false): string { 4 | str = str.replace(/^\s+|\s+$/g, '') 5 | 6 | if (strict) { 7 | str = str.replace(/\n{2,}/g, '\n') 8 | } 9 | 10 | return str 11 | } 12 | 13 | export const FormattedText = ({ 14 | text, 15 | strict = false, 16 | className, 17 | }: { 18 | text: string 19 | strict?: boolean 20 | className?: string 21 | }) => ( 22 |

23 | {cleanUpText(text, strict) 24 | .split('\n') 25 | .map((line, index) => ( 26 | 27 | {line} 28 |
29 |
30 | ))} 31 |

32 | ) 33 | -------------------------------------------------------------------------------- /apps/api/src/config/seed.py: -------------------------------------------------------------------------------- 1 | from src.config.app_config import TEST_USER_ID 2 | from src.config.db import SessionLocal 3 | from src.infrastructure.postgres.user.user_dto import UserDTO 4 | 5 | # ※ 初回のみ、テーブル作成を実行(すでにテーブルが存在する場合は不要) 6 | # Base.metadata.create_all(bind=engine) 7 | 8 | 9 | def seed_data(): 10 | session = SessionLocal() 11 | try: 12 | # シードデータの作成例 13 | seed_items = [UserDTO(id=TEST_USER_ID, username="testuser", email="example@example.com")] 14 | 15 | # 複数のシードデータを一括で追加 16 | session.add_all(seed_items) 17 | session.commit() 18 | except Exception: 19 | session.rollback() 20 | finally: 21 | session.close() 22 | 23 | 24 | if __name__ == "__main__": 25 | seed_data() 26 | -------------------------------------------------------------------------------- /apps/reader/src/hooks/useDisablePinchZooming.ts: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react' 2 | 3 | // https://github.com/excalidraw/excalidraw/blob/7eaf47c9d41a33a6230d8c3a16b5087fc720dcfb/src/packages/excalidraw/index.tsx#L66 4 | export function useDisablePinchZooming(win?: Window) { 5 | useEffect(() => { 6 | const _win = win ?? window 7 | // Block pinch-zooming on iOS outside of the content area 8 | const handleTouchMove = (event: TouchEvent) => { 9 | event.preventDefault() 10 | } 11 | 12 | _win.document.addEventListener('touchmove', handleTouchMove, { 13 | passive: false, 14 | }) 15 | 16 | return () => { 17 | _win.document.removeEventListener('touchmove', handleTouchMove) 18 | } 19 | }, [win]) 20 | } 21 | -------------------------------------------------------------------------------- /apps/api/src/infrastructure/vector.py: -------------------------------------------------------------------------------- 1 | from langchain_weaviate.vectorstores import WeaviateVectorStore 2 | 3 | from src.infrastructure.memory.memory_vector_store import MemoryVectorStore 4 | 5 | 6 | def get_book_content_vector_store() -> WeaviateVectorStore: 7 | """BookContent 用の VectorStore を取得する. 8 | 9 | MemoryVectorStore で生成済みの共有クライアント/Embedding を再利用し、 10 | 不要なコネクションやモデルの重複ロードを防ぐ。 11 | """ 12 | try: 13 | return WeaviateVectorStore( 14 | client=MemoryVectorStore.get_client(), 15 | text_key="content", 16 | index_name=MemoryVectorStore.BOOK_CONTENT_COLLECTION_NAME, 17 | embedding=MemoryVectorStore.get_embedding_model(), 18 | ) 19 | except Exception: 20 | raise 21 | -------------------------------------------------------------------------------- /apps/api/src/presentation/api/handlers/annotation_api_route_handler.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter, Depends, status 2 | 3 | from src.infrastructure.di.injection import get_sync_annotations_usecase 4 | from src.presentation.api.schemas.book_schema import BookUpdateRequest 5 | from src.usecase.annotation.update_annotation_use_case import SyncAnnotationsUseCase 6 | 7 | router = APIRouter() 8 | 9 | 10 | @router.put("", status_code=status.HTTP_204_NO_CONTENT) 11 | async def update_annotation( 12 | book_id: str, 13 | changes: BookUpdateRequest, 14 | sync_annotations_usecase: SyncAnnotationsUseCase = Depends(get_sync_annotations_usecase), 15 | ) -> None: 16 | sync_annotations_usecase.execute(book_id=book_id, annotations=changes.annotations) 17 | -------------------------------------------------------------------------------- /apps/reader/src/types/loading.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Loading task types and interfaces 3 | */ 4 | 5 | export interface LoadingTask { 6 | id: string 7 | message?: string 8 | startTime?: number 9 | progress?: { 10 | current: number 11 | total: number 12 | } 13 | type: 'global' | 'local' 14 | canCancel?: boolean 15 | icon?: string 16 | subTasks?: { 17 | currentFileName?: string 18 | filesCompleted: number 19 | filesTotal: number 20 | } 21 | } 22 | 23 | export type LoadingTaskUpdate = Partial 24 | 25 | export interface LoadingTaskWithUpdates { 26 | id: string 27 | updates: LoadingTaskUpdate 28 | } 29 | 30 | export type LoadingTaskType = 'global' | 'local' 31 | 32 | export type LoadingTaskMap = Map 33 | -------------------------------------------------------------------------------- /apps/reader/src/components/ui/textarea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | 3 | import { cn } from '../../lib/utils' 4 | 5 | const Textarea = React.forwardRef< 6 | HTMLTextAreaElement, 7 | React.ComponentProps<'textarea'> 8 | >(({ className, ...props }, ref) => { 9 | return ( 10 |