npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@lystech/sections

v0.12.4

Published

Contenu de site editable par le client — lecture legere, edition chargee a la demande.

Readme

@lystech/sections

Le contenu d'un site, modifiable par son propriétaire — sans reconstruction ni déploiement.

Pourquoi un paquet à part de @lystech/core

core exige React 18 et embarque antd, Mantine et React Query : 2,4 Mo de JavaScript et 1,2 Mo de CSS. Une clinique qui veut changer trois textes n'a aucune raison de charger un magasin, un panier et PayPal — et beaucoup de sites clients sont en React 19.

Ce paquet pèse 11 Ko en lecture (4,4 Ko compressé) et ne dépend que de React. core le réexporte, pour qu'une boutique qui l'a déjà n'ait rien de plus à installer.

La plateforme fixe l'alphabet, pas les mots

Le serveur ne connaît aucun type de bloc. « plat-du-jour », « praticien », « tarifs » appartiennent au site, qui les déclare. Ce que la plateforme connaît, ce sont les types de champs — text, richText, image, number, boolean, url, date, choice, list — vrais dans tous les métiers, et suffisants pour fabriquer un formulaire pour un bloc jamais vu.

Usage

import { SectionsProvider, useSection } from "@lystech/sections";

<SectionsProvider site="armoise" page="massage" langue={i18n.language}>
  <MaPage />
</SectionsProvider>;

// Dans un composant déjà écrit :
const { contenu: tarifs } = useSection("tarifs-massotherapie", {
  defaut: SITE.tarifs.massotherapie, // ce que le site affichait déjà
});

Le repli n'est pas une option

defaut porte les valeurs actuelles du site. Tant que rien n'est publié — ou si l'API ne répond pas — ce sont elles qui s'affichent. Brancher un site existant ne peut donc rien casser : au pire il continue d'afficher ce qu'il affichait hier.

Publié ≠ brouillon

Le site public ne lit QUE le contenu publié. Un brouillon enregistré ne change rien pour les visiteurs tant que personne n'a cliqué « Publier ».

Champs traduits

Un champ déclaré localized: true stocke { fr: …, en: … } ; le paquet rend la langue courante, avec repli sur une autre langue plutôt que sur du vide — une version anglaise oubliée vaut mieux affichée en français qu'absente.

L'éditeur en place

La personne qui édite ouvre SON site avec un jeton dans l'adresse. Le paquet le détecte, le retire aussitôt de la barre d'adresse, et charge l'interface d'édition — ~33 Ko compressés qu'aucun visiteur ne télécharge jamais, parce que le import() qui y mène n'est atteint que s'il y a un jeton.

Elle voit alors son site, ses polices, sa mise en page, avec chaque bloc modifiable encadré. Ce qu'elle tape apparaît dedans à mesure qu'elle le tape.

Marquer les blocs dans la page

L'éditeur doit savoir quel rectangle de la page correspond à quel bloc. Aucun moyen fiable de le deviner : le site le dit.

const { contenu, ancrage } = useSection("tarifs", { defaut });
return <section {...ancrage}>…</section>;

Hors édition, c'est un attribut data- inerte : aucun poids, aucun effet.

Corriger un prix sur place : champ

Un texte traduit se retrouve tout seul dans la page. Un prix, non : « 85 $ » ne dit pas que ce 85 est le prix de la séance. Le site pose champ(…) sur l'élément qui ne contient QUE la valeur :

const { contenu, champ } = useSection("tarifs", { defaut });
return <p><span {...champ("seance")}>{contenu.seance}</span> $</p>;

En mode « modifier », un clic sur la valeur ouvre une saisie posée dessus : Entrée enregistre le brouillon, Échap annule, et la page montre aussitôt la nouvelle valeur. Un champ qui ne se saisit pas sur une ligne (image, liste, texte riche) ouvre plutôt le panneau de son bloc ; une image ouvre directement la fenêtre de choix d'image (voir 0.10). Si le format vient d'une traduction (« {{amount}} $ »), découpez-le autour de la valeur au lieu de rendre la phrase d'un bloc : seul le nombre doit porter champ.

