@massimo-cassandro/minimo 1.33.2 → 1.33.4
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.
- package/design-tokens/tokens-config-sample.mjs +1 -1
- package/dev-tools/starter-kit/source_files/app/email/_mail-properties.css +26 -0
- package/dev-tools/starter-kit/source_files/app/email/mail-embed.css +66 -0
- package/dev-tools/starter-kit/source_files/app/email/mail-inline.css +214 -0
- package/dev-tools/starter-kit/source_files/app/email/postcss.config.mjs +41 -0
- package/dev-tools/starter-kit/source_files/root/package-tpl.json +5 -5
- package/dev-tools/starter-kit/source_files/root/sf_templates/email/_email-tpl.html.twig +63 -0
- package/dev-tools/starter-kit/source_files/root/sf_templates/email/css/mail-embed.css +1 -0
- package/dev-tools/starter-kit/source_files/root/sf_templates/email/css/mail-inline.css +1 -0
- package/dev-tools/starter-kit/source_files/root/webpack.config.mjs +13 -6
- package/dev-tools/starter-kit/starter-install.sh +58 -29
- package/package.json +1 -1
- package/snippets/twig-symfony/_main-tpl.html.twig +29 -0
- package/snippets/twig-symfony/bundles/TwigBundle/Exception/error.html.twig +5 -11
- package/snippets/twig-symfony/symfony-webpack-setup.md +71 -0
- package/src/components/modal-alert/modal-alert.js +4 -0
- package/src/utilities/validation-error-class.js +22 -5
|
@@ -147,7 +147,7 @@ const config = {
|
|
|
147
147
|
],
|
|
148
148
|
|
|
149
149
|
// Directory containing the CSS files to scan for unresolved custom properties
|
|
150
|
-
dirToCheck: '
|
|
150
|
+
dirToCheck: '../src',
|
|
151
151
|
|
|
152
152
|
// Regular expressions for custom property names to exclude from the check.
|
|
153
153
|
// Useful for intentionally private/internal props (e.g. prefixed with --_).
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/* @import url(../src/css/custom-properties.css); */
|
|
2
|
+
|
|
3
|
+
:root {
|
|
4
|
+
|
|
5
|
+
--mail-font-size-xs: 13px;
|
|
6
|
+
--mail-font-size: 16px;
|
|
7
|
+
--mail-font-size-lg: 18px;
|
|
8
|
+
--mail-font-size-2lg: 21px;
|
|
9
|
+
--mail-font-size-3lg: 24px;
|
|
10
|
+
--mail-font-size-xl: 28px;
|
|
11
|
+
|
|
12
|
+
--mail-body-font: helvetica, arial, sans-serif;
|
|
13
|
+
--mail-body-bg: #efefef;
|
|
14
|
+
--mail-text-color: #333;
|
|
15
|
+
--mail-text-color-light: #373737;
|
|
16
|
+
--mail-gray: #4b4a4a;
|
|
17
|
+
|
|
18
|
+
--mail-content-bg: #fff;
|
|
19
|
+
--mail-spacing: 24px;
|
|
20
|
+
--mail-spacing-half: 12px; /* niente calc(): non supportato da Outlook */
|
|
21
|
+
|
|
22
|
+
--mail-alt-color: #036;
|
|
23
|
+
--mail-darken-alt-color: #010112;
|
|
24
|
+
--mail-link-color: #036;
|
|
25
|
+
|
|
26
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/* stylelint-disable declaration-no-important */
|
|
2
|
+
@import './_mail-properties.css';
|
|
3
|
+
|
|
4
|
+
/* NB: niente selettore universale `*`: inline_css inlinerebbe la regola su ogni elemento */
|
|
5
|
+
body, table, td, p, a { text-size-adjust: 100%; }
|
|
6
|
+
|
|
7
|
+
.content {
|
|
8
|
+
&.body {
|
|
9
|
+
a {
|
|
10
|
+
font-weight: 700;
|
|
11
|
+
color: var(--mail-alt-color);
|
|
12
|
+
text-decoration: underline;
|
|
13
|
+
cursor: pointer;
|
|
14
|
+
|
|
15
|
+
&:hover {
|
|
16
|
+
color: var(--mail-darken-alt-color);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@media only screen and (max-width: 640px) {
|
|
23
|
+
body {
|
|
24
|
+
&[class='yahoo-fix'] {
|
|
25
|
+
.device-width {
|
|
26
|
+
width: 440px !important;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.content {
|
|
32
|
+
&.columns-wrapper {
|
|
33
|
+
td.column-spacer {
|
|
34
|
+
height: 0 !important;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
td.column-width, td.column-spacer {
|
|
38
|
+
display: block !important;
|
|
39
|
+
width: 100% !important;
|
|
40
|
+
}
|
|
41
|
+
.column-content {
|
|
42
|
+
& td {
|
|
43
|
+
& h2 {
|
|
44
|
+
font-size: var(--mail-font-size-3lg) !important;
|
|
45
|
+
}
|
|
46
|
+
& .text {
|
|
47
|
+
font-size: var(--mail-font-size-lg) !important;
|
|
48
|
+
}
|
|
49
|
+
& .buttons {
|
|
50
|
+
font-size: var(--mail-font-size-2lg) !important;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
@media only screen and (max-width: 479px) {
|
|
59
|
+
body {
|
|
60
|
+
&[class='yahoo-fix'] {
|
|
61
|
+
.device-width {
|
|
62
|
+
width: 280px !important;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/* stylelint-disable max-nesting-depth */
|
|
2
|
+
/* stylelint-disable selector-max-compound-selectors */
|
|
3
|
+
/* stylelint-disable selector-max-id */
|
|
4
|
+
/* stylelint-disable property-no-unknown */
|
|
5
|
+
@import './_mail-properties.css';
|
|
6
|
+
|
|
7
|
+
body {
|
|
8
|
+
width: 100%;
|
|
9
|
+
padding: 0;
|
|
10
|
+
margin: 0;
|
|
11
|
+
font-family: var(--mail-body-font);
|
|
12
|
+
font-style: normal;
|
|
13
|
+
font-weight: 400;
|
|
14
|
+
line-height: 1.5;
|
|
15
|
+
color: var(--mail-text-color);
|
|
16
|
+
background-color: var(--mail-body-bg);
|
|
17
|
+
-webkit-font-smoothing: antialiased;
|
|
18
|
+
|
|
19
|
+
.device-width {
|
|
20
|
+
width: 600px;
|
|
21
|
+
padding: 0;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/* .full-width {
|
|
25
|
+
width: 100%;
|
|
26
|
+
padding: 0;
|
|
27
|
+
} */
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/*
|
|
31
|
+
div.outerWrapper {
|
|
32
|
+
background-color: var(--mail-content-bg);
|
|
33
|
+
//border-radius:20px;
|
|
34
|
+
overflow: hidden;
|
|
35
|
+
margin: 0 auto;
|
|
36
|
+
}
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
table {
|
|
40
|
+
border-collapse: collapse;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
p {
|
|
44
|
+
mso-table-lspace: 0;
|
|
45
|
+
mso-table-rspace: 0;
|
|
46
|
+
margin-top: var(--mail-spacing-half);
|
|
47
|
+
margin-bottom: var(--mail-spacing-half);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
#wrapper{
|
|
51
|
+
width: 100%;
|
|
52
|
+
margin: 0 auto;
|
|
53
|
+
border: none;
|
|
54
|
+
|
|
55
|
+
/* &.full-page{
|
|
56
|
+
width: 95%;
|
|
57
|
+
} */
|
|
58
|
+
|
|
59
|
+
> tbody > tr > td {
|
|
60
|
+
width: 100%;
|
|
61
|
+
padding-top: 10px;
|
|
62
|
+
padding-right: 10px;
|
|
63
|
+
padding-bottom: 20px;
|
|
64
|
+
padding-left: 10px;
|
|
65
|
+
background-color: var(--mail-body-bg);
|
|
66
|
+
border: none;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.content {
|
|
71
|
+
margin: 0 auto;
|
|
72
|
+
|
|
73
|
+
&.header {
|
|
74
|
+
> tbody > tr > td {
|
|
75
|
+
padding: 10px;
|
|
76
|
+
text-align: center;
|
|
77
|
+
background: var(--mail-body-bg);
|
|
78
|
+
|
|
79
|
+
> img {
|
|
80
|
+
display: inline-block;
|
|
81
|
+
max-width: 100%;
|
|
82
|
+
height: auto;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
.logo {
|
|
86
|
+
padding-top: 10px;
|
|
87
|
+
padding-bottom: 10px;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
&.body {
|
|
92
|
+
|
|
93
|
+
> tbody > tr {
|
|
94
|
+
> td {
|
|
95
|
+
|
|
96
|
+
padding-top: 20px;
|
|
97
|
+
padding-right: 30px;
|
|
98
|
+
padding-bottom: 20px;
|
|
99
|
+
padding-left: 30px;
|
|
100
|
+
vertical-align: top;
|
|
101
|
+
color: var(--mail-text-color);
|
|
102
|
+
background-color: var(--mail-content-bg);
|
|
103
|
+
|
|
104
|
+
a {
|
|
105
|
+
font-weight: 700;
|
|
106
|
+
color: var(--mail-alt-color);
|
|
107
|
+
text-decoration: underline;
|
|
108
|
+
cursor: pointer;
|
|
109
|
+
|
|
110
|
+
&:hover {
|
|
111
|
+
color: var(--mail-darken-alt-color);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
h1 {
|
|
116
|
+
margin-top: 0;
|
|
117
|
+
margin-bottom: var(--mail-spacing);
|
|
118
|
+
font-size: var(--mail-font-size-xl);
|
|
119
|
+
font-weight: 700;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
p {
|
|
123
|
+
font-size: var(--mail-font-size-2lg);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
img {
|
|
127
|
+
max-width: 100%;
|
|
128
|
+
height: auto;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
&.footer {
|
|
136
|
+
td {
|
|
137
|
+
padding-top: 10px;
|
|
138
|
+
padding-bottom: 10px;
|
|
139
|
+
font-size: 80%;
|
|
140
|
+
color: var(--mail-text-color-light);
|
|
141
|
+
text-align: center;
|
|
142
|
+
background: var(--mail-body-bg);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.logo-network {
|
|
148
|
+
display: inline-block;
|
|
149
|
+
padding: 5px;
|
|
150
|
+
margin: 10px auto;
|
|
151
|
+
background-color: var(--mail-body-bg);
|
|
152
|
+
}
|
|
153
|
+
.network-info {
|
|
154
|
+
font-size: var(--mail-font-size-xs);
|
|
155
|
+
font-style: italic;
|
|
156
|
+
color: #888;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
.text-center {
|
|
161
|
+
text-align: center;
|
|
162
|
+
}
|
|
163
|
+
.text-right {
|
|
164
|
+
text-align: right;
|
|
165
|
+
}
|
|
166
|
+
.text-left {
|
|
167
|
+
text-align: left;
|
|
168
|
+
}
|
|
169
|
+
.small, small {
|
|
170
|
+
font-size: var(--mail-font-size-xs);
|
|
171
|
+
}
|
|
172
|
+
.margin-top {
|
|
173
|
+
margin-top: var(--mail-spacing);
|
|
174
|
+
}
|
|
175
|
+
.margin-bottom {
|
|
176
|
+
margin-bottom: var(--mail-spacing);
|
|
177
|
+
}
|
|
178
|
+
.text-monospace {
|
|
179
|
+
font-family: SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
|
|
180
|
+
}
|
|
181
|
+
.text-muted {
|
|
182
|
+
color: #666;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
em, i {
|
|
187
|
+
font-style: italic;
|
|
188
|
+
}
|
|
189
|
+
strong, b {
|
|
190
|
+
font-weight: 700;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/* body content table */
|
|
194
|
+
.table {
|
|
195
|
+
width: 100%;
|
|
196
|
+
margin: 12px 0 24px;
|
|
197
|
+
border-collapse: collapse;
|
|
198
|
+
|
|
199
|
+
th, td {
|
|
200
|
+
padding: 6px 10px;
|
|
201
|
+
vertical-align: top;
|
|
202
|
+
text-align: left;
|
|
203
|
+
border-bottom: 1px solid var(--mail-gray);
|
|
204
|
+
}
|
|
205
|
+
th {
|
|
206
|
+
font-weight: 600;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
thead {
|
|
210
|
+
th {
|
|
211
|
+
border-bottom-width: 2px;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// config postcss per le CSS delle email (npm run "css email")
|
|
2
|
+
// NB: in ESM perché postcss-nested >= 8 è ESM-only
|
|
3
|
+
//
|
|
4
|
+
// https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-custom-properties#readme
|
|
5
|
+
|
|
6
|
+
import postcssNested from 'postcss-nested';
|
|
7
|
+
import postcssImport from 'postcss-import';
|
|
8
|
+
import customProperties from 'postcss-custom-properties';
|
|
9
|
+
import autoprefixer from 'autoprefixer';
|
|
10
|
+
import cssnano from 'cssnano';
|
|
11
|
+
|
|
12
|
+
// postcss-custom-properties mantiene sempre le dichiarazioni `--*` in :root:
|
|
13
|
+
// una volta risolte le var() non servono più (e i client email non le supportano).
|
|
14
|
+
// Rimuove anche i commenti (finirebbero nel <style> dell'email) e le regole rimaste vuote.
|
|
15
|
+
const cleanup = {
|
|
16
|
+
postcssPlugin: 'email-css-cleanup',
|
|
17
|
+
OnceExit(root) {
|
|
18
|
+
root.walkComments(comment => comment.remove());
|
|
19
|
+
root.walkDecls(/^--/, decl => decl.remove());
|
|
20
|
+
root.walkRules(rule => { if (rule.nodes.length === 0) rule.remove(); });
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export default () => {
|
|
25
|
+
const plugins = [
|
|
26
|
+
// gli @import vanno risolti prima del nesting e delle custom properties
|
|
27
|
+
postcssImport(),
|
|
28
|
+
postcssNested(),
|
|
29
|
+
// risolve le var(--mail-*) e rimuove il blocco :root
|
|
30
|
+
// (i client email non supportano le custom properties)
|
|
31
|
+
customProperties({ preserve: false }),
|
|
32
|
+
cleanup,
|
|
33
|
+
autoprefixer(),
|
|
34
|
+
// https://cssnano.github.io/cssnano/docs/what-are-optimisations/
|
|
35
|
+
// minifySelectors riscrive `.a td, .a th` in `.a :is(td,th)`,
|
|
36
|
+
// non supportato da Gmail/Outlook
|
|
37
|
+
cssnano({ preset: ['default', { minifySelectors: false }] }),
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
return { plugins };
|
|
41
|
+
};
|
|
@@ -18,17 +18,17 @@
|
|
|
18
18
|
],
|
|
19
19
|
"scripts": {
|
|
20
20
|
"progetto JS webpack DEV": "npm run 'launchChrome' && NODE_ENV=development webpack serve --config ./webpack.config.mjs #--open-app-name 'Google Chrome'",
|
|
21
|
-
"
|
|
21
|
+
"php server": "php -S localhost:[[port8000]] # -t root_dir/",
|
|
22
|
+
"progetto sf webpack DEV": "rm -rf ./public/_dev && NODE_ENV=development webpack serve --config ./webpack.config.mjs # http://192.168.1.135:[[port5700]",
|
|
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`",
|
|
22
24
|
"webpack PROD": "npm run 'UPD-version' && NODE_ENV=production webpack --config ./webpack.config.mjs --stats minimal",
|
|
23
25
|
"eslint-check": "npx eslint app/",
|
|
24
26
|
"create-favicons INIT": "npx create-favicons init",
|
|
25
27
|
"create-favicons": "npx create-favicons --dir=./app/favicons",
|
|
26
28
|
"UPD-version": "npx update-version --no-log-v0 --log-file=./changelog.md # --log-patch --pkg=alt/path/to/package.json",
|
|
27
29
|
"upd@m": "npx upd@m",
|
|
28
|
-
"launchChrome": "open -n -a 'Google Chrome' --args --profile-directory='Profile 3'",
|
|
29
|
-
"python server": "python3 -m http.server
|
|
30
|
-
"php server": "php -S localhost:8000 # -t root_dir/",
|
|
31
|
-
"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`",
|
|
30
|
+
"launchChrome": "open -n -a 'Google Chrome' --args --profile-directory='Profile 3' # mbn: 1 / mg2: 3",
|
|
31
|
+
"python server": "python3 -m http.server [[port8000]] # --directory __dirname__ # 8000 = default port",
|
|
32
32
|
"node server": "node serve # richiede ‘npm i serve’",
|
|
33
33
|
"build tokens": "npx buildTokens --config ./app/css/tokens-config.mjs",
|
|
34
34
|
"check unresolved props": "npx checkUnresolvedProps --config ./app/css/tokens-config.mjs",
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{# template base per tutte le email #}
|
|
2
|
+
{% apply inline_css(source('email/css/mail-inline.css')) %}
|
|
3
|
+
<!DOCTYPE html>
|
|
4
|
+
<html lang="it">
|
|
5
|
+
<head>
|
|
6
|
+
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
|
7
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
8
|
+
<meta name="format-detection" content="telephone=no">
|
|
9
|
+
<title>{% block title %}{% endblock %}</title>
|
|
10
|
+
<style type="text/css">
|
|
11
|
+
{%- include 'email/css/mail-inline.css' -%}
|
|
12
|
+
{%- include 'email/css/mail-embed.css' -%}
|
|
13
|
+
</style>
|
|
14
|
+
</head>
|
|
15
|
+
<body class="yahoo-fix" leftmargin="0" topmargin="0" marginwidth="0" marginheight="0">
|
|
16
|
+
{# Wrapper table #}
|
|
17
|
+
<table width="100%" border="0" cellpadding="0" cellspacing="0" align="center" id="wrapper">
|
|
18
|
+
<tbody>
|
|
19
|
+
<tr>
|
|
20
|
+
<td width="100%">
|
|
21
|
+
{# logo #}
|
|
22
|
+
<table class="content header device-width" width="600" border="0" cellpadding="0" cellspacing="0" align="center">
|
|
23
|
+
<tbody>
|
|
24
|
+
<tr>
|
|
25
|
+
<td class="logo" width="100%">
|
|
26
|
+
{% block logo %}
|
|
27
|
+
{# immagine allegata inline (cid:), vedi templates/email/imgs #}
|
|
28
|
+
<img src="{{ email.image('email/imgs/logo-ict-governance-email.png', 'image/png', 'logo-ict-governance.png') }}" alt="ICT Governance" width="200" height="54" border="0">
|
|
29
|
+
{%- endblock %}
|
|
30
|
+
</td>
|
|
31
|
+
</tr>
|
|
32
|
+
</tbody>
|
|
33
|
+
</table>
|
|
34
|
+
|
|
35
|
+
{# content #}
|
|
36
|
+
<table class="content body device-width" width="600" border="0" cellpadding="0" cellspacing="0" align="center">
|
|
37
|
+
<tbody>
|
|
38
|
+
<tr>
|
|
39
|
+
<td>
|
|
40
|
+
<h1 class="text-center">
|
|
41
|
+
{%- block title_h1 %}{{ block('title') }}{% endblock -%}
|
|
42
|
+
</h1>
|
|
43
|
+
{% block content %}{% endblock %}
|
|
44
|
+
</td>
|
|
45
|
+
</tr>
|
|
46
|
+
</tbody>
|
|
47
|
+
</table>
|
|
48
|
+
|
|
49
|
+
{# footer #}
|
|
50
|
+
<table class="content footer device-width" width="600" border="0" cellpadding="0" cellspacing="0" align="center">
|
|
51
|
+
<tbody>
|
|
52
|
+
<tr>
|
|
53
|
+
<td>{% block footer %}Bluvacanze{%- endblock %}</td>
|
|
54
|
+
</tr>
|
|
55
|
+
</tbody>
|
|
56
|
+
</table>
|
|
57
|
+
</td>
|
|
58
|
+
</tr>
|
|
59
|
+
</tbody>
|
|
60
|
+
</table>
|
|
61
|
+
</body>
|
|
62
|
+
</html>
|
|
63
|
+
{% endapply %}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
a,body, p, table, td{-webkit-text-size-adjust:100%;-moz-text-size-adjust:100%;text-size-adjust:100%}.content.body a{font-weight:700;color:#036;text-decoration:underline;cursor:pointer}.content.body a:hover{color:#010112}@media only screen and (max-width:640px){body[class="yahoo-fix"] .device-width{width:440px!important}.content.columns-wrapper td.column-spacer{height:0!important} .content.columns-wrapper td.column-spacer,.content.columns-wrapper td.column-width{display:block!important;width:100%!important}.content.columns-wrapper .column-content td h2{font-size:24px!important}.content.columns-wrapper .column-content td .text{font-size:18px!important}.content.columns-wrapper .column-content td .buttons{font-size:21px!important}}@media only screen and (max-width:479px){body[class="yahoo-fix"] .device-width{width:280px!important}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
body{width:100%;padding:0;margin:0;font-family:helvetica,arial,sans-serif;font-style:normal;font-weight:400;line-height:1.5;color:#333;background-color:#efefef;-webkit-font-smoothing:antialiased}body .device-width{width:600px;padding:0}table{border-collapse:collapse}p{mso-table-lspace:0;mso-table-rspace:0;margin-top:12px;margin-bottom:12px}#wrapper{width:100%;margin:0 auto;border:none}#wrapper > tbody > tr > td{width:100%;padding:10px 10px 20px;background-color:#efefef;border:none}.content{margin:0 auto}.content.header > tbody > tr > td{padding:10px;text-align:center;background:#efefef}.content.header > tbody > tr > td > img{display:inline-block;max-width:100%;height:auto}.content.header .logo{padding-top:10px;padding-bottom:10px}.content.body > tbody > tr > td{padding:20px 30px;vertical-align:top;color:#333;background-color:#fff}.content.body > tbody > tr > td a{font-weight:700;color:#036;text-decoration:underline;cursor:pointer}.content.body > tbody > tr > td a:hover{color:#010112}.content.body > tbody > tr > td h1{margin-top:0;margin-bottom:24px;font-size:28px;font-weight:700}.content.body > tbody > tr > td p{font-size:21px}.content.body > tbody > tr > td img{max-width:100%;height:auto}.content.footer td{padding-top:10px;padding-bottom:10px;font-size:80%;color:#373737;text-align:center;background:#efefef}.logo-network{display:inline-block;padding:5px;margin:10px auto;background-color:#efefef}.network-info{font-size:13px;font-style:italic;color:#888}.text-center{text-align:center}.text-right{text-align:right}.text-left{text-align:left}.small, small{font-size:13px}.margin-top{margin-top:24px}.margin-bottom{margin-bottom:24px}.text-monospace{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-muted{color:#666}em, i{font-style:italic} b,strong{font-weight:700}.table{width:100%;margin:12px 0 24px;border-collapse:collapse} .table td,.table th{padding:6px 10px;vertical-align:top;text-align:left;border-bottom:1px solid #4b4a4a}.table th{font-weight:600}.table thead th{border-bottom-width:2px}
|
|
@@ -39,9 +39,10 @@ const __filename = fileURLToPath(import.meta.url)
|
|
|
39
39
|
|
|
40
40
|
const isDevelopment = process.env.NODE_ENV === 'development'
|
|
41
41
|
,devServerPort = [[port5700]]
|
|
42
|
-
|
|
42
|
+
,apiPort = [[port8000]] // eslint-disable-line no-unused-vars
|
|
43
43
|
,useSass = false
|
|
44
44
|
,inlineCssInDevMode = true
|
|
45
|
+
// sf: inlineCssInDevMode = false
|
|
45
46
|
,useSvgo = true
|
|
46
47
|
,useSvgr = false // svg per react
|
|
47
48
|
,svgoConfig = useSvgo? (await import('./webpack-config-modules/svgo.config.mjs')).default : null
|
|
@@ -59,6 +60,11 @@ const isDevelopment = process.env.NODE_ENV === 'development'
|
|
|
59
60
|
,favicons_path = path.resolve(__dirname, './app/favicons/output') // commentare se non usato
|
|
60
61
|
,favicons_path_regexp = /favicons\/output/ // source pattern per le favicons (regexp o null)
|
|
61
62
|
|
|
63
|
+
//sf:
|
|
64
|
+
//,favicons_path
|
|
65
|
+
// ,favicons_path_regexp = null
|
|
66
|
+
|
|
67
|
+
|
|
62
68
|
// NB: jsconfig.json va tenuto in questa stessa dir (gli alias sono risolti
|
|
63
69
|
// a partire dalla sua posizione)
|
|
64
70
|
,jsConfigAliases = getJsConfigAliases(path.resolve(__dirname, './jsconfig.json'))
|
|
@@ -91,8 +97,9 @@ const isDevelopment = process.env.NODE_ENV === 'development'
|
|
|
91
97
|
// limitata tra le pagine. Da rivedere in futuro se la duplicazione dovesse
|
|
92
98
|
// crescere.
|
|
93
99
|
//
|
|
94
|
-
// TODO: se in futuro la duplicazione di css tra le pagine dovesse crescere
|
|
95
|
-
//
|
|
100
|
+
// TODO: shared.css -> attualmente non generato, ma se in futuro la duplicazione di css tra le pagine dovesse crescere
|
|
101
|
+
// e se si risolve un problema con purgeCSS
|
|
102
|
+
// (componenti importati dal js di più entry), valutare un cacheGroup `shared`
|
|
96
103
|
// dedicato ai css condivisi: andranno escluse le entry `.critical` (devono
|
|
97
104
|
// restare autosufficienti), gestite in safelist.variables le custom properties
|
|
98
105
|
// definite/consumate tra asset diversi (vedi purgecss-variables-safelist.mjs),
|
|
@@ -104,8 +111,8 @@ const shared_chunk_paths = (module) => {
|
|
|
104
111
|
const sep = '[\\\\/]'; // stringa che produce [\\/] nel pattern
|
|
105
112
|
const pathsRegexp = new RegExp([
|
|
106
113
|
'node_modules',
|
|
107
|
-
'app/js',
|
|
108
114
|
'app/src',
|
|
115
|
+
'app/src/js',
|
|
109
116
|
'app/src/web-components',
|
|
110
117
|
'app/src/components',
|
|
111
118
|
].map(p => `${sep}${p.replace(/\//g, sep)}${sep}`).join('|'));
|
|
@@ -441,8 +448,8 @@ const config = {
|
|
|
441
448
|
start_year,
|
|
442
449
|
...(current_year > start_year? [current_year] : [])
|
|
443
450
|
].join('-')
|
|
444
|
-
|
|
445
|
-
,vers = packageJson.version
|
|
451
|
+
,vers = packageJson.version.split('.').slice(0,-1).join('.')
|
|
452
|
+
// ,vers = packageJson.version
|
|
446
453
|
;
|
|
447
454
|
|
|
448
455
|
const author = packageJson.author?.name ?? (packageJson.author ?? '');
|
|
@@ -27,6 +27,11 @@ DIM='\033[2m' # Dim
|
|
|
27
27
|
NC='\033[0m' # No Color
|
|
28
28
|
|
|
29
29
|
DEBUG=FALSE
|
|
30
|
+
for arg in "$@"; do
|
|
31
|
+
case "$arg" in
|
|
32
|
+
--debug) DEBUG=TRUE ;;
|
|
33
|
+
esac
|
|
34
|
+
done
|
|
30
35
|
|
|
31
36
|
# Questo script richiede zsh: se lanciato con sh/bash blocca l'esecuzione
|
|
32
37
|
if [ -z "$ZSH_VERSION" ]; then
|
|
@@ -49,46 +54,66 @@ PORT_8000="80${PORT_SUFFIX}"
|
|
|
49
54
|
PORT_5700="57${PORT_SUFFIX}"
|
|
50
55
|
echo -e "${DIM}Porte: ${PORT_8000} (server), ${PORT_5700} (webpack dev server)${NC}"
|
|
51
56
|
|
|
52
|
-
# Pacchetti opzionali: installati
|
|
53
|
-
#
|
|
54
|
-
#
|
|
55
|
-
#
|
|
56
|
-
#
|
|
57
|
-
#
|
|
58
|
-
#
|
|
59
|
-
#
|
|
60
|
-
#
|
|
61
|
-
#
|
|
62
|
-
#
|
|
57
|
+
# Pacchetti opzionali: installati solo su conferma esplicita dell'utente
|
|
58
|
+
# (vedi ciclo di domande più sotto). Include sia devDependency "pure" del
|
|
59
|
+
# progetto, sia i peer dependencies opzionali di @massimo-cassandro/minimo
|
|
60
|
+
# (vedi package.json di minimo, campi peerDependencies/peerDependenciesMeta:
|
|
61
|
+
# elenco mantenuto a mano, non letto dinamicamente da minimo — se cambiano va
|
|
62
|
+
# aggiornato manualmente anche qui).
|
|
63
|
+
# Ogni voce è "<pacchetto> | <dev> | <descrizione>" (pacchetto e colonna <dev>
|
|
64
|
+
# allineati a colonna fissa solo per leggibilità, non richiesto dal parsing).
|
|
65
|
+
# La colonna <pacchetto> può contenere più pacchetti separati da spazio: in tal
|
|
66
|
+
# caso vengono proposti con un'unica domanda e, se confermati, installati tutti
|
|
67
|
+
# (uno per uno) con la stessa modalità indicata dalla colonna <dev>.
|
|
68
|
+
# La colonna <dev> vale "dev" se il pacchetto va installato con `npm i -D`
|
|
69
|
+
# (devDependency), stringa vuota se va installato con `npm i -S` (dependency
|
|
70
|
+
# vera, es. peer dependency di minimo usata a runtime).
|
|
63
71
|
optionalPackages=(
|
|
64
|
-
"dotenv-webpack
|
|
65
|
-
"@principalstudio/html-webpack-inject-preload | injection dei tag <link rel=preload> nell'html generato"
|
|
66
|
-
"postcss-jit-props
|
|
67
|
-
"@svgdotjs/svg.js
|
|
68
|
-
"blurhash
|
|
69
|
-
"simple-datatables
|
|
70
|
-
"style-dictionary
|
|
72
|
+
"dotenv-webpack | dev | gestione variabili d'ambiente (.env) nel bundle webpack"
|
|
73
|
+
"@principalstudio/html-webpack-inject-preload | dev | injection dei tag <link rel=preload> nell'html generato"
|
|
74
|
+
"postcss-jit-props | dev | include nel CSS compilato solo le custom properties effettivamente usate"
|
|
75
|
+
"@svgdotjs/svg.js | | richiesto da charts/"
|
|
76
|
+
"blurhash | | richiesto da unsplash-page (create-blurhash-canvas)"
|
|
77
|
+
"simple-datatables | | richiesto da s-datatable-component"
|
|
78
|
+
"style-dictionary | dev | richiesto da design-tokens/utilities (build-tokens)"
|
|
79
|
+
"@cybozu/eslint-config | dev | utilizzato da eslint per il check dei file css"
|
|
80
|
+
"postcss-cli postcss-import postcss-nested cssnano | dev | per css email"
|
|
71
81
|
)
|
|
72
82
|
|
|
83
|
+
# Rimuove spazi iniziali/finali da una stringa (usata per i campi estratti
|
|
84
|
+
# da optionalPackages, allineati a colonna fissa con spazi di padding).
|
|
85
|
+
trim() {
|
|
86
|
+
echo "$1" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//'
|
|
87
|
+
}
|
|
88
|
+
|
|
73
89
|
# Chiede conferma per un singolo pacchetto opzionale (default: No).
|
|
74
90
|
ask_optional_pkg() {
|
|
75
91
|
local pkg="$1"
|
|
76
92
|
local desc="$2"
|
|
77
93
|
local reply
|
|
78
|
-
|
|
94
|
+
echo -e "\n${YELLOW}${pkg}${NC}"
|
|
95
|
+
echo -e "${DIM}${desc}${NC}"
|
|
96
|
+
read "reply?Installare questo pacchetto opzionale? [y/N]: "
|
|
79
97
|
[[ "$reply" =~ ^[Yy]$ ]]
|
|
80
98
|
}
|
|
81
99
|
|
|
82
|
-
|
|
100
|
+
selectedOptionalDevPkgs=()
|
|
101
|
+
selectedOptionalDepPkgs=()
|
|
83
102
|
|
|
84
103
|
if [ "$DEBUG" != "TRUE" ]; then
|
|
85
104
|
echo -e "\n${GREEN}...PACCHETTI OPZIONALI${NC}"
|
|
86
105
|
for entry in "${optionalPackages[@]}"; do
|
|
87
|
-
pkg
|
|
88
|
-
pkg="$
|
|
89
|
-
|
|
106
|
+
IFS='|' read -r pkg devFlag desc <<< "$entry"
|
|
107
|
+
pkg="$(trim "$pkg")"
|
|
108
|
+
devFlag="$(trim "$devFlag")"
|
|
109
|
+
desc="$(trim "$desc")"
|
|
90
110
|
if ask_optional_pkg "$pkg" "$desc"; then
|
|
91
|
-
|
|
111
|
+
# ${=pkg}: word splitting di zsh, per le voci con più pacchetti
|
|
112
|
+
if [ "$devFlag" = "dev" ]; then
|
|
113
|
+
selectedOptionalDevPkgs+=(${=pkg})
|
|
114
|
+
else
|
|
115
|
+
selectedOptionalDepPkgs+=(${=pkg})
|
|
116
|
+
fi
|
|
92
117
|
fi
|
|
93
118
|
done
|
|
94
119
|
fi
|
|
@@ -182,6 +207,8 @@ force_cat() {
|
|
|
182
207
|
# riferiti al nome del file sorgente, prima di un eventuale rename_fn) da
|
|
183
208
|
# saltare: usato per escludere package-tpl.json, già copiato a parte prima
|
|
184
209
|
# dell'installazione dei pacchetti npm (vedi più sotto).
|
|
210
|
+
# I file .DS_Store (macOS) vengono sempre ignorati: sono binari e farebbero
|
|
211
|
+
# fallire il sed di safe_cat_with_ports ("illegal byte sequence").
|
|
185
212
|
install_tree() {
|
|
186
213
|
local src_root="$1"
|
|
187
214
|
local dest_root="$2"
|
|
@@ -208,7 +235,7 @@ install_tree() {
|
|
|
208
235
|
dest="${dest_root}/${dir}/${new_base}"
|
|
209
236
|
fi
|
|
210
237
|
"$copy_fn" "$file" "$dest"
|
|
211
|
-
done < <(find "$src_root" -type f)
|
|
238
|
+
done < <(find "$src_root" -type f ! -name .DS_Store)
|
|
212
239
|
}
|
|
213
240
|
|
|
214
241
|
# Rename applicato ai file di source_files/root/ in fase di copia:
|
|
@@ -304,8 +331,10 @@ devDependencies=(
|
|
|
304
331
|
)
|
|
305
332
|
|
|
306
333
|
# pacchetti opzionali selezionati dall'utente (vedi ciclo di domande a inizio
|
|
307
|
-
# script),
|
|
308
|
-
|
|
334
|
+
# script), instradati in devDependencies o dependencies in base alla colonna
|
|
335
|
+
# <dev> di optionalPackages (vedi sopra)
|
|
336
|
+
devDependencies+=("${selectedOptionalDevPkgs[@]}")
|
|
337
|
+
dependencies+=("${selectedOptionalDepPkgs[@]}")
|
|
309
338
|
|
|
310
339
|
if [ "$DEBUG" = "TRUE" ]; then
|
|
311
340
|
dependencies=( @massimo-cassandro/minimo )
|
|
@@ -400,9 +429,9 @@ force_cat "${MINIMO_PKG_DIR}/design-tokens/tokens-config-sample.mjs" "${FRONTEND
|
|
|
400
429
|
force_cat "${MINIMO_PKG_DIR}/dev-tools/create-favicons/src/default-params.mjs" "${FRONTEND_INSTALL_PATH}/favicons/create-favicons-cfg.mjs"
|
|
401
430
|
|
|
402
431
|
|
|
403
|
-
if [ ${#
|
|
432
|
+
if [ ${#selectedOptionalDevPkgs[@]} -gt 0 ] || [ ${#selectedOptionalDepPkgs[@]} -gt 0 ]; then
|
|
404
433
|
echo -e "\n${YELLOW}Pacchetti opzionali installati:${NC}"
|
|
405
|
-
for pkg in "${
|
|
434
|
+
for pkg in "${selectedOptionalDevPkgs[@]}" "${selectedOptionalDepPkgs[@]}"; do
|
|
406
435
|
echo -e "${YELLOW} - $pkg${NC}"
|
|
407
436
|
done
|
|
408
437
|
echo -e "${YELLOW}Potrebbe essere necessaria una modifica alla configurazione webpack e/o postcss per utilizzare questi pacchetti.${NC}"
|
package/package.json
CHANGED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="it">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
|
6
|
+
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
|
7
|
+
<title>{%- block title -%}{%- endblock %} | Marketing</title>
|
|
8
|
+
<meta name="robots" content="noindex, nofollow, noarchive">
|
|
9
|
+
{%- include 'incl/favicons.incl.html.twig'-%}
|
|
10
|
+
{{- '' -}}
|
|
11
|
+
<link rel="preload" href="{{ asset('index.js') }}" as="script">
|
|
12
|
+
<link rel="preload" href="{{ asset('shared.js') }}" as="script">
|
|
13
|
+
<link rel="preload" href="{{ asset('runtime.js') }}" as="script">
|
|
14
|
+
{{- '' -}}
|
|
15
|
+
<link rel="preload" href="{{ asset('index.css') }}" as="style">
|
|
16
|
+
<link rel="stylesheet" href="{{ asset('index.css') }}" type="text/css" media="all">
|
|
17
|
+
<style>
|
|
18
|
+
{%- include '@public_path' ~ asset('layout.critical.css') -%}
|
|
19
|
+
</style>
|
|
20
|
+
{%- block head -%}{%- endblock -%}
|
|
21
|
+
</head>
|
|
22
|
+
<body>
|
|
23
|
+
|
|
24
|
+
<script src="{{ asset('runtime.js') }}"></script>
|
|
25
|
+
<script src="{{ asset('shared.js') }}"></script>
|
|
26
|
+
<script src="{{ asset('index.js') }}"></script>
|
|
27
|
+
{%- block js -%}{%- endblock -%}
|
|
28
|
+
</body>
|
|
29
|
+
</html>
|
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
{#-
|
|
2
|
-
https://symfony.com/doc/current/controller/error_pages.html#testing-error-pages-during-development
|
|
3
|
-
|
|
4
|
-
test: https://project.local:8890/_error/500 (or 403, 404... )
|
|
5
|
-
-#}
|
|
1
|
+
{#- https://symfony.com/doc/current/controller/error_pages.html#testing-error-pages-during-development -#}
|
|
6
2
|
<!DOCTYPE html>
|
|
7
|
-
<html lang="it" data-err-code="{{ status_code }}" data-err-text="{{ status_text
|
|
3
|
+
<html lang="it" data-err-code="{{ status_code }}" data-err-text="{{ status_text}}">
|
|
8
4
|
<head>
|
|
9
5
|
<meta charset="utf-8">
|
|
10
6
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
|
@@ -12,16 +8,14 @@ test: https://project.local:8890/_error/500 (or 403, 404... )
|
|
|
12
8
|
<title>Errore {{ status_code }}</title>
|
|
13
9
|
<meta name="robots" content="noindex, nofollow, noarchive">
|
|
14
10
|
{%- include 'incl/favicons.incl.html.twig'-%}
|
|
15
|
-
<link rel="preload" href="{{ asset('
|
|
16
|
-
<link rel="stylesheet" href="{{ asset('
|
|
11
|
+
<link rel="preload" href="{{ asset('marketing.css') }}" as="style">
|
|
12
|
+
<link rel="stylesheet" href="{{ asset('marketing.css') }}" media="all">
|
|
17
13
|
<link rel="preload" href="{{ asset('error-pages.css') }}" as="style">
|
|
18
14
|
<link rel="stylesheet" href="{{ asset('error-pages.css') }}" media="all">
|
|
19
15
|
<link rel="preload" href="{{ asset('error-pages.js') }}" as="script">
|
|
20
16
|
|
|
21
|
-
<link rel="preload" href="{{ asset('
|
|
17
|
+
<link rel="preload" href="{{ asset('shared.js') }}" as="script">
|
|
22
18
|
<link rel="preload" href="{{ asset('runtime.js') }}" as="script">
|
|
23
|
-
<link rel="preload" href="{{ asset('shared.css') }}" as="style">
|
|
24
|
-
<link rel="stylesheet" href="{{ asset('shared.css') }}" type="text/css" media="all">
|
|
25
19
|
</head>
|
|
26
20
|
<body>
|
|
27
21
|
<div id="root"></div>
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# symfony + webpack config
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
## symfony config folder
|
|
5
|
+
|
|
6
|
+
### `config/services.yaml`
|
|
7
|
+
|
|
8
|
+
```
|
|
9
|
+
parameters:
|
|
10
|
+
assets_dir: '%env(ASSETS_DIR)%'
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### `config/packages/framework.yaml`
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
# see https://symfony.com/doc/current/reference/configuration/framework.html
|
|
17
|
+
framework:
|
|
18
|
+
# [...]
|
|
19
|
+
|
|
20
|
+
assets:
|
|
21
|
+
json_manifest_path: "%kernel.project_dir%/public/%env(ASSETS_DIR)%/manifest.json"
|
|
22
|
+
# base_path: '%env(ASSETS_DIR)%'
|
|
23
|
+
|
|
24
|
+
# manifest specifici
|
|
25
|
+
packages:
|
|
26
|
+
dashboard:
|
|
27
|
+
json_manifest_path: "%kernel.project_dir%/public/dashboard/manifest.json"
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### `config/packages/twig.yaml`
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
twig:
|
|
34
|
+
file_name_pattern: '*.twig'
|
|
35
|
+
|
|
36
|
+
form_themes:
|
|
37
|
+
- '_tpl/incl/bs5-form-layout.html.twig'
|
|
38
|
+
paths:
|
|
39
|
+
'%kernel.project_dir%/public/%env(ASSETS_DIR)%': assets_path
|
|
40
|
+
'%kernel.project_dir%/public': public_path
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
## .env e .env.dev (o .env.local o ...)
|
|
45
|
+
|
|
46
|
+
In `.env`:
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
###> supporto webpack
|
|
50
|
+
# cartella, dentro public, in cui webpack salva js, css immagini ecc
|
|
51
|
+
ASSETS_DIR=build
|
|
52
|
+
###<
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
In `.env.dev`:
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
###> supporto webpack
|
|
59
|
+
# cartella, dentro public, in cui webpack salva js, css immagini ecc
|
|
60
|
+
ASSETS_DIR=_dev
|
|
61
|
+
###<
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
## .gitignore
|
|
66
|
+
|
|
67
|
+
Aggiungere
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
public/_dev
|
|
71
|
+
```
|
|
@@ -25,6 +25,10 @@ const legacyParamNames = {
|
|
|
25
25
|
use_warning_icon: 'useAltIcon'
|
|
26
26
|
};
|
|
27
27
|
|
|
28
|
+
// TODO aggiungere metodo setDefaults per cambiare a livello globale i valori di default
|
|
29
|
+
// senza doverli ripetere ad ogni istanza del modulo
|
|
30
|
+
|
|
31
|
+
|
|
28
32
|
const defaults = {
|
|
29
33
|
|
|
30
34
|
// settings applied to all dialog types; can be overridden per type
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
/*! minimo - Validation Error */
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
/*
|
|
3
|
+
usage:
|
|
4
|
+
throw new ValidationError('Duplicate values found');
|
|
5
|
+
throw new ValidationError('Duplicate values found', errorFields);
|
|
6
|
+
throw new ValidationError('Duplicate values found', errorFields, 'Each value can be used only once');
|
|
7
|
+
*/
|
|
4
8
|
|
|
5
9
|
/*
|
|
6
10
|
example:
|
|
@@ -51,16 +55,29 @@ form.addEventListener('submit', e => {
|
|
|
51
55
|
|
|
52
56
|
/**
|
|
53
57
|
* Custom error class for form validation failures.
|
|
54
|
-
* Extends the native Error with an array of invalid form fields
|
|
58
|
+
* Extends the native Error with an optional array of invalid form fields
|
|
59
|
+
* and an optional extended description.
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* throw new ValidationError('Duplicate values found', [field1, field2], 'Each value can be used only once');
|
|
63
|
+
*
|
|
64
|
+
* // parameters with default values
|
|
65
|
+
* throw new ValidationError(
|
|
66
|
+
* 'Duplicate values found', // message (required)
|
|
67
|
+
* null, // fields (default: null)
|
|
68
|
+
* null // description (default: null)
|
|
69
|
+
* );
|
|
55
70
|
*/
|
|
56
71
|
export class ValidationError extends Error {
|
|
57
72
|
/**
|
|
58
73
|
* @param {string} message - Error message.
|
|
59
|
-
* @param {Element[]} fields - Array of invalid form field elements
|
|
74
|
+
* @param {Element[]|null} [fields] - Array of invalid form field elements (default: null)
|
|
75
|
+
* @param {string|null} [description] - Extended description of the error (default: null)
|
|
60
76
|
*/
|
|
61
|
-
constructor(message, fields) {
|
|
77
|
+
constructor(message, fields = null, description = null) {
|
|
62
78
|
super(message);
|
|
63
79
|
this.name = 'ValidationError';
|
|
64
80
|
this.fields = fields;
|
|
81
|
+
this.description = description;
|
|
65
82
|
}
|
|
66
83
|
}
|