@massimo-cassandro/minimo 1.39.1 → 1.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. package/dev-tools/create-favicons/src/default-params.mjs +1 -1
  2. package/dev-tools/find-orphan-css-classes/find-orphan-css-classes.mjs +451 -0
  3. package/dev-tools/find-orphan-css-classes/orphan-css-classes.config.mjs +44 -0
  4. package/dev-tools/starter-kit/source_files/app/error-pages/error-pages.js +38 -0
  5. package/dev-tools/starter-kit/source_files/root/__project__.code-workspace +4 -0
  6. package/dev-tools/starter-kit/source_files/root/_editorconfig +10 -1
  7. package/dev-tools/starter-kit/source_files/root/_sf/symfony-webpack-setup.md +1 -1
  8. package/dev-tools/starter-kit/source_files/root/_sf/templates/_main-tpl.html.twig +1 -1
  9. package/dev-tools/starter-kit/source_files/root/_sf/templates/bundles/TwigBundle/Exception/error.html.twig +3 -3
  10. package/dev-tools/starter-kit/source_files/root/package-tpl.json +7 -3
  11. package/dev-tools/starter-kit/starter-install.sh +10 -9
  12. package/package.json +4 -3
  13. package/src/minimo.css +1 -4
  14. package/types/classes.d.ts +0 -525
  15. package/dev-tools/starter-kit/source_files/app/api/.gitkeep +0 -0
  16. package/dev-tools/starter-kit/source_files/app/src/components/.gitkeep +0 -0
  17. package/dev-tools/starter-kit/source_files/app/src/js/.gitkeep +0 -0
  18. package/dev-tools/starter-kit/source_files/app/src/pagine/.gitkeep +0 -0
  19. package/dev-tools/starter-kit/source_files/app/src/web-components/.gitkeep +0 -0
  20. package/src/css/margin-padding-bootstrap-media.css +0 -1845
  21. package/src/css/margin-padding-bootstrap.css +0 -465
  22. /package/{snippets/symfony → dev-tools/starter-kit/source_files/root/_sf/controller}/ImgViewerController.php +0 -0
  23. /package/{snippets/symfony → dev-tools/starter-kit/source_files/root/_sf/controller}/UnsplashController.php +0 -0
  24. /package/{snippets/symfony → dev-tools/starter-kit/source_files/root/_sf/controller}/ViewerController.php +0 -0
  25. /package/dev-tools/starter-kit/source_files/root/_sf/templates/{incl → _incl}/flash-messages.html.twig +0 -0
  26. /package/dev-tools/starter-kit/source_files/root/_sf/templates/{incl → _incl}/minimo-form-layout.html.twig +0 -0