Trois états, jamais confondus

| | Qui le voit | |---|---| | Aperçu | la personne qui édite, dans son navigateur | | Brouillon | elle seule, enregistré sur le serveur | | Publié | les visiteurs |

Le jeton

Délivré par POST /site-sections/edit-token depuis la console Lystech. Il vaut quelques heures, pour UNE organisation, et il est plafonné à l'éditeur même si la personne est administratrice : un lien qui fuite ne peut pas ouvrir la console. Le droit est relu dans les rôles à chaque requête, donc retirer un accès ferme les jetons déjà émis sur-le-champ.

Des gestes propres au site : appelEdition

Un site peut offrir, à la personne connectée, des gestes que l'éditeur générique ne connaît pas — retirer un résultat, masquer un évènement. Le contexte fournit un appel qui porte le jeton, sans jamais en livrer la valeur :

const ctx = useSectionsContext();
if (ctx?.modeEdition) {
  const r = await ctx.appelEdition("/results/site/droits");
}

Seuls les chemins de l'API sont acceptés. Un 401 fait sortir du mode édition (jeton expiré) ; un 403 revient tel quel. C'est la ROUTE qui décide de ce que le jeton ouvre : plafonné à l'éditeur partout, il n'obtient plus qu'en de rares routes qui le disent explicitement (ex. /results/site, admins seulement).

Des boutons qui restent des boutons

En mode « modifier », cliquer un texte le corrige au lieu de l'actionner — mais seulement un texte qui a un bloc déclaré où s'enregistrer. Pour qu'une zone reste actionnable quoi qu'il arrive (une suppression et sa confirmation), posez CLASSE_HORS_EDITION dessus — et sur rootClassName des fenêtres surgissantes, qui se rendent hors de l'arbre :

<section className={CLASSE_HORS_EDITION}>…</section>
<Popconfirm rootClassName={CLASSE_HORS_EDITION} …>

Un site qui signe déjà

<SignatureLystech mention={null} /> ne garde que le bouton « Connexion » — pour un pied de page qui porte déjà sa propre mention de Lystech.

La boutique dans l'éditeur (0.7)

Une personne qui administre la boutique gère, depuis son propre site, ses produits, leurs photos et ses collections — au bureau comme au téléphone. Maquette : docs/maquette-editeur.html.

Ce que voit la personne qui édite

  • un bouton Boutique dans la barre d'édition : la liste de ses produits (recherche, filtres En vente / Masqués / Épuisés / Sans collection) et ses collections ;
  • un crayon sur chaque carte produit de la page, qui ouvre sa fiche ;
  • une tuile + Ajouter juste après la dernière carte de la grille ;
  • la fiche : photos (glisser-déposer, ou « Prendre une photo » qui ouvre la caméra du téléphone), nom, prix, prix avant rabais, stock en trois mots (pièce unique / quantité / sans limite), visible ou masqué, collections, description. Panneau latéral au bureau, feuille glissante au téléphone ;
  • un bouton Mobile : la page courante dans un cadre de 390 px, brouillons de textes compris.

Deux rythmes, dits à l'écran. Les textes passent par brouillon puis « Publier ». Le catalogue, lui, est en ligne dès « Enregistrer » : un produit pas prêt reste « Masqué ».

Rien ne disparaît d'un clic. Masquer est réversible. « Supprimer pour toujours » n'existe que sur un produit déjà masqué, après une confirmation explicite, et le serveur refuse un produit déjà vendu. Retirer une photo la détache : elle reste dans la librairie d'images.

Ce que le site doit faire : deux lignes

1. Marquer ses cartes produit — sans ce marquage, pas de crayon ni de tuile (le module reste accessible par la barre) :

import { marqueProduit } from "@lystech/sections";

<div className="carte" {...marqueProduit(produit.UID)}>…</div>

L'attribut posé est data-lystech-produit="<UID>", inerte hors édition. Crayons et tuile vivent dans le Shadow DOM de l'éditeur, posés par-dessus : rien n'est inséré dans l'arbre React du site. La tuile se place d'après les rectangles réels des cartes (grille CSS, flexbox, 2 ou 4 colonnes).

