humalis 1.0.4 → 1.2.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/analyse/accessibilite.js +13 -5
- package/dist/analyse/analyse.js +43 -3
- 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 +67 -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 +10 -2
- package/dist/cibles/mobile/rendu.js +6 -3
- package/dist/cibles/statique/page.js +99 -6
- package/dist/cibles/statique/projet.js +46 -0
- package/dist/cibles/statique/valeurs.js +5 -1
- package/dist/cibles/web/css-ecran.js +31 -1
- package/dist/cibles/web/ecran.js +161 -16
- package/dist/cibles/web/interactions.js +64 -3
- package/dist/cibles/web/projet.js +31 -2
- package/dist/cibles/web/theme.js +11 -2
- package/dist/cibles/web/valeurs.js +17 -0
- package/dist/i18n/messages.js +2 -0
- package/dist/lexer/lexer.js +106 -3
- package/dist/navigateur.js +5289 -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/parser.js +91 -3
- package/dist/parser/theme.js +10 -1
- package/dist/ri/construire.js +27 -1
- package/dist/vocabulaire/table-interactions.js +16 -1
- package/dist/vocabulaire/table.js +4 -4
- package/docs/guide.en.md +183 -2
- package/docs/guide.md +183 -1
- package/llms.txt +14 -1
- package/package.json +4 -2
|
@@ -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
|
}
|
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') || estLeMot(suivant, 'vertical'))) ||
|
|
729
780
|
((genre === 'section' || GENRES_AVEC_TITRE.includes(genre)) && suivant.type === 'CHAINE'));
|
|
730
781
|
}
|
|
731
782
|
function analyserBlocMiseEnPage(flux) {
|
|
@@ -734,6 +785,16 @@ 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
|
+
// « menu vertical : » : les liens en colonne. Les deux se combinent, dans n'importe quel ordre.
|
|
790
|
+
let repliable = false;
|
|
791
|
+
let vertical = false;
|
|
792
|
+
while (genre === 'menu' && (estLeMot(flux.actuel(), 'repliable') || estLeMot(flux.actuel(), 'vertical'))) {
|
|
793
|
+
if (estLeMot(flux.avancer(), 'repliable'))
|
|
794
|
+
repliable = true;
|
|
795
|
+
else
|
|
796
|
+
vertical = true;
|
|
797
|
+
}
|
|
737
798
|
// Le titre d'une section devient son premier élément : un « titre » comme les autres.
|
|
738
799
|
const titre = [];
|
|
739
800
|
let titreBloc;
|
|
@@ -769,7 +830,7 @@ function analyserBlocMiseEnPage(flux) {
|
|
|
769
830
|
} while (flux.accepter('MOT_CLE', 'et'));
|
|
770
831
|
}
|
|
771
832
|
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 };
|
|
833
|
+
return { type: 'Bloc', genre, styles, classes, ...(titreBloc ? { titre: titreBloc } : {}), ...(ancre !== undefined ? { ancre } : {}), ...(nom !== undefined ? { nom } : {}), ...(repliable ? { repliable } : {}), ...(vertical ? { vertical } : {}), corps: [...titre, ...analyserBloc(flux)], ligne: debut.ligne };
|
|
773
834
|
}
|
|
774
835
|
function lireNombre(flux, description) {
|
|
775
836
|
const negatif = flux.accepter('PONCTUATION', '-');
|
|
@@ -936,7 +997,7 @@ function analyserAction(flux) {
|
|
|
936
997
|
// « ajouter nom à panier » : « à » et « de » sont des mots de liaison
|
|
937
998
|
// déjà écartés par le lexer ; « dans » reste permis.
|
|
938
999
|
if (debut.valeur === 'ajouter' || debut.valeur === 'retirer') {
|
|
939
|
-
const valeur =
|
|
1000
|
+
const valeur = analyserOperande(flux);
|
|
940
1001
|
flux.accepter('MOT_CLE', 'dans');
|
|
941
1002
|
const { nom } = flux.attendreNom('le nom de la liste');
|
|
942
1003
|
return {
|
|
@@ -1018,7 +1079,7 @@ function analyserAction(flux) {
|
|
|
1018
1079
|
type: 'Affectation',
|
|
1019
1080
|
nom,
|
|
1020
1081
|
...(champs.length > 0 ? { champs } : {}),
|
|
1021
|
-
valeur:
|
|
1082
|
+
valeur: analyserOperande(flux),
|
|
1022
1083
|
ligne: debut.ligne,
|
|
1023
1084
|
};
|
|
1024
1085
|
}
|
|
@@ -1154,6 +1215,17 @@ function analyserComparaison(flux) {
|
|
|
1154
1215
|
flux.avancer();
|
|
1155
1216
|
return { type: 'Comparaison', gauche: { type: 'MouvementReduit', ligne: debut.ligne }, operateur: pas ? 'faux' : 'vrai', ligne: debut.ligne };
|
|
1156
1217
|
}
|
|
1218
|
+
// « theme est sombre », « theme est clair » (et « theme n'est pas sombre ») : le thème affiché.
|
|
1219
|
+
if (estLeMot(flux.actuel(), 'theme') &&
|
|
1220
|
+
flux.regarder(1).type === 'MOT_CLE' &&
|
|
1221
|
+
flux.regarder(1).valeur === 'est' &&
|
|
1222
|
+
(estLeMot(flux.regarder(2 + pas), 'sombre') || estLeMot(flux.regarder(2 + pas), 'clair'))) {
|
|
1223
|
+
const debut = flux.avancer();
|
|
1224
|
+
for (let i = 0; i < 1 + pas; i++)
|
|
1225
|
+
flux.avancer();
|
|
1226
|
+
const sombre = estLeMot(flux.avancer(), 'sombre');
|
|
1227
|
+
return { type: 'Comparaison', gauche: { type: 'ThemeSombre', ligne: debut.ligne }, operateur: sombre !== Boolean(pas) ? 'vrai' : 'faux', ligne: debut.ligne };
|
|
1228
|
+
}
|
|
1157
1229
|
const gauche = analyserOperande(flux);
|
|
1158
1230
|
// « panier contient "Livre" », « nom contient "Liv" ».
|
|
1159
1231
|
if (estLeMot(flux.actuel(), 'contient')) {
|
|
@@ -1285,6 +1357,14 @@ function analyserApresEst(flux, gauche) {
|
|
|
1285
1357
|
return { type: 'Comparaison', gauche, operateur, droite, ligne: gauche.ligne };
|
|
1286
1358
|
}
|
|
1287
1359
|
/** Variable ou champ de formulaire (« champ "email" »). */
|
|
1360
|
+
/** « code … : » est un bloc de code quand le lexer a recopié ses lignes (CODE_BRUT) juste après les deux-points. */
|
|
1361
|
+
function estBlocDeCode(flux) {
|
|
1362
|
+
for (let decalage = 1; decalage <= 7; decalage++) {
|
|
1363
|
+
if (flux.regarder(decalage).valeur === ':')
|
|
1364
|
+
return flux.regarder(decalage + 2).type === 'CODE_BRUT' || flux.regarder(decalage + 1).type === 'CODE_BRUT';
|
|
1365
|
+
}
|
|
1366
|
+
return false;
|
|
1367
|
+
}
|
|
1288
1368
|
export function analyserOperande(flux) {
|
|
1289
1369
|
if (flux.verifier('MOT_CLE', 'champ') && flux.regarder(1).type === 'CHAINE') {
|
|
1290
1370
|
const debut = flux.avancer();
|
|
@@ -1531,6 +1611,14 @@ function analyserValeur(flux) {
|
|
|
1531
1611
|
flux.avancer();
|
|
1532
1612
|
// « "Bienvenue" en anglais "Welcome" » : les versions écrites à côté.
|
|
1533
1613
|
const versions = lireVersions(flux);
|
|
1614
|
+
// « "GitHub" sans traduction » : le même texte dans toutes les langues (un nom propre).
|
|
1615
|
+
if (estLeMot(flux.actuel(), 'sans') && estLeMot(flux.regarder(1), 'traduction')) {
|
|
1616
|
+
const sans = flux.avancer();
|
|
1617
|
+
flux.avancer();
|
|
1618
|
+
if (versions)
|
|
1619
|
+
throw new ErreurSyntaxique('ce texte a une version traduite et « sans traduction » : gardez l’un ou l’autre.', sans.ligne);
|
|
1620
|
+
return { type: 'Chaine', parties: token.parties ?? [], sansTraduction: true, ligne: token.ligne };
|
|
1621
|
+
}
|
|
1534
1622
|
return { type: 'Chaine', parties: token.parties ?? [], ...(versions ? { versions } : {}), ligne: token.ligne };
|
|
1535
1623
|
}
|
|
1536
1624
|
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,12 @@ 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' });
|
|
694
|
+
// « menu vertical » : la colonne que lit le CSS.
|
|
695
|
+
if (instruction.vertical)
|
|
696
|
+
options.push({ nom: 'menu', valeur: 'vertical' });
|
|
677
697
|
const noeud = {
|
|
678
698
|
type: 'bloc',
|
|
679
699
|
genre: instruction.genre,
|
|
@@ -795,6 +815,10 @@ function traduireValeur(expression, analyse) {
|
|
|
795
815
|
const parties = (liste) => liste.map((p) => {
|
|
796
816
|
if (p.type === 'texte')
|
|
797
817
|
return { texte: p.valeur };
|
|
818
|
+
if (p.type === 'accent')
|
|
819
|
+
return { texte: p.valeur, accent: true };
|
|
820
|
+
if (p.type === 'code')
|
|
821
|
+
return { texte: p.valeur, code: true };
|
|
798
822
|
const format = analyse.formats.get(p);
|
|
799
823
|
const pluriel = analyse.pluriels.get(p);
|
|
800
824
|
return {
|
|
@@ -892,6 +916,8 @@ function traduireValeur(expression, analyse) {
|
|
|
892
916
|
return { type: 'erreurRequete', variable: expression.variable };
|
|
893
917
|
case 'MouvementReduit':
|
|
894
918
|
return { type: 'mouvementReduit' };
|
|
919
|
+
case 'ThemeSombre':
|
|
920
|
+
return { type: 'themeSombre' };
|
|
895
921
|
}
|
|
896
922
|
}
|
|
897
923
|
/** Une durée écrite en chiffres, en millisecondes (l'analyse a refusé les autres là où il le faut). */
|
|
@@ -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,95 @@ 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. In a `section`, the first subtitle is
|
|
191
|
+
the section's title, even when it sits in its own block; every other subtitle of
|
|
192
|
+
the section goes down one level. `avec niveau` (with level) still lets you
|
|
193
|
+
choose.
|
|
194
|
+
|
|
195
|
+
```humalis
|
|
196
|
+
page "home" :
|
|
197
|
+
titre "Home"
|
|
198
|
+
section :
|
|
199
|
+
soustitre "Our pages"
|
|
200
|
+
grille :
|
|
201
|
+
bloc :
|
|
202
|
+
soustitre "Examples"
|
|
203
|
+
texte "Six apps to try."
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
In a `titre`, `soustitre`, or `texte`, `{accent "…"}` highlights a few words
|
|
207
|
+
with the brand color. Screen readers and the browser tab still get plain text.
|
|
208
|
+
|
|
209
|
+
```humalis
|
|
210
|
+
page "offer" :
|
|
211
|
+
titre "A {accent "simple"} offer"
|
|
212
|
+
texte "Price: {accent "free"} forever"
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Lists
|
|
216
|
+
|
|
217
|
+
`liste :` makes bullets; `liste numerotee :` numbers each item. Each instruction in the block becomes one item.
|
|
218
|
+
|
|
219
|
+
```humalis
|
|
220
|
+
page "guide" :
|
|
221
|
+
liste :
|
|
222
|
+
texte "Install Node.js"
|
|
223
|
+
texte "Create the project"
|
|
224
|
+
liste numerotee :
|
|
225
|
+
texte "Write the file"
|
|
226
|
+
texte "Compile"
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Code within a sentence
|
|
230
|
+
|
|
231
|
+
In text, `{code "…"}` displays a short piece of code in a monospace font.
|
|
232
|
+
|
|
233
|
+
```humalis
|
|
234
|
+
page "guide" :
|
|
235
|
+
texte "Write {code "titre"} to add a heading."
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Code block
|
|
239
|
+
|
|
240
|
+
`code language :` copies the indented lines without interpreting them; the language is optional.
|
|
241
|
+
|
|
242
|
+
```humalis
|
|
243
|
+
page "guide" :
|
|
244
|
+
code humalis :
|
|
245
|
+
page "home" :
|
|
246
|
+
titre "Welcome"
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
`avec animation frappe` ("with typing animation") writes the code letter by
|
|
250
|
+
letter, with a cursor; `avec animation frappe au defilement` waits until the
|
|
251
|
+
block scrolls into view. The real code stays in place while it types (screen
|
|
252
|
+
readers, Copy button), and it shows at once if the person asked to reduce
|
|
253
|
+
motion. On a phone, the code shows in full.
|
|
254
|
+
|
|
255
|
+
```humalis
|
|
256
|
+
page "home" :
|
|
257
|
+
code humalis avec animation frappe :
|
|
258
|
+
page "shop" :
|
|
259
|
+
titre "Our products"
|
|
260
|
+
bouton "Order"
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### Table
|
|
264
|
+
|
|
265
|
+
A table has a caption, columns, and rows of equal width. A translation may follow each cell.
|
|
266
|
+
|
|
267
|
+
```humalis
|
|
268
|
+
langues :
|
|
269
|
+
francais
|
|
270
|
+
anglais
|
|
271
|
+
|
|
272
|
+
page "guide" :
|
|
273
|
+
tableau "Commands" en anglais "Commands" :
|
|
274
|
+
colonnes "Command", "Role" en anglais "Role"
|
|
275
|
+
rangee "humalis compile", "builds the project" en anglais "builds the project"
|
|
276
|
+
```
|
|
277
|
+
|
|
181
278
|
### Image
|
|
182
279
|
|
|
183
280
|
```humalis
|
|
@@ -391,10 +488,35 @@ a light (`clair`) and dark (`sombre`) value; `police titres` is the headings
|
|
|
391
488
|
font. Humalis checks contrast: a text colour too pale for its background is
|
|
392
489
|
reported at compile time.
|
|
393
490
|
|
|
491
|
+
A font can be shipped with the site, without relying on an outside service:
|
|
492
|
+
`police titres "Space Grotesk" depuis "./polices/space-grotesk.woff2"` (`.woff2`,
|
|
493
|
+
`.woff`, `.ttf` or `.otf` files).
|
|
494
|
+
|
|
495
|
+
Visitors can also switch themes. `basculer theme` ("toggle theme") switches to
|
|
496
|
+
the opposite of the theme being shown, and `si theme est sombre :` (or `clair`)
|
|
497
|
+
reacts to the theme on screen:
|
|
498
|
+
|
|
499
|
+
```humalis
|
|
500
|
+
theme :
|
|
501
|
+
mode automatique
|
|
502
|
+
|
|
503
|
+
page "home" :
|
|
504
|
+
titre "Home"
|
|
505
|
+
bouton "Change theme"
|
|
506
|
+
quand l'utilisateur clique sur "Change theme" :
|
|
507
|
+
basculer theme
|
|
508
|
+
si theme est sombre :
|
|
509
|
+
texte "Good evening"
|
|
510
|
+
sinon :
|
|
511
|
+
texte "Good day"
|
|
512
|
+
```
|
|
513
|
+
|
|
394
514
|
### Shared header and footer
|
|
395
515
|
|
|
396
516
|
`entete :` (header) and `pied :` (footer) appear on every page. A page can
|
|
397
|
-
leave them out with `sans entete` or `sans pied`.
|
|
517
|
+
leave them out with `sans entete` or `sans pied`. With a header, Humalis adds a
|
|
518
|
+
"Skip to content" link on its own, hidden until the keyboard reaches it: it lets
|
|
519
|
+
keyboard users jump past the header.
|
|
398
520
|
|
|
399
521
|
```humalis
|
|
400
522
|
entete avec colle :
|
|
@@ -488,7 +610,32 @@ page "menu" :
|
|
|
488
610
|
```
|
|
489
611
|
|
|
490
612
|
`fil d'Ariane` is a breadcrumb. In a `menu`, the link to the current page is
|
|
491
|
-
marked as active.
|
|
613
|
+
marked as active. The links of a menu wrap when space runs out. A `menu
|
|
614
|
+
vertical :` stacks its links in a column and never starts a second column: a
|
|
615
|
+
table of contents with a limited height scrolls. It combines with `repliable`
|
|
616
|
+
(`menu vertical repliable :`).
|
|
617
|
+
|
|
618
|
+
```humalis
|
|
619
|
+
page "guide" :
|
|
620
|
+
titre "The guide"
|
|
621
|
+
menu vertical :
|
|
622
|
+
lien "Install" vers "guide"
|
|
623
|
+
lien "Pages" vers "guide"
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
A `menu repliable :` (collapsible menu) folds behind a burger button (named
|
|
627
|
+
"Menu" for screen readers) on a small screen (phone); the icon turns into a cross
|
|
628
|
+
while the menu is open. Choosing a link, tapping outside or pressing Escape
|
|
629
|
+
closes it. On a large screen it stays open:
|
|
630
|
+
|
|
631
|
+
```humalis
|
|
632
|
+
page "home" :
|
|
633
|
+
menu repliable :
|
|
634
|
+
lien "Home" vers "home"
|
|
635
|
+
lien "The menu" vers "home"
|
|
636
|
+
lien "Book" vers "home"
|
|
637
|
+
titre "Home"
|
|
638
|
+
```
|
|
492
639
|
|
|
493
640
|
---
|
|
494
641
|
|
|
@@ -535,6 +682,18 @@ Animations: `apparition` (fade in), `montee` (slide up), `agrandissement`
|
|
|
535
682
|
`agrandi` (grow), `leve` (lift). Humalis respects the person's "reduce motion"
|
|
536
683
|
setting.
|
|
537
684
|
|
|
685
|
+
On a block, `et en cascade` ("and in a cascade") makes its children appear one
|
|
686
|
+
after another:
|
|
687
|
+
|
|
688
|
+
```humalis
|
|
689
|
+
page "home" :
|
|
690
|
+
titre "Our dishes"
|
|
691
|
+
grille avec animation montee au defilement et en cascade :
|
|
692
|
+
texte "Soup of the day"
|
|
693
|
+
texte "Risotto"
|
|
694
|
+
texte "Apple pie"
|
|
695
|
+
```
|
|
696
|
+
|
|
538
697
|
---
|
|
539
698
|
|
|
540
699
|
## 9. Variables and calculations
|
|
@@ -959,8 +1118,10 @@ Actions are written inside an event.
|
|
|
959
1118
|
| `afficher erreur "…"` | shows an error message |
|
|
960
1119
|
| `montrer "name"`, `cacher "name"` | shows or hides an element |
|
|
961
1120
|
| `changer x par …` | changes a variable |
|
|
1121
|
+
| `changer x par champ "Label"` | reads a field's current value into a variable |
|
|
962
1122
|
| `changer texte de "name" par "…"` | changes an element's text |
|
|
963
1123
|
| `ajouter … à list`, `retirer … de list` | changes a list |
|
|
1124
|
+
| `ajouter champ "Label" à list` | adds a field's current value to a list |
|
|
964
1125
|
| `rediriger vers "page"` | goes to a page |
|
|
965
1126
|
| `revenir vers "page"` | goes back, or to that page |
|
|
966
1127
|
| `envoyer requete vers "…"` | calls a server (see [the server](#22-connecting-pages-to-the-server)) |
|
|
@@ -981,6 +1142,22 @@ page "thanks" :
|
|
|
981
1142
|
titre "Thank you!"
|
|
982
1143
|
```
|
|
983
1144
|
|
|
1145
|
+
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.
|
|
1146
|
+
|
|
1147
|
+
```humalis
|
|
1148
|
+
page "tasks" :
|
|
1149
|
+
variable tasks = ["Buy bread"]
|
|
1150
|
+
variable latest = ""
|
|
1151
|
+
champ "New task"
|
|
1152
|
+
bouton "Add"
|
|
1153
|
+
quand l'utilisateur clique sur "Add" :
|
|
1154
|
+
ajouter champ "New task" à tasks
|
|
1155
|
+
changer latest par champ "New task"
|
|
1156
|
+
texte "Latest added: {latest}"
|
|
1157
|
+
pour chaque task dans tasks :
|
|
1158
|
+
texte task
|
|
1159
|
+
```
|
|
1160
|
+
|
|
984
1161
|
---
|
|
985
1162
|
|
|
986
1163
|
## 16. Messages, dialogs and questions
|
|
@@ -1617,9 +1794,13 @@ page "catalogue" :
|
|
|
1617
1794
|
|
|
1618
1795
|
- `langues :` lists the languages (`francais`, `anglais`…).
|
|
1619
1796
|
- `en francais "…"` gives another language's version of a text.
|
|
1797
|
+
- `sans traduction` ("without translation") keeps the same text in every
|
|
1798
|
+
language, for a proper noun: `lien "GitHub" sans traduction vers "https://github.com"`.
|
|
1620
1799
|
- `traductions francais :` translates a repeated text once.
|
|
1621
1800
|
- `selecteur de langue` shows the language switcher; `changer langue par
|
|
1622
1801
|
anglais` and `si langue est anglais :` are used in events.
|
|
1802
|
+
- The message of `copier` is translated like any other text:
|
|
1803
|
+
`copier code avec message "Copied" en francais "Copié"`.
|
|
1623
1804
|
- Humalis reports at compile time every text that lacks its translation.
|
|
1624
1805
|
Humalis's own messages (required field, loading…) follow the language.
|
|
1625
1806
|
|