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
|
|
@ -2005,6 +2005,560 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
@override
|
||||
String get studyButtonGone => 'That studyset no longer exists.';
|
||||
|
||||
@override
|
||||
String get pdfPageField => 'PDF page';
|
||||
|
||||
@override
|
||||
String get studysetTabFiles => 'Files';
|
||||
|
||||
@override
|
||||
String get pdfUploadMenu => 'Upload a PDF';
|
||||
|
||||
@override
|
||||
String get pdfFileType => 'PDFs';
|
||||
|
||||
@override
|
||||
String pdfAdded(String title) {
|
||||
return 'Added \"$title\".';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pdfNotPdf => 'This file isn\'t a PDF.';
|
||||
|
||||
@override
|
||||
String pdfTooLarge(int megabytes) {
|
||||
return 'This PDF is larger than $megabytes MB.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pdfDuplicateTitle => 'PDF already here';
|
||||
|
||||
@override
|
||||
String pdfDuplicateMessage(String title) {
|
||||
return 'This file is \"$title\", which this studyset holds already. Add it as a copy, or keep the one there?';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pdfDuplicateKeep => 'Keep the one there';
|
||||
|
||||
@override
|
||||
String get pdfDuplicateAdd => 'Add a copy';
|
||||
|
||||
@override
|
||||
String get filesEmpty => 'No files yet';
|
||||
|
||||
@override
|
||||
String get filesEmptyHint =>
|
||||
'Use \"Add material → Upload a PDF\" to add one.';
|
||||
|
||||
@override
|
||||
String get filesSort => 'Sort';
|
||||
|
||||
@override
|
||||
String get filesSortName => 'Name (A–Z)';
|
||||
|
||||
@override
|
||||
String get filesSortNewest => 'Newest first';
|
||||
|
||||
@override
|
||||
String get filesSortOldest => 'Oldest first';
|
||||
|
||||
@override
|
||||
String get filesSortLargest => 'Largest first';
|
||||
|
||||
@override
|
||||
String pdfPages(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: '$count pages',
|
||||
one: '1 page',
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String pdfPercentRead(int percent) {
|
||||
return '$percent% read';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pdfDeadlineMet => 'Reading deadline met';
|
||||
|
||||
@override
|
||||
String pdfDeadlineUpcoming(int days, int pages) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
days,
|
||||
locale: localeName,
|
||||
other: '$days days',
|
||||
one: '1 day',
|
||||
);
|
||||
return 'Starts in $_temp0 — $pages/day planned';
|
||||
}
|
||||
|
||||
@override
|
||||
String pdfDeadlinePassed(int pages) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
pages,
|
||||
locale: localeName,
|
||||
other: '$pages pages',
|
||||
one: '1 page',
|
||||
);
|
||||
return 'Deadline passed — $_temp0 left';
|
||||
}
|
||||
|
||||
@override
|
||||
String pdfDeadlineToday(int pages) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
pages,
|
||||
locale: localeName,
|
||||
other: '$pages pages',
|
||||
one: '1 page',
|
||||
);
|
||||
return 'Due today — $_temp0 left';
|
||||
}
|
||||
|
||||
@override
|
||||
String pdfDeadlinePace(int pages, int target, int days) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
days,
|
||||
locale: localeName,
|
||||
other: '$days days',
|
||||
one: '1 day',
|
||||
);
|
||||
return '$pages/day to reach page $target in $_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pdfOptions => 'Actions';
|
||||
|
||||
@override
|
||||
String get pdfRename => 'Rename';
|
||||
|
||||
@override
|
||||
String get pdfRenameTitle => 'Rename PDF';
|
||||
|
||||
@override
|
||||
String get pdfRenameLabel => 'Title';
|
||||
|
||||
@override
|
||||
String get pdfSetDeadline => 'Set reading deadline';
|
||||
|
||||
@override
|
||||
String get pdfEditDeadline => 'Edit reading deadline';
|
||||
|
||||
@override
|
||||
String get pdfMarkFinished => 'Mark as finished';
|
||||
|
||||
@override
|
||||
String get pdfResetReading => 'Reset reading progress';
|
||||
|
||||
@override
|
||||
String get pdfResetTitle => 'Reset reading progress?';
|
||||
|
||||
@override
|
||||
String pdfResetMessage(String title) {
|
||||
return 'How far you\'ve read \"$title\" is forgotten. The PDF, its flashcards and notes stay.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pdfResetConfirm => 'Reset';
|
||||
|
||||
@override
|
||||
String get pdfMoreInfo => 'More info';
|
||||
|
||||
@override
|
||||
String get pdfInfoFileName => 'File name';
|
||||
|
||||
@override
|
||||
String get pdfInfoSize => 'Size';
|
||||
|
||||
@override
|
||||
String get pdfInfoPages => 'Pages';
|
||||
|
||||
@override
|
||||
String get pdfInfoUnknown => 'Known once it is opened';
|
||||
|
||||
@override
|
||||
String get pdfDelete => 'Delete';
|
||||
|
||||
@override
|
||||
String get pdfDeleteTitle => 'Delete PDF?';
|
||||
|
||||
@override
|
||||
String pdfDeleteMessage(String title) {
|
||||
return '\"$title\" is removed from this studyset, with how far it was read. Flashcards and notes made from it stay.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pdfDeleteConfirm => 'Delete';
|
||||
|
||||
@override
|
||||
String get pdfDeleted => 'PDF deleted';
|
||||
|
||||
@override
|
||||
String get deadlineSheetSet => 'Set a reading deadline';
|
||||
|
||||
@override
|
||||
String get deadlineSheetEdit => 'Edit reading deadline';
|
||||
|
||||
@override
|
||||
String get deadlineSheetHelp =>
|
||||
'Reach a page by a day. Progress follows the furthest page you reach as you read.';
|
||||
|
||||
@override
|
||||
String deadlineReachPageOf(int count) {
|
||||
return 'Reach page (of $count)';
|
||||
}
|
||||
|
||||
@override
|
||||
String get deadlineReachPage => 'Reach page';
|
||||
|
||||
@override
|
||||
String get deadlineDecrease => 'Decrease';
|
||||
|
||||
@override
|
||||
String get deadlineIncrease => 'Increase';
|
||||
|
||||
@override
|
||||
String get deadlineChapter => 'Or finish a chapter';
|
||||
|
||||
@override
|
||||
String deadlineChapterEntry(String title, int page) {
|
||||
return '$title · to p.$page';
|
||||
}
|
||||
|
||||
@override
|
||||
String get deadlineBy => 'By';
|
||||
|
||||
@override
|
||||
String get deadlineStart => 'Start on';
|
||||
|
||||
@override
|
||||
String get deadlineStartToday => 'Today';
|
||||
|
||||
@override
|
||||
String get deadlineStartReset => 'Start today';
|
||||
|
||||
@override
|
||||
String get deadlineStartHelp =>
|
||||
'The daily pace counts from this day on. Until then the PDF waits under \"Upcoming\".';
|
||||
|
||||
@override
|
||||
String get deadlineSave => 'Save deadline';
|
||||
|
||||
@override
|
||||
String get deadlineRemove => 'Remove deadline';
|
||||
|
||||
@override
|
||||
String get readerMissing =>
|
||||
'This PDF isn\'t on this device yet. It opens once the device that added it has uploaded it and you\'re online.';
|
||||
|
||||
@override
|
||||
String get readerUnreadable => 'This file can\'t be read as a PDF.';
|
||||
|
||||
@override
|
||||
String get readerBack => 'Back to studyset';
|
||||
|
||||
@override
|
||||
String get readerSearch => 'Search PDF';
|
||||
|
||||
@override
|
||||
String get readerFitWidth => 'Fit width';
|
||||
|
||||
@override
|
||||
String get readerFitHeight => 'Fit height';
|
||||
|
||||
@override
|
||||
String get readerZoomIn => 'Zoom in';
|
||||
|
||||
@override
|
||||
String get readerZoomOut => 'Zoom out';
|
||||
|
||||
@override
|
||||
String get readerSetZoom => 'Set zoom';
|
||||
|
||||
@override
|
||||
String get readerZoomTitle => 'Zoom';
|
||||
|
||||
@override
|
||||
String get readerZoomApply => 'Apply';
|
||||
|
||||
@override
|
||||
String get readerFullscreen => 'Fullscreen';
|
||||
|
||||
@override
|
||||
String get readerExitFullscreen => 'Exit fullscreen';
|
||||
|
||||
@override
|
||||
String get readerPreviousPage => 'Previous page';
|
||||
|
||||
@override
|
||||
String get readerNextPage => 'Next page';
|
||||
|
||||
@override
|
||||
String readerPageOf(int count) {
|
||||
return '/ $count';
|
||||
}
|
||||
|
||||
@override
|
||||
String get readerFindHint => 'Find in PDF';
|
||||
|
||||
@override
|
||||
String readerMatchOf(int current, int total) {
|
||||
return '$current of $total';
|
||||
}
|
||||
|
||||
@override
|
||||
String get readerPreviousMatch => 'Previous match';
|
||||
|
||||
@override
|
||||
String get readerNextMatch => 'Next match';
|
||||
|
||||
@override
|
||||
String get readerCloseSearch => 'Close search';
|
||||
|
||||
@override
|
||||
String get readerExpand => 'Expand';
|
||||
|
||||
@override
|
||||
String get readerCollapse => 'Collapse';
|
||||
|
||||
@override
|
||||
String get readerTabFlashcards => 'Flashcards';
|
||||
|
||||
@override
|
||||
String get readerTabNotes => 'Notes';
|
||||
|
||||
@override
|
||||
String get readerCardCreated => 'Flashcard created';
|
||||
|
||||
@override
|
||||
String get readerCardDeleteTitle => 'Delete flashcard?';
|
||||
|
||||
@override
|
||||
String get readerCardDeleteMessage =>
|
||||
'The flashcard is deleted, with how it was studied.';
|
||||
|
||||
@override
|
||||
String get readerCardDeleteConfirm => 'Delete';
|
||||
|
||||
@override
|
||||
String get readerFrontMatter => 'Front matter';
|
||||
|
||||
@override
|
||||
String readerPage(int page) {
|
||||
return 'Page $page';
|
||||
}
|
||||
|
||||
@override
|
||||
String get readerByPage => 'Page';
|
||||
|
||||
@override
|
||||
String get readerByChapter => 'Chapter';
|
||||
|
||||
@override
|
||||
String get readerCardsEmpty => 'Select text to make flashcards in no time';
|
||||
|
||||
@override
|
||||
String get readerCardsEmptyHint =>
|
||||
'Select the lines that matter and send them to a question or an answer, or press \"Create flashcard\".';
|
||||
|
||||
@override
|
||||
String get readerCreateCard => 'Create flashcard';
|
||||
|
||||
@override
|
||||
String get readerEditCard => 'Edit flashcard';
|
||||
|
||||
@override
|
||||
String get readerBackToCards => 'Back to flashcards';
|
||||
|
||||
@override
|
||||
String readerReferencedPage(int page) {
|
||||
return 'Referenced page: $page';
|
||||
}
|
||||
|
||||
@override
|
||||
String readerUseCurrentPage(int page) {
|
||||
return 'Use current ($page)';
|
||||
}
|
||||
|
||||
@override
|
||||
String get readerQuestion => 'Question';
|
||||
|
||||
@override
|
||||
String get readerAnswer => 'Answer';
|
||||
|
||||
@override
|
||||
String get readerKeepOpen => 'Keep creation mode open';
|
||||
|
||||
@override
|
||||
String get readerClear => 'Clear';
|
||||
|
||||
@override
|
||||
String get readerCreate => 'Create';
|
||||
|
||||
@override
|
||||
String get readerUntitledCard => 'Untitled flashcard';
|
||||
|
||||
@override
|
||||
String get readerCardOptions => 'Card options';
|
||||
|
||||
@override
|
||||
String get readerNoteSaved => 'Saved';
|
||||
|
||||
@override
|
||||
String get readerNoteEditing => 'Editing…';
|
||||
|
||||
@override
|
||||
String get readerNoteSaving => 'Saving…';
|
||||
|
||||
@override
|
||||
String get readerNoteFailed => 'Save failed';
|
||||
|
||||
@override
|
||||
String get readerToQuestion => 'Question';
|
||||
|
||||
@override
|
||||
String get readerToAnswer => 'Answer';
|
||||
|
||||
@override
|
||||
String get readerToNote => 'Notes';
|
||||
|
||||
@override
|
||||
String get readerLineBreaksJoined => 'Line breaks joined (tap to keep)';
|
||||
|
||||
@override
|
||||
String get readerLineBreaksKept => 'Line breaks kept (tap to join)';
|
||||
|
||||
@override
|
||||
String get blockScreenshot => 'Screenshot of the page';
|
||||
|
||||
@override
|
||||
String get readerCaptureCancel => 'Cancel screenshot';
|
||||
|
||||
@override
|
||||
String get readerCaptureHint => 'Drag over the part of the page to keep';
|
||||
|
||||
@override
|
||||
String get readerCaptureFailed =>
|
||||
'The page couldn\'t be drawn. Try again once it has loaded.';
|
||||
|
||||
@override
|
||||
String get readerScreenshotSaved => 'Screenshot saved';
|
||||
|
||||
@override
|
||||
String get imageOpenPdfPage => 'Open the PDF at this page';
|
||||
|
||||
@override
|
||||
String get homeWidgetDeadlines => 'Reading deadlines';
|
||||
|
||||
@override
|
||||
String get deadlinesTitle => 'Reading deadlines';
|
||||
|
||||
@override
|
||||
String get deadlinesCurrent => 'Current';
|
||||
|
||||
@override
|
||||
String get deadlinesUpcoming => 'Upcoming';
|
||||
|
||||
@override
|
||||
String deadlinesProgress(int furthest, int target, String when) {
|
||||
return 'p.$furthest/$target · $when';
|
||||
}
|
||||
|
||||
@override
|
||||
String get deadlinePassedShort => 'deadline passed';
|
||||
|
||||
@override
|
||||
String get deadlineDueTodayShort => 'due today';
|
||||
|
||||
@override
|
||||
String deadlineDaysLeft(int days) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
days,
|
||||
locale: localeName,
|
||||
other: '$days days left',
|
||||
one: '1 day left',
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String deadlineToPageToday(int page) {
|
||||
return 'to p.$page today';
|
||||
}
|
||||
|
||||
@override
|
||||
String deadlineStartsIn(int days) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
days,
|
||||
locale: localeName,
|
||||
other: '$days days',
|
||||
one: '1 day',
|
||||
);
|
||||
return 'Starts in $_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String get deadlinesPlaceholder => 'Appears when you have reading deadlines';
|
||||
|
||||
@override
|
||||
String get deadlinesOpenTimeline => 'Open the timeline';
|
||||
|
||||
@override
|
||||
String get timelineTitle => 'Reading timeline';
|
||||
|
||||
@override
|
||||
String get timelineEmpty => 'No reading deadlines yet';
|
||||
|
||||
@override
|
||||
String get timelineEmptyHint =>
|
||||
'Set a reading deadline on a PDF to see it here.';
|
||||
|
||||
@override
|
||||
String get timelineToday => 'Today';
|
||||
|
||||
@override
|
||||
String timelinePages(int furthest, int target) {
|
||||
return 'p.$furthest/$target';
|
||||
}
|
||||
|
||||
@override
|
||||
String chapterFinished(String chapter, int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: '$count cards',
|
||||
one: 'card',
|
||||
);
|
||||
return 'Finished “$chapter” — study the $_temp0 you made?';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chapterStudyNow => 'Study now';
|
||||
|
||||
@override
|
||||
String get chapterDismiss => 'Dismiss';
|
||||
|
||||
@override
|
||||
String get deadlineChapterLoading => 'Loading chapters…';
|
||||
|
||||
@override
|
||||
String fileSizeKb(int size) {
|
||||
return '$size KB';
|
||||
}
|
||||
|
||||
@override
|
||||
String fileSizeMb(String size) {
|
||||
return '$size MB';
|
||||
}
|
||||
|
||||
@override
|
||||
String pdfPageShort(int page) {
|
||||
return 'p. $page';
|
||||
}
|
||||
|
||||
@override
|
||||
String get ankiImportMenu => 'Import from Anki';
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue