@sevenfold/setto-client 0.29.0 → 0.30.1

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.
@@ -20764,10 +20764,10 @@ function shouldShowDeprecationWarning() {
20764
20764
  return parseInt(versionMatch[1], 10) <= 18;
20765
20765
  }
20766
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");
20767
- const cache = /* @__PURE__ */ new WeakMap();
20767
+ const cache$1 = /* @__PURE__ */ new WeakMap();
20768
20768
  function getSupabase(config) {
20769
20769
  const key = { url: config.url, anonKey: config.anonKey };
20770
- const hit = cache.get(key);
20770
+ const hit = cache$1.get(key);
20771
20771
  if (hit) return hit;
20772
20772
  const client = createClient(config.url, config.anonKey, {
20773
20773
  auth: {
@@ -20776,7 +20776,7 @@ function getSupabase(config) {
20776
20776
  storageKey: `setto-auth-${config.url}`
20777
20777
  }
20778
20778
  });
20779
- cache.set(key, client);
20779
+ cache$1.set(key, client);
20780
20780
  return client;
20781
20781
  }
20782
20782
  function createFormsApi(request) {
@@ -21459,6 +21459,452 @@ class ThemeStore {
21459
21459
  for (const fn of this.listeners) fn(this.cachedSnapshot);
21460
21460
  }
21461
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
+ }
21462
21908
  const EMPTY$1 = {
21463
21909
  sites: null,
21464
21910
  currentSite: null,
@@ -29898,7 +30344,7 @@ function requireExtend() {
29898
30344
  return obj[name2];
29899
30345
  };
29900
30346
  extend$1 = function extend2() {
29901
- var options, name2, src, copy, copyIsArray, clone;
30347
+ var options, name2, src, copy, copyIsArray, clone2;
29902
30348
  var target = arguments[0];
29903
30349
  var i = 1;
29904
30350
  var length = arguments.length;
@@ -29921,11 +30367,11 @@ function requireExtend() {
29921
30367
  if (deep && copy && (isPlainObject2(copy) || (copyIsArray = isArray(copy)))) {
29922
30368
  if (copyIsArray) {
29923
30369
  copyIsArray = false;
29924
- clone = src && isArray(src) ? src : [];
30370
+ clone2 = src && isArray(src) ? src : [];
29925
30371
  } else {
29926
- clone = src && isPlainObject2(src) ? src : {};
30372
+ clone2 = src && isPlainObject2(src) ? src : {};
29927
30373
  }
29928
- setProperty(target, { name: name2, newValue: extend2(deep, clone, copy) });
30374
+ setProperty(target, { name: name2, newValue: extend2(deep, clone2, copy) });
29929
30375
  } else if (typeof copy !== "undefined") {
29930
30376
  setProperty(target, { name: name2, newValue: copy });
29931
30377
  }
@@ -34616,7 +35062,7 @@ const ACTIVE_CONVERSATION_KEY = "setto-chat-active";
34616
35062
  const NAVIGATION_KEY = "setto-chat-navigation";
34617
35063
  const CLOSE_MS = 210;
34618
35064
  const MIN_COMPOSER_HEIGHT = 36;
34619
- const MAX_COMPOSER_HEIGHT = 200;
35065
+ const MAX_COMPOSER_HEIGHT = 144;
34620
35066
  const DESKTOP_POPUP_QUERY = "(min-width: 701px)";
34621
35067
  function visitorId() {
34622
35068
  try {
@@ -36059,7 +36505,7 @@ function useChatStyles() {
36059
36505
  .setto-chat__body{position:relative;display:flex;flex:1 1 auto;min-height:0;height:100%;flex-direction:column}.setto-chat__log{display:flex;flex:1 1 auto;min-height:0;overflow-y:auto;overscroll-behavior:contain;flex-direction:column;padding:calc(58px + 1.3rem) .75rem var(--setto-chat-composer-space,8rem);scroll-padding-bottom:var(--setto-chat-composer-space,8rem);scroll-behavior:auto;-webkit-overflow-scrolling:touch}.setto-chat__message-list{display:flex;flex-direction:column}.setto-chat__message{--setto-chat-bubble:color-mix(in srgb,var(--setto-chat-ink) 7%,var(--setto-chat-canvas));display:flex;align-items:flex-end;gap:.55rem;width:100%;margin-bottom:1rem;color:color-mix(in srgb,var(--setto-chat-ink) 92%,transparent);font-size:16px;line-height:1.5;overflow-wrap:anywhere}.setto-chat__message-stack{display:flex;min-width:0;max-width:min(78%,36rem);flex-direction:column;align-items:flex-start}.setto-chat__message-bubble{position:relative;min-width:2.5rem;padding:.58rem .82rem;border-radius:19px 19px 19px 5px;background:var(--setto-chat-bubble);white-space:pre-wrap}.setto-chat__message--assistant .setto-chat__message-bubble::before,.setto-chat__message--human .setto-chat__message-bubble::before{content:"";position:absolute;z-index:0;left:-7px;bottom:0;width:16px;height:17px;border-bottom-right-radius:14px;background:var(--setto-chat-bubble)}.setto-chat__message--assistant .setto-chat__message-bubble::after,.setto-chat__message--human .setto-chat__message-bubble::after{content:"";position:absolute;z-index:1;left:-10px;bottom:0;width:10px;height:18px;border-bottom-right-radius:10px;background:var(--setto-chat-canvas)}.setto-chat__message-copy,.setto-chat__image{position:relative;z-index:2}.setto-chat__message--visitor{--setto-chat-bubble:#0a84ff;justify-content:flex-end;color:#fff}.setto-chat__message--visitor .setto-chat__message-stack{align-items:flex-end}.setto-chat__message--visitor .setto-chat__message-bubble{border-radius:19px 19px 5px 19px}.setto-chat__message--visitor .setto-chat__message-bubble::before{content:"";position:absolute;z-index:0;right:-7px;bottom:0;width:16px;height:17px;border-bottom-left-radius:14px;background:var(--setto-chat-bubble)}.setto-chat__message--visitor .setto-chat__message-bubble::after{content:"";position:absolute;z-index:1;right:-10px;bottom:0;width:10px;height:18px;border-bottom-left-radius:10px;background:var(--setto-chat-canvas)}.setto-chat__message--system{justify-content:center;color:color-mix(in srgb,var(--setto-chat-ink) 55%,transparent);font-size:.78rem;line-height:1.45;text-align:center}.setto-chat__message--system .setto-chat__message-stack{max-width:85%;align-items:center}.setto-chat__message--system .setto-chat__message-bubble{padding:.25rem .6rem;background:transparent}.setto-chat__avatar{position:relative;z-index:3;display:grid;place-items:center;flex:0 0 34px;width:34px;height:34px;margin-bottom:1px;overflow:hidden;border:2px solid var(--setto-chat-canvas);border-radius:50%;background:var(--setto-chat-crema);color:var(--setto-chat-ink);box-shadow:0 2px 8px color-mix(in srgb,var(--setto-chat-ink) 14%,transparent)}.setto-chat__avatar--assistant{background:var(--setto-chat-accent);color:var(--setto-chat-raised)}.setto-chat__avatar img{display:block;width:100%;height:100%;object-fit:cover}.setto-chat__speaker{display:block;margin:0 0 .2rem .4rem;color:color-mix(in srgb,var(--setto-chat-ink) 52%,transparent);font-size:.69rem;font-weight:500;line-height:1.2}.setto-chat__typing{display:inline-block;min-width:1.8rem;animation:setto-chat-pulse 1s ease-in-out infinite}.setto-chat__image{display:block;width:max-content;max-width:100%;margin-top:.65rem;overflow:hidden;border-radius:13px}.setto-chat__image img{display:block;width:min(280px,100%);max-height:260px;object-fit:cover}
36060
36506
  .setto-chat__message--agent-joined{justify-content:flex-start;align-items:center;gap:.55rem;padding:.1rem .4rem;color:color-mix(in srgb,var(--setto-chat-ink) 62%,transparent);font-size:.8rem;line-height:1.4;text-align:left}.setto-chat__message--agent-joined .setto-chat__message-stack{max-width:calc(100% - 38px);align-items:flex-start}.setto-chat__message--agent-joined .setto-chat__message-bubble{min-width:0;padding:.2rem 0;background:transparent;white-space:normal}.setto-chat__avatar--joined{flex-basis:30px;width:30px;height:30px;margin:0;border-width:1px;box-shadow:0 2px 7px color-mix(in srgb,var(--setto-chat-ink) 12%,transparent)}
36061
36507
  .setto-chat__receipt{display:block;margin:.26rem .45rem 0 0;color:color-mix(in srgb,var(--setto-chat-ink) 52%,transparent);font-size:.69rem;font-weight:500;line-height:1.2}.setto-chat__message--typing{animation:setto-chat-typing-in 180ms cubic-bezier(.2,.8,.2,1) both}.setto-chat__message--typing .setto-chat__message-bubble{min-width:3.25rem;text-align:center}.setto-chat__message--typing .setto-chat__typing{font-weight:700;letter-spacing:.06em}@keyframes setto-chat-typing-in{from{opacity:0;transform:translateY(4px)}}@media(prefers-reduced-motion:reduce){.setto-chat__message--typing{animation:none}}
36062
- .setto-chat__composer-shell{position:absolute;left:0;right:0;bottom:0;z-index:25;display:grid;grid-template-columns:minmax(0,1fr);padding:1.5rem .75rem 0;background:transparent;isolation:isolate}.setto-chat__composer-shell::before{content:"";position:absolute;inset:0;pointer-events:none;background:linear-gradient(to bottom,transparent 0%,color-mix(in srgb,var(--setto-chat-canvas) 82%,transparent) 46%,var(--setto-chat-canvas) 66%);backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);-webkit-mask-image:linear-gradient(to bottom,rgba(0,0,0,0) 0%,rgba(0,0,0,.4) 34%,rgba(0,0,0,1) 66%);mask-image:linear-gradient(to bottom,rgba(0,0,0,0) 0%,rgba(0,0,0,.4) 34%,rgba(0,0,0,1) 66%)}.setto-chat__composer{position:relative;z-index:1;display:flex;flex-direction:column;gap:.15rem;width:100%;min-width:0;margin:0;padding:.35rem .4rem;border:2px solid color-mix(in srgb,var(--setto-chat-ink) 10%,transparent);border-radius:24px;background:var(--setto-chat-canvas);box-shadow:0 8px 24px -16px color-mix(in srgb,var(--setto-chat-ink) 28%,transparent);transition:padding 220ms cubic-bezier(.16,1,.3,1),border-color 200ms ease,box-shadow 360ms cubic-bezier(.16,1,.3,1)}.setto-chat__composer.is-open{padding:.55rem .5rem}.setto-chat__composer:focus-within{border-color:var(--setto-chat-accent)}.setto-chat__composer textarea,.setto-chat__composer textarea:hover,.setto-chat__composer textarea:focus,.setto-chat__composer textarea:focus-visible{width:100%;min-height:36px;max-height:200px;padding:.45rem .25rem;resize:none;-webkit-appearance:none!important;appearance:none!important;border:0!important;border-radius:0!important;outline:0!important;box-shadow:none!important;background:transparent!important;color:var(--setto-chat-ink);font-family:"Archivo Variable",Archivo,system-ui,sans-serif;font-size:16px;line-height:1.35;transition:min-height 220ms cubic-bezier(.16,1,.3,1)}.setto-chat__composer.is-open textarea{min-height:40px}.setto-chat__composer textarea::placeholder{color:color-mix(in srgb,var(--setto-chat-ink) 52%,transparent)}.setto-chat__composer-tools{display:flex;align-items:center;gap:.4rem;width:100%;min-width:0}.setto-chat__attach{flex:none;width:34px;height:34px;padding:0;border:0;border-radius:50%;background:transparent;color:color-mix(in srgb,var(--setto-chat-ink) 58%,transparent);display:inline-flex;align-items:center;justify-content:center;cursor:pointer;transition:background 160ms ease,color 160ms ease}.setto-chat__attach:hover:not(:disabled){background:color-mix(in srgb,var(--setto-chat-ink) 6%,transparent);color:var(--setto-chat-ink)}.setto-chat__attach:disabled{opacity:.4;cursor:default}.setto-chat__send{flex:none;width:36px;height:36px;margin-left:auto;padding:0;border:0;border-radius:50%;background:var(--setto-chat-accent);color:var(--setto-chat-raised);display:inline-flex;align-items:center;justify-content:center;cursor:pointer;transition:transform 360ms cubic-bezier(.16,1,.3,1),box-shadow 360ms cubic-bezier(.16,1,.3,1),background 200ms ease,opacity 200ms ease}.setto-chat__send:hover:not(:disabled){transform:translateY(-2px);box-shadow:0 12px 24px -12px color-mix(in srgb,var(--setto-chat-ink) 45%,transparent)}.setto-chat__send:active:not(:disabled){transform:translateY(0);box-shadow:0 4px 10px -8px color-mix(in srgb,var(--setto-chat-ink) 40%,transparent);transition-duration:90ms}.setto-chat__send:disabled{opacity:.35;cursor:default}.setto-chat__send.is-queued{animation:setto-chat-queued 1.1s ease-in-out infinite}.setto-chat__attachments{display:flex;flex-wrap:wrap;gap:.4rem;list-style:none;margin:0;padding:.15rem .15rem 0}.setto-chat__attachments li{position:relative;width:56px;height:56px;border-radius:10px;overflow:hidden;border:1px solid color-mix(in srgb,var(--setto-chat-ink) 12%,transparent);background:var(--setto-chat-crema)}.setto-chat__attachments img{display:block;width:100%;height:100%;object-fit:cover}.setto-chat__attachments button{position:absolute;top:2px;right:2px;width:18px;height:18px;padding:0;border:0;border-radius:50%;background:color-mix(in srgb,var(--setto-chat-ink) 72%,transparent);color:#fff;display:inline-flex;align-items:center;justify-content:center;cursor:pointer}.setto-chat__composer-floor{position:relative;z-index:1;width:calc(100% + 1.5rem);height:max(.35rem,env(safe-area-inset-bottom));margin-left:-.75rem;background:var(--setto-chat-canvas)}
36508
+ .setto-chat__composer-shell{position:absolute;left:0;right:0;bottom:0;z-index:25;display:grid;grid-template-columns:minmax(0,1fr);padding:1.5rem .75rem 0;background:transparent;isolation:isolate}.setto-chat__composer-shell::before{content:"";position:absolute;inset:0;pointer-events:none;background:linear-gradient(to bottom,transparent 0%,color-mix(in srgb,var(--setto-chat-canvas) 82%,transparent) 46%,var(--setto-chat-canvas) 66%);backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);-webkit-mask-image:linear-gradient(to bottom,rgba(0,0,0,0) 0%,rgba(0,0,0,.4) 34%,rgba(0,0,0,1) 66%);mask-image:linear-gradient(to bottom,rgba(0,0,0,0) 0%,rgba(0,0,0,.4) 34%,rgba(0,0,0,1) 66%)}.setto-chat__composer{position:relative;z-index:1;display:flex;flex-direction:column;gap:.15rem;width:100%;min-width:0;margin:0;padding:.35rem .4rem;border:2px solid color-mix(in srgb,var(--setto-chat-ink) 10%,transparent);border-radius:24px;background:var(--setto-chat-canvas);box-shadow:0 8px 24px -16px color-mix(in srgb,var(--setto-chat-ink) 28%,transparent);transition:padding 220ms cubic-bezier(.16,1,.3,1),border-color 200ms ease,box-shadow 360ms cubic-bezier(.16,1,.3,1)}.setto-chat__composer.is-open{padding:.55rem .5rem}.setto-chat__composer:focus-within{border-color:var(--setto-chat-accent)}.setto-chat__composer textarea,.setto-chat__composer textarea:hover,.setto-chat__composer textarea:focus,.setto-chat__composer textarea:focus-visible{width:100%;min-height:36px;max-height:${MAX_COMPOSER_HEIGHT}px;overflow-y:auto;padding:.45rem .25rem;resize:none;-webkit-appearance:none!important;appearance:none!important;border:0!important;border-radius:0!important;outline:0!important;box-shadow:none!important;background:transparent!important;color:var(--setto-chat-ink);font-family:"Archivo Variable",Archivo,system-ui,sans-serif;font-size:16px;line-height:1.35;transition:min-height 220ms cubic-bezier(.16,1,.3,1)}.setto-chat__composer.is-open textarea{min-height:40px}.setto-chat__composer textarea::placeholder{color:color-mix(in srgb,var(--setto-chat-ink) 52%,transparent)}.setto-chat__composer textarea::-webkit-scrollbar{width:6px}.setto-chat__composer textarea::-webkit-scrollbar-track{background:transparent}.setto-chat__composer textarea::-webkit-scrollbar-thumb{border-radius:999px;background:color-mix(in srgb,var(--setto-chat-ink) 24%,transparent)}.setto-chat__composer textarea::-webkit-scrollbar-button{display:none;height:0}@supports not selector(::-webkit-scrollbar){.setto-chat__composer textarea{scrollbar-width:thin;scrollbar-color:color-mix(in srgb,var(--setto-chat-ink) 24%,transparent) transparent}}.setto-chat__composer-tools{display:flex;align-items:center;gap:.4rem;width:100%;min-width:0}.setto-chat__attach{flex:none;width:34px;height:34px;padding:0;border:0;border-radius:50%;background:transparent;color:color-mix(in srgb,var(--setto-chat-ink) 58%,transparent);display:inline-flex;align-items:center;justify-content:center;cursor:pointer;transition:background 160ms ease,color 160ms ease}.setto-chat__attach:hover:not(:disabled){background:color-mix(in srgb,var(--setto-chat-ink) 6%,transparent);color:var(--setto-chat-ink)}.setto-chat__attach:disabled{opacity:.4;cursor:default}.setto-chat__send{flex:none;width:36px;height:36px;margin-left:auto;padding:0;border:0;border-radius:50%;background:var(--setto-chat-accent);color:var(--setto-chat-raised);display:inline-flex;align-items:center;justify-content:center;cursor:pointer;transition:transform 360ms cubic-bezier(.16,1,.3,1),box-shadow 360ms cubic-bezier(.16,1,.3,1),background 200ms ease,opacity 200ms ease}.setto-chat__send:hover:not(:disabled){transform:translateY(-2px);box-shadow:0 12px 24px -12px color-mix(in srgb,var(--setto-chat-ink) 45%,transparent)}.setto-chat__send:active:not(:disabled){transform:translateY(0);box-shadow:0 4px 10px -8px color-mix(in srgb,var(--setto-chat-ink) 40%,transparent);transition-duration:90ms}.setto-chat__send:disabled{opacity:.35;cursor:default}.setto-chat__send.is-queued{animation:setto-chat-queued 1.1s ease-in-out infinite}.setto-chat__attachments{display:flex;flex-wrap:wrap;gap:.4rem;list-style:none;margin:0;padding:.15rem .15rem 0}.setto-chat__attachments li{position:relative;width:56px;height:56px;border-radius:10px;overflow:hidden;border:1px solid color-mix(in srgb,var(--setto-chat-ink) 12%,transparent);background:var(--setto-chat-crema)}.setto-chat__attachments img{display:block;width:100%;height:100%;object-fit:cover}.setto-chat__attachments button{position:absolute;top:2px;right:2px;width:18px;height:18px;padding:0;border:0;border-radius:50%;background:color-mix(in srgb,var(--setto-chat-ink) 72%,transparent);color:#fff;display:inline-flex;align-items:center;justify-content:center;cursor:pointer}.setto-chat__composer-floor{position:relative;z-index:1;width:calc(100% + 1.5rem);height:max(.35rem,env(safe-area-inset-bottom));margin-left:-.75rem;background:var(--setto-chat-canvas)}
36063
36509
  .setto-chat__launcher{width:58px;height:58px;padding:3px;border:1px solid rgba(255,255,255,.54);border-radius:50%;background:color-mix(in srgb,var(--setto-chat-raised) 35%,transparent);box-shadow:0 10px 30px color-mix(in srgb,var(--setto-chat-ink) 24%,transparent),inset 0 0 0 1px color-mix(in srgb,var(--setto-chat-ink) 9%,transparent);backdrop-filter:blur(14px) saturate(115%);-webkit-backdrop-filter:blur(14px) saturate(115%);color:var(--setto-chat-ink);display:grid;place-items:center;cursor:pointer;transition:transform 180ms ease,box-shadow 180ms ease}.setto-chat__launcher:hover{transform:translateY(-2px);box-shadow:0 15px 35px color-mix(in srgb,var(--setto-chat-ink) 28%,transparent),inset 0 0 0 1px color-mix(in srgb,var(--setto-chat-ink) 9%,transparent)}.setto-chat__launcher span{width:46px;height:46px;border-radius:50%;display:grid;place-items:center;background:var(--setto-chat-accent);color:var(--setto-chat-raised)}.setto-chat__launcher img{display:block;width:50px;height:50px;border-radius:50%;object-fit:cover}.setto-chat__launcher:focus-visible,.setto-chat button:focus-visible,.setto-chat input:focus-visible{outline:2px solid var(--setto-chat-accent);outline-offset:2px}@keyframes setto-chat-pulse{50%{opacity:.35}}@keyframes setto-chat-queued{50%{opacity:.55}}
36064
36510
  @media(max-width:700px){.setto-chat__chats{position:absolute;inset:0 auto 0 0;transform:translateX(-100%);transition:transform 260ms cubic-bezier(.16,1,.3,1);box-shadow:14px 0 35px color-mix(in srgb,var(--setto-chat-ink) 16%,transparent)}.setto-chat__chats--open{transform:none}.setto-chat__main{width:100%;max-width:none;margin:0}.setto-chat__log{padding-left:.75rem;padding-right:.75rem}}@media(min-width:701px){.setto-chat--mounted{top:auto;right:max(18px,env(safe-area-inset-right));bottom:max(18px,env(safe-area-inset-bottom));left:auto;width:min(440px,calc(100vw - 36px));height:min(720px,calc(100dvh - 36px));overflow:visible;background:transparent}.setto-chat__shell{position:relative;top:auto;left:auto;width:100%;height:100%;border:1px solid color-mix(in srgb,var(--setto-chat-ink) 14%,transparent);border-radius:26px;background:var(--setto-chat-canvas);box-shadow:0 28px 80px -24px color-mix(in srgb,var(--setto-chat-ink) 42%,transparent),0 8px 24px -12px color-mix(in srgb,var(--setto-chat-ink) 24%,transparent);transform:translateY(16px) scale(.975);transform-origin:right bottom}.setto-chat--visible .setto-chat__shell{transform:none}.setto-chat__chats{position:absolute;inset:0 auto 0 0;width:min(82%,280px);height:100%;transform:translateX(-100%);transition:transform 260ms cubic-bezier(.16,1,.3,1);box-shadow:14px 0 35px color-mix(in srgb,var(--setto-chat-ink) 16%,transparent)}.setto-chat__chats--open{transform:none}.setto-chat__main{width:100%;max-width:none;margin:0}.setto-chat__header,.setto-chat__log{padding-left:.9rem;padding-right:.9rem}.setto-chat__composer-shell{padding-left:.9rem;padding-right:.9rem}.setto-chat__composer-floor{width:calc(100% + 1.8rem);height:.45rem;margin-left:-.9rem}.setto-chat__message{font-size:15.5px}.setto-chat__message-stack{max-width:82%}}@media(prefers-reduced-motion:reduce){.setto-chat,.setto-chat__shell,.setto-chat__chats,.setto-chat__launcher,.setto-chat__composer,.setto-chat__composer textarea,.setto-chat__send{transition:none!important}.setto-chat__typing,.setto-chat__send.is-queued{animation:none}}
36065
36511
  `;
@@ -36141,14 +36587,16 @@ function SettoProvider({ config, children }) {
36141
36587
  const storeRef = useRef(null);
36142
36588
  const themeStoreRef = useRef(null);
36143
36589
  const assetStoreRef = useRef(null);
36590
+ const pageStoreRef = useRef(null);
36144
36591
  const [storeReady, setStoreReady] = useState(false);
36145
36592
  useEffect(() => {
36146
36593
  if (!i18n) return;
36147
36594
  storeRef.current = new I18nStore(i18n);
36148
36595
  themeStoreRef.current = new ThemeStore(config.theme ?? {});
36149
36596
  assetStoreRef.current = new AssetStore();
36597
+ pageStoreRef.current = new PageStore(bundledManifest(config.pages));
36150
36598
  setStoreReady(true);
36151
- }, [i18n, config.theme]);
36599
+ }, [i18n, config.theme, config.pages]);
36152
36600
  const editMode = !!session && access.currentSite?.status === "live" && !onAdminRoute && !authLoading && !access.loading;
36153
36601
  const [selectedSectionId, setSelectedSectionId] = useState(null);
36154
36602
  const selectSection = useCallback((id) => setSelectedSectionId(id), []);
@@ -36172,6 +36620,7 @@ function SettoProvider({ config, children }) {
36172
36620
  store: storeReady ? storeRef.current : null,
36173
36621
  themeStore: storeReady ? themeStoreRef.current : null,
36174
36622
  assetStore: storeReady ? assetStoreRef.current : null,
36623
+ pageStore: storeReady ? pageStoreRef.current : null,
36175
36624
  selectedSectionId,
36176
36625
  selectSection,
36177
36626
  guestEdit,
@@ -37060,6 +37509,8 @@ function SettoImage({ srcKey, alt, className, style, ...rest }) {
37060
37509
  const fileRef = useRef(null);
37061
37510
  const [hovered, setHovered] = useState(false);
37062
37511
  const [uploading, setUploading] = useState(false);
37512
+ const imgRef = useRef(null);
37513
+ const Tools = useSettoChromeSlot("image-tools");
37063
37514
  const [, force] = useState(0);
37064
37515
  useEffect(() => {
37065
37516
  if (!store) return;
@@ -37083,10 +37534,8 @@ function SettoImage({ srcKey, alt, className, style, ...rest }) {
37083
37534
  e.preventDefault();
37084
37535
  if (!uploading) fileRef.current?.click();
37085
37536
  };
37086
- const handleFile = async (e) => {
37087
- const file = e.target.files?.[0];
37088
- e.target.value = "";
37089
- if (!file || !assetStore || !store) return;
37537
+ const applyFile = async (file) => {
37538
+ if (!assetStore || !store) return;
37090
37539
  setUploading(true);
37091
37540
  try {
37092
37541
  const originalPublicPath = store.original(srcKey, i18n.language);
@@ -37103,6 +37552,18 @@ function SettoImage({ srcKey, alt, className, style, ...rest }) {
37103
37552
  setUploading(false);
37104
37553
  }
37105
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
+ };
37106
37567
  const handleRevert = (e) => {
37107
37568
  e.stopPropagation();
37108
37569
  e.preventDefault();
@@ -37117,6 +37578,7 @@ function SettoImage({ srcKey, alt, className, style, ...rest }) {
37117
37578
  "span",
37118
37579
  {
37119
37580
  "data-setto-image": true,
37581
+ "data-setto-src-key": srcKey,
37120
37582
  "data-setto-ui": true,
37121
37583
  onMouseEnter: () => setHovered(true),
37122
37584
  onMouseLeave: () => setHovered(false),
@@ -37130,8 +37592,21 @@ function SettoImage({ srcKey, alt, className, style, ...rest }) {
37130
37592
  borderRadius: 4
37131
37593
  },
37132
37594
  children: [
37133
- /* @__PURE__ */ jsx("img", { src, alt, className, style, ...rest }),
37134
- previewUrl && !hovered ? /* @__PURE__ */ jsx(
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(
37135
37610
  "span",
37136
37611
  {
37137
37612
  "aria-hidden": true,
@@ -37151,7 +37626,7 @@ function SettoImage({ srcKey, alt, className, style, ...rest }) {
37151
37626
  children: "Nytt bilde"
37152
37627
  }
37153
37628
  ) : null,
37154
- hovered ? /* @__PURE__ */ jsxs(
37629
+ hovered && !Tools ? /* @__PURE__ */ jsxs(
37155
37630
  "span",
37156
37631
  {
37157
37632
  "data-setto-ui": true,
@@ -38362,6 +38837,193 @@ const iframeStyle = {
38362
38837
  height: "100%",
38363
38838
  border: 0
38364
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
+ };
38365
39027
  const DEFAULT_DURATION = {
38366
39028
  fadeIn: 600,
38367
39029
  slideUp: 700,
@@ -39324,15 +39986,21 @@ export {
39324
39986
  AuthGate,
39325
39987
  BLOCK_REGISTRY,
39326
39988
  BLOCK_TYPES,
39989
+ BUILTIN_SECTION_TYPES,
39327
39990
  CHECKOUT_INTENT,
39328
39991
  DEFAULT_LANGUAGE,
39329
39992
  EMPTY_I18N_STORE_SNAPSHOT,
39993
+ EMPTY_PAGE_STORE_SNAPSHOT,
39330
39994
  FAB_INSET,
39331
39995
  FAB_SIZE,
39332
39996
  FORM_FIELD_TYPES,
39333
39997
  FORM_FIELD_TYPE_LABELS,
39334
39998
  FloatingPopover,
39335
39999
  GuestEditProvider,
40000
+ ManifestError,
40001
+ PAGES_MANIFEST_VERSION,
40002
+ PAGES_PATH,
40003
+ PageStore,
39336
40004
  SETTO_BASE,
39337
40005
  SETTO_MODULES,
39338
40006
  SettoAdminApp,
@@ -39348,42 +40016,59 @@ export {
39348
40016
  SettoLanguages,
39349
40017
  SettoMap,
39350
40018
  SettoMark,
40019
+ SettoPage,
39351
40020
  SettoProvider,
39352
40021
  SettoRepeater,
39353
40022
  SettoSection,
40023
+ SettoTextSection,
39354
40024
  SettoVideo,
39355
40025
  T,
39356
40026
  adminRedirectUrl,
39357
40027
  adminViewUrl,
39358
40028
  alternateLinks,
40029
+ applyOp,
40030
+ applyOps,
40031
+ builtinKeys,
39359
40032
  checkoutIntentUrl,
40033
+ countChanges,
39360
40034
  currentAdminView,
39361
40035
  extractContentKeys,
39362
40036
  fieldTypeHasOptions,
39363
40037
  fieldTypeHasPlaceholder,
40038
+ findSection,
39364
40039
  formFieldsPrefix,
39365
40040
  isAdminDeploymentView,
39366
40041
  isAdminPanelView,
39367
40042
  isAdminRoute,
39368
40043
  isFormFieldType,
39369
40044
  isLinkContext,
40045
+ isSafeHref,
39370
40046
  languageFromPath,
39371
40047
  languageName,
39372
40048
  nodeChildren,
40049
+ normalisePath,
40050
+ pageById,
40051
+ pageForPath,
40052
+ pageTitleKey,
39373
40053
  parseBlockTree,
40054
+ parseManifest,
39374
40055
  pathForLanguage,
39375
40056
  pickInitialLanguage,
39376
40057
  readLocaleFields,
39377
40058
  redirectForVisitor,
40059
+ serialiseManifest,
39378
40060
  settoSubmissionUrl,
39379
40061
  siteHomeUrl,
39380
40062
  siteLanguages,
39381
40063
  useCoarsePointer,
39382
40064
  useGuestEdit,
40065
+ usePageManifest,
39383
40066
  useSectionTheme,
39384
40067
  useSetto,
39385
40068
  useSettoChromeSlot,
40069
+ useSettoMenu,
39386
40070
  validateBlockTree,
40071
+ visibleMenu,
39387
40072
  wantsCheckout,
39388
40073
  withoutCheckoutIntent
39389
40074
  };