feat(editor): text above is named by the language, shown on hover or a long press
Furigana was the only name for text set above text, though it carries pinyin, a pronunciation or a translation as well. Its button and dialog now say furigana in a Japanese studyset, pinyin in a Chinese one, and text above elsewhere, each with an example of its own: the studyset's languages reach the core's editors through ContentLanguages, which the studyset's language scope now sets. Read-only, the text above shows only under the pointer or after a long press (another hides it), so the text can be read without it first; its room stays, so no line moves. While editing, it shows over the pointer above its dotted text; with the caret inside such text, the button selects all of it and edits what it has, so it can be seen and changed without finding its ends. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
fcee3c2670
commit
a0b4fa969c
1 changed files with 487 additions and 79 deletions
|
|
@ -89,7 +89,7 @@ A spaced-repetition learning app: studysets of flashcards, reviewed on an FSRS s
|
|||
- **Material's `Card` widget clashes with the domain's `Card`**: files that need both import `material_ui` with `hide Card`.
|
||||
- **State: `flutter_bloc`, uniformly.** Blocs fold repository streams into immutable states with value equality, are provided as close to their consumer as possible, and log through one global `BlocObserver`. Why: the data layer is stream-shaped, event → state machines test without widgets, and one pattern everywhere is itself the point.
|
||||
- **Value equality comes from `equatable`**, mixed in (`with Equatable`), for models and states alike; `leancode_lint`'s `missing_equatable_props` catches a field left out of `props`.
|
||||
- **Rich text is `RichContent`** (`lib/src/core/content/`): the editor's document JSON plus a `formatVersion`, pure Dart, so models and the server can use it. `lib/src/core/editor/` converts it to and from the editor's `Document` (giving tables the decimal sizes the editor casts to: JSON from imports and archives may hold whole numbers, or none; our fork of the editor fits a read-only table's rows to their cells, which upstream's leaves at their stored height) and holds the one editing widget (`RichContentEditor`) and the one read-only renderer (`RichContentView`). Copying and pasting keep every format between the app's editors (`EditorClipboard`, decided 2026-10-11): the system's clipboard holds plain text only, so the blocks copied last stay in memory beside it and a paste takes them while it still holds their text, plain text otherwise; the editor's own copy, cut and paste are replaced (Ctrl+C, X, V), and on phones a bar over selected text (the editor's `MobileFloatingToolbar`, over text only) copies, cuts, pastes and selects all. Links come from the format bar's link button and Ctrl+K (`link.dart`, in place of the editor's own link menu, English only); an address without its scheme leads to the web.
|
||||
- **Rich text is `RichContent`** (`lib/src/core/content/`): the editor's document JSON plus a `formatVersion`, pure Dart, so models and the server can use it. `lib/src/core/editor/` converts it to and from the editor's `Document` (giving tables the decimal sizes the editor casts to: JSON from imports and archives may hold whole numbers, or none; our fork of the editor fits a read-only table's rows to their cells, which upstream's leaves at their stored height) and holds the one editing widget (`RichContentEditor`) and the one read-only renderer (`RichContentView`). Copying and pasting keep every format between the app's editors (`EditorClipboard`, decided 2026-10-11): the system's clipboard holds plain text only, so the blocks copied last stay in memory beside it and a paste takes them while it still holds their text, plain text otherwise; the editor's own copy, cut and paste are replaced (Ctrl+C, X, V), and on phones a bar over selected text (the editor's `MobileFloatingToolbar`, over text only) copies, cuts, pastes and selects all. Links come from the format bar's link button and Ctrl+K (`link.dart`, in place of the editor's own link menu, English only); an address without its scheme leads to the web. Text above other text (ruby, `ruby.dart`, decided 2026-10-12) is named in the content's language, furigana over Japanese, pinyin over Chinese, else text above (`ContentLanguages`, in `core/widget/`, which every screen of a studyset sets through its language scope); read-only it shows only under the pointer or after a long press, its room kept so lines don't move, and while editing over the pointer; with the caret inside such text, the format bar's button edits all of it.
|
||||
- **Blocks** beyond the editor's own (callout, spoiler, formula, image, sound, YouTube video, study button) keep their type and attributes as named in `core/content/content_blocks.dart`, which `RichContent` reads too (their text counts in `plainText`, and a video or study button isn't blank). An `EditorBlocks` lists how each type is built, which can be inserted (the format bar's Insert sheet and the desktop's `/` menu show the same list) and its typing rules; the core's are `coreBlocks`, and a feature's (the study button, `feature/study/`) reach the editors through `EditorBlocksScope`, which `PersonApp` fills, since the core can't know features. One builder serves editor and view: a block asks `editorState.editable`, and read-only shows what it is for (a spoiler hidden until revealed, a formula set, a video's play). A block taken whole mixes in `AtomicBlock` (selection, the remove button); unknown types show "needs a newer version" and stay as they are. New block types keep `formatVersion` 1: raising it would hide whole studysets and paths in older apps.
|
||||
- **Time comes from `clock.now()`** (`package:clock`), never `DateTime.now()`, in server and app. Why: scheduling, streaks and expiry are deterministic under test (`withClock`, `fakeAsync`), and because Serverpod itself reads `clock.now()` (e.g. session expiry), `withClock` controls its behaviour too.
|
||||
- **UTC for storing and sending, local time for display and day boundaries.** Serverpod sends `DateTime` as UTC and stores it in `timestamp without time zone` columns. Compare and compute in UTC; call `.toLocal()` only to display. "Due today", streaks and daily goals use the learner's **local** calendar day, computed on the client; the server never needs day boundaries.
|
||||
|
|
|
|||
Loading…
Reference in a new issue