@mostajs/wagon-ui 0.5.2 → 0.6.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 +14 -0
- package/llms.txt +3 -1
- package/package.json +1 -1
- package/src/index.js +11 -5
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,20 @@
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
|
|
8
|
+
## 0.6.0 — 2026-10-09
|
|
9
|
+
|
|
10
|
+
**Ajouté** : `creerWagonUi({ css })` — la feuille de style **de l'application** (sa configuration, de confiance),
|
|
11
|
+
posée après la feuille de base et le thème, sur **toutes** les pages (y compris `pageDocumentation`) : une
|
|
12
|
+
application peut enfin restyler les pages du wagon qu'elle ne rend pas (connexion, aide, à propos, pages légales).
|
|
13
|
+
Le thème Palette Studio reste borné à `:root{…}`. Une feuille qui contient `</` est écartée entière. Essai T-WUI-25.
|
|
14
|
+
|
|
15
|
+
## 0.5.3 — 2026-10-09
|
|
16
|
+
|
|
17
|
+
**Ajouté** (audit de travel, référencement) : chaque page porte l'icône de l'onglet
|
|
18
|
+
(`<link rel="icon" href="/favicon.svg">`, servie par le wagon 0.8) et, quand une image est connue, `og:image` —
|
|
19
|
+
celle de la page (`page({ image })`), sinon celle de la marque (`marque.image`) ; un chemin du site devient absolu,
|
|
20
|
+
toute autre adresse que https est ignorée. Essai T-WUI-24.
|
|
21
|
+
|
|
8
22
|
## 0.5.2 — 2026-10-09
|
|
9
23
|
|
|
10
24
|
**Corrigé** (audit) : le menu des langues annonce aussi la langue COURANTE aux lecteurs d'écran
|
package/llms.txt
CHANGED
|
@@ -11,7 +11,7 @@ Rendre les écrans communs à tout SaaS, identiques d'un produit à l'autre, san
|
|
|
11
11
|
|
|
12
12
|
## API
|
|
13
13
|
`creerWagonUi({ marque: { nom, auteur, symbole?, slogan?, courriel?, licence? }, couleurs?: { brand, lien, ornement, encre, surface }, langues?: ['fr','en','ar'], liens?: [{ href, libelle }], ressources?: [{ cle, limite, periode: 'jour'|'mois', libelle, parPeriode?, sansLimite?, aucun? }], textes?: { fr: { … } } })` → `ui` :
|
|
14
|
-
- `ui.page({ actif, titre, contenu, lang, connecte?, base?, description? })` — la page entière ; `contenu` = HTML déjà rendu.
|
|
14
|
+
- `ui.page({ actif, titre, contenu, lang, connecte?, base?, description?, image? })` — la page entière ; `contenu` = HTML déjà rendu.
|
|
15
15
|
- `ui.accueil({ lang, titre, lede?, actions?: [{ href, libelle, principal? }], preuves?: [{ titre, texte }], note? })`
|
|
16
16
|
- `ui.tarifs({ lang, paliers: [{ slug, name, price (centimes), active?, limits: { <limite>: n, maxCles } }], sousTitre?, inscriptionHref? })`
|
|
17
17
|
- `ui.inscription({ lang, palier, erreur?, minMotDePasse?, methode?: 'motdepasse'|'code', action? })` · `ui.inscriptionFaite({ lang, cle?, base?, paiement?: { compteId, planSlug } })`
|
|
@@ -26,6 +26,8 @@ Rendre les écrans communs à tout SaaS, identiques d'un produit à l'autre, san
|
|
|
26
26
|
`/`, `/tarifs`, `/aide`, `/apropos`, `/inscription` (POST : plan, nom, courriel, motdepasse), `/connexion` (POST : courriel, motdepasse), `/deconnexion`, `/compte`, `/compte/cles` (POST : libelle), `/compte/cles/revoquer` (POST : prefix), `/compte/portail` (POST), `/paiement` (POST : compte, plan), `/admin/plans` (POST), `/admin/usage`, `/mentions-legales`, `/cgu`, `/cgv`, `/confidentialite`.
|
|
27
27
|
|
|
28
28
|
## PIÈGES
|
|
29
|
+
- 0.6.0 : `creerWagonUi({ css })` — feuille de l'application (de confiance) après base et thème, sur chaque page ; écartée si elle contient `</`.
|
|
30
|
+
- 0.5.3 : icône `/favicon.svg` dans chaque page ; `og:image` depuis `page({ image })` ou `marque.image` (chemin du site ou https).
|
|
29
31
|
- 0.5.1 : `page({ chemin })` — canonique et liens de langue sur la page réelle.
|
|
30
32
|
- 0.5.0 : `creerWagonUi({ …, collant: true, appel: { href, libelle }, selecteurLangue: 'menu' })` — en-tête collant, bouton d'appel, langue en menu déroulant sans script ; défauts inchangés.
|
|
31
33
|
- 0.4.0 : `tarifs.clesPluriel` n'existe plus → `tarifs.cles_one` / `tarifs.cles_other` (+ formes arabes) ; prix formatés par langue (« 9,99 € »).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mostajs/wagon-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.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/index.js
CHANGED
|
@@ -172,7 +172,7 @@ const DOCUMENTS_LEGAUX = Object.freeze([['mentions-legales', 'mentions'], ['cgu'
|
|
|
172
172
|
* parPeriode?: string|object, sansLimite?: string|object, aucun?: string|object }>} [o.ressources]
|
|
173
173
|
* @param {object} [o.textes] surcharges des libellés, par langue : { fr: { tarifs: { note: '…' } } }
|
|
174
174
|
*/
|
|
175
|
-
export function creerWagonUi({ marque, couleurs = {}, langues = ['fr', 'en', 'ar'], liens = [], ressources = [], textes = {}, theme = null, entete = 'simple', collant = false, appel = null, selecteurLangue = 'liens' } = {}) {
|
|
175
|
+
export function creerWagonUi({ marque, couleurs = {}, langues = ['fr', 'en', 'ar'], liens = [], ressources = [], textes = {}, theme = null, entete = 'simple', collant = false, appel = null, selecteurLangue = 'liens', css = '' } = {}) {
|
|
176
176
|
if (!String(marque?.nom ?? '').trim()) throw new Error('@mostajs/wagon-ui : `marque.nom` requis — le nom du service');
|
|
177
177
|
if (!String(marque?.auteur ?? '').trim()) throw new Error('@mostajs/wagon-ui : `marque.auteur` requis — qui édite le service');
|
|
178
178
|
const LANGUES = langues.filter((l) => CATALOGUES[l]);
|
|
@@ -180,6 +180,9 @@ export function creerWagonUi({ marque, couleurs = {}, langues = ['fr', 'en', 'ar
|
|
|
180
180
|
const C = Object.fromEntries(Object.entries({ ...REPLI, ...couleurs }).map(([k, v]) => [k, COULEUR.test(String(v)) ? v : REPLI[k]]));
|
|
181
181
|
const STYLE = style(C);
|
|
182
182
|
/** Le thème courant (Palette Studio), relu à chaque page ; rejeté s'il sort de :root{…}. */
|
|
183
|
+
// 0.6.0 — la feuille DE L'APPLICATION (sa configuration, de confiance), après la base et le thème, sur chaque page.
|
|
184
|
+
// Elle ne peut pas fermer la balise <style> : une feuille qui contient « </ » est écartée entière.
|
|
185
|
+
const feuilleApp = typeof css === 'string' && css.trim() && !/<\//.test(css) ? `<style id="feuille-app">${css}</style>` : '';
|
|
183
186
|
const themeCss = () => { try { const t = typeof theme === 'function' ? String(theme() ?? '') : ''; return THEME_SAIN.test(t) ? t : ''; } catch { return ''; } };
|
|
184
187
|
|
|
185
188
|
/**
|
|
@@ -222,7 +225,7 @@ export function creerWagonUi({ marque, couleurs = {}, langues = ['fr', 'en', 'ar
|
|
|
222
225
|
* La page entière. `contenu` est du HTML déjà rendu (par une vue de ce module, ou par la charge
|
|
223
226
|
* utile). `base` — l'adresse publique — ajoute les en-têtes que lisent les moteurs et les aperçus.
|
|
224
227
|
*/
|
|
225
|
-
page({ actif = '/', chemin: cheminPage = '', titre = '', contenu = '', lang, connecte = false, base = '', description = '' }) {
|
|
228
|
+
page({ actif = '/', chemin: cheminPage = '', titre = '', contenu = '', lang, connecte = false, base = '', description = '', image = '' }) {
|
|
226
229
|
const L = langueDe(lang); const tr = t(L);
|
|
227
230
|
const nom = esc(marque.nom);
|
|
228
231
|
const slogan = dansLangue(marque.slogan, L);
|
|
@@ -231,10 +234,13 @@ export function creerWagonUi({ marque, couleurs = {}, langues = ['fr', 'en', 'ar
|
|
|
231
234
|
const ici = cheminPage || actif;
|
|
232
235
|
const chemin = lien(ici);
|
|
233
236
|
const canonique = base ? `${String(base).replace(/\/$/, '')}${ici === '/' ? '/' : ici}` : '';
|
|
237
|
+
// 0.5.3 — l'image de partage : celle de la page, sinon celle de la marque ; absolue (https ou chemin du site).
|
|
238
|
+
const img = String(image || marque.image || '');
|
|
239
|
+
const imagePartage = /^https:\/\//.test(img) ? img : img.startsWith('/') && !img.startsWith('//') && base ? `${String(base).replace(/\/$/, '')}${img}` : '';
|
|
234
240
|
const tete = canonique ? `<meta name="description" content="${esc(description || slogan)}">
|
|
235
241
|
<link rel="canonical" href="${esc(canonique)}">${LANGUES.map((l) => `<link rel="alternate" hreflang="${l}" href="${esc(canonique)}?lang=${l}">`).join('')}<link rel="alternate" hreflang="x-default" href="${esc(canonique)}">
|
|
236
242
|
<meta property="og:type" content="website"><meta property="og:site_name" content="${nom}"><meta property="og:title" content="${esc(titre)} — ${nom}">
|
|
237
|
-
<meta property="og:description" content="${esc(description || slogan)}"><meta property="og:url" content="${esc(canonique)}"><meta property="og:locale" content="${L}">` : '';
|
|
243
|
+
<meta property="og:description" content="${esc(description || slogan)}"><meta property="og:url" content="${esc(canonique)}"><meta property="og:locale" content="${L}">${imagePartage ? `<meta property="og:image" content="${esc(imagePartage)}">` : ''}` : '';
|
|
238
244
|
const nav = [...liens.map((x) => [x.href, dansLangue(x.libelle, L)]), ['/tarifs', tr('nav.tarifs')], ['/aide', tr('nav.aide')], ['/apropos', tr('nav.apropos')]];
|
|
239
245
|
// 0.5 — le choix de langue en menu déroulant (`selecteurLangue: 'menu'`) : <details>, donc SANS script.
|
|
240
246
|
const choixLangue = LANGUES.length < 2 ? '' : selecteurLangue === 'menu'
|
|
@@ -247,7 +253,7 @@ export function creerWagonUi({ marque, couleurs = {}, langues = ['fr', 'en', 'ar
|
|
|
247
253
|
const boutonAppel = appel?.href ? `<a class="appel" href="${lien(appel.href)}">${esc(dansLangue(appel.libelle, L))}</a>` : '';
|
|
248
254
|
return `<!doctype html><html lang="${L}" dir="${dirDe(L)}"><head><meta charset="utf-8">
|
|
249
255
|
<meta name="viewport" content="width=device-width, initial-scale=1"><title>${esc(titre)} — ${nom}</title>
|
|
250
|
-
|
|
256
|
+
<link rel="icon" href="/favicon.svg" type="image/svg+xml">${tete}<style>${STYLE}</style>${themeCss() ? `<style id="theme-palette">${themeCss()}</style>` : ''}${feuilleApp}</head><body>
|
|
251
257
|
<header${classesTete ? ` class="${classesTete}"` : ''}><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>${boutonAppel}${choixLangue}</div></header>
|
|
252
258
|
<main>${contenu}</main>
|
|
253
259
|
<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>
|
|
@@ -506,7 +512,7 @@ ${anonyme ? `<h2>${esc(tr('usage.anonyme'))}</h2>
|
|
|
506
512
|
: `<a href="${lien(p.href)}"${p.href === actif ? ' class="on" aria-current="page"' : ''}>${esc(p.titre)}</a>`);
|
|
507
513
|
return `<!doctype html><html lang="${L}" dir="${dirDe(L)}"><head><meta charset="utf-8">
|
|
508
514
|
<meta name="viewport" content="width=device-width, initial-scale=1"><title>${esc(titre)} — ${esc(tr('doc.documentation'))} ${nom}</title>
|
|
509
|
-
${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>
|
|
515
|
+
${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>` : ''}${feuilleApp}</head><body>
|
|
510
516
|
<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>
|
|
511
517
|
<input type="checkbox" id="doc-menu" class="bascule">
|
|
512
518
|
<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>
|