@webround/core 0.1.8 → 0.3.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.
- package/dist/base-css.d.ts +8 -0
- package/dist/base-css.js +19 -0
- package/dist/components/WrAltcha.d.ts +5 -0
- package/dist/components/WrAltcha.js +7 -0
- package/dist/components/WrButton.js +19 -0
- package/dist/components/WrForm.d.ts +7 -0
- package/dist/components/WrForm.js +15 -0
- package/dist/components/WrFormPrivacy.d.ts +4 -0
- package/dist/components/WrFormPrivacy.js +6 -0
- package/dist/components/WrFormSuccess.d.ts +4 -0
- package/dist/components/WrFormSuccess.js +6 -0
- package/dist/components/WrIframe.d.ts +8 -0
- package/dist/components/WrIframe.js +13 -0
- package/dist/components/WrImage.d.ts +9 -0
- package/dist/components/WrImage.js +13 -0
- package/dist/components/WrInput.d.ts +8 -0
- package/dist/components/WrInput.js +10 -0
- package/dist/components/WrLangSelector.d.ts +17 -0
- package/dist/components/WrLangSelector.js +27 -0
- package/dist/components/WrLangSync.d.ts +6 -0
- package/dist/components/WrLangSync.js +12 -0
- package/dist/components/WrLink.js +23 -0
- package/dist/components/WrNav.d.ts +26 -0
- package/dist/components/WrNav.js +15 -0
- package/dist/{WrSection.d.ts → components/WrSection.d.ts} +3 -3
- package/dist/components/WrShared.d.ts +22 -0
- package/dist/components/WrShared.js +18 -0
- package/dist/components/WrText.d.ts +11 -0
- package/dist/components/WrText.js +14 -0
- package/dist/components/WrThemeToggle.d.ts +9 -0
- package/dist/components/WrThemeToggle.js +43 -0
- package/dist/components/WrVideo.js +61 -0
- package/dist/context/WrAppContext.d.ts +40 -0
- package/dist/context/WrAppContext.js +95 -0
- package/dist/context/WrLayout.d.ts +23 -0
- package/dist/context/WrLayout.js +18 -0
- package/dist/context/WrPageContext.d.ts +10 -0
- package/dist/context/WrPageContext.js +13 -0
- package/dist/form-react.d.ts +82 -0
- package/dist/form-react.js +165 -0
- package/dist/form.d.ts +83 -0
- package/dist/form.js +175 -0
- package/dist/index.css +651 -0
- package/dist/index.d.ts +20 -8
- package/dist/index.js +20 -8
- package/dist/kinds.d.ts +109 -0
- package/dist/kinds.js +35 -0
- package/dist/lang-codes.d.ts +366 -0
- package/dist/lang-codes.js +166 -0
- package/dist/localized.d.ts +36 -0
- package/dist/localized.js +71 -0
- package/dist/not-found.d.ts +9 -0
- package/dist/not-found.js +13 -0
- package/dist/routing.d.ts +122 -0
- package/dist/routing.js +198 -0
- package/dist/seo-react.d.ts +20 -0
- package/dist/seo-react.js +37 -0
- package/dist/seo.d.ts +167 -0
- package/dist/seo.js +221 -0
- package/dist/site-env.d.ts +25 -0
- package/dist/site-env.js +37 -0
- package/dist/style.d.ts +456 -0
- package/dist/style.js +1081 -0
- package/dist/text-react.d.ts +11 -0
- package/dist/text-react.js +5 -0
- package/dist/theme.d.ts +22 -0
- package/dist/theme.js +45 -0
- package/dist/types.d.ts +37 -21
- package/package.json +93 -7
- package/dist/WrButton.d.ts.map +0 -1
- package/dist/WrButton.js +0 -7
- package/dist/WrButton.js.map +0 -1
- package/dist/WrImage.d.ts +0 -8
- package/dist/WrImage.d.ts.map +0 -1
- package/dist/WrImage.js +0 -7
- package/dist/WrImage.js.map +0 -1
- package/dist/WrLink.d.ts.map +0 -1
- package/dist/WrLink.js +0 -15
- package/dist/WrLink.js.map +0 -1
- package/dist/WrPageContext.d.ts +0 -8
- package/dist/WrPageContext.d.ts.map +0 -1
- package/dist/WrPageContext.js +0 -11
- package/dist/WrPageContext.js.map +0 -1
- package/dist/WrSection.d.ts.map +0 -1
- package/dist/WrSection.js.map +0 -1
- package/dist/WrText.d.ts +0 -11
- package/dist/WrText.d.ts.map +0 -1
- package/dist/WrText.js +0 -7
- package/dist/WrText.js.map +0 -1
- package/dist/WrVideo.d.ts.map +0 -1
- package/dist/WrVideo.js +0 -42
- package/dist/WrVideo.js.map +0 -1
- package/dist/bridge.d.ts +0 -2
- package/dist/bridge.d.ts.map +0 -1
- package/dist/bridge.js +0 -110
- package/dist/bridge.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
- /package/dist/{WrButton.d.ts → components/WrButton.d.ts} +0 -0
- /package/dist/{WrLink.d.ts → components/WrLink.d.ts} +0 -0
- /package/dist/{WrSection.js → components/WrSection.js} +0 -0
- /package/dist/{WrVideo.d.ts → components/WrVideo.d.ts} +0 -0
package/dist/form.js
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { wrLocalized } from './localized.js';
|
|
2
|
+
// The rules of a Webround form, with no React and no network: what a field accepts, what is said when it does not, and what is sent.
|
|
3
|
+
// The real form (core) and the simulated one (editor) read the same rules, so a test form in the editor refuses exactly what the published site refuses.
|
|
4
|
+
export const WR_INPUT_TYPES = ['text', 'email', 'tel', 'number', 'textarea', 'file'];
|
|
5
|
+
/** What the Webround backend (Core, `POST /form/:draftId`) accepts: the same list it checks. */
|
|
6
|
+
export const WR_FORM_FILE_TYPES = [
|
|
7
|
+
'image/jpeg', 'image/png', 'image/webp', 'image/gif', 'image/svg+xml',
|
|
8
|
+
'audio/mpeg', 'audio/wav', 'audio/ogg', 'audio/mp3',
|
|
9
|
+
'application/pdf',
|
|
10
|
+
];
|
|
11
|
+
/** The server takes 40 MB per file; the form stops at 30, so a file that is refused is refused before it travels. */
|
|
12
|
+
export const WR_FORM_FILE_MAX_BYTES = 30 * 1024 * 1024;
|
|
13
|
+
export const WR_FORM_FILES_MAX = 10;
|
|
14
|
+
const EMAIL = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
15
|
+
const isEmpty = (value) => value === undefined || (typeof value === 'string' ? value.trim() === '' : value.length === 0);
|
|
16
|
+
/** Whether `value` is a multiple of `step` (counted from zero), with the rounding a decimal step needs. */
|
|
17
|
+
const onStep = (value, step) => {
|
|
18
|
+
const ratio = value / step;
|
|
19
|
+
return Math.abs(ratio - Math.round(ratio)) < 1e-9;
|
|
20
|
+
};
|
|
21
|
+
/** The first reason a value is not acceptable, or null. An empty value is fine unless the field is required. */
|
|
22
|
+
export function wrValidateField(rule, value) {
|
|
23
|
+
if (isEmpty(value))
|
|
24
|
+
return rule.required ? 'required' : null;
|
|
25
|
+
if (rule.type === 'file') {
|
|
26
|
+
const files = Array.isArray(value) ? value : [];
|
|
27
|
+
if (files.length > WR_FORM_FILES_MAX)
|
|
28
|
+
return 'file-count';
|
|
29
|
+
if (files.some(file => !WR_FORM_FILE_TYPES.includes(file.type)))
|
|
30
|
+
return 'file-type';
|
|
31
|
+
if (files.some(file => file.size > WR_FORM_FILE_MAX_BYTES))
|
|
32
|
+
return 'file-size';
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
const text = typeof value === 'string' ? value.trim() : '';
|
|
36
|
+
if (rule.maxLength !== undefined && text.length > rule.maxLength)
|
|
37
|
+
return 'maxLength';
|
|
38
|
+
if (rule.type === 'email')
|
|
39
|
+
return EMAIL.test(text) ? null : 'email';
|
|
40
|
+
if (rule.type === 'number') {
|
|
41
|
+
const number = Number(text);
|
|
42
|
+
if (!Number.isFinite(number))
|
|
43
|
+
return 'number';
|
|
44
|
+
if (rule.min !== undefined && number < rule.min)
|
|
45
|
+
return 'min';
|
|
46
|
+
if (rule.max !== undefined && number > rule.max)
|
|
47
|
+
return 'max';
|
|
48
|
+
if (rule.step !== undefined && rule.step > 0 && !onStep(number - (rule.min ?? 0), rule.step))
|
|
49
|
+
return 'step';
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
const MESSAGES = {
|
|
54
|
+
en: {
|
|
55
|
+
required: 'This field is required.',
|
|
56
|
+
email: 'Enter a valid email address.',
|
|
57
|
+
number: 'Enter a number.',
|
|
58
|
+
min: 'The value is too low.',
|
|
59
|
+
max: 'The value is too high.',
|
|
60
|
+
step: 'The value is not allowed.',
|
|
61
|
+
maxLength: 'The text is too long.',
|
|
62
|
+
'file-count': 'Too many files (10 at most).',
|
|
63
|
+
'file-size': 'A file is too large (30 MB at most).',
|
|
64
|
+
'file-type': 'This type of file is not accepted.',
|
|
65
|
+
'submit-failed': 'The message could not be sent. Try again later.',
|
|
66
|
+
'rate-limited': 'Too many messages sent. Try again later.',
|
|
67
|
+
expired: 'This form is not accepting messages right now.',
|
|
68
|
+
'not-configured': 'This form is not connected to a site yet.',
|
|
69
|
+
},
|
|
70
|
+
it: {
|
|
71
|
+
required: 'Questo campo è obbligatorio.',
|
|
72
|
+
email: 'Inserisci un indirizzo email valido.',
|
|
73
|
+
number: 'Inserisci un numero.',
|
|
74
|
+
min: 'Il valore è troppo basso.',
|
|
75
|
+
max: 'Il valore è troppo alto.',
|
|
76
|
+
step: 'Il valore non è consentito.',
|
|
77
|
+
maxLength: 'Il testo è troppo lungo.',
|
|
78
|
+
'file-count': 'Troppi file (massimo 10).',
|
|
79
|
+
'file-size': 'Un file è troppo grande (massimo 30 MB).',
|
|
80
|
+
'file-type': 'Questo tipo di file non è accettato.',
|
|
81
|
+
'submit-failed': 'Non è stato possibile inviare il messaggio. Riprova più tardi.',
|
|
82
|
+
'rate-limited': 'Troppi messaggi inviati. Riprova più tardi.',
|
|
83
|
+
expired: 'Questo form al momento non accetta messaggi.',
|
|
84
|
+
'not-configured': 'Questo form non è ancora collegato a un sito.',
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
/** Every message a form can say, in the order the panel lists them. */
|
|
88
|
+
export const WR_FORM_MESSAGE_CODES = Object.keys(MESSAGES.en);
|
|
89
|
+
/** The page JSON of a form holds each message as `msg-<code>` (a translated text like any other field): the messages as `wrFormMessage` reads them. */
|
|
90
|
+
export function wrFormMessagesOf(config) {
|
|
91
|
+
const own = {};
|
|
92
|
+
for (const code of WR_FORM_MESSAGE_CODES) {
|
|
93
|
+
const value = config?.[`msg-${code}`];
|
|
94
|
+
if (typeof value === 'string' || (typeof value === 'object' && value !== null))
|
|
95
|
+
own[code] = value;
|
|
96
|
+
}
|
|
97
|
+
return own;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* The text for a message in the language of the page. Order: what the form says in that language; what Webround says in it (`it-IT` reads as `it`);
|
|
101
|
+
* what the form says as its `default`; English. So a form that writes only the Spanish text gets Spanish, and the languages it did not write
|
|
102
|
+
* keep reading what they read before.
|
|
103
|
+
*/
|
|
104
|
+
export function wrFormMessage(code, lang, own) {
|
|
105
|
+
const custom = own?.[code];
|
|
106
|
+
const inLang = typeof custom === 'object' && custom !== null ? custom[lang] : undefined;
|
|
107
|
+
if (inLang)
|
|
108
|
+
return inLang;
|
|
109
|
+
const builtIn = MESSAGES[lang.split('-')[0].toLowerCase()]?.[code];
|
|
110
|
+
if (builtIn)
|
|
111
|
+
return builtIn;
|
|
112
|
+
return (custom !== undefined ? wrLocalized(custom, 'default') : undefined) || MESSAGES.en[code];
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* The body of `POST /form/:draftId`: a multipart with `formData` (the JSON `{ key: { label, value }, …, proof }`) and the `files`.
|
|
116
|
+
* A file field reads, in the JSON, as the names of the files; the files themselves travel as `files`.
|
|
117
|
+
*/
|
|
118
|
+
export function wrBuildFormBody(entries, proof) {
|
|
119
|
+
const data = {};
|
|
120
|
+
const body = new FormData();
|
|
121
|
+
for (const { key, label, value } of entries) {
|
|
122
|
+
if (typeof value === 'string') {
|
|
123
|
+
data[key] = { label, value };
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
data[key] = { label, value: value.map(file => file.name).join(', ') };
|
|
127
|
+
for (const file of value)
|
|
128
|
+
body.append('files', file, file.name);
|
|
129
|
+
}
|
|
130
|
+
data.proof = proof;
|
|
131
|
+
body.append('formData', JSON.stringify(data));
|
|
132
|
+
return body;
|
|
133
|
+
}
|
|
134
|
+
/** What the backend's answer means to the visitor. */
|
|
135
|
+
export function wrFormFailure(status) {
|
|
136
|
+
if (status === 429)
|
|
137
|
+
return 'rate-limited';
|
|
138
|
+
if (status === 403 || status === 404)
|
|
139
|
+
return 'expired';
|
|
140
|
+
return 'submit-failed';
|
|
141
|
+
}
|
|
142
|
+
/** The fields that cannot be sent as they are, by key. */
|
|
143
|
+
export function wrFormProblems(fields, values) {
|
|
144
|
+
const problems = {};
|
|
145
|
+
for (const [key, rule] of Object.entries(fields)) {
|
|
146
|
+
const problem = wrValidateField(rule, values[key]);
|
|
147
|
+
if (problem)
|
|
148
|
+
problems[key] = problem;
|
|
149
|
+
}
|
|
150
|
+
return problems;
|
|
151
|
+
}
|
|
152
|
+
/** Whether the form may be sent now: no field problem, the proof, the consent. THE rule of the submit button. */
|
|
153
|
+
export function wrFormReady(problems, gates) {
|
|
154
|
+
return Object.keys(problems).length === 0 && gates.proof && gates.privacy !== false;
|
|
155
|
+
}
|
|
156
|
+
export const wrChallengeUrl = (target) => target.coreApiUrl ? `${target.coreApiUrl}/altcha` : undefined;
|
|
157
|
+
/** `POST /form/:draftId`. Anything that goes wrong is a message for the visitor, never an exception. */
|
|
158
|
+
export async function wrSubmitForm(target, entries, proof, fetchImpl = fetch) {
|
|
159
|
+
if (!target.coreApiUrl || !target.draftId)
|
|
160
|
+
return 'not-configured';
|
|
161
|
+
try {
|
|
162
|
+
const res = await fetchImpl(`${target.coreApiUrl}/form/${encodeURIComponent(target.draftId)}`, { method: 'POST', body: wrBuildFormBody(entries, proof) });
|
|
163
|
+
return res.ok ? null : wrFormFailure(res.status);
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
return 'submit-failed';
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/** The transport of a published site. */
|
|
170
|
+
export const wrRealTransport = (target) => ({
|
|
171
|
+
mock: false,
|
|
172
|
+
challengeUrl: wrChallengeUrl(target),
|
|
173
|
+
send: (entries, proof) => wrSubmitForm(target, entries, proof),
|
|
174
|
+
});
|
|
175
|
+
//# sourceMappingURL=form.js.map
|