@@ -43,7 +43,7 @@ export const default_params = {
43
43
  // nome del file snippet, compresa l'estensione
44
44
  // se questo parametro è null e anche `snippet_target_file` lo è, lo snippet non viene generato
45
45
  // se il linguaggio è ejs il nome è sempre 'favicons.incl.ejs' e snippet_name viene ignorato
46
- snippet_name: 'favicon.html',
46
+ snippet_name: 'favicons.html',
47
47
 
48
48
  // path (relativo a questo file) in cui salvare lo snippet
49
49
  // se null, viene utilizzato lo stesso percorso indicato in `output_dir`
@@ -0,0 +1,451 @@
1
+ #!/usr/bin/env node
2
+
3
+
4
+ /*
5
+ * find-orphan-css-classes.mjs
6
+ *
7
+ * Analizza i file sorgente (html, js, php, ecc.) ed elenca le classi CSS
8
+ * utilizzate che non sono definite nei file CSS indicati.
9
+ * Per i file js/jsx controlla anche le classi dei CSS modules
10
+ * (styles.xxx, ${styles.xxx}, styles['xxx']), verificandole rispetto al
11
+ * file .module.css importato.
12
+ *
13
+ * Nessuna dipendenza esterna, richiede Node >= 18.
14
+ *
15
+ * Il report viene scritto nel file `orphan-classes.md`, nella stessa directory
16
+ * del file di configurazione, con link cliccabili alle occorrenze. Se non ci
17
+ * sono classi mancanti il file viene eliminato.
18
+ *
19
+ * Uso:
20
+ * node find-orphan-css-classes.mjs --config orphan-css-classes.config.mjs
21
+ * node find-orphan-css-classes.mjs -c orphan-css-classes.config.mjs --json
22
+ *
23
+ * Opzioni:
24
+ * -c, --config file di configurazione JavaScript (.mjs consigliato, con export default)
25
+ * --json output JSON su stdout (nessun file scritto)
26
+ * -h, --help mostra questo messaggio
27
+ *
28
+ * Configurazione (i percorsi relativi sono risolti rispetto alla directory
29
+ * del file di configurazione):
30
+ * src (obbligatorio) file o directory da analizzare
31
+ * css (obbligatorio) file o directory contenenti i CSS
32
+ * ext estensioni dei sorgenti
33
+ * cssExt estensioni dei file CSS
34
+ * excludeDir nomi di directory da saltare (default: node_modules, .git, dist, vendor)
35
+ * excludeCssFiles percorsi di file CSS (o CSS modules) da non considerare
36
+ * nel confronto (default: [])
37
+ * ignore espressioni regolari (RegExp o stringhe) di classi da ignorare
38
+ * cssModules {
39
+ * enabled attiva il controllo dei CSS modules (default: true)
40
+ * ext estensioni dei sorgenti in cui cercare i CSS modules (default: js, jsx)
41
+ * pattern espressione regolare (RegExp o stringa) che identifica i file module
42
+ * camelCase se true, styles.myClass corrisponde anche a .my-class
43
+ * }
44
+ *
45
+ * Codice di uscita: 0 = nessuna classe mancante, 1 = trovate classi mancanti,
46
+ * 2 = errore di configurazione.
47
+ */
48
+
49
+ import fs from 'node:fs';
50
+ import path from 'node:path';
51
+ import { pathToFileURL } from 'node:url';
52
+ import { parseArgs } from 'node:util';
53
+
54
+ const DEFAULTS = {
55
+ ext: ['html', 'htm', 'js', 'mjs', 'jsx', 'ts', 'tsx', 'php', 'vue', 'twig'],
56
+ cssExt: ['css'],
57
+ excludeDir: ['node_modules', '.git', 'dist', 'vendor'],
58
+ excludeCssFiles: [],
59
+ ignore: [],
60
+ cssModules: {
61
+ enabled: true,
62
+ ext: ['js', 'jsx'],
63
+ pattern: '\\.module\\.(css|scss|sass|less)$',
64
+ camelCase: false
65
+ }
66
+ };
67
+
68
+ const REPORT_FILE = 'orphan-classes.md';
69
+
70
+ const DYN = '\u0000'; /* segnaposto per le parti dinamiche (PHP, template literal, ecc.) */
71
+
72
+ /* classe CSS in un selettore: .nome, con eventuali caratteri di escape */
73
+ const CSS_CLASS_RE = /\.(-?(?:[_a-zA-Z]|\\.)(?:[\w-]|\\.)*)/g;
74
+
75
+ /* class="..." / className='...' / class: `...` */
76
+ const CLASS_ATTR_RE = /\b(?:class|className)\s*[=:]\s*(["'`])((?:(?!\1)[\s\S])*?)\1/g;
77
+
78
+ /* el.classList.add('a', 'b') ecc. */
79
+ const CLASSLIST_RE = /\.classList\.(?:add|remove|toggle|contains|replace)\(([^)]*)\)/g;
80
+ const STRING_RE = /(["'`])((?:(?!\1).)*?)\1/g;
81
+
82
+ /* querySelector('.a .b'), closest('.a'), matches('.a') */
83
+ const SELECTOR_RE = /\b(querySelectorAll|querySelector|closest|matches)\(\s*(["'`])((?:(?!\2).)*?)\2/g;
84
+
85
+ /* getElementsByClassName('a b') */
86
+ const BY_CLASS_RE = /\bgetElementsByClassName\(\s*(["'`])((?:(?!\1).)*?)\1/g;
87
+
88
+ /*
89
+ * Import di un CSS module:
90
+ * import styles from './x.module.css'
91
+ * import * as styles from './x.module.css'
92
+ * import styles, { foo } from './x.module.css'
93
+ * const styles = require('./x.module.css')
94
+ */
95
+ const IMPORT_RE = /\bimport\s+(?:\*\s+as\s+)?([A-Za-z_$][\w$]*)\s*(?:,\s*\{[^}]*\})?\s*from\s*(["'])([^"']+)\2/g;
96
+ const REQUIRE_RE = /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*require\(\s*(["'])([^"']+)\2\s*\)/g;
97
+
98
+ /* ---------- utilità ---------- */
99
+
100
+ function unescapeCss(name) {
101
+ return name.replace(/\\(.)/g, '$1');
102
+ }
103
+
104
+ function escapeRegExp(s) {
105
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
106
+ }
107
+
108
+ function camelize(s) {
109
+ return s.replace(/[-_]+(.)?/g, (_, c) => (c ? c.toUpperCase() : ''));
110
+ }
111
+
112
+ function maskDynamic(value) {
113
+ return value
114
+ .replace(/<\?[\s\S]*?\?>/g, DYN)
115
+ .replace(/\{\{[\s\S]*?\}\}/g, DYN)
116
+ .replace(/\{%[\s\S]*?%\}/g, DYN)
117
+ .replace(/\$\{[^}]*\}/g, DYN);
118
+ }
119
+
120
+ /*
121
+ * Divide un valore di attributo in singole classi.
122
+ * I token che contengono parti dinamiche (es. "btn-<?= $x ?>") vengono
123
+ * scartati, perché il nome reale della classe non è noto.
124
+ */
125
+ function splitTokens(value) {
126
+ return maskDynamic(value)
127
+ .split(/\s+/)
128
+ .filter(t => t && !t.includes(DYN) && !/[<>"'`]/.test(t));
129
+ }
130
+
131
+ function normalizeExt(list) {
132
+ return new Set(list.map(e => e.replace(/^\./, '').toLowerCase()));
133
+ }
134
+
135
+ function collectFiles(entries, extensions, exclude) {
136
+ const out = new Set();
137
+
138
+ const visit = (p, explicit) => {
139
+ let stat;
140
+ try {
141
+ stat = fs.statSync(p);
142
+ } catch {
143
+ console.error(`Percorso non trovato: ${p}`);
144
+ return;
145
+ }
146
+
147
+ if (stat.isDirectory()) {
148
+ for (const entry of fs.readdirSync(p, { withFileTypes: true })) {
149
+ if (exclude.has(entry.name)) continue;
150
+ visit(path.join(p, entry.name), false);
151
+ }
152
+ } else if (explicit || extensions.has(path.extname(p).slice(1).toLowerCase())) {
153
+ out.add(path.resolve(p));
154
+ }
155
+ };
156
+
157
+ entries.forEach(p => visit(p, true));
158
+ return [...out];
159
+ }
160
+
161
+ function buildLineIndex(text) {
162
+ const idx = [0];
163
+ for (let i = 0; i < text.length; i++) {
164
+ if (text.charCodeAt(i) === 10) idx.push(i + 1);
165
+ }
166
+ return idx;
167
+ }
168
+
169
+ function lineAt(idx, pos) {
170
+ let lo = 0;
171
+ let hi = idx.length - 1;
172
+ while (lo < hi) {
173
+ const mid = (lo + hi + 1) >> 1;
174
+ if (idx[mid] <= pos) lo = mid;
175
+ else hi = mid - 1;
176
+ }
177
+ return lo + 1;
178
+ }
179
+
180
+ /* ---------- configurazione ---------- */
181
+
182
+ async function loadConfig(configPath) {
183
+ const abs = path.resolve(configPath);
184
+ const mod = await import(pathToFileURL(abs).href);
185
+ const raw = mod.default ?? mod;
186
+
187
+ for (const key of ['src', 'css']) {
188
+ if (!Array.isArray(raw[key]) || !raw[key].length) {
189
+ throw new Error(`Nel file di configurazione manca la lista "${key}"`);
190
+ }
191
+ }
192
+
193
+ const baseDir = path.dirname(abs);
194
+ const toAbs = list => list.map(p => path.resolve(baseDir, p));
195
+ const modules = { ...DEFAULTS.cssModules, ...(raw.cssModules ?? {}) };
196
+
197
+ return {
198
+ src: toAbs(raw.src),
199
+ css: toAbs(raw.css),
200
+ ext: normalizeExt(raw.ext ?? DEFAULTS.ext),
201
+ cssExt: normalizeExt(raw.cssExt ?? DEFAULTS.cssExt),
202
+ excludeDir: new Set(raw.excludeDir ?? DEFAULTS.excludeDir),
203
+ excludeCssFiles: new Set(toAbs(raw.excludeCssFiles ?? DEFAULTS.excludeCssFiles)),
204
+ baseDir,
205
+ ignore: (raw.ignore ?? DEFAULTS.ignore).map(s => new RegExp(s)),
206
+ cssModules: {
207
+ enabled: Boolean(modules.enabled),
208
+ ext: normalizeExt(modules.ext),
209
+ pattern: new RegExp(modules.pattern),
210
+ camelCase: Boolean(modules.camelCase)
211
+ }
212
+ };
213
+ }
214
+
215
+ /* ---------- estrazione classi dai CSS ---------- */
216
+
217
+ function extractCssClasses(css) {
218
+ const clean = css
219
+ .replace(/\/\*[\s\S]*?\*\//g, '') /* commenti */
220
+ .replace(/"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'/g, '""') /* stringhe */
221
+ .replace(/url\([^)]*\)/gi, 'url()');
222
+
223
+ const classes = new Set();
224
+ const preludeRe = /([^{}]+)\{/g; /* testo che precede ogni "{" */
225
+ let m;
226
+
227
+ while ((m = preludeRe.exec(clean))) {
228
+ /* con il CSS nesting il testo può includere dichiarazioni precedenti */
229
+ const prelude = m[1].slice(m[1].lastIndexOf(';') + 1).trim();
230
+ if (!prelude || prelude.startsWith('@')) continue; /* at-rule: @media, @font-face, ecc. */
231
+
232
+ for (const c of prelude.matchAll(CSS_CLASS_RE)) {
233
+ classes.add(unescapeCss(c[1]));
234
+ }
235
+ }
236
+
237
+ return classes;
238
+ }
239
+
240
+ /* ---------- estrazione classi dai sorgenti ---------- */
241
+
242
+ function* extractUsages(text) {
243
+ for (const m of text.matchAll(CLASS_ATTR_RE)) {
244
+ for (const cls of splitTokens(m[2])) yield { cls, index: m.index };
245
+ }
246
+
247
+ for (const m of text.matchAll(CLASSLIST_RE)) {
248
+ for (const lit of m[1].matchAll(STRING_RE)) {
249
+ for (const cls of splitTokens(lit[2])) yield { cls, index: m.index };
250
+ }
251
+ }
252
+
253
+ for (const m of text.matchAll(BY_CLASS_RE)) {
254
+ for (const cls of splitTokens(m[2])) yield { cls, index: m.index };
255
+ }
256
+
257
+ for (const m of text.matchAll(SELECTOR_RE)) {
258
+ const selector = maskDynamic(m[3]);
259
+ for (const c of selector.matchAll(CSS_CLASS_RE)) {
260
+ /* se la classe è seguita da una parte dinamica il nome è incompleto */
261
+ if (selector[c.index + c[0].length] === DYN) continue;
262
+ yield { cls: unescapeCss(c[1]), index: m.index };
263
+ }
264
+ }
265
+ }
266
+
267
+ /*
268
+ * Classi dei CSS modules: individua le variabili a cui è assegnato un file
269
+ * module (qualunque nome, non solo "styles") e cerca nome.xxx / nome['xxx'].
270
+ * Il caso ${styles.xxx} è coperto da nome.xxx.
271
+ * "module" è il percorso assoluto del file importato, o null se non risolvibile
272
+ * (es. alias di webpack).
273
+ */
274
+ function* extractModuleUsages(text, file, pattern) {
275
+ const bindings = new Map();
276
+
277
+ for (const re of [IMPORT_RE, REQUIRE_RE]) {
278
+ for (const m of text.matchAll(re)) {
279
+ const name = m[1];
280
+ const spec = m[3];
281
+ if (!pattern.test(spec)) continue;
282
+
283
+ let resolved = null;
284
+ if (spec.startsWith('.')) {
285
+ const candidate = path.resolve(path.dirname(file), spec);
286
+ if (fs.existsSync(candidate)) resolved = candidate;
287
+ }
288
+ bindings.set(name, resolved);
289
+ }
290
+ }
291
+
292
+ for (const [name, resolved] of bindings) {
293
+ const id = escapeRegExp(name);
294
+ const dotRe = new RegExp(`(?<![\\w$.])${id}\\.([A-Za-z_$][\\w$]*)`, 'g');
295
+ const bracketRe = new RegExp(`(?<![\\w$.])${id}\\[\\s*(["'])([^"']+)\\1\\s*\\]`, 'g');
296
+
297
+ for (const m of text.matchAll(dotRe)) {
298
+ yield { cls: m[1], index: m.index, isModule: true, module: resolved };
299
+ }
300
+ for (const m of text.matchAll(bracketRe)) {
301
+ yield { cls: m[2], index: m.index, isModule: true, module: resolved };
302
+ }
303
+ }
304
+ }
305
+
306
+ /* ---------- main ---------- */
307
+
308
+ const { values } = parseArgs({
309
+ options: {
310
+ config: { type: 'string', short: 'c' },
311
+ json: { type: 'boolean', default: false },
312
+ help: { type: 'boolean', short: 'h', default: false }
313
+ }
314
+ });
315
+
316
+ if (values.help) {
317
+ const header = fs.readFileSync(new URL(import.meta.url), 'utf8').match(/\/\*[\s\S]*?\*\//)[0];
318
+ console.log(header);
319
+ process.exit(0);
320
+ }
321
+
322
+ if (!values.config) {
323
+ console.error('Specificare il file di configurazione con --config <file> (usare --help per i dettagli).');
324
+ process.exit(2);
325
+ }
326
+
327
+ let cfg;
328
+ try {
329
+ cfg = await loadConfig(values.config);
330
+ } catch (err) {
331
+ console.error(`Errore nel file di configurazione: ${err.message}`);
332
+ process.exit(2);
333
+ }
334
+
335
+ const camel = cfg.cssModules.camelCase;
336
+
337
+ /* con camelCase attivo ogni classe è disponibile anche in forma camelCase */
338
+ function withCamel(set) {
339
+ if (camel) for (const c of [...set]) set.add(camelize(c));
340
+ return set;
341
+ }
342
+
343
+ const allCssFiles = collectFiles(cfg.css, cfg.cssExt, cfg.excludeDir);
344
+ const cssFiles = allCssFiles.filter(f => !cfg.excludeCssFiles.has(f));
345
+ const srcFiles = collectFiles(cfg.src, cfg.ext, cfg.excludeDir).filter(f => !allCssFiles.includes(f));
346
+
347
+ if (!cssFiles.length) {
348
+ console.error('Nessun file CSS trovato.');
349
+ process.exit(2);
350
+ }
351
+
352
+ /*
353
+ * I file module presenti nella lista CSS non contribuiscono alle classi
354
+ * "globali" (sono raggiungibili solo tramite import), ma servono come
355
+ * riferimento di riserva quando l'import non è risolvibile.
356
+ */
357
+ const globalClasses = new Set();
358
+ const allModuleClasses = new Set();
359
+ const moduleCache = new Map();
360
+
361
+ function getModuleClasses(file) {
362
+ if (!moduleCache.has(file)) {
363
+ moduleCache.set(file, withCamel(extractCssClasses(fs.readFileSync(file, 'utf8'))));
364
+ }
365
+ return moduleCache.get(file);
366
+ }
367
+
368
+ for (const file of cssFiles) {
369
+ if (cfg.cssModules.enabled && cfg.cssModules.pattern.test(file)) {
370
+ for (const cls of getModuleClasses(file)) allModuleClasses.add(cls);
371
+ } else {
372
+ for (const cls of extractCssClasses(fs.readFileSync(file, 'utf8'))) globalClasses.add(cls);
373
+ }
374
+ }
375
+
376
+ const fallbackClasses = new Set([...globalClasses, ...allModuleClasses]);
377
+
378
+ function isDefined(usage) {
379
+ /* usages of an excluded CSS module are not checked */
380
+ if (usage.module && cfg.excludeCssFiles.has(usage.module)) return true;
381
+ if (!usage.isModule) return globalClasses.has(usage.cls);
382
+ const set = usage.module ? getModuleClasses(usage.module) : fallbackClasses;
383
+ return set.has(usage.cls);
384
+ }
385
+
386
+ const missing = new Map(); /* classe -> [{ file, line, module? }] */
387
+
388
+ for (const file of srcFiles) {
389
+ const text = fs.readFileSync(file, 'utf8');
390
+ const lineIndex = buildLineIndex(text);
391
+ const rel = path.relative(cfg.baseDir, file);
392
+ const ext = path.extname(file).slice(1).toLowerCase();
393
+
394
+ const usages = [...extractUsages(text)];
395
+ if (cfg.cssModules.enabled && cfg.cssModules.ext.has(ext)) {
396
+ usages.push(...extractModuleUsages(text, file, cfg.cssModules.pattern));
397
+ }
398
+
399
+ for (const usage of usages) {
400
+ if (isDefined(usage) || cfg.ignore.some(re => re.test(usage.cls))) continue;
401
+
402
+ const entry = { file: rel, line: lineAt(lineIndex, usage.index) };
403
+ if (usage.isModule) {
404
+ entry.module = usage.module ? path.relative(cfg.baseDir, usage.module) : 'non risolto';
405
+ }
406
+
407
+ const list = missing.get(usage.cls) ?? [];
408
+ if (!list.some(e => e.file === entry.file && e.line === entry.line && e.module === entry.module)) {
409
+ list.push(entry);
410
+ }
411
+ missing.set(usage.cls, list);
412
+ }
413
+ }
414
+
415
+ const sorted = [...missing.entries()].sort((a, b) => a[0].localeCompare(b[0]));
416
+
417
+ if (values.json) {
418
+ console.log(JSON.stringify({
419
+ cssFiles: cssFiles.length,
420
+ sourceFiles: srcFiles.length,
421
+ definedClasses: globalClasses.size,
422
+ missing: Object.fromEntries(sorted)
423
+ }, null, 2));
424
+ } else {
425
+ const reportPath = path.resolve(cfg.baseDir, REPORT_FILE);
426
+
427
+ if (sorted.length) {
428
+ /* flat list sorted by file and line, with a clickable link to each occurrence */
429
+ const items = sorted
430
+ .flatMap(([cls, locations]) => locations.map(loc => ({ cls, ...loc })))
431
+ .sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line);
432
+
433
+ const lines = items.map(({ cls, file, line, module }) => {
434
+ const href = `${file.split(path.sep).map(encodeURIComponent).join('/')}#L${line}`;
435
+ return `* [${path.basename(file)}:${line}](${href}) -> \`.${cls}\``
436
+ + (module ? ` (CSS module: ${module})` : '');
437
+ });
438
+
439
+ fs.writeFileSync(reportPath, `## Missing CSS classes\n\n${lines.join('\n')}\n`, 'utf-8');
440
+ } else if (fs.existsSync(reportPath)) {
441
+ fs.unlinkSync(reportPath);
442
+ }
443
+
444
+ console.log(
445
+ `File CSS: ${cssFiles.length} (${globalClasses.size} classi globali) | File analizzati: ${srcFiles.length}\n`
446
+ + `Classi mancanti: ${sorted.length}`
447
+ + (sorted.length ? ` -> ${path.relative(process.cwd(), reportPath)}` : '')
448
+ );
449
+ }
450
+
451
+ process.exit(sorted.length ? 1 : 0);
@@ -0,0 +1,44 @@
1
+ /*
2
+ * Configurazione per find-prphan-css-classes.mjs
3
+ * I percorsi relativi sono risolti rispetto alla directory di questo file.
4
+ *
5
+ * Config Da Copiare Nella Root Del Progetto
6
+ */
7
+
8
+ const minimo_path = '../../node_modules/@massimo-cassandro/minimo';
9
+
10
+ export default {
11
+ /* file o directory da analizzare */
12
+ src: ['../src', '../../templates'],
13
+
14
+ /* file o directory contenenti i CSS */
15
+ css: ['./', '../src', `${minimo_path}/src`],
16
+
17
+ /* estensioni dei sorgenti */
18
+ ext: ['html', 'js', 'jsx', 'php'],
19
+
20
+ /* estensioni dei file CSS */
21
+ cssExt: ['css'],
22
+
23
+ /* directory da saltare */
24
+ excludeDir: [/* 'node_modules', */'.git', 'dist', 'vendor'],
25
+
26
+ /* file CSS (o CSS modules) da non considerare nel confronto (default: []) */
27
+ excludeCssFiles: [],
28
+
29
+ /* classi da ignorare (RegExp o stringhe) */
30
+ ignore: [/^js-/, /^is-/],
31
+
32
+ cssModules: {
33
+ enabled: true,
34
+
35
+ /* sorgenti in cui cercare l'uso dei CSS modules */
36
+ ext: ['js', 'jsx'],
37
+
38
+ /* file considerati CSS modules */
39
+ pattern: /\.module\.css$/,
40
+
41
+ /* se true, styles.myClass corrisponde anche a .my-class */
42
+ camelCase: false
43
+ }
44
+ };
@@ -0,0 +1,38 @@
1
+
2
+
3
+ import { unsplashPage } from '@massimo-cassandro/minimo';
4
+ // import './error-pages.css';
5
+
6
+ const errCode = document.documentElement.dataset.errCode || '500',
7
+ errText = document.documentElement.dataset.errText,
8
+ errors = {
9
+ '403': {
10
+ title: 'Ops, non puoi accedere a questa pagina'
11
+ },
12
+ '404': {
13
+ title: 'Ops, questa pagina non esiste'
14
+ },
15
+ '500': {
16
+ title: 'Ops, si è verificato un errore'
17
+ },
18
+ '503': {
19
+ title: 'Siamo spiacenti, il sito è temporaneamente in manutenzione'
20
+ }
21
+ }
22
+ ,url = '/get-unsplash'
23
+ ;
24
+
25
+ document.title = `Errore ${errCode}`;
26
+
27
+ unsplashPage({
28
+ targetElement : document.getElementById('root'),
29
+ className : 'error-page',
30
+ unsplashDataUrl : url,
31
+ utmSource : 'ada-rinascente.bluvacanze.it',
32
+ title : errors[errCode]?.title ?? errText,
33
+ text : `(${errCode} ${errText})`,
34
+ backLink : errCode !== '503'? 'Torna alla <a href="/">Home</a>' : '',
35
+ // hidePhotoLink : true,
36
+ // cssModules : false,
37
+ // cssModulesObj : null
38
+ });
@@ -41,6 +41,10 @@
41
41
  {
42
42
  "filePath": "node_modules/open-props/open-props.style-dictionary-tokens.json",
43
43
  "group": "Default"
44
+ },
45
+ {
46
+ "filePath": "node_modules/@massimo-cassandro/minimo/src/css",
47
+ "group": "Default"
44
48
  }
45
49
  ],
46
50
 
@@ -11,7 +11,16 @@ indent_style = space
11
11
  insert_final_newline = true
12
12
  trim_trailing_whitespace = true
13
13
 
14
+ # [*.yaml]
15
+ # indent_size = 4
16
+
17
+ # [*.php]
18
+ # indent_size = 4
19
+
20
+ # [{compose.yaml,compose.*.yaml}]
21
+ # indent_size = 2
22
+
14
23
  [*.md]
15
- max_line_length = off
24
+ max_line_length = 80
16
25
  trim_trailing_whitespace = false
17
26
 
@@ -34,7 +34,7 @@ twig:
34
34
  file_name_pattern: '*.twig'
35
35
 
36
36
  form_themes:
37
- - '_tpl/incl/bs5-form-layout.html.twig'
37
+ - '_incl/minimo-form-layout.html.twig'
38
38
  paths:
39
39
  '%kernel.project_dir%/public/%env(ASSETS_DIR)%': assets_path
40
40
  '%kernel.project_dir%/public': public_path
@@ -6,7 +6,7 @@
6
6
  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
7
7
  <title>{%- block title -%}{%- endblock %} | Marketing</title>
8
8
  <meta name="robots" content="noindex, nofollow, noarchive">
9
- {%- include 'incl/favicons.incl.html.twig'-%}
9
+ {%- include '_incl/favicons.incl.html.twig'-%}
10
10
  {{- '' -}}
11
11
  <link rel="preload" href="{{ asset('index.js') }}" as="script">
12
12
  <link rel="preload" href="{{ asset('shared.js') }}" as="script">
@@ -7,9 +7,9 @@
7
7
  <meta http-equiv="x-ua-compatible" content="ie=edge">
8
8
  <title>Errore {{ status_code }}</title>
9
9
  <meta name="robots" content="noindex, nofollow, noarchive">
10
- {%- include 'incl/favicons.incl.html.twig'-%}
11
- <link rel="preload" href="{{ asset('marketing.css') }}" as="style">
12
- <link rel="stylesheet" href="{{ asset('marketing.css') }}" media="all">
10
+ {%- include '_incl/favicons.incl.html.twig'-%}
11
+ <link rel="preload" href="{{ asset('index.css') }}" as="style">
12
+ <link rel="stylesheet" href="{{ asset('index.css') }}" media="all">
13
13
  <link rel="preload" href="{{ asset('error-pages.css') }}" as="style">
14
14
  <link rel="stylesheet" href="{{ asset('error-pages.css') }}" media="all">
15
15
  <link rel="preload" href="{{ asset('error-pages.js') }}" as="script">
@@ -21,23 +21,27 @@
21
21
  "php server": "php -S localhost:[[port8000]] # -t root_dir/",
22
22
  "webpack DEV (sf)": "rm -rf ./public/_dev && NODE_ENV=development webpack serve --config ./webpack.config.mjs # http://192.168.1.135:[[port5700]",
23
23
  "symfony server": "symfony serve --port=[[port8000]] --listen-ip=0.0.0.0 # http://192.168.1.135:[[port8000]] # -d (deamon): rimane aperto anche dopo la chiusura del terminale, terminare con `symfony server:stop`",
24
+ "launchChrome": "open -n -a 'Google Chrome' --args --profile-directory='Profile 3' # mbn: 1 / mg2: 3",
24
25
  "webpack PROD": "npm run 'UPD-version' && NODE_ENV=production webpack --config ./webpack.config.mjs --stats minimal",
26
+ "UPD-version": "npx update-version --no-log-v0 --log-file=./changelog.md # --log-patch --pkg=alt/path/to/package.json",
25
27
  "email sass": "npx sass ./app/email:./templates/email/css --no-source-map --style=compressed --watch",
26
28
  "postcss email": "postcss ./app/email/[!_]*.css --dir ./templates/email/css --no-map --verbose --config ./app/email --watch",
27
29
  "eslint-check": "npx eslint app/",
28
30
  "stylelint-check": "npx stylelint 'app/**/*.css'",
29
31
  "create-favicons INIT": "npx create-favicons init",
30
32
  "create-favicons": "npx create-favicons --dir=./app/favicons",
31
- "UPD-version": "npx update-version --no-log-v0 --log-file=./changelog.md # --log-patch --pkg=alt/path/to/package.json",
32
- "upd@m": "npx upd@m",
33
- "launchChrome": "open -n -a 'Google Chrome' --args --profile-directory='Profile 3' # mbn: 1 / mg2: 3",
34
33
  "python server": "python3 -m http.server [[port8000]] # --directory __dirname__ # 8000 = default port",
35
34
  "node server": "node serve # richiede ‘npm i serve’",
36
35
  "build tokens": "npx buildTokens --config ./app/css/tokens-config.mjs",
37
36
  "check unresolved props": "npx checkUnresolvedProps --config ./app/css/tokens-config.mjs",
37
+ "find orphan css classes": "npx findOrphanCssClasses --config ./app/css/orphan-css-classes.config.mjs",
38
38
  "npm-outdated": "npm outdated",
39
+ "upd@m": "npx upd@m",
39
40
  "npm-upd": "npm update --save"
40
41
  },
42
+ "allowScripts": {
43
+ "@parcel/watcher": false
44
+ },
41
45
  "devDependencies": {},
42
46
  "dependencies": {}
43
47
  }
@@ -357,14 +357,20 @@ echo -e "\n${GREEN}...creating default folders${NC}"
357
357
  mkdir -p \
358
358
  "_private" \
359
359
  "${FRONTEND_INSTALL_PATH}" \
360
+ "${FRONTEND_INSTALL_PATH}/api" \
360
361
  "${FRONTEND_INSTALL_PATH}/assets" \
361
- "${FRONTEND_INSTALL_PATH}/src" \
362
362
  "${FRONTEND_INSTALL_PATH}/css" \
363
- "${FRONTEND_INSTALL_PATH}/tpl" \
363
+ "${FRONTEND_INSTALL_PATH}/email" \
364
+ "${FRONTEND_INSTALL_PATH}/error-pages" \
365
+ "${FRONTEND_INSTALL_PATH}/favicons" \
364
366
  "${FRONTEND_INSTALL_PATH}/icons" \
365
367
  "${FRONTEND_INSTALL_PATH}/imgs" \
366
- "${FRONTEND_INSTALL_PATH}/favicons" \
367
- "${FRONTEND_INSTALL_PATH}/error-pages"
368
+ "${FRONTEND_INSTALL_PATH}/src" \
369
+ "${FRONTEND_INSTALL_PATH}/src/components" \
370
+ "${FRONTEND_INSTALL_PATH}/src/js" \
371
+ "${FRONTEND_INSTALL_PATH}/src/pages" \
372
+ "${FRONTEND_INSTALL_PATH}/src/web-components" \
373
+ "${FRONTEND_INSTALL_PATH}/tpl"
368
374
 
369
375
 
370
376
 
@@ -425,11 +431,6 @@ force_cat "${MINIMO_PKG_DIR}/src/minimo.css" "${FRONTEND_INSTALL_PATH}/css/index
425
431
  force_cat "${MINIMO_PKG_DIR}/dev-tools/design-tokens-builder/tokens-config-sample.mjs" "${FRONTEND_INSTALL_PATH}/css/tokens-config.mjs"
426
432
 
427
433
 
428
-
429
- # favicons: config di default per `npx create-favicons`
430
- force_cat "${MINIMO_PKG_DIR}/dev-tools/create-favicons/src/default-params.mjs" "${FRONTEND_INSTALL_PATH}/favicons/create-favicons-cfg.mjs"
431
-
432
-
433
434
  if [ ${#selectedOptionalDevPkgs[@]} -gt 0 ] || [ ${#selectedOptionalDepPkgs[@]} -gt 0 ]; then
434
435
  echo -e "\n${YELLOW}Pacchetti opzionali installati:${NC}"
435
436
  for pkg in "${selectedOptionalDevPkgs[@]}" "${selectedOptionalDepPkgs[@]}"; do