| 1 | /**
|
|---|
| 2 | * Vergelijkt de ax-*-regels van een beheerpagina met partials/admin-styles.ejs.
|
|---|
| 3 | *
|
|---|
| 4 | * node scripts/admin-css.mjs wat zou er gebeuren (leest alleen)
|
|---|
| 5 | * node scripts/admin-css.mjs --doen haalt de dubbele regels weg
|
|---|
| 6 | *
|
|---|
| 7 | * WAAROM EEN ONTLEDING EN GEEN GREP. De eerste poging (16-8) telde
|
|---|
| 8 | * @media-varianten als losse regels en commentaar als selector, en concludeerde
|
|---|
| 9 | * daaruit dat admin-playlists tien "eigen" regels had die het niet had. Een
|
|---|
| 10 | * verkeerd patroon faalt niet, hij liegt. Dit leest de tekens een voor een: hij
|
|---|
| 11 | * kent commentaar, tekenreeksen en geneste blokken, en houdt bij in welke
|
|---|
| 12 | * @media-context een regel staat -- want .ax-track in een media query is een
|
|---|
| 13 | * ANDERE regel dan .ax-track daarbuiten.
|
|---|
| 14 | *
|
|---|
| 15 | * WAT HIJ WEGHAALT: alleen regels die in dezelfde context dezelfde selector en
|
|---|
| 16 | * exact dezelfde verklaringen hebben als de partial. Wijkt er iets af, dan
|
|---|
| 17 | * blijft hij staan en wordt hij gemeld -- dat is een bewuste afwijking van die
|
|---|
| 18 | * pagina en niet aan een script om op te ruimen. De partial hoort BOVENAAN de
|
|---|
| 19 | * pagina te staan, zodat wat blijft staan later komt en dus wint.
|
|---|
| 20 | */
|
|---|
| 21 | import fs from 'node:fs';
|
|---|
| 22 |
|
|---|
| 23 | /** Splitst CSS in blokken. Geen regex: tekens tellen, met besef van context. */
|
|---|
| 24 | function ontleed(css, context = '') {
|
|---|
| 25 | const uit = [];
|
|---|
| 26 | let i = 0, start = 0, diepte = 0, blokStart = -1;
|
|---|
| 27 | const n = css.length;
|
|---|
| 28 | while (i < n) {
|
|---|
| 29 | const c = css[i];
|
|---|
| 30 | // Commentaar overslaan -- ook een { of } daarbinnen telt niet mee.
|
|---|
| 31 | if (c === '/' && css[i + 1] === '*') {
|
|---|
| 32 | const eind = css.indexOf('*/', i + 2);
|
|---|
| 33 | i = eind === -1 ? n : eind + 2;
|
|---|
| 34 | continue;
|
|---|
| 35 | }
|
|---|
| 36 | // Tekenreeksen overslaan (content: "}" bestaat echt).
|
|---|
| 37 | if (c === '"' || c === "'") {
|
|---|
| 38 | const aanhaal = c; i++;
|
|---|
| 39 | while (i < n && css[i] !== aanhaal) { if (css[i] === '\\') i++; i++; }
|
|---|
| 40 | i++;
|
|---|
| 41 | continue;
|
|---|
| 42 | }
|
|---|
| 43 | if (c === '{') {
|
|---|
| 44 | if (diepte === 0) blokStart = i;
|
|---|
| 45 | diepte++; i++; continue;
|
|---|
| 46 | }
|
|---|
| 47 | if (c === '}') {
|
|---|
| 48 | diepte--;
|
|---|
| 49 | if (diepte === 0) {
|
|---|
| 50 | // Commentaar ERAF voor we kijken wat voor blok dit is. Stond dit na de
|
|---|
| 51 | // controle, dan werd `/* Mobiel */ @media (...)` niet als media query
|
|---|
| 52 | // herkend en telde het hele blok als een gewone regel met een
|
|---|
| 53 | // krankzinnige selector -- en de regels erbinnen werden nooit
|
|---|
| 54 | // vergeleken. De eerste versie deed dat, en het viel alleen op omdat de
|
|---|
| 55 | // uitvoer "@media (max-width: 480px)" als selector noemde.
|
|---|
| 56 | const kop = norm(schoon(css.slice(start, blokStart)));
|
|---|
| 57 | const lijf = css.slice(blokStart + 1, i);
|
|---|
| 58 | if (kop.startsWith('@media') || kop.startsWith('@supports')) {
|
|---|
| 59 | // Genest: de binnenkant is zelf weer een regelverzameling. De posities
|
|---|
| 60 | // die daaruit komen zijn RELATIEF aan lijf, dus schuif ze naar deze
|
|---|
| 61 | // tekst op -- anders knipt het weghalen straks de verkeerde bytes,
|
|---|
| 62 | // en dat is het soort fout dat een bestand stilletjes sloopt.
|
|---|
| 63 | const verschuif = blokStart + 1;
|
|---|
| 64 | for (const r of ontleed(lijf, kop)) {
|
|---|
| 65 | uit.push({ ...r, van: r.van + verschuif, tot: r.tot + verschuif });
|
|---|
| 66 | }
|
|---|
| 67 | } else {
|
|---|
| 68 | uit.push({
|
|---|
| 69 | context,
|
|---|
| 70 | selector: kop,
|
|---|
| 71 | verklaringen: normVerklaringen(lijf),
|
|---|
| 72 | van: start, tot: i + 1,
|
|---|
| 73 | });
|
|---|
| 74 | }
|
|---|
| 75 | start = i + 1;
|
|---|
| 76 | }
|
|---|
| 77 | i++; continue;
|
|---|
| 78 | }
|
|---|
| 79 | i++;
|
|---|
| 80 | }
|
|---|
| 81 | return uit;
|
|---|
| 82 | }
|
|---|
| 83 |
|
|---|
| 84 | const norm = (s) => s.replace(/\s+/g, ' ').trim();
|
|---|
| 85 | /** Commentaar voor een selector hoort bij de selector niet. */
|
|---|
| 86 | const schoon = (s) => s.replace(/\/\*[\s\S]*?\*\//g, ' ');
|
|---|
| 87 | /** Verklaringen als gesorteerde verzameling: volgorde binnen een blok telt niet. */
|
|---|
| 88 | function normVerklaringen(lijf) {
|
|---|
| 89 | return schoon(lijf).split(';').map((d) => norm(d)).filter(Boolean).sort().join('; ');
|
|---|
| 90 | }
|
|---|
| 91 |
|
|---|
| 92 | function styleBlok(bestand) {
|
|---|
| 93 | const tekst = fs.readFileSync(bestand, 'utf8');
|
|---|
| 94 | const m = /<style>([\s\S]*?)<\/style>/.exec(tekst);
|
|---|
| 95 | return m ? { tekst, css: m[1], van: m.index + '<style>'.length } : null;
|
|---|
| 96 | }
|
|---|
| 97 |
|
|---|
| 98 | const PARTIAL = 'src/views/partials/admin-styles.ejs';
|
|---|
| 99 | const PAGINAS = process.argv.filter((a) => a.endsWith('.ejs') && a !== PARTIAL);
|
|---|
| 100 | const doen = process.argv.includes('--doen');
|
|---|
| 101 | // --forceer haalt OOK de regels weg die dezelfde selector hebben maar een
|
|---|
| 102 | // andere waarde: de pagina neemt dan de vorm van de partial over. Apart van
|
|---|
| 103 | // --doen omdat het iets anders is -- --doen verplaatst, dit VERANDERT hoe de
|
|---|
| 104 | // pagina eruitziet, en dat hoort een expliciete keuze te zijn.
|
|---|
| 105 | const forceer = process.argv.includes('--forceer');
|
|---|
| 106 |
|
|---|
| 107 | const gedeeld = new Map();
|
|---|
| 108 | for (const r of ontleed(styleBlok(PARTIAL).css)) {
|
|---|
| 109 | gedeeld.set(`${r.context}||${r.selector}`, r.verklaringen);
|
|---|
| 110 | }
|
|---|
| 111 | console.log(`partial: ${gedeeld.size} regels\n`);
|
|---|
| 112 |
|
|---|
| 113 | for (const pad of PAGINAS) {
|
|---|
| 114 | const blok = styleBlok(pad);
|
|---|
| 115 | if (!blok) { console.log(`${pad}: geen style-blok`); continue; }
|
|---|
| 116 | const regels = ontleed(blok.css);
|
|---|
| 117 | const dubbel = [], anders = [], eigen = [];
|
|---|
| 118 | for (const r of regels) {
|
|---|
| 119 | const sleutel = `${r.context}||${r.selector}`;
|
|---|
| 120 | if (!gedeeld.has(sleutel)) eigen.push(r);
|
|---|
| 121 | else if (gedeeld.get(sleutel) === r.verklaringen) dubbel.push(r);
|
|---|
| 122 | else anders.push(r);
|
|---|
| 123 | }
|
|---|
| 124 | console.log(`${pad}`);
|
|---|
| 125 | console.log(` ${dubbel.length} identiek aan de partial -> weg`);
|
|---|
| 126 | console.log(` ${anders.length} zelfde selector, ANDERE waarde -> blijft staan`);
|
|---|
| 127 | for (const r of anders) {
|
|---|
| 128 | console.log(` ${r.context ? r.context + ' ' : ''}${r.selector}`);
|
|---|
| 129 | if (process.argv.includes('--toon')) {
|
|---|
| 130 | const g = gedeeld.get(`${r.context}||${r.selector}`);
|
|---|
| 131 | // Alleen de eigenschappen die ECHT verschillen, anders verzuipt het
|
|---|
| 132 | // verschil in twintig regels die identiek zijn.
|
|---|
| 133 | const kaart = (v) => new Map(v.split('; ').map((d) => [d.split(':')[0].trim(), d]));
|
|---|
| 134 | const [ga, ra] = [kaart(g), kaart(r.verklaringen)];
|
|---|
| 135 | for (const k of new Set([...ga.keys(), ...ra.keys()])) {
|
|---|
| 136 | if (ga.get(k) !== ra.get(k)) console.log(` partial: ${ga.get(k) || '(niets)'}\n pagina : ${ra.get(k) || '(niets)'}`);
|
|---|
| 137 | }
|
|---|
| 138 | }
|
|---|
| 139 | }
|
|---|
| 140 | console.log(` ${eigen.length} alleen op deze pagina -> blijft staan`);
|
|---|
| 141 |
|
|---|
| 142 | const weg = forceer ? [...dubbel, ...anders] : dubbel;
|
|---|
| 143 | if (doen && weg.length) {
|
|---|
| 144 | // Van achter naar voren knippen, anders schuiven de posities.
|
|---|
| 145 | let css = blok.css;
|
|---|
| 146 | for (const r of [...weg].sort((a, b) => b.van - a.van)) {
|
|---|
| 147 | css = css.slice(0, r.van) + css.slice(r.tot);
|
|---|
| 148 | }
|
|---|
| 149 | css = css.replace(/\n{3,}/g, '\n\n');
|
|---|
| 150 | const nieuw = blok.tekst.slice(0, blok.van) + css + blok.tekst.slice(blok.van + blok.css.length);
|
|---|
| 151 | fs.writeFileSync(pad, nieuw);
|
|---|
| 152 | console.log(` geschreven (${weg.length} regels weg${forceer ? ', waarvan ' + anders.length + ' afwijkend' : ''})`);
|
|---|
| 153 | }
|
|---|
| 154 | console.log();
|
|---|
| 155 | }
|
|---|