| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
feat(web): Arabic typography, mirrored icons, and per-block content direction Everything a right-to-left locale needs beyond flipped margins. Arabic is set in Tajawal, forced rather than offered as a fallback. Layering it at the end of the stack does not work here: next/font injects a local "<Family> Fallback" face for CLS, and on macOS that adjusted system font covers Arabic — so per-glyph fallback resolved there and the Arabic webfont was never downloaded. Tajawal carries a Latin subset, so forcing it keeps a mixed screen in one typeface. It overrides the org's custom font too, which needs !important because that font is an inline style, and needs to exist in both @layer utilities and @layer base because the base font-family is declared in both — an override in one layer alone loses on html and body while still winning on their descendants. The same duplication applies to letter-spacing: the -0.02em tracking is tuned for Latin and crushes the joins between Arabic letters. Directional icons mirror via one CSS rule keyed on lucide's per-icon classes, with a data-dir-flip opt-in for libraries that emit none. That beats swapping components at ~250 call sites, each of which would need a mounted gate to avoid a hydration mismatch and would leave every new site a fresh bug. Vertical icons and media transport are deliberately excluded — play and skip are left-to-right everywhere. Authored content picks its own direction, because the UI language and the content language are independent: an English-UI org can host Arabic courses. Rich text uses unicode-bidi: plaintext, which resolves per block from the first strong character. That was the deciding factor over a TipTap extension, which would have written a new attribute into every paragraph of every stored activity and into the Yjs collab stream. Applied narrowly — a blanket rule would break UI strings starting with a digit. Slugs and hostnames are pinned left-to-right, since bidi-reordering a URL produces a different address. Some surfaces stay dir="ltr" because mirroring them is worse than not: CodeMirror computes its geometry physically and code is LTR by language spec; video.js and the audio scrubbers style controls physically, so a flipped DOM fills the progress bar and the buffered overlay in opposite directions; recharts emits absolute SVG coordinates, so flipping moves labels but not the ticks they belong to; the board canvas shares absolute coordinates live between collaborators; and math is LTR by specification. The certificate keeps its fixed layout because html2canvas reproduces logical properties poorly, and gains an await on document.fonts.ready — without it a certificate rasterised before the webfont loads is written to PDF with fallback glyphs, which nobody catches because the on-screen version looks fine. | 1 个月前 | |
feat(editor): library blocks, in-app media previews, flashcard grids Three related gaps. The Library and the editor never knew about each other. An author who had already uploaded a file had to upload it again to use it in an activity. New `blockLibrary` node, in the slash menu and the toolbar: pick an existing item from the Library, or upload one right there -- the upload creates a real Library asset rather than burying the file under a single activity. Media renders inline (watch / read / listen / download); courses, podcasts, communities, boards and playgrounds render as a card that can expand to a chrome-free embed. `ResourcePicker` gains a media tab with thumbnails, opt-in so the existing "add content to folder" flow keeps its current tabs. Clicking a library item opened the raw file in a new tab. It now opens a lightbox with the right viewer plus download, copy link and open-in-new-tab. Both card components funnel every library view -- dashboard, public, folders, search -- so all of them change together. Flashcards could only stack vertically, because a flipcard is a block node and two of them can never share a row. New `flipcardGrid` container holds flipcards in 1-4 columns, with controls to add cards and ungroup; a standalone card can be wrapped into a grid from its own toolbar. Existing flipcards are untouched and keep rendering as full-width rows. Along the way: the media-type table had been copy-pasted into four components and drifted, so it moves to `lib/media/mediaKind.ts` (which also reads `file_mime`, already on the model but unread); the audio player comes out of the Audio block and the resource/embed URL builders come out of their activities, so the new viewers can share them. Both node types are registered in all three canvases and added to the AI content-schema allowlists, otherwise generated content would strip them. | 1 个月前 | |
feat(editor): library blocks, in-app media previews, flashcard grids Three related gaps. The Library and the editor never knew about each other. An author who had already uploaded a file had to upload it again to use it in an activity. New `blockLibrary` node, in the slash menu and the toolbar: pick an existing item from the Library, or upload one right there -- the upload creates a real Library asset rather than burying the file under a single activity. Media renders inline (watch / read / listen / download); courses, podcasts, communities, boards and playgrounds render as a card that can expand to a chrome-free embed. `ResourcePicker` gains a media tab with thumbnails, opt-in so the existing "add content to folder" flow keeps its current tabs. Clicking a library item opened the raw file in a new tab. It now opens a lightbox with the right viewer plus download, copy link and open-in-new-tab. Both card components funnel every library view -- dashboard, public, folders, search -- so all of them change together. Flashcards could only stack vertically, because a flipcard is a block node and two of them can never share a row. New `flipcardGrid` container holds flipcards in 1-4 columns, with controls to add cards and ungroup; a standalone card can be wrapped into a grid from its own toolbar. Existing flipcards are untouched and keep rendering as full-width rows. Along the way: the media-type table had been copy-pasted into four components and drifted, so it moves to `lib/media/mediaKind.ts` (which also reads `file_mime`, already on the model but unread); the audio player comes out of the Audio block and the resource/embed URL builders come out of their activities, so the new viewers can share them. Both node types are registered in all three canvases and added to the AI content-schema allowlists, otherwise generated content would strip them. | 1 个月前 |
| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
| 1 个月前 | ||
| 1 个月前 | ||
| 1 个月前 |