@lystech/sections 0.1.0 → 0.2.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/README.md +109 -55
- package/dist/SectionsProvider.d.ts +56 -0
- package/dist/SignatureLystech.d.ts +9 -0
- package/dist/editeur/Editeur.d.ts +6 -0
- package/dist/editeur/api.d.ts +39 -0
- package/dist/editeur/champs.d.ts +9 -0
- package/dist/editeur/index.d.ts +8 -0
- package/dist/editeur/pont.d.ts +19 -0
- package/dist/editeur/styles.d.ts +1 -0
- package/dist/editeur.js +719 -0
- package/dist/index.d.ts +16 -108
- package/dist/index.js +230 -75
- package/dist/types.d.ts +41 -0
- package/dist/useSection.d.ts +50 -0
- package/package.json +8 -4
- package/dist/index.umd.cjs +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,108 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
export
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
required?: boolean;
|
|
18
|
-
help?: string;
|
|
19
|
-
options?: string[];
|
|
20
|
-
/** Sous-champs d'une `list`. */
|
|
21
|
-
of?: ChampDeclare[];
|
|
22
|
-
/**
|
|
23
|
-
* Champ TRADUIT. La valeur stockée devient { fr: …, en: … } et le paquet
|
|
24
|
-
* rend celle de la langue courante.
|
|
25
|
-
*
|
|
26
|
-
* Le serveur ne connaît pas cette clé — il stocke la déclaration telle
|
|
27
|
-
* quelle. C'est voulu : ajouter une langue ne doit pas demander une
|
|
28
|
-
* migration côté plateforme.
|
|
29
|
-
*/
|
|
30
|
-
localized?: boolean;
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
declare type Contexte = {
|
|
34
|
-
/** Sous-domaine de l'organisation dans Lystech. */
|
|
35
|
-
site: string;
|
|
36
|
-
/** Langue courante — décide quelle version d'un champ traduit est rendue. */
|
|
37
|
-
langue: string;
|
|
38
|
-
sections: SectionPubliee[];
|
|
39
|
-
etat: Etat;
|
|
40
|
-
recharger: () => void;
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
declare type Etat = "chargement" | "pret" | "erreur";
|
|
44
|
-
|
|
45
|
-
export declare type ResultatSection<T> = {
|
|
46
|
-
/** Le contenu à afficher : publié si présent, défaut sinon. */
|
|
47
|
-
contenu: T;
|
|
48
|
-
/** Vrai quand le contenu vient du serveur, faux quand c'est le défaut. */
|
|
49
|
-
publie: boolean;
|
|
50
|
-
/** L'identifiant de la section — nécessaire à l'édition. */
|
|
51
|
-
UID: string | null;
|
|
52
|
-
chargement: boolean;
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
/** Une section telle qu'elle revient du serveur. */
|
|
56
|
-
export declare type SectionPubliee = {
|
|
57
|
-
UID: string;
|
|
58
|
-
page: string;
|
|
59
|
-
blockType: string;
|
|
60
|
-
position: number;
|
|
61
|
-
data: Record<string, unknown> | null;
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
export declare function SectionsProvider({ site, page, langue, apiBaseUrl, children, }: {
|
|
65
|
-
/** Sous-domaine de l'organisation (ex. « armoise »). */
|
|
66
|
-
site: string;
|
|
67
|
-
/** La page dont on veut les sections (ex. « accueil »). */
|
|
68
|
-
page: string;
|
|
69
|
-
langue?: string;
|
|
70
|
-
apiBaseUrl?: string;
|
|
71
|
-
children: ReactNode;
|
|
72
|
-
}): JSX.Element;
|
|
73
|
-
|
|
74
|
-
export declare type TypeDeChamp = "text" | "richText" | "image" | "number" | "boolean" | "url" | "date" | "choice" | "list";
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Le contenu d'un type de bloc sur la page courante.
|
|
78
|
-
*
|
|
79
|
-
* Le cas visé est celui d'un site DÉJÀ DESSINÉ : un bloc par type et par page,
|
|
80
|
-
* dont on veut rendre les mots et les images modifiables — pas un constructeur
|
|
81
|
-
* de pages. Pour des blocs répétables, voir `useSectionList`.
|
|
82
|
-
*/
|
|
83
|
-
export declare function useSection<T extends object>(blockType: string, options: {
|
|
84
|
-
defaut: T;
|
|
85
|
-
}): ResultatSection<T>;
|
|
86
|
-
|
|
87
|
-
/** Plusieurs blocs du même type, dans l'ordre choisi par le client. */
|
|
88
|
-
export declare function useSectionList<T extends object>(blockType: string, options?: {
|
|
89
|
-
defaut?: T[];
|
|
90
|
-
}): {
|
|
91
|
-
items: Array<T & {
|
|
92
|
-
__uid: string;
|
|
93
|
-
}>;
|
|
94
|
-
publie: boolean;
|
|
95
|
-
chargement: boolean;
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Le contexte, ou `null` HORS d'un fournisseur.
|
|
100
|
-
*
|
|
101
|
-
* On ne lève pas : un composant branché sur les sections doit rester
|
|
102
|
-
* utilisable dans une page qui n'en a pas (une maquette, un test, une page
|
|
103
|
-
* d'erreur). Il affichera son repli, ce qui est exactement le bon
|
|
104
|
-
* comportement.
|
|
105
|
-
*/
|
|
106
|
-
export declare function useSectionsContext(): Contexte | null;
|
|
107
|
-
|
|
108
|
-
export { }
|
|
1
|
+
/**
|
|
2
|
+
* @lystech/sections — le contenu d'un site, modifiable par son propriétaire.
|
|
3
|
+
*
|
|
4
|
+
* CE QUI EST EXPORTÉ ICI EST CE QU'UN VISITEUR TÉLÉCHARGE : la lecture, et
|
|
5
|
+
* rien d'autre. L'interface d'édition vivra dans un point d'entrée séparé,
|
|
6
|
+
* chargé à la demande. Un visiteur ne doit pas payer le poids d'un éditeur
|
|
7
|
+
* qu'il ne verra jamais.
|
|
8
|
+
*
|
|
9
|
+
* Le paquet ne dépend PAS de @lystech/core : une clinique qui veut modifier
|
|
10
|
+
* trois textes n'a aucune raison de charger un magasin, un panier et PayPal.
|
|
11
|
+
*/
|
|
12
|
+
export { SectionsProvider, useSectionsContext } from './SectionsProvider';
|
|
13
|
+
export { SignatureLystech } from './SignatureLystech';
|
|
14
|
+
export { useSection, useSectionList } from './useSection';
|
|
15
|
+
export type { BlocDeclare, ChampDeclare, SectionPubliee, TypeDeChamp, } from './types';
|
|
16
|
+
export type { AncrageEdition, ResultatSection } from './useSection';
|
package/dist/index.js
CHANGED
|
@@ -1,107 +1,262 @@
|
|
|
1
|
-
import { jsx as j } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
3
|
-
const
|
|
4
|
-
function
|
|
1
|
+
import { jsx as h, jsxs as j, Fragment as F } from "react/jsx-runtime";
|
|
2
|
+
import { useState as m, useRef as _, useEffect as A, useCallback as I, useMemo as C, useContext as M, createContext as T } from "react";
|
|
3
|
+
const d = "lystech-edit", U = T(null), $ = "https://api.lystech.ca/api";
|
|
4
|
+
function J() {
|
|
5
|
+
if (typeof window > "u") return null;
|
|
6
|
+
const e = (t) => {
|
|
7
|
+
try {
|
|
8
|
+
window.sessionStorage.setItem(d, t);
|
|
9
|
+
} catch {
|
|
10
|
+
}
|
|
11
|
+
return t;
|
|
12
|
+
};
|
|
13
|
+
try {
|
|
14
|
+
const t = window.location.hash.replace(/^#/, "");
|
|
15
|
+
if (t.includes(d + "=")) {
|
|
16
|
+
const o = new URLSearchParams(t), c = o.get(d);
|
|
17
|
+
if (c) {
|
|
18
|
+
o.delete(d);
|
|
19
|
+
const a = o.toString();
|
|
20
|
+
return window.history.replaceState(
|
|
21
|
+
{},
|
|
22
|
+
"",
|
|
23
|
+
window.location.pathname + window.location.search + (a ? "#" + a : "")
|
|
24
|
+
), e(c);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
const n = new URL(window.location.href), r = n.searchParams.get(d);
|
|
28
|
+
return r ? (n.searchParams.delete(d), window.history.replaceState({}, "", n.toString()), e(r)) : window.sessionStorage.getItem(d);
|
|
29
|
+
} catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function q({
|
|
5
34
|
site: e,
|
|
6
|
-
page:
|
|
7
|
-
langue:
|
|
8
|
-
apiBaseUrl:
|
|
9
|
-
|
|
35
|
+
page: t,
|
|
36
|
+
langue: n = "fr",
|
|
37
|
+
apiBaseUrl: r = $,
|
|
38
|
+
sectionsInitiales: o,
|
|
39
|
+
children: c
|
|
10
40
|
}) {
|
|
11
|
-
const [
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
41
|
+
const [a, p] = m(
|
|
42
|
+
o ?? []
|
|
43
|
+
), [u, y] = m(
|
|
44
|
+
o?.length ? "pret" : "chargement"
|
|
45
|
+
), [g, b] = m(0), [P, z] = m({}), [v, O] = m(!1), D = _(null);
|
|
46
|
+
A(() => {
|
|
47
|
+
if (!e || !t) return;
|
|
48
|
+
D.current?.abort();
|
|
49
|
+
const i = new AbortController();
|
|
50
|
+
D.current = i, y("chargement");
|
|
51
|
+
const f = r + "/site-sections/public/" + encodeURIComponent(e) + "/" + encodeURIComponent(t);
|
|
52
|
+
return fetch(f, { signal: i.signal }).then(
|
|
53
|
+
(s) => s.ok ? s.json() : Promise.reject(new Error(String(s.status)))
|
|
54
|
+
).then((s) => {
|
|
55
|
+
i.signal.aborted || (p(Array.isArray(s) ? s : []), y("pret"));
|
|
21
56
|
}).catch((s) => {
|
|
22
|
-
|
|
23
|
-
}), () =>
|
|
24
|
-
}, [e,
|
|
25
|
-
const
|
|
57
|
+
i.signal.aborted || s?.name === "AbortError" || y("erreur");
|
|
58
|
+
}), () => i.abort();
|
|
59
|
+
}, [e, t, r, g]);
|
|
60
|
+
const E = I(() => b((i) => i + 1), []), x = I(
|
|
61
|
+
(i, f) => z((s) => ({ ...s, [i]: f })),
|
|
62
|
+
[]
|
|
63
|
+
), l = _({
|
|
64
|
+
site: e,
|
|
65
|
+
page: t,
|
|
66
|
+
langue: n,
|
|
67
|
+
poserApercu: () => {
|
|
68
|
+
},
|
|
69
|
+
recharger: () => {
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
l.current.site = e, l.current.page = t, l.current.langue = n, l.current.poserApercu = x, l.current.recharger = E, A(() => {
|
|
73
|
+
l.current.surChangementDePage?.(t);
|
|
74
|
+
}, [t]), A(() => {
|
|
75
|
+
const i = J();
|
|
76
|
+
if (!i) return;
|
|
77
|
+
let f = !0, s;
|
|
78
|
+
return import("@lystech/sections/editeur").then((w) => {
|
|
79
|
+
f && (O(!0), s = w.monterEditeur({
|
|
80
|
+
jeton: i,
|
|
81
|
+
site: e,
|
|
82
|
+
apiBaseUrl: r,
|
|
83
|
+
pont: l.current
|
|
84
|
+
}));
|
|
85
|
+
}).catch((w) => {
|
|
86
|
+
console.error("[lystech] éditeur indisponible:", w?.message ?? w);
|
|
87
|
+
}), () => {
|
|
88
|
+
f = !1, s?.();
|
|
89
|
+
};
|
|
90
|
+
}, [e, r]);
|
|
91
|
+
const k = C(
|
|
26
92
|
() => ({
|
|
27
93
|
site: e,
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
94
|
+
page: t,
|
|
95
|
+
langue: n,
|
|
96
|
+
apiBaseUrl: r,
|
|
97
|
+
sections: a,
|
|
98
|
+
etat: u,
|
|
99
|
+
recharger: E,
|
|
100
|
+
apercu: P,
|
|
101
|
+
poserApercu: x,
|
|
102
|
+
modeEdition: v
|
|
32
103
|
}),
|
|
33
|
-
[
|
|
104
|
+
[
|
|
105
|
+
e,
|
|
106
|
+
t,
|
|
107
|
+
n,
|
|
108
|
+
r,
|
|
109
|
+
a,
|
|
110
|
+
u,
|
|
111
|
+
E,
|
|
112
|
+
P,
|
|
113
|
+
x,
|
|
114
|
+
v
|
|
115
|
+
]
|
|
34
116
|
);
|
|
35
|
-
return /* @__PURE__ */
|
|
117
|
+
return /* @__PURE__ */ h(U.Provider, { value: k, children: c });
|
|
118
|
+
}
|
|
119
|
+
function R() {
|
|
120
|
+
return M(U);
|
|
121
|
+
}
|
|
122
|
+
const W = "https://api.lystech.ca/api";
|
|
123
|
+
function G() {
|
|
124
|
+
if (typeof window > "u") return null;
|
|
125
|
+
try {
|
|
126
|
+
const e = new URL(window.location.href), t = e.searchParams.get("lystech-acces");
|
|
127
|
+
return t ? (e.searchParams.delete("lystech-acces"), window.history.replaceState({}, "", e.toString()), t) : null;
|
|
128
|
+
} catch {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
36
131
|
}
|
|
37
|
-
|
|
38
|
-
|
|
132
|
+
const L = {
|
|
133
|
+
"sans-acces": "Vous n'avez pas encore accès à la modification de ce site.",
|
|
134
|
+
"demande-envoyee": "Demande envoyée. La personne responsable du site vous répondra.",
|
|
135
|
+
"trop-de-demandes": "Trop de demandes aujourd'hui. Réessayez demain."
|
|
136
|
+
};
|
|
137
|
+
function K({
|
|
138
|
+
site: e,
|
|
139
|
+
apiBaseUrl: t,
|
|
140
|
+
className: n,
|
|
141
|
+
mention: r = "Réalisé avec Lystech",
|
|
142
|
+
connexion: o = "Connexion"
|
|
143
|
+
}) {
|
|
144
|
+
const c = R(), a = e ?? c?.site ?? "", p = t ?? c?.apiBaseUrl ?? W, [u, y] = m(null);
|
|
145
|
+
if (A(() => y(G()), []), !a) return null;
|
|
146
|
+
const g = (b) => p + "/site-sections/edit-entry?site=" + encodeURIComponent(a) + (b ? "&demander=1" : "");
|
|
147
|
+
return /* @__PURE__ */ j(
|
|
148
|
+
"div",
|
|
149
|
+
{
|
|
150
|
+
className: n,
|
|
151
|
+
style: {
|
|
152
|
+
display: "flex",
|
|
153
|
+
flexWrap: "wrap",
|
|
154
|
+
alignItems: "center",
|
|
155
|
+
gap: "0.5em",
|
|
156
|
+
fontSize: "0.85em",
|
|
157
|
+
opacity: 0.7,
|
|
158
|
+
lineHeight: 1.5
|
|
159
|
+
},
|
|
160
|
+
children: [
|
|
161
|
+
/* @__PURE__ */ h(
|
|
162
|
+
"a",
|
|
163
|
+
{
|
|
164
|
+
href: "https://lystech.ca",
|
|
165
|
+
target: "_blank",
|
|
166
|
+
rel: "noreferrer",
|
|
167
|
+
style: { color: "inherit" },
|
|
168
|
+
children: r
|
|
169
|
+
}
|
|
170
|
+
),
|
|
171
|
+
!c?.modeEdition && !u && /* @__PURE__ */ j(F, { children: [
|
|
172
|
+
/* @__PURE__ */ h("span", { "aria-hidden": "true", children: "·" }),
|
|
173
|
+
/* @__PURE__ */ h("a", { href: g(!1), style: { color: "inherit" }, children: o })
|
|
174
|
+
] }),
|
|
175
|
+
u && /* @__PURE__ */ j("span", { style: { display: "inline-flex", gap: "0.5em", flexWrap: "wrap" }, children: [
|
|
176
|
+
/* @__PURE__ */ h("span", { "aria-hidden": "true", children: "·" }),
|
|
177
|
+
/* @__PURE__ */ h("span", { children: L[u] ?? L["sans-acces"] }),
|
|
178
|
+
u === "sans-acces" && /* @__PURE__ */ h("a", { href: g(!0), style: { color: "inherit" }, children: "Demander l'accès" })
|
|
179
|
+
] })
|
|
180
|
+
]
|
|
181
|
+
}
|
|
182
|
+
);
|
|
39
183
|
}
|
|
40
|
-
function
|
|
41
|
-
return !!e && typeof e == "object" && !Array.isArray(e) && Object.keys(e).every((
|
|
184
|
+
function H(e) {
|
|
185
|
+
return !!e && typeof e == "object" && !Array.isArray(e) && Object.keys(e).every((t) => /^[a-z]{2}(-[A-Za-z]{2})?$/.test(t));
|
|
42
186
|
}
|
|
43
|
-
function
|
|
44
|
-
if (Array.isArray(e)) return e.map((
|
|
45
|
-
if (
|
|
46
|
-
const
|
|
47
|
-
return t
|
|
187
|
+
function S(e, t) {
|
|
188
|
+
if (Array.isArray(e)) return e.map((n) => S(n, t));
|
|
189
|
+
if (H(e)) {
|
|
190
|
+
const n = e, r = t.slice(0, 2);
|
|
191
|
+
return n[t] ?? n[r] ?? Object.values(n).find((c) => c != null) ?? "";
|
|
48
192
|
}
|
|
49
193
|
if (e && typeof e == "object") {
|
|
50
|
-
const
|
|
51
|
-
for (const [
|
|
52
|
-
|
|
53
|
-
return
|
|
194
|
+
const n = {};
|
|
195
|
+
for (const [r, o] of Object.entries(e))
|
|
196
|
+
n[r] = S(o, t);
|
|
197
|
+
return n;
|
|
54
198
|
}
|
|
55
199
|
return e;
|
|
56
200
|
}
|
|
57
|
-
function
|
|
58
|
-
if (!
|
|
201
|
+
function N(e, t) {
|
|
202
|
+
if (!t || typeof t != "object" || Array.isArray(t))
|
|
59
203
|
return e;
|
|
60
|
-
const
|
|
61
|
-
for (const [
|
|
62
|
-
|
|
63
|
-
return
|
|
204
|
+
const n = { ...e };
|
|
205
|
+
for (const [r, o] of Object.entries(t))
|
|
206
|
+
o != null && (n[r] = o);
|
|
207
|
+
return n;
|
|
64
208
|
}
|
|
65
|
-
function
|
|
66
|
-
const
|
|
67
|
-
return
|
|
68
|
-
const o =
|
|
69
|
-
(
|
|
70
|
-
),
|
|
209
|
+
function Q(e, t) {
|
|
210
|
+
const n = R(), r = n?.apercu?.[e];
|
|
211
|
+
return C(() => {
|
|
212
|
+
const o = n?.sections.find(
|
|
213
|
+
(p) => p.blockType === e
|
|
214
|
+
), c = r ?? o?.data ?? null, a = c ? S(c, n?.langue ?? "fr") : null;
|
|
71
215
|
return {
|
|
72
|
-
contenu:
|
|
73
|
-
publie: !!
|
|
216
|
+
contenu: N(t.defaut, a),
|
|
217
|
+
publie: !!o?.data,
|
|
74
218
|
UID: o?.UID ?? null,
|
|
75
|
-
chargement:
|
|
219
|
+
chargement: n?.etat === "chargement",
|
|
220
|
+
ancrage: { "data-lystech-section": e }
|
|
76
221
|
};
|
|
77
|
-
}, [
|
|
222
|
+
}, [
|
|
223
|
+
n?.sections,
|
|
224
|
+
n?.langue,
|
|
225
|
+
n?.etat,
|
|
226
|
+
r,
|
|
227
|
+
e,
|
|
228
|
+
t.defaut
|
|
229
|
+
]);
|
|
78
230
|
}
|
|
79
|
-
function
|
|
80
|
-
const
|
|
81
|
-
return
|
|
82
|
-
const o = (
|
|
83
|
-
(
|
|
231
|
+
function X(e, t) {
|
|
232
|
+
const n = R();
|
|
233
|
+
return C(() => {
|
|
234
|
+
const r = { "data-lystech-section": e }, o = (n?.sections ?? []).filter(
|
|
235
|
+
(c) => c.blockType === e
|
|
84
236
|
);
|
|
85
237
|
return o.length === 0 ? {
|
|
86
|
-
items: (
|
|
87
|
-
...
|
|
88
|
-
__uid: `defaut-${
|
|
238
|
+
items: (t?.defaut ?? []).map((c, a) => ({
|
|
239
|
+
...c,
|
|
240
|
+
__uid: `defaut-${a}`
|
|
89
241
|
})),
|
|
90
242
|
publie: !1,
|
|
91
|
-
chargement:
|
|
243
|
+
chargement: n?.etat === "chargement",
|
|
244
|
+
ancrage: r
|
|
92
245
|
} : {
|
|
93
|
-
items: o.map((
|
|
94
|
-
...
|
|
95
|
-
__uid:
|
|
246
|
+
items: o.map((c) => ({
|
|
247
|
+
...S(c.data ?? {}, n?.langue ?? "fr"),
|
|
248
|
+
__uid: c.UID
|
|
96
249
|
})),
|
|
97
250
|
publie: !0,
|
|
98
|
-
chargement:
|
|
251
|
+
chargement: n?.etat === "chargement",
|
|
252
|
+
ancrage: r
|
|
99
253
|
};
|
|
100
|
-
}, [
|
|
254
|
+
}, [n?.sections, n?.langue, n?.etat, e, t?.defaut]);
|
|
101
255
|
}
|
|
102
256
|
export {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
257
|
+
q as SectionsProvider,
|
|
258
|
+
K as SignatureLystech,
|
|
259
|
+
Q as useSection,
|
|
260
|
+
X as useSectionList,
|
|
261
|
+
R as useSectionsContext
|
|
107
262
|
};
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export type TypeDeChamp = "text" | "richText" | "image" | "number" | "boolean" | "url" | "date" | "choice" | "list";
|
|
2
|
+
export type ChampDeclare = {
|
|
3
|
+
key: string;
|
|
4
|
+
label: string;
|
|
5
|
+
type: TypeDeChamp;
|
|
6
|
+
required?: boolean;
|
|
7
|
+
help?: string;
|
|
8
|
+
options?: string[];
|
|
9
|
+
/** Sous-champs d'une `list`. */
|
|
10
|
+
of?: ChampDeclare[];
|
|
11
|
+
/**
|
|
12
|
+
* Champ TRADUIT. La valeur stockée devient { fr: …, en: … } et le paquet
|
|
13
|
+
* rend celle de la langue courante.
|
|
14
|
+
*
|
|
15
|
+
* Le serveur ne connaît pas cette clé — il stocke la déclaration telle
|
|
16
|
+
* quelle. C'est voulu : ajouter une langue ne doit pas demander une
|
|
17
|
+
* migration côté plateforme.
|
|
18
|
+
*/
|
|
19
|
+
localized?: boolean;
|
|
20
|
+
};
|
|
21
|
+
export type BlocDeclare = {
|
|
22
|
+
blockType: string;
|
|
23
|
+
label: string;
|
|
24
|
+
description?: string;
|
|
25
|
+
fields: ChampDeclare[];
|
|
26
|
+
pages?: string[] | null;
|
|
27
|
+
maxPerPage?: number | null;
|
|
28
|
+
};
|
|
29
|
+
/** Une section telle qu'elle revient du serveur. */
|
|
30
|
+
export type SectionPubliee = {
|
|
31
|
+
UID: string;
|
|
32
|
+
page: string;
|
|
33
|
+
blockType: string;
|
|
34
|
+
position: number;
|
|
35
|
+
data: Record<string, unknown> | null;
|
|
36
|
+
};
|
|
37
|
+
export type ValeurTraduite = {
|
|
38
|
+
[langue: string]: unknown;
|
|
39
|
+
};
|
|
40
|
+
/** Un champ traduit est un objet de langues ; on le reconnaît sans schéma. */
|
|
41
|
+
export declare function estTraduite(v: unknown): v is ValeurTraduite;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LES ATTRIBUTS À POSER SUR LE CONTENEUR DE LA SECTION.
|
|
3
|
+
*
|
|
4
|
+
* L'éditeur doit pouvoir retrouver, DANS LA PAGE, le rectangle qui correspond
|
|
5
|
+
* à un bloc — pour l'encadrer et y poser un bouton. Il n'existe aucun moyen
|
|
6
|
+
* fiable de le deviner : deux sections peuvent afficher les mêmes mots, et un
|
|
7
|
+
* site est libre de sa structure. Le site le dit donc explicitement, en
|
|
8
|
+
* étalant ces attributs sur son élément :
|
|
9
|
+
*
|
|
10
|
+
* const { contenu, ancrage } = useSection("tarifs", { defaut });
|
|
11
|
+
* return <section {...ancrage}>…</section>;
|
|
12
|
+
*
|
|
13
|
+
* Hors mode édition, c'est un attribut `data-` inerte : aucun poids, aucun
|
|
14
|
+
* effet, aucun style.
|
|
15
|
+
*/
|
|
16
|
+
export type AncrageEdition = {
|
|
17
|
+
"data-lystech-section": string;
|
|
18
|
+
};
|
|
19
|
+
export type ResultatSection<T> = {
|
|
20
|
+
/** Le contenu à afficher : aperçu, sinon publié, sinon défaut. */
|
|
21
|
+
contenu: T;
|
|
22
|
+
/** Vrai quand le contenu vient du serveur, faux quand c'est le défaut. */
|
|
23
|
+
publie: boolean;
|
|
24
|
+
/** L'identifiant de la section — nécessaire à l'édition. */
|
|
25
|
+
UID: string | null;
|
|
26
|
+
chargement: boolean;
|
|
27
|
+
/** À étaler sur le conteneur pour que l'éditeur puisse le situer. */
|
|
28
|
+
ancrage: AncrageEdition;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Le contenu d'un type de bloc sur la page courante.
|
|
32
|
+
*
|
|
33
|
+
* Le cas visé est celui d'un site DÉJÀ DESSINÉ : un bloc par type et par page,
|
|
34
|
+
* dont on veut rendre les mots et les images modifiables — pas un constructeur
|
|
35
|
+
* de pages. Pour des blocs répétables, voir `useSectionList`.
|
|
36
|
+
*/
|
|
37
|
+
export declare function useSection<T extends object>(blockType: string, options: {
|
|
38
|
+
defaut: T;
|
|
39
|
+
}): ResultatSection<T>;
|
|
40
|
+
/** Plusieurs blocs du même type, dans l'ordre choisi par le client. */
|
|
41
|
+
export declare function useSectionList<T extends object>(blockType: string, options?: {
|
|
42
|
+
defaut?: T[];
|
|
43
|
+
}): {
|
|
44
|
+
items: Array<T & {
|
|
45
|
+
__uid: string;
|
|
46
|
+
}>;
|
|
47
|
+
publie: boolean;
|
|
48
|
+
chargement: boolean;
|
|
49
|
+
ancrage: AncrageEdition;
|
|
50
|
+
};
|
package/package.json
CHANGED
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lystech/sections",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Contenu de site editable par le client — lecture legere, edition chargee a la demande.",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "./dist/index.
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
7
|
"module": "./dist/index.js",
|
|
8
8
|
"types": "./dist/index.d.ts",
|
|
9
9
|
"exports": {
|
|
10
10
|
".": {
|
|
11
11
|
"types": "./dist/index.d.ts",
|
|
12
|
-
"import": "./dist/index.js"
|
|
13
|
-
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./editeur": {
|
|
15
|
+
"types": "./dist/editeur/index.d.ts",
|
|
16
|
+
"import": "./dist/editeur.js"
|
|
14
17
|
}
|
|
15
18
|
},
|
|
19
|
+
"sideEffects": false,
|
|
16
20
|
"files": ["dist"],
|
|
17
21
|
"scripts": {
|
|
18
22
|
"build": "tsc -b && vite build",
|
package/dist/index.umd.cjs
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
(function(s,a){typeof exports=="object"&&typeof module<"u"?a(exports,require("react/jsx-runtime"),require("react")):typeof define=="function"&&define.amd?define(["exports","react/jsx-runtime","react"],a):(s=typeof globalThis<"u"?globalThis:s||self,a(s.LystechSections={},s.jsxRuntime,s.React))})(this,(function(s,a,c){"use strict";const y=c.createContext(null),j="https://api.lystech.ca/api";function A({site:t,page:n,langue:e="fr",apiBaseUrl:o=j,children:r}){const[i,l]=c.useState([]),[S,h]=c.useState("chargement"),[E,P]=c.useState(0),b=c.useRef(null);c.useEffect(()=>{if(!t||!n)return;b.current?.abort();const f=new AbortController;return b.current=f,h("chargement"),fetch(`${o}/site-sections/public/${encodeURIComponent(t)}/${encodeURIComponent(n)}`,{signal:f.signal}).then(u=>u.ok?u.json():Promise.reject(new Error(String(u.status)))).then(u=>{f.signal.aborted||(l(Array.isArray(u)?u:[]),h("pret"))}).catch(u=>{f.signal.aborted||u?.name==="AbortError"||h("erreur")}),()=>f.abort()},[t,n,o,E]);const R=c.useMemo(()=>({site:t,langue:e,sections:i,etat:S,recharger:()=>P(f=>f+1)}),[t,e,i,S]);return a.jsx(y.Provider,{value:R,children:r})}function m(){return c.useContext(y)}function g(t){return!!t&&typeof t=="object"&&!Array.isArray(t)&&Object.keys(t).every(n=>/^[a-z]{2}(-[A-Za-z]{2})?$/.test(n))}function d(t,n){if(Array.isArray(t))return t.map(e=>d(e,n));if(g(t)){const e=t,o=n.slice(0,2);return e[n]??e[o]??Object.values(e).find(i=>i!=null)??""}if(t&&typeof t=="object"){const e={};for(const[o,r]of Object.entries(t))e[o]=d(r,n);return e}return t}function p(t,n){if(!n||typeof n!="object"||Array.isArray(n))return t;const e={...t};for(const[o,r]of Object.entries(n))r!=null&&(e[o]=r);return e}function x(t,n){const e=m();return c.useMemo(()=>{const o=e?.sections.find(l=>l.blockType===t),r=o?.data??null,i=r?d(r,e?.langue??"fr"):null;return{contenu:p(n.defaut,i),publie:!!r,UID:o?.UID??null,chargement:e?.etat==="chargement"}},[e?.sections,e?.langue,e?.etat,t,n.defaut])}function C(t,n){const e=m();return c.useMemo(()=>{const o=(e?.sections??[]).filter(r=>r.blockType===t);return o.length===0?{items:(n?.defaut??[]).map((r,i)=>({...r,__uid:`defaut-${i}`})),publie:!1,chargement:e?.etat==="chargement"}:{items:o.map(r=>({...d(r.data??{},e?.langue??"fr"),__uid:r.UID})),publie:!0,chargement:e?.etat==="chargement"}},[e?.sections,e?.langue,e?.etat,t,n?.defaut])}s.SectionsProvider=A,s.useSection=x,s.useSectionList=C,s.useSectionsContext=m,Object.defineProperty(s,Symbol.toStringTag,{value:"Module"})}));
|