@sevenfold/setto-client 0.28.1 → 0.30.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/dist/LanguageSwitcher.d.ts +8 -3
- package/dist/SettoImage.d.ts +22 -0
- package/dist/index.d.ts +7 -1
- package/dist/lib/page-store.d.ts +84 -0
- package/dist/pages/SettoPage.d.ts +63 -0
- package/dist/pages/bundled.d.ts +10 -0
- package/dist/pages/manifest.d.ts +202 -0
- package/dist/provider.d.ts +7 -0
- package/dist/setto-client.js +967 -245
- package/dist/setto-client.js.map +1 -1
- package/dist/types.d.ts +13 -0
- package/package.json +1 -1
package/dist/setto-client.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsx, jsxs, Fragment } from "react/jsx-runtime";
|
|
2
2
|
import * as React from "react";
|
|
3
3
|
import { useState, useRef, useEffect, useMemo, useId, useInsertionEffect, Children, createContext, useContext, useCallback, useLayoutEffect, useSyncExternalStore, forwardRef, Fragment as Fragment$1 } from "react";
|
|
4
4
|
import { useTranslation } from "react-i18next";
|
|
@@ -72,235 +72,6 @@ function pickInitialLanguage(available, browserLanguages = typeof navigator ===
|
|
|
72
72
|
}
|
|
73
73
|
return fallback;
|
|
74
74
|
}
|
|
75
|
-
function normalise(pathname) {
|
|
76
|
-
const path2 = pathname.startsWith("/") ? pathname : `/${pathname}`;
|
|
77
|
-
return path2.length > 1 && path2.endsWith("/") ? path2.slice(0, -1) : path2;
|
|
78
|
-
}
|
|
79
|
-
function languageFromPath(pathname, languages, primary = languages[0] ?? DEFAULT_LANGUAGE) {
|
|
80
|
-
const path2 = normalise(pathname);
|
|
81
|
-
const first = path2.split("/")[1] ?? "";
|
|
82
|
-
const match = languages.find(
|
|
83
|
-
(tag) => tag !== primary && tag.toLowerCase() === first.toLowerCase()
|
|
84
|
-
);
|
|
85
|
-
if (!match) return { language: primary, basename: "", rest: path2 };
|
|
86
|
-
const rest = path2.slice(first.length + 1);
|
|
87
|
-
return { language: match, basename: `/${match}`, rest: rest === "" ? "/" : rest };
|
|
88
|
-
}
|
|
89
|
-
function pathForLanguage(pathname, target, languages, primary = languages[0] ?? DEFAULT_LANGUAGE) {
|
|
90
|
-
const { rest } = languageFromPath(pathname, languages, primary);
|
|
91
|
-
if (target === primary) return rest;
|
|
92
|
-
return rest === "/" ? `/${target}` : `/${target}${rest}`;
|
|
93
|
-
}
|
|
94
|
-
function alternateLinks(origin, pathname, languages, primary = languages[0] ?? DEFAULT_LANGUAGE) {
|
|
95
|
-
if (languages.length < 2) return [];
|
|
96
|
-
const links = languages.map((tag) => ({
|
|
97
|
-
hreflang: tag,
|
|
98
|
-
href: origin + pathForLanguage(pathname, tag, languages, primary)
|
|
99
|
-
}));
|
|
100
|
-
links.push({
|
|
101
|
-
hreflang: "x-default",
|
|
102
|
-
href: origin + pathForLanguage(pathname, primary, languages, primary)
|
|
103
|
-
});
|
|
104
|
-
return links;
|
|
105
|
-
}
|
|
106
|
-
function isReservedPath(pathname) {
|
|
107
|
-
return pathname === "/setto" || pathname.startsWith("/setto/");
|
|
108
|
-
}
|
|
109
|
-
function redirectForVisitor(pathname, languages, browserLanguages, stored, primary = languages[0] ?? DEFAULT_LANGUAGE) {
|
|
110
|
-
if (languages.length < 2) return null;
|
|
111
|
-
if (isReservedPath(pathname)) return null;
|
|
112
|
-
const current = languageFromPath(pathname, languages, primary);
|
|
113
|
-
if (current.basename !== "") return null;
|
|
114
|
-
if (stored) {
|
|
115
|
-
return stored !== primary && languages.includes(stored) ? pathForLanguage(pathname, stored, languages, primary) : null;
|
|
116
|
-
}
|
|
117
|
-
for (const wanted of browserLanguages) {
|
|
118
|
-
const base = wanted.toLowerCase().split("-")[0];
|
|
119
|
-
if (!base) continue;
|
|
120
|
-
if (base === primary.toLowerCase().split("-")[0]) return null;
|
|
121
|
-
const norwegian2 = ["no", "nb", "nn"];
|
|
122
|
-
if (norwegian2.includes(base) && norwegian2.includes(primary.toLowerCase())) return null;
|
|
123
|
-
const hit = languages.find((tag) => tag.toLowerCase().split("-")[0] === base);
|
|
124
|
-
if (hit && hit !== primary) return pathForLanguage(pathname, hit, languages, primary);
|
|
125
|
-
}
|
|
126
|
-
return null;
|
|
127
|
-
}
|
|
128
|
-
const INLINE_MARKER = "data-setto-languages-inline";
|
|
129
|
-
function SettoLanguages({ languages, inline = false }) {
|
|
130
|
-
const { i18n } = useTranslation();
|
|
131
|
-
const [open, setOpen] = useState(false);
|
|
132
|
-
const ref = useRef(null);
|
|
133
|
-
useEffect(() => {
|
|
134
|
-
if (!inline) return;
|
|
135
|
-
const root2 = document.documentElement;
|
|
136
|
-
const count = Number(root2.getAttribute(INLINE_MARKER) ?? "0") + 1;
|
|
137
|
-
root2.setAttribute(INLINE_MARKER, String(count));
|
|
138
|
-
return () => {
|
|
139
|
-
const left = Number(root2.getAttribute(INLINE_MARKER) ?? "1") - 1;
|
|
140
|
-
if (left > 0) root2.setAttribute(INLINE_MARKER, String(left));
|
|
141
|
-
else root2.removeAttribute(INLINE_MARKER);
|
|
142
|
-
};
|
|
143
|
-
}, [inline]);
|
|
144
|
-
useEffect(() => {
|
|
145
|
-
if (!open) return;
|
|
146
|
-
const onDown = (e) => {
|
|
147
|
-
if (!ref.current?.contains(e.target)) setOpen(false);
|
|
148
|
-
};
|
|
149
|
-
const onKey = (e) => {
|
|
150
|
-
if (e.key === "Escape") setOpen(false);
|
|
151
|
-
};
|
|
152
|
-
document.addEventListener("mousedown", onDown);
|
|
153
|
-
document.addEventListener("keydown", onKey);
|
|
154
|
-
return () => {
|
|
155
|
-
document.removeEventListener("mousedown", onDown);
|
|
156
|
-
document.removeEventListener("keydown", onKey);
|
|
157
|
-
};
|
|
158
|
-
}, [open]);
|
|
159
|
-
if (languages.length < 2) return null;
|
|
160
|
-
const current = i18n.language;
|
|
161
|
-
const choose = (tag) => {
|
|
162
|
-
setOpen(false);
|
|
163
|
-
if (tag === current) return;
|
|
164
|
-
storeLanguage(tag);
|
|
165
|
-
const target = pathForLanguage(window.location.pathname, tag, languages);
|
|
166
|
-
window.location.assign(target + window.location.search + window.location.hash);
|
|
167
|
-
};
|
|
168
|
-
return /* @__PURE__ */ jsxs("div", { ref, style: inline ? inlineWrapStyle : floatingWrapStyle, children: [
|
|
169
|
-
/* @__PURE__ */ jsxs(
|
|
170
|
-
"button",
|
|
171
|
-
{
|
|
172
|
-
type: "button",
|
|
173
|
-
onClick: () => setOpen((o) => !o),
|
|
174
|
-
"aria-expanded": open,
|
|
175
|
-
"aria-haspopup": "listbox",
|
|
176
|
-
"aria-label": `Språk: ${languageName(current)}`,
|
|
177
|
-
style: inline ? inlineTriggerStyle : floatingTriggerStyle,
|
|
178
|
-
children: [
|
|
179
|
-
/* @__PURE__ */ jsx(GlobeIcon, {}),
|
|
180
|
-
languageName(current)
|
|
181
|
-
]
|
|
182
|
-
}
|
|
183
|
-
),
|
|
184
|
-
open ? /* @__PURE__ */ jsx("ul", { role: "listbox", style: inline ? inlineListStyle : floatingListStyle, children: languages.map((tag) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
|
|
185
|
-
"button",
|
|
186
|
-
{
|
|
187
|
-
type: "button",
|
|
188
|
-
role: "option",
|
|
189
|
-
"aria-selected": tag === current,
|
|
190
|
-
onClick: () => choose(tag),
|
|
191
|
-
style: {
|
|
192
|
-
...optionStyle,
|
|
193
|
-
fontWeight: tag === current ? 600 : 400
|
|
194
|
-
},
|
|
195
|
-
children: languageName(tag)
|
|
196
|
-
}
|
|
197
|
-
) }, tag)) }) : null
|
|
198
|
-
] });
|
|
199
|
-
}
|
|
200
|
-
function AutoLanguageSwitcher({ languages }) {
|
|
201
|
-
const [inlineElsewhere, setInlineElsewhere] = useState(false);
|
|
202
|
-
useEffect(() => {
|
|
203
|
-
const check = () => setInlineElsewhere(document.documentElement.hasAttribute(INLINE_MARKER));
|
|
204
|
-
check();
|
|
205
|
-
const observer = new MutationObserver(check);
|
|
206
|
-
observer.observe(document.documentElement, {
|
|
207
|
-
attributes: true,
|
|
208
|
-
attributeFilter: [INLINE_MARKER]
|
|
209
|
-
});
|
|
210
|
-
return () => observer.disconnect();
|
|
211
|
-
}, []);
|
|
212
|
-
if (inlineElsewhere || languages.length < 2) return null;
|
|
213
|
-
return /* @__PURE__ */ jsx(SettoLanguages, { languages });
|
|
214
|
-
}
|
|
215
|
-
function GlobeIcon() {
|
|
216
|
-
return /* @__PURE__ */ jsxs(
|
|
217
|
-
"svg",
|
|
218
|
-
{
|
|
219
|
-
width: "15",
|
|
220
|
-
height: "15",
|
|
221
|
-
viewBox: "0 0 24 24",
|
|
222
|
-
fill: "none",
|
|
223
|
-
stroke: "currentColor",
|
|
224
|
-
strokeWidth: "1.8",
|
|
225
|
-
strokeLinecap: "round",
|
|
226
|
-
strokeLinejoin: "round",
|
|
227
|
-
"aria-hidden": "true",
|
|
228
|
-
children: [
|
|
229
|
-
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "9" }),
|
|
230
|
-
/* @__PURE__ */ jsx("path", { d: "M3 12h18M12 3c2.4 2.6 3.6 5.6 3.6 9s-1.2 6.4-3.6 9c-2.4-2.6-3.6-5.6-3.6-9S9.6 5.6 12 3z" })
|
|
231
|
-
]
|
|
232
|
-
}
|
|
233
|
-
);
|
|
234
|
-
}
|
|
235
|
-
const floatingWrapStyle = {
|
|
236
|
-
position: "fixed",
|
|
237
|
-
left: "max(16px, env(safe-area-inset-left))",
|
|
238
|
-
bottom: "max(16px, env(safe-area-inset-bottom))",
|
|
239
|
-
zIndex: 2147482e3,
|
|
240
|
-
fontFamily: 'system-ui, -apple-system, "Segoe UI", sans-serif'
|
|
241
|
-
};
|
|
242
|
-
const inlineWrapStyle = {
|
|
243
|
-
position: "relative",
|
|
244
|
-
display: "inline-block",
|
|
245
|
-
fontFamily: "inherit"
|
|
246
|
-
};
|
|
247
|
-
const triggerBase = {
|
|
248
|
-
display: "inline-flex",
|
|
249
|
-
alignItems: "center",
|
|
250
|
-
gap: 7,
|
|
251
|
-
minHeight: 40,
|
|
252
|
-
fontSize: 13.5,
|
|
253
|
-
fontWeight: 500,
|
|
254
|
-
lineHeight: 1,
|
|
255
|
-
cursor: "pointer"
|
|
256
|
-
};
|
|
257
|
-
const floatingTriggerStyle = {
|
|
258
|
-
...triggerBase,
|
|
259
|
-
padding: "8px 14px",
|
|
260
|
-
borderRadius: 999,
|
|
261
|
-
border: "1px solid rgba(127, 127, 127, 0.32)",
|
|
262
|
-
background: "rgba(255, 255, 255, 0.82)",
|
|
263
|
-
backdropFilter: "blur(8px)",
|
|
264
|
-
WebkitBackdropFilter: "blur(8px)",
|
|
265
|
-
color: "#1a1a1a",
|
|
266
|
-
boxShadow: "0 4px 14px rgba(0, 0, 0, 0.12)"
|
|
267
|
-
};
|
|
268
|
-
const inlineTriggerStyle = {
|
|
269
|
-
...triggerBase,
|
|
270
|
-
padding: "8px 6px",
|
|
271
|
-
border: 0,
|
|
272
|
-
borderRadius: 8,
|
|
273
|
-
background: "transparent",
|
|
274
|
-
color: "inherit",
|
|
275
|
-
opacity: 0.75
|
|
276
|
-
};
|
|
277
|
-
const listBase = {
|
|
278
|
-
position: "absolute",
|
|
279
|
-
margin: 0,
|
|
280
|
-
padding: 4,
|
|
281
|
-
listStyle: "none",
|
|
282
|
-
minWidth: 150,
|
|
283
|
-
borderRadius: 12,
|
|
284
|
-
border: "1px solid rgba(127, 127, 127, 0.24)",
|
|
285
|
-
background: "#ffffff",
|
|
286
|
-
boxShadow: "0 12px 32px rgba(0, 0, 0, 0.18)",
|
|
287
|
-
zIndex: 1
|
|
288
|
-
};
|
|
289
|
-
const floatingListStyle = { ...listBase, left: 0, bottom: "calc(100% + 8px)" };
|
|
290
|
-
const inlineListStyle = { ...listBase, left: 0, top: "calc(100% + 8px)" };
|
|
291
|
-
const optionStyle = {
|
|
292
|
-
display: "block",
|
|
293
|
-
width: "100%",
|
|
294
|
-
minHeight: 40,
|
|
295
|
-
padding: "9px 12px",
|
|
296
|
-
border: 0,
|
|
297
|
-
borderRadius: 8,
|
|
298
|
-
background: "transparent",
|
|
299
|
-
color: "#1a1a1a",
|
|
300
|
-
fontSize: 13.5,
|
|
301
|
-
textAlign: "left",
|
|
302
|
-
cursor: "pointer"
|
|
303
|
-
};
|
|
304
75
|
function __rest(s, e) {
|
|
305
76
|
var t = {};
|
|
306
77
|
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
@@ -20993,10 +20764,10 @@ function shouldShowDeprecationWarning() {
|
|
|
20993
20764
|
return parseInt(versionMatch[1], 10) <= 18;
|
|
20994
20765
|
}
|
|
20995
20766
|
if (shouldShowDeprecationWarning()) console.warn("⚠️ Node.js 18 and below are deprecated and will no longer be supported in future versions of @supabase/supabase-js. Please upgrade to Node.js 20 or later. For more information, visit: https://github.com/orgs/supabase/discussions/37217");
|
|
20996
|
-
const cache = /* @__PURE__ */ new WeakMap();
|
|
20767
|
+
const cache$1 = /* @__PURE__ */ new WeakMap();
|
|
20997
20768
|
function getSupabase(config) {
|
|
20998
20769
|
const key = { url: config.url, anonKey: config.anonKey };
|
|
20999
|
-
const hit = cache.get(key);
|
|
20770
|
+
const hit = cache$1.get(key);
|
|
21000
20771
|
if (hit) return hit;
|
|
21001
20772
|
const client = createClient(config.url, config.anonKey, {
|
|
21002
20773
|
auth: {
|
|
@@ -21005,7 +20776,7 @@ function getSupabase(config) {
|
|
|
21005
20776
|
storageKey: `setto-auth-${config.url}`
|
|
21006
20777
|
}
|
|
21007
20778
|
});
|
|
21008
|
-
cache.set(key, client);
|
|
20779
|
+
cache$1.set(key, client);
|
|
21009
20780
|
return client;
|
|
21010
20781
|
}
|
|
21011
20782
|
function createFormsApi(request) {
|
|
@@ -21473,6 +21244,59 @@ class I18nStore {
|
|
|
21473
21244
|
for (const fn of this.listeners) fn(this.cachedSnapshot);
|
|
21474
21245
|
}
|
|
21475
21246
|
}
|
|
21247
|
+
function normalise(pathname) {
|
|
21248
|
+
const path2 = pathname.startsWith("/") ? pathname : `/${pathname}`;
|
|
21249
|
+
return path2.length > 1 && path2.endsWith("/") ? path2.slice(0, -1) : path2;
|
|
21250
|
+
}
|
|
21251
|
+
function languageFromPath(pathname, languages, primary = languages[0] ?? DEFAULT_LANGUAGE) {
|
|
21252
|
+
const path2 = normalise(pathname);
|
|
21253
|
+
const first = path2.split("/")[1] ?? "";
|
|
21254
|
+
const match = languages.find(
|
|
21255
|
+
(tag) => tag !== primary && tag.toLowerCase() === first.toLowerCase()
|
|
21256
|
+
);
|
|
21257
|
+
if (!match) return { language: primary, basename: "", rest: path2 };
|
|
21258
|
+
const rest = path2.slice(first.length + 1);
|
|
21259
|
+
return { language: match, basename: `/${match}`, rest: rest === "" ? "/" : rest };
|
|
21260
|
+
}
|
|
21261
|
+
function pathForLanguage(pathname, target, languages, primary = languages[0] ?? DEFAULT_LANGUAGE) {
|
|
21262
|
+
const { rest } = languageFromPath(pathname, languages, primary);
|
|
21263
|
+
if (target === primary) return rest;
|
|
21264
|
+
return rest === "/" ? `/${target}` : `/${target}${rest}`;
|
|
21265
|
+
}
|
|
21266
|
+
function alternateLinks(origin, pathname, languages, primary = languages[0] ?? DEFAULT_LANGUAGE) {
|
|
21267
|
+
if (languages.length < 2) return [];
|
|
21268
|
+
const links = languages.map((tag) => ({
|
|
21269
|
+
hreflang: tag,
|
|
21270
|
+
href: origin + pathForLanguage(pathname, tag, languages, primary)
|
|
21271
|
+
}));
|
|
21272
|
+
links.push({
|
|
21273
|
+
hreflang: "x-default",
|
|
21274
|
+
href: origin + pathForLanguage(pathname, primary, languages, primary)
|
|
21275
|
+
});
|
|
21276
|
+
return links;
|
|
21277
|
+
}
|
|
21278
|
+
function isReservedPath(pathname) {
|
|
21279
|
+
return pathname === "/setto" || pathname.startsWith("/setto/");
|
|
21280
|
+
}
|
|
21281
|
+
function redirectForVisitor(pathname, languages, browserLanguages, stored, primary = languages[0] ?? DEFAULT_LANGUAGE) {
|
|
21282
|
+
if (languages.length < 2) return null;
|
|
21283
|
+
if (isReservedPath(pathname)) return null;
|
|
21284
|
+
const current = languageFromPath(pathname, languages, primary);
|
|
21285
|
+
if (current.basename !== "") return null;
|
|
21286
|
+
if (stored) {
|
|
21287
|
+
return stored !== primary && languages.includes(stored) ? pathForLanguage(pathname, stored, languages, primary) : null;
|
|
21288
|
+
}
|
|
21289
|
+
for (const wanted of browserLanguages) {
|
|
21290
|
+
const base = wanted.toLowerCase().split("-")[0];
|
|
21291
|
+
if (!base) continue;
|
|
21292
|
+
if (base === primary.toLowerCase().split("-")[0]) return null;
|
|
21293
|
+
const norwegian2 = ["no", "nb", "nn"];
|
|
21294
|
+
if (norwegian2.includes(base) && norwegian2.includes(primary.toLowerCase())) return null;
|
|
21295
|
+
const hit = languages.find((tag) => tag.toLowerCase().split("-")[0] === base);
|
|
21296
|
+
if (hit && hit !== primary) return pathForLanguage(pathname, hit, languages, primary);
|
|
21297
|
+
}
|
|
21298
|
+
return null;
|
|
21299
|
+
}
|
|
21476
21300
|
const SETTO_IMAGE_REPO_DIR = "public/images/setto";
|
|
21477
21301
|
const SETTO_IMAGE_URL_PREFIX = "/images/setto";
|
|
21478
21302
|
const MIME_EXT = {
|
|
@@ -21635,6 +21459,452 @@ class ThemeStore {
|
|
|
21635
21459
|
for (const fn of this.listeners) fn(this.cachedSnapshot);
|
|
21636
21460
|
}
|
|
21637
21461
|
}
|
|
21462
|
+
const PAGES_MANIFEST_VERSION = 1;
|
|
21463
|
+
const PAGES_PATH = "src/content/pages.json";
|
|
21464
|
+
const BUILTIN_SECTION_TYPES = ["text"];
|
|
21465
|
+
const ID = /^[a-z0-9][a-z0-9-]{0,62}$/;
|
|
21466
|
+
const PATH = /^\/(?:[a-z0-9][a-z0-9-]*(?:\/[a-z0-9][a-z0-9-]*)*)?$/;
|
|
21467
|
+
function isRecord$1(value) {
|
|
21468
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
21469
|
+
}
|
|
21470
|
+
function parseManifest(input) {
|
|
21471
|
+
const issues = [];
|
|
21472
|
+
if (!isRecord$1(input)) return { ok: false, issues: [{ at: "$", message: "not an object" }] };
|
|
21473
|
+
if (input.version !== PAGES_MANIFEST_VERSION) {
|
|
21474
|
+
issues.push({ at: "version", message: `expected ${PAGES_MANIFEST_VERSION}` });
|
|
21475
|
+
}
|
|
21476
|
+
const pages = [];
|
|
21477
|
+
const pageIds = /* @__PURE__ */ new Set();
|
|
21478
|
+
const paths = /* @__PURE__ */ new Set();
|
|
21479
|
+
const sectionIds = /* @__PURE__ */ new Set();
|
|
21480
|
+
const rawPages = Array.isArray(input.pages) ? input.pages : null;
|
|
21481
|
+
if (!rawPages) issues.push({ at: "pages", message: "must be a list" });
|
|
21482
|
+
(rawPages ?? []).forEach((raw, i) => {
|
|
21483
|
+
const at = `pages[${i}]`;
|
|
21484
|
+
if (!isRecord$1(raw)) {
|
|
21485
|
+
issues.push({ at, message: "not an object" });
|
|
21486
|
+
return;
|
|
21487
|
+
}
|
|
21488
|
+
const id = String(raw.id ?? "");
|
|
21489
|
+
const path2 = String(raw.path ?? "");
|
|
21490
|
+
const titleKey = String(raw.titleKey ?? "");
|
|
21491
|
+
if (!ID.test(id)) issues.push({ at: `${at}.id`, message: `invalid id "${id}"` });
|
|
21492
|
+
else if (pageIds.has(id)) issues.push({ at: `${at}.id`, message: `duplicate page "${id}"` });
|
|
21493
|
+
pageIds.add(id);
|
|
21494
|
+
if (!PATH.test(path2)) issues.push({ at: `${at}.path`, message: `invalid path "${path2}"` });
|
|
21495
|
+
else if (path2 === "/setto" || path2.startsWith("/setto/")) {
|
|
21496
|
+
issues.push({ at: `${at}.path`, message: "/setto belongs to the editor" });
|
|
21497
|
+
} else if (paths.has(path2)) issues.push({ at: `${at}.path`, message: `duplicate path "${path2}"` });
|
|
21498
|
+
paths.add(path2);
|
|
21499
|
+
if (!titleKey) issues.push({ at: `${at}.titleKey`, message: "required" });
|
|
21500
|
+
const sections = [];
|
|
21501
|
+
const rawSections = Array.isArray(raw.sections) ? raw.sections : null;
|
|
21502
|
+
if (!rawSections) issues.push({ at: `${at}.sections`, message: "must be a list" });
|
|
21503
|
+
(rawSections ?? []).forEach((s, j) => {
|
|
21504
|
+
const sat = `${at}.sections[${j}]`;
|
|
21505
|
+
if (!isRecord$1(s)) {
|
|
21506
|
+
issues.push({ at: sat, message: "not an object" });
|
|
21507
|
+
return;
|
|
21508
|
+
}
|
|
21509
|
+
const sid = String(s.id ?? "");
|
|
21510
|
+
if (!ID.test(sid)) issues.push({ at: `${sat}.id`, message: `invalid id "${sid}"` });
|
|
21511
|
+
else if (sectionIds.has(sid)) issues.push({ at: `${sat}.id`, message: `duplicate section "${sid}"` });
|
|
21512
|
+
sectionIds.add(sid);
|
|
21513
|
+
const type = s.type;
|
|
21514
|
+
if (type !== void 0 && !BUILTIN_SECTION_TYPES.includes(type)) {
|
|
21515
|
+
issues.push({ at: `${sat}.type`, message: `unknown section type "${String(type)}"` });
|
|
21516
|
+
}
|
|
21517
|
+
sections.push({
|
|
21518
|
+
id: sid,
|
|
21519
|
+
...type !== void 0 ? { type } : {},
|
|
21520
|
+
...s.hidden === true ? { hidden: true } : {},
|
|
21521
|
+
...typeof s.label === "string" && s.label.trim() ? { label: s.label.trim() } : {}
|
|
21522
|
+
});
|
|
21523
|
+
});
|
|
21524
|
+
pages.push({
|
|
21525
|
+
id,
|
|
21526
|
+
path: path2,
|
|
21527
|
+
titleKey,
|
|
21528
|
+
...raw.hidden === true ? { hidden: true } : {},
|
|
21529
|
+
sections
|
|
21530
|
+
});
|
|
21531
|
+
});
|
|
21532
|
+
const menu = parseMenu(input.menu, "menu", issues);
|
|
21533
|
+
const footer2 = parseMenu(input.footer, "footer", issues);
|
|
21534
|
+
if (issues.length > 0) return { ok: false, issues };
|
|
21535
|
+
return { ok: true, manifest: { version: PAGES_MANIFEST_VERSION, pages, menu, footer: footer2 } };
|
|
21536
|
+
}
|
|
21537
|
+
function parseMenu(raw, at, issues) {
|
|
21538
|
+
if (raw === void 0) return [];
|
|
21539
|
+
if (!Array.isArray(raw)) {
|
|
21540
|
+
issues.push({ at, message: "must be a list" });
|
|
21541
|
+
return [];
|
|
21542
|
+
}
|
|
21543
|
+
const ids = /* @__PURE__ */ new Set();
|
|
21544
|
+
const items = [];
|
|
21545
|
+
raw.forEach((item, i) => {
|
|
21546
|
+
const iat = `${at}[${i}]`;
|
|
21547
|
+
if (!isRecord$1(item)) {
|
|
21548
|
+
issues.push({ at: iat, message: "not an object" });
|
|
21549
|
+
return;
|
|
21550
|
+
}
|
|
21551
|
+
const id = String(item.id ?? "");
|
|
21552
|
+
const href = String(item.href ?? "");
|
|
21553
|
+
const labelKey = String(item.labelKey ?? "");
|
|
21554
|
+
if (!ID.test(id)) issues.push({ at: `${iat}.id`, message: `invalid id "${id}"` });
|
|
21555
|
+
else if (ids.has(id)) issues.push({ at: `${iat}.id`, message: `duplicate item "${id}"` });
|
|
21556
|
+
ids.add(id);
|
|
21557
|
+
if (!isSafeHref(href)) issues.push({ at: `${iat}.href`, message: `invalid link "${href}"` });
|
|
21558
|
+
if (!labelKey) issues.push({ at: `${iat}.labelKey`, message: "required" });
|
|
21559
|
+
items.push({ id, href, labelKey, ...item.hidden === true ? { hidden: true } : {} });
|
|
21560
|
+
});
|
|
21561
|
+
return items;
|
|
21562
|
+
}
|
|
21563
|
+
function isSafeHref(href) {
|
|
21564
|
+
if (!href) return false;
|
|
21565
|
+
if (href.startsWith("/") && !href.startsWith("//")) return true;
|
|
21566
|
+
if (href.startsWith("#")) return true;
|
|
21567
|
+
return /^(https?:\/\/|mailto:|tel:)/i.test(href);
|
|
21568
|
+
}
|
|
21569
|
+
function normalisePath(path2) {
|
|
21570
|
+
const bare = (path2.split(/[?#]/)[0] ?? "").replace(/\/+$/, "");
|
|
21571
|
+
return bare === "" ? "/" : bare.toLowerCase();
|
|
21572
|
+
}
|
|
21573
|
+
function pageForPath(manifest, path2) {
|
|
21574
|
+
const wanted = normalisePath(path2);
|
|
21575
|
+
return manifest.pages.find((p) => p.path === wanted) ?? null;
|
|
21576
|
+
}
|
|
21577
|
+
function pageById(manifest, id) {
|
|
21578
|
+
return manifest.pages.find((p) => p.id === id) ?? null;
|
|
21579
|
+
}
|
|
21580
|
+
function findSection(manifest, sectionId) {
|
|
21581
|
+
for (const page of manifest.pages) {
|
|
21582
|
+
const index2 = page.sections.findIndex((s) => s.id === sectionId);
|
|
21583
|
+
if (index2 >= 0) return { page, index: index2, section: page.sections[index2] };
|
|
21584
|
+
}
|
|
21585
|
+
return null;
|
|
21586
|
+
}
|
|
21587
|
+
function visibleMenu(manifest, list2) {
|
|
21588
|
+
return manifest[list2].filter((item) => !item.hidden && !leadsNowhere(manifest, item.href));
|
|
21589
|
+
}
|
|
21590
|
+
function leadsNowhere(manifest, href) {
|
|
21591
|
+
if (!href.startsWith("/") && !href.startsWith("#")) return false;
|
|
21592
|
+
const [pathPart, fragment] = href.split("#");
|
|
21593
|
+
const page = pageForPath(manifest, pathPart || "/");
|
|
21594
|
+
if (pathPart && !page) return false;
|
|
21595
|
+
if (page?.hidden) return true;
|
|
21596
|
+
if (!fragment) return false;
|
|
21597
|
+
const target = findSection(manifest, fragment);
|
|
21598
|
+
return target ? Boolean(target.section.hidden || target.page.hidden) : false;
|
|
21599
|
+
}
|
|
21600
|
+
class ManifestError extends Error {
|
|
21601
|
+
constructor(code2, message) {
|
|
21602
|
+
super(message);
|
|
21603
|
+
this.code = code2;
|
|
21604
|
+
this.name = "ManifestError";
|
|
21605
|
+
}
|
|
21606
|
+
code;
|
|
21607
|
+
}
|
|
21608
|
+
function clone(manifest) {
|
|
21609
|
+
return JSON.parse(JSON.stringify(manifest));
|
|
21610
|
+
}
|
|
21611
|
+
function mustPage(manifest, pageId) {
|
|
21612
|
+
const page = pageById(manifest, pageId);
|
|
21613
|
+
if (!page) throw new ManifestError("page_not_found", `no page "${pageId}"`);
|
|
21614
|
+
return page;
|
|
21615
|
+
}
|
|
21616
|
+
function moveSection(manifest, sectionId, delta) {
|
|
21617
|
+
const next = clone(manifest);
|
|
21618
|
+
const found = findSection(next, sectionId);
|
|
21619
|
+
if (!found) throw new ManifestError("section_not_found", `no section "${sectionId}"`);
|
|
21620
|
+
const { page, index: index2 } = found;
|
|
21621
|
+
let target = index2 + delta;
|
|
21622
|
+
while (target >= 0 && target < page.sections.length && page.sections[target].hidden) target += delta;
|
|
21623
|
+
if (target < 0 || target >= page.sections.length) return manifest;
|
|
21624
|
+
const [moved] = page.sections.splice(index2, 1);
|
|
21625
|
+
page.sections.splice(target, 0, moved);
|
|
21626
|
+
return next;
|
|
21627
|
+
}
|
|
21628
|
+
function placeSection(manifest, sectionId, index2) {
|
|
21629
|
+
const next = clone(manifest);
|
|
21630
|
+
const found = findSection(next, sectionId);
|
|
21631
|
+
if (!found) throw new ManifestError("section_not_found", `no section "${sectionId}"`);
|
|
21632
|
+
const [moved] = found.page.sections.splice(found.index, 1);
|
|
21633
|
+
const at = Math.max(0, Math.min(index2, found.page.sections.length));
|
|
21634
|
+
found.page.sections.splice(at, 0, moved);
|
|
21635
|
+
return next;
|
|
21636
|
+
}
|
|
21637
|
+
function setSectionHidden(manifest, sectionId, hidden) {
|
|
21638
|
+
const next = clone(manifest);
|
|
21639
|
+
const found = findSection(next, sectionId);
|
|
21640
|
+
if (!found) throw new ManifestError("section_not_found", `no section "${sectionId}"`);
|
|
21641
|
+
if (hidden) found.section.hidden = true;
|
|
21642
|
+
else delete found.section.hidden;
|
|
21643
|
+
return next;
|
|
21644
|
+
}
|
|
21645
|
+
function addSection(manifest, pageId, section, index2) {
|
|
21646
|
+
if (!ID.test(section.id)) throw new ManifestError("invalid", `invalid section id "${section.id}"`);
|
|
21647
|
+
if (findSection(manifest, section.id)) {
|
|
21648
|
+
throw new ManifestError("duplicate_id", `section "${section.id}" already exists`);
|
|
21649
|
+
}
|
|
21650
|
+
const next = clone(manifest);
|
|
21651
|
+
const page = mustPage(next, pageId);
|
|
21652
|
+
const at = index2 === void 0 ? page.sections.length : Math.max(0, Math.min(index2, page.sections.length));
|
|
21653
|
+
page.sections.splice(at, 0, { ...section });
|
|
21654
|
+
return next;
|
|
21655
|
+
}
|
|
21656
|
+
function addPage(manifest, page) {
|
|
21657
|
+
const path2 = normalisePath(page.path);
|
|
21658
|
+
if (!ID.test(page.id) || !PATH.test(path2) || path2 === "/setto" || path2.startsWith("/setto/")) {
|
|
21659
|
+
throw new ManifestError("invalid", `invalid page "${page.id}" at "${page.path}"`);
|
|
21660
|
+
}
|
|
21661
|
+
if (pageById(manifest, page.id)) throw new ManifestError("duplicate_id", `page "${page.id}" already exists`);
|
|
21662
|
+
if (pageForPath(manifest, path2)) throw new ManifestError("duplicate_path", `a page already lives at ${path2}`);
|
|
21663
|
+
let next = clone(manifest);
|
|
21664
|
+
next.pages.push({ id: page.id, path: path2, titleKey: page.titleKey, sections: [] });
|
|
21665
|
+
for (const section of page.sections) next = addSection(next, page.id, section);
|
|
21666
|
+
return next;
|
|
21667
|
+
}
|
|
21668
|
+
function setPageHidden(manifest, pageId, hidden) {
|
|
21669
|
+
const next = clone(manifest);
|
|
21670
|
+
const page = mustPage(next, pageId);
|
|
21671
|
+
if (page.path === "/" && hidden) throw new ManifestError("invalid", "the front page cannot be hidden");
|
|
21672
|
+
if (hidden) page.hidden = true;
|
|
21673
|
+
else delete page.hidden;
|
|
21674
|
+
return next;
|
|
21675
|
+
}
|
|
21676
|
+
function upsertMenuItem(manifest, list2, item, index2) {
|
|
21677
|
+
if (!ID.test(item.id) || !isSafeHref(item.href) || !item.labelKey) {
|
|
21678
|
+
throw new ManifestError("invalid", `invalid link "${item.id}"`);
|
|
21679
|
+
}
|
|
21680
|
+
const next = clone(manifest);
|
|
21681
|
+
const items = next[list2];
|
|
21682
|
+
const existing = items.findIndex((i) => i.id === item.id);
|
|
21683
|
+
const clean = { id: item.id, href: item.href, labelKey: item.labelKey };
|
|
21684
|
+
if (item.hidden) clean.hidden = true;
|
|
21685
|
+
if (existing >= 0) items[existing] = clean;
|
|
21686
|
+
else items.splice(index2 === void 0 ? items.length : Math.max(0, Math.min(index2, items.length)), 0, clean);
|
|
21687
|
+
return next;
|
|
21688
|
+
}
|
|
21689
|
+
function moveMenuItem(manifest, list2, itemId, delta) {
|
|
21690
|
+
const next = clone(manifest);
|
|
21691
|
+
const items = next[list2];
|
|
21692
|
+
const index2 = items.findIndex((i) => i.id === itemId);
|
|
21693
|
+
if (index2 < 0) throw new ManifestError("item_not_found", `no link "${itemId}" in ${list2}`);
|
|
21694
|
+
const target = index2 + delta;
|
|
21695
|
+
if (target < 0 || target >= items.length) return manifest;
|
|
21696
|
+
const [moved] = items.splice(index2, 1);
|
|
21697
|
+
items.splice(target, 0, moved);
|
|
21698
|
+
return next;
|
|
21699
|
+
}
|
|
21700
|
+
function setMenuItemHidden(manifest, list2, itemId, hidden) {
|
|
21701
|
+
const next = clone(manifest);
|
|
21702
|
+
const item = next[list2].find((i) => i.id === itemId);
|
|
21703
|
+
if (!item) throw new ManifestError("item_not_found", `no link "${itemId}" in ${list2}`);
|
|
21704
|
+
if (hidden) item.hidden = true;
|
|
21705
|
+
else delete item.hidden;
|
|
21706
|
+
return next;
|
|
21707
|
+
}
|
|
21708
|
+
function applyOp(manifest, op) {
|
|
21709
|
+
switch (op.op) {
|
|
21710
|
+
case "move_section":
|
|
21711
|
+
return moveSection(manifest, op.sectionId, op.delta);
|
|
21712
|
+
case "place_section":
|
|
21713
|
+
return placeSection(manifest, op.sectionId, op.index);
|
|
21714
|
+
case "hide_section":
|
|
21715
|
+
return setSectionHidden(manifest, op.sectionId, true);
|
|
21716
|
+
case "show_section":
|
|
21717
|
+
return setSectionHidden(manifest, op.sectionId, false);
|
|
21718
|
+
case "add_section":
|
|
21719
|
+
return addSection(manifest, op.pageId, op.section, op.index);
|
|
21720
|
+
case "add_page":
|
|
21721
|
+
return addPage(manifest, op.page);
|
|
21722
|
+
case "hide_page":
|
|
21723
|
+
return setPageHidden(manifest, op.pageId, true);
|
|
21724
|
+
case "show_page":
|
|
21725
|
+
return setPageHidden(manifest, op.pageId, false);
|
|
21726
|
+
case "upsert_link":
|
|
21727
|
+
return upsertMenuItem(manifest, op.list, op.item, op.index);
|
|
21728
|
+
case "move_link":
|
|
21729
|
+
return moveMenuItem(manifest, op.list, op.itemId, op.delta);
|
|
21730
|
+
case "hide_link":
|
|
21731
|
+
return setMenuItemHidden(manifest, op.list, op.itemId, true);
|
|
21732
|
+
case "show_link":
|
|
21733
|
+
return setMenuItemHidden(manifest, op.list, op.itemId, false);
|
|
21734
|
+
}
|
|
21735
|
+
}
|
|
21736
|
+
function applyOps(manifest, ops) {
|
|
21737
|
+
let next = manifest;
|
|
21738
|
+
for (let i = 0; i < ops.length; i++) {
|
|
21739
|
+
try {
|
|
21740
|
+
next = applyOp(next, ops[i]);
|
|
21741
|
+
} catch (err) {
|
|
21742
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err), at: i };
|
|
21743
|
+
}
|
|
21744
|
+
}
|
|
21745
|
+
return { ok: true, manifest: next };
|
|
21746
|
+
}
|
|
21747
|
+
function builtinKeys(sectionId) {
|
|
21748
|
+
const base = `sections.${sectionId}`;
|
|
21749
|
+
return {
|
|
21750
|
+
heading: `${base}.heading`,
|
|
21751
|
+
intro: `${base}.intro`,
|
|
21752
|
+
items: `${base}.items`,
|
|
21753
|
+
itemHeading: (itemKey) => `${base}.items.${itemKey}.heading`,
|
|
21754
|
+
itemText: (itemKey) => `${base}.items.${itemKey}.text`
|
|
21755
|
+
};
|
|
21756
|
+
}
|
|
21757
|
+
function pageTitleKey(pageId) {
|
|
21758
|
+
return `pages.${pageId}.title`;
|
|
21759
|
+
}
|
|
21760
|
+
function serialiseManifest(manifest) {
|
|
21761
|
+
return JSON.stringify(manifest, null, 2) + "\n";
|
|
21762
|
+
}
|
|
21763
|
+
const EMPTY_PAGE_STORE_SNAPSHOT = {
|
|
21764
|
+
manifest: null,
|
|
21765
|
+
changes: 0,
|
|
21766
|
+
version: 0
|
|
21767
|
+
};
|
|
21768
|
+
class PageStore {
|
|
21769
|
+
data;
|
|
21770
|
+
baseline;
|
|
21771
|
+
version = 0;
|
|
21772
|
+
listeners = /* @__PURE__ */ new Set();
|
|
21773
|
+
cached;
|
|
21774
|
+
constructor(initial) {
|
|
21775
|
+
this.data = initial ? structuredClone(initial) : null;
|
|
21776
|
+
this.baseline = initial ? structuredClone(initial) : null;
|
|
21777
|
+
this.cached = { manifest: this.data, changes: 0, version: 0 };
|
|
21778
|
+
}
|
|
21779
|
+
snapshot() {
|
|
21780
|
+
return this.cached;
|
|
21781
|
+
}
|
|
21782
|
+
subscribe(listener) {
|
|
21783
|
+
this.listeners.add(listener);
|
|
21784
|
+
return () => this.listeners.delete(listener);
|
|
21785
|
+
}
|
|
21786
|
+
get manifest() {
|
|
21787
|
+
return this.data;
|
|
21788
|
+
}
|
|
21789
|
+
/** True once the site has a manifest to edit. */
|
|
21790
|
+
has() {
|
|
21791
|
+
return this.data !== null;
|
|
21792
|
+
}
|
|
21793
|
+
/**
|
|
21794
|
+
* Apply changes as drafts. All of them or none: a list that fails halfway
|
|
21795
|
+
* leaves the page exactly as it was and says which one failed.
|
|
21796
|
+
*/
|
|
21797
|
+
apply(ops) {
|
|
21798
|
+
if (!this.data) return { ok: false, error: "this site has no page manifest", at: 0 };
|
|
21799
|
+
const result = applyOps(this.data, ops);
|
|
21800
|
+
if (!result.ok) return result;
|
|
21801
|
+
this.data = result.manifest;
|
|
21802
|
+
this.bump();
|
|
21803
|
+
return { ok: true };
|
|
21804
|
+
}
|
|
21805
|
+
/**
|
|
21806
|
+
* Put the draft back to an earlier manifest, keeping the baseline.
|
|
21807
|
+
*
|
|
21808
|
+
* What «Angre» on one of Pixel's changes does: the panel remembers the
|
|
21809
|
+
* manifest from before the change and hands it back here. Checked like
|
|
21810
|
+
* anything else, because it has been through a round trip it did not have
|
|
21811
|
+
* to survive.
|
|
21812
|
+
*/
|
|
21813
|
+
restore(manifest) {
|
|
21814
|
+
const parsed = parseManifest(manifest);
|
|
21815
|
+
if (!parsed.ok) return { ok: false, error: parsed.issues[0]?.message ?? "invalid manifest" };
|
|
21816
|
+
this.data = parsed.manifest;
|
|
21817
|
+
this.bump();
|
|
21818
|
+
return { ok: true };
|
|
21819
|
+
}
|
|
21820
|
+
/**
|
|
21821
|
+
* Replace the baseline with what the repository holds, dropping drafts.
|
|
21822
|
+
*
|
|
21823
|
+
* Checked first: the file in the repository can be newer than this bundle,
|
|
21824
|
+
* or hand-edited, and a baseline this store cannot render must not replace
|
|
21825
|
+
* the one it can. Returns whether it was taken.
|
|
21826
|
+
*/
|
|
21827
|
+
loadBaseline(next) {
|
|
21828
|
+
const parsed = parseManifest(next);
|
|
21829
|
+
if (!parsed.ok) {
|
|
21830
|
+
console.warn("[setto] pages.json from the repository is not valid and was ignored", parsed.issues);
|
|
21831
|
+
return false;
|
|
21832
|
+
}
|
|
21833
|
+
this.data = parsed.manifest;
|
|
21834
|
+
this.baseline = structuredClone(parsed.manifest);
|
|
21835
|
+
this.bump();
|
|
21836
|
+
return true;
|
|
21837
|
+
}
|
|
21838
|
+
revertAll() {
|
|
21839
|
+
this.data = this.baseline ? structuredClone(this.baseline) : null;
|
|
21840
|
+
this.bump();
|
|
21841
|
+
}
|
|
21842
|
+
commit() {
|
|
21843
|
+
this.baseline = this.data ? structuredClone(this.data) : null;
|
|
21844
|
+
this.bump();
|
|
21845
|
+
}
|
|
21846
|
+
/** Number of changes, for the toolbar's count. Zero means nothing to publish. */
|
|
21847
|
+
size() {
|
|
21848
|
+
return this.cached.changes;
|
|
21849
|
+
}
|
|
21850
|
+
serialise() {
|
|
21851
|
+
return this.data ? serialiseManifest(this.data) : null;
|
|
21852
|
+
}
|
|
21853
|
+
bump() {
|
|
21854
|
+
this.version += 1;
|
|
21855
|
+
this.cached = {
|
|
21856
|
+
manifest: this.data,
|
|
21857
|
+
changes: countChanges(this.baseline, this.data),
|
|
21858
|
+
version: this.version
|
|
21859
|
+
};
|
|
21860
|
+
for (const fn of this.listeners) fn(this.cached);
|
|
21861
|
+
}
|
|
21862
|
+
}
|
|
21863
|
+
function countChanges(before, after) {
|
|
21864
|
+
if (!before || !after) return before === after ? 0 : 1;
|
|
21865
|
+
let changes = 0;
|
|
21866
|
+
const same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
|
21867
|
+
const beforePages = new Map(before.pages.map((p) => [p.id, p]));
|
|
21868
|
+
for (const page of after.pages) {
|
|
21869
|
+
const was = beforePages.get(page.id);
|
|
21870
|
+
if (!was) {
|
|
21871
|
+
changes += 1;
|
|
21872
|
+
continue;
|
|
21873
|
+
}
|
|
21874
|
+
if (was.path !== page.path || was.titleKey !== page.titleKey || Boolean(was.hidden) !== Boolean(page.hidden)) {
|
|
21875
|
+
changes += 1;
|
|
21876
|
+
}
|
|
21877
|
+
const wasSections = new Map(was.sections.map((s, i) => [s.id, { s, i }]));
|
|
21878
|
+
page.sections.forEach((s, i) => {
|
|
21879
|
+
const prior = wasSections.get(s.id);
|
|
21880
|
+
if (!prior || prior.i !== i || !same(prior.s, s)) changes += 1;
|
|
21881
|
+
});
|
|
21882
|
+
if (was.sections.length > page.sections.length) changes += was.sections.length - page.sections.length;
|
|
21883
|
+
}
|
|
21884
|
+
if (before.pages.length > after.pages.length) changes += before.pages.length - after.pages.length;
|
|
21885
|
+
for (const list2 of ["menu", "footer"]) {
|
|
21886
|
+
const wasItems = new Map(before[list2].map((item, i) => [item.id, { item, i }]));
|
|
21887
|
+
after[list2].forEach((item, i) => {
|
|
21888
|
+
const prior = wasItems.get(item.id);
|
|
21889
|
+
if (!prior || prior.i !== i || !same(prior.item, item)) changes += 1;
|
|
21890
|
+
});
|
|
21891
|
+
if (before[list2].length > after[list2].length) changes += before[list2].length - after[list2].length;
|
|
21892
|
+
}
|
|
21893
|
+
return changes;
|
|
21894
|
+
}
|
|
21895
|
+
const cache = /* @__PURE__ */ new WeakMap();
|
|
21896
|
+
function bundledManifest(raw) {
|
|
21897
|
+
if (!raw || typeof raw !== "object") return null;
|
|
21898
|
+
const hit = cache.get(raw);
|
|
21899
|
+
if (hit !== void 0) return hit;
|
|
21900
|
+
const parsed = parseManifest(raw);
|
|
21901
|
+
if (!parsed.ok) {
|
|
21902
|
+
console.warn("[setto] pages.json is not valid and was ignored", parsed.issues);
|
|
21903
|
+
}
|
|
21904
|
+
const manifest = parsed.ok ? parsed.manifest : null;
|
|
21905
|
+
cache.set(raw, manifest);
|
|
21906
|
+
return manifest;
|
|
21907
|
+
}
|
|
21638
21908
|
const EMPTY$1 = {
|
|
21639
21909
|
sites: null,
|
|
21640
21910
|
currentSite: null,
|
|
@@ -30074,7 +30344,7 @@ function requireExtend() {
|
|
|
30074
30344
|
return obj[name2];
|
|
30075
30345
|
};
|
|
30076
30346
|
extend$1 = function extend2() {
|
|
30077
|
-
var options, name2, src, copy, copyIsArray,
|
|
30347
|
+
var options, name2, src, copy, copyIsArray, clone2;
|
|
30078
30348
|
var target = arguments[0];
|
|
30079
30349
|
var i = 1;
|
|
30080
30350
|
var length = arguments.length;
|
|
@@ -30097,11 +30367,11 @@ function requireExtend() {
|
|
|
30097
30367
|
if (deep && copy && (isPlainObject2(copy) || (copyIsArray = isArray(copy)))) {
|
|
30098
30368
|
if (copyIsArray) {
|
|
30099
30369
|
copyIsArray = false;
|
|
30100
|
-
|
|
30370
|
+
clone2 = src && isArray(src) ? src : [];
|
|
30101
30371
|
} else {
|
|
30102
|
-
|
|
30372
|
+
clone2 = src && isPlainObject2(src) ? src : {};
|
|
30103
30373
|
}
|
|
30104
|
-
setProperty(target, { name: name2, newValue: extend2(deep,
|
|
30374
|
+
setProperty(target, { name: name2, newValue: extend2(deep, clone2, copy) });
|
|
30105
30375
|
} else if (typeof copy !== "undefined") {
|
|
30106
30376
|
setProperty(target, { name: name2, newValue: copy });
|
|
30107
30377
|
}
|
|
@@ -36317,14 +36587,16 @@ function SettoProvider({ config, children }) {
|
|
|
36317
36587
|
const storeRef = useRef(null);
|
|
36318
36588
|
const themeStoreRef = useRef(null);
|
|
36319
36589
|
const assetStoreRef = useRef(null);
|
|
36590
|
+
const pageStoreRef = useRef(null);
|
|
36320
36591
|
const [storeReady, setStoreReady] = useState(false);
|
|
36321
36592
|
useEffect(() => {
|
|
36322
36593
|
if (!i18n) return;
|
|
36323
36594
|
storeRef.current = new I18nStore(i18n);
|
|
36324
36595
|
themeStoreRef.current = new ThemeStore(config.theme ?? {});
|
|
36325
36596
|
assetStoreRef.current = new AssetStore();
|
|
36597
|
+
pageStoreRef.current = new PageStore(bundledManifest(config.pages));
|
|
36326
36598
|
setStoreReady(true);
|
|
36327
|
-
}, [i18n, config.theme]);
|
|
36599
|
+
}, [i18n, config.theme, config.pages]);
|
|
36328
36600
|
const editMode = !!session && access.currentSite?.status === "live" && !onAdminRoute && !authLoading && !access.loading;
|
|
36329
36601
|
const [selectedSectionId, setSelectedSectionId] = useState(null);
|
|
36330
36602
|
const selectSection = useCallback((id) => setSelectedSectionId(id), []);
|
|
@@ -36348,6 +36620,7 @@ function SettoProvider({ config, children }) {
|
|
|
36348
36620
|
store: storeReady ? storeRef.current : null,
|
|
36349
36621
|
themeStore: storeReady ? themeStoreRef.current : null,
|
|
36350
36622
|
assetStore: storeReady ? assetStoreRef.current : null,
|
|
36623
|
+
pageStore: storeReady ? pageStoreRef.current : null,
|
|
36351
36624
|
selectedSectionId,
|
|
36352
36625
|
selectSection,
|
|
36353
36626
|
guestEdit,
|
|
@@ -36368,6 +36641,219 @@ function useSetto() {
|
|
|
36368
36641
|
}
|
|
36369
36642
|
return ctx;
|
|
36370
36643
|
}
|
|
36644
|
+
const INLINE_MARKER = "data-setto-languages-inline";
|
|
36645
|
+
function SettoLanguages({ languages: given, inline = false }) {
|
|
36646
|
+
const { i18n } = useTranslation();
|
|
36647
|
+
const setto = useContext(SettoContext);
|
|
36648
|
+
const configured = setto?.config.languages;
|
|
36649
|
+
const languages = given ?? siteLanguages(configured, configured?.[0]);
|
|
36650
|
+
const [open, setOpen] = useState(false);
|
|
36651
|
+
const [hover, setHover] = useState(false);
|
|
36652
|
+
const [surface, setSurface] = useState(null);
|
|
36653
|
+
const ref = useRef(null);
|
|
36654
|
+
useEffect(() => {
|
|
36655
|
+
if (!inline) return;
|
|
36656
|
+
const root2 = document.documentElement;
|
|
36657
|
+
const count = Number(root2.getAttribute(INLINE_MARKER) ?? "0") + 1;
|
|
36658
|
+
root2.setAttribute(INLINE_MARKER, String(count));
|
|
36659
|
+
return () => {
|
|
36660
|
+
const left = Number(root2.getAttribute(INLINE_MARKER) ?? "1") - 1;
|
|
36661
|
+
if (left > 0) root2.setAttribute(INLINE_MARKER, String(left));
|
|
36662
|
+
else root2.removeAttribute(INLINE_MARKER);
|
|
36663
|
+
};
|
|
36664
|
+
}, [inline]);
|
|
36665
|
+
useEffect(() => {
|
|
36666
|
+
if (!open) return;
|
|
36667
|
+
const onDown = (e) => {
|
|
36668
|
+
if (!ref.current?.contains(e.target)) setOpen(false);
|
|
36669
|
+
};
|
|
36670
|
+
const onKey = (e) => {
|
|
36671
|
+
if (e.key === "Escape") setOpen(false);
|
|
36672
|
+
};
|
|
36673
|
+
document.addEventListener("mousedown", onDown);
|
|
36674
|
+
document.addEventListener("keydown", onKey);
|
|
36675
|
+
return () => {
|
|
36676
|
+
document.removeEventListener("mousedown", onDown);
|
|
36677
|
+
document.removeEventListener("keydown", onKey);
|
|
36678
|
+
};
|
|
36679
|
+
}, [open]);
|
|
36680
|
+
if (languages.length < 2) return null;
|
|
36681
|
+
const current = i18n.language;
|
|
36682
|
+
const toggle = () => {
|
|
36683
|
+
if (!open && inline && ref.current) setSurface(surroundings(ref.current));
|
|
36684
|
+
setOpen((o) => !o);
|
|
36685
|
+
};
|
|
36686
|
+
const choose = (tag) => {
|
|
36687
|
+
setOpen(false);
|
|
36688
|
+
if (tag === current) return;
|
|
36689
|
+
storeLanguage(tag);
|
|
36690
|
+
const target = pathForLanguage(window.location.pathname, tag, languages);
|
|
36691
|
+
window.location.assign(target + window.location.search + window.location.hash);
|
|
36692
|
+
};
|
|
36693
|
+
return /* @__PURE__ */ jsxs("div", { ref, style: inline ? inlineWrapStyle : floatingWrapStyle, children: [
|
|
36694
|
+
/* @__PURE__ */ jsxs(
|
|
36695
|
+
"button",
|
|
36696
|
+
{
|
|
36697
|
+
type: "button",
|
|
36698
|
+
onClick: toggle,
|
|
36699
|
+
onMouseEnter: () => setHover(true),
|
|
36700
|
+
onMouseLeave: () => setHover(false),
|
|
36701
|
+
"aria-expanded": open,
|
|
36702
|
+
"aria-haspopup": "listbox",
|
|
36703
|
+
"aria-label": `Språk: ${languageName(current)}`,
|
|
36704
|
+
title: inline ? languageName(current) : void 0,
|
|
36705
|
+
style: inline ? { ...inlineTriggerStyle, opacity: hover || open ? 1 : inlineTriggerStyle.opacity } : floatingTriggerStyle,
|
|
36706
|
+
children: [
|
|
36707
|
+
/* @__PURE__ */ jsx(GlobeIcon, {}),
|
|
36708
|
+
inline ? shortCode(current) : languageName(current)
|
|
36709
|
+
]
|
|
36710
|
+
}
|
|
36711
|
+
),
|
|
36712
|
+
open ? /* @__PURE__ */ jsx(
|
|
36713
|
+
"ul",
|
|
36714
|
+
{
|
|
36715
|
+
role: "listbox",
|
|
36716
|
+
style: inline ? { ...inlineListStyle, ...surface ? { background: surface.background, color: surface.color } : {} } : floatingListStyle,
|
|
36717
|
+
children: languages.map((tag) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
|
|
36718
|
+
"button",
|
|
36719
|
+
{
|
|
36720
|
+
type: "button",
|
|
36721
|
+
role: "option",
|
|
36722
|
+
"aria-selected": tag === current,
|
|
36723
|
+
onClick: () => choose(tag),
|
|
36724
|
+
style: {
|
|
36725
|
+
...optionStyle,
|
|
36726
|
+
...inline ? { color: "inherit" } : {},
|
|
36727
|
+
fontWeight: tag === current ? 600 : 400
|
|
36728
|
+
},
|
|
36729
|
+
children: languageName(tag)
|
|
36730
|
+
}
|
|
36731
|
+
) }, tag))
|
|
36732
|
+
}
|
|
36733
|
+
) : null
|
|
36734
|
+
] });
|
|
36735
|
+
}
|
|
36736
|
+
function AutoLanguageSwitcher({ languages }) {
|
|
36737
|
+
const [inlineElsewhere, setInlineElsewhere] = useState(false);
|
|
36738
|
+
useEffect(() => {
|
|
36739
|
+
const check = () => setInlineElsewhere(document.documentElement.hasAttribute(INLINE_MARKER));
|
|
36740
|
+
check();
|
|
36741
|
+
const observer = new MutationObserver(check);
|
|
36742
|
+
observer.observe(document.documentElement, {
|
|
36743
|
+
attributes: true,
|
|
36744
|
+
attributeFilter: [INLINE_MARKER]
|
|
36745
|
+
});
|
|
36746
|
+
return () => observer.disconnect();
|
|
36747
|
+
}, []);
|
|
36748
|
+
if (inlineElsewhere || languages.length < 2) return null;
|
|
36749
|
+
return /* @__PURE__ */ jsx(SettoLanguages, { languages });
|
|
36750
|
+
}
|
|
36751
|
+
function shortCode(tag) {
|
|
36752
|
+
return tag.toUpperCase();
|
|
36753
|
+
}
|
|
36754
|
+
function surroundings(el) {
|
|
36755
|
+
const color2 = getComputedStyle(el).color;
|
|
36756
|
+
for (let node2 = el; node2; node2 = node2.parentElement) {
|
|
36757
|
+
const background = getComputedStyle(node2).backgroundColor;
|
|
36758
|
+
if (background && !/^(transparent|rgba\(.*,\s*0\))$/.test(background)) return { background, color: color2 };
|
|
36759
|
+
}
|
|
36760
|
+
return { background: "#ffffff", color: "#1a1a1a" };
|
|
36761
|
+
}
|
|
36762
|
+
function GlobeIcon() {
|
|
36763
|
+
return /* @__PURE__ */ jsxs(
|
|
36764
|
+
"svg",
|
|
36765
|
+
{
|
|
36766
|
+
width: "15",
|
|
36767
|
+
height: "15",
|
|
36768
|
+
viewBox: "0 0 24 24",
|
|
36769
|
+
fill: "none",
|
|
36770
|
+
stroke: "currentColor",
|
|
36771
|
+
strokeWidth: "1.8",
|
|
36772
|
+
strokeLinecap: "round",
|
|
36773
|
+
strokeLinejoin: "round",
|
|
36774
|
+
"aria-hidden": "true",
|
|
36775
|
+
children: [
|
|
36776
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "9" }),
|
|
36777
|
+
/* @__PURE__ */ jsx("path", { d: "M3 12h18M12 3c2.4 2.6 3.6 5.6 3.6 9s-1.2 6.4-3.6 9c-2.4-2.6-3.6-5.6-3.6-9S9.6 5.6 12 3z" })
|
|
36778
|
+
]
|
|
36779
|
+
}
|
|
36780
|
+
);
|
|
36781
|
+
}
|
|
36782
|
+
const floatingWrapStyle = {
|
|
36783
|
+
position: "fixed",
|
|
36784
|
+
left: "max(16px, env(safe-area-inset-left))",
|
|
36785
|
+
bottom: "max(16px, env(safe-area-inset-bottom))",
|
|
36786
|
+
zIndex: 2147482e3,
|
|
36787
|
+
fontFamily: 'system-ui, -apple-system, "Segoe UI", sans-serif'
|
|
36788
|
+
};
|
|
36789
|
+
const inlineWrapStyle = {
|
|
36790
|
+
position: "relative",
|
|
36791
|
+
display: "inline-block",
|
|
36792
|
+
fontFamily: "inherit"
|
|
36793
|
+
};
|
|
36794
|
+
const triggerBase = {
|
|
36795
|
+
display: "inline-flex",
|
|
36796
|
+
alignItems: "center",
|
|
36797
|
+
gap: 7,
|
|
36798
|
+
minHeight: 40,
|
|
36799
|
+
fontSize: 13.5,
|
|
36800
|
+
fontWeight: 500,
|
|
36801
|
+
lineHeight: 1,
|
|
36802
|
+
cursor: "pointer"
|
|
36803
|
+
};
|
|
36804
|
+
const floatingTriggerStyle = {
|
|
36805
|
+
...triggerBase,
|
|
36806
|
+
padding: "8px 14px",
|
|
36807
|
+
borderRadius: 999,
|
|
36808
|
+
border: "1px solid rgba(127, 127, 127, 0.32)",
|
|
36809
|
+
background: "rgba(255, 255, 255, 0.82)",
|
|
36810
|
+
backdropFilter: "blur(8px)",
|
|
36811
|
+
WebkitBackdropFilter: "blur(8px)",
|
|
36812
|
+
color: "#1a1a1a",
|
|
36813
|
+
boxShadow: "0 4px 14px rgba(0, 0, 0, 0.12)"
|
|
36814
|
+
};
|
|
36815
|
+
const inlineTriggerStyle = {
|
|
36816
|
+
...triggerBase,
|
|
36817
|
+
gap: 5,
|
|
36818
|
+
minHeight: 36,
|
|
36819
|
+
padding: "6px 6px",
|
|
36820
|
+
border: 0,
|
|
36821
|
+
borderRadius: 8,
|
|
36822
|
+
background: "transparent",
|
|
36823
|
+
color: "inherit",
|
|
36824
|
+
fontSize: 12,
|
|
36825
|
+
fontWeight: 500,
|
|
36826
|
+
letterSpacing: "0.06em",
|
|
36827
|
+
opacity: 0.7,
|
|
36828
|
+
transition: "opacity 160ms ease"
|
|
36829
|
+
};
|
|
36830
|
+
const listBase = {
|
|
36831
|
+
position: "absolute",
|
|
36832
|
+
margin: 0,
|
|
36833
|
+
padding: 4,
|
|
36834
|
+
listStyle: "none",
|
|
36835
|
+
minWidth: 150,
|
|
36836
|
+
borderRadius: 12,
|
|
36837
|
+
border: "1px solid rgba(127, 127, 127, 0.24)",
|
|
36838
|
+
background: "#ffffff",
|
|
36839
|
+
boxShadow: "0 12px 32px rgba(0, 0, 0, 0.18)",
|
|
36840
|
+
zIndex: 1
|
|
36841
|
+
};
|
|
36842
|
+
const floatingListStyle = { ...listBase, left: 0, bottom: "calc(100% + 8px)" };
|
|
36843
|
+
const inlineListStyle = { ...listBase, right: 0, top: "calc(100% + 8px)" };
|
|
36844
|
+
const optionStyle = {
|
|
36845
|
+
display: "block",
|
|
36846
|
+
width: "100%",
|
|
36847
|
+
minHeight: 40,
|
|
36848
|
+
padding: "9px 12px",
|
|
36849
|
+
border: 0,
|
|
36850
|
+
borderRadius: 8,
|
|
36851
|
+
background: "transparent",
|
|
36852
|
+
color: "#1a1a1a",
|
|
36853
|
+
fontSize: 13.5,
|
|
36854
|
+
textAlign: "left",
|
|
36855
|
+
cursor: "pointer"
|
|
36856
|
+
};
|
|
36371
36857
|
function GuestEditProvider({ children }) {
|
|
36372
36858
|
const { guestEdit, themeStore } = useSetto();
|
|
36373
36859
|
const SectionTools = useSettoChromeSlot("section-toolbar");
|
|
@@ -37023,6 +37509,8 @@ function SettoImage({ srcKey, alt, className, style, ...rest }) {
|
|
|
37023
37509
|
const fileRef = useRef(null);
|
|
37024
37510
|
const [hovered, setHovered] = useState(false);
|
|
37025
37511
|
const [uploading, setUploading] = useState(false);
|
|
37512
|
+
const imgRef = useRef(null);
|
|
37513
|
+
const Tools = useSettoChromeSlot("image-tools");
|
|
37026
37514
|
const [, force] = useState(0);
|
|
37027
37515
|
useEffect(() => {
|
|
37028
37516
|
if (!store) return;
|
|
@@ -37046,10 +37534,8 @@ function SettoImage({ srcKey, alt, className, style, ...rest }) {
|
|
|
37046
37534
|
e.preventDefault();
|
|
37047
37535
|
if (!uploading) fileRef.current?.click();
|
|
37048
37536
|
};
|
|
37049
|
-
const
|
|
37050
|
-
|
|
37051
|
-
e.target.value = "";
|
|
37052
|
-
if (!file || !assetStore || !store) return;
|
|
37537
|
+
const applyFile = async (file) => {
|
|
37538
|
+
if (!assetStore || !store) return;
|
|
37053
37539
|
setUploading(true);
|
|
37054
37540
|
try {
|
|
37055
37541
|
const originalPublicPath = store.original(srcKey, i18n.language);
|
|
@@ -37066,6 +37552,18 @@ function SettoImage({ srcKey, alt, className, style, ...rest }) {
|
|
|
37066
37552
|
setUploading(false);
|
|
37067
37553
|
}
|
|
37068
37554
|
};
|
|
37555
|
+
const handleFile = async (e) => {
|
|
37556
|
+
const file = e.target.files?.[0];
|
|
37557
|
+
e.target.value = "";
|
|
37558
|
+
if (file) await applyFile(file);
|
|
37559
|
+
};
|
|
37560
|
+
const revert2 = () => {
|
|
37561
|
+
if (!assetStore || !store) return;
|
|
37562
|
+
const original = assetStore.revert(srcKey);
|
|
37563
|
+
if (original !== null) {
|
|
37564
|
+
setSrcAllLanguages(store, i18n, srcKey, original);
|
|
37565
|
+
}
|
|
37566
|
+
};
|
|
37069
37567
|
const handleRevert = (e) => {
|
|
37070
37568
|
e.stopPropagation();
|
|
37071
37569
|
e.preventDefault();
|
|
@@ -37080,6 +37578,7 @@ function SettoImage({ srcKey, alt, className, style, ...rest }) {
|
|
|
37080
37578
|
"span",
|
|
37081
37579
|
{
|
|
37082
37580
|
"data-setto-image": true,
|
|
37581
|
+
"data-setto-src-key": srcKey,
|
|
37083
37582
|
"data-setto-ui": true,
|
|
37084
37583
|
onMouseEnter: () => setHovered(true),
|
|
37085
37584
|
onMouseLeave: () => setHovered(false),
|
|
@@ -37093,8 +37592,21 @@ function SettoImage({ srcKey, alt, className, style, ...rest }) {
|
|
|
37093
37592
|
borderRadius: 4
|
|
37094
37593
|
},
|
|
37095
37594
|
children: [
|
|
37096
|
-
/* @__PURE__ */ jsx("img", { src, alt, className, style, ...rest }),
|
|
37097
|
-
|
|
37595
|
+
/* @__PURE__ */ jsx("img", { ref: imgRef, src, alt, className, style, ...rest }),
|
|
37596
|
+
Tools ? /* @__PURE__ */ jsx(
|
|
37597
|
+
Tools,
|
|
37598
|
+
{
|
|
37599
|
+
srcKey,
|
|
37600
|
+
alt,
|
|
37601
|
+
hovered,
|
|
37602
|
+
hasDraft: Boolean(previewUrl),
|
|
37603
|
+
busy: uploading,
|
|
37604
|
+
imageEl: imgRef.current,
|
|
37605
|
+
applyFile,
|
|
37606
|
+
revert: revert2
|
|
37607
|
+
}
|
|
37608
|
+
) : null,
|
|
37609
|
+
previewUrl && !hovered && !Tools ? /* @__PURE__ */ jsx(
|
|
37098
37610
|
"span",
|
|
37099
37611
|
{
|
|
37100
37612
|
"aria-hidden": true,
|
|
@@ -37114,7 +37626,7 @@ function SettoImage({ srcKey, alt, className, style, ...rest }) {
|
|
|
37114
37626
|
children: "Nytt bilde"
|
|
37115
37627
|
}
|
|
37116
37628
|
) : null,
|
|
37117
|
-
hovered ? /* @__PURE__ */ jsxs(
|
|
37629
|
+
hovered && !Tools ? /* @__PURE__ */ jsxs(
|
|
37118
37630
|
"span",
|
|
37119
37631
|
{
|
|
37120
37632
|
"data-setto-ui": true,
|
|
@@ -38325,6 +38837,193 @@ const iframeStyle = {
|
|
|
38325
38837
|
height: "100%",
|
|
38326
38838
|
border: 0
|
|
38327
38839
|
};
|
|
38840
|
+
function usePageManifest() {
|
|
38841
|
+
const { pageStore, config } = useSetto();
|
|
38842
|
+
const snap = useSyncExternalStore(
|
|
38843
|
+
(cb) => pageStore?.subscribe(() => cb()) ?? (() => {
|
|
38844
|
+
}),
|
|
38845
|
+
() => pageStore?.snapshot() ?? EMPTY_PAGE_STORE_SNAPSHOT,
|
|
38846
|
+
() => pageStore?.snapshot() ?? EMPTY_PAGE_STORE_SNAPSHOT
|
|
38847
|
+
);
|
|
38848
|
+
return pageStore ? snap.manifest : bundledManifest(config.pages);
|
|
38849
|
+
}
|
|
38850
|
+
function useSettoMenu(list2 = "menu") {
|
|
38851
|
+
const manifest = usePageManifest();
|
|
38852
|
+
return manifest ? visibleMenu(manifest, list2) : null;
|
|
38853
|
+
}
|
|
38854
|
+
function SettoPage({ pageId, path: path2, sections = {}, builtins, fallback = null }) {
|
|
38855
|
+
const manifest = usePageManifest();
|
|
38856
|
+
const { editMode } = useSetto();
|
|
38857
|
+
if (!manifest) {
|
|
38858
|
+
if (!pageId && path2 !== void 0 && path2 !== "/" && path2 !== "") return /* @__PURE__ */ jsx(Fragment, { children: fallback });
|
|
38859
|
+
return /* @__PURE__ */ jsx(Fragment, { children: Object.entries(sections).map(([id, Component]) => /* @__PURE__ */ jsx(Component, {}, id)) });
|
|
38860
|
+
}
|
|
38861
|
+
const page = pageId ? pageById(manifest, pageId) : pageForPath(manifest, path2 ?? "/");
|
|
38862
|
+
if (!page) {
|
|
38863
|
+
if (pageId) {
|
|
38864
|
+
return /* @__PURE__ */ jsx(Fragment, { children: Object.entries(sections).map(([id, Component]) => /* @__PURE__ */ jsx(Component, {}, id)) });
|
|
38865
|
+
}
|
|
38866
|
+
return /* @__PURE__ */ jsx(Fragment, { children: fallback });
|
|
38867
|
+
}
|
|
38868
|
+
if (page.hidden && !editMode) return /* @__PURE__ */ jsx(Fragment, { children: fallback });
|
|
38869
|
+
return /* @__PURE__ */ jsx(PageBody, { page, sections, builtins, editMode, manifest });
|
|
38870
|
+
}
|
|
38871
|
+
function PageBody({
|
|
38872
|
+
page,
|
|
38873
|
+
sections,
|
|
38874
|
+
builtins,
|
|
38875
|
+
editMode,
|
|
38876
|
+
manifest
|
|
38877
|
+
}) {
|
|
38878
|
+
usePageTitle(page);
|
|
38879
|
+
const HiddenPageNote = useSettoChromeSlot("hidden-page");
|
|
38880
|
+
const listed = new Set(manifest.pages.flatMap((p) => p.sections.map((s) => s.id)));
|
|
38881
|
+
const unlisted = Object.keys(sections).filter((id) => !listed.has(id));
|
|
38882
|
+
const firstVisible = page.sections.find((s) => !s.hidden && (s.type || sections[s.id]))?.id;
|
|
38883
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
38884
|
+
page.hidden && editMode ? HiddenPageNote ? /* @__PURE__ */ jsx(HiddenPageNote, { pageId: page.id }) : /* @__PURE__ */ jsx(HiddenNote, { text: "Denne siden er skjult for besøkende." }) : null,
|
|
38885
|
+
page.sections.map((section) => /* @__PURE__ */ jsx(Fragment$1, { children: renderSection(section, sections, builtins, editMode, section.id === firstVisible) }, section.id)),
|
|
38886
|
+
unlisted.map((id) => {
|
|
38887
|
+
const Component = sections[id];
|
|
38888
|
+
return /* @__PURE__ */ jsx(Component, {}, id);
|
|
38889
|
+
})
|
|
38890
|
+
] });
|
|
38891
|
+
}
|
|
38892
|
+
function renderSection(section, sections, builtins, editMode, first) {
|
|
38893
|
+
if (section.hidden) {
|
|
38894
|
+
return editMode ? /* @__PURE__ */ jsx(HiddenSection, { section }) : null;
|
|
38895
|
+
}
|
|
38896
|
+
if (section.type) {
|
|
38897
|
+
const Builtin = builtins?.[section.type] ?? BUILTIN_RENDERERS[section.type];
|
|
38898
|
+
return /* @__PURE__ */ jsx(Builtin, { sectionId: section.id, first });
|
|
38899
|
+
}
|
|
38900
|
+
const Component = sections[section.id];
|
|
38901
|
+
if (!Component) {
|
|
38902
|
+
warnOnce(section.id);
|
|
38903
|
+
return null;
|
|
38904
|
+
}
|
|
38905
|
+
return /* @__PURE__ */ jsx(Component, {});
|
|
38906
|
+
}
|
|
38907
|
+
const warned = /* @__PURE__ */ new Set();
|
|
38908
|
+
function warnOnce(id) {
|
|
38909
|
+
if (warned.has(id)) return;
|
|
38910
|
+
warned.add(id);
|
|
38911
|
+
console.warn(`[setto] section "${id}" is in pages.json but the page does not provide it`);
|
|
38912
|
+
}
|
|
38913
|
+
function HiddenSection({ section }) {
|
|
38914
|
+
const { pageStore } = useSetto();
|
|
38915
|
+
const Offered = useSettoChromeSlot("hidden-section");
|
|
38916
|
+
const label = section.label ?? section.id;
|
|
38917
|
+
if (Offered) return /* @__PURE__ */ jsx(Offered, { sectionId: section.id, label });
|
|
38918
|
+
return /* @__PURE__ */ jsxs("div", { "data-setto-ui": true, style: hiddenBarStyle, children: [
|
|
38919
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
38920
|
+
"Skjult: ",
|
|
38921
|
+
label
|
|
38922
|
+
] }),
|
|
38923
|
+
pageStore ? /* @__PURE__ */ jsx(
|
|
38924
|
+
"button",
|
|
38925
|
+
{
|
|
38926
|
+
type: "button",
|
|
38927
|
+
"data-setto-ui": true,
|
|
38928
|
+
style: hiddenBtnStyle,
|
|
38929
|
+
onClick: () => pageStore.apply([{ op: "show_section", sectionId: section.id }]),
|
|
38930
|
+
children: "Vis"
|
|
38931
|
+
}
|
|
38932
|
+
) : null
|
|
38933
|
+
] });
|
|
38934
|
+
}
|
|
38935
|
+
function HiddenNote({ text: text2 }) {
|
|
38936
|
+
return /* @__PURE__ */ jsx("div", { "data-setto-ui": true, style: hiddenBarStyle, children: /* @__PURE__ */ jsx("span", { children: text2 }) });
|
|
38937
|
+
}
|
|
38938
|
+
function usePageTitle(page) {
|
|
38939
|
+
const { t } = useTranslation();
|
|
38940
|
+
const original = useRef(null);
|
|
38941
|
+
const title = page.path === "/" ? null : t(page.titleKey);
|
|
38942
|
+
useEffect(() => {
|
|
38943
|
+
if (!title || typeof document === "undefined") return;
|
|
38944
|
+
original.current ??= document.title;
|
|
38945
|
+
const base = original.current;
|
|
38946
|
+
document.title = base ? `${title} · ${base}` : title;
|
|
38947
|
+
return () => {
|
|
38948
|
+
document.title = base;
|
|
38949
|
+
};
|
|
38950
|
+
}, [title]);
|
|
38951
|
+
}
|
|
38952
|
+
function SettoTextSection({ sectionId, first }) {
|
|
38953
|
+
const keys2 = builtinKeys(sectionId);
|
|
38954
|
+
const { i18n } = useTranslation();
|
|
38955
|
+
const { store, editMode } = useSetto();
|
|
38956
|
+
const intro2 = store ? store.get(keys2.intro, i18n.language) : i18n.t(keys2.intro, { defaultValue: "" });
|
|
38957
|
+
const Heading = first ? "h1" : "h2";
|
|
38958
|
+
return /* @__PURE__ */ jsx(SettoSection, { sectionId, style: textSectionStyle, children: /* @__PURE__ */ jsxs("div", { style: textInnerStyle, children: [
|
|
38959
|
+
/* @__PURE__ */ jsx(Heading, { style: first ? textH1Style : textH2Style, children: /* @__PURE__ */ jsx(T, { k: keys2.heading }) }),
|
|
38960
|
+
intro2 || editMode ? /* @__PURE__ */ jsx("p", { style: textIntroStyle, children: /* @__PURE__ */ jsx(T, { k: keys2.intro }) }) : null,
|
|
38961
|
+
/* @__PURE__ */ jsx(
|
|
38962
|
+
SettoRepeater,
|
|
38963
|
+
{
|
|
38964
|
+
itemsKey: keys2.items,
|
|
38965
|
+
defaultItem: { heading: "Overskrift", text: "Tekst" },
|
|
38966
|
+
itemLabel: "avsnitt",
|
|
38967
|
+
children: (itemKey) => /* @__PURE__ */ jsx(TextItem, { sectionId, itemKey })
|
|
38968
|
+
}
|
|
38969
|
+
)
|
|
38970
|
+
] }) });
|
|
38971
|
+
}
|
|
38972
|
+
function TextItem({ sectionId, itemKey }) {
|
|
38973
|
+
const keys2 = builtinKeys(sectionId);
|
|
38974
|
+
const { i18n } = useTranslation();
|
|
38975
|
+
const { store, editMode } = useSetto();
|
|
38976
|
+
const heading2 = store ? store.get(keys2.itemHeading(itemKey), i18n.language) : i18n.t(keys2.itemHeading(itemKey), { defaultValue: "" });
|
|
38977
|
+
return /* @__PURE__ */ jsxs("div", { style: textItemStyle, children: [
|
|
38978
|
+
heading2 || editMode ? /* @__PURE__ */ jsx("h3", { style: textH3Style, children: /* @__PURE__ */ jsx(T, { k: keys2.itemHeading(itemKey) }) }) : null,
|
|
38979
|
+
/* @__PURE__ */ jsx("p", { style: textParagraphStyle, children: /* @__PURE__ */ jsx(T, { k: keys2.itemText(itemKey) }) })
|
|
38980
|
+
] });
|
|
38981
|
+
}
|
|
38982
|
+
const BUILTIN_RENDERERS = {
|
|
38983
|
+
text: SettoTextSection
|
|
38984
|
+
};
|
|
38985
|
+
const textSectionStyle = { padding: "clamp(48px, 8vw, 96px) 16px" };
|
|
38986
|
+
const textInnerStyle = { maxWidth: "68ch", margin: "0 auto" };
|
|
38987
|
+
const textH1Style = {
|
|
38988
|
+
fontFamily: "var(--setto-display-font, inherit)",
|
|
38989
|
+
fontSize: "clamp(2rem, 4vw, 2.75rem)",
|
|
38990
|
+
lineHeight: 1.15,
|
|
38991
|
+
fontWeight: 600,
|
|
38992
|
+
margin: "0 0 0.75em"
|
|
38993
|
+
};
|
|
38994
|
+
const textH2Style = { ...textH1Style, fontSize: "clamp(1.5rem, 3vw, 2rem)" };
|
|
38995
|
+
const textH3Style = {
|
|
38996
|
+
fontFamily: "var(--setto-display-font, inherit)",
|
|
38997
|
+
fontSize: "1.125rem",
|
|
38998
|
+
lineHeight: 1.3,
|
|
38999
|
+
fontWeight: 600,
|
|
39000
|
+
margin: "0 0 0.4em"
|
|
39001
|
+
};
|
|
39002
|
+
const textIntroStyle = { fontSize: "1.125rem", lineHeight: 1.6, opacity: 0.85, margin: "0 0 2em" };
|
|
39003
|
+
const textItemStyle = { margin: "0 0 1.75em" };
|
|
39004
|
+
const textParagraphStyle = { lineHeight: 1.7, margin: 0, whiteSpace: "pre-line" };
|
|
39005
|
+
const hiddenBarStyle = {
|
|
39006
|
+
display: "flex",
|
|
39007
|
+
alignItems: "center",
|
|
39008
|
+
justifyContent: "center",
|
|
39009
|
+
gap: 12,
|
|
39010
|
+
padding: "10px 16px",
|
|
39011
|
+
margin: "4px 0",
|
|
39012
|
+
border: "1px dashed rgba(100, 10, 255, 0.45)",
|
|
39013
|
+
borderRadius: 8,
|
|
39014
|
+
background: "rgba(100, 10, 255, 0.05)",
|
|
39015
|
+
color: "#3b2a66",
|
|
39016
|
+
font: '500 13px system-ui, -apple-system, "Segoe UI", sans-serif'
|
|
39017
|
+
};
|
|
39018
|
+
const hiddenBtnStyle = {
|
|
39019
|
+
border: "none",
|
|
39020
|
+
borderRadius: 999,
|
|
39021
|
+
padding: "4px 12px",
|
|
39022
|
+
background: "#640AFF",
|
|
39023
|
+
color: "#fff",
|
|
39024
|
+
font: '600 12px system-ui, -apple-system, "Segoe UI", sans-serif',
|
|
39025
|
+
cursor: "pointer"
|
|
39026
|
+
};
|
|
38328
39027
|
const DEFAULT_DURATION = {
|
|
38329
39028
|
fadeIn: 600,
|
|
38330
39029
|
slideUp: 700,
|
|
@@ -39287,15 +39986,21 @@ export {
|
|
|
39287
39986
|
AuthGate,
|
|
39288
39987
|
BLOCK_REGISTRY,
|
|
39289
39988
|
BLOCK_TYPES,
|
|
39989
|
+
BUILTIN_SECTION_TYPES,
|
|
39290
39990
|
CHECKOUT_INTENT,
|
|
39291
39991
|
DEFAULT_LANGUAGE,
|
|
39292
39992
|
EMPTY_I18N_STORE_SNAPSHOT,
|
|
39993
|
+
EMPTY_PAGE_STORE_SNAPSHOT,
|
|
39293
39994
|
FAB_INSET,
|
|
39294
39995
|
FAB_SIZE,
|
|
39295
39996
|
FORM_FIELD_TYPES,
|
|
39296
39997
|
FORM_FIELD_TYPE_LABELS,
|
|
39297
39998
|
FloatingPopover,
|
|
39298
39999
|
GuestEditProvider,
|
|
40000
|
+
ManifestError,
|
|
40001
|
+
PAGES_MANIFEST_VERSION,
|
|
40002
|
+
PAGES_PATH,
|
|
40003
|
+
PageStore,
|
|
39299
40004
|
SETTO_BASE,
|
|
39300
40005
|
SETTO_MODULES,
|
|
39301
40006
|
SettoAdminApp,
|
|
@@ -39311,42 +40016,59 @@ export {
|
|
|
39311
40016
|
SettoLanguages,
|
|
39312
40017
|
SettoMap,
|
|
39313
40018
|
SettoMark,
|
|
40019
|
+
SettoPage,
|
|
39314
40020
|
SettoProvider,
|
|
39315
40021
|
SettoRepeater,
|
|
39316
40022
|
SettoSection,
|
|
40023
|
+
SettoTextSection,
|
|
39317
40024
|
SettoVideo,
|
|
39318
40025
|
T,
|
|
39319
40026
|
adminRedirectUrl,
|
|
39320
40027
|
adminViewUrl,
|
|
39321
40028
|
alternateLinks,
|
|
40029
|
+
applyOp,
|
|
40030
|
+
applyOps,
|
|
40031
|
+
builtinKeys,
|
|
39322
40032
|
checkoutIntentUrl,
|
|
40033
|
+
countChanges,
|
|
39323
40034
|
currentAdminView,
|
|
39324
40035
|
extractContentKeys,
|
|
39325
40036
|
fieldTypeHasOptions,
|
|
39326
40037
|
fieldTypeHasPlaceholder,
|
|
40038
|
+
findSection,
|
|
39327
40039
|
formFieldsPrefix,
|
|
39328
40040
|
isAdminDeploymentView,
|
|
39329
40041
|
isAdminPanelView,
|
|
39330
40042
|
isAdminRoute,
|
|
39331
40043
|
isFormFieldType,
|
|
39332
40044
|
isLinkContext,
|
|
40045
|
+
isSafeHref,
|
|
39333
40046
|
languageFromPath,
|
|
39334
40047
|
languageName,
|
|
39335
40048
|
nodeChildren,
|
|
40049
|
+
normalisePath,
|
|
40050
|
+
pageById,
|
|
40051
|
+
pageForPath,
|
|
40052
|
+
pageTitleKey,
|
|
39336
40053
|
parseBlockTree,
|
|
40054
|
+
parseManifest,
|
|
39337
40055
|
pathForLanguage,
|
|
39338
40056
|
pickInitialLanguage,
|
|
39339
40057
|
readLocaleFields,
|
|
39340
40058
|
redirectForVisitor,
|
|
40059
|
+
serialiseManifest,
|
|
39341
40060
|
settoSubmissionUrl,
|
|
39342
40061
|
siteHomeUrl,
|
|
39343
40062
|
siteLanguages,
|
|
39344
40063
|
useCoarsePointer,
|
|
39345
40064
|
useGuestEdit,
|
|
40065
|
+
usePageManifest,
|
|
39346
40066
|
useSectionTheme,
|
|
39347
40067
|
useSetto,
|
|
39348
40068
|
useSettoChromeSlot,
|
|
40069
|
+
useSettoMenu,
|
|
39349
40070
|
validateBlockTree,
|
|
40071
|
+
visibleMenu,
|
|
39350
40072
|
wantsCheckout,
|
|
39351
40073
|
withoutCheckoutIntent
|
|
39352
40074
|
};
|