@mostajs/wagon-ui 0.5.1 → 0.5.3

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 CHANGED
@@ -5,6 +5,18 @@
5
5
 
6
6
 
7
7
 
8
+ ## 0.5.3 — 2026-10-09
9
+
10
+ **Ajouté** (audit de travel, référencement) : chaque page porte l'icône de l'onglet
11
+ (`<link rel="icon" href="/favicon.svg">`, servie par le wagon 0.8) et, quand une image est connue, `og:image` —
12
+ celle de la page (`page({ image })`), sinon celle de la marque (`marque.image`) ; un chemin du site devient absolu,
13
+ toute autre adresse que https est ignorée. Essai T-WUI-24.
14
+
15
+ ## 0.5.2 — 2026-10-09
16
+
17
+ **Corrigé** (audit) : le menu des langues annonce aussi la langue COURANTE aux lecteurs d'écran
18
+ (`aria-label="Langue : Français"`). Essai T-WUI-23.
19
+
8
20
  ## 0.5.1 — 2026-10-09
9
21
 
10
22
  **Corrigé** (audit du 09/10/2026) : `page({ chemin })` — l'adresse canonique et les liens de langue désignent la page
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,7 @@ 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.5.3 : icône `/favicon.svg` dans chaque page ; `og:image` depuis `page({ image })` ou `marque.image` (chemin du site ou https).
29
30
  - 0.5.1 : `page({ chemin })` — canonique et liens de langue sur la page réelle.
30
31
  - 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
32
  - 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.5.1",
3
+ "version": "0.5.3",
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
@@ -222,7 +222,7 @@ export function creerWagonUi({ marque, couleurs = {}, langues = ['fr', 'en', 'ar
222
222
  * La page entière. `contenu` est du HTML déjà rendu (par une vue de ce module, ou par la charge
223
223
  * utile). `base` — l'adresse publique — ajoute les en-têtes que lisent les moteurs et les aperçus.
224
224
  */
225
- page({ actif = '/', chemin: cheminPage = '', titre = '', contenu = '', lang, connecte = false, base = '', description = '' }) {
225
+ page({ actif = '/', chemin: cheminPage = '', titre = '', contenu = '', lang, connecte = false, base = '', description = '', image = '' }) {
226
226
  const L = langueDe(lang); const tr = t(L);
227
227
  const nom = esc(marque.nom);
228
228
  const slogan = dansLangue(marque.slogan, L);
@@ -231,14 +231,17 @@ export function creerWagonUi({ marque, couleurs = {}, langues = ['fr', 'en', 'ar
231
231
  const ici = cheminPage || actif;
232
232
  const chemin = lien(ici);
233
233
  const canonique = base ? `${String(base).replace(/\/$/, '')}${ici === '/' ? '/' : ici}` : '';
234
+ // 0.5.3 — l'image de partage : celle de la page, sinon celle de la marque ; absolue (https ou chemin du site).
235
+ const img = String(image || marque.image || '');
236
+ const imagePartage = /^https:\/\//.test(img) ? img : img.startsWith('/') && !img.startsWith('//') && base ? `${String(base).replace(/\/$/, '')}${img}` : '';
234
237
  const tete = canonique ? `<meta name="description" content="${esc(description || slogan)}">
235
238
  <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
239
  <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}">` : '';
240
+ <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
241
  const nav = [...liens.map((x) => [x.href, dansLangue(x.libelle, L)]), ['/tarifs', tr('nav.tarifs')], ['/aide', tr('nav.aide')], ['/apropos', tr('nav.apropos')]];
239
242
  // 0.5 — le choix de langue en menu déroulant (`selecteurLangue: 'menu'`) : <details>, donc SANS script.
240
243
  const choixLangue = LANGUES.length < 2 ? '' : selecteurLangue === 'menu'
241
- ? `<details class="choix-langue"><summary aria-label="${esc(tr('layout.langue'))}">🌐 ${L.toUpperCase()} ▾</summary><ul>${LANGUES.map((l) => `<li><a href="${chemin}?lang=${l}" hreflang="${l}" lang="${l}" rel="alternate"${l === L ? ' aria-current="true"' : ''}>${esc(LIBELLES_LANGUES[l])}</a></li>`).join('')}</ul></details>`
244
+ ? `<details class="choix-langue"><summary aria-label="${esc(`${tr('layout.langue')}: ${LIBELLES_LANGUES[L]}`)}">🌐 ${L.toUpperCase()} ▾</summary><ul>${LANGUES.map((l) => `<li><a href="${chemin}?lang=${l}" hreflang="${l}" lang="${l}" rel="alternate"${l === L ? ' aria-current="true"' : ''}>${esc(LIBELLES_LANGUES[l])}</a></li>`).join('')}</ul></details>`
242
245
  : `<span class="langues">${LANGUES.map((l) => (l === L
243
246
  ? `<b lang="${l}">${esc(LIBELLES_LANGUES[l])}</b>`
244
247
  : `<a href="${chemin}?lang=${l}" hreflang="${l}" lang="${l}" rel="alternate">${esc(LIBELLES_LANGUES[l])}</a>`)).join(' · ')}</span>`;
@@ -247,7 +250,7 @@ export function creerWagonUi({ marque, couleurs = {}, langues = ['fr', 'en', 'ar
247
250
  const boutonAppel = appel?.href ? `<a class="appel" href="${lien(appel.href)}">${esc(dansLangue(appel.libelle, L))}</a>` : '';
248
251
  return `<!doctype html><html lang="${L}" dir="${dirDe(L)}"><head><meta charset="utf-8">
249
252
  <meta name="viewport" content="width=device-width, initial-scale=1"><title>${esc(titre)} — ${nom}</title>
250
- ${tete}<style>${STYLE}</style>${themeCss() ? `<style id="theme-palette">${themeCss()}</style>` : ''}</head><body>
253
+ <link rel="icon" href="/favicon.svg" type="image/svg+xml">${tete}<style>${STYLE}</style>${themeCss() ? `<style id="theme-palette">${themeCss()}</style>` : ''}</head><body>
251
254
  <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
255
  <main>${contenu}</main>
253
256
  <footer><b>${nom}</b>${slogan ? ` — ${esc(slogan)}` : ''} · ${esc(tr('layout.auteur'))} : <b>${esc(marque.auteur)}</b>${marque.courriel ? ` &lt;${esc(marque.courriel)}&gt;` : ''} · ${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>