68 lines
3.8 KiB
JavaScript
68 lines
3.8 KiB
JavaScript
// Reports visible text whose computed font size is off the theme's type scale.
|
|
// Core Redmine uses relative sizes (0.8em, 90%, …) that compound when nested; the theme should
|
|
// leave only M3 scale sizes. Exits 1 if anything is off-scale.
|
|
// Usage: node check-font-sizes.mjs [--verbose]
|
|
import { chromium } from 'playwright';
|
|
|
|
const BASE = process.env.REDMINE_URL ?? 'http://127.0.0.1:3070';
|
|
// M3 type scale in px (label-small 11 … display-large 57)
|
|
const SCALE = [11, 12, 14, 16, 22, 24, 28, 32, 36, 45, 57];
|
|
const pages = [
|
|
'/my/page', '/projects', '/projects/apollo', '/projects/apollo/issues', '/issues/13', '/projects/apollo/issues/new',
|
|
'/projects/apollo/roadmap', '/versions/2', '/projects/apollo/activity', '/projects/apollo/wiki', '/news/1',
|
|
'/boards/1/topics/1', '/projects/apollo/documents', '/projects/apollo/files', '/projects/apollo/time_entries',
|
|
'/projects/apollo/time_entries/report?criteria[]=user&columns=month', '/search?q=login', '/my/account',
|
|
'/admin', '/settings', '/users', '/roles/permissions', '/projects/apollo/issues/calendar',
|
|
'/projects/apollo/repository', '/projects/apollo/repository/portal/revisions', '/projects/apollo/repository/portal/entry/lib/portal.rb',
|
|
'/projects/apollo/repository/portal/annotate/lib/portal.rb', '/projects/apollo/repository/portal/revisions/main/diff',
|
|
'/projects/apollo/wiki/Wiki/history', '/projects/apollo/wiki/Wiki/diff?version=2&version_from=1', '/projects/apollo/settings',
|
|
'/projects/apollo/settings/members', '/projects/apollo/settings/versions', '/projects/apollo/settings/repositories', '/trackers',
|
|
'/issue_statuses', '/custom_fields', '/custom_fields/1/edit', '/enumerations', '/admin/info', '/admin/plugins',
|
|
'/workflows/permissions?role_id=3&tracker_id=1', '/projects/apollo/boards',
|
|
];
|
|
|
|
const browser = await chromium.launch();
|
|
const page = await browser.newPage({ viewport: { width: 1440, height: 900 } });
|
|
await page.goto(`${BASE}/login`);
|
|
await page.fill('#username', 'admin');
|
|
await page.fill('#password', 'admin');
|
|
await page.click('#login-submit');
|
|
await page.waitForURL((url) => !url.pathname.startsWith('/login'));
|
|
|
|
const offenders = new Map(); // "selector @ size" -> pages
|
|
for (const path of pages) {
|
|
await page.goto(`${BASE}${path}`, { waitUntil: 'networkidle' });
|
|
const found = await page.evaluate((scale) => {
|
|
const out = [];
|
|
const describe = (el) => {
|
|
const parts = [];
|
|
for (let e = el; e && e !== document.body && parts.length < 3; e = e.parentElement) {
|
|
parts.unshift(e.id ? `#${e.id}` : e.tagName.toLowerCase() + [...e.classList].slice(0, 2).map((c) => `.${c}`).join(''));
|
|
if (e.id) break;
|
|
}
|
|
return parts.join(' > ');
|
|
};
|
|
for (const el of document.querySelectorAll('body *:not(script):not(style):not(svg *)')) {
|
|
if (el.closest('table.gantt-table, .avatar')) continue; // Gantt is zoomed; avatar initials are graphics
|
|
const hasText = [...el.childNodes].some((n) => n.nodeType === 3 && n.textContent.trim());
|
|
if (!hasText || !el.offsetParent) continue;
|
|
const size = parseFloat(getComputedStyle(el).fontSize);
|
|
if (!scale.some((s) => Math.abs(s - size) < 0.3)) out.push([describe(el), Math.round(size * 10) / 10]);
|
|
}
|
|
return out;
|
|
}, SCALE);
|
|
for (const [sel, size] of found) {
|
|
const key = `${sel} @ ${size}px`;
|
|
if (!offenders.has(key)) offenders.set(key, new Set());
|
|
offenders.get(key).add(path);
|
|
}
|
|
}
|
|
await browser.close();
|
|
|
|
const rows = [...offenders].sort((a, b) => parseFloat(a[0].split('@ ')[1]) - parseFloat(b[0].split('@ ')[1]));
|
|
for (const [key, where] of rows) {
|
|
console.log(`${key} (${where.size} page${where.size > 1 ? 's' : ''}${process.argv.includes('--verbose') ? ': ' + [...where].join(', ') : ''})`);
|
|
}
|
|
console.log(rows.length ? `\n${rows.length} off-scale text styles` : 'All text on the type scale.');
|
|
process.exit(rows.length ? 1 : 0);
|