@mostajs/wagon-ui 0.1.0 → 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/CHANGELOG.md +21 -0
- package/README.md +16 -0
- package/llms.txt +3 -0
- package/package.json +1 -1
- package/src/i18n/ar.js +10 -0
- package/src/i18n/en.js +10 -0
- package/src/i18n/fr.js +10 -0
- package/src/index.js +106 -13
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
**Auteur** : Dr Hamid MADANI <drmdh@msn.com>
|
|
4
4
|
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
## 0.3.0 — 2026-10-02
|
|
8
|
+
|
|
9
|
+
- **Palette Studio** (palette.amia.fr) : les jetons portent désormais les noms de la palette (`--brand`, `--link`,
|
|
10
|
+
`--accent`, `--text`, `--text-muted`, `--surface-alt`, `--text-on-brand`…), les anciens noms en dérivent.
|
|
11
|
+
**Le texte ne prend jamais `--accent`** (souvent illisible : 2,65:1 pour #F472B6) — liens et titres prennent `--link`.
|
|
12
|
+
- Option `theme: () => css` : le thème du projet, relu à chaque page et posé après la feuille de base ; refusé s'il
|
|
13
|
+
sort de `:root{…}` (aucune balise, aucun sélecteur).
|
|
14
|
+
- Option `entete: 'barre'` : l'en-tête des pages prend le style de la documentation (bande de marque).
|
|
15
|
+
- Essais T-WUI-13 à 15, écrits avant le code, chacun confronté à un sabotage.
|
|
16
|
+
- ⚠️ Rupture mineure : `--accent` désignait la couleur des liens ; il désigne désormais l'ornement (comme dans la palette).
|
|
17
|
+
## 0.2.0 — 2026-10-02
|
|
18
|
+
|
|
19
|
+
- **Affichage PC, tablette, mobile** : en-tête repliable derrière un **menu hamburger** (sans script : case à
|
|
20
|
+
cocher + CSS, compatible avec une CSP stricte), tableaux défilants, champs pleine largeur, grille des tarifs
|
|
21
|
+
sur une colonne sous 760 px.
|
|
22
|
+
- **Documentation publique** (`pageDocumentation`, `rechercheDocumentation`) : la mise en page des sites de
|
|
23
|
+
référence d'API — en-tête, onglets (Guides, Recettes, Référence API…), recherche, barre latérale par sections
|
|
24
|
+
(repliée derrière un hamburger sur mobile), sommaire « Sur cette page », lien vers la version Markdown ; fr, en, ar.
|
|
25
|
+
- Essais T-WUI-10 à 12, écrits avant le code, chacun confronté à un sabotage.
|
|
5
26
|
## 0.1.0 — 2026-10-01
|
|
6
27
|
Première version : les pages du wagon de mdseal (mise en page, accueil, tarifs, inscription, connexion par mot
|
|
7
28
|
de passe ou par code, espace client, administration des paliers et de l'usage, pages légales, introuvable), en
|
package/README.md
CHANGED
|
@@ -38,3 +38,19 @@ La logique vient des modules : comptes (`@mostajs/comptes`), abonnements et quot
|
|
|
38
38
|
(`@mostajs/subscriptions-plan`), connexion (`@mostajs/auth-lite`, `@mostajs/auth-plug`).
|
|
39
39
|
|
|
40
40
|
Voir `llms.txt` pour l'API complète.
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
## Documentation publique (0.2)
|
|
44
|
+
|
|
45
|
+
```js
|
|
46
|
+
const html = ui.pageDocumentation({
|
|
47
|
+
lang: 'fr', version: 'v1', base: 'https://acme.test',
|
|
48
|
+
onglets: [{ href: '/docs', libelle: 'Guides' }, { href: '/docs/api', libelle: 'Référence API' }], onglet: '/docs',
|
|
49
|
+
sections: [{ titre: 'Bien démarrer', pages: [{ href: '/docs', titre: 'Vue d’ensemble' }] }], actif: '/docs',
|
|
50
|
+
titre: 'Vue d’ensemble', sousTitre: '…', contenu: '<h2 id="a">…</h2>', sommaire: [{ id: 'a', titre: '…' }],
|
|
51
|
+
markdown: '/docs/index.md',
|
|
52
|
+
});
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
La recherche est un formulaire GET vers `recherche` (par défaut `/docs/recherche?q=`) : l'application cherche
|
|
56
|
+
dans ses pages et rend `ui.rechercheDocumentation({ lang, q, resultats })` dans `pageDocumentation`.
|
package/llms.txt
CHANGED
|
@@ -29,3 +29,6 @@ Rendre les écrans communs à tout SaaS, identiques d'un produit à l'autre, san
|
|
|
29
29
|
- `legal({ html })` n'échappe pas `html` : il doit venir des textes de l'éditeur (mostajs/templates), jamais d'un formulaire.
|
|
30
30
|
- Les prix sont en CENTIMES. `-1` = sans limite ; `0` = non compris.
|
|
31
31
|
- Une ressource sans `parPeriode` s'affiche « libellé : n ».
|
|
32
|
+
|
|
33
|
+
- 0.2 : `pageDocumentation({ lang, onglets, onglet, sections, actif, titre, sousTitre, contenu, sommaire, markdown, version, base, recherche, q })`, `rechercheDocumentation({ lang, q, resultats })` ; menu hamburger sans script dans `page()`.
|
|
34
|
+
- 0.3 : `creerWagonUi({ …, theme: () => ':root{…}', entete: 'simple'|'barre' })` ; jetons aux noms de Palette Studio ; le texte prend `--link`, jamais `--accent`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mostajs/wagon-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Les pages du wagon d'un SaaS — mise en page (langues, pied légal, en-têtes pour les moteurs), accueil, tarifs, inscription, connexion (mot de passe ou code), espace client (palier, jauges, clés, factures, gestion de l'abonnement), administration (paliers, usage), pages légales — en fr, en, ar. Extraites de mdseal, sans ce qu'il vend : l'identité et les ressources mesurées viennent de la configuration. Vues pures, zéro dépendance.",
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"author": "Dr Hamid MADANI <drmdh@msn.com>",
|
package/src/i18n/ar.js
CHANGED
|
@@ -7,7 +7,17 @@ export const ar = {
|
|
|
7
7
|
"apropos": "حول",
|
|
8
8
|
"legal": "قانوني"
|
|
9
9
|
},
|
|
10
|
+
"doc": {
|
|
11
|
+
"documentation": "التوثيق",
|
|
12
|
+
"rechercher": "بحث",
|
|
13
|
+
"surCettePage": "في هذه الصفحة",
|
|
14
|
+
"markdown": "عرض بصيغة Markdown",
|
|
15
|
+
"resultats": "نتائج البحث عن «{q}»",
|
|
16
|
+
"aucun": "لا توجد نتائج.",
|
|
17
|
+
"accueil": "الرئيسية"
|
|
18
|
+
},
|
|
10
19
|
"layout": {
|
|
20
|
+
"menu": "القائمة",
|
|
11
21
|
"auteur": "المؤلّف",
|
|
12
22
|
"propulse": "مبنيّ على منظومة",
|
|
13
23
|
"langue": "اللغة"
|
package/src/i18n/en.js
CHANGED
|
@@ -7,7 +7,17 @@ export const en = {
|
|
|
7
7
|
"apropos": "About",
|
|
8
8
|
"legal": "Legal"
|
|
9
9
|
},
|
|
10
|
+
"doc": {
|
|
11
|
+
"documentation": "Documentation",
|
|
12
|
+
"rechercher": "Search",
|
|
13
|
+
"surCettePage": "On this page",
|
|
14
|
+
"markdown": "View as Markdown",
|
|
15
|
+
"resultats": "Results for “{q}”",
|
|
16
|
+
"aucun": "No result.",
|
|
17
|
+
"accueil": "Home"
|
|
18
|
+
},
|
|
10
19
|
"layout": {
|
|
20
|
+
"menu": "Menu",
|
|
11
21
|
"auteur": "Author",
|
|
12
22
|
"propulse": "built on the",
|
|
13
23
|
"langue": "Language"
|
package/src/i18n/fr.js
CHANGED
|
@@ -7,7 +7,17 @@ export const fr = {
|
|
|
7
7
|
"apropos": "À propos",
|
|
8
8
|
"legal": "Légal"
|
|
9
9
|
},
|
|
10
|
+
"doc": {
|
|
11
|
+
"documentation": "Documentation",
|
|
12
|
+
"rechercher": "Rechercher",
|
|
13
|
+
"surCettePage": "Sur cette page",
|
|
14
|
+
"markdown": "Voir en Markdown",
|
|
15
|
+
"resultats": "Résultats pour « {q} »",
|
|
16
|
+
"aucun": "Aucun résultat.",
|
|
17
|
+
"accueil": "Accueil"
|
|
18
|
+
},
|
|
10
19
|
"layout": {
|
|
20
|
+
"menu": "Menu",
|
|
11
21
|
"auteur": "Auteur",
|
|
12
22
|
"propulse": "propulsé par l’écosystème",
|
|
13
23
|
"langue": "Langue"
|
package/src/index.js
CHANGED
|
@@ -39,23 +39,27 @@ const lien = (href) => {
|
|
|
39
39
|
const NBSP = ' ';
|
|
40
40
|
const euros = (centimes) => `${(Number(centimes) || 0) / 100}${NBSP}€`;
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
// 0.3 : les jetons portent les NOMS de Palette Studio (palette.amia.fr) — un thème qui les redéfinit habille le site
|
|
43
|
+
// entier ; les anciens noms en dérivent par var(), donc le suivent. Contrastes des replis vérifiés sur blanc.
|
|
44
|
+
const DERIVES = '--line:#e2e8f0;--text-muted:#475569;--surface-alt:#f1f5f9;--hover:#eef2f9;--field-border:#cbd5e0;'
|
|
45
|
+
+ '--success-bg:#f0fdf4;--success-text:#166534;--error-bg:#fef2f2;--error-text:#991b1b;'
|
|
46
|
+
+ '--doux:var(--text-muted);--fond-doux:var(--surface-alt);--survol:var(--hover);--bordure:var(--field-border);'
|
|
47
|
+
+ '--ink:var(--text);--ornement:var(--accent);--succes-fond:var(--success-bg);--succes-texte:var(--success-text);--succes-bord:#bbf7d0;'
|
|
48
|
+
+ '--erreur-fond:var(--error-bg);--erreur-texte:var(--error-text);--erreur-bord:#fecaca';
|
|
45
49
|
const REPLI = { brand: '#06284A', lien: '#2c5282', ornement: '#2c5282', encre: '#1a1a1a', surface: '#fff' };
|
|
46
50
|
const COULEUR = /^#[0-9a-fA-F]{3,8}$/;
|
|
47
51
|
|
|
48
52
|
/** La feuille de style de mdseal, sans ce qui servait la charge utile. Aucune couleur en dur. */
|
|
49
53
|
function style(c) {
|
|
50
|
-
return `:root{--brand:${c.brand};--
|
|
54
|
+
return `:root{--brand:${c.brand};--link:${c.lien};--accent:${c.ornement};--text:${c.encre};--surface:${c.surface};--bg:${c.surface};--text-on-brand:#ffffff;${DERIVES}}
|
|
51
55
|
*{box-sizing:border-box} body{font:15px/1.6 system-ui,sans-serif;margin:0;color:var(--ink);background:var(--surface)}
|
|
52
56
|
header{display:flex;align-items:center;gap:1.2rem;flex-wrap:wrap;max-width:920px;margin:0 auto;padding:1rem;border-bottom:3px solid var(--ornement)}
|
|
53
57
|
.brand{font-weight:800;font-size:1.25rem;color:var(--brand);text-decoration:none}
|
|
54
|
-
nav{display:flex;gap:.3rem;flex-wrap:wrap} nav a{color:var(--
|
|
58
|
+
nav{display:flex;gap:.3rem;flex-wrap:wrap} nav a{color:var(--link);text-decoration:none;padding:.3em .7em;border-radius:6px} nav a.on,nav a:hover{background:var(--survol)}
|
|
55
59
|
main{max-width:920px;margin:0 auto;padding:0 1rem 2rem}
|
|
56
|
-
h1{color:var(--brand)} h2{color:var(--
|
|
60
|
+
h1{color:var(--brand)} h2{color:var(--link);border-bottom:1px solid var(--line);padding-bottom:.2em;margin-top:1.6em}
|
|
57
61
|
h3{color:var(--brand);font-size:1.02rem;margin:1.2em 0 .3em}
|
|
58
|
-
fieldset{border:1px solid var(--line);border-radius:8px;margin:.8em 0;padding:.6em 1em} legend{color:var(--
|
|
62
|
+
fieldset{border:1px solid var(--line);border-radius:8px;margin:.8em 0;padding:.6em 1em} legend{color:var(--link);font-weight:700}
|
|
59
63
|
input[type=text],input[type=email],input[type=password],input[type=number]{padding:5px 8px;border:1px solid var(--bordure);border-radius:6px;font-size:14px}
|
|
60
64
|
input[type=text],input[type=email],input[type=password]{min-width:15em} input[type=number]{width:7em}
|
|
61
65
|
.champs{display:flex;flex-wrap:wrap;gap:.8em 1.4em;align-items:flex-end}
|
|
@@ -82,11 +86,59 @@ pre,code,kbd,samp{direction:ltr;text-align:left;unicode-bidi:isolate} [dir="rtl"
|
|
|
82
86
|
.jauges{list-style:none;padding:0} .jauges li{margin:.5em 0}
|
|
83
87
|
.barre{display:inline-block;vertical-align:middle;width:9em;height:.55em;background:var(--line);border-radius:99px;margin-inline-start:.6em;overflow:hidden}
|
|
84
88
|
.barre i{display:block;height:100%;background:var(--ornement)}
|
|
85
|
-
.moi{margin-inline-start:auto;font-size:.9em} .moi a{color:var(--
|
|
86
|
-
.langues{margin-inline-start:.8em;font-size:.9em;color:var(--doux)} .langues a{color:var(--
|
|
89
|
+
.moi{margin-inline-start:auto;font-size:.9em} .moi a{color:var(--link);text-decoration:none}
|
|
90
|
+
.langues{margin-inline-start:.8em;font-size:.9em;color:var(--doux)} .langues a{color:var(--link);text-decoration:none} .langues b{color:var(--brand)}
|
|
87
91
|
.hint{color:var(--doux);font-size:.9em} footer{max-width:920px;margin:2rem auto 0;padding:1rem;border-top:1px solid var(--line);color:var(--doux);font-size:.9em}
|
|
88
|
-
footer a{color:var(--
|
|
92
|
+
footer a{color:var(--link)}
|
|
93
|
+
.menu{display:flex;align-items:center;gap:1.2rem;flex:1 1 auto;flex-wrap:wrap}
|
|
94
|
+
.bascule{position:absolute;opacity:0;width:1px;height:1px;pointer-events:none}
|
|
95
|
+
.burger{display:none;margin-inline-start:auto;font-size:1.5rem;line-height:1;cursor:pointer;padding:.2em .45em;border:1px solid var(--line);border-radius:6px;color:var(--brand);user-select:none}
|
|
96
|
+
.bascule:focus-visible+.burger{outline:2px solid var(--link);outline-offset:2px}
|
|
97
|
+
table{display:block;max-width:100%;overflow-x:auto}
|
|
98
|
+
img,pre{max-width:100%} pre{overflow-x:auto}
|
|
99
|
+
@media (max-width: 760px){
|
|
100
|
+
.burger{display:block}
|
|
101
|
+
.menu{display:none;flex-basis:100%;flex-direction:column;align-items:stretch;gap:.4rem;padding-top:.4rem}
|
|
102
|
+
.bascule:checked ~ .menu{display:flex}
|
|
103
|
+
nav{flex-direction:column} nav a{padding:.55em .7em}
|
|
104
|
+
.moi,.langues{margin-inline-start:0}
|
|
105
|
+
header{padding:.7rem .8rem} main{padding:0 .8rem 1.5rem} h1{font-size:1.45rem}
|
|
106
|
+
input[type=text],input[type=email],input[type=password]{min-width:0;width:100%}
|
|
107
|
+
.champs label{flex:1 1 100%}
|
|
108
|
+
.grille-tarifs{grid-template-columns:1fr}
|
|
89
109
|
}
|
|
110
|
+
header.barre{max-width:none;background:var(--brand);border-bottom:0;padding:.8rem max(1rem,calc((100% - 920px)/2))}
|
|
111
|
+
header.barre .brand,header.barre nav a,header.barre .moi a,header.barre .langues a,header.barre .langues b,header.barre .langues{color:var(--text-on-brand)}
|
|
112
|
+
header.barre nav a{opacity:.9} header.barre nav a.on,header.barre nav a:hover{background:rgba(255,255,255,.16);opacity:1}
|
|
113
|
+
header.barre .burger{color:var(--text-on-brand);border-color:rgba(255,255,255,.4)}
|
|
114
|
+
header.barre + main{padding-top:1.2rem}`;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Un thème de Palette Studio n'est accepté que s'il ne fait que redéfinir des jetons dans :root{…}. */
|
|
118
|
+
const THEME_SAIN = /^\s*:root\s*\{[^{}<]*\}\s*$/;
|
|
119
|
+
|
|
120
|
+
/** La documentation (0.2) : la mise en page des sites de référence d'API — onglets, barre latérale, sommaire. */
|
|
121
|
+
const STYLE_DOC = `body{background:var(--surface)} .doc-tete{display:flex;align-items:center;gap:1rem;flex-wrap:wrap;padding:.8rem 1.2rem;background:var(--brand);color:var(--surface)}
|
|
122
|
+
.doc-tete .brand{color:var(--surface)} .doc-tete .doc-mot{opacity:.85} .doc-tete .version{font-size:.8em;border:1px solid currentColor;border-radius:4px;padding:0 .4em;opacity:.85}
|
|
123
|
+
.doc-tete .moi a,.doc-tete .langues a,.doc-tete .langues b{color:var(--surface)} .doc-tete .langues{color:var(--surface)}
|
|
124
|
+
.doc-barre{display:flex;align-items:center;gap:.6rem;flex-wrap:wrap;padding:.4rem 1.2rem;background:var(--brand);border-top:1px solid rgba(255,255,255,.15)}
|
|
125
|
+
.onglets{display:flex;gap:.3rem;flex-wrap:wrap} .onglets a{color:var(--surface);text-decoration:none;padding:.35em .8em;border-radius:6px;opacity:.9} .onglets a.on,.onglets a:hover{background:rgba(255,255,255,.16);opacity:1}
|
|
126
|
+
.recherche{margin-inline-start:auto} .recherche input{padding:.4em .7em;border-radius:6px;border:0;min-width:14em}
|
|
127
|
+
.doc{display:grid;grid-template-columns:16rem minmax(0,1fr) 14rem;max-width:1280px;margin:0 auto}
|
|
128
|
+
.laterale{border-inline-end:1px solid var(--line);padding:1.4rem 1rem;position:sticky;top:0;align-self:start;max-height:100vh;overflow:auto}
|
|
129
|
+
.laterale h3{font-size:.78rem;letter-spacing:.06em;text-transform:uppercase;color:var(--doux);margin:1.4em 0 .4em} .laterale h3:first-child{margin-top:0}
|
|
130
|
+
.laterale ul{list-style:none;margin:0;padding:0} .laterale a{display:block;padding:.35em .6em;border-radius:6px;color:var(--ink);text-decoration:none} .laterale a.on{background:var(--fond-doux);font-weight:600} .laterale a:hover{background:var(--survol)}
|
|
131
|
+
.doc-corps{padding:1.6rem 2.2rem 3rem;min-width:0} .doc-corps h1{margin:.1em 0 .2em;font-size:2.1rem} .doc-corps .lede{font-size:1.12rem;color:var(--doux);margin:.2em 0 1em}
|
|
132
|
+
.doc-corps hr{border:0;border-top:1px solid var(--line);margin:1.2em 0 1.6em} .doc-corps a.md{float:inline-end;font-size:.88em;border:1px solid var(--line);border-radius:6px;padding:.3em .7em;text-decoration:none;color:var(--link)}
|
|
133
|
+
.sommaire{padding:1.6rem 1rem;position:sticky;top:0;align-self:start;font-size:.9em} .sommaire p{color:var(--doux);margin:0 0 .4em}
|
|
134
|
+
.sommaire ul{list-style:none;margin:0;padding:0;border-inline-start:2px solid var(--line)} .sommaire a{display:block;padding:.25em .8em;color:var(--doux);text-decoration:none} .sommaire a:hover{color:var(--link)}
|
|
135
|
+
.doc-barre .burger{color:var(--surface);border-color:rgba(255,255,255,.4)}
|
|
136
|
+
@media (max-width: 1100px){ .doc{grid-template-columns:15rem minmax(0,1fr)} .sommaire{display:none} }
|
|
137
|
+
@media (max-width: 760px){
|
|
138
|
+
.doc{grid-template-columns:1fr} .laterale{display:none;position:static;max-height:none;border-inline-end:0;border-bottom:1px solid var(--line)}
|
|
139
|
+
.bascule:checked ~ .doc .laterale{display:block} .doc-barre .burger{display:block;margin-inline-start:0}
|
|
140
|
+
.recherche{flex-basis:100%} .recherche input{width:100%;min-width:0} .doc-corps{padding:1.2rem .9rem 2rem} .doc-corps h1{font-size:1.6rem}
|
|
141
|
+
}`;
|
|
90
142
|
|
|
91
143
|
/** Un texte multilingue de la configuration : chaîne simple, ou { fr, en, ar }. */
|
|
92
144
|
const dansLangue = (v, lang) => (v && typeof v === 'object' ? (v[lang] ?? v.fr ?? Object.values(v)[0] ?? '') : (v ?? ''));
|
|
@@ -104,13 +156,15 @@ const DOCUMENTS_LEGAUX = Object.freeze([['mentions-legales', 'mentions'], ['cgu'
|
|
|
104
156
|
* parPeriode?: string|object, sansLimite?: string|object, aucun?: string|object }>} [o.ressources]
|
|
105
157
|
* @param {object} [o.textes] surcharges des libellés, par langue : { fr: { tarifs: { note: '…' } } }
|
|
106
158
|
*/
|
|
107
|
-
export function creerWagonUi({ marque, couleurs = {}, langues = ['fr', 'en', 'ar'], liens = [], ressources = [], textes = {} } = {}) {
|
|
159
|
+
export function creerWagonUi({ marque, couleurs = {}, langues = ['fr', 'en', 'ar'], liens = [], ressources = [], textes = {}, theme = null, entete = 'simple' } = {}) {
|
|
108
160
|
if (!String(marque?.nom ?? '').trim()) throw new Error('@mostajs/wagon-ui : `marque.nom` requis — le nom du service');
|
|
109
161
|
if (!String(marque?.auteur ?? '').trim()) throw new Error('@mostajs/wagon-ui : `marque.auteur` requis — qui édite le service');
|
|
110
162
|
const LANGUES = langues.filter((l) => CATALOGUES[l]);
|
|
111
163
|
if (!LANGUES.length) throw new Error('@mostajs/wagon-ui : aucune langue connue (fr, en, ar)');
|
|
112
164
|
const C = Object.fromEntries(Object.entries({ ...REPLI, ...couleurs }).map(([k, v]) => [k, COULEUR.test(String(v)) ? v : REPLI[k]]));
|
|
113
165
|
const STYLE = style(C);
|
|
166
|
+
/** Le thème courant (Palette Studio), relu à chaque page ; rejeté s'il sort de :root{…}. */
|
|
167
|
+
const themeCss = () => { try { const t = typeof theme === 'function' ? String(theme() ?? '') : ''; return THEME_SAIN.test(t) ? t : ''; } catch { return ''; } };
|
|
114
168
|
|
|
115
169
|
/** Le traducteur d'une langue : surcharges de l'application, puis le catalogue, puis le français. */
|
|
116
170
|
function t(lang) {
|
|
@@ -151,8 +205,8 @@ export function creerWagonUi({ marque, couleurs = {}, langues = ['fr', 'en', 'ar
|
|
|
151
205
|
: `<a href="${chemin}?lang=${l}" hreflang="${l}" lang="${l}" rel="alternate">${esc(LIBELLES_LANGUES[l])}</a>`)).join(' · ')}</span>`;
|
|
152
206
|
return `<!doctype html><html lang="${L}" dir="${dirDe(L)}"><head><meta charset="utf-8">
|
|
153
207
|
<meta name="viewport" content="width=device-width, initial-scale=1"><title>${esc(titre)} — ${nom}</title>
|
|
154
|
-
${tete}<style>${STYLE}</style
|
|
155
|
-
<header><a class="brand" href="/">${marque.symbole ? `${esc(marque.symbole)} ` : ''}${nom}</a><nav>${nav.map(([h, l]) => `<a href="${lien(h)}"${actif === h ? ' class="on"' : ''}>${esc(l)}</a>`).join('')}</nav><span class="moi"><a href="${connecte ? '/compte' : '/connexion'}">${esc(tr(connecte ? 'espace.compte' : 'espace.seConnecter'))}</a>${connecte ? ` · <a href="/deconnexion">${esc(tr('espace.seDeconnecter'))}</a>` : ''}</span>${choixLangue}</header>
|
|
208
|
+
${tete}<style>${STYLE}</style>${themeCss() ? `<style id="theme-palette">${themeCss()}</style>` : ''}</head><body>
|
|
209
|
+
<header${entete === 'barre' ? ' class="barre"' : ''}><a class="brand" href="/">${marque.symbole ? `${esc(marque.symbole)} ` : ''}${nom}</a><input type="checkbox" id="wagon-menu" class="bascule"><label for="wagon-menu" class="burger" aria-label="${esc(tr('layout.menu'))}">☰</label><div class="menu"><nav>${nav.map(([h, l]) => `<a href="${lien(h)}"${actif === h ? ' class="on"' : ''}>${esc(l)}</a>`).join('')}</nav><span class="moi"><a href="${connecte ? '/compte' : '/connexion'}">${esc(tr(connecte ? 'espace.compte' : 'espace.seConnecter'))}</a>${connecte ? ` · <a href="/deconnexion">${esc(tr('espace.seDeconnecter'))}</a>` : ''}</span>${choixLangue}</div></header>
|
|
156
210
|
<main>${contenu}</main>
|
|
157
211
|
<footer><b>${nom}</b>${slogan ? ` — ${esc(slogan)}` : ''} · ${esc(tr('layout.auteur'))} : <b>${esc(marque.auteur)}</b>${marque.courriel ? ` <${esc(marque.courriel)}>` : ''} · ${esc(marque.licence ?? 'AGPL-3.0')} · ${esc(tr('layout.propulse'))} <code>@mostajs/*</code><br><span class="legal">${DOCUMENTS_LEGAUX.map(([h, k]) => `<a href="/${h}">${esc(tr(`legal.${k}`))}</a>`).join(' · ')}</span></footer>
|
|
158
212
|
</body></html>`;
|
|
@@ -393,6 +447,45 @@ ${anonyme ? `<h2>${esc(tr('usage.anonyme'))}</h2>
|
|
|
393
447
|
: `<h1>${esc(tr(`legal.${cle}`))}</h1><p class="hint">${esc(tr('legal.indisponible'))}</p>`;
|
|
394
448
|
},
|
|
395
449
|
|
|
450
|
+
/**
|
|
451
|
+
* 0.2 — LA DOCUMENTATION publique, page entière : en-tête, onglets (Guides, Recettes, Référence API…),
|
|
452
|
+
* recherche, barre latérale par sections, contenu, sommaire « Sur cette page », lien vers la version
|
|
453
|
+
* Markdown. `contenu` est du HTML rendu par l'application depuis SES textes (jamais d'un formulaire).
|
|
454
|
+
* Sans script : la barre latérale se replie derrière un hamburger sur mobile.
|
|
455
|
+
*/
|
|
456
|
+
pageDocumentation({ lang, onglets = [], onglet = '', sections = [], actif = '', titre = '', sousTitre = '', contenu = '', sommaire = [], markdown = '', version = '', base = '', recherche = '/docs/recherche', q = '' }) {
|
|
457
|
+
const L = langueDe(lang); const tr = t(L);
|
|
458
|
+
const nom = esc(marque.nom);
|
|
459
|
+
const canonique = base && actif ? `${String(base).replace(/\/$/, '')}${actif}` : '';
|
|
460
|
+
const chemin = lien(actif || '/');
|
|
461
|
+
const choixLangue = LANGUES.length < 2 ? '' : `<span class="langues">${LANGUES.map((l) => (l === L ? `<b lang="${l}">${esc(LIBELLES_LANGUES[l])}</b>` : `<a href="${chemin}?lang=${l}" hreflang="${l}" lang="${l}" rel="alternate">${esc(LIBELLES_LANGUES[l])}</a>`)).join(' · ')}</span>`;
|
|
462
|
+
const lienPage = (p) => (p.externe
|
|
463
|
+
? `<a href="${lien(p.href)}" rel="noopener" target="_blank">${esc(p.titre)} ↗</a>`
|
|
464
|
+
: `<a href="${lien(p.href)}"${p.href === actif ? ' class="on" aria-current="page"' : ''}>${esc(p.titre)}</a>`);
|
|
465
|
+
return `<!doctype html><html lang="${L}" dir="${dirDe(L)}"><head><meta charset="utf-8">
|
|
466
|
+
<meta name="viewport" content="width=device-width, initial-scale=1"><title>${esc(titre)} — ${esc(tr('doc.documentation'))} ${nom}</title>
|
|
467
|
+
${canonique ? `<link rel="canonical" href="${esc(canonique)}">${sousTitre ? `<meta name="description" content="${esc(sousTitre)}">` : ''}` : ''}<style>${STYLE}${STYLE_DOC}</style>${themeCss() ? `<style id="theme-palette">${themeCss()}</style>` : ''}</head><body>
|
|
468
|
+
<header class="doc-tete"><a class="brand" href="/">${marque.symbole ? `${esc(marque.symbole)} ` : ''}${nom}</a><span class="doc-mot">${esc(tr('doc.documentation'))}</span>${version ? `<span class="version">${esc(version)}</span>` : ''}<span class="moi"><a href="/">${esc(tr('doc.accueil'))}</a></span>${choixLangue}</header>
|
|
469
|
+
<input type="checkbox" id="doc-menu" class="bascule">
|
|
470
|
+
<div class="doc-barre"><label for="doc-menu" class="burger" aria-label="${esc(tr('layout.menu'))}">☰</label><nav class="onglets">${onglets.map((o) => `<a href="${lien(o.href)}"${o.href === onglet ? ' class="on"' : ''}>${esc(dansLangue(o.libelle, L))}</a>`).join('')}</nav><form class="recherche" method="get" action="${lien(recherche)}"><input type="search" name="q" value="${esc(q)}" placeholder="${esc(tr('doc.rechercher'))}" aria-label="${esc(tr('doc.rechercher'))}"></form></div>
|
|
471
|
+
<div class="doc">
|
|
472
|
+
<aside class="laterale">${sections.map((x) => `<h3>${esc(dansLangue(x.titre, L))}</h3><ul>${(x.pages ?? []).map((p) => `<li>${lienPage({ ...p, titre: dansLangue(p.titre, L) })}</li>`).join('')}</ul>`).join('')}</aside>
|
|
473
|
+
<main class="doc-corps">${markdown ? `<a class="md" href="${lien(markdown)}">${esc(tr('doc.markdown'))}</a>` : ''}<h1>${esc(titre)}</h1>${sousTitre ? `<p class="lede">${esc(sousTitre)}</p>` : ''}<hr>
|
|
474
|
+
${contenu}</main>
|
|
475
|
+
<aside class="sommaire">${sommaire.length ? `<p>${esc(tr('doc.surCettePage'))}</p><ul>${sommaire.map((x) => `<li><a href="#${esc(String(x.id).replace(/[^A-Za-z0-9_-]/g, ''))}">${esc(x.titre)}</a></li>`).join('')}</ul>` : ''}</aside>
|
|
476
|
+
</div>
|
|
477
|
+
<footer><b>${nom}</b> · ${esc(tr('layout.auteur'))} : <b>${esc(marque.auteur)}</b> · ${esc(marque.licence ?? 'AGPL-3.0')}<br><span class="legal">${DOCUMENTS_LEGAUX.map(([h, k]) => `<a href="/${h}">${esc(tr(`legal.${k}`))}</a>`).join(' · ')}</span></footer>
|
|
478
|
+
</body></html>`;
|
|
479
|
+
},
|
|
480
|
+
|
|
481
|
+
/** Les résultats d'une recherche dans la documentation (le contenu de `pageDocumentation`). */
|
|
482
|
+
rechercheDocumentation({ lang, q = '', resultats = [] }) {
|
|
483
|
+
const tr = t(langueDe(lang));
|
|
484
|
+
return `<p class="lede">${esc(tr('doc.resultats', { q }))}</p>${resultats.length
|
|
485
|
+
? `<ul class="resultats">${resultats.map((r) => `<li><a href="${lien(r.href)}">${esc(r.titre)}</a>${r.extrait ? `<br><span class="hint">${esc(r.extrait)}</span>` : ''}</li>`).join('')}</ul>`
|
|
486
|
+
: `<p>${esc(tr('doc.aucun'))}</p>`}`;
|
|
487
|
+
},
|
|
488
|
+
|
|
396
489
|
introuvable({ lang }) {
|
|
397
490
|
const tr = t(langueDe(lang));
|
|
398
491
|
return `<h1>${esc(tr('erreur.introuvable'))}</h1><p>${esc(tr('erreur.introuvableTexte'))}</p><p><a href="/">${esc(tr('erreur.retour'))}</a></p>`;
|