Add font family choice to the text widget, move toolbar below the editor
Six more vendored families alongside the existing Noto Sans (Inter, Source Sans 3, Noto Serif, Crimson Text, Arvo, IBM Plex Mono -- sans/ serif/slab/mono variety), all OFL-licensed with their own per-family license file in app/fonts/ since each has a different copyright holder. Static Regular/Bold/Italic/BoldItalic builds only -- variable-font-only families (Inter and Source Sans's current Google Fonts releases, plus Playfair Display/Lora/Merriweather) were skipped in favor of static builds from their own upstream repos, keeping every family's loading code uniform with what was already there. Considered but deliberately left out: Georgia -- a proprietary Microsoft core font, not freely redistributable, unlike everything else vendored here. Also moves the bold/italic/underline/color toolbar below the contenteditable box per request, and reorders the dialog's Settings card to a more natural family-then-size order. Fixes a latent migration bug this surfaced: migration 20 (static image widget) used Base.metadata.create_all, which creates every table declared in Base.metadata that's missing, not just its own new one -- harmless when nothing else pending, but once TextWidgetConfig existed it would silently pre-create text_widget_configs (in whatever shape models.py currently declares) before migration 21 got a turn, so migration 21's own CREATE TABLE (or a later ALTER TABLE adding font_family) would collide with a table create_all had already leaked into existence. Both migrations 20 and 21 now use raw, frozen CREATE TABLE SQL instead, matching migration 17's existing precedent for exactly this reason.
This commit is contained in:
+46
-6
@@ -505,18 +505,57 @@ def _migration_20(conn) -> None:
|
||||
image (or a PDF's first page) the user last uploaded (see
|
||||
app/image_upload.py, routers/api_widgets.py's api_widget_static_
|
||||
upload) -- no live upstream to poll, unlike every other widget type.
|
||||
Brand new table with no existing data to carry forward, so this is
|
||||
just create_all's usual "creates the one new table; existing ones
|
||||
untouched" shape (see _migration_2)."""
|
||||
Base.metadata.create_all(bind=conn)
|
||||
|
||||
Raw CREATE TABLE, not Base.metadata.create_all (this migration
|
||||
originally used create_all -- switched retroactively once it turned
|
||||
out to matter): create_all creates every table declared in Base.
|
||||
metadata that's missing, not just this migration's own new one, so
|
||||
it would just as happily create text_widget_configs (a LATER
|
||||
migration's model, once TextWidgetConfig existed in models.py) years
|
||||
before migration 21 gets a turn -- and then migration 21's own
|
||||
CREATE TABLE collides with the one create_all already snuck in. Same
|
||||
fix, same reasoning as migration 21's own comment about migration
|
||||
22's ALTER TABLE -- see that one for the fuller explanation."""
|
||||
conn.execute(text(
|
||||
"CREATE TABLE static_widget_configs ("
|
||||
"widget_id INTEGER PRIMARY KEY REFERENCES widgets(id) ON DELETE CASCADE, "
|
||||
"image BLOB, "
|
||||
"original_filename TEXT NOT NULL DEFAULT '', "
|
||||
"uploaded_at REAL NOT NULL DEFAULT 0.0, "
|
||||
"display_mode TEXT NOT NULL DEFAULT 'crop_fill')"
|
||||
))
|
||||
|
||||
|
||||
def _migration_21(conn) -> None:
|
||||
"""New widget type: a text widget shows user-authored rich text (see
|
||||
app/text_content.py, app/widgets/text.py, models.TextWidgetConfig)
|
||||
-- another no-live-upstream type like migration 20's static image.
|
||||
Same brand-new-table create_all shape."""
|
||||
Base.metadata.create_all(bind=conn)
|
||||
|
||||
Raw CREATE TABLE (not Base.metadata.create_all, unlike migration 20's
|
||||
static_widget_configs) because migration 22 adds a column to this
|
||||
same table right after -- create_all always reflects models.py's
|
||||
CURRENT shape, so replaying the full migration chain on an old
|
||||
database would have it already include that later column and
|
||||
collide with migration 22's ALTER TABLE. Same reason migration 17's
|
||||
task_widget_configs CREATE TABLE is raw SQL rather than create_all,
|
||||
ahead of migration 19's ALTER TABLE ADD COLUMN name."""
|
||||
conn.execute(text(
|
||||
"CREATE TABLE text_widget_configs ("
|
||||
"widget_id INTEGER PRIMARY KEY REFERENCES widgets(id) ON DELETE CASCADE, "
|
||||
"content TEXT, "
|
||||
"font_size INTEGER NOT NULL DEFAULT 28, "
|
||||
"align TEXT NOT NULL DEFAULT 'left', "
|
||||
"background_color TEXT NOT NULL DEFAULT '#ffffff')"
|
||||
))
|
||||
|
||||
|
||||
def _migration_22(conn) -> None:
|
||||
"""Adds a font family choice to the text widget (app/widgets/text.py's
|
||||
FONT_FAMILIES) alongside its existing font_size -- both whole-widget
|
||||
settings, not per-run. "sans" (Noto Sans) matches the column default
|
||||
so existing text widgets keep rendering in the same font they always
|
||||
have."""
|
||||
conn.execute(text("ALTER TABLE text_widget_configs ADD COLUMN font_family TEXT NOT NULL DEFAULT 'sans'"))
|
||||
|
||||
|
||||
MIGRATIONS = [
|
||||
@@ -541,6 +580,7 @@ MIGRATIONS = [
|
||||
(19, _migration_19),
|
||||
(20, _migration_20),
|
||||
(21, _migration_21),
|
||||
(22, _migration_22),
|
||||
]
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user