Add font family choice to the text widget, move toolbar below the editor
Build and push server image / test (push) Successful in 29s
Build and push server image / build-and-push (push) Successful in 2m1s
Build and push server image / deploy (push) Successful in 51s

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:
Thomas Faour
2026-07-25 16:05:22 +00:00
parent 3735c5bfa7
commit edbd90745b
39 changed files with 659 additions and 36 deletions
@@ -5,6 +5,8 @@
<p class="sub">A short block of styled text -- select some and use the
toolbar, or just type. Saved as plain text plus style flags, not raw
HTML.</p>
<div class="richtext-editor" id="text-editor" contenteditable="true"
data-content='{{ (text_cfg.content if text_cfg else none) | tojson }}'></div>
<div class="richtext-toolbar" id="text-toolbar">
<button type="button" class="richtext-btn" id="text-bold" title="Bold" data-cmd="bold"><b>B</b></button>
<button type="button" class="richtext-btn" id="text-italic" title="Italic" data-cmd="italic"><i>I</i></button>
@@ -14,13 +16,18 @@
<label class="richtext-color-label" title="Highlight color">&#9635;<input type="color" id="text-highlight" value="#ffff00"></label>
<button type="button" class="richtext-btn" id="text-highlight-clear" title="Remove highlight">&times;</button>
</div>
<div class="richtext-editor" id="text-editor" contenteditable="true"
data-content='{{ (text_cfg.content if text_cfg else none) | tojson }}'></div>
</section>
<section class="card" style="margin-top: 20px;">
<h2 class="card-title">Settings</h2>
<form id="text-config-form">
<label>Font family
<select id="text_font_family">
{% for value, label in text_font_families.items() %}
<option value="{{ value }}" {% if text_cfg and text_cfg.font_family == value %}selected{% endif %}>{{ label }}</option>
{% endfor %}
</select>
</label>
<label>Font size
<input type="range" id="text_font_size" min="10" max="96" step="2"
value="{{ text_cfg.font_size if text_cfg else 28 }}">