From 3017c3aeb58efc8ae80602a7d1fee1a629baa144 Mon Sep 17 00:00:00 2001 From: SandObserver <260779319+SandObserver@users.noreply.github.com> Date: Mon, 7 Sep 2026 20:42:07 -0400 Subject: [PATCH] Sync the bundled widget and dashboard code --- public/js/admin-app-form.js | 6 +-- public/js/admin-auth.js | 4 +- public/js/admin-color-control.js | 4 +- public/js/admin-drag.js | 2 +- public/js/admin-list.js | 8 ++-- public/js/admin-settings.js | 4 +- public/js/admin-shared.js | 2 +- public/js/admin-widget-form.js | 6 +-- public/js/admin.js | 19 +++++---- public/js/dashboard.js | 6 +-- public/js/spotlight.js | 2 +- public/js/ui.js | 2 +- public/js/utils.js | 4 +- public/js/widget-config-form.js | 6 +-- public/js/widget-error.js | 15 +++++-- public/js/widget-toolbox.js | 2 +- public/widgets/backup/backup.html | 2 +- public/widgets/books/data.js | 42 ++++++++++++++++--- public/widgets/books/demo.js | 30 +++++++++++-- public/widgets/books/i18n/de.json | 5 +++ public/widgets/books/i18n/en.json | 5 +++ public/widgets/books/i18n/es.json | 5 +++ public/widgets/books/i18n/fa.json | 5 +++ public/widgets/books/i18n/fr.json | 5 +++ public/widgets/books/i18n/zh-Hans.json | 5 +++ public/widgets/books/index.html | 27 +++++++----- public/widgets/books/widget.json | 8 ++++ .../widgets/connections/connections-map.html | 2 +- .../widgets/connections/connections-vpn.html | 4 +- public/widgets/dashboard-switch/index.html | 2 +- public/widgets/disk-health/index.html | 8 +--- public/widgets/dns/index.html | 2 +- public/widgets/github/contributions.html | 3 +- public/widgets/github/pullrequests.html | 2 +- public/widgets/nowplaying/index.html | 3 +- public/widgets/system-summary/index.html | 4 +- public/widgets/weather/index.html | 4 +- 37 files changed, 181 insertions(+), 84 deletions(-) diff --git a/public/js/admin-app-form.js b/public/js/admin-app-form.js index 7ab7132..c5bc669 100644 --- a/public/js/admin-app-form.js +++ b/public/js/admin-app-form.js @@ -1,4 +1,4 @@ -import { clr as rc, el, inp as inpById, q as qSel, qa, qi, tgt } from '/js/utils.js?v=4c1189b9'; +import { clr as rc, el, inp as inpById, q as qSel, qa, qi, tgt } from '/js/utils.js?v=e8b2a9f7'; import { html, raw, setHtml } from '/js/html.js?v=c71f8903'; import { loadLocalIcons, resolveIcon, iconChain, cdnIconName } from '/js/icons.js?v=69c2b9bd'; import { state } from '/js/admin-state.js?v=7d68e98e'; @@ -19,9 +19,9 @@ import { initInlineEdit, setTogDisabled, wireChecklist, -} from '/js/admin-shared.js?v=d7dd8b71'; +} from '/js/admin-shared.js?v=70ec6be1'; import { MAX_LABELS } from '/js/badge-logic.js?v=b3c8b6c2'; -import { renderColorControl, BADGE_SWATCHES, BADGE_DEFAULT } from '/js/admin-color-control.js?v=f638b750'; +import { renderColorControl, BADGE_SWATCHES, BADGE_DEFAULT } from '/js/admin-color-control.js?v=2d28867b'; import { badgeErrorAdvice, TONE } from '/js/admin-error.js?v=10f3cdb1'; export function buildFolderForm(body, item) { diff --git a/public/js/admin-auth.js b/public/js/admin-auth.js index d4458d7..f500868 100644 --- a/public/js/admin-auth.js +++ b/public/js/admin-auth.js @@ -1,7 +1,7 @@ -import { ag, ap } from '/js/admin-shared.js?v=d7dd8b71'; +import { ag, ap } from '/js/admin-shared.js?v=70ec6be1'; import { t } from '/js/i18n.js?v=e644a5c5'; import { pwStrength } from '/js/password-strength.js?v=42f45ac7'; -import { el, inp as inpById, qa } from '/js/utils.js?v=4c1189b9'; +import { el, inp as inpById, qa } from '/js/utils.js?v=e8b2a9f7'; export async function checkAuth(onLogin) { try { diff --git a/public/js/admin-color-control.js b/public/js/admin-color-control.js index b935d08..63dd3d9 100644 --- a/public/js/admin-color-control.js +++ b/public/js/admin-color-control.js @@ -1,7 +1,7 @@ -import { PE_SVG, initInlineEdit, toast } from '/js/admin-shared.js?v=d7dd8b71'; +import { PE_SVG, initInlineEdit, toast } from '/js/admin-shared.js?v=70ec6be1'; import { t } from '/js/i18n.js?v=e644a5c5'; import { html, raw, setHtml } from '/js/html.js?v=c71f8903'; -import { qa, q } from '/js/utils.js?v=4c1189b9'; +import { qa, q } from '/js/utils.js?v=e8b2a9f7'; const CC_SWATCHES = ['#1c1c1e', '#8e8e93', '#f2f2f7', '#ff393c', '#ffcd00', '#35c759', '#0289ff', '#cb30df']; export const BADGE_DEFAULT = '#1e6ef4'; diff --git a/public/js/admin-drag.js b/public/js/admin-drag.js index 14ded00..e360eb3 100644 --- a/public/js/admin-drag.js +++ b/public/js/admin-drag.js @@ -12,7 +12,7 @@ import { applyDrop, canJoinFolder, folderRowZone } from '/js/admin-drag-logic.js?v=6b767e76'; import { state } from '/js/admin-state.js?v=7d68e98e'; import { snapshotItems } from '/js/admin-save-logic.js?v=4f71ef6c'; -import { qa } from '/js/utils.js?v=4c1189b9'; +import { qa } from '/js/utils.js?v=e8b2a9f7'; /** @type {(before: unknown) => void} */ let _save = () => {}; diff --git a/public/js/admin-list.js b/public/js/admin-list.js index dbd7788..b4ec511 100644 --- a/public/js/admin-list.js +++ b/public/js/admin-list.js @@ -14,12 +14,12 @@ import { collapsedFolders, filter, state } from '/js/admin-state.js?v=7d68e98e'; import { snapshotItems } from '/js/admin-save-logic.js?v=4f71ef6c'; import { reorderItems } from '/js/admin-logic.js?v=dcf7c37d'; -import { initDrag, wireRowDrag } from '/js/admin-drag.js?v=971d097b'; -import { paintIcon } from '/js/admin-shared.js?v=d7dd8b71'; -import { clr as rc, el, qa, setUserText } from '/js/utils.js?v=4c1189b9'; +import { initDrag, wireRowDrag } from '/js/admin-drag.js?v=f3b01482'; +import { paintIcon } from '/js/admin-shared.js?v=70ec6be1'; +import { clr as rc, el, qa, setUserText } from '/js/utils.js?v=e8b2a9f7'; import { html, raw, setHtml } from '/js/html.js?v=c71f8903'; import { t } from '/js/i18n.js?v=e644a5c5'; -import { sizeLabel } from '/js/admin-widget-form.js?v=cc1dfdec'; +import { sizeLabel } from '/js/admin-widget-form.js?v=997ca7c6'; import { widgetGlyph } from '/js/widget-glyphs.js?v=12b0a947'; /** @type {{ openModal: (idx: number|null) => void, diff --git a/public/js/admin-settings.js b/public/js/admin-settings.js index 55bfc6e..daf7b60 100644 --- a/public/js/admin-settings.js +++ b/public/js/admin-settings.js @@ -1,9 +1,9 @@ -import { toast, ag, ap } from '/js/admin-shared.js?v=d7dd8b71'; +import { toast, ag, ap } from '/js/admin-shared.js?v=70ec6be1'; import { pwStrength } from '/js/password-strength.js?v=42f45ac7'; import { t } from '/js/i18n.js?v=e644a5c5'; import { shouldWritePassword, settingsSaveBlocker, clearsStoredPassword, BLOCK } from '/js/admin-logic.js?v=dcf7c37d'; import { confirmText } from '/js/modal.js?v=11fa1eff'; -import { el, inp, q, qa, setUserText } from '/js/utils.js?v=4c1189b9'; +import { el, inp, q, qa, setUserText } from '/js/utils.js?v=e8b2a9f7'; /* Mirrors the server's rule: auth cannot be switched on with no password. */ let _passwordSet = false; diff --git a/public/js/admin-shared.js b/public/js/admin-shared.js index 5f3ab1b..6a16ae7 100644 --- a/public/js/admin-shared.js +++ b/public/js/admin-shared.js @@ -1,6 +1,6 @@ /* Stateless helpers shared by the admin modules. Mutable state stays out. */ import { nextActiveIndex, recoversSession, toastHoldMs } from '/js/admin-logic.js?v=dcf7c37d'; -import { el, qa, q } from '/js/utils.js?v=4c1189b9'; +import { el, qa, q } from '/js/utils.js?v=e8b2a9f7'; import { t } from '/js/i18n.js?v=e644a5c5'; import { iconChain } from '/js/icons.js?v=69c2b9bd'; diff --git a/public/js/admin-widget-form.js b/public/js/admin-widget-form.js index 97c6ffe..5d2ecfe 100644 --- a/public/js/admin-widget-form.js +++ b/public/js/admin-widget-form.js @@ -1,10 +1,10 @@ import { state } from '/js/admin-state.js?v=7d68e98e'; -import { PE_SVG, CHEV_SVG, initInlineEdit } from '/js/admin-shared.js?v=d7dd8b71'; -import { renderWidgetConfigForm } from '/js/widget-config-form.js?v=02918336'; +import { PE_SVG, CHEV_SVG, initInlineEdit } from '/js/admin-shared.js?v=70ec6be1'; +import { renderWidgetConfigForm } from '/js/widget-config-form.js?v=e6ec5157'; import { html, raw, setHtml } from '/js/html.js?v=c71f8903'; import { sizesForView, widgetConfigMode, rejectionLines, carriesTypedValues } from '/js/admin-logic.js?v=dcf7c37d'; import { t } from '/js/i18n.js?v=e644a5c5'; -import { q, qi, qa } from '/js/utils.js?v=4c1189b9'; +import { q, qi, qa } from '/js/utils.js?v=e8b2a9f7'; const SIZE_ICONS = { small: diff --git a/public/js/admin.js b/public/js/admin.js index 89fde1e..6a5cef3 100644 --- a/public/js/admin.js +++ b/public/js/admin.js @@ -1,6 +1,6 @@ -import { buildAppForm, buildFolderForm, captureActLabels, serializeKvRows } from '/js/admin-app-form.js?v=4a5478d6'; -import { checkAuth, requireLogin, wirePasswordStrength } from '/js/admin-auth.js?v=681cfd29'; -import { initList, render, syncFilterUI } from '/js/admin-list.js?v=8524801f'; +import { buildAppForm, buildFolderForm, captureActLabels, serializeKvRows } from '/js/admin-app-form.js?v=8c9ef961'; +import { checkAuth, requireLogin, wirePasswordStrength } from '/js/admin-auth.js?v=1bb8584a'; +import { initList, render, syncFilterUI } from '/js/admin-list.js?v=2067483a'; import { resolveAdminSection } from '/js/admin-logic.js?v=dcf7c37d'; import { buildAppItem, @@ -10,10 +10,10 @@ import { snapshotItems, upsertItem, } from '/js/admin-save-logic.js?v=4f71ef6c'; -import { loadSettings, showBgFields, showBgFit, showWallpaperFile } from '/js/admin-settings.js?v=909c58ad'; -import { ag, ap, initInlineEdit, paintIcon, setReauthHandler, toast } from '/js/admin-shared.js?v=d7dd8b71'; +import { loadSettings, showBgFields, showBgFit, showWallpaperFile } from '/js/admin-settings.js?v=9f7cf590'; +import { ag, ap, initInlineEdit, paintIcon, setReauthHandler, toast } from '/js/admin-shared.js?v=70ec6be1'; import { collapsedFolders, filter, state } from '/js/admin-state.js?v=7d68e98e'; -import { buildWidgetForm } from '/js/admin-widget-form.js?v=cc1dfdec'; +import { buildWidgetForm } from '/js/admin-widget-form.js?v=997ca7c6'; import { html, raw, setHtml } from '/js/html.js?v=c71f8903'; import { initI18n, LANGUAGES, t } from '/js/i18n.js?v=e644a5c5'; import { loadLocalIcons } from '/js/icons.js?v=69c2b9bd'; @@ -29,8 +29,8 @@ import { import { isMobileLayout, onLayoutChange } from '/js/layout.js?v=9de1cb7d'; import { confirmModal, confirmText, openModal as openDialog, promptModal } from '/js/modal.js?v=11fa1eff'; import { readMode, watchSystemTheme, writeMode } from '/js/theme.js?v=00c011c9'; -import { el, inp, q, qa, clr as rc, sanitizeCssUrl, setUserText, tgt } from '/js/utils.js?v=4c1189b9'; -import { normalizeColorInput } from '/js/admin-color-control.js?v=f638b750'; +import { el, inp, q, qa, clr as rc, sanitizeCssUrl, setUserText, tgt } from '/js/utils.js?v=e8b2a9f7'; +import { normalizeColorInput } from '/js/admin-color-control.js?v=2d28867b'; import { parseYamlTolerant, YamlLiteError } from '/js/yaml-lite.js?v=6ebb564c'; import { loadWallpaper, saveWallpaper } from '/js/wallpaper-cache.js?v=c5f8a3e6'; @@ -609,7 +609,8 @@ function initNav() { localStorage.setItem(STORE, id); } links.forEach(l => l.addEventListener('click', () => show(l.dataset.sec))); - show(localStorage.getItem(STORE)); + addEventListener('hashchange', () => show(location.hash.slice(1))); + show(location.hash.slice(1) || localStorage.getItem(STORE)); } function initAllInlineEdits() { diff --git a/public/js/dashboard.js b/public/js/dashboard.js index c90f5a8..cadb38b 100644 --- a/public/js/dashboard.js +++ b/public/js/dashboard.js @@ -24,13 +24,13 @@ import { setUserText, teardownWidgets, titleWhenTruncated, -} from '/js/utils.js?v=4c1189b9'; -import { initSpotlight } from '/js/spotlight.js?v=c8d5d2d8'; +} from '/js/utils.js?v=e8b2a9f7'; +import { initSpotlight } from '/js/spotlight.js?v=c81114db'; import { html, setHtml, raw } from '/js/html.js?v=c71f8903'; import { initI18n, t, currentLang } from '/js/i18n.js?v=e644a5c5'; import { pwStrength, passwordMismatch } from '/js/password-strength.js?v=42f45ac7'; import { sanitizeItemLinks } from '/js/link-url.js?v=54adb40f'; -import { initUI, mkFolder, openFolderDesktop, openFolderMobile, buildMobile } from '/js/ui.js?v=b71e1c75'; +import { initUI, mkFolder, openFolderDesktop, openFolderMobile, buildMobile } from '/js/ui.js?v=3c2c7d0b'; import { badgeMinimum, badgeSignature, computeBadgeVisual, readBadgeUpdate } from '/js/badge-logic.js?v=b3c8b6c2'; import { formatNumber } from '/js/format-number.js?v=4a5ccef4'; import { closeBadgePopover, wireBadgePopover } from '/js/badge-popover.js?v=aa52b1a3'; diff --git a/public/js/spotlight.js b/public/js/spotlight.js index b0aff04..6f1ce91 100644 --- a/public/js/spotlight.js +++ b/public/js/spotlight.js @@ -1,4 +1,4 @@ -import { mk, clr, el, inp as inpById, q, qa, setUserText } from '/js/utils.js?v=4c1189b9'; +import { mk, clr, el, inp as inpById, q, qa, setUserText } from '/js/utils.js?v=e8b2a9f7'; import { t } from '/js/i18n.js?v=e644a5c5'; /* Attached to the window so a re-open can undo the previous one. */ diff --git a/public/js/ui.js b/public/js/ui.js index 972351a..bb002b5 100644 --- a/public/js/ui.js +++ b/public/js/ui.js @@ -13,7 +13,7 @@ import { q, qa, setUserText, -} from '/js/utils.js?v=4c1189b9'; +} from '/js/utils.js?v=e8b2a9f7'; import { t, currentLang } from '/js/i18n.js?v=e644a5c5'; import { toneForColor } from '/js/label-contrast.js?v=c1ac6fb8'; import { mobileMetrics, gridColumnWidth, gridCellCount } from '/js/mobile-metrics.js?v=ab5fe77e'; diff --git a/public/js/utils.js b/public/js/utils.js index affedf6..f18a8cf 100644 --- a/public/js/utils.js +++ b/public/js/utils.js @@ -417,7 +417,7 @@ export function renderEmptyState(items) { box.className = 'empty-state'; const mark = mk('img', { src: BRAND_MARK, alt: '' }); mark.className = 'empty-state-mark'; - const title = mk('p'); + const title = mk('h2'); title.className = 'empty-state-title'; title.textContent = t('home.emptyTitle'); const sub = mk('p'); @@ -426,7 +426,7 @@ export function renderEmptyState(items) { const btn = mk('a', { href: '/admin/' }); btn.className = 'empty-state-btn'; btn.textContent = t('home.emptyAction'); - const hint = mk('a', { href: '/admin/' }); + const hint = mk('a', { href: '/admin/#general' }); hint.className = 'empty-state-hint'; hint.textContent = t('home.emptyImportLink'); box.append(mark, title, sub, btn, hint); diff --git a/public/js/widget-config-form.js b/public/js/widget-config-form.js index 9210ea2..b9b2961 100644 --- a/public/js/widget-config-form.js +++ b/public/js/widget-config-form.js @@ -4,8 +4,8 @@ import { t } from '/js/i18n.js?v=e644a5c5'; import { html, raw, setHtml } from '/js/html.js?v=c71f8903'; -import { wireChecklist } from '/js/admin-shared.js?v=d7dd8b71'; -import { renderColorControl } from '/js/admin-color-control.js?v=f638b750'; +import { wireChecklist } from '/js/admin-shared.js?v=70ec6be1'; +import { renderColorControl } from '/js/admin-color-control.js?v=2d28867b'; import { seedCarried, applyOptionSet, @@ -15,7 +15,7 @@ import { visibleFieldFlags, } from '/js/admin-logic.js?v=dcf7c37d'; import { optionsErrorAdvice, TONE } from '/js/admin-error.js?v=10f3cdb1'; -import { qi } from '/js/utils.js?v=4c1189b9'; +import { qi } from '/js/utils.js?v=e8b2a9f7'; const PE = ''; diff --git a/public/js/widget-error.js b/public/js/widget-error.js index 1822d94..0d6fca7 100644 --- a/public/js/widget-error.js +++ b/public/js/widget-error.js @@ -108,7 +108,10 @@ const CSS = ` its own hidden rule the caption never goes away. */ .wt-cap[hidden] { display: none; } .wt-cap svg { width: 12px; height: 12px; flex: 0 0 auto; opacity: 0.85; } -.wt-cap b { font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.wt-cap b { font-weight: 500; overflow: hidden; min-width: 0; + /* Two lines, then clip. The narrowest card cannot hold the longest line on + one, and an ellipsised failure does not say what failed. */ + display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; } .wt-cap i { font-style: normal; opacity: 0.7; flex: 0 0 auto; } .wt-cap-auto { position: absolute; inset-inline: 16px; bottom: 12px; } .wt-cap-center { position: absolute; inset: 0; justify-content: center; text-align: center; padding: 0 14px; } @@ -129,8 +132,14 @@ function ensureStyle(doc) { @param {any} opts */ export function errorState(opts = {}) { const root = opts.root; - const contentOf = () => - [].concat((typeof opts.content === 'function' ? opts.content() : opts.content) || root || []).filter(Boolean); + /* Named content, else everything in the root but the caption. inert is a CSS + filter, which a descendant cannot escape, so fading the root would fade the + failure line with it. */ + const contentOf = () => { + const named = typeof opts.content === 'function' ? opts.content() : opts.content; + if (named) return [].concat(named).filter(Boolean); + return root ? [...root.children].filter(el => el !== cap) : []; + }; const t = typeof opts.t === 'function' ? opts.t : (_k, fallback) => fallback; const doc = root && root.ownerDocument; ensureStyle(doc); diff --git a/public/js/widget-toolbox.js b/public/js/widget-toolbox.js index 39d5fcd..1fa49d6 100644 --- a/public/js/widget-toolbox.js +++ b/public/js/widget-toolbox.js @@ -4,7 +4,7 @@ import { esc, html, setHtml } from '/js/html.js?v=c71f8903'; import { isSafeLinkUrl } from '/js/link-url.js?v=54adb40f'; import { jitter } from '/js/jitter.js?v=4eeef4c9'; -import { errorState as _errorState, errorKind, errorCopy } from '/js/widget-error.js?v=e06eef64'; +import { errorState as _errorState, errorKind, errorCopy } from '/js/widget-error.js?v=27d63b49'; export { esc, html, setHtml }; diff --git a/public/widgets/backup/backup.html b/public/widgets/backup/backup.html index 54faa29..9ae7426 100644 --- a/public/widgets/backup/backup.html +++ b/public/widgets/backup/backup.html @@ -120,7 +120,7 @@