Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

### Added

- Live Activity can show several labelled values from one poll, each with its

Check warning on line 12 in CHANGELOG.md

View workflow job for this annotation

GitHub Actions / test

entry is 220 characters; keep it to one line: "Live Activity can show several labelled values from one p..."
own name, colour, unit and threshold. The badge shows the first label that
reaches its threshold; the rest open on hover, tap or keyboard focus.

Expand All @@ -22,7 +22,7 @@
- Release images carry an SBOM attestation, verifiable with
`cosign verify-attestation`. See `docs/security.md`.

- Support for Windows High Contrast. The wallpaper and the blurred materials are

Check warning on line 25 in CHANGELOG.md

View workflow job for this annotation

GitHub Actions / test

entry is 179 characters; keep it to one line: "Support for Windows High Contrast. The wallpaper and the ..."
dropped, the current page is marked by shape as well as colour, and badges keep
their status colour.

Expand All @@ -36,7 +36,7 @@
- Badge text is white wherever white is readable, instead of whichever ink
scores higher.

- Badges show the number alone on every layout, so a unit cannot make one wider

Check warning on line 39 in CHANGELOG.md

View workflow job for this annotation

GitHub Actions / test

entry is 167 characters; keep it to one line: "Badges show the number alone on every layout, so a unit c..."
than the icon it marks. The unit stays in the popover and in the badge's
accessible name.

Expand All @@ -54,26 +54,26 @@
- Scripts, stylesheets, translation catalogs and the web manifest no longer
carry a duplicate `Content-Type` header.

- Counted messages now use each language's own plural rules. French and Persian

Check warning on line 57 in CHANGELOG.md

View workflow job for this annotation

GitHub Actions / test

entry runs to 3 sentences; one, or two when the second is an action

Check warning on line 57 in CHANGELOG.md

View workflow job for this annotation

GitHub Actions / test

entry is 199 characters; keep it to one line: "Counted messages now use each language's own plural rules..."
put a count of zero in the plural form. The import confirmation and the badge
label limit were still counting in English.

- The Pull Requests widget now translates its caption, and the clock takes its
day and month names from the reader's locale. Both were always English.

- Persian now uses the import, export, widget and file terms that shipped

Check warning on line 64 in CHANGELOG.md

View workflow job for this annotation

GitHub Actions / test

entry is 165 characters; keep it to one line: "Persian now uses the import, export, widget and file term..."
software uses, one word per concept, and no longer leaves "Import" in English
in one message.

- Settings now translates its accessible names, its remaining field labels and
its page title, which were English in every language.

- German, Spanish, French, Chinese and Persian now use one agreed word per

Check warning on line 71 in CHANGELOG.md

View workflow job for this annotation

GitHub Actions / test

entry is 177 characters; keep it to one line: "German, Spanish, French, Chinese and Persian now use one ..."
concept, and no longer leave "Logging", "API key", "Collection ID" or
"Authentication Proxy" in English.

- A tile label is now cut at the end of its own name. On a right-to-left
dashboard a Latin name lost its beginning instead.
- A tile label is now cut at the end of its own name in Safari too. On a
right-to-left dashboard a Latin name lost its beginning instead.

- Opening and saving an app no longer rewrites a colour that was not changed.

Expand All @@ -100,7 +100,7 @@
- The badge popover can be hovered without closing, and no longer truncates the
label names it exists to show.

- The page indicator now takes its tint from the wallpaper behind it, and marks

Check warning on line 103 in CHANGELOG.md

View workflow job for this annotation

GitHub Actions / test

entry is 180 characters; keep it to one line: "The page indicator now takes its tint from the wallpaper ..."
the current page by shape as well as colour. Over a light wallpaper the whole
indicator was invisible.

Expand All @@ -123,7 +123,7 @@
- In Settings, the logging note now sits with the row it describes, and the host
address note says what it does rather than naming the mechanism.

- Numbers on the dashboard now use the digits of the reader's locale. A Persian

Check warning on line 126 in CHANGELOG.md

View workflow job for this annotation

GitHub Actions / test

entry is 270 characters; keep it to one line: "Numbers on the dashboard now use the digits of the reader..."
reader whose device asks for Persian digits sees them in badges, the weather,
the system summary, disk health, Pi-hole and reading progress; one whose device
asks for Latin digits keeps those.
Expand All @@ -139,7 +139,7 @@
- Save is the same button on every Settings pane. It was green on two panes and
teal on a third, for one action.