2. Relire ses produits quand l'éditeur en change un. Après chaque enregistrement, l'éditeur émet lystech:catalogue-modifie sur window (détail : { quoi, UID, action }). Avec React Query :

import { useCatalogueModifie } from "@lystech/sections";

const qc = useQueryClient();
useCatalogueModifie(() => qc.invalidateQueries());

L'aperçu mobile relaie l'événement dans son cadre : le site s'y met à jour de la même façon.

Un site peut aussi ouvrir le module lui-même — useSectionsContext() ?.ouvrirBoutique(UID | "nouveau") rend faux si la personne n'y a pas droit.

Qui y a droit

Routes /site-sections/boutique/* du serveur. Le jeton d'édition reste plafonné à l'éditeur partout ailleurs ; ici, le serveur relit à chaque requête le rôle réel de la personne et exige administrateur (un éditeur reçoit 403 : il garde l'édition des textes, le bouton Boutique ne s'affiche pas). Organisation = celle du jeton, jamais un en-tête. Aucune route de commandes, clients ou paiements.

Le module (≈33 Ko compressés), l'aperçu mobile et la fenêtre d'images sont des fichiers à part, chargés au premier clic.

L'aperçu mobile

Un cadre nommé lystech-apercu charge la même page ; le fournisseur qui s'y trouve le reconnaît, ne monte pas l'éditeur et reçoit les brouillons par postMessage (même origine seulement). useSectionsContext()?.modeApercu permet à un site de couper ce qui n'a pas de sens dans un aperçu.

Lecture seule (0.10). L'aperçu se regarde, il ne s'utilise pas : un calque transparent couvre le cadre et ne relaie que le défilement (molette, doigt, clavier) ; le cadre est hors du parcours clavier ; une garde posée dans la page annule clics, envois de formulaire et touches. Aucun bouton, lien, panier ni paiement ne répond dans l'aperçu, et la barre le dit : « Aperçu — lecture seule ».

La grille, les collections, les brouillons (0.9)

Une carte « + » dans la grille

En mode édition, pour une personne qui administre la boutique, une carte « + Ajouter un produit » apparaît au bout de la grille de produits, à la taille exacte d'une carte. Rien à poser côté site : l'éditeur trouve la grille à partir des cartes marquées par marqueProduit. Un visiteur, ou un éditeur de textes sans droit sur le catalogue, n'a jamais cette carte dans sa page.

Au clic : « Ajouter un produit existant » (recherche, vignettes, plusieurs à la fois, ceux déjà présents marqués) ou « Créer un nouveau produit », rangé d'office dans la collection affichée. Hors d'une collection : la création seule.

Marquer la collection affichée : marqueCollection

import { marqueCollection } from "@lystech/sections";

<div className="collection" {...marqueCollection(c.UID, c.UID === choisie?.UID)}>…</div>

Le deuxième argument dit « c'est elle que la grille montre ». L'éditeur y pose un crayon (nom, description, qui peut commander, ses produits) et un « + » (y ajouter des produits), et la carte « + » de la grille la garnit. La barre de l'éditeur gagne une entrée Collections.

Des brouillons, publiés quand on veut

Un nouveau produit est rangé en brouillon — jamais visible, jamais vendable, gardé sur le serveur (il survit au navigateur fermé et suit la personne d'un appareil à l'autre). « Enregistrer et suivant » vide la fiche pour le produit d'après sans fermer le panneau ; les photos partent en arrière-plan. Une nouvelle collection naît aussi en brouillon : on la nomme, on la garnit (produits existants et nouveaux), sans qu'elle apparaisse.

Publier (N), dans la barre, liste les brouillons (et les textes modifiés) : on décoche ce qui n'est pas prêt ; ce qui ne peut pas partir (nom de moins de 3 lettres, pas de prix, pas de photo) le dit sur sa ligne. Chaque élément est publié pour lui-même : un échec n'annule pas les autres, un double clic ne crée pas de doublon. Pour une collection, on choisit à ce moment-là comment l'ouvrir. Corriger un produit déjà en ligne reste, lui, immédiat.

Le crayon flottant : BoutonEdition et connexionPropre

Un site qui a sa propre connexion (une boutique @lystech/core) n'a pas besoin de la fenêtre « Connexion » du pied de page :

<SectionsProvider site="bijouxunyk" page={page} connexionPropre session={{ cle, entetes }}>
  …
  <BoutonEdition connecte={isLoggedIn} />
</SectionsProvider>

connexionPropre retire « Connexion » de SignatureLystech (défaut : non, rien ne change pour les autres sites). BoutonEdition ne rend rien tant que personne n'est connecté — ni code téléchargé, ni requête. Connecté, une question au serveur (« puis-je éditer CE site ? ») ; si oui, un crayon rond de 56 px en bas de l'écran (coin="gauche" par défaut, à l'opposé du panier). Au clic, le serveur délivre un jeton d'édition à la session déjà ouverte, pour l'organisation de ce site seulement, et l'éditeur se monte sur la page, sans recharger. En mode édition, le crayon disparaît.

Catalogue à grande échelle, propriétés, librairie d'images (0.10)

Des listes paginées par le serveur

La liste des produits, le choix de « produits existants » pour une collection et les produits d'une collection sont la même liste : le serveur cherche (nom et description), filtre, trie et découpe en pages de 40 ; la liste ne dessine que ce qui se voit et ne lit que les pages qu'elle va montrer. Pastilles d'état (En vente, Masqués, Épuisés, Sans collection, Brouillons) avec leurs nombres ; « Filtres » : collection, stock (en stock, épuisé, pièce unique, quantité, sans limite), prix min-max, date d'ajout, caractéristiques, et le tri. Les filtres posés se relisent sous la barre et s'ôtent d'un clic ; ils sont mémorisés pendant la session, par liste. Les collections ont les mêmes outils (ouverture, vide ou garnie, date, tri).

Sélectionner coche plusieurs produits — « Tout cocher » prend tous ceux qui correspondent aux filtres (2 000 au plus) — puis Appliquer : ajouter ou retirer des caractéristiques, ajouter à une collection ou en retirer.

Serveur requis : editeur-catalogue-010-v1 ou plus (routes /site-sections/boutique/recherche/*, /proprietes, /valeurs, /librairie).

Propriétés et caractéristiques

Le modèle de la console : une propriété (Couleur) a des valeurs (Rouge, Bleu) ; un produit en porte plusieurs si besoin. L'onglet Propriétés les crée, les renomme, gère leurs valeurs et montre combien de produits portent chacune (« voir les produits » ouvre la liste filtrée). Supprimer dit toujours combien de produits seront touchés. Dans la fiche d'un produit, le bloc Caractéristiques propose les valeurs existantes au fil de la frappe et crée ce qui manque (« Créer « Rouge » »), sans doublon. Les brouillons gardent leurs caractéristiques jusqu'à la publication.

Choisir une image : téléverser ou librairie

Une seule fenêtre pour toutes les images : un champ image d'une section (dans son panneau, ou sur place si le site pose champ("photo") sur l'image), les photos d'un produit, l'image d'une collection. Deux onglets : Téléverser (glisser-déposer, fichiers, caméra du téléphone ; le serveur redresse, ramène à 2000 px en WebP et retire la position GPS) et Librairie (les images de l'organisation, en vignettes de 256 px, recherche et « vedettes »). Une image de la librairie est réutilisée, jamais copiée. La librairie est ouverte aux éditeurs (images des sections) ; l'attacher à un produit ou à une collection reste réservé aux administrateurs.

Chaque texte se modifie là où il est, brouillon puis « Publier » (0.11)

Pourquoi un clic « ne faisait rien » (≤ 0.10)

Un clic sur un texte ouvrait bien la saisie… puis la perte de focus de l'élément précédent (le <main> d'un site, focalisable pour le lien « Aller au contenu ») la refermait aussitôt. Seuls les textes hors de <main> se modifiaient. Les doublons (« Boutique » au menu, au pied de page et dans le fil d'Ariane) ouvraient une question technique ; un texte composé (à côté d'un lien) ou avec une valeur calculée ne se cliquait pas.

Les marques invisibles

En mode édition seulement, chaque texte rendu par t() porte son identité en caractères invisibles (U+2060 à U+2064, voir src/marques.ts) : l'éditeur sait exactement de quelle clé vient chaque morceau de page, quelle que soit sa valeur actuelle, même publiée. Les marques voyagent avec le texte (props, variables) : un texte partagé est retrouvé partout où le site l'affiche. Elles sont retirées des attributs (href, alt, title…) et du titre de l'onglet. Un visiteur, le HTML prérendu, n'en voient jamais.

Exigence inchangée : bindI18nStore: "added" dans la configuration react-i18next.

  • Un texte seul dans son élément se modifie dans la page (Entrée, Échap).
  • Un texte composé ou à valeur automatique ({{plan}}) s'ouvre dans une bulle posée dessus ; la page suit à chaque lettre.
  • Un clic sur un lien ou un bouton modifie son texte (le mode « Naviguer » de la barre rend la main aux liens).

« Utilisé à N endroits »

Pour un texte, la liste de ses endroits : cette page (en direct) et les autres pages (chargées en coulisse dans une fenêtre invisible, gardé 6 h). « Montrer » fait défiler et allume l'endroit ; « Aller voir » change de page, le texte en cours d'écriture déjà visible. Deux props, facultatives :

<SectionsProvider
  /* L'adresse de chaque page de l'éditeur (défaut : « / » + nom ; null = pas d'adresse fixe). */
  adresses={{ accueil: "/", connexion: "/login", fiche: null }}
  /* Changer de page sans recharger (défaut : pushState + popstate). */
  naviguer={navigate}
  …
