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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ The format follows [Keep a Changelog] (https://keepachangelog.com/en/1.1.0/).
- **The device test diagnoses more, and says how to fix what fails** (#2054). New rows: local storage, the ALTCHA captcha's needs, the device ID the device limit reads (a short code, signals read, whether they hold between reads, read-only), the clock against the server's, time zone and language, screen, connection and theme. A failing row opens how to fix it; a check still running counts down from 20 seconds.
- **The device test names a blocker and gives a verdict** (#2062). New row: whether a request reaches the server, with its time. The device ID and the captcha say when a blocker stopped them; a CSS-timed notice shows when the test's own scripts never ran. A summary sits above the rows, location is one row, the PDF comes last, and the copy carries version and setup.
- **The device test talks to the plugin, under a per-address limit** (#2064). One request now feeds the server and clock rows, and names a page served from an old cache. Settings → Rate Limit caps runs per address (default 60 per 10 minutes; 0 off), past which the row warns with the wait. The ALTCHA cap shares the counter.
- **The device test groups its rows in sections and asks for the location step** (#2065). Six native `<details>` sections stay closed while they pass, with a chip counting their rows, and open by themselves on a warning or a failure. The test is finished once the visitor reads the location, whose button now sits in its section.

### Fixed

Expand Down
115 changes: 115 additions & 0 deletions assets/css/ffc-device-check.css
Original file line number Diff line number Diff line change
Expand Up @@ -142,3 +142,118 @@
color: var(--ffc-text);
font-size: var(--ffc-font-size-sm);
}

/* The sections (#2065): native <details>, so they open and close without
the script. The script colours the chip from the rows and marks the
section's state; a section that fails takes the danger border. */
.ffc-device-check .ffc-device-check-groups {
display: flex;
flex-direction: column;
gap: var(--ffc-spacing-xs);
margin: 0 0 var(--ffc-spacing-lg);
}

.ffc-device-check .ffc-device-check-group {
border: 1px solid var(--ffc-border);
border-radius: var(--ffc-radius-md);
background: var(--ffc-bg-card);
}

.ffc-device-check .ffc-device-check-group[data-ffc-state="fail"] {
border-color: var(--ffc-danger-border);
}

.ffc-device-check .ffc-device-check-group[data-ffc-state="warn"] {
border-color: var(--ffc-warning-border);
}

.ffc-device-check .ffc-device-check-summary {
display: flex;
align-items: center;
gap: var(--ffc-spacing-sm);
min-height: 44px;
padding: var(--ffc-spacing-sm) var(--ffc-spacing-md);
box-sizing: border-box;
cursor: pointer;
list-style: none;
}

.ffc-device-check .ffc-device-check-summary::-webkit-details-marker {
display: none;
}

.ffc-device-check .ffc-device-check-summary:focus-visible {
outline: 2px solid var(--ffc-border-focus);
outline-offset: 2px;
border-radius: var(--ffc-radius-md);
}

.ffc-device-check .ffc-device-check-group-name {
display: flex;
flex-grow: 1;
align-items: center;
gap: var(--ffc-spacing-xs);
font-weight: 600;
}

.ffc-device-check .ffc-device-check-chip {
padding: var(--ffc-spacing-3xs) var(--ffc-spacing-sm);
border-radius: var(--ffc-radius-pill);
font-size: var(--ffc-font-size-xs);
font-weight: 600;
white-space: nowrap;
}

.ffc-device-check .ffc-device-check-chip.is-ok {
background: var(--ffc-success-bg);
color: var(--ffc-success-text);
}

.ffc-device-check .ffc-device-check-chip.is-warn {
background: var(--ffc-warning-bg);
color: var(--ffc-warning-text);
}

.ffc-device-check .ffc-device-check-chip.is-fail {
background: var(--ffc-danger-bg);
color: var(--ffc-danger-text);
}

.ffc-device-check .ffc-device-check-chip.is-info {
background: var(--ffc-info-bg);
color: var(--ffc-info-text);
}

.ffc-device-check .ffc-device-check-chevron {
display: flex;
color: var(--ffc-text-muted);
}

.ffc-device-check .ffc-device-check-group[open] > .ffc-device-check-summary .ffc-device-check-chevron {
transform: rotate(180deg);
}

.ffc-device-check .ffc-device-check-group .ffc-device-check-list {
margin: 0;
padding: 0 var(--ffc-spacing-md);
}

.ffc-device-check .ffc-device-check-group .ffc-device-check-row {
border-top: 1px solid var(--ffc-border);
border-bottom: 0;
}

.ffc-device-check .ffc-device-check-group-actions {
padding: 0 var(--ffc-spacing-md) var(--ffc-spacing-md);
}

@media (forced-colors: active) {
/* The chips: the text still says the state; the border gives them
their badge shape back when the system replaces the colours. */
.ffc-device-check .ffc-device-check-chip.is-ok,
.ffc-device-check .ffc-device-check-chip.is-warn,
.ffc-device-check .ffc-device-check-chip.is-fail,
.ffc-device-check .ffc-device-check-chip.is-info {
border: 1px solid CanvasText;
}
}
2 changes: 1 addition & 1 deletion assets/css/ffc-device-check.min.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

105 changes: 95 additions & 10 deletions assets/js/ffc-device-check.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,31 +62,38 @@
* no answer yet, then the worst state any row reached.
*/
function verdict() {
paintGroups();
var box = document.querySelector('.ffc-device-check-verdict');
var ns = window.FFC || {};
if (!box || typeof ns.notice !== 'function') {
return;
}
var worst = -1;
var todo = false;
var rows = document.querySelectorAll('.ffc-device-check-row');
for (var i = 0; i < rows.length; i++) {
if (!rows[i].hasAttribute('data-ffc-checked') || countdowns[rows[i].getAttribute('data-ffc-check')]) {
if (rowState(rows[i]) === 'checking') {
worst = -1;
break;
}
['info', 'ok', 'warn', 'fail'].forEach(function (state) {
if (rows[i].classList.contains('is-' + state)) {
worst = Math.max(worst, RANK[state]);
}
});
worst = Math.max(worst, RANK[rowState(rows[i])]);
todo = todo || rows[i].hasAttribute('data-ffc-todo');
}
// A step the visitor still owes (the location reading) comes before
// any warning, but a failure is told first (#2065).
var key = 'checking';
if (worst === RANK.fail) {
key = 'fail';
} else if (worst >= 0) {
key = todo ? 'todo' : (worst === RANK.warn ? 'warn' : 'ok');
}
var key = worst < 0 ? 'checking' : (worst === RANK.fail ? 'fail' : (worst === RANK.warn ? 'warn' : 'ok'));
if (key === drawn) {
return;
}
drawn = key;
var notice = {
checking: ['info', 'sync', text('verdictChecking', 'Running the checks…'), ''],
todo: ['info', 'map-pin', text('verdictTodo', 'Press "Test my location" under Device settings to finish the test.'), text('verdictTodoTitle', 'One step left')],
ok: ['success', 'success', text('verdictOk', 'Every check passed: the forms should work here.'), text('verdictOkTitle', 'This device is ready')],
warn: ['warning', 'warning', text('verdictWarn', 'The forms should work, but the items in yellow may get in the way. Open "How to fix it" on each one.'), text('verdictWarnTitle', 'Ready, with warnings')],
fail: ['danger', 'error', text('verdictFail', 'Fix the items in red: open "How to fix it" on each one, then reload this page.'), text('verdictFailTitle', 'The forms may not work here')]
Expand All @@ -96,6 +103,71 @@
box.setAttribute('data-ffc-verdict', key);
}

/**
* A row's state for the verdict and the sections: `checking` until it
* has an answer that is not a countdown, then its colour.
*
* @param {HTMLElement} el The row.
* @returns {string} `checking` | `info` | `ok` | `warn` | `fail`.
*/
function rowState(el) {
if (!el.hasAttribute('data-ffc-checked') || countdowns[el.getAttribute('data-ffc-check')]) {
return 'checking';
}
var states = ['fail', 'warn', 'ok', 'info'];
for (var i = 0; i < states.length; i++) {
if (el.classList.contains('is-' + states[i])) {
return states[i];
}
}
return 'info';
}

/**
* Each section's chip, and its opening (#2065): a section opens by
* itself when a row in it warns, fails or waits on the visitor, and is
* never closed again by the script, so the screen does not move under
* someone reading it.
*/
function paintGroups() {
var groups = document.querySelectorAll('.ffc-device-check-group');
for (var g = 0; g < groups.length; g++) {
var count = { checking: 0, info: 0, ok: 0, warn: 0, fail: 0 };
var todo = 0;
var rows = groups[g].querySelectorAll('.ffc-device-check-row');
for (var i = 0; i < rows.length; i++) {
count[rowState(rows[i])]++;
todo += rows[i].hasAttribute('data-ffc-todo') ? 1 : 0;
}
var chip = groups[g].querySelector('.ffc-device-check-chip');
var state = 'ok';
var label = text('chipOk', '%s OK').replace('%s', String(count.ok + count.info));
if (count.fail) {
state = 'fail';
label = 1 === count.fail ? text('chipFailOne', '1 failure') : text('chipFailMany', '%s failures').replace('%s', String(count.fail));
} else if (count.warn) {
state = 'warn';
label = 1 === count.warn ? text('chipWarnOne', '1 warning') : text('chipWarnMany', '%s warnings').replace('%s', String(count.warn));
} else if (count.checking) {
state = 'info';
label = text('chipChecking', 'Checking…');
} else if (todo) {
state = 'info';
label = text('chipTodo', 'Test pending');
}
if (chip) {
chip.classList.remove('is-ok', 'is-warn', 'is-fail', 'is-info');
chip.classList.add('is-' + state);
chip.textContent = label;
}
groups[g].setAttribute('data-ffc-state', state);
if (count.fail || count.warn || todo) {
groups[g].open = true;
}
}
}


/** How long a check may run before its row says it got no answer. */
var PENDING_SECONDS = 20;

Expand Down Expand Up @@ -759,9 +831,22 @@
var perm = geo.perm;
var acc = geo.acc;
var worst = acc && RANK[acc.state] > RANK[perm.state] ? acc : perm;
set('location', worst.state, acc
? fill(text('locationWithAccuracy', '%1$s · %2$s'), [perm.value, acc.value])
: perm.value, worst.help);
// Until the visitor reads the location, the test is not finished
// (#2065): the row says so and the verdict asks for the step.
var owed = !acc && !!navigator.geolocation;
var el = row('location');
if (el) {
if (owed) {
el.setAttribute('data-ffc-todo', '1');
} else {
el.removeAttribute('data-ffc-todo');
}
}
var value = perm.value;
if (acc || owed) {
value = fill(text('locationWithAccuracy', '%1$s · %2$s'), [perm.value, acc ? acc.value : text('locationTodo', 'precision not tested yet')]);
}
set('location', worst.state, value, worst.help);
}

function noGeolocation() {
Expand Down
2 changes: 1 addition & 1 deletion assets/js/ffc-device-check.min.js

Large diffs are not rendered by default.

Loading
Loading