- In right-to-left languages a badge now sits on the leading corner of its tile,

Check warning on line 142 in CHANGELOG.md

View workflow job for this annotation

GitHub Actions / test

entry is 211 characters; keep it to one line: "In right-to-left languages a badge now sits on the leadin..."
the badge popover follows it, a Settings row's title and subtitle share an
edge, and the Now Playing arrows point the way they read.

Expand Down
2 changes: 1 addition & 1 deletion ui/admin/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,6 @@ <h1 class="sec-title" id="ev-title">General</h1>
</div></div>
</div>

<script type="module" src="/js/admin.js?v=8f8d0f89"></script>
<script type="module" src="/js/admin.js?v=60c0ec1e"></script>
</body>
</html>
12 changes: 4 additions & 8 deletions ui/css/dashboard.css
Original file line number Diff line number Diff line change
Expand Up @@ -215,14 +215,10 @@
}

/* ── Icon label ── */
/* A name is user text, so its language need not be the interface language.
plaintext takes the direction from the name itself, so the ellipsis falls
at the name's own end. These labels are centred, so it moves no text: dir
on the block would, and drags a row to the other edge. */
.ilabel {
font-size:var(--fs-footnote);line-height:var(--lh-footnote);letter-spacing:var(--tr-footnote); font-weight:500; color:var(--label-color);
text-align:center; text-shadow:var(--label-shadow); margin-top:6px;
white-space:nowrap; overflow:hidden; text-overflow:ellipsis; unicode-bidi:plaintext;
white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}

