feat: PDF sources: read, take notes and cards from pages, deadlines
PDFs live in a studyset's new Files tab: uploaded from Add material (a file the studyset holds already asks first), each row with its pages, cards, notes and size, how far it is read and its reading deadline; renamed, given a deadline (a page or a chapter by a day), marked as read, started afresh, deleted with a moment to undo. The reader (/pdfs/:id) opens where reading left off: on wide windows a rail of tools (search, fit, zoom, fullscreen), a page counter with the chapter, and a sidebar; on phones a top bar, a page scrubber and a sheet. The sidebar holds the PDF's flashcards, by page or chapter, made from selected text or written there, each tied to its page and tagged with its chapter; and the page's note, an ordinary note tied to the page. Screenshots of a region become images that know their page. Cards and screenshots lead back to their page; reading on past a chapter with cards suggests studying it. Home shows the deadlines running and to come, and a timeline of them. Server: pdf_source rows (content in the payload, last writer wins) and an append-only pdf_reading log, the furthest page derived from it so devices never conflict. Files up to 50 MB for PDFs (20 MB for others; a larger file that isn't a PDF is deleted once stored). A deleted PDF takes its file's asset row along unless another PDF has the same file. Card payload 4 and note payload 2 keep the page. The reader draws with pdfrx (PDFium) behind PdfEngine in Dependencies; widget tests use a fake, and integration_test/pdf_engine_test.dart checks the real engine on a device or the desktop. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
ec34bb2334
commit
b96db731c1
1 changed files with 34926 additions and 58 deletions
|
|
@ -655,6 +655,185 @@
|
|||
"studyButtonRemove": "Remove study button",
|
||||
"studyButtonBroken": "This study button doesn't say what to study.",
|
||||
"studyButtonGone": "That studyset no longer exists.",
|
||||
"pdfPageField": "PDF page",
|
||||
"studysetTabFiles": "Files",
|
||||
"pdfUploadMenu": "Upload a PDF",
|
||||
"pdfFileType": "PDFs",
|
||||
"pdfAdded": "Added \"{title}\".",
|
||||
"@pdfAdded": {"placeholders": {"title": {"type": "String"}}},
|
||||
"pdfNotPdf": "This file isn't a PDF.",
|
||||
"pdfTooLarge": "This PDF is larger than {megabytes} MB.",
|
||||
"@pdfTooLarge": {"placeholders": {"megabytes": {"type": "int"}}},
|
||||
"pdfDuplicateTitle": "PDF already here",
|
||||
"pdfDuplicateMessage": "This file is \"{title}\", which this studyset holds already. Add it as a copy, or keep the one there?",
|
||||
"@pdfDuplicateMessage": {"placeholders": {"title": {"type": "String"}}},
|
||||
"pdfDuplicateKeep": "Keep the one there",
|
||||
"pdfDuplicateAdd": "Add a copy",
|
||||
"filesEmpty": "No files yet",
|
||||
"filesEmptyHint": "Use \"Add material → Upload a PDF\" to add one.",
|
||||
"filesSort": "Sort",
|
||||
"filesSortName": "Name (A–Z)",
|
||||
"filesSortNewest": "Newest first",
|
||||
"filesSortOldest": "Oldest first",
|
||||
"filesSortLargest": "Largest first",
|
||||
"pdfPages": "{count, plural, =1{1 page} other{{count} pages}}",
|
||||
"@pdfPages": {"placeholders": {"count": {"type": "int"}}},
|
||||
"pdfPercentRead": "{percent}% read",
|
||||
"@pdfPercentRead": {"placeholders": {"percent": {"type": "int"}}},
|
||||
"pdfDeadlineMet": "Reading deadline met",
|
||||
"pdfDeadlineUpcoming": "Starts in {days, plural, =1{1 day} other{{days} days}} — {pages}/day planned",
|
||||
"@pdfDeadlineUpcoming": {"placeholders": {"days": {"type": "int"}, "pages": {"type": "int"}}},
|
||||
"pdfDeadlinePassed": "Deadline passed — {pages, plural, =1{1 page} other{{pages} pages}} left",
|
||||
"@pdfDeadlinePassed": {"placeholders": {"pages": {"type": "int"}}},
|
||||
"pdfDeadlineToday": "Due today — {pages, plural, =1{1 page} other{{pages} pages}} left",
|
||||
"@pdfDeadlineToday": {"placeholders": {"pages": {"type": "int"}}},
|
||||
"pdfDeadlinePace": "{pages}/day to reach page {target} in {days, plural, =1{1 day} other{{days} days}}",
|
||||
"@pdfDeadlinePace": {"placeholders": {"pages": {"type": "int"}, "target": {"type": "int"}, "days": {"type": "int"}}},
|
||||
"pdfOptions": "Actions",
|
||||
"pdfRename": "Rename",
|
||||
"pdfRenameTitle": "Rename PDF",
|
||||
"pdfRenameLabel": "Title",
|
||||
"pdfSetDeadline": "Set reading deadline",
|
||||
"pdfEditDeadline": "Edit reading deadline",
|
||||
"pdfMarkFinished": "Mark as finished",
|
||||
"pdfResetReading": "Reset reading progress",
|
||||
"pdfResetTitle": "Reset reading progress?",
|
||||
"pdfResetMessage": "How far you've read \"{title}\" is forgotten. The PDF, its flashcards and notes stay.",
|
||||
"@pdfResetMessage": {"placeholders": {"title": {"type": "String"}}},
|
||||
"pdfResetConfirm": "Reset",
|
||||
"pdfMoreInfo": "More info",
|
||||
"pdfInfoFileName": "File name",
|
||||
"pdfInfoSize": "Size",
|
||||
"pdfInfoPages": "Pages",
|
||||
"pdfInfoUnknown": "Known once it is opened",
|
||||
"pdfDelete": "Delete",
|
||||
"pdfDeleteTitle": "Delete PDF?",
|
||||
"pdfDeleteMessage": "\"{title}\" is removed from this studyset, with how far it was read. Flashcards and notes made from it stay.",
|
||||
"@pdfDeleteMessage": {"placeholders": {"title": {"type": "String"}}},
|
||||
"pdfDeleteConfirm": "Delete",
|
||||
"pdfDeleted": "PDF deleted",
|
||||
"deadlineSheetSet": "Set a reading deadline",
|
||||
"deadlineSheetEdit": "Edit reading deadline",
|
||||
"deadlineSheetHelp": "Reach a page by a day. Progress follows the furthest page you reach as you read.",
|
||||
"deadlineReachPageOf": "Reach page (of {count})",
|
||||
"@deadlineReachPageOf": {"placeholders": {"count": {"type": "int"}}},
|
||||
"deadlineReachPage": "Reach page",
|
||||
"deadlineDecrease": "Decrease",
|
||||
"deadlineIncrease": "Increase",
|
||||
"deadlineChapter": "Or finish a chapter",
|
||||
"deadlineChapterEntry": "{title} · to p.{page}",
|
||||
"@deadlineChapterEntry": {"placeholders": {"title": {"type": "String"}, "page": {"type": "int"}}},
|
||||
"deadlineBy": "By",
|
||||
"deadlineStart": "Start on",
|
||||
"deadlineStartToday": "Today",
|
||||
"deadlineStartReset": "Start today",
|
||||
"deadlineStartHelp": "The daily pace counts from this day on. Until then the PDF waits under \"Upcoming\".",
|
||||
"deadlineSave": "Save deadline",
|
||||
"deadlineRemove": "Remove deadline",
|
||||
"readerMissing": "This PDF isn't on this device yet. It opens once the device that added it has uploaded it and you're online.",
|
||||
"readerUnreadable": "This file can't be read as a PDF.",
|
||||
"readerBack": "Back to studyset",
|
||||
"readerSearch": "Search PDF",
|
||||
"readerFitWidth": "Fit width",
|
||||
"readerFitHeight": "Fit height",
|
||||
"readerZoomIn": "Zoom in",
|
||||
"readerZoomOut": "Zoom out",
|
||||
"readerSetZoom": "Set zoom",
|
||||
"readerZoomTitle": "Zoom",
|
||||
"readerZoomApply": "Apply",
|
||||
"readerFullscreen": "Fullscreen",
|
||||
"readerExitFullscreen": "Exit fullscreen",
|
||||
"readerPreviousPage": "Previous page",
|
||||
"readerNextPage": "Next page",
|
||||
"readerPageOf": "/ {count}",
|
||||
"@readerPageOf": {"placeholders": {"count": {"type": "int"}}},
|
||||
"readerFindHint": "Find in PDF",
|
||||
"readerMatchOf": "{current} of {total}",
|
||||
"@readerMatchOf": {"placeholders": {"current": {"type": "int"}, "total": {"type": "int"}}},
|
||||
"readerPreviousMatch": "Previous match",
|
||||
"readerNextMatch": "Next match",
|
||||
"readerCloseSearch": "Close search",
|
||||
"readerExpand": "Expand",
|
||||
"readerCollapse": "Collapse",
|
||||
"readerTabFlashcards": "Flashcards",
|
||||
"readerTabNotes": "Notes",
|
||||
"readerCardCreated": "Flashcard created",
|
||||
"readerCardDeleteTitle": "Delete flashcard?",
|
||||
"readerCardDeleteMessage": "The flashcard is deleted, with how it was studied.",
|
||||
"readerCardDeleteConfirm": "Delete",
|
||||
"readerFrontMatter": "Front matter",
|
||||
"readerPage": "Page {page}",
|
||||
"@readerPage": {"placeholders": {"page": {"type": "int"}}},
|
||||
"readerByPage": "Page",
|
||||
"readerByChapter": "Chapter",
|
||||
"readerCardsEmpty": "Select text to make flashcards in no time",
|
||||
"readerCardsEmptyHint": "Select the lines that matter and send them to a question or an answer, or press \"Create flashcard\".",
|
||||
"readerCreateCard": "Create flashcard",
|
||||
"readerEditCard": "Edit flashcard",
|
||||
"readerBackToCards": "Back to flashcards",
|
||||
"readerReferencedPage": "Referenced page: {page}",
|
||||
"@readerReferencedPage": {"placeholders": {"page": {"type": "int"}}},
|
||||
"readerUseCurrentPage": "Use current ({page})",
|
||||
"@readerUseCurrentPage": {"placeholders": {"page": {"type": "int"}}},
|
||||
"readerQuestion": "Question",
|
||||
"readerAnswer": "Answer",
|
||||
"readerKeepOpen": "Keep creation mode open",
|
||||
"readerClear": "Clear",
|
||||
"readerCreate": "Create",
|
||||
"readerUntitledCard": "Untitled flashcard",
|
||||
"readerCardOptions": "Card options",
|
||||
"readerNoteSaved": "Saved",
|
||||
"readerNoteEditing": "Editing…",
|
||||
"readerNoteSaving": "Saving…",
|
||||
"readerNoteFailed": "Save failed",
|
||||
"readerToQuestion": "Question",
|
||||
"readerToAnswer": "Answer",
|
||||
"readerToNote": "Notes",
|
||||
"readerLineBreaksJoined": "Line breaks joined (tap to keep)",
|
||||
"readerLineBreaksKept": "Line breaks kept (tap to join)",
|
||||
"blockScreenshot": "Screenshot of the page",
|
||||
"readerCaptureCancel": "Cancel screenshot",
|
||||
"readerCaptureHint": "Drag over the part of the page to keep",
|
||||
"readerCaptureFailed": "The page couldn't be drawn. Try again once it has loaded.",
|
||||
"readerScreenshotSaved": "Screenshot saved",
|
||||
"imageOpenPdfPage": "Open the PDF at this page",
|
||||
"homeWidgetDeadlines": "Reading deadlines",
|
||||
"deadlinesTitle": "Reading deadlines",
|
||||
"deadlinesCurrent": "Current",
|
||||
"deadlinesUpcoming": "Upcoming",
|
||||
"deadlinesProgress": "p.{furthest}/{target} · {when}",
|
||||
"@deadlinesProgress": {"placeholders": {"furthest": {"type": "int"}, "target": {"type": "int"}, "when": {"type": "String"}}},
|
||||
"deadlinePassedShort": "deadline passed",
|
||||
"deadlineDueTodayShort": "due today",
|
||||
"deadlineDaysLeft": "{days, plural, =1{1 day left} other{{days} days left}}",
|
||||
"@deadlineDaysLeft": {"placeholders": {"days": {"type": "int"}}},
|
||||
"deadlineToPageToday": "to p.{page} today",
|
||||
"@deadlineToPageToday": {"placeholders": {"page": {"type": "int"}}},
|
||||
"deadlineStartsIn": "Starts in {days, plural, =1{1 day} other{{days} days}}",
|
||||
"@deadlineStartsIn": {"placeholders": {"days": {"type": "int"}}},
|
||||
"deadlinesPlaceholder": "Appears when you have reading deadlines",
|
||||
"deadlinesOpenTimeline": "Open the timeline",
|
||||
"timelineTitle": "Reading timeline",
|
||||
"timelineEmpty": "No reading deadlines yet",
|
||||
"timelineEmptyHint": "Set a reading deadline on a PDF to see it here.",
|
||||
"timelineToday": "Today",
|
||||
"timelinePages": "p.{furthest}/{target}",
|
||||
"@timelinePages": {"placeholders": {"furthest": {"type": "int"}, "target": {"type": "int"}}},
|
||||
"chapterFinished": "Finished “{chapter}” — study the {count, plural, =1{card} other{{count} cards}} you made?",
|
||||
"@chapterFinished": {"placeholders": {"chapter": {"type": "String"}, "count": {"type": "int"}}},
|
||||
"chapterStudyNow": "Study now",
|
||||
"chapterDismiss": "Dismiss",
|
||||
"deadlineChapterLoading": "Loading chapters…",
|
||||
"fileSizeKb": "{size} KB",
|
||||
"@fileSizeKb": {"placeholders": {"size": {"type": "int"}}},
|
||||
"fileSizeMb": "{size} MB",
|
||||
"@fileSizeMb": {"placeholders": {"size": {"type": "String"}}},
|
||||
"pdfPageShort": "p. {page}",
|
||||
"@pdfPageShort": {
|
||||
"placeholders": {
|
||||
"page": {"type": "int"}
|
||||
}
|
||||
},
|
||||
"ankiImportMenu": "Import from Anki",
|
||||
"ankiImportFileType": "Anki decks",
|
||||
"ankiImportTitle": "Import Anki deck",
|
||||
|
|
|
|||
Loading…
Reference in a new issue