>

Brouillon, puis « Publier » — d'un coup

Tout ce qu'on écrit (textes, coordonnées partagées, images des sections) va dans un brouillon sur le serveur, visible seulement en mode édition, sur tous les appareils. « Publier (N) » montre la liste des changements (avant → après) et les brouillons de la boutique, puis met tout en ligne en une écriture (POST /site-sections/publish-all, une seule reconstruction du site prérendu). « Annuler » remet la version en ligne (/revert-all). Un serveur plus ancien est servi section par section.

Le volet partage la largeur

Dès 768 px, le volet ne recouvre plus le site : la page se resserre à côté et ses règles de largeur suivent (les @media sont réévaluées contre la largeur restante), les éléments fixes (en-tête, bouton du panier) restent dans la zone du site. Poignée redimensionnable (souris, doigt, clavier ; double-clic = largeur par défaut), largeur retenue sur l'appareil. Le site peut aussi lire html.lystech-volet-ouvert et --lystech-volet. Limite : un site qui mesure la fenêtre en JavaScript garde la vraie largeur.

Naviguer sans perdre le fil, page ↔ volet (0.11.2)

  • Changer de page ne ferme plus le volet : choix de la page en tête de « Contenus », liens du site, retour arrière. Contenu ouvert, recherche, défilement et saisie en cours sont gardés ; la liste suit la nouvelle page sans être remontée. L'état D'INTERFACE du volet (jamais de texte saisi) est gardé pour l'onglet (sessionStorage) ; une saisie pas encore enregistrée part en brouillon (invisible des visiteurs) si l'onglet se recharge.
  • La sélection survit aux changements de page et aux mises à jour : si l'élément est sur la page, il reste allumé, en vue et focalisé dans le volet ; sinon elle attend qu'il réapparaisse.
  • Surligner (icône de la barre et du volet) : un contour discret sur tout ce qui se modifie ; survoler une entrée du volet allume l'élément dans la page, et inversement.
  • Montrer (icône cible sur chaque entrée et chaque champ) : la page défile jusqu'à l'élément et l'allume — sur sa page s'il le faut. Au téléphone, le volet se replie en bas le temps de voir.
  • Clic dans la page : volet fermé, il MODIFIE sur place (la barre gagne « Voir dans le panneau ») ; volet ouvert à côté du site, il SÉLECTIONNE l'entrée correspondante.
  • Carte produit ou collection : le crayon ouvre une petite fenêtre sur la carte (nom, prix, en vente / masqué ; nom, description, accès), en ligne dès l'enregistrement ; la fiche complète reste à un clic.

