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.
56 lines
2.7 KiB
HTML
56 lines
2.7 KiB
HTML
<h2 class="dialog-title">Text widget</h2>
|
|
|
|
<section class="card">
|
|
<h2 class="card-title">Text</h2>
|
|
<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>
|
|
<button type="button" class="richtext-btn" id="text-underline" title="Underline" data-cmd="underline"><u>U</u></button>
|
|
<span class="richtext-toolbar-sep"></span>
|
|
<label class="richtext-color-label" title="Text color">A<input type="color" id="text-color" value="#000000"></label>
|
|
<label class="richtext-color-label" title="Highlight color">▣<input type="color" id="text-highlight" value="#ffff00"></label>
|
|
<button type="button" class="richtext-btn" id="text-highlight-clear" title="Remove highlight">×</button>
|
|
</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 }}">
|
|
<span class="slider-value" id="text_font_size_value">{{ text_cfg.font_size if text_cfg else 28 }}px</span>
|
|
</label>
|
|
<label>Alignment
|
|
<select id="text_align">
|
|
{% for value, label in [("left", "Left"), ("center", "Center"), ("right", "Right")] %}
|
|
<option value="{{ value }}" {% if text_cfg and text_cfg.align == value %}selected{% endif %}>{{ label }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</label>
|
|
<label>Background color
|
|
<input type="color" id="text_background_color" value="{{ text_cfg.background_color if text_cfg else '#ffffff' }}">
|
|
</label>
|
|
<button type="submit">Save</button>
|
|
</form>
|
|
</section>
|
|
|
|
<section class="card" style="margin-top: 20px;">
|
|
<h2 class="card-title">Preview</h2>
|
|
<p class="sub">How this widget currently renders.</p>
|
|
<img class="preview-img" id="text-preview" alt="Text widget preview">
|
|
<button type="button" class="secondary" id="text-preview-refresh">Refresh now</button>
|
|
</section>
|