/* Labels measured against a light background. The tone is set on the
Expand Down Expand Up @@ -740,8 +736,8 @@
.dyn-grid { display:grid; grid-template-columns:repeat(6,var(--iw)); gap:24px; justify-content:center; }
.dyn-mob-btn { flex:1; height:var(--rh); background:none; border:none; padding:0; display:flex; flex-direction:column; align-items:center; justify-content:center; cursor:pointer; flex-shrink:0; -webkit-tap-highlight-color:transparent; -webkit-appearance:none; }
.dyn-mob-icon { display:flex; flex-direction:column; align-items:center; justify-content:center; width:var(--cw); height:var(--rh); cursor:pointer; text-decoration:none; flex-shrink:0; -webkit-tap-highlight-color:transparent; }
.dyn-mob-label { font-weight:500; color:rgba(255,255,255,.88); text-align:center; margin-top:2px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; unicode-bidi:plaintext; text-shadow:0 1px 3px rgba(0,0,0,.65); font-size:var(--lfs); width:var(--lw); }
.dyn-fold-label { font-weight:500; color:rgba(255,255,255,.90); text-align:center; margin-top:3px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; unicode-bidi:plaintext; text-shadow:0 1px 3px rgba(0,0,0,.70); font-size:var(--lfs); width:var(--lw); }
.dyn-mob-label { font-weight:500; color:rgba(255,255,255,.88); text-align:center; margin-top:2px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; text-shadow:0 1px 3px rgba(0,0,0,.65); font-size:var(--lfs); width:var(--lw); }
.dyn-fold-label { font-weight:500; color:rgba(255,255,255,.90); text-align:center; margin-top:3px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; text-shadow:0 1px 3px rgba(0,0,0,.70); font-size:var(--lfs); width:var(--lw); }
.dyn-fold-wrap { position:absolute; inset:0; pointer-events:none; border-radius:var(--br); background:rgba(120,130,160,.35); display:grid; grid-template-columns:repeat(3,1fr); grid-template-rows:repeat(3,1fr); gap:var(--gap); padding:var(--pad); overflow:hidden; -webkit-backdrop-filter:blur(7px); backdrop-filter:blur(7px); box-shadow:inset 1px 1px 0 rgba(255,255,255,.22),inset -1px -1px 0 rgba(0,0,0,.12); }
.dyn-fold-cell { display:flex; align-items:center; justify-content:center; z-index:1; pointer-events:none; min-width:0; min-height:0; }
.dyn-fold-sheen { position:absolute; inset:0; pointer-events:none; z-index:2; border-radius:inherit; background:linear-gradient(135deg,rgba(255,255,255,.12) 0%,transparent 55%); }
Expand All @@ -755,7 +751,7 @@
.folder-overlay-mobile:focus, .dyn-fold-anchor:focus,
.folder-overlay:focus, .folder-icon-link:focus { outline:none; }
.dyn-fold-anchor { display:flex; flex-direction:column; align-items:center; gap:4px; text-decoration:none; -webkit-tap-highlight-color:transparent; }
.dyn-fold-inner-label { font-weight:400; color:rgba(255,255,255,.90); text-align:center; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; unicode-bidi:plaintext; text-shadow:0 1px 3px rgba(0,0,0,.6); font-size:var(--lfs); width:var(--fiw); }
.dyn-fold-inner-label { font-weight:400; color:rgba(255,255,255,.90); text-align:center; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; text-shadow:0 1px 3px rgba(0,0,0,.6); font-size:var(--lfs); width:var(--fiw); }
.dyn-title-mob { position:absolute; pointer-events:none; font-size:var(--tfs); }
.dyn-box-mob { position:absolute; display:flex; flex-direction:column; align-items:center; left:var(--left); width:var(--bw); height:var(--bh); top:var(--top); border-radius:var(--br); padding:var(--pt) var(--ph) var(--pb); }
.dyn-dock-icon { display:flex; align-items:center; justify-content:center; flex:1; height:100%; cursor:pointer; text-decoration:none; -webkit-tap-highlight-color:transparent; overflow:visible; position:relative; }
Expand Down
4 changes: 2 additions & 2 deletions ui/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

<title>Stackyard</title>
<link rel="stylesheet" href="/css/tokens.css?v=5fc1ecde">
<link rel="stylesheet" href="/css/dashboard.css?v=c8e14ace">
<link rel="stylesheet" href="/css/dashboard.css?v=1ec545fd">
</head>
<body>
<header class="visually-hidden"><h1>Stackyard</h1></header>
Expand Down Expand Up @@ -53,5 +53,5 @@
</div>
</div>
</div>
<script type="module" src="/js/dashboard.js?v=40c606df"></script>
<script type="module" src="/js/dashboard.js?v=d0047d7b"></script>
</body></html>
6 changes: 3 additions & 3 deletions ui/js/admin-app-form.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { clr as rc, el, inp as inpById, q as qSel, qa, qi, tgt } from '/js/utils.js?v=26566e09';
import { clr as rc, el, inp as inpById, q as qSel, qa, qi, tgt } from '/js/utils.js?v=b9a5b134';
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=c23e6346';
Expand All @@ -13,9 +13,9 @@ import {
initInlineEdit,
setTogDisabled,
wireChecklist,
} from '/js/admin-shared.js?v=e4464b78';
} from '/js/admin-shared.js?v=6ca8f3dd';
import { MAX_LABELS } from '/js/badge-logic.js?v=41a929ac';
import { renderColorControl, BADGE_SWATCHES, BADGE_DEFAULT } from '/js/admin-color-control.js?v=49b9c909';
import { renderColorControl, BADGE_SWATCHES, BADGE_DEFAULT } from '/js/admin-color-control.js?v=ff330e63';
import { badgeErrorAdvice, TONE } from '/js/admin-error.js?v=10f3cdb1';

export function buildFolderForm(body, item) {
Expand Down
4 changes: 2 additions & 2 deletions ui/js/admin-auth.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ag, ap } from '/js/admin-shared.js?v=e4464b78';
import { ag, ap } from '/js/admin-shared.js?v=6ca8f3dd';
import { t } from '/js/i18n.js?v=83239bf4';
import { pwStrength } from '/js/password-strength.js?v=42f45ac7';
import { el, inp as inpById, qa } from '/js/utils.js?v=26566e09';
import { el, inp as inpById, qa } from '/js/utils.js?v=b9a5b134';

export async function checkAuth(onLogin) {
try {
Expand Down
4 changes: 2 additions & 2 deletions ui/js/admin-color-control.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { PE_SVG, initInlineEdit, toast } from '/js/admin-shared.js?v=e4464b78';
import { PE_SVG, initInlineEdit, toast } from '/js/admin-shared.js?v=6ca8f3dd';
import { t } from '/js/i18n.js?v=83239bf4';
import { html, raw, setHtml } from '/js/html.js?v=c71f8903';
import { qa, q } from '/js/utils.js?v=26566e09';
import { qa, q } from '/js/utils.js?v=b9a5b134';

const CC_SWATCHES = ['#1c1c1e', '#8e8e93', '#f2f2f7', '#ff393c', '#ffcd00', '#35c759', '#0289ff', '#cb30df'];
export const BADGE_DEFAULT = '#1e6ef4';
Expand Down
4 changes: 2 additions & 2 deletions ui/js/admin-settings.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { toast, ag, ap } from '/js/admin-shared.js?v=e4464b78';
import { toast, ag, ap } from '/js/admin-shared.js?v=6ca8f3dd';
import { pwStrength } from '/js/password-strength.js?v=42f45ac7';
import { t } from '/js/i18n.js?v=83239bf4';
import { shouldWritePassword, settingsSaveBlocker, clearsStoredPassword, BLOCK } from '/js/admin-logic.js?v=d17394da';
import { el, inp, q, qa, setUserText } from '/js/utils.js?v=26566e09';
import { el, inp, q, qa, setUserText } from '/js/utils.js?v=b9a5b134';

/* Mirrors the server's rule: auth cannot be switched on with no password. */
let _passwordSet = false;
Expand Down
2 changes: 1 addition & 1 deletion ui/js/admin-shared.js
Original file line number Diff line number Diff line change
@@ -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=d17394da';
import { el, qa, q } from '/js/utils.js?v=26566e09';
import { el, qa, q } from '/js/utils.js?v=b9a5b134';
import { t } from '/js/i18n.js?v=83239bf4';

