humalis 1.0.3 → 1.1.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/LICENSE +85 -201
- package/LICENSE.en.md +86 -0
- package/NOTICE +6 -3
- package/README.en.md +1 -1
- package/README.md +1 -1
- package/dist/analyse/accessibilite.js +13 -5
- package/dist/analyse/analyse.js +43 -3
- package/dist/analyse/ecran.js +1 -1
- package/dist/analyse/langues.js +30 -10
- package/dist/analyse/medias.js +4 -1
- package/dist/analyse/navigation.js +35 -0
- package/dist/analyse/niveaux.js +33 -0
- package/dist/analyse/noms.js +1 -1
- package/dist/analyse/site.js +30 -8
- package/dist/cibles/commun/colorerCode.js +59 -0
- package/dist/cibles/commun/mouvement.js +110 -4
- package/dist/cibles/mobile/projet.js +7 -2
- package/dist/cibles/mobile/rendu.js +6 -3
- package/dist/cibles/statique/elements-js.js +1 -1
- package/dist/cibles/statique/page.js +99 -6
- package/dist/cibles/statique/projet.js +31 -0
- package/dist/cibles/statique/valeurs.js +5 -1
- package/dist/cibles/statique/widgets.js +1 -1
- package/dist/cibles/web/css-ecran.js +22 -1
- package/dist/cibles/web/ecran.js +161 -16
- package/dist/cibles/web/interactions.js +50 -3
- package/dist/cibles/web/projet.js +29 -2
- package/dist/cibles/web/theme.js +11 -2
- package/dist/cibles/web/valeurs.js +17 -0
- package/dist/cibles/web/widgets.js +1 -1
- package/dist/i18n/messages.js +2 -0
- package/dist/lexer/lexer.js +106 -3
- package/dist/navigateur.js +5274 -0
- package/dist/parser/interactions.js +6 -2
- package/dist/parser/langues.js +1 -1
- package/dist/parser/medias.js +6 -1
- package/dist/parser/miseEnPage.js +1 -1
- package/dist/parser/parser.js +85 -3
- package/dist/parser/theme.js +10 -1
- package/dist/ri/construire.js +24 -1
- package/dist/vocabulaire/table-ecran.js +3 -3
- package/dist/vocabulaire/table-interactions.js +16 -1
- package/dist/vocabulaire/table.js +4 -4
- package/docs/guide.en.md +170 -2
- package/docs/guide.md +171 -1
- package/llms.txt +14 -1
- package/package.json +7 -4
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { normaliser } from '../lexer/motsCles.js';
|
|
2
2
|
import { ErreurSyntaxique } from './erreurs.js';
|
|
3
|
+
import { lireVersions } from './langues.js';
|
|
3
4
|
/** Vrai si le token est ce mot, écrit comme mot-clé ou comme nom (sans accent ni majuscule). */
|
|
4
5
|
function estLeMot(token, mot) {
|
|
5
6
|
return (token.type === 'MOT_CLE' || token.type === 'IDENTIFIANT') && normaliser(token.original ?? token.valeur) === mot;
|
|
@@ -122,8 +123,11 @@ function analyserCopier(flux, outils) {
|
|
|
122
123
|
if (flux.verifier('MOT_CLE', 'avec') && estLeMot(flux.regarder(1), 'message')) {
|
|
123
124
|
flux.avancer();
|
|
124
125
|
flux.avancer();
|
|
125
|
-
const
|
|
126
|
-
|
|
126
|
+
const jeton = flux.attendre('CHAINE', undefined, 'le texte du message entre guillemets (copier code avec message "Code copié")');
|
|
127
|
+
// « avec message "Copié" en anglais "Copied" » : les versions écrites à côté.
|
|
128
|
+
const versions = lireVersions(flux);
|
|
129
|
+
const texte = versions ? { texte: { type: 'Chaine', parties: jeton.parties ?? [], versions, ligne: jeton.ligne } } : {};
|
|
130
|
+
return { type: 'Copier', valeur, message: jeton.valeur, ...texte, ligne: debut.ligne };
|
|
127
131
|
}
|
|
128
132
|
if (estLeMot(flux.actuel(), 'sans') && estLeMot(flux.regarder(1), 'message')) {
|
|
129
133
|
flux.avancer();
|
package/dist/parser/langues.js
CHANGED
|
@@ -85,5 +85,5 @@ export function estSelecteurLangue(flux) {
|
|
|
85
85
|
}
|
|
86
86
|
/** Le texte écrit, interpolations comprises (« {n article} dans le panier ») : la clé d'une traduction. */
|
|
87
87
|
export function texteSource(parties) {
|
|
88
|
-
return parties.map((p) => (p.type === '
|
|
88
|
+
return parties.map((p) => (p.type === 'variable' ? `{${p.valeur}}` : p.valeur)).join('');
|
|
89
89
|
}
|
package/dist/parser/medias.js
CHANGED
|
@@ -16,7 +16,12 @@ export function analyserOptionMedia(flux, nom) {
|
|
|
16
16
|
const valeur = flux.avancer().valeur;
|
|
17
17
|
if (estLeMot(flux.actuel(), 'defilement')) {
|
|
18
18
|
flux.avancer();
|
|
19
|
-
|
|
19
|
+
// « … au defilement et en cascade » : les enfants du bloc, l'un après l'autre.
|
|
20
|
+
const enCascade = flux.actuel().valeur === 'et' && estLeMot(flux.regarder(1), 'en') && estLeMot(flux.regarder(2), 'cascade');
|
|
21
|
+
if (enCascade)
|
|
22
|
+
for (let i = 0; i < 3; i++)
|
|
23
|
+
flux.avancer();
|
|
24
|
+
return { propriete: 'animation', valeur, defilement: true, ...(enCascade ? { cascade: true } : {}) };
|
|
20
25
|
}
|
|
21
26
|
return { propriete: 'animation', valeur };
|
|
22
27
|
}
|
|
@@ -30,7 +30,7 @@ export function analyserStylePropre(flux, propriete) {
|
|
|
30
30
|
return { propriete: nom, valeur: '5' };
|
|
31
31
|
// « photo "{p.photo}" » : un texte, avec ses {variables}.
|
|
32
32
|
if (nom === 'photo') {
|
|
33
|
-
const jeton = flux.attendre('CHAINE', undefined, 'l’image entre guillemets : avec photo "
|
|
33
|
+
const jeton = flux.attendre('CHAINE', undefined, 'l’image entre guillemets : avec photo "camille.jpg"');
|
|
34
34
|
return { propriete: nom, valeur: jeton.valeur, parties: jeton.parties ?? [] };
|
|
35
35
|
}
|
|
36
36
|
const media = analyserOptionMedia(flux, nom);
|
package/dist/parser/parser.js
CHANGED
|
@@ -398,6 +398,56 @@ function analyserBloc(flux) {
|
|
|
398
398
|
return instructions;
|
|
399
399
|
}
|
|
400
400
|
function analyserInstruction(flux) {
|
|
401
|
+
if (estLeMot(flux.actuel(), 'tableau') && (flux.regarder(1).type === 'CHAINE' || flux.regarder(1).valeur === ':')) {
|
|
402
|
+
const debut = flux.avancer();
|
|
403
|
+
const legende = flux.verifier('PONCTUATION', ':') ? undefined : analyserValeur(flux);
|
|
404
|
+
flux.attendre('PONCTUATION', ':', 'deux-points après la légende du tableau');
|
|
405
|
+
flux.sauterFinsDeLigne();
|
|
406
|
+
const colonnes = [];
|
|
407
|
+
const rangees = [];
|
|
408
|
+
if (flux.accepter('INDENT')) {
|
|
409
|
+
flux.sauterFinsDeLigne();
|
|
410
|
+
while (!flux.verifier('DEDENT') && !flux.estFini()) {
|
|
411
|
+
const genre = flux.avancer();
|
|
412
|
+
if (!estLeMot(genre, 'colonnes') && !estLeMot(genre, 'rangee'))
|
|
413
|
+
throw new ErreurSyntaxique('un tableau contient « colonnes » et « rangee ».', genre.ligne);
|
|
414
|
+
const cellules = [analyserValeur(flux)];
|
|
415
|
+
while (flux.accepter('PONCTUATION', ','))
|
|
416
|
+
cellules.push(analyserValeur(flux));
|
|
417
|
+
if (estLeMot(genre, 'colonnes'))
|
|
418
|
+
colonnes.push(...cellules);
|
|
419
|
+
else
|
|
420
|
+
rangees.push(cellules);
|
|
421
|
+
flux.sauterFinsDeLigne();
|
|
422
|
+
}
|
|
423
|
+
flux.accepter('DEDENT');
|
|
424
|
+
}
|
|
425
|
+
return { type: 'TableauDocumentation', ...(legende ? { legende } : {}), colonnes, rangees, ligne: debut.ligne };
|
|
426
|
+
}
|
|
427
|
+
if (estLeMot(flux.actuel(), 'liste') && (flux.regarder(1).valeur === ':' || (estLeMot(flux.regarder(1), 'numerotee') && flux.regarder(2).valeur === ':'))) {
|
|
428
|
+
const debut = flux.avancer();
|
|
429
|
+
const numerotee = estLeMot(flux.actuel(), 'numerotee');
|
|
430
|
+
if (numerotee)
|
|
431
|
+
flux.avancer();
|
|
432
|
+
flux.attendre('PONCTUATION', ':', 'deux-points après « liste »');
|
|
433
|
+
return { type: 'Bloc', genre: numerotee ? 'listeNumerotee' : 'liste', styles: [], classes: [], corps: analyserBloc(flux), ligne: debut.ligne };
|
|
434
|
+
}
|
|
435
|
+
if (estLeMot(flux.actuel(), 'code') && estBlocDeCode(flux)) {
|
|
436
|
+
const debut = flux.avancer();
|
|
437
|
+
const langage = flux.verifier('PONCTUATION', ':') || estLeMot(flux.actuel(), 'avec') ? undefined : flux.avancer().valeur;
|
|
438
|
+
// « avec animation frappe » (au chargement), « … au defilement » (quand le bloc arrive à l'écran).
|
|
439
|
+
let frappe;
|
|
440
|
+
if (estLeMot(flux.actuel(), 'avec')) {
|
|
441
|
+
flux.avancer(); // avec
|
|
442
|
+
flux.avancer(); // animation
|
|
443
|
+
flux.avancer(); // frappe
|
|
444
|
+
frappe = estLeMot(flux.actuel(), 'au') ? (flux.avancer(), flux.avancer(), 'defilement') : 'chargement';
|
|
445
|
+
}
|
|
446
|
+
flux.attendre('PONCTUATION', ':', 'deux-points après « code »');
|
|
447
|
+
flux.accepter('FIN_LIGNE');
|
|
448
|
+
const code = flux.attendre('CODE_BRUT', undefined, 'les lignes du bloc de code').valeur;
|
|
449
|
+
return { type: 'CodeDocumentation', ...(langage ? { langage } : {}), ...(frappe ? { frappe } : {}), code, ligne: debut.ligne };
|
|
450
|
+
}
|
|
401
451
|
if (estLeMot(flux.actuel(), 'en') && estLeMot(flux.regarder(1), 'supposant')) {
|
|
402
452
|
const debut = flux.avancer();
|
|
403
453
|
for (const motAttendu of ['supposant', 'que', 'ca', 'marche']) {
|
|
@@ -726,6 +776,7 @@ function estBlocMiseEnPage(flux) {
|
|
|
726
776
|
const suivant = flux.regarder(1);
|
|
727
777
|
return ((suivant.type === 'PONCTUATION' && suivant.valeur === ':') ||
|
|
728
778
|
(suivant.type === 'MOT_CLE' && suivant.valeur === 'avec') ||
|
|
779
|
+
(genre === 'menu' && estLeMot(suivant, 'repliable')) ||
|
|
729
780
|
((genre === 'section' || GENRES_AVEC_TITRE.includes(genre)) && suivant.type === 'CHAINE'));
|
|
730
781
|
}
|
|
731
782
|
function analyserBlocMiseEnPage(flux) {
|
|
@@ -734,6 +785,10 @@ function analyserBlocMiseEnPage(flux) {
|
|
|
734
785
|
if (filAriane)
|
|
735
786
|
flux.avancer();
|
|
736
787
|
const genre = filAriane ? 'filAriane' : GENRES_BLOC.find((g) => estLeMot(debut, g));
|
|
788
|
+
// « menu repliable : » : sur un petit écran, un bouton « Menu » ouvre et ferme les liens.
|
|
789
|
+
const repliable = genre === 'menu' && estLeMot(flux.actuel(), 'repliable');
|
|
790
|
+
if (repliable)
|
|
791
|
+
flux.avancer();
|
|
737
792
|
// Le titre d'une section devient son premier élément : un « titre » comme les autres.
|
|
738
793
|
const titre = [];
|
|
739
794
|
let titreBloc;
|
|
@@ -769,7 +824,7 @@ function analyserBlocMiseEnPage(flux) {
|
|
|
769
824
|
} while (flux.accepter('MOT_CLE', 'et'));
|
|
770
825
|
}
|
|
771
826
|
flux.attendre('PONCTUATION', ':', `deux-points à la fin de la ligne « ${genre} », puis son contenu en dessous, décalé`);
|
|
772
|
-
return { type: 'Bloc', genre, styles, classes, ...(titreBloc ? { titre: titreBloc } : {}), ...(ancre !== undefined ? { ancre } : {}), ...(nom !== undefined ? { nom } : {}), corps: [...titre, ...analyserBloc(flux)], ligne: debut.ligne };
|
|
827
|
+
return { type: 'Bloc', genre, styles, classes, ...(titreBloc ? { titre: titreBloc } : {}), ...(ancre !== undefined ? { ancre } : {}), ...(nom !== undefined ? { nom } : {}), ...(repliable ? { repliable } : {}), corps: [...titre, ...analyserBloc(flux)], ligne: debut.ligne };
|
|
773
828
|
}
|
|
774
829
|
function lireNombre(flux, description) {
|
|
775
830
|
const negatif = flux.accepter('PONCTUATION', '-');
|
|
@@ -936,7 +991,7 @@ function analyserAction(flux) {
|
|
|
936
991
|
// « ajouter nom à panier » : « à » et « de » sont des mots de liaison
|
|
937
992
|
// déjà écartés par le lexer ; « dans » reste permis.
|
|
938
993
|
if (debut.valeur === 'ajouter' || debut.valeur === 'retirer') {
|
|
939
|
-
const valeur =
|
|
994
|
+
const valeur = analyserOperande(flux);
|
|
940
995
|
flux.accepter('MOT_CLE', 'dans');
|
|
941
996
|
const { nom } = flux.attendreNom('le nom de la liste');
|
|
942
997
|
return {
|
|
@@ -1018,7 +1073,7 @@ function analyserAction(flux) {
|
|
|
1018
1073
|
type: 'Affectation',
|
|
1019
1074
|
nom,
|
|
1020
1075
|
...(champs.length > 0 ? { champs } : {}),
|
|
1021
|
-
valeur:
|
|
1076
|
+
valeur: analyserOperande(flux),
|
|
1022
1077
|
ligne: debut.ligne,
|
|
1023
1078
|
};
|
|
1024
1079
|
}
|
|
@@ -1154,6 +1209,17 @@ function analyserComparaison(flux) {
|
|
|
1154
1209
|
flux.avancer();
|
|
1155
1210
|
return { type: 'Comparaison', gauche: { type: 'MouvementReduit', ligne: debut.ligne }, operateur: pas ? 'faux' : 'vrai', ligne: debut.ligne };
|
|
1156
1211
|
}
|
|
1212
|
+
// « theme est sombre », « theme est clair » (et « theme n'est pas sombre ») : le thème affiché.
|
|
1213
|
+
if (estLeMot(flux.actuel(), 'theme') &&
|
|
1214
|
+
flux.regarder(1).type === 'MOT_CLE' &&
|
|
1215
|
+
flux.regarder(1).valeur === 'est' &&
|
|
1216
|
+
(estLeMot(flux.regarder(2 + pas), 'sombre') || estLeMot(flux.regarder(2 + pas), 'clair'))) {
|
|
1217
|
+
const debut = flux.avancer();
|
|
1218
|
+
for (let i = 0; i < 1 + pas; i++)
|
|
1219
|
+
flux.avancer();
|
|
1220
|
+
const sombre = estLeMot(flux.avancer(), 'sombre');
|
|
1221
|
+
return { type: 'Comparaison', gauche: { type: 'ThemeSombre', ligne: debut.ligne }, operateur: sombre !== Boolean(pas) ? 'vrai' : 'faux', ligne: debut.ligne };
|
|
1222
|
+
}
|
|
1157
1223
|
const gauche = analyserOperande(flux);
|
|
1158
1224
|
// « panier contient "Livre" », « nom contient "Liv" ».
|
|
1159
1225
|
if (estLeMot(flux.actuel(), 'contient')) {
|
|
@@ -1285,6 +1351,14 @@ function analyserApresEst(flux, gauche) {
|
|
|
1285
1351
|
return { type: 'Comparaison', gauche, operateur, droite, ligne: gauche.ligne };
|
|
1286
1352
|
}
|
|
1287
1353
|
/** Variable ou champ de formulaire (« champ "email" »). */
|
|
1354
|
+
/** « code … : » est un bloc de code quand le lexer a recopié ses lignes (CODE_BRUT) juste après les deux-points. */
|
|
1355
|
+
function estBlocDeCode(flux) {
|
|
1356
|
+
for (let decalage = 1; decalage <= 7; decalage++) {
|
|
1357
|
+
if (flux.regarder(decalage).valeur === ':')
|
|
1358
|
+
return flux.regarder(decalage + 2).type === 'CODE_BRUT' || flux.regarder(decalage + 1).type === 'CODE_BRUT';
|
|
1359
|
+
}
|
|
1360
|
+
return false;
|
|
1361
|
+
}
|
|
1288
1362
|
export function analyserOperande(flux) {
|
|
1289
1363
|
if (flux.verifier('MOT_CLE', 'champ') && flux.regarder(1).type === 'CHAINE') {
|
|
1290
1364
|
const debut = flux.avancer();
|
|
@@ -1531,6 +1605,14 @@ function analyserValeur(flux) {
|
|
|
1531
1605
|
flux.avancer();
|
|
1532
1606
|
// « "Bienvenue" en anglais "Welcome" » : les versions écrites à côté.
|
|
1533
1607
|
const versions = lireVersions(flux);
|
|
1608
|
+
// « "GitHub" sans traduction » : le même texte dans toutes les langues (un nom propre).
|
|
1609
|
+
if (estLeMot(flux.actuel(), 'sans') && estLeMot(flux.regarder(1), 'traduction')) {
|
|
1610
|
+
const sans = flux.avancer();
|
|
1611
|
+
flux.avancer();
|
|
1612
|
+
if (versions)
|
|
1613
|
+
throw new ErreurSyntaxique('ce texte a une version traduite et « sans traduction » : gardez l’un ou l’autre.', sans.ligne);
|
|
1614
|
+
return { type: 'Chaine', parties: token.parties ?? [], sansTraduction: true, ligne: token.ligne };
|
|
1615
|
+
}
|
|
1534
1616
|
return { type: 'Chaine', parties: token.parties ?? [], ...(versions ? { versions } : {}), ligne: token.ligne };
|
|
1535
1617
|
}
|
|
1536
1618
|
if (token.type === 'NOMBRE') {
|
package/dist/parser/theme.js
CHANGED
|
@@ -107,14 +107,23 @@ export function analyserTheme(flux) {
|
|
|
107
107
|
flux.accepter('DEDENT');
|
|
108
108
|
return theme;
|
|
109
109
|
}
|
|
110
|
-
/** «
|
|
110
|
+
/** « basculer theme » : le thème inverse de celui qu'on voit. */
|
|
111
|
+
const estBasculerTheme = (flux) => estLeMot(flux.actuel(), 'basculer') && estLeMot(flux.regarder(1), 'theme');
|
|
112
|
+
/** « changer theme par sombre » (ou par "sombre") : le choix du visiteur, gardé par son appareil ; ou « basculer theme ». */
|
|
111
113
|
export function estChangerTheme(flux) {
|
|
114
|
+
if (estBasculerTheme(flux))
|
|
115
|
+
return true;
|
|
112
116
|
if (!(flux.verifier('MOT_CLE', 'changer') && estLeMot(flux.regarder(1), 'theme') && flux.regarder(2).valeur === 'par'))
|
|
113
117
|
return false;
|
|
114
118
|
const valeur = flux.regarder(3);
|
|
115
119
|
return valeur.type === 'CHAINE' || (estMot(valeur) && MODES.includes(normaliser(valeur.valeur)));
|
|
116
120
|
}
|
|
117
121
|
export function analyserChangerTheme(flux) {
|
|
122
|
+
if (estBasculerTheme(flux)) {
|
|
123
|
+
const debut = flux.avancer();
|
|
124
|
+
const mot = flux.avancer();
|
|
125
|
+
return { type: 'Action', action: 'changerTheme', arguments: [{ type: 'Chaine', parties: [{ type: 'texte', valeur: 'bascule' }], ligne: mot.ligne }], ligne: debut.ligne };
|
|
126
|
+
}
|
|
118
127
|
const debut = flux.avancer();
|
|
119
128
|
flux.avancer();
|
|
120
129
|
flux.avancer();
|
package/dist/ri/construire.js
CHANGED
|
@@ -330,6 +330,12 @@ class Construction {
|
|
|
330
330
|
case 'Bloc':
|
|
331
331
|
noeuds.push(instruction.genre === 'carte' ? this.carte(instruction, gestionnaires) : this.bloc(instruction, gestionnaires));
|
|
332
332
|
break;
|
|
333
|
+
case 'CodeDocumentation':
|
|
334
|
+
noeuds.push({ type: 'codeDocumentation', ...(instruction.langage ? { langage: instruction.langage } : {}), ...(instruction.frappe ? { frappe: instruction.frappe } : {}), code: instruction.code, ligne: instruction.ligne });
|
|
335
|
+
break;
|
|
336
|
+
case 'TableauDocumentation':
|
|
337
|
+
noeuds.push({ type: 'tableauDocumentation', legende: this.valeur(instruction.legende), colonnes: instruction.colonnes.map((c) => this.valeur(c)), rangees: instruction.rangees.map((r) => r.map((c) => this.valeur(c))), ligne: instruction.ligne });
|
|
338
|
+
break;
|
|
333
339
|
case 'Formulaire':
|
|
334
340
|
noeuds.push({
|
|
335
341
|
type: 'formulaire',
|
|
@@ -578,7 +584,12 @@ class Construction {
|
|
|
578
584
|
});
|
|
579
585
|
break;
|
|
580
586
|
case 'Copier':
|
|
581
|
-
actions.push({
|
|
587
|
+
actions.push({
|
|
588
|
+
type: 'copie',
|
|
589
|
+
valeur: this.valeur(instruction.valeur),
|
|
590
|
+
message: instruction.message === undefined ? 'Copié' : instruction.message,
|
|
591
|
+
...(instruction.texte ? { texte: this.valeur(instruction.texte) } : {}),
|
|
592
|
+
});
|
|
582
593
|
break;
|
|
583
594
|
case 'Partager': {
|
|
584
595
|
const lien = instruction.lien;
|
|
@@ -648,6 +659,9 @@ class Construction {
|
|
|
648
659
|
// « animation montee au defilement » : la marque que lit l'observateur du défilement.
|
|
649
660
|
if (s.defilement)
|
|
650
661
|
options.push({ nom: 'animation', valeur: 'defilement' });
|
|
662
|
+
// « … et en cascade » : les enfants du bloc s'animent, l'un après l'autre.
|
|
663
|
+
if (s.cascade)
|
|
664
|
+
options.push({ nom: 'animation', valeur: 'cascade' });
|
|
651
665
|
}
|
|
652
666
|
return { options, reglages: reglages };
|
|
653
667
|
}
|
|
@@ -674,6 +688,9 @@ class Construction {
|
|
|
674
688
|
/** Un bloc de mise en page, ou un bloc d'éléments d'écran avec son état caché. */
|
|
675
689
|
bloc(instruction, gestionnaires, groupe) {
|
|
676
690
|
const { options, reglages } = this.separer(instruction.styles);
|
|
691
|
+
// « menu repliable » : la marque que lisent le CSS et le bouton « Menu ».
|
|
692
|
+
if (instruction.repliable)
|
|
693
|
+
options.push({ nom: 'menu', valeur: 'repliable' });
|
|
677
694
|
const noeud = {
|
|
678
695
|
type: 'bloc',
|
|
679
696
|
genre: instruction.genre,
|
|
@@ -795,6 +812,10 @@ function traduireValeur(expression, analyse) {
|
|
|
795
812
|
const parties = (liste) => liste.map((p) => {
|
|
796
813
|
if (p.type === 'texte')
|
|
797
814
|
return { texte: p.valeur };
|
|
815
|
+
if (p.type === 'accent')
|
|
816
|
+
return { texte: p.valeur, accent: true };
|
|
817
|
+
if (p.type === 'code')
|
|
818
|
+
return { texte: p.valeur, code: true };
|
|
798
819
|
const format = analyse.formats.get(p);
|
|
799
820
|
const pluriel = analyse.pluriels.get(p);
|
|
800
821
|
return {
|
|
@@ -892,6 +913,8 @@ function traduireValeur(expression, analyse) {
|
|
|
892
913
|
return { type: 'erreurRequete', variable: expression.variable };
|
|
893
914
|
case 'MouvementReduit':
|
|
894
915
|
return { type: 'mouvementReduit' };
|
|
916
|
+
case 'ThemeSombre':
|
|
917
|
+
return { type: 'themeSombre' };
|
|
895
918
|
}
|
|
896
919
|
}
|
|
897
920
|
/** Une durée écrite en chiffres, en millisecondes (l'analyse a refusé les autres là où il le faut). */
|
|
@@ -238,8 +238,8 @@ export const ENTREES_ECRAN = [
|
|
|
238
238
|
}),
|
|
239
239
|
elementEcran({
|
|
240
240
|
mot: 'avatar',
|
|
241
|
-
description: 'Le visage d’une personne : sa photo (« avec photo "
|
|
242
|
-
exemple: PAGE(' avatar "
|
|
241
|
+
description: 'Le visage d’une personne : sa photo (« avec photo "camille.jpg" ») ou, sans photo, ses initiales sur un fond de couleur.',
|
|
242
|
+
exemple: PAGE(' avatar "Camille Martin"\n avatar "Le Fleuve" avec photo "logo.png" et taille grande'),
|
|
243
243
|
web: '<span role="img" aria-label="…"> avec <img> ou les initiales',
|
|
244
244
|
mobile: '<View> avec <Image> ou <Text>',
|
|
245
245
|
statique: '<span class="humalis-avatar" role="img">',
|
|
@@ -366,7 +366,7 @@ export const ENTREES_ECRAN = [
|
|
|
366
366
|
optionEcran('description', 'Le texte lu par un lecteur d’écran quand l’élément n’a pas de texte visible : « avec description "Panier" ».', PAGE(' icone "panier" avec description "Panier"'), ['icone', 'avatar', 'progression', 'etoiles', 'compteur', 'qr', 'chargement', 'badge', 'image', 'audio']),
|
|
367
367
|
optionEcran('decoratif', 'Une image purement décorative, ignorée par le lecteur d’écran : « avec decoratif ».', PAGE(' titre "Accueil"\n image "filet.png" avec decoratif'), ['image', 'icone']),
|
|
368
368
|
optionEcran('niveau', 'Le niveau d’un sous-titre, de 2 à 6 : « avec niveau 3 ».', PAGE(' titre "Accueil"\n soustitre "Détails" avec niveau 3'), ['soustitre']),
|
|
369
|
-
optionEcran('photo', 'L’image d’un avatar : « avec photo "
|
|
369
|
+
optionEcran('photo', 'L’image d’un avatar : « avec photo "camille.jpg" », ou « avec photo "{p.photo}" » pour une image qui vient d’une donnée.', PAGE(' avatar "Camille Martin" avec photo "camille.jpg"'), ['avatar']),
|
|
370
370
|
optionEcran('modifiable', 'Des étoiles sur lesquelles on clique pour changer la variable : « etoiles note avec modifiable ».', PAGE(' variable note = 3\n etoiles note avec modifiable'), ['etoiles']),
|
|
371
371
|
optionEcran('ouvert', 'Un onglet ou un deroulant ouvert au départ : « deroulant "Détails" avec ouvert : ».', PAGE(' deroulant "Détails" avec ouvert :\n texte "a"'), ['onglet', 'deroulant']),
|
|
372
372
|
optionEcran('plusieurs', 'Un accordéon dont plusieurs deroulant peuvent être ouverts en même temps.', PAGE(' accordeon avec plusieurs :\n deroulant "A" :\n texte "a"\n deroulant "B" :\n texte "b"'), ['accordeon']),
|
|
@@ -109,7 +109,7 @@ export const ENTREES_INTERACTIONS = [
|
|
|
109
109
|
concept: 'action.copier',
|
|
110
110
|
cibles: ecran('await copier(texte, afficherMessage) : navigator.clipboard.writeText, repli execCommand (src/humalis/presse-papier.ts)', 'await copier(texte, afficherMessage) : Clipboard.setStringAsync d’expo-clipboard', "await copier(texte) : navigator.clipboard.writeText, repli execCommand (humalis.js)"),
|
|
111
111
|
couvre: ['web:navigator.clipboard.writeText', 'expo:expo-clipboard'],
|
|
112
|
-
description: 'Dans un événement : « copier code » met le texte dans le presse-papier, puis affiche « Copié » (ou « Copie impossible »). « avec message "Code copié" » change le texte, « sans message » le tait.',
|
|
112
|
+
description: 'Dans un événement : « copier code » met le texte dans le presse-papier, puis affiche « Copié » (ou « Copie impossible »). « avec message "Code copié" » change le texte (et « en anglais "Code copied" » le traduit), « sans message » le tait.',
|
|
113
113
|
exemple: AU_CLIC(' copier code', ' variable code = "REY-2026"\n'),
|
|
114
114
|
}),
|
|
115
115
|
mot({
|
|
@@ -262,4 +262,19 @@ export const ENTREES_INTERACTIONS = [
|
|
|
262
262
|
statut: 'supporte',
|
|
263
263
|
etape: 9,
|
|
264
264
|
},
|
|
265
|
+
{
|
|
266
|
+
mot: 'theme sombre',
|
|
267
|
+
accentue: 'thème sombre',
|
|
268
|
+
variantes: ['theme clair'],
|
|
269
|
+
categorie: 'comparaison',
|
|
270
|
+
concept: 'condition.themeSombre',
|
|
271
|
+
// Comme « mouvement est reduit » : une tournure lue dans une condition, jamais un mot-clé du lexer.
|
|
272
|
+
contextes: ['session'],
|
|
273
|
+
cibles: ecran("useThemeSombre() : data-theme de <html>, sinon matchMedia('(prefers-color-scheme: dark)'), suivi en direct", "useThemeSombre() : useColorScheme() === 'dark'", 'humalis.themeSombre() : data-theme de <html>, sinon le système, la page redessinée au changement'),
|
|
274
|
+
couvre: [],
|
|
275
|
+
description: 'La condition « theme est sombre » est vraie quand le thème affiché est sombre (choix du visiteur ou du projet, sinon le système) ; « theme est clair » et « theme n’est pas sombre » sont son contraire. « basculer theme » passe au thème inverse. Le HTML écrit d’avance la considère fausse.',
|
|
276
|
+
exemple: 'theme :\n mode automatique\n\npage "accueil" :\n titre "Accueil"\n bouton "Changer de thème"\n quand l\'utilisateur clique sur "Changer de thème" :\n basculer theme\n si theme est sombre :\n texte "Bonne soirée"',
|
|
277
|
+
statut: 'supporte',
|
|
278
|
+
etape: 9,
|
|
279
|
+
},
|
|
265
280
|
];
|
|
@@ -2169,8 +2169,8 @@ export const VOCABULAIRE = [
|
|
|
2169
2169
|
cibles: ecran('<ul> et ses <li>', 'FlatList', '<ul>'),
|
|
2170
2170
|
couvre: ['html:ul', 'html:ol', 'html:li', 'html:menu', 'rn:FlatList'],
|
|
2171
2171
|
description: 'Une liste à puces (ou numérotée). Ailleurs, « liste » reste un nom de variable possible.',
|
|
2172
|
-
exemple: PAGE('
|
|
2173
|
-
statut: '
|
|
2172
|
+
exemple: PAGE(' liste :\n texte "Livre"'),
|
|
2173
|
+
statut: 'supporte',
|
|
2174
2174
|
etape: 1,
|
|
2175
2175
|
note: 'L’élément n’est pas encore pris en charge. Comme type de champ d’une donnée du serveur, « liste de texte » marche déjà.',
|
|
2176
2176
|
},
|
|
@@ -2183,8 +2183,8 @@ export const VOCABULAIRE = [
|
|
|
2183
2183
|
cibles: ecran('<table> et ses lignes', 'une liste de lignes', '<table>'),
|
|
2184
2184
|
couvre: ['html:table', 'html:thead', 'html:tbody', 'html:tr', 'html:td', 'html:th', 'html:caption'],
|
|
2185
2185
|
description: 'Un tableau, décrit par ses colonnes et la liste qu’il affiche.',
|
|
2186
|
-
exemple: PAGE(' tableau
|
|
2187
|
-
statut: '
|
|
2186
|
+
exemple: PAGE(' tableau "Produits" :\n colonnes "Nom", "Prix"\n rangee "Livre", "10"'),
|
|
2187
|
+
statut: 'supporte',
|
|
2188
2188
|
etape: 1,
|
|
2189
2189
|
},
|
|
2190
2190
|
{
|
package/docs/guide.en.md
CHANGED
|
@@ -103,6 +103,14 @@ Text that is displayed goes between quotes: `titre "Welcome"`. Without quotes,
|
|
|
103
103
|
it is a variable name: `texte firstname` shows what the variable `firstname`
|
|
104
104
|
contains.
|
|
105
105
|
|
|
106
|
+
To put a quote inside a text, write `\"`:
|
|
107
|
+
|
|
108
|
+
```humalis
|
|
109
|
+
page "quote" :
|
|
110
|
+
titre "A quote"
|
|
111
|
+
texte "She said \"hello\" as she came in."
|
|
112
|
+
```
|
|
113
|
+
|
|
106
114
|
### Accents are allowed, never required
|
|
107
115
|
|
|
108
116
|
`si age est supérieur ou égal à 18` and `si age est superieur ou egal a 18`
|
|
@@ -178,6 +186,93 @@ page "menu" :
|
|
|
178
186
|
Keep the levels in order, without skipping: screen readers use them to move
|
|
179
187
|
through the page.
|
|
180
188
|
|
|
189
|
+
Inside a block placed under a subtitle, a `soustitre` goes down one level on its
|
|
190
|
+
own: a card in a section becomes level 3. `avec niveau` (with level) still lets
|
|
191
|
+
you choose.
|
|
192
|
+
|
|
193
|
+
```humalis
|
|
194
|
+
page "home" :
|
|
195
|
+
titre "Home"
|
|
196
|
+
section :
|
|
197
|
+
soustitre "Our pages"
|
|
198
|
+
grille :
|
|
199
|
+
bloc :
|
|
200
|
+
soustitre "Examples"
|
|
201
|
+
texte "Six apps to try."
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
In a `titre`, `soustitre`, or `texte`, `{accent "…"}` highlights a few words
|
|
205
|
+
with the brand color. Screen readers and the browser tab still get plain text.
|
|
206
|
+
|
|
207
|
+
```humalis
|
|
208
|
+
page "offer" :
|
|
209
|
+
titre "A {accent "simple"} offer"
|
|
210
|
+
texte "Price: {accent "free"} forever"
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Lists
|
|
214
|
+
|
|
215
|
+
`liste :` makes bullets; `liste numerotee :` numbers each item. Each instruction in the block becomes one item.
|
|
216
|
+
|
|
217
|
+
```humalis
|
|
218
|
+
page "guide" :
|
|
219
|
+
liste :
|
|
220
|
+
texte "Install Node.js"
|
|
221
|
+
texte "Create the project"
|
|
222
|
+
liste numerotee :
|
|
223
|
+
texte "Write the file"
|
|
224
|
+
texte "Compile"
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### Code within a sentence
|
|
228
|
+
|
|
229
|
+
In text, `{code "…"}` displays a short piece of code in a monospace font.
|
|
230
|
+
|
|
231
|
+
```humalis
|
|
232
|
+
page "guide" :
|
|
233
|
+
texte "Write {code "titre"} to add a heading."
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Code block
|
|
237
|
+
|
|
238
|
+
`code language :` copies the indented lines without interpreting them; the language is optional.
|
|
239
|
+
|
|
240
|
+
```humalis
|
|
241
|
+
page "guide" :
|
|
242
|
+
code humalis :
|
|
243
|
+
page "home" :
|
|
244
|
+
titre "Welcome"
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
`avec animation frappe` ("with typing animation") writes the code letter by
|
|
248
|
+
letter, with a cursor; `avec animation frappe au defilement` waits until the
|
|
249
|
+
block scrolls into view. The real code stays in place while it types (screen
|
|
250
|
+
readers, Copy button), and it shows at once if the person asked to reduce
|
|
251
|
+
motion. On a phone, the code shows in full.
|
|
252
|
+
|
|
253
|
+
```humalis
|
|
254
|
+
page "home" :
|
|
255
|
+
code humalis avec animation frappe :
|
|
256
|
+
page "shop" :
|
|
257
|
+
titre "Our products"
|
|
258
|
+
bouton "Order"
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### Table
|
|
262
|
+
|
|
263
|
+
A table has a caption, columns, and rows of equal width. A translation may follow each cell.
|
|
264
|
+
|
|
265
|
+
```humalis
|
|
266
|
+
langues :
|
|
267
|
+
francais
|
|
268
|
+
anglais
|
|
269
|
+
|
|
270
|
+
page "guide" :
|
|
271
|
+
tableau "Commands" en anglais "Commands" :
|
|
272
|
+
colonnes "Command", "Role" en anglais "Role"
|
|
273
|
+
rangee "humalis compile", "builds the project" en anglais "builds the project"
|
|
274
|
+
```
|
|
275
|
+
|
|
181
276
|
### Image
|
|
182
277
|
|
|
183
278
|
```humalis
|
|
@@ -391,10 +486,35 @@ a light (`clair`) and dark (`sombre`) value; `police titres` is the headings
|
|
|
391
486
|
font. Humalis checks contrast: a text colour too pale for its background is
|
|
392
487
|
reported at compile time.
|
|
393
488
|
|
|
489
|
+
A font can be shipped with the site, without relying on an outside service:
|
|
490
|
+
`police titres "Space Grotesk" depuis "./polices/space-grotesk.woff2"` (`.woff2`,
|
|
491
|
+
`.woff`, `.ttf` or `.otf` files).
|
|
492
|
+
|
|
493
|
+
Visitors can also switch themes. `basculer theme` ("toggle theme") switches to
|
|
494
|
+
the opposite of the theme being shown, and `si theme est sombre :` (or `clair`)
|
|
495
|
+
reacts to the theme on screen:
|
|
496
|
+
|
|
497
|
+
```humalis
|
|
498
|
+
theme :
|
|
499
|
+
mode automatique
|
|
500
|
+
|
|
501
|
+
page "home" :
|
|
502
|
+
titre "Home"
|
|
503
|
+
bouton "Change theme"
|
|
504
|
+
quand l'utilisateur clique sur "Change theme" :
|
|
505
|
+
basculer theme
|
|
506
|
+
si theme est sombre :
|
|
507
|
+
texte "Good evening"
|
|
508
|
+
sinon :
|
|
509
|
+
texte "Good day"
|
|
510
|
+
```
|
|
511
|
+
|
|
394
512
|
### Shared header and footer
|
|
395
513
|
|
|
396
514
|
`entete :` (header) and `pied :` (footer) appear on every page. A page can
|
|
397
|
-
leave them out with `sans entete` or `sans pied`.
|
|
515
|
+
leave them out with `sans entete` or `sans pied`. With a header, Humalis adds a
|
|
516
|
+
"Skip to content" link on its own, hidden until the keyboard reaches it: it lets
|
|
517
|
+
keyboard users jump past the header.
|
|
398
518
|
|
|
399
519
|
```humalis
|
|
400
520
|
entete avec colle :
|
|
@@ -488,7 +608,21 @@ page "menu" :
|
|
|
488
608
|
```
|
|
489
609
|
|
|
490
610
|
`fil d'Ariane` is a breadcrumb. In a `menu`, the link to the current page is
|
|
491
|
-
marked as active.
|
|
611
|
+
marked as active. The links of a menu wrap when space runs out. A menu that
|
|
612
|
+
your CSS turns into a column (`flex-direction: column`) keeps that wrapping: add
|
|
613
|
+
`flex-wrap: nowrap` so it scrolls instead of starting a second column.
|
|
614
|
+
|
|
615
|
+
A `menu repliable :` (collapsible menu) folds behind a "Menu" button on a small
|
|
616
|
+
screen (phone); choosing a link closes it. On a large screen it stays open:
|
|
617
|
+
|
|
618
|
+
```humalis
|
|
619
|
+
page "home" :
|
|
620
|
+
menu repliable :
|
|
621
|
+
lien "Home" vers "home"
|
|
622
|
+
lien "The menu" vers "home"
|
|
623
|
+
lien "Book" vers "home"
|
|
624
|
+
titre "Home"
|
|
625
|
+
```
|
|
492
626
|
|
|
493
627
|
---
|
|
494
628
|
|
|
@@ -535,6 +669,18 @@ Animations: `apparition` (fade in), `montee` (slide up), `agrandissement`
|
|
|
535
669
|
`agrandi` (grow), `leve` (lift). Humalis respects the person's "reduce motion"
|
|
536
670
|
setting.
|
|
537
671
|
|
|
672
|
+
On a block, `et en cascade` ("and in a cascade") makes its children appear one
|
|
673
|
+
after another:
|
|
674
|
+
|
|
675
|
+
```humalis
|
|
676
|
+
page "home" :
|
|
677
|
+
titre "Our dishes"
|
|
678
|
+
grille avec animation montee au defilement et en cascade :
|
|
679
|
+
texte "Soup of the day"
|
|
680
|
+
texte "Risotto"
|
|
681
|
+
texte "Apple pie"
|
|
682
|
+
```
|
|
683
|
+
|
|
538
684
|
---
|
|
539
685
|
|
|
540
686
|
## 9. Variables and calculations
|
|
@@ -959,8 +1105,10 @@ Actions are written inside an event.
|
|
|
959
1105
|
| `afficher erreur "…"` | shows an error message |
|
|
960
1106
|
| `montrer "name"`, `cacher "name"` | shows or hides an element |
|
|
961
1107
|
| `changer x par …` | changes a variable |
|
|
1108
|
+
| `changer x par champ "Label"` | reads a field's current value into a variable |
|
|
962
1109
|
| `changer texte de "name" par "…"` | changes an element's text |
|
|
963
1110
|
| `ajouter … à list`, `retirer … de list` | changes a list |
|
|
1111
|
+
| `ajouter champ "Label" à list` | adds a field's current value to a list |
|
|
964
1112
|
| `rediriger vers "page"` | goes to a page |
|
|
965
1113
|
| `revenir vers "page"` | goes back, or to that page |
|
|
966
1114
|
| `envoyer requete vers "…"` | calls a server (see [the server](#22-connecting-pages-to-the-server)) |
|
|
@@ -981,6 +1129,22 @@ page "thanks" :
|
|
|
981
1129
|
titre "Thank you!"
|
|
982
1130
|
```
|
|
983
1131
|
|
|
1132
|
+
Inside an action, `champ "Label"` reads what the person entered when they click. A number field yields a number, a checkbox yields true or false, and other fields yield text. Use the base language label even when the field has a translation.
|
|
1133
|
+
|
|
1134
|
+
```humalis
|
|
1135
|
+
page "tasks" :
|
|
1136
|
+
variable tasks = ["Buy bread"]
|
|
1137
|
+
variable latest = ""
|
|
1138
|
+
champ "New task"
|
|
1139
|
+
bouton "Add"
|
|
1140
|
+
quand l'utilisateur clique sur "Add" :
|
|
1141
|
+
ajouter champ "New task" à tasks
|
|
1142
|
+
changer latest par champ "New task"
|
|
1143
|
+
texte "Latest added: {latest}"
|
|
1144
|
+
pour chaque task dans tasks :
|
|
1145
|
+
texte task
|
|
1146
|
+
```
|
|
1147
|
+
|
|
984
1148
|
---
|
|
985
1149
|
|
|
986
1150
|
## 16. Messages, dialogs and questions
|
|
@@ -1617,9 +1781,13 @@ page "catalogue" :
|
|
|
1617
1781
|
|
|
1618
1782
|
- `langues :` lists the languages (`francais`, `anglais`…).
|
|
1619
1783
|
- `en francais "…"` gives another language's version of a text.
|
|
1784
|
+
- `sans traduction` ("without translation") keeps the same text in every
|
|
1785
|
+
language, for a proper noun: `lien "GitHub" sans traduction vers "https://github.com"`.
|
|
1620
1786
|
- `traductions francais :` translates a repeated text once.
|
|
1621
1787
|
- `selecteur de langue` shows the language switcher; `changer langue par
|
|
1622
1788
|
anglais` and `si langue est anglais :` are used in events.
|
|
1789
|
+
- The message of `copier` is translated like any other text:
|
|
1790
|
+
`copier code avec message "Copied" en francais "Copié"`.
|
|
1623
1791
|
- Humalis reports at compile time every text that lacks its translation.
|
|
1624
1792
|
Humalis's own messages (required field, loading…) follow the language.
|
|
1625
1793
|
|