Point d'extension : les éléments repérables (src/editeur/selection.ts)

Tout ce qui se montre, se surligne ou se sélectionne passe par un registre. Un module (les zones de sections, par exemple) y inscrit SES éléments ; le surlignage, « Montrer » et le clic-sélection les traitent comme les textes.

import { enregistrerFournisseur, avertir, EVENEMENT_SELECTION, type DetailSelection } from "./selection";

// 1. Inscrire ses éléments (au montage) ; la fonction rendue les retire.
const retirer = enregistrerFournisseur("zones", () =>
  zones.map((z) => ({
    id: "zone:" + z.id,               // stable tant que l'élément existe
    type: "section",                  // libre : "texte" | "champ" | "produit" | "collection" | …
    element: z.el,                    // l'élément dans la page
    libelle: z.nom,                   // affiché en TEXTE (jamais en HTML)
    cible: { blockType: z.blockType },// ce que le volet ouvre
    zone: () => z.el.getBoundingClientRect(), // facultatif : la zone exacte
  })),
);
// 2. Quand ses éléments changent : avertir() (regroupé, une image au plus).
// 3. Réagir à une sélection (clic dans la page volet ouvert, ou « Montrer ») :
window.addEventListener(EVENEMENT_SELECTION, (e) => {
  const { reperable, origine } = (e as CustomEvent<DetailSelection>).detail; // origine : "page" | "volet" | "navigation"
  if (reperable.type === "section") ouvrirLaZone(reperable.cible.blockType);
});