export const API = '';
Expand Down
6 changes: 3 additions & 3 deletions ui/js/admin-widget-form.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { state } from '/js/admin-state.js?v=c23e6346';
import { PE_SVG, CHEV_SVG, initInlineEdit } from '/js/admin-shared.js?v=e4464b78';
import { renderWidgetConfigForm } from '/js/widget-config-form.js?v=adbc3f62';
import { PE_SVG, CHEV_SVG, initInlineEdit } from '/js/admin-shared.js?v=6ca8f3dd';
import { renderWidgetConfigForm } from '/js/widget-config-form.js?v=0d82ab23';
import { html, raw, setHtml } from '/js/html.js?v=c71f8903';
import { sizesForView, widgetConfigMode, rejectionLines, carriesTypedValues } from '/js/admin-logic.js?v=d17394da';
import { t } from '/js/i18n.js?v=83239bf4';
import { q, qi, qa } from '/js/utils.js?v=26566e09';
import { q, qi, qa } from '/js/utils.js?v=b9a5b134';

const SIZE_ICONS = {
small:
Expand Down
14 changes: 7 additions & 7 deletions ui/js/admin.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { buildAppForm, buildFolderForm, captureActLabels, serializeKvRows } from '/js/admin-app-form.js?v=6aa6302b';
import { checkAuth, requireLogin, wirePasswordStrength } from '/js/admin-auth.js?v=d19a6fdf';
import { buildAppForm, buildFolderForm, captureActLabels, serializeKvRows } from '/js/admin-app-form.js?v=ab1a8e24';
import { checkAuth, requireLogin, wirePasswordStrength } from '/js/admin-auth.js?v=245d301f';
import { applyDrop, canJoinFolder, folderRowZone } from '/js/admin-drag-logic.js?v=ebe3e806';
import { reorderItems, resolveAdminSection } from '/js/admin-logic.js?v=d17394da';
import {
Expand All @@ -10,10 +10,10 @@ import {
snapshotItems,
upsertItem,
} from '/js/admin-save-logic.js?v=48a9e055';
import { loadSettings, showBgFields, showBgFit, showWallpaperFile } from '/js/admin-settings.js?v=4638d447';
import { ag, ap, initInlineEdit, setReauthHandler, toast } from '/js/admin-shared.js?v=e4464b78';
import { loadSettings, showBgFields, showBgFit, showWallpaperFile } from '/js/admin-settings.js?v=4aead8ca';
import { ag, ap, initInlineEdit, setReauthHandler, toast } from '/js/admin-shared.js?v=6ca8f3dd';
import { state } from '/js/admin-state.js?v=c23e6346';
import { buildWidgetForm } from '/js/admin-widget-form.js?v=892f9ffe';
import { buildWidgetForm } from '/js/admin-widget-form.js?v=1033c33a';
import { html, raw, setHtml } from '/js/html.js?v=c71f8903';
import { initI18n, LANGUAGES, t } from '/js/i18n.js?v=83239bf4';
import { iconChain, loadLocalIcons, resolveIcon } from '/js/icons.js?v=69c2b9bd';
Expand All @@ -29,9 +29,9 @@ import {
import { isMobileLayout, onLayoutChange } from '/js/layout.js?v=28416a75';
import { confirmModal, openModal as openDialog, promptModal } from '/js/modal.js?v=ff76dc56';
import { readMode, watchSystemTheme, writeMode } from '/js/theme.js?v=db4192cd';
import { el, inp, q, qa, clr as rc, sanitizeCssUrl, setUserText, tgt } from '/js/utils.js?v=26566e09';
import { el, inp, q, qa, clr as rc, sanitizeCssUrl, setUserText, tgt } from '/js/utils.js?v=b9a5b134';
import { widgetGlyph } from '/js/widget-glyphs.js?v=b5036986';
import { normalizeColorInput } from '/js/admin-color-control.js?v=49b9c909';
import { normalizeColorInput } from '/js/admin-color-control.js?v=ff330e63';
import { parseYamlTolerant, YamlLiteError } from '/js/yaml-lite.js?v=cceca788';

/* A class rather than a bare media query. Some phones report a wider CSS
Expand Down
7 changes: 4 additions & 3 deletions ui/js/dashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@ import {
setUserText,
teardownWidgets,
titleWhenTruncated,
} from '/js/utils.js?v=26566e09';
import { initSpotlight } from '/js/spotlight.js?v=71c04e24';
} from '/js/utils.js?v=b9a5b134';
import { initSpotlight } from '/js/spotlight.js?v=00545746';
import { html, setHtml, raw } from '/js/html.js?v=c71f8903';
import { initI18n, t, currentLang } from '/js/i18n.js?v=83239bf4';
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=8390a5b8';
import { initUI, mkFolder, openFolderDesktop, openFolderMobile, buildMobile } from '/js/ui.js?v=b400beaf';
import { badgeMinimum, badgeSignature, computeBadgeVisual, readBadgeUpdate } from '/js/badge-logic.js?v=41a929ac';
import { formatNumber } from '/js/format-number.js?v=e2165e12';
import { closeBadgePopover, wireBadgePopover } from '/js/badge-popover.js?v=08aae50f';
Expand Down Expand Up @@ -309,6 +309,7 @@ function mkIcon(item) {
if (showLabel) {
const l = mk('div');
l.className = 'ilabel';
l.dir = 'auto';
l.style.width = iw + 12 + 'px';
setUserText(l, item.label || item.id);
a.appendChild(l);
Expand Down
2 changes: 1 addition & 1 deletion ui/js/spotlight.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { mk, clr, el, inp as inpById, q, qa, setUserText } from '/js/utils.js?v=26566e09';
import { mk, clr, el, inp as inpById, q, qa, setUserText } from '/js/utils.js?v=b9a5b134';
import { wrapTab } from '/js/dialog.js?v=05935547';
import { t } from '/js/i18n.js?v=83239bf4';

Expand Down
7 changes: 6 additions & 1 deletion ui/js/ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
q,
qa,
setUserText,
} from '/js/utils.js?v=26566e09';
} from '/js/utils.js?v=b9a5b134';
import { t, currentLang } from '/js/i18n.js?v=83239bf4';
import { trapFocus } from '/js/dialog.js?v=05935547';
import { toneForColor } from '/js/label-contrast.js?v=38adb276';
Expand Down Expand Up @@ -139,6 +139,7 @@ export function mkFolder(item) {
if (showLabel) {
const l = mk('div');
l.className = 'ilabel';
l.dir = 'auto';
l.style.width = iw + 12 + 'px';
setUserText(l, item.label || t('type.folder'));
a.appendChild(l);
Expand Down Expand Up @@ -188,6 +189,7 @@ export function openFolderDesktop(folder) {
if (showLabel) {
const l = mk('div');
l.className = 'ilabel';
l.dir = 'auto';
l.style.width = iw + 12 + 'px';
setUserText(l, child.label || child.id);
a.appendChild(l);
Expand Down Expand Up @@ -316,6 +318,7 @@ function mFolder(item, cw, rh, isz, ir, im, sc) {
if (showLabel) {
const l = mk('div');
l.className = 'dyn-fold-label';
l.dir = 'auto';
css(l, { '--lfs': Math.max(9, Math.round(9 * sc)) + 'px', '--lw': cw - 4 + 'px' });
setUserText(l, item.label || t('type.folder'));
a.appendChild(l);
Expand Down Expand Up @@ -452,6 +455,7 @@ export function openFolderMobile(folder, isz, _ir, _im, sc) {
if (showLabel) {
const l = mk('div');
l.className = 'dyn-fold-inner-label';
l.dir = 'auto';
css(l, { '--lfs': Math.max(11, Math.round(11 * ptScale)) + 'px', '--fiw': folderIconW + 'px' });
setUserText(l, child.label || child.id);
a.appendChild(l);
Expand Down Expand Up @@ -695,6 +699,7 @@ export function buildMobile() {
if (showLabel) {
const l = mk('div');
l.className = 'dyn-mob-label';
l.dir = 'auto';
css(l, { '--lfs': Math.max(9, Math.round(9 * sc)) + 'px', '--lw': '100%' });
setUserText(l, item.label || item.id);
a.appendChild(l);
Expand Down
4 changes: 4 additions & 0 deletions ui/js/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ export { esc } from '/js/html.js?v=c71f8903';
alignment as well as bidi, so a Latin name on the block dragged its whole row
to the other edge: a title and its subtitle ended up on opposite sides.

A centred label sets `dir` on the block as well, because alignment cannot move
centred text. Without it the block's direction decides which end is cut, and
WebKit ignores unicode-bidi:plaintext for that.

@param {HTMLElement} node @param {string} text @returns {HTMLElement} */
export const setUserText = (node, text) => {
node.textContent = '';
Expand Down
6 changes: 3 additions & 3 deletions ui/js/widget-config-form.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

import { t } from '/js/i18n.js?v=83239bf4';
import { html, raw, setHtml } from '/js/html.js?v=c71f8903';
import { wireChecklist } from '/js/admin-shared.js?v=e4464b78';
import { renderColorControl } from '/js/admin-color-control.js?v=49b9c909';
import { wireChecklist } from '/js/admin-shared.js?v=6ca8f3dd';
import { renderColorControl } from '/js/admin-color-control.js?v=ff330e63';
import {
seedCarried,
applyOptionSet,
Expand All @@ -15,7 +15,7 @@ import {
visibleFieldFlags,
} from '/js/admin-logic.js?v=d17394da';
import { optionsErrorAdvice, TONE } from '/js/admin-error.js?v=10f3cdb1';
import { qi } from '/js/utils.js?v=26566e09';
import { qi } from '/js/utils.js?v=b9a5b134';

const PE =
'<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M11 4H6a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-5"/><path d="M18.4 2.6a1.85 1.85 0 0 1 2.6 2.6l-9.1 9.1-3.4 1 1-3.4z"/></svg>';
Expand Down
31 changes: 23 additions & 8 deletions ui/test/dashboard-localisation.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -196,20 +196,35 @@ test('the first-run prompt and API error are translated', () => {
});

/* An app's name is user text, so its language can differ from the interface
language. Without this the block's direction decides where the line is cut,
and a Latin name on a right-to-left dashboard lost its beginning: "Backup and
Storage" read "...nd Storage".
language. Without a direction of its own the block's direction decides which
end is cut, and a Latin name on a right-to-left dashboard lost its beginning:
"Backup and Storage" read "...ckup and St".

unicode-bidi:plaintext fixes it in Blink and does nothing in WebKit, so the
attribute is what the label carries. It is safe here only because these labels
are centred: dir sets alignment too, which moves a name off its own icon. */
test('every tile label carries its own direction', () => {
const ui = read('js/ui.js');
const dashboard = read('js/dashboard.js');
for (const [src, name] of [
[ui, 'ui.js'],
[dashboard, 'dashboard.js'],
]) {
for (const m of src.matchAll(
/className = '(ilabel|dyn-mob-label|dyn-fold-label|dyn-fold-inner-label)';\n(\s*)(.*)/g,
)) {
assert.match(m[3], /^l\.dir = 'auto';$/, `${name}: .${m[1]} is cut by the page direction`);
}
}
});

dir on the block would set alignment too, which moves a name away from its own
icon. These labels are centred, so plaintext moves nothing. */
test('every tile label is cut at the end of its own name, and stays centred', () => {
test('the labels the direction is set on are centred', () => {
const css = read('css/dashboard.css');
for (const cls of ['ilabel', 'dyn-mob-label', 'dyn-fold-label', 'dyn-fold-inner-label']) {
const rule = [...css.matchAll(/([^{}]+)\{([^{}]*)\}/g)].find(
m => new RegExp(`\\.${cls}\\s*[,{]|\\.${cls}$`).test(m[1].trim()) && m[2].includes('text-overflow'),
);
assert.ok(rule, `no truncating rule for .${cls}`);
assert.match(rule[2], /unicode-bidi:plaintext/, `.${cls} is cut by the page direction`);
assert.match(rule[2], /text-align:center/, `.${cls} is not centred`);
assert.match(rule[2], /text-align:center/, `.${cls} is not centred, so dir would move it`);
}
});