humalis 1.3.0 → 1.4.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/cibles/commun/colorerCode.js +3 -10
- package/dist/cibles/commun/mouvement.js +9 -2
- package/dist/cibles/web/css-ecran.js +3 -2
- package/dist/cibles/web/projet.js +24 -9
- package/dist/cli.js +7 -1
- package/dist/lexer/lexer.js +27 -0
- package/dist/navigateur.js +68 -68
- package/dist/outils/version.js +15 -0
- package/dist/parser/langues.js +30 -2
- package/dist/vocabulaire/table-langues.js +1 -1
- package/docs/guide.en.md +12 -3
- package/docs/guide.md +12 -2
- package/llms.txt +4 -0
- package/package.json +1 -1
|
@@ -19,16 +19,9 @@ export function colorerCodeHumalis(code, langage) {
|
|
|
19
19
|
continue;
|
|
20
20
|
const debut = debuts[token.ligne - 1] + token.colonne - 1;
|
|
21
21
|
let fin = debut + (token.original ?? token.valeur).length;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
if (code[fin] === '"' && code[fin - 1] !== '\\') {
|
|
26
|
-
fin++;
|
|
27
|
-
break;
|
|
28
|
-
}
|
|
29
|
-
fin++;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
22
|
+
// Un texte s'arrête où le lexer l'a fermé (le code entre accents graves et {code "…"} compris).
|
|
23
|
+
if (token.type === 'CHAINE' && token.longueur !== undefined)
|
|
24
|
+
fin = debut + token.longueur;
|
|
32
25
|
plages.push({ debut, fin, classe: token.type === 'MOT_CLE' ? 'mot' : token.type === 'CHAINE' ? 'texte' : 'nombre' });
|
|
33
26
|
}
|
|
34
27
|
for (let index = 0; index < lignes.length; index++) {
|
|
@@ -286,6 +286,13 @@ export const ENTETE_WEB = `// Produit par Humalis : la hauteur de l'en-tête, po
|
|
|
286
286
|
${SUIVI_ENTETE.replace('function suivreLaHauteurDeLEntete() {', 'export function suivreLaHauteurDeLEntete(): void {').replace("const entete = () => document.querySelector(", "const entete = () => document.querySelector<HTMLElement>(")}
|
|
287
287
|
`;
|
|
288
288
|
/** Le prérendu capture la page après le script : il retire les marques du mouvement (rien de caché sans JavaScript). */
|
|
289
|
-
|
|
290
|
-
|
|
289
|
+
/**
|
|
290
|
+
* Le nettoyage du HTML prérendu. Les préchargements que Vite ajoute en chargeant une page
|
|
291
|
+
* (« modulepreload » avec as="script", adresse complète) ne servent à rien dans la photo : le
|
|
292
|
+
* script principal les redemande. Avec le mouvement, le script marque aussi la page
|
|
293
|
+
* (humalis-js, data-humalis-vu) : le HTML prérendu ne garde aucune de ces marques.
|
|
294
|
+
*/
|
|
295
|
+
export const nettoyagePrerendu = (mouvement) => ` // La photo de chaque page ne garde que ce que le navigateur doit lire au départ.
|
|
296
|
+
transformHtml: (html) => html.replace(/<link rel="modulepreload" as="script"[^>]*>/g, '')${mouvement ? NETTOYAGE_MOUVEMENT : ''},
|
|
291
297
|
`;
|
|
298
|
+
const NETTOYAGE_MOUVEMENT = `.replace(/(<html[^>]*class="[^"]*)\\bhumalis-js\\b/, '$1').replace(/ data-humalis-vu=""/g, '').replace(/ data-humalis-tape="[a-z-]*"/g, '').replace(/<code class="humalis-frappe-copie"[\\s\\S]*?<\\/code>/g, '')`;
|
|
@@ -142,10 +142,11 @@ export function cssEcran(codes) {
|
|
|
142
142
|
break;
|
|
143
143
|
case 'fixe':
|
|
144
144
|
// « entete avec fixe » : l'en-tête ne bouge pas ; sur le web, la racine devient la zone qui
|
|
145
|
-
// défile, sous l'en-tête
|
|
145
|
+
// défile, sous l'en-tête (sans glisser de côté : un décor qui dépasse ne la déplace pas) ;
|
|
146
|
+
// sur le statique, la page laisse la place de l'en-tête.
|
|
146
147
|
regles.push(`header${classe} { position: fixed; top: 0; left: 0; right: 0; z-index: 50; background: var(--humalis-page); }`);
|
|
147
148
|
regles.push('html:has(#racine > .humalis-fixe-oui), html:has(#racine > .humalis-fixe-oui) body { height: 100%; overflow: hidden; }');
|
|
148
|
-
regles.push('#racine:has(> .humalis-fixe-oui) { position: fixed; top: var(--humalis-hauteur-entete, 0px); left: 0; right: 0; bottom: 0; overflow-y: auto; overscroll-behavior: contain; }');
|
|
149
|
+
regles.push('#racine:has(> .humalis-fixe-oui) { position: fixed; top: var(--humalis-hauteur-entete, 0px); left: 0; right: 0; bottom: 0; overflow-y: auto; overflow-x: hidden; overscroll-behavior: contain; }');
|
|
149
150
|
regles.push('body:has(header.humalis-fixe-oui) { padding-top: var(--humalis-hauteur-entete, 0px); }');
|
|
150
151
|
break;
|
|
151
152
|
}
|
|
@@ -3,7 +3,7 @@ import { robotsTxt, manifeste, fichiersDuSite, balisesIcone, estExterne } from '
|
|
|
3
3
|
import { sousActions } from '../../ri/actions.js';
|
|
4
4
|
import { cssEcran } from './css-ecran.js';
|
|
5
5
|
import { cssElements } from './css-elements.js';
|
|
6
|
-
import { cssMouvement, animeAuDefilement, MOUVEMENT_WEB,
|
|
6
|
+
import { cssMouvement, animeAuDefilement, MOUVEMENT_WEB, nettoyagePrerendu, ENTETE_WEB, suitLEntete } from '../commun/mouvement.js';
|
|
7
7
|
import { fichiersDesElements, dependancesDesElements, dependancesDeDeveloppement } from './widgets.js';
|
|
8
8
|
import { fichiersGraphique, cssGraphique } from './graphique.js';
|
|
9
9
|
import { cssTheme, changeLeTheme, modeParDefaut, themeTs, SCRIPT_THEME } from './theme.js';
|
|
@@ -437,7 +437,7 @@ ${lignesRoutes}
|
|
|
437
437
|
waitFor: 'meta[name="description"]',
|
|
438
438
|
// Les adresses des « envoyer requete » du programme, neutralisées pendant le prérendu.
|
|
439
439
|
apiPatterns: [${motifs}],
|
|
440
|
-
${mouvement
|
|
440
|
+
${nettoyagePrerendu(mouvement)}};
|
|
441
441
|
`;
|
|
442
442
|
}
|
|
443
443
|
/** Disposition.tsx : l'en-tête et le pied communs autour des pages, qui restent montés quand on change de page. */
|
|
@@ -512,7 +512,21 @@ function lignesDeRoute(r, toutes, niveau, cheminParent) {
|
|
|
512
512
|
return lignes;
|
|
513
513
|
}
|
|
514
514
|
function mainTsx(routes, feuilles, partage, session, introuvable, dispositions = {}, defilement = false, mouvement = false, entete = false) {
|
|
515
|
-
|
|
515
|
+
// Chaque page dans son propre fichier, chargé à sa première visite ; celle de l'adresse ouverte
|
|
516
|
+
// (et son parent, pour une sous-page) est chargée avant l'affichage, pour ne jamais montrer d'écran vide.
|
|
517
|
+
const aDesFils = (r) => routes.some((f) => f.parent === r.nom);
|
|
518
|
+
const imports = routes.length === 0 ? '' : `const chargeurs = {
|
|
519
|
+
${routes.map((r) => ` ${r.composant}: () => import('./pages/${r.composant}'),`).join('\n')}
|
|
520
|
+
};
|
|
521
|
+
const adresses: [keyof typeof chargeurs, string, boolean][] = [
|
|
522
|
+
${routes.map((r) => ` [${JSON.stringify(r.composant)}, ${JSON.stringify(r.chemin)}, ${aDesFils(r)}],`).join('\n')}
|
|
523
|
+
];
|
|
524
|
+
const ouvertes = new Map<string, ComponentType>();
|
|
525
|
+
for (const [nom, chemin, parent] of adresses) {
|
|
526
|
+
if (matchPath({ path: chemin, end: !parent }, location.pathname)) ouvertes.set(nom, (await chargeurs[nom]()).default);
|
|
527
|
+
}
|
|
528
|
+
const page = (nom: keyof typeof chargeurs): ComponentType => ouvertes.get(nom) ?? lazy(chargeurs[nom]);
|
|
529
|
+
${routes.map((r) => `const ${r.composant} = page(${JSON.stringify(r.composant)});`).join('\n')}`;
|
|
516
530
|
// Les pages qui ont le même en-tête et le même pied partagent une disposition ; une sous-page suit son parent.
|
|
517
531
|
const groupes = new Map();
|
|
518
532
|
for (const r of routes.filter((r) => r.parent === undefined)) {
|
|
@@ -530,25 +544,26 @@ function mainTsx(routes, feuilles, partage, session, introuvable, dispositions =
|
|
|
530
544
|
for (const nom of [...enveloppes].reverse())
|
|
531
545
|
routesTsx = `<${nom}>\n${routesTsx.replace(/^/gm, ' ')}\n</${nom}>`;
|
|
532
546
|
routesTsx = routesTsx.replace(/^/gm, ' ');
|
|
533
|
-
return `import { StrictMode } from 'react';
|
|
547
|
+
return `import { ${routes.length === 0 ? '' : 'lazy, '}StrictMode, Suspense${routes.length === 0 ? '' : ', type ComponentType'} } from 'react';
|
|
534
548
|
import { createRoot } from 'react-dom/client';
|
|
535
|
-
import { BrowserRouter, Route, Routes } from 'react-router';
|
|
549
|
+
import { BrowserRouter, Route, Routes${routes.length === 0 ? '' : ', matchPath'} } from 'react-router';
|
|
536
550
|
import { Messages } from './humalis/messages';${defilement ? "\nimport { Defilement } from './humalis/navigation';" : ''}
|
|
537
551
|
${session ? "import { Session } from './humalis/session';\n" : ''}${partage ? "import { Partage } from './humalis/partage';\n" : ''}${routes.some((r) => r.reservee) ? "import { Reservee } from './humalis/reservee';\n" : ''}${introuvable ? `import ${introuvable} from './pages/${introuvable}';` : "import Introuvable from './Introuvable';"}
|
|
538
|
-
${dispositions.entete || dispositions.pied ? "import Disposition from './Disposition';\n" : ''}
|
|
539
|
-
import './humalis/styles.css';
|
|
552
|
+
${dispositions.entete || dispositions.pied ? "import Disposition from './Disposition';\n" : ''}import './humalis/styles.css';
|
|
540
553
|
${mouvement ? "import { observerLeDefilement } from './humalis/mouvement';\n" : ''}${entete ? "import { suivreLaHauteurDeLEntete } from './humalis/entete';\n" : ''}${feuilles.map((f) => `import './styles/${f}';\n`).join('')}
|
|
541
554
|
// Un hébergeur statique sert /documentation/index.html pour /documentation/ :
|
|
542
555
|
// le routeur lit l'adresse sans « index.html », comme le dossier.
|
|
543
556
|
if (/\\/index\\.html$/.test(location.pathname)) {
|
|
544
557
|
history.replaceState(history.state, '', location.pathname.replace(/index\\.html$/, '') + location.search + location.hash);
|
|
545
558
|
}
|
|
546
|
-
|
|
559
|
+
${imports ? `\n${imports}\n` : ''}
|
|
547
560
|
createRoot(document.getElementById('racine')!).render(
|
|
548
561
|
<StrictMode>
|
|
549
562
|
<BrowserRouter>
|
|
550
563
|
${defilement ? ' <Defilement />\n' : ''} <Messages>
|
|
551
|
-
|
|
564
|
+
<Suspense fallback={null}>
|
|
565
|
+
${routesTsx.replace(/^/gm, ' ')}
|
|
566
|
+
</Suspense>
|
|
552
567
|
</Messages>
|
|
553
568
|
</BrowserRouter>
|
|
554
569
|
</StrictMode>,
|
package/dist/cli.js
CHANGED
|
@@ -20,7 +20,9 @@ import { formaterSource } from './outils/formatage.js';
|
|
|
20
20
|
import { developper, trouverPrincipal } from './outils/developpement.js';
|
|
21
21
|
import { ecrireSiChange } from './outils/ecriture.js';
|
|
22
22
|
import { testerProjet } from './outils/tests.js';
|
|
23
|
+
import { reponseSimple } from './outils/version.js';
|
|
23
24
|
const AIDE = `Utilisation :
|
|
25
|
+
humalis --version
|
|
24
26
|
humalis nouveau <nom>
|
|
25
27
|
humalis formater <fichier.humalis> [--verifier]
|
|
26
28
|
humalis developper [<fichier.humalis>] [--port <nombre>]
|
|
@@ -555,7 +557,11 @@ function principal(arguments_) {
|
|
|
555
557
|
return 0;
|
|
556
558
|
}
|
|
557
559
|
const argumentsCli = process.argv.slice(2);
|
|
558
|
-
|
|
560
|
+
const reponse = reponseSimple(argumentsCli, AIDE);
|
|
561
|
+
if (reponse !== undefined) {
|
|
562
|
+
console.log(reponse);
|
|
563
|
+
}
|
|
564
|
+
else if (argumentsCli[0] === 'tester') {
|
|
559
565
|
const optionsTester = argumentsCli.slice(1);
|
|
560
566
|
const ciblesTester = optionsTester.filter((argument) => argument !== '--detail');
|
|
561
567
|
if (ciblesTester.length > 1 || optionsTester.filter((argument) => argument === '--detail').length > 1 || ciblesTester.some((argument) => argument.startsWith('--'))) {
|
package/dist/lexer/lexer.js
CHANGED
|
@@ -30,6 +30,14 @@ function motColle(ligne, debut, premier) {
|
|
|
30
30
|
}
|
|
31
31
|
return null;
|
|
32
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* L'accent grave qui ferme un `code` commencé à `debut` (juste après le premier), sur la
|
|
35
|
+
* même ligne et après au moins un caractère ; -1 sinon : un accent grave seul reste un caractère.
|
|
36
|
+
*/
|
|
37
|
+
function finDuCode(texte, debut) {
|
|
38
|
+
const fin = texte.indexOf('`', debut);
|
|
39
|
+
return fin > debut ? fin : -1;
|
|
40
|
+
}
|
|
33
41
|
/**
|
|
34
42
|
* Le guillemet qui ferme un {code "…"} ou un {accent "…"} commencé à `debut` : le premier « "} »
|
|
35
43
|
* où les accolades du contenu sont équilibrées ({code "{accent "…"}"}), sinon le premier « "} »
|
|
@@ -52,6 +60,17 @@ function decouperChaine(valeur, ligne, colonne) {
|
|
|
52
60
|
let tampon = '';
|
|
53
61
|
let i = 0;
|
|
54
62
|
while (i < valeur.length) {
|
|
63
|
+
// `titre "Bonjour"` : le code d'une phrase, recopié tel quel, comme {code "…"}.
|
|
64
|
+
const finCode = valeur[i] === '`' ? finDuCode(valeur, i + 1) : -1;
|
|
65
|
+
if (finCode >= 0) {
|
|
66
|
+
if (tampon !== '') {
|
|
67
|
+
parties.push({ type: 'texte', valeur: tampon });
|
|
68
|
+
tampon = '';
|
|
69
|
+
}
|
|
70
|
+
parties.push({ type: 'code', valeur: valeur.slice(i + 1, finCode) });
|
|
71
|
+
i = finCode + 1;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
55
74
|
if (valeur[i] === '{') {
|
|
56
75
|
if (tampon !== '') {
|
|
57
76
|
parties.push({ type: 'texte', valeur: tampon });
|
|
@@ -223,6 +242,13 @@ export function analyser(source) {
|
|
|
223
242
|
let valeur = '';
|
|
224
243
|
let profondeur = 0;
|
|
225
244
|
while (i < ligne.length && (ligne[i] !== '"' || (profondeur > 0 && ligne.indexOf('}', i + 1) >= 0))) {
|
|
245
|
+
// `…` : le code d'une phrase, recopié tel quel jusqu'à l'accent grave qui le ferme.
|
|
246
|
+
const finCode = ligne[i] === '`' ? finDuCode(ligne, i + 1) : -1;
|
|
247
|
+
if (finCode >= 0) {
|
|
248
|
+
valeur += ligne.slice(i, finCode + 1);
|
|
249
|
+
i = finCode + 1;
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
226
252
|
// {code "…"} et {accent "…"} : recopiés tels quels jusqu'au premier « "} »
|
|
227
253
|
// (accolades, guillemets et antislashs compris).
|
|
228
254
|
const morceau = /^\{(code|accent)\s*"/.exec(ligne.slice(i));
|
|
@@ -257,6 +283,7 @@ export function analyser(source) {
|
|
|
257
283
|
valeur,
|
|
258
284
|
ligne: numLigne + 1,
|
|
259
285
|
colonne: debut + 1,
|
|
286
|
+
longueur: i - debut,
|
|
260
287
|
parties: decouperChaine(valeur, numLigne + 1, debut + 1),
|
|
261
288
|
});
|
|
262
289
|
continue;
|