Le registre ne balaie jamais la page : chaque fournisseur connaît ses éléments. Positions relevées une fois par changement (registre ou taille de la page), puis le défilement ne déplace qu'un calque.

Sections par audience (0.8)

Une section peut viser une audience : tout le monde, clients connectés, non-membres, membres (n'importe quel plan actif) ou des plans précis. Le choix se fait dans l'éditeur (« Visible pour », en tête du panneau d'un bloc) et s'applique tout de suite.

Le filtrage est fait par le serveur, jamais par la page :

  • la route publique (/site-sections/public/...) — donc aussi le HTML prérendu, fabriqué en la lisant — ne contient QUE les sections « tout le monde » ;
  • le fournisseur lit d'abord /site-sections/membre/..., où le serveur reconnaît le visiteur (session, adhésion) et ne livre que ce à quoi il a droit. Une section non permise arrive en coquille vide (hidden) : le site sait qu'elle existe et n'affiche PAS son repli à la place.
const { contenu, visible } = useSection("offre-membres", { defaut });
if (!visible) return null;

Règle : un contenu réservé ne va jamais dans le defaut du code — le code d'un site est public. Il s'écrit dans l'éditeur.

Session : par défaut le cookie de l'API est envoyé (credentials: "include"). Un site branché sur @lystech/core passe la sienne :

<SectionsProvider site="unyk" page="accueil" session={useSectionsSession()}>

Pour un bloc codé en dur (un bouton, un mot d'accueil), <Audience> décide de l'affichage d'après le visiteur reconnu par le serveur — jamais pour un contenu sensible :

<Audience show="nonMembers"><BoutonDevenirMembre /></Audience>
<Audience show="plans" plans={[uidPlanOr]} fallback={<Teaser />}>…</Audience>

Sites prérendus

Un site généré au build affiche les replis du code dans son HTML, puis bascule sur le contenu publié une fois le JavaScript chargé — l'ancien prix clignote vers le nouveau, et les moteurs de recherche n'indexent que l'ancien.

Passez le contenu au premier rendu pour supprimer les deux problèmes :

<SectionsProvider site="armoise" page="massage" sectionsInitiales={dejaLu}>

où dejaLu vient d'un appel, dans le script de prérendu, à GET /site-sections/public/<site>/<page>.

Le constructeur de pages (0.12)

Jusqu'ici, on modifiait ce que le site avait dessiné. On peut maintenant composer : poser des sections, choisir leur type, leur style et leurs conditions d'affichage, et y mettre des valeurs tirées de la boutique — sur la page, au bureau comme au téléphone, toujours en brouillon puis « Publier (N) ». Conception : CONCEPTION-CONSTRUCTEUR.md.

Le site dit où l'on compose : <ZoneSections>

import { ZoneSections } from "@lystech/sections";

<ZoneSections nom="accueil-milieu" libelle="Milieu de l'accueil" />

Une zone vide n'affiche rien pour un visiteur ; dans l'éditeur, elle offre « + Ajouter une section ». Son contenu est UNE section du serveur (blockType = "zone:<nom>") : le même brouillon, le même « Publier », le même « Annuler » que tout le reste, et aucune déclaration à faire.

Dans l'éditeur

  • un clic sélectionne une section (ses liens ne partent pas) ; sa barre : glisser (poignée, ou flèches haut/bas au clavier), monter, descendre, modifier, style, affichage, dupliquer, masquer, supprimer (confirmé) ;
  • un « + » au-dessus et au-dessous ouvre le bac à outils : texte, image et texte, bouton, galerie, produits, compte à rebours, chiffre clé, bannière, FAQ, espace — plus les composants du site et « Mes modèles » ;
  • le panneau d'une section a trois onglets : Contenu (avec « Insérer une valeur »), Style (avancé : alignement, largeur, espace, couleurs de la palette du site ou personnalisée, classes de la liste blanche) et Affichage (masquer ; seulement si connecté, membre, avant/après une date, compte à rebours en cours/fini, collection ouverte/fermée, téléphone/ ordinateur — toutes les règles ou au moins une) ;
  • la liste des sections d'une zone (calques) se réordonne au doigt et au clavier ; « Garder comme modèle » réutilise une section ou une zone entière ;
  • Voir comme (œil de la barre) : visiteur, client connecté, membre, et une date simulée.

Le thème : que les sections ressemblent au site

const THEME: ThemeSite = {
  couleurs: [{ nom: "rose", libelle: "Rose", valeur: "var(--rose)" }],
  classes: [{ nom: "encadre", libelle: "Encadré", classe: "box box--ombre" }], // liste blanche
  roles: { titre: "h2", bouton: "btn btn--primaire", texte: "prose" },
  adresseProduit: (p) => `/produit/${p.UID}`,
  composants: [{ type: "produits", libelle: "Produits", rendu: MesProduits }], // remplace le préfait
};
<SectionsProvider theme={THEME} …>

Construit UNE fois, hors du composant. Les composants préfaits ne posent aucun style visuel : des classes lz-* de spécificité nulle (mise en page seulement) et les classes du site par rôle. Variables facultatives : --lystech-zone-largeur, --lystech-zone-gouttiere, --lystech-zone-rayon, --lystech-zone-voile.

Un composant du site reçoit { contenu, texte, valeur, role, langue, edition, theme } ; il peut déclarer ses champs (l'alphabet commun) et une icone (tracé SVG 24 × 24). Créer un composant, pour la personne qui édite, c'est composer des sections existantes et les garder comme modèle — jamais du code.

Valeurs de données

{{commandes:2024-01-01}} dans un texte, ou le composant « Chiffre clé ». Registre du serveur, en liste blanche, publiques et agrégées : commandes, articles vendus, pièces uniques vendues, clients, produits en vente, collections ouvertes, membres, fermeture / jours avant la fermeture / collection ouverte, compte à rebours. La route publique ne calcule que les clés mentionnées par le contenu EN LIGNE. Serveur requis : constructeur-012-v1 ou plus.

Sécurité et poids

Aucun HTML, CSS ni JavaScript libre : textes rendus comme texte, couleurs = palette ou hex validé, classes = liste blanche du site, liens = https, http, mailto, tel ou chemin du site, images = stockage de la plateforme ou fichier du site. Les sections réservées aux connectés ou aux membres ne quittent le serveur que vers eux (route publique et HTML prérendu : jamais). Un site sans zone ne télécharge rien de plus ; galerie, produits et compte à rebours se chargent à la demande, leur place réservée.