@sevenfold/setto-client 0.6.2 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +58 -10
- package/dist/admin/App.d.ts +3 -2
- package/dist/admin/DeploymentsView.d.ts +5 -0
- package/dist/admin/styles.d.ts +20 -0
- package/dist/block-tree/schema.d.ts +14 -16
- package/dist/edit-mode/mount.d.ts +1 -1
- package/dist/index.d.ts +6 -3
- package/dist/lib/api.d.ts +15 -0
- package/dist/lib/i18n-store.d.ts +38 -0
- package/dist/lib/nested-resource.d.ts +7 -1
- package/dist/lib/urls.d.ts +13 -0
- package/dist/modules/forms/FormsView.d.ts +17 -0
- package/dist/modules/forms/SettoForm.d.ts +58 -0
- package/dist/modules/forms/api.d.ts +40 -0
- package/dist/modules/forms/field-editor.d.ts +29 -0
- package/dist/modules/forms/fields.d.ts +112 -0
- package/dist/modules/forms/index.d.ts +12 -0
- package/dist/modules/index.d.ts +5 -0
- package/dist/modules/module.d.ts +30 -0
- package/dist/preview-bar.d.ts +3 -0
- package/dist/setto-client.js +2743 -1097
- package/dist/setto-client.js.map +1 -1
- package/dist/types.d.ts +16 -0
- package/package.json +1 -1
- package/dist/SettoForm.d.ts +0 -18
package/dist/setto-client.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx, jsxs, Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { useState, useRef, useEffect, useSyncExternalStore, useLayoutEffect, createContext, useContext, useCallback, useMemo, useId, forwardRef, Fragment as Fragment$1 } from "react";
|
|
2
|
+
import { useState, useRef, useEffect, useSyncExternalStore, useLayoutEffect, createContext, useContext, useCallback, useMemo, useId, useInsertionEffect, forwardRef, Fragment as Fragment$1 } from "react";
|
|
3
3
|
import { useTranslation } from "react-i18next";
|
|
4
4
|
import { createPortal } from "react-dom";
|
|
5
5
|
function __rest(s, e) {
|
|
@@ -20709,6 +20709,28 @@ function getSupabase(config) {
|
|
|
20709
20709
|
cache.set(key, client);
|
|
20710
20710
|
return client;
|
|
20711
20711
|
}
|
|
20712
|
+
function createFormsApi(request) {
|
|
20713
|
+
const base = (siteId) => `/sites/${encodeURIComponent(siteId)}/forms`;
|
|
20714
|
+
return {
|
|
20715
|
+
getSettings(siteId) {
|
|
20716
|
+
return request(`${base(siteId)}/settings`, {}, "getFormSettings");
|
|
20717
|
+
},
|
|
20718
|
+
saveSettings(siteId, recipients) {
|
|
20719
|
+
return request(
|
|
20720
|
+
`${base(siteId)}/settings`,
|
|
20721
|
+
{ method: "PUT", body: { recipients } },
|
|
20722
|
+
"saveFormSettings"
|
|
20723
|
+
);
|
|
20724
|
+
},
|
|
20725
|
+
deleteSubmission(siteId, id) {
|
|
20726
|
+
return request(
|
|
20727
|
+
`${base(siteId)}/submissions/${encodeURIComponent(id)}`,
|
|
20728
|
+
{ method: "DELETE" },
|
|
20729
|
+
"deleteSubmission"
|
|
20730
|
+
);
|
|
20731
|
+
}
|
|
20732
|
+
};
|
|
20733
|
+
}
|
|
20712
20734
|
async function bearer(sb) {
|
|
20713
20735
|
const { data } = await sb.auth.getSession();
|
|
20714
20736
|
const token = data.session?.access_token;
|
|
@@ -20739,7 +20761,23 @@ async function asError(res, fallback) {
|
|
|
20739
20761
|
}
|
|
20740
20762
|
function createApi(args) {
|
|
20741
20763
|
const { apiUrl, supabase } = args;
|
|
20764
|
+
const request = async (path, init = {}, label = `${init.method ?? "GET"} ${path}`) => {
|
|
20765
|
+
const token = await bearer(supabase);
|
|
20766
|
+
const res = await fetch(`${apiUrl}${path}`, {
|
|
20767
|
+
method: init.method ?? "GET",
|
|
20768
|
+
headers: {
|
|
20769
|
+
Authorization: `Bearer ${token}`,
|
|
20770
|
+
...init.body !== void 0 ? { "Content-Type": "application/json" } : {}
|
|
20771
|
+
},
|
|
20772
|
+
body: init.body !== void 0 ? JSON.stringify(init.body) : void 0
|
|
20773
|
+
});
|
|
20774
|
+
if (!res.ok) throw await asError(res, `${label} failed`);
|
|
20775
|
+
if (res.status === 204) return void 0;
|
|
20776
|
+
return parseJson(res);
|
|
20777
|
+
};
|
|
20742
20778
|
return {
|
|
20779
|
+
request,
|
|
20780
|
+
forms: createFormsApi(request),
|
|
20743
20781
|
async getContent(siteId) {
|
|
20744
20782
|
const token = await bearer(supabase);
|
|
20745
20783
|
const res = await fetch(`${apiUrl}/sites/${encodeURIComponent(siteId)}/content`, {
|
|
@@ -20832,14 +20870,20 @@ function getNested(obj, key) {
|
|
|
20832
20870
|
}
|
|
20833
20871
|
function deleteNested(obj, key) {
|
|
20834
20872
|
const parts = key.split(".");
|
|
20873
|
+
const chain = [obj];
|
|
20835
20874
|
let cur = obj;
|
|
20836
20875
|
for (let i = 0; i < parts.length - 1; i++) {
|
|
20837
20876
|
const part = parts[i];
|
|
20838
20877
|
const next = cur[part];
|
|
20839
20878
|
if (!next || typeof next !== "object") return;
|
|
20840
20879
|
cur = next;
|
|
20880
|
+
chain.push(cur);
|
|
20841
20881
|
}
|
|
20842
20882
|
delete cur[parts[parts.length - 1]];
|
|
20883
|
+
for (let i = chain.length - 1; i > 0; i--) {
|
|
20884
|
+
if (Object.keys(chain[i]).length > 0) break;
|
|
20885
|
+
delete chain[i - 1][parts[i - 1]];
|
|
20886
|
+
}
|
|
20843
20887
|
}
|
|
20844
20888
|
function collectKeysUnder(obj, prefix) {
|
|
20845
20889
|
const base = getNested(obj, prefix);
|
|
@@ -20863,6 +20907,8 @@ class I18nStore {
|
|
|
20863
20907
|
/** Stable namespace used by react-i18next defaults. */
|
|
20864
20908
|
ns;
|
|
20865
20909
|
i18n;
|
|
20910
|
+
/** >0 while a batch is running; consumers are notified once at the end. */
|
|
20911
|
+
batchDepth = 0;
|
|
20866
20912
|
/** Cached reference — useSyncExternalStore requires referential stability. */
|
|
20867
20913
|
cachedSnapshot = {
|
|
20868
20914
|
drafts: this.drafts,
|
|
@@ -20903,34 +20949,49 @@ class I18nStore {
|
|
|
20903
20949
|
set(key, lng, value) {
|
|
20904
20950
|
const id = `${lng}::${key}`;
|
|
20905
20951
|
const existing = this.drafts.get(id);
|
|
20906
|
-
const
|
|
20907
|
-
|
|
20952
|
+
const resource = this.i18n.getResource(lng, this.ns, key);
|
|
20953
|
+
const original = existing?.original ?? String(resource ?? "");
|
|
20954
|
+
const created = existing ? existing.created === true : resource === void 0;
|
|
20955
|
+
if (value === original && !created) {
|
|
20908
20956
|
this.drafts.delete(id);
|
|
20909
20957
|
} else {
|
|
20910
|
-
this.drafts.set(id, { key, lng, original, current: value });
|
|
20958
|
+
this.drafts.set(id, { key, lng, original, current: value, ...created ? { created } : {} });
|
|
20911
20959
|
}
|
|
20912
20960
|
this.i18n.addResource(lng, this.ns, key, value);
|
|
20913
|
-
this.
|
|
20914
|
-
|
|
20961
|
+
this.notify();
|
|
20962
|
+
}
|
|
20963
|
+
/**
|
|
20964
|
+
* Deletes a key from the bundle. A key that was created this session simply
|
|
20965
|
+
* disappears; one that was on disk is kept as a removal draft so the
|
|
20966
|
+
* deletion counts as a change and reverts cleanly.
|
|
20967
|
+
*/
|
|
20968
|
+
delete(key, lng) {
|
|
20969
|
+
const id = `${lng}::${key}`;
|
|
20970
|
+
const existing = this.drafts.get(id);
|
|
20971
|
+
const resource = this.i18n.getResource(lng, this.ns, key);
|
|
20972
|
+
if (existing?.created) {
|
|
20973
|
+
this.drafts.delete(id);
|
|
20974
|
+
} else if (existing || resource !== void 0) {
|
|
20975
|
+
const original = existing?.original ?? String(resource ?? "");
|
|
20976
|
+
this.drafts.set(id, { key, lng, original, current: "", removed: true });
|
|
20977
|
+
}
|
|
20978
|
+
this.deleteFromBundle(lng, key);
|
|
20979
|
+
this.notify();
|
|
20915
20980
|
}
|
|
20916
20981
|
/** Resets a single draft back to its original value. */
|
|
20917
20982
|
revert(key, lng) {
|
|
20918
20983
|
const id = `${lng}::${key}`;
|
|
20919
20984
|
const draft = this.drafts.get(id);
|
|
20920
20985
|
if (!draft) return;
|
|
20921
|
-
this.
|
|
20986
|
+
this.restore(draft);
|
|
20922
20987
|
this.drafts.delete(id);
|
|
20923
|
-
this.
|
|
20924
|
-
this.bump();
|
|
20988
|
+
this.notify();
|
|
20925
20989
|
}
|
|
20926
20990
|
/** Resets every draft. */
|
|
20927
20991
|
revertAll() {
|
|
20928
|
-
for (const draft of this.drafts.values())
|
|
20929
|
-
this.i18n.addResource(draft.lng, this.ns, draft.key, draft.original);
|
|
20930
|
-
}
|
|
20992
|
+
for (const draft of this.drafts.values()) this.restore(draft);
|
|
20931
20993
|
this.drafts.clear();
|
|
20932
|
-
this.
|
|
20933
|
-
this.bump();
|
|
20994
|
+
this.notify();
|
|
20934
20995
|
}
|
|
20935
20996
|
/** Marks the current state as the new baseline (after a successful publish). */
|
|
20936
20997
|
commit() {
|
|
@@ -20951,34 +21012,58 @@ class I18nStore {
|
|
|
20951
21012
|
* Adds the same key across all loaded languages.
|
|
20952
21013
|
*/
|
|
20953
21014
|
addListItem(prefix, _lng, template) {
|
|
20954
|
-
const langs = this.
|
|
21015
|
+
const langs = this.languages(_lng);
|
|
20955
21016
|
const allKeys = langs.flatMap((l) => this.getListKeys(prefix, l));
|
|
20956
21017
|
const numeric = allKeys.map(Number).filter((n) => !Number.isNaN(n));
|
|
20957
21018
|
const nextKey = numeric.length > 0 ? String(Math.max(...numeric) + 1) : "1";
|
|
20958
|
-
|
|
20959
|
-
for (const
|
|
20960
|
-
|
|
21019
|
+
this.batch(() => {
|
|
21020
|
+
for (const lang of langs) {
|
|
21021
|
+
for (const [field, value] of Object.entries(template)) {
|
|
21022
|
+
this.set(`${prefix}.${nextKey}.${field}`, lang, value);
|
|
21023
|
+
}
|
|
20961
21024
|
}
|
|
20962
|
-
}
|
|
21025
|
+
});
|
|
20963
21026
|
return nextKey;
|
|
20964
21027
|
}
|
|
20965
21028
|
/** Removes a list item and all nested keys under `prefix.itemKey` in every language. */
|
|
20966
21029
|
removeListItem(prefix, itemKey, _lng) {
|
|
20967
|
-
|
|
20968
|
-
|
|
20969
|
-
|
|
20970
|
-
|
|
20971
|
-
|
|
20972
|
-
|
|
20973
|
-
|
|
20974
|
-
|
|
20975
|
-
|
|
20976
|
-
|
|
20977
|
-
|
|
20978
|
-
|
|
20979
|
-
|
|
20980
|
-
|
|
20981
|
-
|
|
21030
|
+
this.batch(() => {
|
|
21031
|
+
for (const lang of this.languages(_lng)) {
|
|
21032
|
+
for (const leaf of this.leavesUnder(`${prefix}.${itemKey}`, lang)) {
|
|
21033
|
+
this.delete(leaf, lang);
|
|
21034
|
+
}
|
|
21035
|
+
}
|
|
21036
|
+
});
|
|
21037
|
+
}
|
|
21038
|
+
/**
|
|
21039
|
+
* Moves a list item one step up or down by swapping its values with its
|
|
21040
|
+
* neighbour's, in every language. Items keep their numeric keys and the keys
|
|
21041
|
+
* keep their order; only the contents trade places. That is what makes the
|
|
21042
|
+
* move a set of ordinary drafts — publish, revert and the change count all
|
|
21043
|
+
* work without knowing a move happened.
|
|
21044
|
+
*/
|
|
21045
|
+
moveListItem(prefix, itemKey, direction, _lng) {
|
|
21046
|
+
const keys = this.getListKeys(prefix, _lng).sort(compareListKeys);
|
|
21047
|
+
const index = keys.indexOf(itemKey);
|
|
21048
|
+
const targetIndex = index + direction;
|
|
21049
|
+
if (index < 0 || targetIndex < 0 || targetIndex >= keys.length) return false;
|
|
21050
|
+
const other = keys[targetIndex];
|
|
21051
|
+
this.batch(() => {
|
|
21052
|
+
for (const lang of this.languages(_lng)) {
|
|
21053
|
+
const a = `${prefix}.${itemKey}`;
|
|
21054
|
+
const b = `${prefix}.${other}`;
|
|
21055
|
+
const fieldsA = this.leavesUnder(a, lang).map((k) => k.slice(a.length + 1));
|
|
21056
|
+
const fieldsB = this.leavesUnder(b, lang).map((k) => k.slice(b.length + 1));
|
|
21057
|
+
const fields = [.../* @__PURE__ */ new Set([...fieldsA, ...fieldsB])];
|
|
21058
|
+
const valuesA = new Map(fields.map((f) => [f, this.valueOrUndefined(`${a}.${f}`, lang)]));
|
|
21059
|
+
const valuesB = new Map(fields.map((f) => [f, this.valueOrUndefined(`${b}.${f}`, lang)]));
|
|
21060
|
+
for (const f of fields) {
|
|
21061
|
+
this.setOrDelete(`${a}.${f}`, lang, valuesB.get(f));
|
|
21062
|
+
this.setOrDelete(`${b}.${f}`, lang, valuesA.get(f));
|
|
21063
|
+
}
|
|
21064
|
+
}
|
|
21065
|
+
});
|
|
21066
|
+
return true;
|
|
20982
21067
|
}
|
|
20983
21068
|
/** Re-sorts list keys after removal (optional compaction — not used by default). */
|
|
20984
21069
|
sortListKeys(prefix, lng) {
|
|
@@ -20999,6 +21084,64 @@ class I18nStore {
|
|
|
20999
21084
|
}
|
|
21000
21085
|
return out;
|
|
21001
21086
|
}
|
|
21087
|
+
/** Runs `fn` and notifies consumers once, however many edits it makes. */
|
|
21088
|
+
batch(fn) {
|
|
21089
|
+
this.batchDepth += 1;
|
|
21090
|
+
try {
|
|
21091
|
+
fn();
|
|
21092
|
+
} finally {
|
|
21093
|
+
this.batchDepth -= 1;
|
|
21094
|
+
}
|
|
21095
|
+
if (this.batchDepth === 0) this.notify();
|
|
21096
|
+
}
|
|
21097
|
+
languages(fallback) {
|
|
21098
|
+
return this.i18n.languages?.length ? [...this.i18n.languages] : [fallback];
|
|
21099
|
+
}
|
|
21100
|
+
valueOrUndefined(key, lng) {
|
|
21101
|
+
const resource = this.i18n.getResource(lng, this.ns, key);
|
|
21102
|
+
return resource === void 0 ? void 0 : String(resource);
|
|
21103
|
+
}
|
|
21104
|
+
setOrDelete(key, lng, value) {
|
|
21105
|
+
if (value === void 0) {
|
|
21106
|
+
if (this.i18n.getResource(lng, this.ns, key) !== void 0) this.delete(key, lng);
|
|
21107
|
+
} else {
|
|
21108
|
+
this.set(key, lng, value);
|
|
21109
|
+
}
|
|
21110
|
+
}
|
|
21111
|
+
/** Every string key under `prefix`, nested to any depth. */
|
|
21112
|
+
leavesUnder(prefix, lng) {
|
|
21113
|
+
const bundle = this.i18n.getResourceBundle(lng, this.ns) ?? {};
|
|
21114
|
+
const node = getNested(bundle, prefix);
|
|
21115
|
+
const out = [];
|
|
21116
|
+
const walk2 = (value, path) => {
|
|
21117
|
+
if (value && typeof value === "object") {
|
|
21118
|
+
for (const [k, v] of Object.entries(value)) {
|
|
21119
|
+
walk2(v, `${path}.${k}`);
|
|
21120
|
+
}
|
|
21121
|
+
} else if (value !== void 0) {
|
|
21122
|
+
out.push(path);
|
|
21123
|
+
}
|
|
21124
|
+
};
|
|
21125
|
+
walk2(node, prefix);
|
|
21126
|
+
return out;
|
|
21127
|
+
}
|
|
21128
|
+
deleteFromBundle(lng, key) {
|
|
21129
|
+
const bundle = this.i18n.getResourceBundle(lng, this.ns) ?? {};
|
|
21130
|
+
deleteNested(bundle, key);
|
|
21131
|
+
this.i18n.addResourceBundle(lng, this.ns, bundle, true, true);
|
|
21132
|
+
}
|
|
21133
|
+
restore(draft) {
|
|
21134
|
+
if (draft.created) {
|
|
21135
|
+
this.deleteFromBundle(draft.lng, draft.key);
|
|
21136
|
+
} else {
|
|
21137
|
+
this.i18n.addResource(draft.lng, this.ns, draft.key, draft.original);
|
|
21138
|
+
}
|
|
21139
|
+
}
|
|
21140
|
+
notify() {
|
|
21141
|
+
if (this.batchDepth > 0) return;
|
|
21142
|
+
this.i18n.emit("languageChanged", this.i18n.language);
|
|
21143
|
+
this.bump();
|
|
21144
|
+
}
|
|
21002
21145
|
bump() {
|
|
21003
21146
|
this.version += 1;
|
|
21004
21147
|
this.cachedSnapshot = { drafts: this.drafts, version: this.version };
|
|
@@ -21215,6 +21358,136 @@ function useSiteAccess(supabase, session, siteId) {
|
|
|
21215
21358
|
}, [supabase, session, siteId]);
|
|
21216
21359
|
return state;
|
|
21217
21360
|
}
|
|
21361
|
+
const PREVIEW_HOST = /(^|\.)site\.setto\.no$/i;
|
|
21362
|
+
function isPreviewHost(hostname) {
|
|
21363
|
+
return PREVIEW_HOST.test(hostname);
|
|
21364
|
+
}
|
|
21365
|
+
function SettoPreviewBar() {
|
|
21366
|
+
const { supabase, api, config, session, authLoading, currentSite, sites, editMode } = useSetto();
|
|
21367
|
+
const [onPreview, setOnPreview] = useState(false);
|
|
21368
|
+
const [busy, setBusy] = useState(false);
|
|
21369
|
+
const [error, setError] = useState(null);
|
|
21370
|
+
const [paid, setPaid] = useState(false);
|
|
21371
|
+
useEffect(() => {
|
|
21372
|
+
setOnPreview(isPreviewHost(window.location.hostname));
|
|
21373
|
+
setPaid(new URLSearchParams(window.location.search).get("betaling") === "ok");
|
|
21374
|
+
}, []);
|
|
21375
|
+
if (!onPreview || authLoading) return null;
|
|
21376
|
+
if (session && sites === null) return null;
|
|
21377
|
+
const bought = paid || currentSite?.status === "live";
|
|
21378
|
+
const owner = !!session && !!currentSite && !bought;
|
|
21379
|
+
const name = currentSite?.display_name?.trim() || config.displayName?.trim() || config.siteId;
|
|
21380
|
+
if (bought) return null;
|
|
21381
|
+
const signIn = () => {
|
|
21382
|
+
window.location.href = "/setto";
|
|
21383
|
+
};
|
|
21384
|
+
const publish = async () => {
|
|
21385
|
+
setBusy(true);
|
|
21386
|
+
setError(null);
|
|
21387
|
+
try {
|
|
21388
|
+
const token = (await supabase.auth.getSession()).data.session?.access_token;
|
|
21389
|
+
if (!token) {
|
|
21390
|
+
signIn();
|
|
21391
|
+
return;
|
|
21392
|
+
}
|
|
21393
|
+
const data = await api.request(
|
|
21394
|
+
`/onboarding/sites/${config.siteId}/checkout`,
|
|
21395
|
+
{
|
|
21396
|
+
method: "POST",
|
|
21397
|
+
// Back to the page they were looking at, not to a chat window.
|
|
21398
|
+
body: { returnTo: window.location.origin + window.location.pathname }
|
|
21399
|
+
},
|
|
21400
|
+
"checkout"
|
|
21401
|
+
);
|
|
21402
|
+
if (!data.url) throw new Error("checkout_failed");
|
|
21403
|
+
window.location.href = data.url;
|
|
21404
|
+
} catch (err) {
|
|
21405
|
+
const detail = err instanceof Error ? err.message : "";
|
|
21406
|
+
setError(
|
|
21407
|
+
detail.includes("already_paid") ? "Denne siden er allerede betalt." : detail.includes("not_found") ? "Fant ingen bestilling for denne siden på kontoen din." : detail.includes("stripe_not_configured") ? "Betaling er ikke satt opp ennå." : "Klarte ikke starte betalingen. Prøv igjen."
|
|
21408
|
+
);
|
|
21409
|
+
setBusy(false);
|
|
21410
|
+
}
|
|
21411
|
+
};
|
|
21412
|
+
return createPortal(
|
|
21413
|
+
/*
|
|
21414
|
+
* Lifted above the editor's floating button when edit mode is on, which
|
|
21415
|
+
* sits in the same corner band. Sideways would collide again the moment
|
|
21416
|
+
* its toolbar opens, since that expands rightward.
|
|
21417
|
+
*/
|
|
21418
|
+
/* @__PURE__ */ jsx(
|
|
21419
|
+
"div",
|
|
21420
|
+
{
|
|
21421
|
+
style: editMode ? { ...barStyle, bottom: "calc(max(16px, env(safe-area-inset-bottom, 0px)) + 68px)" } : barStyle,
|
|
21422
|
+
"data-setto-preview-bar": "",
|
|
21423
|
+
children: owner ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
21424
|
+
/* @__PURE__ */ jsx("span", { style: dotStyle, "aria-hidden": true }),
|
|
21425
|
+
/* @__PURE__ */ jsxs("span", { style: labelStyle, children: [
|
|
21426
|
+
"Du forhåndsviser nå ",
|
|
21427
|
+
/* @__PURE__ */ jsx("strong", { style: { fontWeight: 600 }, children: name })
|
|
21428
|
+
] }),
|
|
21429
|
+
error && /* @__PURE__ */ jsx("span", { style: errorStyle$1, children: error }),
|
|
21430
|
+
/* @__PURE__ */ jsx("button", { type: "button", style: primaryStyle, onClick: publish, disabled: busy, children: busy ? "Åpner betaling …" : "Publiser" })
|
|
21431
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
21432
|
+
/* @__PURE__ */ jsx("span", { style: dotStyle, "aria-hidden": true }),
|
|
21433
|
+
/* @__PURE__ */ jsxs("span", { style: labelStyle, children: [
|
|
21434
|
+
"Forhåndsvisning av ",
|
|
21435
|
+
name
|
|
21436
|
+
] }),
|
|
21437
|
+
/* @__PURE__ */ jsx("button", { type: "button", style: primaryStyle, onClick: signIn, children: "Rediger" })
|
|
21438
|
+
] })
|
|
21439
|
+
}
|
|
21440
|
+
),
|
|
21441
|
+
document.body
|
|
21442
|
+
);
|
|
21443
|
+
}
|
|
21444
|
+
const barStyle = {
|
|
21445
|
+
position: "fixed",
|
|
21446
|
+
left: "50%",
|
|
21447
|
+
bottom: "max(16px, env(safe-area-inset-bottom, 0px))",
|
|
21448
|
+
transform: "translateX(-50%)",
|
|
21449
|
+
zIndex: 2147483e3,
|
|
21450
|
+
display: "flex",
|
|
21451
|
+
alignItems: "center",
|
|
21452
|
+
gap: 14,
|
|
21453
|
+
maxWidth: "calc(100vw - 24px)",
|
|
21454
|
+
padding: "10px 10px 10px 18px",
|
|
21455
|
+
borderRadius: 999,
|
|
21456
|
+
background: "#211711",
|
|
21457
|
+
color: "#fffdf8",
|
|
21458
|
+
fontFamily: 'system-ui, -apple-system, "Segoe UI", sans-serif',
|
|
21459
|
+
fontSize: 14,
|
|
21460
|
+
lineHeight: 1.3,
|
|
21461
|
+
boxShadow: "0 18px 40px -18px rgba(0, 0, 0, 0.65)"
|
|
21462
|
+
};
|
|
21463
|
+
const dotStyle = {
|
|
21464
|
+
width: 7,
|
|
21465
|
+
height: 7,
|
|
21466
|
+
borderRadius: "50%",
|
|
21467
|
+
background: "#c4502a",
|
|
21468
|
+
flex: "none"
|
|
21469
|
+
};
|
|
21470
|
+
const labelStyle = {
|
|
21471
|
+
whiteSpace: "nowrap",
|
|
21472
|
+
overflow: "hidden",
|
|
21473
|
+
textOverflow: "ellipsis"
|
|
21474
|
+
};
|
|
21475
|
+
const primaryStyle = {
|
|
21476
|
+
flex: "none",
|
|
21477
|
+
padding: "9px 18px",
|
|
21478
|
+
border: 0,
|
|
21479
|
+
borderRadius: 999,
|
|
21480
|
+
background: "#c4502a",
|
|
21481
|
+
color: "#fffdf8",
|
|
21482
|
+
font: "inherit",
|
|
21483
|
+
fontWeight: 600,
|
|
21484
|
+
cursor: "pointer"
|
|
21485
|
+
};
|
|
21486
|
+
const errorStyle$1 = {
|
|
21487
|
+
color: "#ffb4a2",
|
|
21488
|
+
fontSize: 13,
|
|
21489
|
+
whiteSpace: "nowrap"
|
|
21490
|
+
};
|
|
21218
21491
|
const SETTO_BASE = "/setto";
|
|
21219
21492
|
function siteHomeUrl(pathname = "/") {
|
|
21220
21493
|
const u = new URL(window.location.href);
|
|
@@ -21228,9 +21501,18 @@ function isAdminRoute() {
|
|
|
21228
21501
|
function isAdminDeploymentView() {
|
|
21229
21502
|
return isAdminRoute() && new URLSearchParams(window.location.search).has("deployment");
|
|
21230
21503
|
}
|
|
21504
|
+
function adminViewUrl(view) {
|
|
21505
|
+
return `${SETTO_BASE}?view=${encodeURIComponent(view)}`;
|
|
21506
|
+
}
|
|
21507
|
+
function currentAdminView() {
|
|
21508
|
+
return new URLSearchParams(window.location.search).get("view");
|
|
21509
|
+
}
|
|
21510
|
+
function isAdminPanelView() {
|
|
21511
|
+
return isAdminDeploymentView() || isAdminRoute() && currentAdminView() !== null;
|
|
21512
|
+
}
|
|
21231
21513
|
const FAB_SIZE = 52;
|
|
21232
21514
|
const FAB_INSET = 16;
|
|
21233
|
-
const STYLE_ID$
|
|
21515
|
+
const STYLE_ID$3 = "setto-edit-layout";
|
|
21234
21516
|
const HTML_CLASS = "setto-edit-mode";
|
|
21235
21517
|
function useSettoDocumentLayout(active) {
|
|
21236
21518
|
useEffect(() => {
|
|
@@ -21239,10 +21521,10 @@ function useSettoDocumentLayout(active) {
|
|
|
21239
21521
|
html.classList.add(HTML_CLASS);
|
|
21240
21522
|
html.style.setProperty("--setto-fab-size", `${FAB_SIZE}px`);
|
|
21241
21523
|
html.style.setProperty("--setto-fab-inset", `${FAB_INSET}px`);
|
|
21242
|
-
let styleEl = document.getElementById(STYLE_ID$
|
|
21524
|
+
let styleEl = document.getElementById(STYLE_ID$3);
|
|
21243
21525
|
if (!styleEl) {
|
|
21244
21526
|
styleEl = document.createElement("style");
|
|
21245
|
-
styleEl.id = STYLE_ID$
|
|
21527
|
+
styleEl.id = STYLE_ID$3;
|
|
21246
21528
|
styleEl.textContent = `
|
|
21247
21529
|
html.setto-edit-mode {
|
|
21248
21530
|
width: 100%;
|
|
@@ -21888,10 +22170,10 @@ function EditOnboardingDialog() {
|
|
|
21888
22170
|
]
|
|
21889
22171
|
}
|
|
21890
22172
|
),
|
|
21891
|
-
/* @__PURE__ */ jsx("button", { type: "button", onClick: close, style: primaryBtnStyle$
|
|
22173
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: close, style: primaryBtnStyle$2, children: "Forstått" })
|
|
21892
22174
|
] });
|
|
21893
22175
|
}
|
|
21894
|
-
const primaryBtnStyle$
|
|
22176
|
+
const primaryBtnStyle$2 = {
|
|
21895
22177
|
background: "#640AFF",
|
|
21896
22178
|
color: "#fff",
|
|
21897
22179
|
border: "none",
|
|
@@ -22130,14 +22412,14 @@ function PublishDialog({
|
|
|
22130
22412
|
return /* @__PURE__ */ jsx(EditOverlay, { onClose: void 0, children: isReady ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
22131
22413
|
/* @__PURE__ */ jsx("h2", { style: { margin: "0 0 12px", fontSize: 18, fontWeight: 600 }, children: "Publisert" }),
|
|
22132
22414
|
/* @__PURE__ */ jsx("p", { style: { margin: "0 0 20px", fontSize: 14, lineHeight: 1.5, color: "#444" }, children: "Endringene dine er live." }),
|
|
22133
|
-
/* @__PURE__ */ jsx("button", { type: "button", onClick: onComplete, style: primaryBtnStyle, children: "OK" })
|
|
22415
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: onComplete, style: primaryBtnStyle$1, children: "OK" })
|
|
22134
22416
|
] }) : errored ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
22135
22417
|
/* @__PURE__ */ jsx("h2", { style: { margin: "0 0 12px", fontSize: 18, fontWeight: 600, color: "#dc2626" }, children: "Publisering feilet" }),
|
|
22136
22418
|
/* @__PURE__ */ jsxs("p", { style: { margin: "0 0 20px", fontSize: 14, lineHeight: 1.5, color: "#444" }, children: [
|
|
22137
22419
|
progressStatus === "error" ? "Bygg feilet." : "Avbrutt.",
|
|
22138
22420
|
row?.error_message ? ` ${row.error_message}` : ""
|
|
22139
22421
|
] }),
|
|
22140
|
-
/* @__PURE__ */ jsx("button", { type: "button", onClick: onComplete, style: secondaryBtnStyle, children: "OK" })
|
|
22422
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: onComplete, style: secondaryBtnStyle$1, children: "OK" })
|
|
22141
22423
|
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
22142
22424
|
/* @__PURE__ */ jsx("h2", { style: { margin: "0 0 16px", fontSize: 16, fontWeight: 600 }, children: "Publiserer endringer" }),
|
|
22143
22425
|
/* @__PURE__ */ jsx("div", { style: { marginBottom: 20 }, children: /* @__PURE__ */ jsx(
|
|
@@ -22147,10 +22429,10 @@ function PublishDialog({
|
|
|
22147
22429
|
buildingElapsed: isBuilding ? elapsed : void 0
|
|
22148
22430
|
}
|
|
22149
22431
|
) }),
|
|
22150
|
-
/* @__PURE__ */ jsx("button", { type: "button", onClick: onClose, style: secondaryBtnStyle, children: "Lukk" })
|
|
22432
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: onClose, style: secondaryBtnStyle$1, children: "Lukk" })
|
|
22151
22433
|
] }) });
|
|
22152
22434
|
}
|
|
22153
|
-
const primaryBtnStyle = {
|
|
22435
|
+
const primaryBtnStyle$1 = {
|
|
22154
22436
|
background: "#640AFF",
|
|
22155
22437
|
color: "#fff",
|
|
22156
22438
|
border: "none",
|
|
@@ -22161,8 +22443,8 @@ const primaryBtnStyle = {
|
|
|
22161
22443
|
cursor: "pointer",
|
|
22162
22444
|
width: "100%"
|
|
22163
22445
|
};
|
|
22164
|
-
const secondaryBtnStyle = {
|
|
22165
|
-
...primaryBtnStyle,
|
|
22446
|
+
const secondaryBtnStyle$1 = {
|
|
22447
|
+
...primaryBtnStyle$1,
|
|
22166
22448
|
background: "transparent",
|
|
22167
22449
|
color: "#666",
|
|
22168
22450
|
border: "1px solid #ddd"
|
|
@@ -22216,292 +22498,2125 @@ function useEditBaseline() {
|
|
|
22216
22498
|
};
|
|
22217
22499
|
}, [session, store, config.siteId]);
|
|
22218
22500
|
}
|
|
22219
|
-
|
|
22220
|
-
|
|
22221
|
-
|
|
22222
|
-
|
|
22223
|
-
|
|
22224
|
-
}
|
|
22225
|
-
|
|
22226
|
-
|
|
22227
|
-
|
|
22228
|
-
|
|
22229
|
-
|
|
22230
|
-
|
|
22231
|
-
|
|
22232
|
-
|
|
22233
|
-
|
|
22234
|
-
|
|
22235
|
-
|
|
22236
|
-
|
|
22237
|
-
|
|
22238
|
-
|
|
22239
|
-
|
|
22240
|
-
|
|
22241
|
-
|
|
22242
|
-
|
|
22243
|
-
|
|
22244
|
-
|
|
22245
|
-
|
|
22246
|
-
|
|
22247
|
-
|
|
22248
|
-
|
|
22249
|
-
|
|
22250
|
-
|
|
22251
|
-
|
|
22501
|
+
const shellStyle$1 = {
|
|
22502
|
+
minHeight: "100dvh",
|
|
22503
|
+
background: "#0a0a0d",
|
|
22504
|
+
color: "#fff",
|
|
22505
|
+
fontFamily: 'system-ui, -apple-system, "Segoe UI", sans-serif'
|
|
22506
|
+
};
|
|
22507
|
+
const headerStyle = {
|
|
22508
|
+
display: "flex",
|
|
22509
|
+
alignItems: "center",
|
|
22510
|
+
justifyContent: "space-between",
|
|
22511
|
+
padding: "16px 24px",
|
|
22512
|
+
borderBottom: "1px solid #1c1c22"
|
|
22513
|
+
};
|
|
22514
|
+
const mainStyle = {
|
|
22515
|
+
maxWidth: 720,
|
|
22516
|
+
margin: "0 auto",
|
|
22517
|
+
padding: 24,
|
|
22518
|
+
display: "flex",
|
|
22519
|
+
flexDirection: "column",
|
|
22520
|
+
gap: 16
|
|
22521
|
+
};
|
|
22522
|
+
const cardStyle = {
|
|
22523
|
+
background: "#15151a",
|
|
22524
|
+
borderRadius: 12,
|
|
22525
|
+
padding: 20,
|
|
22526
|
+
display: "flex",
|
|
22527
|
+
flexDirection: "column",
|
|
22528
|
+
gap: 12
|
|
22529
|
+
};
|
|
22530
|
+
const panelStyle = {
|
|
22531
|
+
background: "#1a1a20",
|
|
22532
|
+
borderRadius: 8,
|
|
22533
|
+
padding: 14,
|
|
22534
|
+
display: "flex",
|
|
22535
|
+
flexDirection: "column",
|
|
22536
|
+
gap: 10
|
|
22537
|
+
};
|
|
22538
|
+
const rowStyle = {
|
|
22539
|
+
display: "flex",
|
|
22540
|
+
alignItems: "center",
|
|
22541
|
+
gap: 10,
|
|
22542
|
+
background: "#1a1a20",
|
|
22543
|
+
padding: "6px 10px",
|
|
22544
|
+
borderRadius: 6
|
|
22545
|
+
};
|
|
22546
|
+
const mutedStyle = {
|
|
22547
|
+
color: "#888",
|
|
22548
|
+
fontSize: 13,
|
|
22549
|
+
margin: 0
|
|
22550
|
+
};
|
|
22551
|
+
const errStyle = {
|
|
22552
|
+
color: "#ff7a7a",
|
|
22553
|
+
fontSize: 13,
|
|
22554
|
+
margin: 0
|
|
22555
|
+
};
|
|
22556
|
+
const subheadingStyle = {
|
|
22557
|
+
margin: 0,
|
|
22558
|
+
fontSize: 14,
|
|
22559
|
+
color: "#aaa"
|
|
22560
|
+
};
|
|
22561
|
+
const primaryBtnStyle = {
|
|
22562
|
+
display: "inline-block",
|
|
22563
|
+
background: "#640AFF",
|
|
22564
|
+
color: "#fff",
|
|
22565
|
+
border: "none",
|
|
22566
|
+
borderRadius: 6,
|
|
22567
|
+
padding: "10px 14px",
|
|
22568
|
+
fontSize: 14,
|
|
22569
|
+
fontWeight: 500,
|
|
22570
|
+
textDecoration: "none",
|
|
22571
|
+
cursor: "pointer"
|
|
22572
|
+
};
|
|
22573
|
+
const secondaryBtnStyle = {
|
|
22574
|
+
background: "transparent",
|
|
22575
|
+
color: "#bda6ff",
|
|
22576
|
+
border: "1px solid #2a2a32",
|
|
22577
|
+
borderRadius: 6,
|
|
22578
|
+
padding: "6px 10px",
|
|
22579
|
+
fontSize: 12,
|
|
22580
|
+
cursor: "pointer"
|
|
22581
|
+
};
|
|
22582
|
+
const dangerBtnStyle = {
|
|
22583
|
+
alignSelf: "flex-start",
|
|
22584
|
+
background: "transparent",
|
|
22585
|
+
color: "#ff7a7a",
|
|
22586
|
+
border: "1px solid #4a3030",
|
|
22587
|
+
borderRadius: 6,
|
|
22588
|
+
padding: "8px 14px",
|
|
22589
|
+
fontSize: 13,
|
|
22590
|
+
cursor: "pointer"
|
|
22591
|
+
};
|
|
22592
|
+
const inputStyle$2 = {
|
|
22593
|
+
width: "100%",
|
|
22594
|
+
boxSizing: "border-box",
|
|
22595
|
+
background: "#0f0f13",
|
|
22596
|
+
color: "#fff",
|
|
22597
|
+
border: "1px solid #2a2a32",
|
|
22598
|
+
borderRadius: 6,
|
|
22599
|
+
padding: "8px 10px",
|
|
22600
|
+
fontSize: 13,
|
|
22601
|
+
fontFamily: "inherit"
|
|
22602
|
+
};
|
|
22603
|
+
const tabBarStyle = {
|
|
22604
|
+
display: "flex",
|
|
22605
|
+
gap: 4,
|
|
22606
|
+
borderBottom: "1px solid #1c1c22",
|
|
22607
|
+
paddingBottom: 0
|
|
22608
|
+
};
|
|
22609
|
+
function tabStyle(active) {
|
|
22610
|
+
return {
|
|
22611
|
+
background: "transparent",
|
|
22612
|
+
border: "none",
|
|
22613
|
+
borderBottom: active ? "2px solid #640AFF" : "2px solid transparent",
|
|
22614
|
+
color: active ? "#fff" : "#888",
|
|
22615
|
+
padding: "8px 12px",
|
|
22616
|
+
fontSize: 13,
|
|
22617
|
+
fontWeight: 500,
|
|
22618
|
+
cursor: "pointer",
|
|
22619
|
+
marginBottom: -1
|
|
22620
|
+
};
|
|
22252
22621
|
}
|
|
22253
|
-
|
|
22254
|
-
|
|
22255
|
-
|
|
22256
|
-
|
|
22257
|
-
|
|
22258
|
-
|
|
22259
|
-
|
|
22260
|
-
const
|
|
22261
|
-
|
|
22262
|
-
|
|
22263
|
-
|
|
22264
|
-
|
|
22265
|
-
);
|
|
22266
|
-
useSyncExternalStore(
|
|
22267
|
-
(cb) => themeStore?.subscribe(() => cb()) ?? (() => {
|
|
22268
|
-
}),
|
|
22269
|
-
() => themeStore?.snapshot().version ?? 0,
|
|
22270
|
-
() => 0
|
|
22271
|
-
);
|
|
22272
|
-
useSyncExternalStore(
|
|
22273
|
-
(cb) => assetStore?.subscribe(() => cb()) ?? (() => {
|
|
22274
|
-
}),
|
|
22275
|
-
() => assetStore?.snapshot().version ?? 0,
|
|
22276
|
-
() => 0
|
|
22277
|
-
);
|
|
22278
|
-
const [publishing, setPublishing] = useState(false);
|
|
22279
|
-
const [activeDeployment, setActiveDeployment] = useState(null);
|
|
22280
|
-
const [publishDialogOpen, setPublishDialogOpen] = useState(false);
|
|
22622
|
+
function FormsView({ siteId }) {
|
|
22623
|
+
return /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 16 }, children: [
|
|
22624
|
+
/* @__PURE__ */ jsx(RecipientsPanel, { siteId }),
|
|
22625
|
+
/* @__PURE__ */ jsx(SubmissionList, { siteId })
|
|
22626
|
+
] });
|
|
22627
|
+
}
|
|
22628
|
+
function RecipientsPanel({ siteId }) {
|
|
22629
|
+
const { api } = useSetto();
|
|
22630
|
+
const [settings, setSettings] = useState(null);
|
|
22631
|
+
const [editing, setEditing] = useState(false);
|
|
22632
|
+
const [draft, setDraft] = useState("");
|
|
22633
|
+
const [saving, setSaving] = useState(false);
|
|
22281
22634
|
const [error, setError] = useState(null);
|
|
22282
|
-
const [publishedNotice, setPublishedNotice] = useState(false);
|
|
22283
|
-
const [menuOpen, setMenuOpen] = useState(false);
|
|
22284
|
-
const [expanded, setExpanded] = useState(false);
|
|
22285
|
-
const menuRef = useRef(null);
|
|
22286
|
-
const publishedNoticeTimer = useRef(null);
|
|
22287
|
-
const { row: deploymentRow, status: deploymentStatus } = useDeploymentStatus(
|
|
22288
|
-
supabase,
|
|
22289
|
-
activeDeployment
|
|
22290
|
-
);
|
|
22291
22635
|
useEffect(() => {
|
|
22292
22636
|
let cancelled = false;
|
|
22293
|
-
|
|
22294
|
-
|
|
22295
|
-
|
|
22296
|
-
|
|
22297
|
-
|
|
22298
|
-
const id = await fetchInProgressDeploymentId(supabase, config.siteId);
|
|
22299
|
-
if (!cancelled && id) {
|
|
22300
|
-
setActiveDeployment((current) => current ?? id);
|
|
22301
|
-
}
|
|
22302
|
-
};
|
|
22303
|
-
void resume();
|
|
22637
|
+
api.forms.getSettings(siteId).then((s) => {
|
|
22638
|
+
if (!cancelled) setSettings(s);
|
|
22639
|
+
}).catch((err) => {
|
|
22640
|
+
if (!cancelled) setError(err instanceof Error ? err.message : "Kunne ikke hente innstillinger");
|
|
22641
|
+
});
|
|
22304
22642
|
return () => {
|
|
22305
22643
|
cancelled = true;
|
|
22306
22644
|
};
|
|
22307
|
-
}, [api,
|
|
22308
|
-
const
|
|
22309
|
-
|
|
22310
|
-
|
|
22311
|
-
|
|
22312
|
-
|
|
22313
|
-
const
|
|
22314
|
-
|
|
22315
|
-
|
|
22316
|
-
|
|
22317
|
-
|
|
22645
|
+
}, [api, siteId]);
|
|
22646
|
+
const startEditing = () => {
|
|
22647
|
+
setDraft((settings?.recipients ?? []).join("\n"));
|
|
22648
|
+
setEditing(true);
|
|
22649
|
+
setError(null);
|
|
22650
|
+
};
|
|
22651
|
+
const save = async (recipients) => {
|
|
22652
|
+
setSaving(true);
|
|
22653
|
+
setError(null);
|
|
22654
|
+
try {
|
|
22655
|
+
const next = await api.forms.saveSettings(siteId, recipients);
|
|
22656
|
+
setSettings(next);
|
|
22657
|
+
setEditing(false);
|
|
22658
|
+
} catch (err) {
|
|
22659
|
+
setError(err instanceof Error ? err.message : "Kunne ikke lagre");
|
|
22660
|
+
} finally {
|
|
22661
|
+
setSaving(false);
|
|
22318
22662
|
}
|
|
22319
|
-
}, [error, showToolbarProgress, showPublishedNotice]);
|
|
22320
|
-
const toggleExpanded = () => {
|
|
22321
|
-
setExpanded((prev) => {
|
|
22322
|
-
const next = !prev;
|
|
22323
|
-
if (!next) setMenuOpen(false);
|
|
22324
|
-
return next;
|
|
22325
|
-
});
|
|
22326
22663
|
};
|
|
22664
|
+
const parseDraft = () => draft.split(/[\n,;]+/).map((s) => s.trim()).filter(Boolean);
|
|
22665
|
+
return /* @__PURE__ */ jsxs("section", { style: panelStyle, children: [
|
|
22666
|
+
/* @__PURE__ */ jsx("h3", { style: { margin: 0, fontSize: 15 }, children: "Hvem får e-post?" }),
|
|
22667
|
+
error ? /* @__PURE__ */ jsx("p", { style: errStyle, children: error }) : null,
|
|
22668
|
+
settings === null && !error ? /* @__PURE__ */ jsx("p", { style: mutedStyle, children: "Laster …" }) : null,
|
|
22669
|
+
settings && !editing ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
22670
|
+
/* @__PURE__ */ jsx("p", { style: { margin: 0, fontSize: 14 }, children: settings.effectiveRecipients.length > 0 ? settings.effectiveRecipients.join(", ") : /* @__PURE__ */ jsx("span", { style: { color: "#ff7a7a" }, children: "Ingen mottakere — innsendinger lagres, men ingen får beskjed." }) }),
|
|
22671
|
+
/* @__PURE__ */ jsx("p", { style: mutedStyle, children: settings.recipients.length === 0 ? "Standard: eierne av siden. Hver innsending lagres også her i historikken." : `Standard er ${settings.defaultRecipients.join(", ") || "ingen"}.` }),
|
|
22672
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8 }, children: [
|
|
22673
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: startEditing, style: secondaryBtnStyle, children: "Endre mottakere" }),
|
|
22674
|
+
settings.recipients.length > 0 ? /* @__PURE__ */ jsx("button", { type: "button", onClick: () => void save([]), disabled: saving, style: secondaryBtnStyle, children: "Bruk standard" }) : null
|
|
22675
|
+
] })
|
|
22676
|
+
] }) : null,
|
|
22677
|
+
settings && editing ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
22678
|
+
/* @__PURE__ */ jsxs("label", { style: { display: "grid", gap: 6 }, children: [
|
|
22679
|
+
/* @__PURE__ */ jsx("span", { style: mutedStyle, children: "E-postadresser, én per linje. Tomt betyr standard (eierne)." }),
|
|
22680
|
+
/* @__PURE__ */ jsx(
|
|
22681
|
+
"textarea",
|
|
22682
|
+
{
|
|
22683
|
+
value: draft,
|
|
22684
|
+
rows: 3,
|
|
22685
|
+
onChange: (e) => setDraft(e.target.value),
|
|
22686
|
+
style: { ...inputStyle$2, resize: "vertical" },
|
|
22687
|
+
placeholder: settings.defaultRecipients.join("\n"),
|
|
22688
|
+
autoFocus: true
|
|
22689
|
+
}
|
|
22690
|
+
)
|
|
22691
|
+
] }),
|
|
22692
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8 }, children: [
|
|
22693
|
+
/* @__PURE__ */ jsx(
|
|
22694
|
+
"button",
|
|
22695
|
+
{
|
|
22696
|
+
type: "button",
|
|
22697
|
+
onClick: () => void save(parseDraft()),
|
|
22698
|
+
disabled: saving,
|
|
22699
|
+
style: { ...primaryBtnStyle, padding: "8px 12px", fontSize: 13 },
|
|
22700
|
+
children: saving ? "Lagrer …" : "Lagre"
|
|
22701
|
+
}
|
|
22702
|
+
),
|
|
22703
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => setEditing(false), disabled: saving, style: secondaryBtnStyle, children: "Avbryt" })
|
|
22704
|
+
] })
|
|
22705
|
+
] }) : null
|
|
22706
|
+
] });
|
|
22707
|
+
}
|
|
22708
|
+
const PAGE = 50;
|
|
22709
|
+
function SubmissionList({ siteId }) {
|
|
22710
|
+
const { supabase, api } = useSetto();
|
|
22711
|
+
const [rows, setRows] = useState(null);
|
|
22712
|
+
const [error, setError] = useState(null);
|
|
22713
|
+
const [openId, setOpenId] = useState(null);
|
|
22714
|
+
const [hasMore, setHasMore] = useState(false);
|
|
22715
|
+
const [loadingMore, setLoadingMore] = useState(false);
|
|
22327
22716
|
useEffect(() => {
|
|
22717
|
+
let cancelled = false;
|
|
22718
|
+
supabase.from("form_submissions").select("*").eq("site_id", siteId).order("created_at", { ascending: false }).limit(PAGE).then(({ data, error: err }) => {
|
|
22719
|
+
if (cancelled) return;
|
|
22720
|
+
if (err) setError(err.message);
|
|
22721
|
+
const list = data ?? [];
|
|
22722
|
+
setRows(list);
|
|
22723
|
+
setHasMore(list.length === PAGE);
|
|
22724
|
+
});
|
|
22725
|
+
const channel = supabase.channel(`setto-site-forms-${siteId}`).on(
|
|
22726
|
+
"postgres_changes",
|
|
22727
|
+
{ event: "*", schema: "public", table: "form_submissions", filter: `site_id=eq.${siteId}` },
|
|
22728
|
+
(payload) => {
|
|
22729
|
+
const next = payload.new;
|
|
22730
|
+
if (!next || !next.id) return;
|
|
22731
|
+
setRows((prev) => {
|
|
22732
|
+
if (!prev) return [next];
|
|
22733
|
+
const without = prev.filter((r) => r.id !== next.id);
|
|
22734
|
+
return [next, ...without].sort((a, b) => b.created_at.localeCompare(a.created_at));
|
|
22735
|
+
});
|
|
22736
|
+
}
|
|
22737
|
+
).subscribe();
|
|
22328
22738
|
return () => {
|
|
22329
|
-
|
|
22739
|
+
cancelled = true;
|
|
22740
|
+
supabase.removeChannel(channel);
|
|
22330
22741
|
};
|
|
22331
|
-
}, []);
|
|
22332
|
-
|
|
22333
|
-
if (!
|
|
22334
|
-
|
|
22335
|
-
|
|
22336
|
-
|
|
22337
|
-
|
|
22338
|
-
|
|
22339
|
-
|
|
22340
|
-
publishedNoticeTimer.current = setTimeout(() => setPublishedNotice(false), 6e3);
|
|
22341
|
-
return;
|
|
22342
|
-
}
|
|
22343
|
-
if (publishDialogOpen) return;
|
|
22344
|
-
if (status === "error" || status === "canceled") {
|
|
22345
|
-
setError(
|
|
22346
|
-
status === "canceled" ? "Publisering avbrutt." : deploymentRow.error_message?.includes("webhook") ? "Publisering utløpt (ingen webhook)." : "Publisering feilet."
|
|
22347
|
-
);
|
|
22348
|
-
setActiveDeployment(null);
|
|
22742
|
+
}, [supabase, siteId]);
|
|
22743
|
+
const loadMore = async () => {
|
|
22744
|
+
if (!rows || rows.length === 0) return;
|
|
22745
|
+
setLoadingMore(true);
|
|
22746
|
+
const oldest = rows[rows.length - 1].created_at;
|
|
22747
|
+
const { data, error: err } = await supabase.from("form_submissions").select("*").eq("site_id", siteId).lt("created_at", oldest).order("created_at", { ascending: false }).limit(PAGE);
|
|
22748
|
+
setLoadingMore(false);
|
|
22749
|
+
if (err) {
|
|
22750
|
+
setError(err.message);
|
|
22349
22751
|
return;
|
|
22350
22752
|
}
|
|
22351
|
-
|
|
22352
|
-
|
|
22353
|
-
|
|
22753
|
+
const more = data ?? [];
|
|
22754
|
+
setRows((prev) => [...prev ?? [], ...more]);
|
|
22755
|
+
setHasMore(more.length === PAGE);
|
|
22756
|
+
};
|
|
22757
|
+
const remove2 = async (id) => {
|
|
22758
|
+
if (!window.confirm("Slette denne henvendelsen? Dette kan ikke angres.")) return;
|
|
22759
|
+
try {
|
|
22760
|
+
await api.forms.deleteSubmission(siteId, id);
|
|
22761
|
+
setRows((prev) => (prev ?? []).filter((r) => r.id !== id));
|
|
22762
|
+
if (openId === id) setOpenId(null);
|
|
22763
|
+
} catch (err) {
|
|
22764
|
+
setError(err instanceof Error ? err.message : "Kunne ikke slette");
|
|
22354
22765
|
}
|
|
22355
|
-
}
|
|
22356
|
-
|
|
22357
|
-
|
|
22358
|
-
|
|
22359
|
-
|
|
22360
|
-
|
|
22361
|
-
|
|
22362
|
-
|
|
22363
|
-
|
|
22364
|
-
|
|
22365
|
-
|
|
22366
|
-
|
|
22367
|
-
|
|
22368
|
-
|
|
22369
|
-
|
|
22370
|
-
|
|
22371
|
-
|
|
22372
|
-
|
|
22373
|
-
|
|
22374
|
-
|
|
22375
|
-
|
|
22376
|
-
|
|
22377
|
-
|
|
22766
|
+
};
|
|
22767
|
+
if (!rows) return /* @__PURE__ */ jsx("p", { style: mutedStyle, children: "Laster henvendelser …" });
|
|
22768
|
+
return /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 12 }, children: [
|
|
22769
|
+
/* @__PURE__ */ jsx("h3", { style: subheadingStyle, children: "Henvendelser" }),
|
|
22770
|
+
error ? /* @__PURE__ */ jsx("p", { style: errStyle, children: error }) : null,
|
|
22771
|
+
rows.length === 0 ? /* @__PURE__ */ jsx("p", { style: mutedStyle, children: "Ingen henvendelser ennå. Når noen sender inn et skjema på siden, dukker det opp her og i e-posten." }) : /* @__PURE__ */ jsx("ul", { style: { listStyle: "none", padding: 0, margin: 0, display: "flex", flexDirection: "column", gap: 6 }, children: rows.map((row) => /* @__PURE__ */ jsx(
|
|
22772
|
+
SubmissionItem,
|
|
22773
|
+
{
|
|
22774
|
+
row,
|
|
22775
|
+
open: openId === row.id,
|
|
22776
|
+
onToggle: () => setOpenId(openId === row.id ? null : row.id),
|
|
22777
|
+
onDelete: () => void remove2(row.id)
|
|
22778
|
+
},
|
|
22779
|
+
row.id
|
|
22780
|
+
)) }),
|
|
22781
|
+
hasMore ? /* @__PURE__ */ jsx("button", { type: "button", onClick: () => void loadMore(), disabled: loadingMore, style: secondaryBtnStyle, children: loadingMore ? "Laster …" : "Vis eldre" }) : null
|
|
22782
|
+
] });
|
|
22783
|
+
}
|
|
22784
|
+
function SubmissionItem({
|
|
22785
|
+
row,
|
|
22786
|
+
open,
|
|
22787
|
+
onToggle,
|
|
22788
|
+
onDelete
|
|
22789
|
+
}) {
|
|
22790
|
+
const fields = submissionFields(row);
|
|
22791
|
+
const sender = senderOf(fields);
|
|
22792
|
+
const title = row.meta.title ?? row.form_id;
|
|
22793
|
+
return /* @__PURE__ */ jsxs("li", { children: [
|
|
22794
|
+
/* @__PURE__ */ jsxs(
|
|
22795
|
+
"button",
|
|
22796
|
+
{
|
|
22797
|
+
type: "button",
|
|
22798
|
+
onClick: onToggle,
|
|
22799
|
+
"aria-expanded": open,
|
|
22800
|
+
style: { ...rowStyle, width: "100%", textAlign: "left", color: "inherit", border: "none", cursor: "pointer", font: "inherit" },
|
|
22801
|
+
children: [
|
|
22802
|
+
/* @__PURE__ */ jsx("span", { style: statusDotStyle(row.email_status), title: emailStatusLabel(row) }),
|
|
22803
|
+
/* @__PURE__ */ jsxs("span", { style: { flex: 1, fontSize: 13, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: [
|
|
22804
|
+
sender ?? /* @__PURE__ */ jsx("span", { style: { color: "#aaa" }, children: "Uten navn" }),
|
|
22805
|
+
/* @__PURE__ */ jsxs("span", { style: { color: "#888" }, children: [
|
|
22806
|
+
" · ",
|
|
22807
|
+
title
|
|
22808
|
+
] })
|
|
22809
|
+
] }),
|
|
22810
|
+
/* @__PURE__ */ jsx("time", { style: { fontSize: 11, color: "#888", flexShrink: 0 }, children: formatWhen(row.created_at) })
|
|
22811
|
+
]
|
|
22378
22812
|
}
|
|
22813
|
+
),
|
|
22814
|
+
open ? /* @__PURE__ */ jsxs("div", { style: { ...panelStyle, marginTop: 4, gap: 12 }, children: [
|
|
22815
|
+
/* @__PURE__ */ jsx("dl", { style: { margin: 0, display: "grid", gap: 10 }, children: fields.map((f) => /* @__PURE__ */ jsxs("div", { children: [
|
|
22816
|
+
/* @__PURE__ */ jsx("dt", { style: { fontSize: 11, color: "#888", textTransform: "uppercase", letterSpacing: "0.04em" }, children: f.label }),
|
|
22817
|
+
/* @__PURE__ */ jsx("dd", { style: { margin: 0, fontSize: 14, whiteSpace: "pre-wrap", wordBreak: "break-word" }, children: f.value === "" ? /* @__PURE__ */ jsx("span", { style: { color: "#666" }, children: "—" }) : f.value })
|
|
22818
|
+
] }, f.name)) }),
|
|
22819
|
+
/* @__PURE__ */ jsxs("p", { style: mutedStyle, children: [
|
|
22820
|
+
emailStatusLabel(row),
|
|
22821
|
+
row.recipients.length > 0 ? ` til ${row.recipients.join(", ")}` : "",
|
|
22822
|
+
row.email_error ? ` — ${row.email_error}` : "",
|
|
22823
|
+
row.meta.page ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
22824
|
+
" · ",
|
|
22825
|
+
/* @__PURE__ */ jsx("a", { href: row.meta.page, style: { color: "#bda6ff" }, target: "_blank", rel: "noreferrer", children: "Sendt fra siden" })
|
|
22826
|
+
] }) : null
|
|
22827
|
+
] }),
|
|
22828
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: onDelete, style: dangerBtnStyle, children: "Slett henvendelse" })
|
|
22829
|
+
] }) : null
|
|
22830
|
+
] });
|
|
22831
|
+
}
|
|
22832
|
+
function submissionFields(row) {
|
|
22833
|
+
const flat = [];
|
|
22834
|
+
flatten(row.data, "", flat);
|
|
22835
|
+
const rank = new Map((row.field_order ?? []).map((n, i) => [n, i]));
|
|
22836
|
+
const rankOf = (name) => rank.get(name) ?? rank.get(name.split(".")[0]) ?? Number.MAX_SAFE_INTEGER;
|
|
22837
|
+
return flat.map((f, i) => ({ f, i })).sort((a, b) => rankOf(a.f.name) - rankOf(b.f.name) || a.i - b.i).map(({ f }) => ({
|
|
22838
|
+
name: f.name,
|
|
22839
|
+
label: row.labels?.[f.name] ?? row.labels?.[f.name.split(".")[0]] ?? prettify(f.name),
|
|
22840
|
+
value: f.value
|
|
22841
|
+
}));
|
|
22842
|
+
}
|
|
22843
|
+
function flatten(value, path, out) {
|
|
22844
|
+
if (value === null || value === void 0) {
|
|
22845
|
+
out.push({ name: path, value: "" });
|
|
22846
|
+
} else if (typeof value === "boolean") {
|
|
22847
|
+
out.push({ name: path, value: value ? "Ja" : "Nei" });
|
|
22848
|
+
} else if (typeof value === "string" || typeof value === "number") {
|
|
22849
|
+
out.push({ name: path, value: String(value) });
|
|
22850
|
+
} else if (Array.isArray(value)) {
|
|
22851
|
+
if (value.every((v) => typeof v !== "object" || v === null)) {
|
|
22852
|
+
out.push({ name: path, value: value.map((v) => v == null ? "" : String(v)).filter(Boolean).join(", ") });
|
|
22853
|
+
} else {
|
|
22854
|
+
value.forEach((item, i) => flatten(item, `${path}.${i + 1}`, out));
|
|
22855
|
+
}
|
|
22856
|
+
} else if (typeof value === "object") {
|
|
22857
|
+
for (const [k, v] of Object.entries(value)) {
|
|
22858
|
+
flatten(v, path ? `${path}.${k}` : k, out);
|
|
22859
|
+
}
|
|
22860
|
+
}
|
|
22861
|
+
}
|
|
22862
|
+
function prettify(name) {
|
|
22863
|
+
const leaf = name.split(".").pop() ?? name;
|
|
22864
|
+
const words = leaf.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_\-.]+/g, " ").trim().toLowerCase();
|
|
22865
|
+
return words ? words[0].toUpperCase() + words.slice(1) : name;
|
|
22866
|
+
}
|
|
22867
|
+
const EMAIL_RE$1 = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/;
|
|
22868
|
+
function senderOf(fields) {
|
|
22869
|
+
const named = fields.find(
|
|
22870
|
+
(f) => /(^|[._-])(navn|name|fullname|full_name|fornavn|firstname|first_name)$/i.test(f.name) && f.value && !EMAIL_RE$1.test(f.value)
|
|
22871
|
+
);
|
|
22872
|
+
if (named) return named.value.slice(0, 80);
|
|
22873
|
+
const email = fields.find((f) => EMAIL_RE$1.test(f.value));
|
|
22874
|
+
if (email) return email.value;
|
|
22875
|
+
const first = fields.find((f) => f.value !== "");
|
|
22876
|
+
return first ? first.value.slice(0, 80) : null;
|
|
22877
|
+
}
|
|
22878
|
+
function emailStatusLabel(row) {
|
|
22879
|
+
switch (row.email_status) {
|
|
22880
|
+
case "sent":
|
|
22881
|
+
return "E-post sendt";
|
|
22882
|
+
case "failed":
|
|
22883
|
+
return "E-post feilet";
|
|
22884
|
+
case "skipped":
|
|
22885
|
+
return "Ingen mottakere";
|
|
22886
|
+
case "pending":
|
|
22887
|
+
return "Sender e-post";
|
|
22888
|
+
}
|
|
22889
|
+
}
|
|
22890
|
+
function formatWhen(iso) {
|
|
22891
|
+
const d = new Date(iso);
|
|
22892
|
+
const now = /* @__PURE__ */ new Date();
|
|
22893
|
+
const sameDay = d.toDateString() === now.toDateString();
|
|
22894
|
+
return sameDay ? d.toLocaleTimeString("nb-NO", { hour: "2-digit", minute: "2-digit" }) : d.toLocaleDateString("nb-NO", { day: "numeric", month: "short" }) + " " + d.toLocaleTimeString("nb-NO", { hour: "2-digit", minute: "2-digit" });
|
|
22895
|
+
}
|
|
22896
|
+
function statusDotStyle(status) {
|
|
22897
|
+
const colour = status === "sent" ? "#34d399" : status === "failed" ? "#ff7a7a" : status === "skipped" ? "#fbbf24" : "#888";
|
|
22898
|
+
return { width: 8, height: 8, borderRadius: "50%", background: colour, flexShrink: 0 };
|
|
22899
|
+
}
|
|
22900
|
+
const MARGIN = 10;
|
|
22901
|
+
const OFFSET = 8;
|
|
22902
|
+
const MIN_TOUCH = 44;
|
|
22903
|
+
function placeChip(anchor, width, height) {
|
|
22904
|
+
const vw = window.innerWidth;
|
|
22905
|
+
const vh = window.innerHeight;
|
|
22906
|
+
let left = anchor.left + anchor.width / 2 - width / 2;
|
|
22907
|
+
let top = anchor.bottom + OFFSET;
|
|
22908
|
+
if (left + width > vw - MARGIN) left = vw - width - MARGIN;
|
|
22909
|
+
if (left < MARGIN) left = MARGIN;
|
|
22910
|
+
if (top + height > vh - MARGIN) {
|
|
22911
|
+
top = anchor.top - height - OFFSET;
|
|
22912
|
+
}
|
|
22913
|
+
if (top < MARGIN) top = MARGIN;
|
|
22914
|
+
return { left, top };
|
|
22915
|
+
}
|
|
22916
|
+
function LinkActionChip({ anchorRef, visible }) {
|
|
22917
|
+
const chipRef = useRef(null);
|
|
22918
|
+
const [position, setPosition] = useState(null);
|
|
22919
|
+
useLayoutEffect(() => {
|
|
22920
|
+
if (!visible || !anchorRef.current) {
|
|
22921
|
+
setPosition(null);
|
|
22922
|
+
return;
|
|
22923
|
+
}
|
|
22924
|
+
const update = () => {
|
|
22925
|
+
const el = anchorRef.current;
|
|
22926
|
+
const chip = chipRef.current;
|
|
22927
|
+
if (!el) return;
|
|
22928
|
+
const rect = el.getBoundingClientRect();
|
|
22929
|
+
const width = chip?.offsetWidth ?? 120;
|
|
22930
|
+
const height = chip?.offsetHeight ?? MIN_TOUCH;
|
|
22931
|
+
setPosition(placeChip(rect, width, height));
|
|
22379
22932
|
};
|
|
22380
|
-
|
|
22381
|
-
|
|
22382
|
-
|
|
22383
|
-
window.addEventListener("focus", syncActiveDeployment);
|
|
22384
|
-
document.addEventListener("visibilitychange", onVisible);
|
|
22933
|
+
update();
|
|
22934
|
+
window.addEventListener("scroll", update, true);
|
|
22935
|
+
window.addEventListener("resize", update);
|
|
22385
22936
|
return () => {
|
|
22386
|
-
window.removeEventListener("
|
|
22387
|
-
|
|
22388
|
-
};
|
|
22389
|
-
}, [activeDeployment, publishDialogOpen, supabase]);
|
|
22390
|
-
const languages = i18n.languages?.length ? Array.from(new Set(i18n.languages)) : ["no", "en"];
|
|
22391
|
-
useEffect(() => {
|
|
22392
|
-
if (!menuOpen) return;
|
|
22393
|
-
const close = (e) => {
|
|
22394
|
-
if (menuRef.current && !menuRef.current.contains(e.target)) {
|
|
22395
|
-
setMenuOpen(false);
|
|
22396
|
-
}
|
|
22937
|
+
window.removeEventListener("scroll", update, true);
|
|
22938
|
+
window.removeEventListener("resize", update);
|
|
22397
22939
|
};
|
|
22398
|
-
|
|
22399
|
-
|
|
22400
|
-
|
|
22401
|
-
const
|
|
22402
|
-
|
|
22403
|
-
|
|
22404
|
-
|
|
22405
|
-
|
|
22406
|
-
|
|
22407
|
-
|
|
22408
|
-
|
|
22409
|
-
|
|
22410
|
-
|
|
22411
|
-
|
|
22412
|
-
|
|
22413
|
-
|
|
22414
|
-
|
|
22415
|
-
|
|
22416
|
-
|
|
22417
|
-
|
|
22418
|
-
|
|
22419
|
-
|
|
22420
|
-
|
|
22421
|
-
|
|
22422
|
-
|
|
22423
|
-
|
|
22424
|
-
|
|
22425
|
-
|
|
22426
|
-
|
|
22427
|
-
|
|
22428
|
-
|
|
22429
|
-
|
|
22430
|
-
|
|
22431
|
-
|
|
22432
|
-
|
|
22433
|
-
|
|
22434
|
-
|
|
22435
|
-
|
|
22436
|
-
|
|
22437
|
-
|
|
22438
|
-
|
|
22439
|
-
}
|
|
22440
|
-
|
|
22441
|
-
}
|
|
22442
|
-
const result = await api.publish(config.siteId, files);
|
|
22443
|
-
store.commit();
|
|
22444
|
-
themeStore?.commit();
|
|
22445
|
-
assetStore?.commit();
|
|
22446
|
-
let deploymentId = result.deploymentId;
|
|
22447
|
-
if (!deploymentId && result.trackingDeferred) {
|
|
22448
|
-
deploymentId = await waitForDeploymentByCommit(
|
|
22449
|
-
supabase,
|
|
22450
|
-
config.siteId,
|
|
22451
|
-
result.commitSha
|
|
22452
|
-
);
|
|
22453
|
-
}
|
|
22454
|
-
if (deploymentId) {
|
|
22455
|
-
setActiveDeployment(deploymentId);
|
|
22456
|
-
} else {
|
|
22457
|
-
setPublishDialogOpen(false);
|
|
22458
|
-
setPublishedNotice(true);
|
|
22940
|
+
}, [visible, anchorRef]);
|
|
22941
|
+
if (!visible || !anchorRef.current) return null;
|
|
22942
|
+
const host = document.getElementById("setto-hint-root") ?? document.body;
|
|
22943
|
+
const style = {
|
|
22944
|
+
position: "fixed",
|
|
22945
|
+
left: position?.left ?? 0,
|
|
22946
|
+
top: position?.top ?? 0,
|
|
22947
|
+
visibility: position ? "visible" : "hidden",
|
|
22948
|
+
minHeight: MIN_TOUCH,
|
|
22949
|
+
minWidth: MIN_TOUCH,
|
|
22950
|
+
padding: "10px 14px",
|
|
22951
|
+
background: "#1a1a1a",
|
|
22952
|
+
color: "#fff",
|
|
22953
|
+
fontSize: 13,
|
|
22954
|
+
fontWeight: 500,
|
|
22955
|
+
border: "none",
|
|
22956
|
+
borderRadius: 8,
|
|
22957
|
+
boxShadow: "0 4px 16px rgba(0,0,0,0.2)",
|
|
22958
|
+
fontFamily: "system-ui, sans-serif",
|
|
22959
|
+
whiteSpace: "nowrap",
|
|
22960
|
+
touchAction: "manipulation",
|
|
22961
|
+
zIndex: 2147483647
|
|
22962
|
+
};
|
|
22963
|
+
return createPortal(
|
|
22964
|
+
/* @__PURE__ */ jsx(
|
|
22965
|
+
"button",
|
|
22966
|
+
{
|
|
22967
|
+
ref: chipRef,
|
|
22968
|
+
type: "button",
|
|
22969
|
+
"data-setto-ui": true,
|
|
22970
|
+
style,
|
|
22971
|
+
onMouseDown: (e) => {
|
|
22972
|
+
e.preventDefault();
|
|
22973
|
+
e.stopPropagation();
|
|
22974
|
+
},
|
|
22975
|
+
onClick: (e) => {
|
|
22976
|
+
e.preventDefault();
|
|
22977
|
+
e.stopPropagation();
|
|
22978
|
+
const el = anchorRef.current;
|
|
22979
|
+
if (el) activateLinkContext(el);
|
|
22980
|
+
el?.blur();
|
|
22981
|
+
},
|
|
22982
|
+
children: "Naviger ↗"
|
|
22459
22983
|
}
|
|
22460
|
-
|
|
22461
|
-
|
|
22462
|
-
|
|
22463
|
-
|
|
22464
|
-
|
|
22984
|
+
),
|
|
22985
|
+
host
|
|
22986
|
+
);
|
|
22987
|
+
}
|
|
22988
|
+
function placeCaretAtPoint(x, y, el) {
|
|
22989
|
+
el.focus();
|
|
22990
|
+
const doc = el.ownerDocument;
|
|
22991
|
+
const selection = doc.getSelection();
|
|
22992
|
+
if (!selection) return;
|
|
22993
|
+
const range = rangeFromPoint(doc, x, y);
|
|
22994
|
+
if (!range) return;
|
|
22995
|
+
if (!el.contains(range.startContainer)) {
|
|
22996
|
+
range.selectNodeContents(el);
|
|
22997
|
+
range.collapse(false);
|
|
22998
|
+
}
|
|
22999
|
+
selection.removeAllRanges();
|
|
23000
|
+
selection.addRange(range);
|
|
23001
|
+
}
|
|
23002
|
+
function rangeFromPoint(doc, x, y) {
|
|
23003
|
+
if (doc.caretRangeFromPoint) {
|
|
23004
|
+
return doc.caretRangeFromPoint(x, y);
|
|
23005
|
+
}
|
|
23006
|
+
const legacy = doc;
|
|
23007
|
+
const pos = legacy.caretPositionFromPoint?.(x, y);
|
|
23008
|
+
if (!pos) return null;
|
|
23009
|
+
const range = doc.createRange();
|
|
23010
|
+
range.setStart(pos.offsetNode, pos.offset);
|
|
23011
|
+
range.collapse(true);
|
|
23012
|
+
return range;
|
|
23013
|
+
}
|
|
23014
|
+
const GuestEditContext = createContext(null);
|
|
23015
|
+
function GuestEditProvider({ children }) {
|
|
23016
|
+
const { editMode, themeStore } = useSetto();
|
|
23017
|
+
const { selectSection } = useSectionEdit();
|
|
23018
|
+
const [active, setActive] = useState(false);
|
|
23019
|
+
useEffect(() => {
|
|
23020
|
+
if (editMode && active) setActive(false);
|
|
23021
|
+
}, [editMode, active]);
|
|
23022
|
+
useEffect(() => {
|
|
23023
|
+
document.documentElement.classList.toggle("setto-guest-editing", active);
|
|
23024
|
+
return () => document.documentElement.classList.remove("setto-guest-editing");
|
|
23025
|
+
}, [active]);
|
|
23026
|
+
const start = useCallback(() => setActive(true), []);
|
|
23027
|
+
const stop = useCallback(() => {
|
|
23028
|
+
setActive(false);
|
|
23029
|
+
selectSection(null);
|
|
23030
|
+
}, [selectSection]);
|
|
23031
|
+
return /* @__PURE__ */ jsxs(GuestEditContext.Provider, { value: { active, start, stop }, children: [
|
|
23032
|
+
children,
|
|
23033
|
+
active && themeStore ? createPortal(/* @__PURE__ */ jsx(SectionToolbar, {}), document.body) : null
|
|
23034
|
+
] });
|
|
23035
|
+
}
|
|
23036
|
+
function useGuestEdit() {
|
|
23037
|
+
const ctx = useContext(GuestEditContext);
|
|
23038
|
+
if (!ctx) {
|
|
23039
|
+
return { active: false, start: () => {
|
|
23040
|
+
}, stop: () => {
|
|
23041
|
+
} };
|
|
23042
|
+
}
|
|
23043
|
+
return ctx;
|
|
23044
|
+
}
|
|
23045
|
+
function T({ k }) {
|
|
23046
|
+
const { t, i18n } = useTranslation();
|
|
23047
|
+
const { editMode, store } = useSetto();
|
|
23048
|
+
const guestEdit = useGuestEdit();
|
|
23049
|
+
const editable = editMode || guestEdit.active;
|
|
23050
|
+
const ref = useRef(null);
|
|
23051
|
+
const [focused, setFocused] = useState(false);
|
|
23052
|
+
const [linkContext, setLinkContext] = useState(false);
|
|
23053
|
+
const coarsePointer = useCoarsePointer();
|
|
23054
|
+
const [, force] = useState(0);
|
|
23055
|
+
useEffect(() => {
|
|
23056
|
+
if (!store) return;
|
|
23057
|
+
return store.subscribe(() => force((x) => x + 1));
|
|
23058
|
+
}, [store]);
|
|
23059
|
+
const value = store ? store.get(k, i18n.language) : t(k);
|
|
23060
|
+
useEffect(() => {
|
|
23061
|
+
const el = ref.current;
|
|
23062
|
+
if (!el || focused || !editable) return;
|
|
23063
|
+
if (el.textContent !== value) el.textContent = value;
|
|
23064
|
+
}, [value, focused, editable]);
|
|
23065
|
+
if (!editable) return /* @__PURE__ */ jsx(Fragment, { children: value });
|
|
23066
|
+
const handleMouseDown = (e) => {
|
|
23067
|
+
e.stopPropagation();
|
|
23068
|
+
const el = e.currentTarget;
|
|
23069
|
+
if (!isLinkContext(el)) return;
|
|
23070
|
+
if (e.ctrlKey || e.metaKey) return;
|
|
23071
|
+
e.preventDefault();
|
|
23072
|
+
};
|
|
23073
|
+
const handleMouseUp = (e) => {
|
|
23074
|
+
if (e.button !== 0) return;
|
|
23075
|
+
e.stopPropagation();
|
|
23076
|
+
const el = e.currentTarget;
|
|
23077
|
+
if (isLinkContext(el) && (e.ctrlKey || e.metaKey)) return;
|
|
23078
|
+
if (isLinkContext(el)) {
|
|
23079
|
+
placeCaretAtPoint(e.clientX, e.clientY, el);
|
|
22465
23080
|
}
|
|
22466
23081
|
};
|
|
22467
|
-
const
|
|
22468
|
-
|
|
22469
|
-
|
|
22470
|
-
|
|
22471
|
-
if (
|
|
23082
|
+
const handleClick = (e) => {
|
|
23083
|
+
e.stopPropagation();
|
|
23084
|
+
const el = e.currentTarget;
|
|
23085
|
+
if (isLinkContext(el) && (e.ctrlKey || e.metaKey)) return;
|
|
23086
|
+
if (isLinkContext(el)) {
|
|
23087
|
+
e.preventDefault();
|
|
23088
|
+
}
|
|
22472
23089
|
};
|
|
22473
|
-
const
|
|
22474
|
-
|
|
22475
|
-
|
|
23090
|
+
const handleKeyDown = (e) => {
|
|
23091
|
+
if (!isLinkContext(e.currentTarget)) return;
|
|
23092
|
+
e.stopPropagation();
|
|
22476
23093
|
};
|
|
22477
|
-
const
|
|
22478
|
-
|
|
22479
|
-
|
|
22480
|
-
|
|
22481
|
-
|
|
22482
|
-
|
|
22483
|
-
setExpanded(false);
|
|
23094
|
+
const handleKeyUp = (e) => {
|
|
23095
|
+
if (!isLinkContext(e.currentTarget)) return;
|
|
23096
|
+
if (e.key === " " || e.key === "Enter") {
|
|
23097
|
+
e.preventDefault();
|
|
23098
|
+
e.stopPropagation();
|
|
23099
|
+
}
|
|
22484
23100
|
};
|
|
22485
|
-
const
|
|
22486
|
-
|
|
22487
|
-
|
|
23101
|
+
const commit = (el) => {
|
|
23102
|
+
const text = el.textContent ?? "";
|
|
23103
|
+
store?.set(k, i18n.language, text);
|
|
22488
23104
|
};
|
|
22489
|
-
const
|
|
22490
|
-
|
|
22491
|
-
|
|
23105
|
+
const handleBlur = (e) => {
|
|
23106
|
+
setFocused(false);
|
|
23107
|
+
setLinkContext(false);
|
|
23108
|
+
commit(e.currentTarget);
|
|
23109
|
+
};
|
|
23110
|
+
const handleInput = (e) => {
|
|
23111
|
+
store?.set(k, i18n.language, e.currentTarget.textContent ?? "");
|
|
23112
|
+
};
|
|
23113
|
+
const handlePaste = (e) => {
|
|
23114
|
+
e.preventDefault();
|
|
23115
|
+
const text = e.clipboardData.getData("text/plain");
|
|
23116
|
+
if (!text) return;
|
|
23117
|
+
const el = e.currentTarget;
|
|
23118
|
+
el.focus();
|
|
23119
|
+
document.execCommand("insertText", false, text);
|
|
23120
|
+
store?.set(k, i18n.language, el.textContent ?? "");
|
|
22492
23121
|
};
|
|
23122
|
+
const lang = toBcp47(i18n.language);
|
|
23123
|
+
const showLinkChip = coarsePointer && focused && linkContext;
|
|
22493
23124
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
22494
|
-
/* @__PURE__ */
|
|
22495
|
-
"
|
|
23125
|
+
/* @__PURE__ */ jsx(
|
|
23126
|
+
"span",
|
|
22496
23127
|
{
|
|
22497
|
-
|
|
22498
|
-
"
|
|
22499
|
-
|
|
22500
|
-
|
|
22501
|
-
|
|
22502
|
-
|
|
22503
|
-
|
|
22504
|
-
|
|
23128
|
+
ref,
|
|
23129
|
+
"data-setto-key": k,
|
|
23130
|
+
contentEditable: true,
|
|
23131
|
+
suppressContentEditableWarning: true,
|
|
23132
|
+
lang,
|
|
23133
|
+
spellCheck: false,
|
|
23134
|
+
role: "textbox",
|
|
23135
|
+
tabIndex: 0,
|
|
23136
|
+
onFocus: (e) => {
|
|
23137
|
+
setFocused(true);
|
|
23138
|
+
setLinkContext(isLinkContext(e.currentTarget));
|
|
23139
|
+
},
|
|
23140
|
+
onBlur: handleBlur,
|
|
23141
|
+
onInput: handleInput,
|
|
23142
|
+
onPaste: handlePaste,
|
|
23143
|
+
onMouseDown: handleMouseDown,
|
|
23144
|
+
onMouseUp: handleMouseUp,
|
|
23145
|
+
onClick: handleClick,
|
|
23146
|
+
onKeyDown: handleKeyDown,
|
|
23147
|
+
onKeyUp: handleKeyUp,
|
|
23148
|
+
style: {
|
|
23149
|
+
cursor: "text",
|
|
23150
|
+
color: "inherit",
|
|
23151
|
+
outline: focused ? editMode ? "2px solid #640AFF" : "2px solid #C4502A" : void 0,
|
|
23152
|
+
outlineOffset: 2,
|
|
23153
|
+
borderRadius: 2,
|
|
23154
|
+
transition: "outline-color 120ms"
|
|
23155
|
+
}
|
|
23156
|
+
}
|
|
23157
|
+
),
|
|
23158
|
+
showLinkChip ? /* @__PURE__ */ jsx(LinkActionChip, { anchorRef: ref, visible: focused }) : null
|
|
23159
|
+
] });
|
|
23160
|
+
}
|
|
23161
|
+
function toBcp47(code) {
|
|
23162
|
+
const base = (code.split("-")[0] ?? code).toLowerCase();
|
|
23163
|
+
if (base === "no" || base === "nb" || base === "nn") return "nb-NO";
|
|
23164
|
+
if (base === "en") return "en";
|
|
23165
|
+
return code;
|
|
23166
|
+
}
|
|
23167
|
+
const FORM_FIELD_TYPES = [
|
|
23168
|
+
"text",
|
|
23169
|
+
"email",
|
|
23170
|
+
"tel",
|
|
23171
|
+
"number",
|
|
23172
|
+
"url",
|
|
23173
|
+
"date",
|
|
23174
|
+
"textarea",
|
|
23175
|
+
"select",
|
|
23176
|
+
"radio",
|
|
23177
|
+
"checkbox",
|
|
23178
|
+
"checkboxes"
|
|
23179
|
+
];
|
|
23180
|
+
const FORM_FIELD_TYPE_LABELS = {
|
|
23181
|
+
text: "Kort tekst",
|
|
23182
|
+
email: "E-post",
|
|
23183
|
+
tel: "Telefon",
|
|
23184
|
+
number: "Tall",
|
|
23185
|
+
url: "Nettadresse",
|
|
23186
|
+
date: "Dato",
|
|
23187
|
+
textarea: "Lang tekst",
|
|
23188
|
+
select: "Nedtrekksliste",
|
|
23189
|
+
radio: "Ett valg",
|
|
23190
|
+
checkbox: "Avkrysning",
|
|
23191
|
+
checkboxes: "Flere valg"
|
|
23192
|
+
};
|
|
23193
|
+
function isFormFieldType(value) {
|
|
23194
|
+
return typeof value === "string" && FORM_FIELD_TYPES.includes(value);
|
|
23195
|
+
}
|
|
23196
|
+
function fieldTypeHasOptions(type) {
|
|
23197
|
+
return type === "select" || type === "radio" || type === "checkboxes";
|
|
23198
|
+
}
|
|
23199
|
+
function fieldTypeHasPlaceholder(type) {
|
|
23200
|
+
return !fieldTypeHasOptions(type) && type !== "checkbox" && type !== "date";
|
|
23201
|
+
}
|
|
23202
|
+
function formFieldsPrefix(formId) {
|
|
23203
|
+
return `form.${formId}.fields`;
|
|
23204
|
+
}
|
|
23205
|
+
function parseOptions(value) {
|
|
23206
|
+
if (!value) return [];
|
|
23207
|
+
return value.split("\n").map((o) => o.trim()).filter((o) => o !== "");
|
|
23208
|
+
}
|
|
23209
|
+
function readString(item, prop) {
|
|
23210
|
+
const value = item[prop];
|
|
23211
|
+
return typeof value === "string" ? value : "";
|
|
23212
|
+
}
|
|
23213
|
+
function readLocaleFields(i18n, lng, formId, ns = "translation") {
|
|
23214
|
+
const prefix = formFieldsPrefix(formId);
|
|
23215
|
+
const candidates = [lng, ...i18n.languages ?? []].filter((l, i, all) => all.indexOf(l) === i);
|
|
23216
|
+
let bundle;
|
|
23217
|
+
let list;
|
|
23218
|
+
for (const candidate of candidates) {
|
|
23219
|
+
const b = i18n.getResourceBundle(candidate, ns);
|
|
23220
|
+
const l = getNested(b, prefix);
|
|
23221
|
+
if (l && typeof l === "object" && !Array.isArray(l)) {
|
|
23222
|
+
bundle = b;
|
|
23223
|
+
list = l;
|
|
23224
|
+
break;
|
|
23225
|
+
}
|
|
23226
|
+
}
|
|
23227
|
+
if (!list) return null;
|
|
23228
|
+
const fields = [];
|
|
23229
|
+
for (const itemKey of collectKeysUnder(bundle, prefix)) {
|
|
23230
|
+
const item = list[itemKey];
|
|
23231
|
+
if (!item || typeof item !== "object") continue;
|
|
23232
|
+
const record = item;
|
|
23233
|
+
const name = readString(record, "name").trim();
|
|
23234
|
+
if (!name) continue;
|
|
23235
|
+
const type = isFormFieldType(record.type) ? record.type : "text";
|
|
23236
|
+
fields.push({
|
|
23237
|
+
itemKey,
|
|
23238
|
+
name,
|
|
23239
|
+
type,
|
|
23240
|
+
labelKey: `${prefix}.${itemKey}.label`,
|
|
23241
|
+
label: readString(record, "label"),
|
|
23242
|
+
placeholder: readString(record, "placeholder"),
|
|
23243
|
+
required: readString(record, "required") === "true",
|
|
23244
|
+
options: parseOptions(readString(record, "options"))
|
|
23245
|
+
});
|
|
23246
|
+
}
|
|
23247
|
+
return fields;
|
|
23248
|
+
}
|
|
23249
|
+
function resolvePropFields(fields, t) {
|
|
23250
|
+
return fields.map((field) => ({
|
|
23251
|
+
name: field.name,
|
|
23252
|
+
type: isFormFieldType(field.type) ? field.type : "text",
|
|
23253
|
+
labelKey: field.labelKey,
|
|
23254
|
+
label: t(field.labelKey, { defaultValue: "" }),
|
|
23255
|
+
placeholder: field.placeholderKey ? t(field.placeholderKey, { defaultValue: "" }) : "",
|
|
23256
|
+
required: field.required === true,
|
|
23257
|
+
options: field.optionsKey ? parseOptions(t(field.optionsKey, { defaultValue: "" })) : []
|
|
23258
|
+
}));
|
|
23259
|
+
}
|
|
23260
|
+
function slugifyFieldName(label) {
|
|
23261
|
+
const slug = label.toLowerCase().replace(/æ/g, "ae").replace(/ø/g, "o").replace(/å/g, "a").normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9_]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
|
|
23262
|
+
return slug || "felt";
|
|
23263
|
+
}
|
|
23264
|
+
function uniqueFieldName(base, taken) {
|
|
23265
|
+
const used = new Set(taken);
|
|
23266
|
+
if (!used.has(base)) return base;
|
|
23267
|
+
let n = 2;
|
|
23268
|
+
while (used.has(`${base}-${n}`)) n += 1;
|
|
23269
|
+
return `${base}-${n}`;
|
|
23270
|
+
}
|
|
23271
|
+
function emptyValue(type) {
|
|
23272
|
+
if (type === "checkbox") return false;
|
|
23273
|
+
if (type === "checkboxes") return [];
|
|
23274
|
+
return "";
|
|
23275
|
+
}
|
|
23276
|
+
function isBlank(value) {
|
|
23277
|
+
if (value === void 0) return true;
|
|
23278
|
+
if (typeof value === "string") return value.trim() === "";
|
|
23279
|
+
if (typeof value === "boolean") return !value;
|
|
23280
|
+
return value.length === 0;
|
|
23281
|
+
}
|
|
23282
|
+
const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/;
|
|
23283
|
+
const URL_RE = /^(https?:\/\/)?[^\s/$.?#].[^\s]*$/i;
|
|
23284
|
+
const FIELD_ERROR_DEFAULTS = {
|
|
23285
|
+
required: "Må fylles ut",
|
|
23286
|
+
email: "Skriv inn en gyldig e-postadresse",
|
|
23287
|
+
url: "Skriv inn en gyldig nettadresse",
|
|
23288
|
+
number: "Skriv inn et tall"
|
|
23289
|
+
};
|
|
23290
|
+
function validateValues(fields, values) {
|
|
23291
|
+
const errors = {};
|
|
23292
|
+
for (const field of fields) {
|
|
23293
|
+
const value = values[field.name];
|
|
23294
|
+
if (field.required && isBlank(value)) {
|
|
23295
|
+
errors[field.name] = "required";
|
|
23296
|
+
continue;
|
|
23297
|
+
}
|
|
23298
|
+
if (typeof value !== "string" || value.trim() === "") continue;
|
|
23299
|
+
if (field.type === "email" && !EMAIL_RE.test(value.trim())) errors[field.name] = "email";
|
|
23300
|
+
if (field.type === "url" && !URL_RE.test(value.trim())) errors[field.name] = "url";
|
|
23301
|
+
if (field.type === "number" && Number.isNaN(Number(value.trim()))) errors[field.name] = "number";
|
|
23302
|
+
}
|
|
23303
|
+
return errors;
|
|
23304
|
+
}
|
|
23305
|
+
function buildEnvelope(fields, values, extra) {
|
|
23306
|
+
const data = {};
|
|
23307
|
+
const labels = {};
|
|
23308
|
+
for (const field of fields) {
|
|
23309
|
+
const value = values[field.name] ?? emptyValue(field.type);
|
|
23310
|
+
data[field.name] = typeof value === "string" ? value.trim() : value;
|
|
23311
|
+
if (field.label.trim()) labels[field.name] = field.label.trim();
|
|
23312
|
+
}
|
|
23313
|
+
return {
|
|
23314
|
+
data,
|
|
23315
|
+
labels,
|
|
23316
|
+
order: fields.map((f) => f.name),
|
|
23317
|
+
...extra.title ? { title: extra.title } : {},
|
|
23318
|
+
...extra.page ? { page: extra.page } : {},
|
|
23319
|
+
hp: extra.hp,
|
|
23320
|
+
elapsed: Math.max(0, Math.round(extra.elapsed))
|
|
23321
|
+
};
|
|
23322
|
+
}
|
|
23323
|
+
function settoSubmissionUrl(apiUrl, siteId, formId) {
|
|
23324
|
+
return `${apiUrl.replace(/\/+$/, "")}/sites/${encodeURIComponent(siteId)}/forms/${encodeURIComponent(formId)}/submissions`;
|
|
23325
|
+
}
|
|
23326
|
+
function FloatingPopover({
|
|
23327
|
+
open,
|
|
23328
|
+
anchorRef,
|
|
23329
|
+
onClose,
|
|
23330
|
+
ariaLabel,
|
|
23331
|
+
children,
|
|
23332
|
+
style,
|
|
23333
|
+
minWidth = 280
|
|
23334
|
+
}) {
|
|
23335
|
+
const popoverRef = useRef(null);
|
|
23336
|
+
const [pos, setPos] = useState(null);
|
|
23337
|
+
useLayoutEffect(() => {
|
|
23338
|
+
if (!open || !anchorRef.current) {
|
|
23339
|
+
setPos(null);
|
|
23340
|
+
return;
|
|
23341
|
+
}
|
|
23342
|
+
const update = () => {
|
|
23343
|
+
const anchor = anchorRef.current;
|
|
23344
|
+
const pop = popoverRef.current;
|
|
23345
|
+
if (!anchor) return;
|
|
23346
|
+
const rect = anchor.getBoundingClientRect();
|
|
23347
|
+
const popoverH = pop?.offsetHeight ?? 200;
|
|
23348
|
+
const popoverW = pop?.offsetWidth ?? minWidth;
|
|
23349
|
+
let top = rect.bottom + 6;
|
|
23350
|
+
let left = rect.left;
|
|
23351
|
+
if (left + popoverW > window.innerWidth - 8) {
|
|
23352
|
+
left = window.innerWidth - popoverW - 8;
|
|
23353
|
+
}
|
|
23354
|
+
if (left < 8) left = 8;
|
|
23355
|
+
if (top + popoverH > window.innerHeight - 8) {
|
|
23356
|
+
top = rect.top - popoverH - 6;
|
|
23357
|
+
}
|
|
23358
|
+
if (top < 8) top = rect.bottom + 6;
|
|
23359
|
+
setPos({ top, left });
|
|
23360
|
+
};
|
|
23361
|
+
update();
|
|
23362
|
+
requestAnimationFrame(update);
|
|
23363
|
+
window.addEventListener("scroll", update, true);
|
|
23364
|
+
window.addEventListener("resize", update);
|
|
23365
|
+
return () => {
|
|
23366
|
+
window.removeEventListener("scroll", update, true);
|
|
23367
|
+
window.removeEventListener("resize", update);
|
|
23368
|
+
};
|
|
23369
|
+
}, [open, anchorRef, minWidth]);
|
|
23370
|
+
useEffect(() => {
|
|
23371
|
+
if (!open) return;
|
|
23372
|
+
const onKey = (e) => {
|
|
23373
|
+
if (e.key === "Escape") onClose();
|
|
23374
|
+
};
|
|
23375
|
+
window.addEventListener("keydown", onKey);
|
|
23376
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
23377
|
+
}, [open, onClose]);
|
|
23378
|
+
useEffect(() => {
|
|
23379
|
+
if (!open) return;
|
|
23380
|
+
const close = (e) => {
|
|
23381
|
+
const target = e.target;
|
|
23382
|
+
if (anchorRef.current?.contains(target)) return;
|
|
23383
|
+
if (popoverRef.current?.contains(target)) return;
|
|
23384
|
+
onClose();
|
|
23385
|
+
};
|
|
23386
|
+
document.addEventListener("mousedown", close);
|
|
23387
|
+
return () => document.removeEventListener("mousedown", close);
|
|
23388
|
+
}, [open, onClose, anchorRef]);
|
|
23389
|
+
if (!open) return null;
|
|
23390
|
+
return createPortal(
|
|
23391
|
+
/* @__PURE__ */ jsx(
|
|
23392
|
+
"div",
|
|
23393
|
+
{
|
|
23394
|
+
ref: popoverRef,
|
|
23395
|
+
role: "dialog",
|
|
23396
|
+
"aria-label": ariaLabel,
|
|
23397
|
+
"data-setto-ui": true,
|
|
23398
|
+
style: {
|
|
23399
|
+
position: "fixed",
|
|
23400
|
+
top: pos?.top ?? -9999,
|
|
23401
|
+
left: pos?.left ?? 0,
|
|
23402
|
+
visibility: pos ? "visible" : "hidden",
|
|
23403
|
+
zIndex: 2147483647,
|
|
23404
|
+
background: "#fff",
|
|
23405
|
+
border: "1px solid #e0e0e0",
|
|
23406
|
+
borderRadius: 8,
|
|
23407
|
+
boxShadow: "0 8px 24px rgba(0,0,0,0.15)",
|
|
23408
|
+
fontFamily: 'system-ui, -apple-system, "Segoe UI", sans-serif',
|
|
23409
|
+
...style
|
|
23410
|
+
},
|
|
23411
|
+
onMouseDown: (e) => e.stopPropagation(),
|
|
23412
|
+
onClick: (e) => e.stopPropagation(),
|
|
23413
|
+
children
|
|
23414
|
+
}
|
|
23415
|
+
),
|
|
23416
|
+
document.body
|
|
23417
|
+
);
|
|
23418
|
+
}
|
|
23419
|
+
const STYLE_ID$2 = "setto-form-edit-styles";
|
|
23420
|
+
function useFormEditStyles() {
|
|
23421
|
+
useEffect(() => {
|
|
23422
|
+
if (document.getElementById(STYLE_ID$2)) return;
|
|
23423
|
+
const el = document.createElement("style");
|
|
23424
|
+
el.id = STYLE_ID$2;
|
|
23425
|
+
el.textContent = `
|
|
23426
|
+
.setto-form-field-tools {
|
|
23427
|
+
opacity: 0;
|
|
23428
|
+
transition: opacity 120ms ease;
|
|
23429
|
+
}
|
|
23430
|
+
.setto-form__field:hover .setto-form-field-tools,
|
|
23431
|
+
.setto-form__field:focus-within .setto-form-field-tools,
|
|
23432
|
+
.setto-form-field-tools[data-open] {
|
|
23433
|
+
opacity: 1;
|
|
23434
|
+
}
|
|
23435
|
+
@media (hover: none) {
|
|
23436
|
+
.setto-form-field-tools { opacity: 1; }
|
|
23437
|
+
}
|
|
23438
|
+
`;
|
|
23439
|
+
document.head.appendChild(el);
|
|
23440
|
+
}, []);
|
|
23441
|
+
}
|
|
23442
|
+
function FormFieldTools({
|
|
23443
|
+
field,
|
|
23444
|
+
index,
|
|
23445
|
+
count,
|
|
23446
|
+
autoOpen,
|
|
23447
|
+
onMove,
|
|
23448
|
+
onRemove,
|
|
23449
|
+
onChange
|
|
23450
|
+
}) {
|
|
23451
|
+
useFormEditStyles();
|
|
23452
|
+
const [open, setOpen] = useState(autoOpen === true);
|
|
23453
|
+
const gearRef = useRef(null);
|
|
23454
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
23455
|
+
/* @__PURE__ */ jsxs(
|
|
23456
|
+
"div",
|
|
23457
|
+
{
|
|
23458
|
+
"data-setto-ui": true,
|
|
23459
|
+
className: "setto-form-field-tools",
|
|
23460
|
+
style: toolsStyle,
|
|
23461
|
+
...open ? { "data-open": "" } : {},
|
|
23462
|
+
onMouseDown: (e) => e.stopPropagation(),
|
|
23463
|
+
onClick: (e) => e.stopPropagation(),
|
|
23464
|
+
children: [
|
|
23465
|
+
/* @__PURE__ */ jsx(ToolButton, { label: "Flytt opp", disabled: index === 0, onClick: () => onMove(-1), children: /* @__PURE__ */ jsx(ArrowIcon, { direction: "up" }) }),
|
|
23466
|
+
/* @__PURE__ */ jsx(
|
|
23467
|
+
ToolButton,
|
|
23468
|
+
{
|
|
23469
|
+
label: "Flytt ned",
|
|
23470
|
+
disabled: index === count - 1,
|
|
23471
|
+
onClick: () => onMove(1),
|
|
23472
|
+
children: /* @__PURE__ */ jsx(ArrowIcon, { direction: "down" })
|
|
23473
|
+
}
|
|
23474
|
+
),
|
|
23475
|
+
/* @__PURE__ */ jsx(
|
|
23476
|
+
ToolButton,
|
|
23477
|
+
{
|
|
23478
|
+
ref: gearRef,
|
|
23479
|
+
label: "Feltinnstillinger",
|
|
23480
|
+
active: open,
|
|
23481
|
+
onClick: () => setOpen((o) => !o),
|
|
23482
|
+
children: /* @__PURE__ */ jsx(GearIcon$1, {})
|
|
23483
|
+
}
|
|
23484
|
+
),
|
|
23485
|
+
/* @__PURE__ */ jsx(ToolButton, { label: "Fjern felt", disabled: count <= 1, onClick: onRemove, danger: true, children: "×" })
|
|
23486
|
+
]
|
|
23487
|
+
}
|
|
23488
|
+
),
|
|
23489
|
+
/* @__PURE__ */ jsx(
|
|
23490
|
+
FloatingPopover,
|
|
23491
|
+
{
|
|
23492
|
+
open,
|
|
23493
|
+
anchorRef: gearRef,
|
|
23494
|
+
onClose: () => setOpen(false),
|
|
23495
|
+
ariaLabel: "Feltinnstillinger",
|
|
23496
|
+
minWidth: 300,
|
|
23497
|
+
style: { width: 300, padding: 14 },
|
|
23498
|
+
children: /* @__PURE__ */ jsx(FieldSettings, { field, onChange })
|
|
23499
|
+
}
|
|
23500
|
+
)
|
|
23501
|
+
] });
|
|
23502
|
+
}
|
|
23503
|
+
function FieldSettings({ field, onChange }) {
|
|
23504
|
+
const [label, setLabel] = useState(field.label);
|
|
23505
|
+
const [placeholder, setPlaceholder] = useState(field.placeholder);
|
|
23506
|
+
const [options, setOptions] = useState(field.options.join("\n"));
|
|
23507
|
+
const [name, setName] = useState(field.name);
|
|
23508
|
+
useEffect(() => setLabel(field.label), [field.label]);
|
|
23509
|
+
useEffect(() => setPlaceholder(field.placeholder), [field.placeholder]);
|
|
23510
|
+
useEffect(() => setName(field.name), [field.name]);
|
|
23511
|
+
return /* @__PURE__ */ jsxs("div", { style: { display: "grid", gap: 10, fontSize: 13, color: "#222" }, children: [
|
|
23512
|
+
/* @__PURE__ */ jsx(Row, { label: "Type", children: /* @__PURE__ */ jsx(
|
|
23513
|
+
"select",
|
|
23514
|
+
{
|
|
23515
|
+
value: field.type,
|
|
23516
|
+
onChange: (e) => onChange({ type: e.target.value }),
|
|
23517
|
+
style: inputStyle$1,
|
|
23518
|
+
children: FORM_FIELD_TYPES.map((type) => /* @__PURE__ */ jsx("option", { value: type, children: FORM_FIELD_TYPE_LABELS[type] }, type))
|
|
23519
|
+
}
|
|
23520
|
+
) }),
|
|
23521
|
+
/* @__PURE__ */ jsx(Row, { label: "Etikett", children: /* @__PURE__ */ jsx(
|
|
23522
|
+
"input",
|
|
23523
|
+
{
|
|
23524
|
+
type: "text",
|
|
23525
|
+
value: label,
|
|
23526
|
+
onChange: (e) => setLabel(e.target.value),
|
|
23527
|
+
onBlur: () => label !== field.label && onChange({ label }),
|
|
23528
|
+
style: inputStyle$1
|
|
23529
|
+
}
|
|
23530
|
+
) }),
|
|
23531
|
+
fieldTypeHasPlaceholder(field.type) ? /* @__PURE__ */ jsx(Row, { label: "Plassholder", children: /* @__PURE__ */ jsx(
|
|
23532
|
+
"input",
|
|
23533
|
+
{
|
|
23534
|
+
type: "text",
|
|
23535
|
+
value: placeholder,
|
|
23536
|
+
onChange: (e) => setPlaceholder(e.target.value),
|
|
23537
|
+
onBlur: () => placeholder !== field.placeholder && onChange({ placeholder }),
|
|
23538
|
+
style: inputStyle$1
|
|
23539
|
+
}
|
|
23540
|
+
) }) : null,
|
|
23541
|
+
fieldTypeHasOptions(field.type) ? /* @__PURE__ */ jsx(Row, { label: "Alternativer", hint: "Ett per linje", children: /* @__PURE__ */ jsx(
|
|
23542
|
+
"textarea",
|
|
23543
|
+
{
|
|
23544
|
+
value: options,
|
|
23545
|
+
rows: 4,
|
|
23546
|
+
onChange: (e) => setOptions(e.target.value),
|
|
23547
|
+
onBlur: () => options !== field.options.join("\n") && onChange({ options }),
|
|
23548
|
+
style: { ...inputStyle$1, resize: "vertical", fontFamily: "inherit" }
|
|
23549
|
+
}
|
|
23550
|
+
) }) : null,
|
|
23551
|
+
/* @__PURE__ */ jsxs("label", { style: { display: "flex", alignItems: "center", gap: 8, cursor: "pointer" }, children: [
|
|
23552
|
+
/* @__PURE__ */ jsx(
|
|
23553
|
+
"input",
|
|
23554
|
+
{
|
|
23555
|
+
type: "checkbox",
|
|
23556
|
+
checked: field.required,
|
|
23557
|
+
onChange: (e) => onChange({ required: e.target.checked })
|
|
23558
|
+
}
|
|
23559
|
+
),
|
|
23560
|
+
"Må fylles ut"
|
|
23561
|
+
] }),
|
|
23562
|
+
/* @__PURE__ */ jsx(Row, { label: "Teknisk navn", hint: "Brukes i e-posten og i historikken", children: /* @__PURE__ */ jsx(
|
|
23563
|
+
"input",
|
|
23564
|
+
{
|
|
23565
|
+
type: "text",
|
|
23566
|
+
value: name,
|
|
23567
|
+
onChange: (e) => setName(e.target.value),
|
|
23568
|
+
onBlur: () => {
|
|
23569
|
+
const next = name.trim();
|
|
23570
|
+
if (next && next !== field.name) onChange({ name: next });
|
|
23571
|
+
else setName(field.name);
|
|
23572
|
+
},
|
|
23573
|
+
style: { ...inputStyle$1, fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace" }
|
|
23574
|
+
}
|
|
23575
|
+
) })
|
|
23576
|
+
] });
|
|
23577
|
+
}
|
|
23578
|
+
function Row({ label, hint, children }) {
|
|
23579
|
+
return /* @__PURE__ */ jsxs("label", { style: { display: "grid", gap: 4 }, children: [
|
|
23580
|
+
/* @__PURE__ */ jsxs("span", { style: { fontSize: 12, color: "#666" }, children: [
|
|
23581
|
+
label,
|
|
23582
|
+
hint ? /* @__PURE__ */ jsxs("span", { style: { color: "#999" }, children: [
|
|
23583
|
+
" · ",
|
|
23584
|
+
hint
|
|
23585
|
+
] }) : null
|
|
23586
|
+
] }),
|
|
23587
|
+
children
|
|
23588
|
+
] });
|
|
23589
|
+
}
|
|
23590
|
+
function ToolButton({ label, onClick, disabled, active, danger, children, ref }) {
|
|
23591
|
+
return /* @__PURE__ */ jsx(
|
|
23592
|
+
"button",
|
|
23593
|
+
{
|
|
23594
|
+
ref,
|
|
23595
|
+
type: "button",
|
|
23596
|
+
"data-setto-ui": true,
|
|
23597
|
+
"aria-label": label,
|
|
23598
|
+
title: label,
|
|
23599
|
+
disabled,
|
|
23600
|
+
onClick,
|
|
23601
|
+
style: {
|
|
23602
|
+
...toolBtnStyle,
|
|
23603
|
+
...active ? { background: "#640AFF", color: "#fff", borderColor: "#640AFF" } : {},
|
|
23604
|
+
...danger ? { fontSize: 16 } : {},
|
|
23605
|
+
...disabled ? { opacity: 0.35, cursor: "default" } : {}
|
|
23606
|
+
},
|
|
23607
|
+
children
|
|
23608
|
+
}
|
|
23609
|
+
);
|
|
23610
|
+
}
|
|
23611
|
+
function ArrowIcon({ direction }) {
|
|
23612
|
+
return /* @__PURE__ */ jsx(
|
|
23613
|
+
"svg",
|
|
23614
|
+
{
|
|
23615
|
+
width: "12",
|
|
23616
|
+
height: "12",
|
|
23617
|
+
viewBox: "0 0 24 24",
|
|
23618
|
+
fill: "none",
|
|
23619
|
+
stroke: "currentColor",
|
|
23620
|
+
strokeWidth: "2.5",
|
|
23621
|
+
strokeLinecap: "round",
|
|
23622
|
+
strokeLinejoin: "round",
|
|
23623
|
+
"aria-hidden": "true",
|
|
23624
|
+
style: direction === "down" ? { transform: "rotate(180deg)" } : void 0,
|
|
23625
|
+
children: /* @__PURE__ */ jsx("path", { d: "M12 19V5M5 12l7-7 7 7" })
|
|
23626
|
+
}
|
|
23627
|
+
);
|
|
23628
|
+
}
|
|
23629
|
+
function GearIcon$1() {
|
|
23630
|
+
return /* @__PURE__ */ jsxs(
|
|
23631
|
+
"svg",
|
|
23632
|
+
{
|
|
23633
|
+
width: "13",
|
|
23634
|
+
height: "13",
|
|
23635
|
+
viewBox: "0 0 24 24",
|
|
23636
|
+
fill: "none",
|
|
23637
|
+
stroke: "currentColor",
|
|
23638
|
+
strokeWidth: "2",
|
|
23639
|
+
strokeLinecap: "round",
|
|
23640
|
+
strokeLinejoin: "round",
|
|
23641
|
+
"aria-hidden": "true",
|
|
23642
|
+
children: [
|
|
23643
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "3" }),
|
|
23644
|
+
/* @__PURE__ */ jsx("path", { d: "M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09a1.65 1.65 0 0 0-1-1.51 1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09a1.65 1.65 0 0 0 1.51-1 1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z" })
|
|
23645
|
+
]
|
|
23646
|
+
}
|
|
23647
|
+
);
|
|
23648
|
+
}
|
|
23649
|
+
const toolsStyle = {
|
|
23650
|
+
position: "absolute",
|
|
23651
|
+
top: -12,
|
|
23652
|
+
right: 0,
|
|
23653
|
+
zIndex: 2,
|
|
23654
|
+
display: "flex",
|
|
23655
|
+
gap: 4,
|
|
23656
|
+
padding: 3,
|
|
23657
|
+
background: "#ffffff",
|
|
23658
|
+
border: "1px solid #e0e0e0",
|
|
23659
|
+
borderRadius: 999,
|
|
23660
|
+
boxShadow: "0 1px 4px rgba(0,0,0,0.18)",
|
|
23661
|
+
fontFamily: 'system-ui, -apple-system, "Segoe UI", sans-serif'
|
|
23662
|
+
};
|
|
23663
|
+
const toolBtnStyle = {
|
|
23664
|
+
display: "flex",
|
|
23665
|
+
alignItems: "center",
|
|
23666
|
+
justifyContent: "center",
|
|
23667
|
+
width: 24,
|
|
23668
|
+
height: 24,
|
|
23669
|
+
padding: 0,
|
|
23670
|
+
background: "transparent",
|
|
23671
|
+
border: "1px solid transparent",
|
|
23672
|
+
borderRadius: "50%",
|
|
23673
|
+
fontSize: 13,
|
|
23674
|
+
lineHeight: 1,
|
|
23675
|
+
color: "#444",
|
|
23676
|
+
cursor: "pointer"
|
|
23677
|
+
};
|
|
23678
|
+
const inputStyle$1 = {
|
|
23679
|
+
width: "100%",
|
|
23680
|
+
boxSizing: "border-box",
|
|
23681
|
+
padding: "6px 8px",
|
|
23682
|
+
border: "1px solid #d8d8d8",
|
|
23683
|
+
borderRadius: 6,
|
|
23684
|
+
fontSize: 13,
|
|
23685
|
+
color: "#222",
|
|
23686
|
+
background: "#fff",
|
|
23687
|
+
fontFamily: "inherit"
|
|
23688
|
+
};
|
|
23689
|
+
const HONEYPOT_NAME = "website";
|
|
23690
|
+
const warnedEndpointKeys = /* @__PURE__ */ new Set();
|
|
23691
|
+
function cx(...parts) {
|
|
23692
|
+
return parts.filter(Boolean).join(" ");
|
|
23693
|
+
}
|
|
23694
|
+
function SettoForm({
|
|
23695
|
+
formId,
|
|
23696
|
+
fields: propFields,
|
|
23697
|
+
endpoint,
|
|
23698
|
+
endpointKey,
|
|
23699
|
+
className,
|
|
23700
|
+
classNames = {}
|
|
23701
|
+
}) {
|
|
23702
|
+
const { t, i18n } = useTranslation();
|
|
23703
|
+
const { config, editMode, store } = useSetto();
|
|
23704
|
+
const guestEdit = useGuestEdit();
|
|
23705
|
+
const editable = editMode || guestEdit.active;
|
|
23706
|
+
const lng = i18n.language;
|
|
23707
|
+
const uid = useId();
|
|
23708
|
+
useFormStyles();
|
|
23709
|
+
const [, force] = useState(0);
|
|
23710
|
+
useEffect(() => {
|
|
23711
|
+
if (!store) return;
|
|
23712
|
+
return store.subscribe(() => force((x) => x + 1));
|
|
23713
|
+
}, [store]);
|
|
23714
|
+
useEffect(() => {
|
|
23715
|
+
if (endpointKey && !warnedEndpointKeys.has(formId)) {
|
|
23716
|
+
warnedEndpointKeys.add(formId);
|
|
23717
|
+
console.warn(
|
|
23718
|
+
`[setto] <SettoForm formId="${formId}"> passes endpointKey, which is no longer used. Forms submit to Setto; pass \`endpoint\` for a custom backend.`
|
|
23719
|
+
);
|
|
23720
|
+
}
|
|
23721
|
+
}, [endpointKey, formId]);
|
|
23722
|
+
const localeFields = readLocaleFields(i18n, lng, formId);
|
|
23723
|
+
const fields = localeFields ?? resolvePropFields(propFields ?? [], t);
|
|
23724
|
+
const fromLocale = localeFields !== null;
|
|
23725
|
+
const prefix = formFieldsPrefix(formId);
|
|
23726
|
+
const [values, setValues] = useState({});
|
|
23727
|
+
const [errors, setErrors] = useState({});
|
|
23728
|
+
const [status, setStatus] = useState("idle");
|
|
23729
|
+
const [justAdded, setJustAdded] = useState(null);
|
|
23730
|
+
const formRef = useRef(null);
|
|
23731
|
+
const successRef = useRef(null);
|
|
23732
|
+
const honeypotRef = useRef(null);
|
|
23733
|
+
const startedAt = useRef(null);
|
|
23734
|
+
useEffect(() => {
|
|
23735
|
+
if (status === "success") successRef.current?.focus({ preventScroll: false });
|
|
23736
|
+
}, [status]);
|
|
23737
|
+
const valueOf = (field) => values[field.name] ?? emptyValue(field.type);
|
|
23738
|
+
const setValue = (name, value) => {
|
|
23739
|
+
setValues((prev) => ({ ...prev, [name]: value }));
|
|
23740
|
+
setErrors((prev) => {
|
|
23741
|
+
if (!(name in prev)) return prev;
|
|
23742
|
+
const next = { ...prev };
|
|
23743
|
+
delete next[name];
|
|
23744
|
+
return next;
|
|
23745
|
+
});
|
|
23746
|
+
};
|
|
23747
|
+
const markStarted = () => {
|
|
23748
|
+
if (startedAt.current === null) startedAt.current = performance.now();
|
|
23749
|
+
};
|
|
23750
|
+
const submitKey = `form.${formId}.submit`;
|
|
23751
|
+
const successKey = `form.${formId}.success`;
|
|
23752
|
+
const errorKey = `form.${formId}.error`;
|
|
23753
|
+
const title = t(`form.${formId}.title`, { defaultValue: "" }) || void 0;
|
|
23754
|
+
const onSubmit = async (e) => {
|
|
23755
|
+
e.preventDefault();
|
|
23756
|
+
if (status === "submitting") return;
|
|
23757
|
+
const nextErrors = validateValues(fields, values);
|
|
23758
|
+
setErrors(nextErrors);
|
|
23759
|
+
const firstInvalid = fields.find((f) => nextErrors[f.name]);
|
|
23760
|
+
if (firstInvalid) {
|
|
23761
|
+
const el = formRef.current?.querySelector(
|
|
23762
|
+
`[data-setto-field="${cssEscape(firstInvalid.name)}"] [name]`
|
|
23763
|
+
);
|
|
23764
|
+
el?.focus();
|
|
23765
|
+
return;
|
|
23766
|
+
}
|
|
23767
|
+
setStatus("submitting");
|
|
23768
|
+
const elapsed = startedAt.current === null ? 0 : performance.now() - startedAt.current;
|
|
23769
|
+
const envelope = buildEnvelope(fields, values, {
|
|
23770
|
+
title,
|
|
23771
|
+
page: typeof window !== "undefined" ? window.location.href : void 0,
|
|
23772
|
+
hp: honeypotRef.current?.value ?? "",
|
|
23773
|
+
elapsed
|
|
23774
|
+
});
|
|
23775
|
+
try {
|
|
23776
|
+
const url = endpoint || settoSubmissionUrl(config.apiUrl, config.siteId, formId);
|
|
23777
|
+
const res = await fetch(url, {
|
|
23778
|
+
method: "POST",
|
|
23779
|
+
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
|
23780
|
+
body: JSON.stringify(endpoint ? envelope.data : envelope)
|
|
23781
|
+
});
|
|
23782
|
+
if (!res.ok) throw new Error(`Form submission failed: ${res.status}`);
|
|
23783
|
+
setStatus("success");
|
|
23784
|
+
setValues({});
|
|
23785
|
+
startedAt.current = null;
|
|
23786
|
+
} catch (err) {
|
|
23787
|
+
console.error("[setto] form submission failed", err);
|
|
23788
|
+
setStatus("error");
|
|
23789
|
+
}
|
|
23790
|
+
};
|
|
23791
|
+
const ensureLocaleFields = () => {
|
|
23792
|
+
const keys = /* @__PURE__ */ new Map();
|
|
23793
|
+
if (!store) return keys;
|
|
23794
|
+
if (fromLocale) {
|
|
23795
|
+
for (const f of fields) if (f.itemKey) keys.set(f.name, f.itemKey);
|
|
23796
|
+
return keys;
|
|
23797
|
+
}
|
|
23798
|
+
const langs = i18n.languages?.length ? i18n.languages : [lng];
|
|
23799
|
+
for (const prop of propFields ?? []) {
|
|
23800
|
+
const itemKey = store.addListItem(prefix, lng, {
|
|
23801
|
+
name: prop.name,
|
|
23802
|
+
type: prop.type,
|
|
23803
|
+
...prop.required ? { required: "true" } : {}
|
|
23804
|
+
});
|
|
23805
|
+
for (const lang of langs) {
|
|
23806
|
+
const ft = i18n.getFixedT(lang);
|
|
23807
|
+
const label = ft(prop.labelKey, { defaultValue: "" });
|
|
23808
|
+
if (label) store.set(`${prefix}.${itemKey}.label`, lang, label);
|
|
23809
|
+
const placeholder = prop.placeholderKey ? ft(prop.placeholderKey, { defaultValue: "" }) : "";
|
|
23810
|
+
if (placeholder) store.set(`${prefix}.${itemKey}.placeholder`, lang, placeholder);
|
|
23811
|
+
const options = prop.optionsKey ? ft(prop.optionsKey, { defaultValue: "" }) : "";
|
|
23812
|
+
if (options) store.set(`${prefix}.${itemKey}.options`, lang, options);
|
|
23813
|
+
}
|
|
23814
|
+
keys.set(prop.name, itemKey);
|
|
23815
|
+
}
|
|
23816
|
+
return keys;
|
|
23817
|
+
};
|
|
23818
|
+
const itemKeyFor = (field) => field.itemKey ?? ensureLocaleFields().get(field.name);
|
|
23819
|
+
const allLanguages = () => i18n.languages?.length ? [...i18n.languages] : [lng];
|
|
23820
|
+
const setEverywhere = (key, value) => {
|
|
23821
|
+
for (const lang of allLanguages()) {
|
|
23822
|
+
if (value === void 0 || value === "") store.delete(key, lang);
|
|
23823
|
+
else store.set(key, lang, value);
|
|
23824
|
+
}
|
|
23825
|
+
};
|
|
23826
|
+
const addField = () => {
|
|
23827
|
+
if (!store) return;
|
|
23828
|
+
store.batch(() => {
|
|
23829
|
+
ensureLocaleFields();
|
|
23830
|
+
const taken = fields.map((f) => f.name);
|
|
23831
|
+
const itemKey = store.addListItem(prefix, lng, {
|
|
23832
|
+
name: uniqueFieldName("felt", taken),
|
|
23833
|
+
type: "text"
|
|
23834
|
+
});
|
|
23835
|
+
for (const lang of allLanguages()) store.set(`${prefix}.${itemKey}.label`, lang, "Nytt felt");
|
|
23836
|
+
setJustAdded(itemKey);
|
|
23837
|
+
});
|
|
23838
|
+
};
|
|
23839
|
+
const moveField = (field, direction) => {
|
|
23840
|
+
if (!store) return;
|
|
23841
|
+
store.batch(() => {
|
|
23842
|
+
const itemKey = itemKeyFor(field);
|
|
23843
|
+
if (itemKey) store.moveListItem(prefix, itemKey, direction, lng);
|
|
23844
|
+
});
|
|
23845
|
+
};
|
|
23846
|
+
const removeField = (field) => {
|
|
23847
|
+
if (!store || fields.length <= 1) return;
|
|
23848
|
+
store.batch(() => {
|
|
23849
|
+
const itemKey = itemKeyFor(field);
|
|
23850
|
+
if (itemKey) store.removeListItem(prefix, itemKey, lng);
|
|
23851
|
+
});
|
|
23852
|
+
};
|
|
23853
|
+
const updateField = (field, patch) => {
|
|
23854
|
+
if (!store) return;
|
|
23855
|
+
store.batch(() => {
|
|
23856
|
+
const itemKey = itemKeyFor(field);
|
|
23857
|
+
if (!itemKey) return;
|
|
23858
|
+
const item = `${prefix}.${itemKey}`;
|
|
23859
|
+
const others = fields.filter((f) => f !== field).map((f) => f.name);
|
|
23860
|
+
if (patch.type !== void 0) setEverywhere(`${item}.type`, patch.type);
|
|
23861
|
+
if (patch.required !== void 0) {
|
|
23862
|
+
setEverywhere(`${item}.required`, patch.required ? "true" : void 0);
|
|
23863
|
+
}
|
|
23864
|
+
if (patch.name !== void 0) {
|
|
23865
|
+
setEverywhere(`${item}.name`, uniqueFieldName(slugifyFieldName(patch.name), others));
|
|
23866
|
+
}
|
|
23867
|
+
if (patch.label !== void 0) {
|
|
23868
|
+
store.set(`${item}.label`, lng, patch.label);
|
|
23869
|
+
if (patch.name === void 0 && /^felt(-\d+)?$/.test(field.name) && patch.label.trim()) {
|
|
23870
|
+
setEverywhere(`${item}.name`, uniqueFieldName(slugifyFieldName(patch.label), others));
|
|
23871
|
+
}
|
|
23872
|
+
}
|
|
23873
|
+
if (patch.placeholder !== void 0) {
|
|
23874
|
+
if (patch.placeholder) store.set(`${item}.placeholder`, lng, patch.placeholder);
|
|
23875
|
+
else store.delete(`${item}.placeholder`, lng);
|
|
23876
|
+
}
|
|
23877
|
+
if (patch.options !== void 0) {
|
|
23878
|
+
if (patch.options.trim()) store.set(`${item}.options`, lng, patch.options);
|
|
23879
|
+
else store.delete(`${item}.options`, lng);
|
|
23880
|
+
}
|
|
23881
|
+
});
|
|
23882
|
+
};
|
|
23883
|
+
if (status === "success") {
|
|
23884
|
+
return /* @__PURE__ */ jsxs(
|
|
23885
|
+
"div",
|
|
23886
|
+
{
|
|
23887
|
+
ref: successRef,
|
|
23888
|
+
tabIndex: -1,
|
|
23889
|
+
role: "status",
|
|
23890
|
+
"aria-live": "polite",
|
|
23891
|
+
"data-setto-form": formId,
|
|
23892
|
+
"data-setto-form-status": "success",
|
|
23893
|
+
className: cx("setto-form__success", className, classNames.success),
|
|
23894
|
+
children: [
|
|
23895
|
+
/* @__PURE__ */ jsx(CheckIcon, {}),
|
|
23896
|
+
/* @__PURE__ */ jsx("p", { className: "setto-form__success-text", children: /* @__PURE__ */ jsx(T, { k: successKey }) }),
|
|
23897
|
+
/* @__PURE__ */ jsx(
|
|
23898
|
+
"button",
|
|
23899
|
+
{
|
|
23900
|
+
type: "button",
|
|
23901
|
+
className: "setto-form__again",
|
|
23902
|
+
onClick: () => {
|
|
23903
|
+
setStatus("idle");
|
|
23904
|
+
setErrors({});
|
|
23905
|
+
},
|
|
23906
|
+
children: t("form.again", { defaultValue: "Send en ny melding" })
|
|
23907
|
+
}
|
|
23908
|
+
)
|
|
23909
|
+
]
|
|
23910
|
+
}
|
|
23911
|
+
);
|
|
23912
|
+
}
|
|
23913
|
+
const submitting = status === "submitting";
|
|
23914
|
+
return /* @__PURE__ */ jsxs(
|
|
23915
|
+
"form",
|
|
23916
|
+
{
|
|
23917
|
+
ref: formRef,
|
|
23918
|
+
"data-setto-form": formId,
|
|
23919
|
+
className: cx("setto-form", className, classNames.form),
|
|
23920
|
+
onSubmit,
|
|
23921
|
+
onFocusCapture: markStarted,
|
|
23922
|
+
onPointerDownCapture: markStarted,
|
|
23923
|
+
onKeyDownCapture: markStarted,
|
|
23924
|
+
noValidate: true,
|
|
23925
|
+
children: [
|
|
23926
|
+
fields.map((field, index) => {
|
|
23927
|
+
const id = `${uid}-${field.name}`;
|
|
23928
|
+
const error = errors[field.name];
|
|
23929
|
+
const errorId = error ? `${id}-error` : void 0;
|
|
23930
|
+
return /* @__PURE__ */ jsxs(
|
|
23931
|
+
"div",
|
|
23932
|
+
{
|
|
23933
|
+
"data-setto-field": field.name,
|
|
23934
|
+
className: cx("setto-form__field", `setto-form__field--${field.type}`, classNames.field),
|
|
23935
|
+
children: [
|
|
23936
|
+
/* @__PURE__ */ jsx(
|
|
23937
|
+
FieldControl,
|
|
23938
|
+
{
|
|
23939
|
+
field,
|
|
23940
|
+
id,
|
|
23941
|
+
value: valueOf(field),
|
|
23942
|
+
editable,
|
|
23943
|
+
invalid: error !== void 0,
|
|
23944
|
+
describedBy: errorId,
|
|
23945
|
+
classNames,
|
|
23946
|
+
placeholderFallback: t("form.choose", { defaultValue: "Velg …" }),
|
|
23947
|
+
onChange: (value) => setValue(field.name, value)
|
|
23948
|
+
}
|
|
23949
|
+
),
|
|
23950
|
+
error ? /* @__PURE__ */ jsx("p", { id: errorId, className: cx("setto-form__field-error", classNames.fieldError), children: t(`form.validation.${error}`, { defaultValue: FIELD_ERROR_DEFAULTS[error] }) }) : null,
|
|
23951
|
+
editable && store ? /* @__PURE__ */ jsx(
|
|
23952
|
+
FormFieldTools,
|
|
23953
|
+
{
|
|
23954
|
+
field,
|
|
23955
|
+
index,
|
|
23956
|
+
count: fields.length,
|
|
23957
|
+
autoOpen: justAdded !== null && justAdded === field.itemKey,
|
|
23958
|
+
onMove: (direction) => moveField(field, direction),
|
|
23959
|
+
onRemove: () => removeField(field),
|
|
23960
|
+
onChange: (patch) => updateField(field, patch)
|
|
23961
|
+
}
|
|
23962
|
+
) : null
|
|
23963
|
+
]
|
|
23964
|
+
},
|
|
23965
|
+
field.itemKey ?? field.name
|
|
23966
|
+
);
|
|
23967
|
+
}),
|
|
23968
|
+
editable && store ? /* @__PURE__ */ jsx(
|
|
23969
|
+
"button",
|
|
23970
|
+
{
|
|
23971
|
+
type: "button",
|
|
23972
|
+
"data-setto-ui": true,
|
|
23973
|
+
className: "setto-form__add-field",
|
|
23974
|
+
style: addFieldStyle,
|
|
23975
|
+
onClick: addField,
|
|
23976
|
+
children: "+ Legg til felt"
|
|
23977
|
+
}
|
|
23978
|
+
) : null,
|
|
23979
|
+
/* @__PURE__ */ jsxs("div", { className: "setto-form__hp", "aria-hidden": "true", children: [
|
|
23980
|
+
/* @__PURE__ */ jsx("label", { htmlFor: `${uid}-${HONEYPOT_NAME}`, children: "Nettside" }),
|
|
23981
|
+
/* @__PURE__ */ jsx(
|
|
23982
|
+
"input",
|
|
23983
|
+
{
|
|
23984
|
+
ref: honeypotRef,
|
|
23985
|
+
id: `${uid}-${HONEYPOT_NAME}`,
|
|
23986
|
+
type: "text",
|
|
23987
|
+
name: HONEYPOT_NAME,
|
|
23988
|
+
tabIndex: -1,
|
|
23989
|
+
autoComplete: "off",
|
|
23990
|
+
defaultValue: ""
|
|
23991
|
+
}
|
|
23992
|
+
)
|
|
23993
|
+
] }),
|
|
23994
|
+
/* @__PURE__ */ jsxs("div", { className: cx("setto-form__actions", classNames.actions), children: [
|
|
23995
|
+
/* @__PURE__ */ jsx(
|
|
23996
|
+
"button",
|
|
23997
|
+
{
|
|
23998
|
+
type: "submit",
|
|
23999
|
+
className: cx("setto-form__button", classNames.button),
|
|
24000
|
+
disabled: submitting,
|
|
24001
|
+
"aria-busy": submitting || void 0,
|
|
24002
|
+
children: submitting ? t("form.sending", { defaultValue: "Sender …" }) : /* @__PURE__ */ jsx(T, { k: submitKey })
|
|
24003
|
+
}
|
|
24004
|
+
),
|
|
24005
|
+
status === "error" ? /* @__PURE__ */ jsx("p", { role: "alert", "data-setto-form-status": "error", className: cx("setto-form__error", classNames.error), children: /* @__PURE__ */ jsx(T, { k: errorKey }) }) : null
|
|
24006
|
+
] }),
|
|
24007
|
+
editable ? /* @__PURE__ */ jsxs("div", { className: "setto-form__edit-previews", style: previewStyle, children: [
|
|
24008
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
24009
|
+
/* @__PURE__ */ jsx("span", { style: previewHintStyle, children: "Etter innsending vises:" }),
|
|
24010
|
+
" ",
|
|
24011
|
+
/* @__PURE__ */ jsx(T, { k: successKey })
|
|
24012
|
+
] }),
|
|
24013
|
+
status !== "error" ? /* @__PURE__ */ jsxs("div", { children: [
|
|
24014
|
+
/* @__PURE__ */ jsx("span", { style: previewHintStyle, children: "Ved feil vises:" }),
|
|
24015
|
+
" ",
|
|
24016
|
+
/* @__PURE__ */ jsx(T, { k: errorKey })
|
|
24017
|
+
] }) : null
|
|
24018
|
+
] }) : null
|
|
24019
|
+
]
|
|
24020
|
+
}
|
|
24021
|
+
);
|
|
24022
|
+
}
|
|
24023
|
+
function FieldControl({
|
|
24024
|
+
field,
|
|
24025
|
+
id,
|
|
24026
|
+
value,
|
|
24027
|
+
editable,
|
|
24028
|
+
invalid,
|
|
24029
|
+
describedBy,
|
|
24030
|
+
classNames,
|
|
24031
|
+
placeholderFallback,
|
|
24032
|
+
onChange
|
|
24033
|
+
}) {
|
|
24034
|
+
const labelId = `${id}-label`;
|
|
24035
|
+
const aria = {
|
|
24036
|
+
"aria-invalid": invalid || void 0,
|
|
24037
|
+
"aria-describedby": describedBy,
|
|
24038
|
+
required: field.required || void 0
|
|
24039
|
+
};
|
|
24040
|
+
const labelText = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
24041
|
+
/* @__PURE__ */ jsx(T, { k: field.labelKey }),
|
|
24042
|
+
field.required ? /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "setto-form__required", children: "*" }) : null
|
|
24043
|
+
] });
|
|
24044
|
+
if (field.type === "checkbox") {
|
|
24045
|
+
const checked = value === true;
|
|
24046
|
+
return /* @__PURE__ */ jsxs(
|
|
24047
|
+
FieldLabel,
|
|
24048
|
+
{
|
|
24049
|
+
editable,
|
|
24050
|
+
htmlFor: id,
|
|
24051
|
+
id: labelId,
|
|
24052
|
+
className: cx("setto-form__check", classNames.checkbox),
|
|
24053
|
+
children: [
|
|
24054
|
+
/* @__PURE__ */ jsx(
|
|
24055
|
+
"input",
|
|
24056
|
+
{
|
|
24057
|
+
id,
|
|
24058
|
+
type: "checkbox",
|
|
24059
|
+
name: field.name,
|
|
24060
|
+
checked,
|
|
24061
|
+
onChange: (e) => onChange(e.target.checked),
|
|
24062
|
+
...editable ? { "aria-labelledby": labelId } : {},
|
|
24063
|
+
...aria
|
|
24064
|
+
}
|
|
24065
|
+
),
|
|
24066
|
+
/* @__PURE__ */ jsx("span", { children: labelText })
|
|
24067
|
+
]
|
|
24068
|
+
}
|
|
24069
|
+
);
|
|
24070
|
+
}
|
|
24071
|
+
if (field.type === "radio" || field.type === "checkboxes") {
|
|
24072
|
+
const multiple = field.type === "checkboxes";
|
|
24073
|
+
const selected = multiple ? Array.isArray(value) ? value : [] : value;
|
|
24074
|
+
const toggle = (option, on) => {
|
|
24075
|
+
if (!multiple) {
|
|
24076
|
+
onChange(option);
|
|
24077
|
+
return;
|
|
24078
|
+
}
|
|
24079
|
+
const set = new Set(Array.isArray(selected) ? selected : []);
|
|
24080
|
+
if (on) set.add(option);
|
|
24081
|
+
else set.delete(option);
|
|
24082
|
+
onChange(field.options.filter((o) => set.has(o)));
|
|
24083
|
+
};
|
|
24084
|
+
return /* @__PURE__ */ jsxs(
|
|
24085
|
+
"fieldset",
|
|
24086
|
+
{
|
|
24087
|
+
className: "setto-form__fieldset",
|
|
24088
|
+
role: multiple ? void 0 : "radiogroup",
|
|
24089
|
+
"aria-invalid": invalid || void 0,
|
|
24090
|
+
"aria-describedby": describedBy,
|
|
24091
|
+
"aria-required": !multiple && field.required ? true : void 0,
|
|
24092
|
+
children: [
|
|
24093
|
+
/* @__PURE__ */ jsx("legend", { className: cx("setto-form__legend", classNames.label), children: labelText }),
|
|
24094
|
+
/* @__PURE__ */ jsx("div", { className: cx("setto-form__options", classNames.options), children: field.options.map((option, i) => {
|
|
24095
|
+
const optionId = `${id}-${i}`;
|
|
24096
|
+
const on = multiple ? selected.includes(option) : selected === option;
|
|
24097
|
+
return /* @__PURE__ */ jsxs("label", { htmlFor: optionId, className: cx("setto-form__option", classNames.option), children: [
|
|
24098
|
+
/* @__PURE__ */ jsx(
|
|
24099
|
+
"input",
|
|
24100
|
+
{
|
|
24101
|
+
id: optionId,
|
|
24102
|
+
type: multiple ? "checkbox" : "radio",
|
|
24103
|
+
name: field.name,
|
|
24104
|
+
value: option,
|
|
24105
|
+
checked: on,
|
|
24106
|
+
required: !multiple && field.required ? true : void 0,
|
|
24107
|
+
onChange: (e) => toggle(option, e.target.checked)
|
|
24108
|
+
}
|
|
24109
|
+
),
|
|
24110
|
+
/* @__PURE__ */ jsx("span", { children: option })
|
|
24111
|
+
] }, option);
|
|
24112
|
+
}) })
|
|
24113
|
+
]
|
|
24114
|
+
}
|
|
24115
|
+
);
|
|
24116
|
+
}
|
|
24117
|
+
const text = typeof value === "string" ? value : "";
|
|
24118
|
+
const labelled = editable ? { "aria-labelledby": labelId } : {};
|
|
24119
|
+
let control;
|
|
24120
|
+
if (field.type === "textarea") {
|
|
24121
|
+
control = /* @__PURE__ */ jsx(
|
|
24122
|
+
"textarea",
|
|
24123
|
+
{
|
|
24124
|
+
id,
|
|
24125
|
+
name: field.name,
|
|
24126
|
+
value: text,
|
|
24127
|
+
rows: 5,
|
|
24128
|
+
placeholder: field.placeholder || void 0,
|
|
24129
|
+
onChange: (e) => onChange(e.target.value),
|
|
24130
|
+
className: cx("setto-form__textarea", classNames.textarea),
|
|
24131
|
+
...labelled,
|
|
24132
|
+
...aria
|
|
24133
|
+
}
|
|
24134
|
+
);
|
|
24135
|
+
} else if (field.type === "select") {
|
|
24136
|
+
control = /* @__PURE__ */ jsxs(
|
|
24137
|
+
"select",
|
|
24138
|
+
{
|
|
24139
|
+
id,
|
|
24140
|
+
name: field.name,
|
|
24141
|
+
value: text,
|
|
24142
|
+
onChange: (e) => onChange(e.target.value),
|
|
24143
|
+
className: cx("setto-form__select", classNames.select),
|
|
24144
|
+
...labelled,
|
|
24145
|
+
...aria,
|
|
24146
|
+
children: [
|
|
24147
|
+
/* @__PURE__ */ jsx("option", { value: "", disabled: field.required, children: field.placeholder || placeholderFallback }),
|
|
24148
|
+
field.options.map((option) => /* @__PURE__ */ jsx("option", { value: option, children: option }, option))
|
|
24149
|
+
]
|
|
24150
|
+
}
|
|
24151
|
+
);
|
|
24152
|
+
} else {
|
|
24153
|
+
control = /* @__PURE__ */ jsx(
|
|
24154
|
+
"input",
|
|
24155
|
+
{
|
|
24156
|
+
id,
|
|
24157
|
+
type: inputType(field),
|
|
24158
|
+
name: field.name,
|
|
24159
|
+
value: text,
|
|
24160
|
+
placeholder: field.placeholder || void 0,
|
|
24161
|
+
inputMode: inputMode(field),
|
|
24162
|
+
autoComplete: autoCompleteFor(field),
|
|
24163
|
+
onChange: (e) => onChange(e.target.value),
|
|
24164
|
+
className: cx("setto-form__input", classNames.input),
|
|
24165
|
+
...labelled,
|
|
24166
|
+
...aria
|
|
24167
|
+
}
|
|
24168
|
+
);
|
|
24169
|
+
}
|
|
24170
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
24171
|
+
/* @__PURE__ */ jsx(
|
|
24172
|
+
FieldLabel,
|
|
24173
|
+
{
|
|
24174
|
+
editable,
|
|
24175
|
+
htmlFor: id,
|
|
24176
|
+
id: labelId,
|
|
24177
|
+
className: cx("setto-form__label", classNames.label),
|
|
24178
|
+
children: labelText
|
|
24179
|
+
}
|
|
24180
|
+
),
|
|
24181
|
+
control
|
|
24182
|
+
] });
|
|
24183
|
+
}
|
|
24184
|
+
function FieldLabel({
|
|
24185
|
+
editable,
|
|
24186
|
+
htmlFor,
|
|
24187
|
+
id,
|
|
24188
|
+
className,
|
|
24189
|
+
children
|
|
24190
|
+
}) {
|
|
24191
|
+
if (editable) {
|
|
24192
|
+
return /* @__PURE__ */ jsx("span", { id, className, children });
|
|
24193
|
+
}
|
|
24194
|
+
return /* @__PURE__ */ jsx("label", { htmlFor, className, children });
|
|
24195
|
+
}
|
|
24196
|
+
function inputType(field) {
|
|
24197
|
+
switch (field.type) {
|
|
24198
|
+
case "email":
|
|
24199
|
+
case "tel":
|
|
24200
|
+
case "url":
|
|
24201
|
+
case "date":
|
|
24202
|
+
return field.type;
|
|
24203
|
+
case "number":
|
|
24204
|
+
return "text";
|
|
24205
|
+
default:
|
|
24206
|
+
return "text";
|
|
24207
|
+
}
|
|
24208
|
+
}
|
|
24209
|
+
function inputMode(field) {
|
|
24210
|
+
switch (field.type) {
|
|
24211
|
+
case "email":
|
|
24212
|
+
return "email";
|
|
24213
|
+
case "tel":
|
|
24214
|
+
return "tel";
|
|
24215
|
+
case "url":
|
|
24216
|
+
return "url";
|
|
24217
|
+
case "number":
|
|
24218
|
+
return "decimal";
|
|
24219
|
+
default:
|
|
24220
|
+
return void 0;
|
|
24221
|
+
}
|
|
24222
|
+
}
|
|
24223
|
+
function autoCompleteFor(field) {
|
|
24224
|
+
if (field.type === "email") return "email";
|
|
24225
|
+
if (field.type === "tel") return "tel";
|
|
24226
|
+
if (field.type === "url") return "url";
|
|
24227
|
+
if (/^(navn|name|fullt?[-_]?navn|full[-_]?name)$/i.test(field.name)) return "name";
|
|
24228
|
+
if (/^(firma|bedrift|company|organisasjon|organization)$/i.test(field.name)) return "organization";
|
|
24229
|
+
return void 0;
|
|
24230
|
+
}
|
|
24231
|
+
function cssEscape(value) {
|
|
24232
|
+
return typeof CSS !== "undefined" && typeof CSS.escape === "function" ? CSS.escape(value) : value.replace(/["\\]/g, "\\$&");
|
|
24233
|
+
}
|
|
24234
|
+
function CheckIcon() {
|
|
24235
|
+
return /* @__PURE__ */ jsxs(
|
|
24236
|
+
"svg",
|
|
24237
|
+
{
|
|
24238
|
+
className: "setto-form__success-icon",
|
|
24239
|
+
viewBox: "0 0 40 40",
|
|
24240
|
+
fill: "none",
|
|
24241
|
+
"aria-hidden": "true",
|
|
24242
|
+
focusable: "false",
|
|
24243
|
+
children: [
|
|
24244
|
+
/* @__PURE__ */ jsx("circle", { cx: "20", cy: "20", r: "19", stroke: "currentColor", strokeWidth: "2" }),
|
|
24245
|
+
/* @__PURE__ */ jsx(
|
|
24246
|
+
"path",
|
|
24247
|
+
{
|
|
24248
|
+
d: "M12 20.5l5.5 5.5L28 15",
|
|
24249
|
+
stroke: "currentColor",
|
|
24250
|
+
strokeWidth: "2.5",
|
|
24251
|
+
strokeLinecap: "round",
|
|
24252
|
+
strokeLinejoin: "round"
|
|
24253
|
+
}
|
|
24254
|
+
)
|
|
24255
|
+
]
|
|
24256
|
+
}
|
|
24257
|
+
);
|
|
24258
|
+
}
|
|
24259
|
+
const STYLE_ID$1 = "setto-form-styles";
|
|
24260
|
+
const BASE_CSS = `
|
|
24261
|
+
:where(.setto-form){display:grid;gap:1.1rem;position:relative}
|
|
24262
|
+
:where(.setto-form__field){display:grid;gap:.4rem;position:relative}
|
|
24263
|
+
:where(.setto-form__label,.setto-form__legend){font-weight:500;line-height:1.3;padding:0}
|
|
24264
|
+
:where(.setto-form__required){opacity:.6;margin-left:.15em}
|
|
24265
|
+
:where(.setto-form__input,.setto-form__textarea,.setto-form__select){width:100%;box-sizing:border-box;padding:.7rem .85rem;border:1px solid color-mix(in srgb,currentColor 28%,transparent);border-radius:.6rem;background:color-mix(in srgb,currentColor 4%,transparent);color:inherit;font:inherit;line-height:1.4}
|
|
24266
|
+
:where(.setto-form__input,.setto-form__textarea,.setto-form__select)::placeholder{color:inherit;opacity:.5}
|
|
24267
|
+
:where(.setto-form__input,.setto-form__textarea,.setto-form__select):focus-visible{outline:2px solid currentColor;outline-offset:1px}
|
|
24268
|
+
:where(.setto-form__textarea){min-height:7.5rem;resize:vertical}
|
|
24269
|
+
:where(.setto-form__fieldset){border:0;padding:0;margin:0;min-width:0;display:grid;gap:.5rem}
|
|
24270
|
+
:where(.setto-form__options){display:grid;gap:.5rem}
|
|
24271
|
+
:where(.setto-form__option,.setto-form__check){display:flex;gap:.6rem;align-items:flex-start;line-height:1.4;cursor:pointer;font-weight:400}
|
|
24272
|
+
:where(.setto-form__option input,.setto-form__check input){margin:.25em 0 0;flex:none;accent-color:currentColor}
|
|
24273
|
+
:where(.setto-form__field-error,.setto-form__error){font-size:.875rem;color:#b42318;margin:0}
|
|
24274
|
+
:where(.setto-form [aria-invalid="true"]){border-color:#b42318}
|
|
24275
|
+
:where(.setto-form__actions){display:flex;flex-wrap:wrap;gap:.75rem 1rem;align-items:center}
|
|
24276
|
+
:where(.setto-form__button){cursor:pointer;font:inherit;font-weight:500;padding:.75rem 1.4rem;border-radius:999px;border:1px solid currentColor;background:transparent;color:inherit}
|
|
24277
|
+
:where(.setto-form__button:disabled){opacity:.6;cursor:progress}
|
|
24278
|
+
:where(.setto-form__success){display:grid;gap:.75rem;justify-items:start;outline:none}
|
|
24279
|
+
:where(.setto-form__success-icon){width:2.5rem;height:2.5rem;color:#0f7b4f}
|
|
24280
|
+
:where(.setto-form__success-text){margin:0;font-size:1.05em}
|
|
24281
|
+
:where(.setto-form__again){background:none;border:0;padding:0;color:inherit;text-decoration:underline;cursor:pointer;font:inherit;opacity:.8;width:fit-content;max-width:100%;text-align:inherit}
|
|
24282
|
+
:where(.setto-form__hp){position:absolute;left:-10000px;top:auto;width:1px;height:1px;overflow:hidden;opacity:0;pointer-events:none}
|
|
24283
|
+
`;
|
|
24284
|
+
function useFormStyles() {
|
|
24285
|
+
useInsertionEffect(() => {
|
|
24286
|
+
if (typeof document === "undefined" || document.getElementById(STYLE_ID$1)) return;
|
|
24287
|
+
const el = document.createElement("style");
|
|
24288
|
+
el.id = STYLE_ID$1;
|
|
24289
|
+
el.textContent = BASE_CSS;
|
|
24290
|
+
document.head.appendChild(el);
|
|
24291
|
+
}, []);
|
|
24292
|
+
}
|
|
24293
|
+
const addFieldStyle = {
|
|
24294
|
+
justifySelf: "start",
|
|
24295
|
+
padding: "6px 12px",
|
|
24296
|
+
border: "1px dashed #999",
|
|
24297
|
+
borderRadius: 999,
|
|
24298
|
+
background: "rgba(255,255,255,0.85)",
|
|
24299
|
+
color: "#333",
|
|
24300
|
+
fontSize: 13,
|
|
24301
|
+
fontFamily: 'system-ui, -apple-system, "Segoe UI", sans-serif',
|
|
24302
|
+
cursor: "pointer"
|
|
24303
|
+
};
|
|
24304
|
+
const previewStyle = {
|
|
24305
|
+
display: "grid",
|
|
24306
|
+
gap: 6,
|
|
24307
|
+
padding: "10px 12px",
|
|
24308
|
+
border: "1px dashed rgba(100,10,255,0.35)",
|
|
24309
|
+
borderRadius: 8,
|
|
24310
|
+
fontSize: "0.9em",
|
|
24311
|
+
lineHeight: 1.4
|
|
24312
|
+
};
|
|
24313
|
+
const previewHintStyle = {
|
|
24314
|
+
color: "#640AFF",
|
|
24315
|
+
fontSize: 11,
|
|
24316
|
+
fontFamily: 'system-ui, -apple-system, "Segoe UI", sans-serif',
|
|
24317
|
+
textTransform: "uppercase",
|
|
24318
|
+
letterSpacing: "0.04em",
|
|
24319
|
+
marginRight: 4
|
|
24320
|
+
};
|
|
24321
|
+
const formsModule = {
|
|
24322
|
+
id: "forms",
|
|
24323
|
+
label: "Skjemaer",
|
|
24324
|
+
AdminView: FormsView
|
|
24325
|
+
};
|
|
24326
|
+
const SETTO_MODULES = [formsModule];
|
|
24327
|
+
function findModule(id) {
|
|
24328
|
+
return id ? SETTO_MODULES.find((m) => m.id === id) : void 0;
|
|
24329
|
+
}
|
|
24330
|
+
function SettoMark({
|
|
24331
|
+
size = 24,
|
|
24332
|
+
letterColor = "#F2EBDF",
|
|
24333
|
+
dotColor = "#C4502A",
|
|
24334
|
+
style
|
|
24335
|
+
}) {
|
|
24336
|
+
return /* @__PURE__ */ jsxs(
|
|
24337
|
+
"svg",
|
|
24338
|
+
{
|
|
24339
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
24340
|
+
viewBox: "0 0 64 64",
|
|
24341
|
+
width: size,
|
|
24342
|
+
height: size,
|
|
24343
|
+
"aria-hidden": true,
|
|
24344
|
+
style: { display: "block", ...style },
|
|
24345
|
+
children: [
|
|
24346
|
+
/* @__PURE__ */ jsx(
|
|
24347
|
+
"text",
|
|
24348
|
+
{
|
|
24349
|
+
x: "32",
|
|
24350
|
+
y: "46",
|
|
24351
|
+
textAnchor: "middle",
|
|
24352
|
+
fontFamily: "Georgia, serif",
|
|
24353
|
+
fontStyle: "italic",
|
|
24354
|
+
fontSize: "42",
|
|
24355
|
+
fill: letterColor,
|
|
24356
|
+
children: "S"
|
|
24357
|
+
}
|
|
24358
|
+
),
|
|
24359
|
+
/* @__PURE__ */ jsx("circle", { cx: "49", cy: "17", r: "5", fill: dotColor })
|
|
24360
|
+
]
|
|
24361
|
+
}
|
|
24362
|
+
);
|
|
24363
|
+
}
|
|
24364
|
+
const BLEKK = "#211711";
|
|
24365
|
+
const CREMA = "#F2EBDF";
|
|
24366
|
+
const TERRACOTTA = "#C4502A";
|
|
24367
|
+
function EditToolbar() {
|
|
24368
|
+
const { store, themeStore, assetStore, api, config, supabase } = useSetto();
|
|
24369
|
+
const { i18n } = useTranslation();
|
|
24370
|
+
useEditBaseline();
|
|
24371
|
+
const snap = useSyncExternalStore(
|
|
24372
|
+
(cb) => store?.subscribe(() => cb()) ?? (() => {
|
|
24373
|
+
}),
|
|
24374
|
+
() => store?.snapshot() ?? EMPTY_I18N_STORE_SNAPSHOT,
|
|
24375
|
+
() => EMPTY_I18N_STORE_SNAPSHOT
|
|
24376
|
+
);
|
|
24377
|
+
useSyncExternalStore(
|
|
24378
|
+
(cb) => themeStore?.subscribe(() => cb()) ?? (() => {
|
|
24379
|
+
}),
|
|
24380
|
+
() => themeStore?.snapshot().version ?? 0,
|
|
24381
|
+
() => 0
|
|
24382
|
+
);
|
|
24383
|
+
useSyncExternalStore(
|
|
24384
|
+
(cb) => assetStore?.subscribe(() => cb()) ?? (() => {
|
|
24385
|
+
}),
|
|
24386
|
+
() => assetStore?.snapshot().version ?? 0,
|
|
24387
|
+
() => 0
|
|
24388
|
+
);
|
|
24389
|
+
const [publishing, setPublishing] = useState(false);
|
|
24390
|
+
const [activeDeployment, setActiveDeployment] = useState(null);
|
|
24391
|
+
const [publishDialogOpen, setPublishDialogOpen] = useState(false);
|
|
24392
|
+
const [error, setError] = useState(null);
|
|
24393
|
+
const [publishedNotice, setPublishedNotice] = useState(false);
|
|
24394
|
+
const [menuOpen, setMenuOpen] = useState(false);
|
|
24395
|
+
const [expanded, setExpanded] = useState(false);
|
|
24396
|
+
const menuRef = useRef(null);
|
|
24397
|
+
const publishedNoticeTimer = useRef(null);
|
|
24398
|
+
const { row: deploymentRow, status: deploymentStatus } = useDeploymentStatus(
|
|
24399
|
+
supabase,
|
|
24400
|
+
activeDeployment
|
|
24401
|
+
);
|
|
24402
|
+
useEffect(() => {
|
|
24403
|
+
let cancelled = false;
|
|
24404
|
+
const resume = async () => {
|
|
24405
|
+
try {
|
|
24406
|
+
await api.expireStaleDeployments(config.siteId);
|
|
24407
|
+
} catch {
|
|
24408
|
+
}
|
|
24409
|
+
const id = await fetchInProgressDeploymentId(supabase, config.siteId);
|
|
24410
|
+
if (!cancelled && id) {
|
|
24411
|
+
setActiveDeployment((current) => current ?? id);
|
|
24412
|
+
}
|
|
24413
|
+
};
|
|
24414
|
+
void resume();
|
|
24415
|
+
return () => {
|
|
24416
|
+
cancelled = true;
|
|
24417
|
+
};
|
|
24418
|
+
}, [api, supabase, config.siteId]);
|
|
24419
|
+
const textDraftCount = snap.drafts.size;
|
|
24420
|
+
const themeDraftCount = themeStore?.size() ?? 0;
|
|
24421
|
+
const assetDraftCount = assetStore?.size() ?? 0;
|
|
24422
|
+
const draftCount = textDraftCount + themeDraftCount + assetDraftCount;
|
|
24423
|
+
const hasDrafts = draftCount > 0;
|
|
24424
|
+
const showToolbarProgress = activeDeployment !== null && !publishDialogOpen && deploymentRow !== null && isDeploymentInProgress(deploymentRow.status) && !isDeploymentStale(deploymentRow);
|
|
24425
|
+
const showPublishedNotice = publishedNotice && !publishDialogOpen;
|
|
24426
|
+
useEffect(() => {
|
|
24427
|
+
if (error || showToolbarProgress || showPublishedNotice) {
|
|
24428
|
+
setExpanded(true);
|
|
24429
|
+
}
|
|
24430
|
+
}, [error, showToolbarProgress, showPublishedNotice]);
|
|
24431
|
+
const toggleExpanded = () => {
|
|
24432
|
+
setExpanded((prev) => {
|
|
24433
|
+
const next = !prev;
|
|
24434
|
+
if (!next) setMenuOpen(false);
|
|
24435
|
+
return next;
|
|
24436
|
+
});
|
|
24437
|
+
};
|
|
24438
|
+
useEffect(() => {
|
|
24439
|
+
return () => {
|
|
24440
|
+
if (publishedNoticeTimer.current) clearTimeout(publishedNoticeTimer.current);
|
|
24441
|
+
};
|
|
24442
|
+
}, []);
|
|
24443
|
+
useEffect(() => {
|
|
24444
|
+
if (!activeDeployment || !deploymentRow) return;
|
|
24445
|
+
const { status } = deploymentRow;
|
|
24446
|
+
if (status === "ready") {
|
|
24447
|
+
if (publishDialogOpen) return;
|
|
24448
|
+
setPublishedNotice(true);
|
|
24449
|
+
setActiveDeployment(null);
|
|
24450
|
+
if (publishedNoticeTimer.current) clearTimeout(publishedNoticeTimer.current);
|
|
24451
|
+
publishedNoticeTimer.current = setTimeout(() => setPublishedNotice(false), 6e3);
|
|
24452
|
+
return;
|
|
24453
|
+
}
|
|
24454
|
+
if (publishDialogOpen) return;
|
|
24455
|
+
if (status === "error" || status === "canceled") {
|
|
24456
|
+
setError(
|
|
24457
|
+
status === "canceled" ? "Publisering avbrutt." : deploymentRow.error_message?.includes("webhook") ? "Publisering utløpt (ingen webhook)." : "Publisering feilet."
|
|
24458
|
+
);
|
|
24459
|
+
setActiveDeployment(null);
|
|
24460
|
+
return;
|
|
24461
|
+
}
|
|
24462
|
+
if (isDeploymentStale(deploymentRow)) {
|
|
24463
|
+
setError("Publisering utløpt (ingen webhook).");
|
|
24464
|
+
setActiveDeployment(null);
|
|
24465
|
+
}
|
|
24466
|
+
}, [activeDeployment, deploymentRow, publishDialogOpen]);
|
|
24467
|
+
useEffect(() => {
|
|
24468
|
+
if (!activeDeployment) return;
|
|
24469
|
+
const syncActiveDeployment = async () => {
|
|
24470
|
+
const { data } = await supabase.from("deployments").select("status").eq("id", activeDeployment).maybeSingle();
|
|
24471
|
+
if (!data) {
|
|
24472
|
+
setActiveDeployment(null);
|
|
24473
|
+
return;
|
|
24474
|
+
}
|
|
24475
|
+
const status = data.status;
|
|
24476
|
+
if (status === "ready") {
|
|
24477
|
+
if (!publishDialogOpen) {
|
|
24478
|
+
setPublishedNotice(true);
|
|
24479
|
+
if (publishedNoticeTimer.current) clearTimeout(publishedNoticeTimer.current);
|
|
24480
|
+
publishedNoticeTimer.current = setTimeout(() => setPublishedNotice(false), 6e3);
|
|
24481
|
+
}
|
|
24482
|
+
setActiveDeployment(null);
|
|
24483
|
+
return;
|
|
24484
|
+
}
|
|
24485
|
+
if (!isDeploymentInProgress(status)) {
|
|
24486
|
+
if (status === "canceled") setError("Publisering avbrutt.");
|
|
24487
|
+
else if (status === "error") setError("Publisering feilet.");
|
|
24488
|
+
setActiveDeployment(null);
|
|
24489
|
+
}
|
|
24490
|
+
};
|
|
24491
|
+
const onVisible = () => {
|
|
24492
|
+
if (document.visibilityState === "visible") void syncActiveDeployment();
|
|
24493
|
+
};
|
|
24494
|
+
window.addEventListener("focus", syncActiveDeployment);
|
|
24495
|
+
document.addEventListener("visibilitychange", onVisible);
|
|
24496
|
+
return () => {
|
|
24497
|
+
window.removeEventListener("focus", syncActiveDeployment);
|
|
24498
|
+
document.removeEventListener("visibilitychange", onVisible);
|
|
24499
|
+
};
|
|
24500
|
+
}, [activeDeployment, publishDialogOpen, supabase]);
|
|
24501
|
+
const languages = i18n.languages?.length ? Array.from(new Set(i18n.languages)) : ["no", "en"];
|
|
24502
|
+
useEffect(() => {
|
|
24503
|
+
if (!menuOpen) return;
|
|
24504
|
+
const close = (e) => {
|
|
24505
|
+
if (menuRef.current && !menuRef.current.contains(e.target)) {
|
|
24506
|
+
setMenuOpen(false);
|
|
24507
|
+
}
|
|
24508
|
+
};
|
|
24509
|
+
document.addEventListener("mousedown", close);
|
|
24510
|
+
return () => document.removeEventListener("mousedown", close);
|
|
24511
|
+
}, [menuOpen]);
|
|
24512
|
+
const publish = async () => {
|
|
24513
|
+
if (!store) return;
|
|
24514
|
+
setPublishing(true);
|
|
24515
|
+
setPublishDialogOpen(true);
|
|
24516
|
+
setError(null);
|
|
24517
|
+
setPublishedNotice(false);
|
|
24518
|
+
try {
|
|
24519
|
+
const bundles = store.serialiseBundles();
|
|
24520
|
+
const files = Object.entries(bundles).map(([lng, data]) => ({
|
|
24521
|
+
path: pathForLang(config.siteId, lng, languages),
|
|
24522
|
+
content: JSON.stringify(data, null, 2) + "\n"
|
|
24523
|
+
}));
|
|
24524
|
+
if (themeStore && themeDraftCount > 0) {
|
|
24525
|
+
files.push({
|
|
24526
|
+
path: pathForTheme(config.siteId, config.themePath),
|
|
24527
|
+
content: JSON.stringify(themeStore.serialise(), null, 2) + "\n"
|
|
24528
|
+
});
|
|
24529
|
+
}
|
|
24530
|
+
if (assetStore && assetDraftCount > 0) {
|
|
24531
|
+
const result2 = await api.publish(config.siteId, files, {
|
|
24532
|
+
assets: assetStore.pendingAssets()
|
|
24533
|
+
});
|
|
24534
|
+
store.commit();
|
|
24535
|
+
themeStore?.commit();
|
|
24536
|
+
assetStore.commit();
|
|
24537
|
+
let deploymentId2 = result2.deploymentId;
|
|
24538
|
+
if (!deploymentId2 && result2.trackingDeferred) {
|
|
24539
|
+
deploymentId2 = await waitForDeploymentByCommit(
|
|
24540
|
+
supabase,
|
|
24541
|
+
config.siteId,
|
|
24542
|
+
result2.commitSha
|
|
24543
|
+
);
|
|
24544
|
+
}
|
|
24545
|
+
if (deploymentId2) {
|
|
24546
|
+
setActiveDeployment(deploymentId2);
|
|
24547
|
+
} else {
|
|
24548
|
+
setPublishDialogOpen(false);
|
|
24549
|
+
setPublishedNotice(true);
|
|
24550
|
+
}
|
|
24551
|
+
return;
|
|
24552
|
+
}
|
|
24553
|
+
const result = await api.publish(config.siteId, files);
|
|
24554
|
+
store.commit();
|
|
24555
|
+
themeStore?.commit();
|
|
24556
|
+
assetStore?.commit();
|
|
24557
|
+
let deploymentId = result.deploymentId;
|
|
24558
|
+
if (!deploymentId && result.trackingDeferred) {
|
|
24559
|
+
deploymentId = await waitForDeploymentByCommit(
|
|
24560
|
+
supabase,
|
|
24561
|
+
config.siteId,
|
|
24562
|
+
result.commitSha
|
|
24563
|
+
);
|
|
24564
|
+
}
|
|
24565
|
+
if (deploymentId) {
|
|
24566
|
+
setActiveDeployment(deploymentId);
|
|
24567
|
+
} else {
|
|
24568
|
+
setPublishDialogOpen(false);
|
|
24569
|
+
setPublishedNotice(true);
|
|
24570
|
+
}
|
|
24571
|
+
} catch (err) {
|
|
24572
|
+
setError(err instanceof Error ? err.message : "unknown error");
|
|
24573
|
+
setPublishDialogOpen(false);
|
|
24574
|
+
} finally {
|
|
24575
|
+
setPublishing(false);
|
|
24576
|
+
}
|
|
24577
|
+
};
|
|
24578
|
+
const completePublish = () => {
|
|
24579
|
+
setPublishDialogOpen(false);
|
|
24580
|
+
setActiveDeployment(null);
|
|
24581
|
+
setPublishedNotice(false);
|
|
24582
|
+
if (publishedNoticeTimer.current) clearTimeout(publishedNoticeTimer.current);
|
|
24583
|
+
};
|
|
24584
|
+
const dismissPublishedNotice = () => {
|
|
24585
|
+
setPublishedNotice(false);
|
|
24586
|
+
if (publishedNoticeTimer.current) clearTimeout(publishedNoticeTimer.current);
|
|
24587
|
+
};
|
|
24588
|
+
const cancelDrafts = () => {
|
|
24589
|
+
setMenuOpen(false);
|
|
24590
|
+
store?.revertAll();
|
|
24591
|
+
themeStore?.revertAll();
|
|
24592
|
+
assetStore?.revertAll();
|
|
24593
|
+
setError(null);
|
|
24594
|
+
setExpanded(false);
|
|
24595
|
+
};
|
|
24596
|
+
const signOut = () => {
|
|
24597
|
+
setMenuOpen(false);
|
|
24598
|
+
void supabase.auth.signOut();
|
|
24599
|
+
};
|
|
24600
|
+
const goToHistory = () => {
|
|
24601
|
+
setMenuOpen(false);
|
|
24602
|
+
window.location.href = SETTO_BASE;
|
|
24603
|
+
};
|
|
24604
|
+
const goToSettings = () => {
|
|
24605
|
+
setMenuOpen(false);
|
|
24606
|
+
window.location.href = adminViewUrl(SETTO_MODULES[0]?.id ?? "forms");
|
|
24607
|
+
};
|
|
24608
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
24609
|
+
/* @__PURE__ */ jsxs(
|
|
24610
|
+
"div",
|
|
24611
|
+
{
|
|
24612
|
+
role: "toolbar",
|
|
24613
|
+
"aria-label": "Setto editor",
|
|
24614
|
+
style: {
|
|
24615
|
+
...containerStyleFor(hasDrafts),
|
|
24616
|
+
...expanded ? expandedContainerStyle : {}
|
|
24617
|
+
},
|
|
24618
|
+
children: [
|
|
24619
|
+
/* @__PURE__ */ jsx(
|
|
22505
24620
|
"button",
|
|
22506
24621
|
{
|
|
22507
24622
|
type: "button",
|
|
@@ -22570,6 +24685,7 @@ function EditToolbar() {
|
|
|
22570
24685
|
}
|
|
22571
24686
|
),
|
|
22572
24687
|
/* @__PURE__ */ jsx(MenuItem, { onClick: goToHistory, icon: /* @__PURE__ */ jsx(HistoryIcon, {}), children: "Historikk" }),
|
|
24688
|
+
/* @__PURE__ */ jsx(MenuItem, { onClick: goToSettings, icon: /* @__PURE__ */ jsx(GearIcon, {}), children: "Innstillinger" }),
|
|
22573
24689
|
/* @__PURE__ */ jsx(MenuItem, { onClick: signOut, icon: /* @__PURE__ */ jsx(LogOutIcon, {}), children: "Logg ut" })
|
|
22574
24690
|
] }) : null
|
|
22575
24691
|
] })
|
|
@@ -22719,442 +24835,184 @@ function MenuItem({
|
|
|
22719
24835
|
children,
|
|
22720
24836
|
disabled = false
|
|
22721
24837
|
}) {
|
|
22722
|
-
return /* @__PURE__ */ jsxs(
|
|
22723
|
-
"button",
|
|
22724
|
-
{
|
|
22725
|
-
type: "button",
|
|
22726
|
-
onClick,
|
|
22727
|
-
disabled,
|
|
22728
|
-
style: { ...menuItemStyle, opacity: disabled ? 0.4 : 1, cursor: disabled ? "not-allowed" : "pointer" },
|
|
22729
|
-
role: "menuitem",
|
|
22730
|
-
onMouseEnter: (e) => {
|
|
22731
|
-
if (!disabled) e.currentTarget.style.background = "#f5f5f5";
|
|
22732
|
-
},
|
|
22733
|
-
onMouseLeave: (e) => {
|
|
22734
|
-
e.currentTarget.style.background = "transparent";
|
|
22735
|
-
},
|
|
22736
|
-
children: [
|
|
22737
|
-
/* @__PURE__ */ jsx("span", { style: menuIconWrapStyle, "aria-hidden": true, children: icon }),
|
|
22738
|
-
children
|
|
22739
|
-
]
|
|
22740
|
-
}
|
|
22741
|
-
);
|
|
22742
|
-
}
|
|
22743
|
-
const menuIconWrapStyle = {
|
|
22744
|
-
display: "flex",
|
|
22745
|
-
alignItems: "center",
|
|
22746
|
-
justifyContent: "center",
|
|
22747
|
-
flexShrink: 0,
|
|
22748
|
-
color: "#666"
|
|
22749
|
-
};
|
|
22750
|
-
function PublishedCheckIcon() {
|
|
22751
|
-
return /* @__PURE__ */ jsx(
|
|
22752
|
-
"svg",
|
|
22753
|
-
{
|
|
22754
|
-
width: "14",
|
|
22755
|
-
height: "14",
|
|
22756
|
-
viewBox: "0 0 24 24",
|
|
22757
|
-
fill: "none",
|
|
22758
|
-
stroke: "currentColor",
|
|
22759
|
-
strokeWidth: "2.5",
|
|
22760
|
-
strokeLinecap: "round",
|
|
22761
|
-
strokeLinejoin: "round",
|
|
22762
|
-
"aria-hidden": true,
|
|
22763
|
-
children: /* @__PURE__ */ jsx("polyline", { points: "20 6 9 17 4 12" })
|
|
22764
|
-
}
|
|
22765
|
-
);
|
|
22766
|
-
}
|
|
22767
|
-
function HistoryIcon() {
|
|
22768
|
-
return /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
22769
|
-
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10" }),
|
|
22770
|
-
/* @__PURE__ */ jsx("polyline", { points: "12 6 12 12 16 14" })
|
|
22771
|
-
] });
|
|
22772
|
-
}
|
|
22773
|
-
function LogOutIcon() {
|
|
22774
|
-
return /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
22775
|
-
/* @__PURE__ */ jsx("path", { d: "M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" }),
|
|
22776
|
-
/* @__PURE__ */ jsx("polyline", { points: "16 17 21 12 16 7" }),
|
|
22777
|
-
/* @__PURE__ */ jsx("line", { x1: "21", y1: "12", x2: "9", y2: "12" })
|
|
22778
|
-
] });
|
|
22779
|
-
}
|
|
22780
|
-
function UndoIcon() {
|
|
22781
|
-
return /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
22782
|
-
/* @__PURE__ */ jsx("path", { d: "M3 7v6h6" }),
|
|
22783
|
-
/* @__PURE__ */ jsx("path", { d: "M21 17a9 9 0 0 0-15-6.7L3 13" })
|
|
22784
|
-
] });
|
|
22785
|
-
}
|
|
22786
|
-
function EditModeShell({ children, themeStore, active }) {
|
|
22787
|
-
useSettoDocumentLayout(active);
|
|
22788
|
-
const [toolbarRoot, setToolbarRoot] = useState(null);
|
|
22789
|
-
const [hintRoot, setHintRoot] = useState(null);
|
|
22790
|
-
const [sectionToolbarRoot, setSectionToolbarRoot] = useState(null);
|
|
22791
|
-
useEffect(() => {
|
|
22792
|
-
if (!active) {
|
|
22793
|
-
setToolbarRoot(null);
|
|
22794
|
-
setHintRoot(null);
|
|
22795
|
-
setSectionToolbarRoot(null);
|
|
22796
|
-
return;
|
|
22797
|
-
}
|
|
22798
|
-
const toolbar = document.createElement("div");
|
|
22799
|
-
toolbar.id = "setto-toolbar-root";
|
|
22800
|
-
document.body.appendChild(toolbar);
|
|
22801
|
-
const hint = document.createElement("div");
|
|
22802
|
-
hint.id = "setto-hint-root";
|
|
22803
|
-
hint.style.pointerEvents = "none";
|
|
22804
|
-
document.body.appendChild(hint);
|
|
22805
|
-
const sectionBar = document.createElement("div");
|
|
22806
|
-
sectionBar.id = "setto-section-toolbar-root";
|
|
22807
|
-
sectionBar.style.pointerEvents = "none";
|
|
22808
|
-
document.body.appendChild(sectionBar);
|
|
22809
|
-
setToolbarRoot(toolbar);
|
|
22810
|
-
setHintRoot(hint);
|
|
22811
|
-
setSectionToolbarRoot(sectionBar);
|
|
22812
|
-
return () => {
|
|
22813
|
-
toolbar.remove();
|
|
22814
|
-
hint.remove();
|
|
22815
|
-
sectionBar.remove();
|
|
22816
|
-
};
|
|
22817
|
-
}, [active]);
|
|
22818
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
22819
|
-
children,
|
|
22820
|
-
active && toolbarRoot ? createPortal(/* @__PURE__ */ jsx(EditToolbar, {}), toolbarRoot) : null,
|
|
22821
|
-
active && hintRoot ? createPortal(/* @__PURE__ */ jsx(EditLinkHint, {}), hintRoot) : null,
|
|
22822
|
-
active && sectionToolbarRoot && themeStore ? createPortal(/* @__PURE__ */ jsx(SectionToolbar, {}), sectionToolbarRoot) : null
|
|
22823
|
-
] });
|
|
22824
|
-
}
|
|
22825
|
-
const SettoContext = createContext(null);
|
|
22826
|
-
function SettoProvider({ config, children }) {
|
|
22827
|
-
const supabase = useMemo(() => getSupabase(config.supabase), [config.supabase]);
|
|
22828
|
-
const api = useMemo(() => createApi({ apiUrl: config.apiUrl, supabase }), [config.apiUrl, supabase]);
|
|
22829
|
-
const [session, setSession] = useState(null);
|
|
22830
|
-
const [authLoading, setAuthLoading] = useState(true);
|
|
22831
|
-
const [onAdminRoute, setOnAdminRoute] = useState(
|
|
22832
|
-
() => typeof window === "undefined" ? false : isAdminRoute()
|
|
22833
|
-
);
|
|
22834
|
-
useEffect(() => {
|
|
22835
|
-
let cancelled = false;
|
|
22836
|
-
supabase.auth.getSession().then(({ data }) => {
|
|
22837
|
-
if (cancelled) return;
|
|
22838
|
-
setSession(data.session ?? null);
|
|
22839
|
-
setAuthLoading(false);
|
|
22840
|
-
});
|
|
22841
|
-
const { data: sub } = supabase.auth.onAuthStateChange((_event, session2) => {
|
|
22842
|
-
setSession(session2);
|
|
22843
|
-
});
|
|
22844
|
-
return () => {
|
|
22845
|
-
cancelled = true;
|
|
22846
|
-
sub.subscription.unsubscribe();
|
|
22847
|
-
};
|
|
22848
|
-
}, [supabase]);
|
|
22849
|
-
useEffect(() => {
|
|
22850
|
-
const compute = () => setOnAdminRoute(isAdminRoute());
|
|
22851
|
-
compute();
|
|
22852
|
-
window.addEventListener("popstate", compute);
|
|
22853
|
-
return () => window.removeEventListener("popstate", compute);
|
|
22854
|
-
}, []);
|
|
22855
|
-
const access = useSiteAccess(supabase, session, config.siteId);
|
|
22856
|
-
const { i18n } = useTranslation();
|
|
22857
|
-
const storeRef = useRef(null);
|
|
22858
|
-
const themeStoreRef = useRef(null);
|
|
22859
|
-
const assetStoreRef = useRef(null);
|
|
22860
|
-
const [storeReady, setStoreReady] = useState(false);
|
|
22861
|
-
useEffect(() => {
|
|
22862
|
-
if (!i18n) return;
|
|
22863
|
-
storeRef.current = new I18nStore(i18n);
|
|
22864
|
-
themeStoreRef.current = new ThemeStore(config.theme ?? {});
|
|
22865
|
-
assetStoreRef.current = new AssetStore();
|
|
22866
|
-
setStoreReady(true);
|
|
22867
|
-
}, [i18n, config.theme]);
|
|
22868
|
-
const editMode = !!session && !!access.currentSite && !onAdminRoute && !authLoading && !access.loading;
|
|
22869
|
-
const value = {
|
|
22870
|
-
config,
|
|
22871
|
-
supabase,
|
|
22872
|
-
api,
|
|
22873
|
-
session,
|
|
22874
|
-
authLoading,
|
|
22875
|
-
editMode,
|
|
22876
|
-
currentSite: access.currentSite,
|
|
22877
|
-
sites: access.sites,
|
|
22878
|
-
store: storeReady ? storeRef.current : null,
|
|
22879
|
-
themeStore: storeReady ? themeStoreRef.current : null,
|
|
22880
|
-
assetStore: storeReady ? assetStoreRef.current : null
|
|
22881
|
-
};
|
|
22882
|
-
const editShellActive = editMode && !!value.store;
|
|
22883
|
-
return /* @__PURE__ */ jsx(SettoContext.Provider, { value, children: /* @__PURE__ */ jsx(SectionEditProvider, { children: /* @__PURE__ */ jsx(EditModeShell, { active: editShellActive, themeStore: value.themeStore, children }) }) });
|
|
22884
|
-
}
|
|
22885
|
-
function useSetto() {
|
|
22886
|
-
const ctx = useContext(SettoContext);
|
|
22887
|
-
if (!ctx) {
|
|
22888
|
-
throw new Error("useSetto() must be used inside <SettoProvider>");
|
|
22889
|
-
}
|
|
22890
|
-
return ctx;
|
|
22891
|
-
}
|
|
22892
|
-
const GuestEditContext = createContext(null);
|
|
22893
|
-
function GuestEditProvider({ children }) {
|
|
22894
|
-
const { editMode, themeStore } = useSetto();
|
|
22895
|
-
const { selectSection } = useSectionEdit();
|
|
22896
|
-
const [active, setActive] = useState(false);
|
|
22897
|
-
useEffect(() => {
|
|
22898
|
-
if (editMode && active) setActive(false);
|
|
22899
|
-
}, [editMode, active]);
|
|
22900
|
-
useEffect(() => {
|
|
22901
|
-
document.documentElement.classList.toggle("setto-guest-editing", active);
|
|
22902
|
-
return () => document.documentElement.classList.remove("setto-guest-editing");
|
|
22903
|
-
}, [active]);
|
|
22904
|
-
const start = useCallback(() => setActive(true), []);
|
|
22905
|
-
const stop = useCallback(() => {
|
|
22906
|
-
setActive(false);
|
|
22907
|
-
selectSection(null);
|
|
22908
|
-
}, [selectSection]);
|
|
22909
|
-
return /* @__PURE__ */ jsxs(GuestEditContext.Provider, { value: { active, start, stop }, children: [
|
|
22910
|
-
children,
|
|
22911
|
-
active && themeStore ? createPortal(/* @__PURE__ */ jsx(SectionToolbar, {}), document.body) : null
|
|
22912
|
-
] });
|
|
22913
|
-
}
|
|
22914
|
-
function useGuestEdit() {
|
|
22915
|
-
const ctx = useContext(GuestEditContext);
|
|
22916
|
-
if (!ctx) {
|
|
22917
|
-
return { active: false, start: () => {
|
|
22918
|
-
}, stop: () => {
|
|
22919
|
-
} };
|
|
22920
|
-
}
|
|
22921
|
-
return ctx;
|
|
22922
|
-
}
|
|
22923
|
-
const MARGIN = 10;
|
|
22924
|
-
const OFFSET = 8;
|
|
22925
|
-
const MIN_TOUCH = 44;
|
|
22926
|
-
function placeChip(anchor, width, height) {
|
|
22927
|
-
const vw = window.innerWidth;
|
|
22928
|
-
const vh = window.innerHeight;
|
|
22929
|
-
let left = anchor.left + anchor.width / 2 - width / 2;
|
|
22930
|
-
let top = anchor.bottom + OFFSET;
|
|
22931
|
-
if (left + width > vw - MARGIN) left = vw - width - MARGIN;
|
|
22932
|
-
if (left < MARGIN) left = MARGIN;
|
|
22933
|
-
if (top + height > vh - MARGIN) {
|
|
22934
|
-
top = anchor.top - height - OFFSET;
|
|
22935
|
-
}
|
|
22936
|
-
if (top < MARGIN) top = MARGIN;
|
|
22937
|
-
return { left, top };
|
|
24838
|
+
return /* @__PURE__ */ jsxs(
|
|
24839
|
+
"button",
|
|
24840
|
+
{
|
|
24841
|
+
type: "button",
|
|
24842
|
+
onClick,
|
|
24843
|
+
disabled,
|
|
24844
|
+
style: { ...menuItemStyle, opacity: disabled ? 0.4 : 1, cursor: disabled ? "not-allowed" : "pointer" },
|
|
24845
|
+
role: "menuitem",
|
|
24846
|
+
onMouseEnter: (e) => {
|
|
24847
|
+
if (!disabled) e.currentTarget.style.background = "#f5f5f5";
|
|
24848
|
+
},
|
|
24849
|
+
onMouseLeave: (e) => {
|
|
24850
|
+
e.currentTarget.style.background = "transparent";
|
|
24851
|
+
},
|
|
24852
|
+
children: [
|
|
24853
|
+
/* @__PURE__ */ jsx("span", { style: menuIconWrapStyle, "aria-hidden": true, children: icon }),
|
|
24854
|
+
children
|
|
24855
|
+
]
|
|
24856
|
+
}
|
|
24857
|
+
);
|
|
22938
24858
|
}
|
|
22939
|
-
|
|
22940
|
-
|
|
22941
|
-
|
|
22942
|
-
|
|
22943
|
-
|
|
22944
|
-
|
|
22945
|
-
|
|
24859
|
+
const menuIconWrapStyle = {
|
|
24860
|
+
display: "flex",
|
|
24861
|
+
alignItems: "center",
|
|
24862
|
+
justifyContent: "center",
|
|
24863
|
+
flexShrink: 0,
|
|
24864
|
+
color: "#666"
|
|
24865
|
+
};
|
|
24866
|
+
function PublishedCheckIcon() {
|
|
24867
|
+
return /* @__PURE__ */ jsx(
|
|
24868
|
+
"svg",
|
|
24869
|
+
{
|
|
24870
|
+
width: "14",
|
|
24871
|
+
height: "14",
|
|
24872
|
+
viewBox: "0 0 24 24",
|
|
24873
|
+
fill: "none",
|
|
24874
|
+
stroke: "currentColor",
|
|
24875
|
+
strokeWidth: "2.5",
|
|
24876
|
+
strokeLinecap: "round",
|
|
24877
|
+
strokeLinejoin: "round",
|
|
24878
|
+
"aria-hidden": true,
|
|
24879
|
+
children: /* @__PURE__ */ jsx("polyline", { points: "20 6 9 17 4 12" })
|
|
22946
24880
|
}
|
|
22947
|
-
const update = () => {
|
|
22948
|
-
const el = anchorRef.current;
|
|
22949
|
-
const chip = chipRef.current;
|
|
22950
|
-
if (!el) return;
|
|
22951
|
-
const rect = el.getBoundingClientRect();
|
|
22952
|
-
const width = chip?.offsetWidth ?? 120;
|
|
22953
|
-
const height = chip?.offsetHeight ?? MIN_TOUCH;
|
|
22954
|
-
setPosition(placeChip(rect, width, height));
|
|
22955
|
-
};
|
|
22956
|
-
update();
|
|
22957
|
-
window.addEventListener("scroll", update, true);
|
|
22958
|
-
window.addEventListener("resize", update);
|
|
22959
|
-
return () => {
|
|
22960
|
-
window.removeEventListener("scroll", update, true);
|
|
22961
|
-
window.removeEventListener("resize", update);
|
|
22962
|
-
};
|
|
22963
|
-
}, [visible, anchorRef]);
|
|
22964
|
-
if (!visible || !anchorRef.current) return null;
|
|
22965
|
-
const host = document.getElementById("setto-hint-root") ?? document.body;
|
|
22966
|
-
const style = {
|
|
22967
|
-
position: "fixed",
|
|
22968
|
-
left: position?.left ?? 0,
|
|
22969
|
-
top: position?.top ?? 0,
|
|
22970
|
-
visibility: position ? "visible" : "hidden",
|
|
22971
|
-
minHeight: MIN_TOUCH,
|
|
22972
|
-
minWidth: MIN_TOUCH,
|
|
22973
|
-
padding: "10px 14px",
|
|
22974
|
-
background: "#1a1a1a",
|
|
22975
|
-
color: "#fff",
|
|
22976
|
-
fontSize: 13,
|
|
22977
|
-
fontWeight: 500,
|
|
22978
|
-
border: "none",
|
|
22979
|
-
borderRadius: 8,
|
|
22980
|
-
boxShadow: "0 4px 16px rgba(0,0,0,0.2)",
|
|
22981
|
-
fontFamily: "system-ui, sans-serif",
|
|
22982
|
-
whiteSpace: "nowrap",
|
|
22983
|
-
touchAction: "manipulation",
|
|
22984
|
-
zIndex: 2147483647
|
|
22985
|
-
};
|
|
22986
|
-
return createPortal(
|
|
22987
|
-
/* @__PURE__ */ jsx(
|
|
22988
|
-
"button",
|
|
22989
|
-
{
|
|
22990
|
-
ref: chipRef,
|
|
22991
|
-
type: "button",
|
|
22992
|
-
"data-setto-ui": true,
|
|
22993
|
-
style,
|
|
22994
|
-
onMouseDown: (e) => {
|
|
22995
|
-
e.preventDefault();
|
|
22996
|
-
e.stopPropagation();
|
|
22997
|
-
},
|
|
22998
|
-
onClick: (e) => {
|
|
22999
|
-
e.preventDefault();
|
|
23000
|
-
e.stopPropagation();
|
|
23001
|
-
const el = anchorRef.current;
|
|
23002
|
-
if (el) activateLinkContext(el);
|
|
23003
|
-
el?.blur();
|
|
23004
|
-
},
|
|
23005
|
-
children: "Naviger ↗"
|
|
23006
|
-
}
|
|
23007
|
-
),
|
|
23008
|
-
host
|
|
23009
24881
|
);
|
|
23010
24882
|
}
|
|
23011
|
-
function
|
|
23012
|
-
|
|
23013
|
-
|
|
23014
|
-
|
|
23015
|
-
|
|
23016
|
-
const range = rangeFromPoint(doc, x, y);
|
|
23017
|
-
if (!range) return;
|
|
23018
|
-
if (!el.contains(range.startContainer)) {
|
|
23019
|
-
range.selectNodeContents(el);
|
|
23020
|
-
range.collapse(false);
|
|
23021
|
-
}
|
|
23022
|
-
selection.removeAllRanges();
|
|
23023
|
-
selection.addRange(range);
|
|
24883
|
+
function HistoryIcon() {
|
|
24884
|
+
return /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
24885
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10" }),
|
|
24886
|
+
/* @__PURE__ */ jsx("polyline", { points: "12 6 12 12 16 14" })
|
|
24887
|
+
] });
|
|
23024
24888
|
}
|
|
23025
|
-
function
|
|
23026
|
-
|
|
23027
|
-
|
|
23028
|
-
|
|
23029
|
-
|
|
23030
|
-
const pos = legacy.caretPositionFromPoint?.(x, y);
|
|
23031
|
-
if (!pos) return null;
|
|
23032
|
-
const range = doc.createRange();
|
|
23033
|
-
range.setStart(pos.offsetNode, pos.offset);
|
|
23034
|
-
range.collapse(true);
|
|
23035
|
-
return range;
|
|
24889
|
+
function GearIcon() {
|
|
24890
|
+
return /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
24891
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "3" }),
|
|
24892
|
+
/* @__PURE__ */ jsx("path", { d: "M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09a1.65 1.65 0 0 0-1-1.51 1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09a1.65 1.65 0 0 0 1.51-1 1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z" })
|
|
24893
|
+
] });
|
|
23036
24894
|
}
|
|
23037
|
-
function
|
|
23038
|
-
|
|
23039
|
-
|
|
23040
|
-
|
|
23041
|
-
|
|
23042
|
-
|
|
23043
|
-
|
|
23044
|
-
|
|
23045
|
-
|
|
23046
|
-
|
|
23047
|
-
|
|
23048
|
-
|
|
23049
|
-
|
|
23050
|
-
|
|
23051
|
-
|
|
24895
|
+
function LogOutIcon() {
|
|
24896
|
+
return /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
24897
|
+
/* @__PURE__ */ jsx("path", { d: "M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" }),
|
|
24898
|
+
/* @__PURE__ */ jsx("polyline", { points: "16 17 21 12 16 7" }),
|
|
24899
|
+
/* @__PURE__ */ jsx("line", { x1: "21", y1: "12", x2: "9", y2: "12" })
|
|
24900
|
+
] });
|
|
24901
|
+
}
|
|
24902
|
+
function UndoIcon() {
|
|
24903
|
+
return /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
24904
|
+
/* @__PURE__ */ jsx("path", { d: "M3 7v6h6" }),
|
|
24905
|
+
/* @__PURE__ */ jsx("path", { d: "M21 17a9 9 0 0 0-15-6.7L3 13" })
|
|
24906
|
+
] });
|
|
24907
|
+
}
|
|
24908
|
+
function EditModeShell({ children, themeStore, active }) {
|
|
24909
|
+
useSettoDocumentLayout(active);
|
|
24910
|
+
const [toolbarRoot, setToolbarRoot] = useState(null);
|
|
24911
|
+
const [hintRoot, setHintRoot] = useState(null);
|
|
24912
|
+
const [sectionToolbarRoot, setSectionToolbarRoot] = useState(null);
|
|
23052
24913
|
useEffect(() => {
|
|
23053
|
-
|
|
23054
|
-
|
|
23055
|
-
|
|
23056
|
-
|
|
23057
|
-
|
|
23058
|
-
const handleMouseDown = (e) => {
|
|
23059
|
-
e.stopPropagation();
|
|
23060
|
-
const el = e.currentTarget;
|
|
23061
|
-
if (!isLinkContext(el)) return;
|
|
23062
|
-
if (e.ctrlKey || e.metaKey) return;
|
|
23063
|
-
e.preventDefault();
|
|
23064
|
-
};
|
|
23065
|
-
const handleMouseUp = (e) => {
|
|
23066
|
-
if (e.button !== 0) return;
|
|
23067
|
-
e.stopPropagation();
|
|
23068
|
-
const el = e.currentTarget;
|
|
23069
|
-
if (isLinkContext(el) && (e.ctrlKey || e.metaKey)) return;
|
|
23070
|
-
if (isLinkContext(el)) {
|
|
23071
|
-
placeCaretAtPoint(e.clientX, e.clientY, el);
|
|
23072
|
-
}
|
|
23073
|
-
};
|
|
23074
|
-
const handleClick = (e) => {
|
|
23075
|
-
e.stopPropagation();
|
|
23076
|
-
const el = e.currentTarget;
|
|
23077
|
-
if (isLinkContext(el) && (e.ctrlKey || e.metaKey)) return;
|
|
23078
|
-
if (isLinkContext(el)) {
|
|
23079
|
-
e.preventDefault();
|
|
23080
|
-
}
|
|
23081
|
-
};
|
|
23082
|
-
const handleKeyDown = (e) => {
|
|
23083
|
-
if (!isLinkContext(e.currentTarget)) return;
|
|
23084
|
-
e.stopPropagation();
|
|
23085
|
-
};
|
|
23086
|
-
const handleKeyUp = (e) => {
|
|
23087
|
-
if (!isLinkContext(e.currentTarget)) return;
|
|
23088
|
-
if (e.key === " " || e.key === "Enter") {
|
|
23089
|
-
e.preventDefault();
|
|
23090
|
-
e.stopPropagation();
|
|
24914
|
+
if (!active) {
|
|
24915
|
+
setToolbarRoot(null);
|
|
24916
|
+
setHintRoot(null);
|
|
24917
|
+
setSectionToolbarRoot(null);
|
|
24918
|
+
return;
|
|
23091
24919
|
}
|
|
23092
|
-
|
|
23093
|
-
|
|
23094
|
-
|
|
23095
|
-
|
|
23096
|
-
|
|
23097
|
-
|
|
23098
|
-
|
|
23099
|
-
|
|
23100
|
-
|
|
23101
|
-
|
|
23102
|
-
|
|
23103
|
-
|
|
23104
|
-
|
|
23105
|
-
|
|
23106
|
-
|
|
23107
|
-
|
|
23108
|
-
|
|
23109
|
-
|
|
23110
|
-
|
|
23111
|
-
|
|
23112
|
-
store?.set(k, i18n.language, el.textContent ?? "");
|
|
23113
|
-
};
|
|
23114
|
-
const lang = toBcp47(i18n.language);
|
|
23115
|
-
const showLinkChip = coarsePointer && focused && linkContext;
|
|
24920
|
+
const toolbar = document.createElement("div");
|
|
24921
|
+
toolbar.id = "setto-toolbar-root";
|
|
24922
|
+
document.body.appendChild(toolbar);
|
|
24923
|
+
const hint = document.createElement("div");
|
|
24924
|
+
hint.id = "setto-hint-root";
|
|
24925
|
+
hint.style.pointerEvents = "none";
|
|
24926
|
+
document.body.appendChild(hint);
|
|
24927
|
+
const sectionBar = document.createElement("div");
|
|
24928
|
+
sectionBar.id = "setto-section-toolbar-root";
|
|
24929
|
+
sectionBar.style.pointerEvents = "none";
|
|
24930
|
+
document.body.appendChild(sectionBar);
|
|
24931
|
+
setToolbarRoot(toolbar);
|
|
24932
|
+
setHintRoot(hint);
|
|
24933
|
+
setSectionToolbarRoot(sectionBar);
|
|
24934
|
+
return () => {
|
|
24935
|
+
toolbar.remove();
|
|
24936
|
+
hint.remove();
|
|
24937
|
+
sectionBar.remove();
|
|
24938
|
+
};
|
|
24939
|
+
}, [active]);
|
|
23116
24940
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
23117
|
-
|
|
23118
|
-
|
|
23119
|
-
|
|
23120
|
-
|
|
23121
|
-
"data-setto-key": k,
|
|
23122
|
-
contentEditable: true,
|
|
23123
|
-
suppressContentEditableWarning: true,
|
|
23124
|
-
lang,
|
|
23125
|
-
spellCheck: false,
|
|
23126
|
-
role: "textbox",
|
|
23127
|
-
tabIndex: 0,
|
|
23128
|
-
onFocus: (e) => {
|
|
23129
|
-
setFocused(true);
|
|
23130
|
-
setLinkContext(isLinkContext(e.currentTarget));
|
|
23131
|
-
},
|
|
23132
|
-
onBlur: handleBlur,
|
|
23133
|
-
onInput: handleInput,
|
|
23134
|
-
onPaste: handlePaste,
|
|
23135
|
-
onMouseDown: handleMouseDown,
|
|
23136
|
-
onMouseUp: handleMouseUp,
|
|
23137
|
-
onClick: handleClick,
|
|
23138
|
-
onKeyDown: handleKeyDown,
|
|
23139
|
-
onKeyUp: handleKeyUp,
|
|
23140
|
-
style: {
|
|
23141
|
-
cursor: "text",
|
|
23142
|
-
color: "inherit",
|
|
23143
|
-
outline: focused ? editMode ? "2px solid #640AFF" : "2px solid #C4502A" : void 0,
|
|
23144
|
-
outlineOffset: 2,
|
|
23145
|
-
borderRadius: 2,
|
|
23146
|
-
transition: "outline-color 120ms"
|
|
23147
|
-
}
|
|
23148
|
-
}
|
|
23149
|
-
),
|
|
23150
|
-
showLinkChip ? /* @__PURE__ */ jsx(LinkActionChip, { anchorRef: ref, visible: focused }) : null
|
|
24941
|
+
children,
|
|
24942
|
+
active && toolbarRoot ? createPortal(/* @__PURE__ */ jsx(EditToolbar, {}), toolbarRoot) : null,
|
|
24943
|
+
active && hintRoot ? createPortal(/* @__PURE__ */ jsx(EditLinkHint, {}), hintRoot) : null,
|
|
24944
|
+
active && sectionToolbarRoot && themeStore ? createPortal(/* @__PURE__ */ jsx(SectionToolbar, {}), sectionToolbarRoot) : null
|
|
23151
24945
|
] });
|
|
23152
24946
|
}
|
|
23153
|
-
|
|
23154
|
-
|
|
23155
|
-
|
|
23156
|
-
|
|
23157
|
-
|
|
24947
|
+
const SettoContext = createContext(null);
|
|
24948
|
+
function SettoProvider({ config, children }) {
|
|
24949
|
+
const supabase = useMemo(() => getSupabase(config.supabase), [config.supabase]);
|
|
24950
|
+
const api = useMemo(() => createApi({ apiUrl: config.apiUrl, supabase }), [config.apiUrl, supabase]);
|
|
24951
|
+
const [session, setSession] = useState(null);
|
|
24952
|
+
const [authLoading, setAuthLoading] = useState(true);
|
|
24953
|
+
const [onAdminRoute, setOnAdminRoute] = useState(
|
|
24954
|
+
() => typeof window === "undefined" ? false : isAdminRoute()
|
|
24955
|
+
);
|
|
24956
|
+
useEffect(() => {
|
|
24957
|
+
let cancelled = false;
|
|
24958
|
+
supabase.auth.getSession().then(({ data }) => {
|
|
24959
|
+
if (cancelled) return;
|
|
24960
|
+
setSession(data.session ?? null);
|
|
24961
|
+
setAuthLoading(false);
|
|
24962
|
+
});
|
|
24963
|
+
const { data: sub } = supabase.auth.onAuthStateChange((_event, session2) => {
|
|
24964
|
+
setSession(session2);
|
|
24965
|
+
});
|
|
24966
|
+
return () => {
|
|
24967
|
+
cancelled = true;
|
|
24968
|
+
sub.subscription.unsubscribe();
|
|
24969
|
+
};
|
|
24970
|
+
}, [supabase]);
|
|
24971
|
+
useEffect(() => {
|
|
24972
|
+
const compute = () => setOnAdminRoute(isAdminRoute());
|
|
24973
|
+
compute();
|
|
24974
|
+
window.addEventListener("popstate", compute);
|
|
24975
|
+
return () => window.removeEventListener("popstate", compute);
|
|
24976
|
+
}, []);
|
|
24977
|
+
const access = useSiteAccess(supabase, session, config.siteId);
|
|
24978
|
+
const { i18n } = useTranslation();
|
|
24979
|
+
const storeRef = useRef(null);
|
|
24980
|
+
const themeStoreRef = useRef(null);
|
|
24981
|
+
const assetStoreRef = useRef(null);
|
|
24982
|
+
const [storeReady, setStoreReady] = useState(false);
|
|
24983
|
+
useEffect(() => {
|
|
24984
|
+
if (!i18n) return;
|
|
24985
|
+
storeRef.current = new I18nStore(i18n);
|
|
24986
|
+
themeStoreRef.current = new ThemeStore(config.theme ?? {});
|
|
24987
|
+
assetStoreRef.current = new AssetStore();
|
|
24988
|
+
setStoreReady(true);
|
|
24989
|
+
}, [i18n, config.theme]);
|
|
24990
|
+
const editMode = !!session && !!access.currentSite && !onAdminRoute && !authLoading && !access.loading;
|
|
24991
|
+
const value = {
|
|
24992
|
+
config,
|
|
24993
|
+
supabase,
|
|
24994
|
+
api,
|
|
24995
|
+
session,
|
|
24996
|
+
authLoading,
|
|
24997
|
+
editMode,
|
|
24998
|
+
currentSite: access.currentSite,
|
|
24999
|
+
sites: access.sites,
|
|
25000
|
+
store: storeReady ? storeRef.current : null,
|
|
25001
|
+
themeStore: storeReady ? themeStoreRef.current : null,
|
|
25002
|
+
assetStore: storeReady ? assetStoreRef.current : null
|
|
25003
|
+
};
|
|
25004
|
+
const editShellActive = editMode && !!value.store;
|
|
25005
|
+
return /* @__PURE__ */ jsx(SettoContext.Provider, { value, children: /* @__PURE__ */ jsxs(SectionEditProvider, { children: [
|
|
25006
|
+
/* @__PURE__ */ jsx(EditModeShell, { active: editShellActive, themeStore: value.themeStore, children }),
|
|
25007
|
+
!onAdminRoute && /* @__PURE__ */ jsx(SettoPreviewBar, {})
|
|
25008
|
+
] }) });
|
|
25009
|
+
}
|
|
25010
|
+
function useSetto() {
|
|
25011
|
+
const ctx = useContext(SettoContext);
|
|
25012
|
+
if (!ctx) {
|
|
25013
|
+
throw new Error("useSetto() must be used inside <SettoProvider>");
|
|
25014
|
+
}
|
|
25015
|
+
return ctx;
|
|
23158
25016
|
}
|
|
23159
25017
|
function isTextEditClick(target) {
|
|
23160
25018
|
if (target.closest("[data-setto-key]")) return true;
|
|
@@ -23284,99 +25142,6 @@ const SettoBlock = forwardRef(
|
|
|
23284
25142
|
);
|
|
23285
25143
|
}
|
|
23286
25144
|
);
|
|
23287
|
-
function FloatingPopover({
|
|
23288
|
-
open,
|
|
23289
|
-
anchorRef,
|
|
23290
|
-
onClose,
|
|
23291
|
-
ariaLabel,
|
|
23292
|
-
children,
|
|
23293
|
-
style,
|
|
23294
|
-
minWidth = 280
|
|
23295
|
-
}) {
|
|
23296
|
-
const popoverRef = useRef(null);
|
|
23297
|
-
const [pos, setPos] = useState(null);
|
|
23298
|
-
useLayoutEffect(() => {
|
|
23299
|
-
if (!open || !anchorRef.current) {
|
|
23300
|
-
setPos(null);
|
|
23301
|
-
return;
|
|
23302
|
-
}
|
|
23303
|
-
const update = () => {
|
|
23304
|
-
const anchor = anchorRef.current;
|
|
23305
|
-
const pop = popoverRef.current;
|
|
23306
|
-
if (!anchor) return;
|
|
23307
|
-
const rect = anchor.getBoundingClientRect();
|
|
23308
|
-
const popoverH = pop?.offsetHeight ?? 200;
|
|
23309
|
-
const popoverW = pop?.offsetWidth ?? minWidth;
|
|
23310
|
-
let top = rect.bottom + 6;
|
|
23311
|
-
let left = rect.left;
|
|
23312
|
-
if (left + popoverW > window.innerWidth - 8) {
|
|
23313
|
-
left = window.innerWidth - popoverW - 8;
|
|
23314
|
-
}
|
|
23315
|
-
if (left < 8) left = 8;
|
|
23316
|
-
if (top + popoverH > window.innerHeight - 8) {
|
|
23317
|
-
top = rect.top - popoverH - 6;
|
|
23318
|
-
}
|
|
23319
|
-
if (top < 8) top = rect.bottom + 6;
|
|
23320
|
-
setPos({ top, left });
|
|
23321
|
-
};
|
|
23322
|
-
update();
|
|
23323
|
-
requestAnimationFrame(update);
|
|
23324
|
-
window.addEventListener("scroll", update, true);
|
|
23325
|
-
window.addEventListener("resize", update);
|
|
23326
|
-
return () => {
|
|
23327
|
-
window.removeEventListener("scroll", update, true);
|
|
23328
|
-
window.removeEventListener("resize", update);
|
|
23329
|
-
};
|
|
23330
|
-
}, [open, anchorRef, minWidth]);
|
|
23331
|
-
useEffect(() => {
|
|
23332
|
-
if (!open) return;
|
|
23333
|
-
const onKey = (e) => {
|
|
23334
|
-
if (e.key === "Escape") onClose();
|
|
23335
|
-
};
|
|
23336
|
-
window.addEventListener("keydown", onKey);
|
|
23337
|
-
return () => window.removeEventListener("keydown", onKey);
|
|
23338
|
-
}, [open, onClose]);
|
|
23339
|
-
useEffect(() => {
|
|
23340
|
-
if (!open) return;
|
|
23341
|
-
const close = (e) => {
|
|
23342
|
-
const target = e.target;
|
|
23343
|
-
if (anchorRef.current?.contains(target)) return;
|
|
23344
|
-
if (popoverRef.current?.contains(target)) return;
|
|
23345
|
-
onClose();
|
|
23346
|
-
};
|
|
23347
|
-
document.addEventListener("mousedown", close);
|
|
23348
|
-
return () => document.removeEventListener("mousedown", close);
|
|
23349
|
-
}, [open, onClose, anchorRef]);
|
|
23350
|
-
if (!open) return null;
|
|
23351
|
-
return createPortal(
|
|
23352
|
-
/* @__PURE__ */ jsx(
|
|
23353
|
-
"div",
|
|
23354
|
-
{
|
|
23355
|
-
ref: popoverRef,
|
|
23356
|
-
role: "dialog",
|
|
23357
|
-
"aria-label": ariaLabel,
|
|
23358
|
-
"data-setto-ui": true,
|
|
23359
|
-
style: {
|
|
23360
|
-
position: "fixed",
|
|
23361
|
-
top: pos?.top ?? -9999,
|
|
23362
|
-
left: pos?.left ?? 0,
|
|
23363
|
-
visibility: pos ? "visible" : "hidden",
|
|
23364
|
-
zIndex: 2147483647,
|
|
23365
|
-
background: "#fff",
|
|
23366
|
-
border: "1px solid #e0e0e0",
|
|
23367
|
-
borderRadius: 8,
|
|
23368
|
-
boxShadow: "0 8px 24px rgba(0,0,0,0.15)",
|
|
23369
|
-
fontFamily: 'system-ui, -apple-system, "Segoe UI", sans-serif',
|
|
23370
|
-
...style
|
|
23371
|
-
},
|
|
23372
|
-
onMouseDown: (e) => e.stopPropagation(),
|
|
23373
|
-
onClick: (e) => e.stopPropagation(),
|
|
23374
|
-
children
|
|
23375
|
-
}
|
|
23376
|
-
),
|
|
23377
|
-
document.body
|
|
23378
|
-
);
|
|
23379
|
-
}
|
|
23380
25145
|
function SettoIcon({ k, icons, size = 24, className, style }) {
|
|
23381
25146
|
const { t, i18n } = useTranslation();
|
|
23382
25147
|
const { editMode, store } = useSetto();
|
|
@@ -23812,95 +25577,17 @@ const removeBtnStyle = {
|
|
|
23812
25577
|
justifyContent: "center",
|
|
23813
25578
|
width: 24,
|
|
23814
25579
|
height: 24,
|
|
23815
|
-
padding: 0,
|
|
23816
|
-
background: "#ffffff",
|
|
23817
|
-
border: "1px solid #e0e0e0",
|
|
23818
|
-
borderRadius: "50%",
|
|
23819
|
-
fontSize: 16,
|
|
23820
|
-
lineHeight: 1,
|
|
23821
|
-
color: "#444",
|
|
23822
|
-
cursor: "pointer",
|
|
23823
|
-
boxShadow: "0 1px 4px rgba(0,0,0,0.18)",
|
|
23824
|
-
fontFamily: 'system-ui, -apple-system, "Segoe UI", sans-serif'
|
|
23825
|
-
};
|
|
23826
|
-
function SettoForm({
|
|
23827
|
-
formId,
|
|
23828
|
-
fields,
|
|
23829
|
-
endpoint,
|
|
23830
|
-
endpointKey,
|
|
23831
|
-
className
|
|
23832
|
-
}) {
|
|
23833
|
-
const { t } = useTranslation();
|
|
23834
|
-
const [status, setStatus] = useState("idle");
|
|
23835
|
-
const [values, setValues] = useState(
|
|
23836
|
-
() => Object.fromEntries(fields.map((f) => [f.name, ""]))
|
|
23837
|
-
);
|
|
23838
|
-
const setField = useCallback(
|
|
23839
|
-
(name, value) => setValues((prev) => ({ ...prev, [name]: value })),
|
|
23840
|
-
[]
|
|
23841
|
-
);
|
|
23842
|
-
const resolveEndpoint = () => {
|
|
23843
|
-
if (endpoint) return endpoint;
|
|
23844
|
-
if (endpointKey) {
|
|
23845
|
-
const resolved = t(endpointKey, { defaultValue: "" });
|
|
23846
|
-
return resolved && resolved !== endpointKey ? resolved : null;
|
|
23847
|
-
}
|
|
23848
|
-
return null;
|
|
23849
|
-
};
|
|
23850
|
-
const onSubmit = async (e) => {
|
|
23851
|
-
e.preventDefault();
|
|
23852
|
-
const url = resolveEndpoint();
|
|
23853
|
-
if (!url) {
|
|
23854
|
-
setStatus("error");
|
|
23855
|
-
return;
|
|
23856
|
-
}
|
|
23857
|
-
setStatus("submitting");
|
|
23858
|
-
try {
|
|
23859
|
-
const res = await fetch(url, {
|
|
23860
|
-
method: "POST",
|
|
23861
|
-
headers: { "Content-Type": "application/json" },
|
|
23862
|
-
body: JSON.stringify(values)
|
|
23863
|
-
});
|
|
23864
|
-
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
23865
|
-
setStatus("success");
|
|
23866
|
-
setValues(
|
|
23867
|
-
(prev) => Object.fromEntries(Object.keys(prev).map((k) => [k, ""]))
|
|
23868
|
-
);
|
|
23869
|
-
} catch {
|
|
23870
|
-
setStatus("error");
|
|
23871
|
-
}
|
|
23872
|
-
};
|
|
23873
|
-
if (status === "success") {
|
|
23874
|
-
return /* @__PURE__ */ jsx("div", { className, "data-setto-form-status": "success", children: /* @__PURE__ */ jsx("p", { children: /* @__PURE__ */ jsx(T, { k: `form.${formId}.success` }) }) });
|
|
23875
|
-
}
|
|
23876
|
-
return /* @__PURE__ */ jsxs("form", { className, onSubmit, "data-setto-form": formId, noValidate: true, children: [
|
|
23877
|
-
fields.map((field) => /* @__PURE__ */ jsxs("label", { "data-setto-form-field": field.name, children: [
|
|
23878
|
-
/* @__PURE__ */ jsx("span", { children: /* @__PURE__ */ jsx(T, { k: field.labelKey }) }),
|
|
23879
|
-
field.type === "textarea" ? /* @__PURE__ */ jsx(
|
|
23880
|
-
"textarea",
|
|
23881
|
-
{
|
|
23882
|
-
name: field.name,
|
|
23883
|
-
required: field.required,
|
|
23884
|
-
value: values[field.name] ?? "",
|
|
23885
|
-
placeholder: field.placeholderKey ? t(field.placeholderKey, { defaultValue: "" }) : void 0,
|
|
23886
|
-
onChange: (e) => setField(field.name, e.target.value)
|
|
23887
|
-
}
|
|
23888
|
-
) : /* @__PURE__ */ jsx(
|
|
23889
|
-
"input",
|
|
23890
|
-
{
|
|
23891
|
-
type: field.type,
|
|
23892
|
-
name: field.name,
|
|
23893
|
-
required: field.required,
|
|
23894
|
-
value: values[field.name] ?? "",
|
|
23895
|
-
placeholder: field.placeholderKey ? t(field.placeholderKey, { defaultValue: "" }) : void 0,
|
|
23896
|
-
onChange: (e) => setField(field.name, e.target.value)
|
|
23897
|
-
}
|
|
23898
|
-
)
|
|
23899
|
-
] }, field.name)),
|
|
23900
|
-
/* @__PURE__ */ jsx("button", { type: "submit", disabled: status === "submitting", children: /* @__PURE__ */ jsx(T, { k: `form.${formId}.submit` }) }),
|
|
23901
|
-
status === "error" ? /* @__PURE__ */ jsx("p", { "data-setto-form-status": "error", role: "alert", children: /* @__PURE__ */ jsx(T, { k: `form.${formId}.error` }) }) : null
|
|
23902
|
-
] });
|
|
23903
|
-
}
|
|
25580
|
+
padding: 0,
|
|
25581
|
+
background: "#ffffff",
|
|
25582
|
+
border: "1px solid #e0e0e0",
|
|
25583
|
+
borderRadius: "50%",
|
|
25584
|
+
fontSize: 16,
|
|
25585
|
+
lineHeight: 1,
|
|
25586
|
+
color: "#444",
|
|
25587
|
+
cursor: "pointer",
|
|
25588
|
+
boxShadow: "0 1px 4px rgba(0,0,0,0.18)",
|
|
25589
|
+
fontFamily: 'system-ui, -apple-system, "Segoe UI", sans-serif'
|
|
25590
|
+
};
|
|
23904
25591
|
function SettoVideo({ srcKey, posterKey, title, className }) {
|
|
23905
25592
|
const { t, i18n } = useTranslation();
|
|
23906
25593
|
const { store } = useSetto();
|
|
@@ -24166,7 +25853,7 @@ function AuthGate({ children }) {
|
|
|
24166
25853
|
};
|
|
24167
25854
|
if (mode === "set_password") {
|
|
24168
25855
|
const callback = authCallbackType();
|
|
24169
|
-
return /* @__PURE__ */ jsx("div", { style: shellStyle
|
|
25856
|
+
return /* @__PURE__ */ jsx("div", { style: shellStyle, children: /* @__PURE__ */ jsxs("form", { onSubmit: submitSetPassword, style: formStyle, children: [
|
|
24170
25857
|
/* @__PURE__ */ jsx("h1", { style: { margin: 0, fontSize: 22 }, children: "Setto" }),
|
|
24171
25858
|
/* @__PURE__ */ jsx("p", { style: { margin: 0, color: "#888", fontSize: 13 }, children: callback === "invite" ? "Velg et passord for å fullføre invitasjonen." : "Velg et nytt passord." }),
|
|
24172
25859
|
/* @__PURE__ */ jsx(
|
|
@@ -24201,7 +25888,7 @@ function AuthGate({ children }) {
|
|
|
24201
25888
|
] }) });
|
|
24202
25889
|
}
|
|
24203
25890
|
if (mode === "forgot") {
|
|
24204
|
-
return /* @__PURE__ */ jsx("div", { style: shellStyle
|
|
25891
|
+
return /* @__PURE__ */ jsx("div", { style: shellStyle, children: /* @__PURE__ */ jsxs("form", { onSubmit: submitForgot, style: formStyle, children: [
|
|
24205
25892
|
/* @__PURE__ */ jsx("h1", { style: { margin: 0, fontSize: 22 }, children: "Setto" }),
|
|
24206
25893
|
/* @__PURE__ */ jsx("p", { style: { margin: 0, color: "#888", fontSize: 13 }, children: "Skriv inn e-posten din, så sender vi en lenke for å nullstille passordet." }),
|
|
24207
25894
|
/* @__PURE__ */ jsx(
|
|
@@ -24234,7 +25921,7 @@ function AuthGate({ children }) {
|
|
|
24234
25921
|
)
|
|
24235
25922
|
] }) });
|
|
24236
25923
|
}
|
|
24237
|
-
return /* @__PURE__ */ jsx("div", { style: shellStyle
|
|
25924
|
+
return /* @__PURE__ */ jsx("div", { style: shellStyle, children: /* @__PURE__ */ jsxs("form", { onSubmit: submitSignIn, style: formStyle, children: [
|
|
24238
25925
|
/* @__PURE__ */ jsx("h1", { style: { margin: 0, fontSize: 22 }, children: "Setto" }),
|
|
24239
25926
|
/* @__PURE__ */ jsx("p", { style: { margin: 0, color: "#888", fontSize: 13 }, children: "Logg inn for å redigere innholdet. Har du fått invitasjon, bruk lenken i e-posten første gang." }),
|
|
24240
25927
|
/* @__PURE__ */ jsx(
|
|
@@ -24279,7 +25966,7 @@ function AuthGate({ children }) {
|
|
|
24279
25966
|
)
|
|
24280
25967
|
] }) });
|
|
24281
25968
|
}
|
|
24282
|
-
const shellStyle
|
|
25969
|
+
const shellStyle = {
|
|
24283
25970
|
minHeight: "100dvh",
|
|
24284
25971
|
display: "flex",
|
|
24285
25972
|
alignItems: "center",
|
|
@@ -24324,7 +26011,7 @@ const linkStyle = {
|
|
|
24324
26011
|
cursor: "pointer"
|
|
24325
26012
|
};
|
|
24326
26013
|
const loadingStyle = {
|
|
24327
|
-
...shellStyle
|
|
26014
|
+
...shellStyle,
|
|
24328
26015
|
fontSize: 14,
|
|
24329
26016
|
color: "#888"
|
|
24330
26017
|
};
|
|
@@ -24338,72 +26025,7 @@ function deploymentAdminUrl(deploymentId) {
|
|
|
24338
26025
|
function focusedDeploymentId() {
|
|
24339
26026
|
return new URLSearchParams(window.location.search).get("deployment");
|
|
24340
26027
|
}
|
|
24341
|
-
function
|
|
24342
|
-
return /* @__PURE__ */ jsx(AuthGate, { children: /* @__PURE__ */ jsx(SiteList, {}) });
|
|
24343
|
-
}
|
|
24344
|
-
function SiteList() {
|
|
24345
|
-
const { supabase, session, sites, currentSite, config } = useSetto();
|
|
24346
|
-
const [error, setError] = useState(null);
|
|
24347
|
-
const redirectAfterSignIn = useRef(false);
|
|
24348
|
-
useEffect(() => {
|
|
24349
|
-
const { data: sub } = supabase.auth.onAuthStateChange((event) => {
|
|
24350
|
-
if (event === "SIGNED_IN" && !isAdminDeploymentView()) {
|
|
24351
|
-
redirectAfterSignIn.current = true;
|
|
24352
|
-
}
|
|
24353
|
-
});
|
|
24354
|
-
return () => sub.subscription.unsubscribe();
|
|
24355
|
-
}, [supabase]);
|
|
24356
|
-
useEffect(() => {
|
|
24357
|
-
if (!redirectAfterSignIn.current || !session || sites === null || isAdminDeploymentView()) {
|
|
24358
|
-
return;
|
|
24359
|
-
}
|
|
24360
|
-
if (currentSite) {
|
|
24361
|
-
redirectAfterSignIn.current = false;
|
|
24362
|
-
window.location.replace(siteHomeUrl());
|
|
24363
|
-
return;
|
|
24364
|
-
}
|
|
24365
|
-
redirectAfterSignIn.current = false;
|
|
24366
|
-
}, [session, sites, currentSite]);
|
|
24367
|
-
if (redirectAfterSignIn.current && session && (sites === null || currentSite)) {
|
|
24368
|
-
return /* @__PURE__ */ jsx("div", { style: loadingRedirectStyle, children: "Åpner redigering …" });
|
|
24369
|
-
}
|
|
24370
|
-
return /* @__PURE__ */ jsxs("div", { style: shellStyle, children: [
|
|
24371
|
-
/* @__PURE__ */ jsxs("header", { style: headerStyle, children: [
|
|
24372
|
-
/* @__PURE__ */ jsx("h1", { style: { margin: 0, fontSize: 22 }, children: "Setto" }),
|
|
24373
|
-
/* @__PURE__ */ jsx(SignOutButton, {})
|
|
24374
|
-
] }),
|
|
24375
|
-
/* @__PURE__ */ jsxs("main", { style: mainStyle, children: [
|
|
24376
|
-
error ? /* @__PURE__ */ jsx("p", { style: errStyle, children: error }) : null,
|
|
24377
|
-
sites === null ? /* @__PURE__ */ jsx("p", { style: mutedStyle, children: "Laster sider…" }) : null,
|
|
24378
|
-
currentSite ? /* @__PURE__ */ jsxs("section", { style: cardStyle, children: [
|
|
24379
|
-
/* @__PURE__ */ jsx("h2", { style: { margin: 0, fontSize: 18 }, children: currentSite.display_name }),
|
|
24380
|
-
/* @__PURE__ */ jsxs("p", { style: mutedStyle, children: [
|
|
24381
|
-
currentSite.repo_owner,
|
|
24382
|
-
"/",
|
|
24383
|
-
currentSite.repo_name,
|
|
24384
|
-
" · branch ",
|
|
24385
|
-
currentSite.branch
|
|
24386
|
-
] }),
|
|
24387
|
-
/* @__PURE__ */ jsx("div", { style: { display: "flex", gap: 8 }, children: /* @__PURE__ */ jsx("a", { href: siteHomeUrl(), style: primaryBtnLinkStyle, children: "Begynn å redigere" }) }),
|
|
24388
|
-
/* @__PURE__ */ jsx(DeploymentList, { siteId: currentSite.id, onError: setError })
|
|
24389
|
-
] }) : sites && sites.length === 0 ? /* @__PURE__ */ jsx("p", { style: mutedStyle, children: "Du er ikke medlem av noen sider ennå. Be administratoren legge deg til." }) : null,
|
|
24390
|
-
sites && !currentSite && sites.length > 0 ? /* @__PURE__ */ jsxs("section", { style: cardStyle, children: [
|
|
24391
|
-
/* @__PURE__ */ jsx("h2", { style: { margin: 0, fontSize: 16 }, children: "Andre sider du har tilgang til" }),
|
|
24392
|
-
/* @__PURE__ */ jsx("ul", { style: { listStyle: "none", padding: 0, margin: 0 }, children: sites.map((s) => /* @__PURE__ */ jsxs("li", { style: { padding: "6px 0" }, children: [
|
|
24393
|
-
/* @__PURE__ */ jsx("code", { style: { color: "#bda6ff" }, children: s.id }),
|
|
24394
|
-
" — ",
|
|
24395
|
-
s.display_name
|
|
24396
|
-
] }, s.id)) }),
|
|
24397
|
-
/* @__PURE__ */ jsxs("p", { style: mutedStyle, children: [
|
|
24398
|
-
"Du er logget inn på denne siden (",
|
|
24399
|
-
config.siteId,
|
|
24400
|
-
"), men er ikke medlem her. Spør administrator om tilgang."
|
|
24401
|
-
] })
|
|
24402
|
-
] }) : null
|
|
24403
|
-
] })
|
|
24404
|
-
] });
|
|
24405
|
-
}
|
|
24406
|
-
function DeploymentList({
|
|
26028
|
+
function DeploymentsView({
|
|
24407
26029
|
siteId,
|
|
24408
26030
|
onError
|
|
24409
26031
|
}) {
|
|
@@ -24464,7 +26086,7 @@ function DeploymentList({
|
|
|
24464
26086
|
const showFocusPanel = focusId !== null && (focusRow === null || isDeploymentInProgress(focusRow.status));
|
|
24465
26087
|
if (!rows) return /* @__PURE__ */ jsx("p", { style: mutedStyle, children: "Laster deploys…" });
|
|
24466
26088
|
return /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 12 }, children: [
|
|
24467
|
-
showFocusPanel ? /* @__PURE__ */ jsxs("section", { style:
|
|
26089
|
+
showFocusPanel ? /* @__PURE__ */ jsxs("section", { style: panelStyle, children: [
|
|
24468
26090
|
/* @__PURE__ */ jsx("h3", { style: { margin: 0, fontSize: 15 }, children: "Publisering pågår" }),
|
|
24469
26091
|
focusRow ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
24470
26092
|
/* @__PURE__ */ jsx(PublishProgressBar, { status: focusRow.status }),
|
|
@@ -24488,14 +26110,14 @@ function DeploymentList({
|
|
|
24488
26110
|
type: "button",
|
|
24489
26111
|
onClick: () => void cancelDeployment(),
|
|
24490
26112
|
disabled: canceling,
|
|
24491
|
-
style:
|
|
26113
|
+
style: dangerBtnStyle,
|
|
24492
26114
|
children: canceling ? "Avbryter …" : "Avbryt publisering"
|
|
24493
26115
|
}
|
|
24494
26116
|
)
|
|
24495
26117
|
] }) : /* @__PURE__ */ jsx("p", { style: mutedStyle, children: "Laster status …" })
|
|
24496
26118
|
] }) : null,
|
|
24497
26119
|
rows.length === 0 ? /* @__PURE__ */ jsx("p", { style: mutedStyle, children: "Ingen deploys ennå." }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
24498
|
-
/* @__PURE__ */ jsx("h3", { style:
|
|
26120
|
+
/* @__PURE__ */ jsx("h3", { style: subheadingStyle, children: "Historikk" }),
|
|
24499
26121
|
/* @__PURE__ */ jsx("ul", { style: { listStyle: "none", padding: 0, margin: 0, display: "flex", flexDirection: "column", gap: 6 }, children: rows.map((r) => {
|
|
24500
26122
|
const focused = r.id === focusId;
|
|
24501
26123
|
const inProgress = isDeploymentInProgress(r.status);
|
|
@@ -24504,7 +26126,7 @@ function DeploymentList({
|
|
|
24504
26126
|
{
|
|
24505
26127
|
href: inProgress ? deploymentAdminUrl(r.id) : void 0,
|
|
24506
26128
|
style: {
|
|
24507
|
-
...
|
|
26129
|
+
...rowStyle,
|
|
24508
26130
|
textDecoration: "none",
|
|
24509
26131
|
color: "inherit",
|
|
24510
26132
|
outline: focused ? "1px solid #640AFF" : void 0,
|
|
@@ -24541,9 +26163,120 @@ function statusLabel(row) {
|
|
|
24541
26163
|
return "Avbrutt";
|
|
24542
26164
|
}
|
|
24543
26165
|
}
|
|
26166
|
+
function depDotStyle(status) {
|
|
26167
|
+
const colour = status === "ready" ? "#34d399" : status === "error" || status === "canceled" ? "#ff7a7a" : status === "building" ? "#fbbf24" : "#888";
|
|
26168
|
+
return {
|
|
26169
|
+
width: 8,
|
|
26170
|
+
height: 8,
|
|
26171
|
+
borderRadius: "50%",
|
|
26172
|
+
background: colour,
|
|
26173
|
+
flexShrink: 0
|
|
26174
|
+
};
|
|
26175
|
+
}
|
|
26176
|
+
function SettoAdminApp() {
|
|
26177
|
+
return /* @__PURE__ */ jsx(AuthGate, { children: /* @__PURE__ */ jsx(SiteList, {}) });
|
|
26178
|
+
}
|
|
26179
|
+
const DEPLOYMENTS_VIEW = "deployments";
|
|
26180
|
+
function SiteList() {
|
|
26181
|
+
const { supabase, session, sites, currentSite, config } = useSetto();
|
|
26182
|
+
const [error, setError] = useState(null);
|
|
26183
|
+
const [view, setView] = useState(() => currentAdminView() ?? DEPLOYMENTS_VIEW);
|
|
26184
|
+
const redirectAfterSignIn = useRef(false);
|
|
26185
|
+
useEffect(() => {
|
|
26186
|
+
const { data: sub } = supabase.auth.onAuthStateChange((event) => {
|
|
26187
|
+
if (event === "SIGNED_IN" && !isAdminPanelView()) {
|
|
26188
|
+
redirectAfterSignIn.current = true;
|
|
26189
|
+
}
|
|
26190
|
+
});
|
|
26191
|
+
return () => sub.subscription.unsubscribe();
|
|
26192
|
+
}, [supabase]);
|
|
26193
|
+
useEffect(() => {
|
|
26194
|
+
if (!redirectAfterSignIn.current || !session || sites === null || isAdminPanelView()) {
|
|
26195
|
+
return;
|
|
26196
|
+
}
|
|
26197
|
+
if (currentSite) {
|
|
26198
|
+
redirectAfterSignIn.current = false;
|
|
26199
|
+
window.location.replace(siteHomeUrl());
|
|
26200
|
+
return;
|
|
26201
|
+
}
|
|
26202
|
+
redirectAfterSignIn.current = false;
|
|
26203
|
+
}, [session, sites, currentSite]);
|
|
26204
|
+
useEffect(() => {
|
|
26205
|
+
const onPopState = () => setView(currentAdminView() ?? DEPLOYMENTS_VIEW);
|
|
26206
|
+
window.addEventListener("popstate", onPopState);
|
|
26207
|
+
return () => window.removeEventListener("popstate", onPopState);
|
|
26208
|
+
}, []);
|
|
26209
|
+
const showView = (id) => {
|
|
26210
|
+
setView(id);
|
|
26211
|
+
setError(null);
|
|
26212
|
+
window.history.pushState(null, "", id === DEPLOYMENTS_VIEW ? SETTO_BASE : adminViewUrl(id));
|
|
26213
|
+
};
|
|
26214
|
+
if (redirectAfterSignIn.current && session && (sites === null || currentSite)) {
|
|
26215
|
+
return /* @__PURE__ */ jsx("div", { style: loadingRedirectStyle, children: "Åpner redigering …" });
|
|
26216
|
+
}
|
|
26217
|
+
const activeModule = findModule(view);
|
|
26218
|
+
return /* @__PURE__ */ jsxs("div", { style: shellStyle$1, children: [
|
|
26219
|
+
/* @__PURE__ */ jsxs("header", { style: headerStyle, children: [
|
|
26220
|
+
/* @__PURE__ */ jsx("h1", { style: { margin: 0, fontSize: 22 }, children: "Setto" }),
|
|
26221
|
+
/* @__PURE__ */ jsx(SignOutButton, {})
|
|
26222
|
+
] }),
|
|
26223
|
+
/* @__PURE__ */ jsxs("main", { style: mainStyle, children: [
|
|
26224
|
+
error ? /* @__PURE__ */ jsx("p", { style: errStyle, children: error }) : null,
|
|
26225
|
+
sites === null ? /* @__PURE__ */ jsx("p", { style: mutedStyle, children: "Laster sider…" }) : null,
|
|
26226
|
+
currentSite ? /* @__PURE__ */ jsxs("section", { style: cardStyle, children: [
|
|
26227
|
+
/* @__PURE__ */ jsx("h2", { style: { margin: 0, fontSize: 18 }, children: currentSite.display_name }),
|
|
26228
|
+
/* @__PURE__ */ jsxs("p", { style: mutedStyle, children: [
|
|
26229
|
+
currentSite.repo_owner,
|
|
26230
|
+
"/",
|
|
26231
|
+
currentSite.repo_name,
|
|
26232
|
+
" · branch ",
|
|
26233
|
+
currentSite.branch
|
|
26234
|
+
] }),
|
|
26235
|
+
/* @__PURE__ */ jsx("div", { style: { display: "flex", gap: 8 }, children: /* @__PURE__ */ jsx("a", { href: siteHomeUrl(), style: primaryBtnStyle, children: "Begynn å redigere" }) }),
|
|
26236
|
+
/* @__PURE__ */ jsxs("nav", { style: tabBarStyle, "aria-label": "Visninger", children: [
|
|
26237
|
+
/* @__PURE__ */ jsx(
|
|
26238
|
+
"button",
|
|
26239
|
+
{
|
|
26240
|
+
type: "button",
|
|
26241
|
+
style: tabStyle(!activeModule),
|
|
26242
|
+
"aria-current": !activeModule ? "page" : void 0,
|
|
26243
|
+
onClick: () => showView(DEPLOYMENTS_VIEW),
|
|
26244
|
+
children: "Publiseringer"
|
|
26245
|
+
}
|
|
26246
|
+
),
|
|
26247
|
+
SETTO_MODULES.map((m) => /* @__PURE__ */ jsx(
|
|
26248
|
+
"button",
|
|
26249
|
+
{
|
|
26250
|
+
type: "button",
|
|
26251
|
+
style: tabStyle(activeModule?.id === m.id),
|
|
26252
|
+
"aria-current": activeModule?.id === m.id ? "page" : void 0,
|
|
26253
|
+
onClick: () => showView(m.id),
|
|
26254
|
+
children: m.label
|
|
26255
|
+
},
|
|
26256
|
+
m.id
|
|
26257
|
+
))
|
|
26258
|
+
] }),
|
|
26259
|
+
activeModule ? /* @__PURE__ */ jsx(activeModule.AdminView, { siteId: currentSite.id }) : /* @__PURE__ */ jsx(DeploymentsView, { siteId: currentSite.id, onError: setError })
|
|
26260
|
+
] }) : sites && sites.length === 0 ? /* @__PURE__ */ jsx("p", { style: mutedStyle, children: "Du er ikke medlem av noen sider ennå. Be administratoren legge deg til." }) : null,
|
|
26261
|
+
sites && !currentSite && sites.length > 0 ? /* @__PURE__ */ jsxs("section", { style: cardStyle, children: [
|
|
26262
|
+
/* @__PURE__ */ jsx("h2", { style: { margin: 0, fontSize: 16 }, children: "Andre sider du har tilgang til" }),
|
|
26263
|
+
/* @__PURE__ */ jsx("ul", { style: { listStyle: "none", padding: 0, margin: 0 }, children: sites.map((s) => /* @__PURE__ */ jsxs("li", { style: { padding: "6px 0" }, children: [
|
|
26264
|
+
/* @__PURE__ */ jsx("code", { style: { color: "#bda6ff" }, children: s.id }),
|
|
26265
|
+
" — ",
|
|
26266
|
+
s.display_name
|
|
26267
|
+
] }, s.id)) }),
|
|
26268
|
+
/* @__PURE__ */ jsxs("p", { style: mutedStyle, children: [
|
|
26269
|
+
"Du er logget inn på denne siden (",
|
|
26270
|
+
config.siteId,
|
|
26271
|
+
"), men er ikke medlem her. Spør administrator om tilgang."
|
|
26272
|
+
] })
|
|
26273
|
+
] }) : null
|
|
26274
|
+
] })
|
|
26275
|
+
] });
|
|
26276
|
+
}
|
|
24544
26277
|
function SignOutButton() {
|
|
24545
26278
|
const { supabase } = useSetto();
|
|
24546
|
-
return /* @__PURE__ */ jsx("button", { onClick: () => supabase.auth.signOut(), style:
|
|
26279
|
+
return /* @__PURE__ */ jsx("button", { onClick: () => supabase.auth.signOut(), style: secondaryBtnStyle, children: "Logg ut" });
|
|
24547
26280
|
}
|
|
24548
26281
|
const loadingRedirectStyle = {
|
|
24549
26282
|
minHeight: "100dvh",
|
|
@@ -24555,101 +26288,6 @@ const loadingRedirectStyle = {
|
|
|
24555
26288
|
fontSize: 14,
|
|
24556
26289
|
fontFamily: 'system-ui, -apple-system, "Segoe UI", sans-serif'
|
|
24557
26290
|
};
|
|
24558
|
-
const shellStyle = {
|
|
24559
|
-
minHeight: "100dvh",
|
|
24560
|
-
background: "#0a0a0d",
|
|
24561
|
-
color: "#fff",
|
|
24562
|
-
fontFamily: 'system-ui, -apple-system, "Segoe UI", sans-serif'
|
|
24563
|
-
};
|
|
24564
|
-
const headerStyle = {
|
|
24565
|
-
display: "flex",
|
|
24566
|
-
alignItems: "center",
|
|
24567
|
-
justifyContent: "space-between",
|
|
24568
|
-
padding: "16px 24px",
|
|
24569
|
-
borderBottom: "1px solid #1c1c22"
|
|
24570
|
-
};
|
|
24571
|
-
const mainStyle = {
|
|
24572
|
-
maxWidth: 720,
|
|
24573
|
-
margin: "0 auto",
|
|
24574
|
-
padding: 24,
|
|
24575
|
-
display: "flex",
|
|
24576
|
-
flexDirection: "column",
|
|
24577
|
-
gap: 16
|
|
24578
|
-
};
|
|
24579
|
-
const cardStyle = {
|
|
24580
|
-
background: "#15151a",
|
|
24581
|
-
borderRadius: 12,
|
|
24582
|
-
padding: 20,
|
|
24583
|
-
display: "flex",
|
|
24584
|
-
flexDirection: "column",
|
|
24585
|
-
gap: 12
|
|
24586
|
-
};
|
|
24587
|
-
const mutedStyle = {
|
|
24588
|
-
color: "#888",
|
|
24589
|
-
fontSize: 13,
|
|
24590
|
-
margin: 0
|
|
24591
|
-
};
|
|
24592
|
-
const errStyle = {
|
|
24593
|
-
color: "#ff7a7a",
|
|
24594
|
-
fontSize: 13,
|
|
24595
|
-
margin: 0
|
|
24596
|
-
};
|
|
24597
|
-
const primaryBtnLinkStyle = {
|
|
24598
|
-
display: "inline-block",
|
|
24599
|
-
background: "#640AFF",
|
|
24600
|
-
color: "#fff",
|
|
24601
|
-
border: "none",
|
|
24602
|
-
borderRadius: 6,
|
|
24603
|
-
padding: "10px 14px",
|
|
24604
|
-
fontSize: 14,
|
|
24605
|
-
fontWeight: 500,
|
|
24606
|
-
textDecoration: "none"
|
|
24607
|
-
};
|
|
24608
|
-
const signOutBtnStyle = {
|
|
24609
|
-
background: "transparent",
|
|
24610
|
-
color: "#bda6ff",
|
|
24611
|
-
border: "1px solid #2a2a32",
|
|
24612
|
-
borderRadius: 6,
|
|
24613
|
-
padding: "6px 10px",
|
|
24614
|
-
fontSize: 12,
|
|
24615
|
-
cursor: "pointer"
|
|
24616
|
-
};
|
|
24617
|
-
const activeDepStyle = {
|
|
24618
|
-
background: "#1a1a20",
|
|
24619
|
-
borderRadius: 8,
|
|
24620
|
-
padding: 14,
|
|
24621
|
-
display: "flex",
|
|
24622
|
-
flexDirection: "column",
|
|
24623
|
-
gap: 10
|
|
24624
|
-
};
|
|
24625
|
-
const depRowStyle = {
|
|
24626
|
-
display: "flex",
|
|
24627
|
-
alignItems: "center",
|
|
24628
|
-
gap: 10,
|
|
24629
|
-
background: "#1a1a20",
|
|
24630
|
-
padding: "6px 10px",
|
|
24631
|
-
borderRadius: 6
|
|
24632
|
-
};
|
|
24633
|
-
const cancelBtnStyle = {
|
|
24634
|
-
alignSelf: "flex-start",
|
|
24635
|
-
background: "transparent",
|
|
24636
|
-
color: "#ff7a7a",
|
|
24637
|
-
border: "1px solid #4a3030",
|
|
24638
|
-
borderRadius: 6,
|
|
24639
|
-
padding: "8px 14px",
|
|
24640
|
-
fontSize: 13,
|
|
24641
|
-
cursor: "pointer"
|
|
24642
|
-
};
|
|
24643
|
-
function depDotStyle(status) {
|
|
24644
|
-
const colour = status === "ready" ? "#34d399" : status === "error" || status === "canceled" ? "#ff7a7a" : status === "building" ? "#fbbf24" : "#888";
|
|
24645
|
-
return {
|
|
24646
|
-
width: 8,
|
|
24647
|
-
height: 8,
|
|
24648
|
-
borderRadius: "50%",
|
|
24649
|
-
background: colour,
|
|
24650
|
-
flexShrink: 0
|
|
24651
|
-
};
|
|
24652
|
-
}
|
|
24653
26291
|
const BLOCK_REGISTRY = {
|
|
24654
26292
|
section: { allowedChildren: "any", themed: true, selectable: true },
|
|
24655
26293
|
block: { allowedChildren: "any", themed: true, selectable: true },
|
|
@@ -24678,7 +26316,6 @@ function nodeChildren(node) {
|
|
|
24678
26316
|
return [];
|
|
24679
26317
|
}
|
|
24680
26318
|
}
|
|
24681
|
-
const FIELD_TYPES = ["text", "email", "tel", "textarea"];
|
|
24682
26319
|
const ANIMATION_PRESETS = ["fadeIn", "slideUp", "stagger", "scrollReveal"];
|
|
24683
26320
|
function validateBlockTree(input) {
|
|
24684
26321
|
const issues = [];
|
|
@@ -24783,18 +26420,17 @@ function validateTypeSpecific(raw, type, path, issues) {
|
|
|
24783
26420
|
break;
|
|
24784
26421
|
case "form":
|
|
24785
26422
|
requireString(raw, "formId", path, issues);
|
|
24786
|
-
if (
|
|
24787
|
-
|
|
24788
|
-
|
|
24789
|
-
|
|
24790
|
-
|
|
24791
|
-
|
|
26423
|
+
if (raw.fields !== void 0) {
|
|
26424
|
+
if (!Array.isArray(raw.fields)) {
|
|
26425
|
+
issues.push({ path: `${path}.fields`, message: "form.fields must be an array" });
|
|
26426
|
+
break;
|
|
26427
|
+
}
|
|
26428
|
+
for (let i = 0; i < raw.fields.length; i++) {
|
|
26429
|
+
validateFormField(raw.fields[i], `${path}.fields[${i}]`, issues);
|
|
26430
|
+
}
|
|
24792
26431
|
}
|
|
24793
|
-
if (raw.endpoint
|
|
24794
|
-
issues.push({
|
|
24795
|
-
path,
|
|
24796
|
-
message: "form must declare either endpoint or endpointKey"
|
|
24797
|
-
});
|
|
26432
|
+
if (raw.endpoint !== void 0 && typeof raw.endpoint !== "string") {
|
|
26433
|
+
issues.push({ path: `${path}.endpoint`, message: "form.endpoint must be a string" });
|
|
24798
26434
|
}
|
|
24799
26435
|
break;
|
|
24800
26436
|
case "video":
|
|
@@ -24819,10 +26455,10 @@ function validateFormField(raw, path, issues) {
|
|
|
24819
26455
|
if (typeof raw.name !== "string" || raw.name.length === 0) {
|
|
24820
26456
|
issues.push({ path: `${path}.name`, message: "field.name must be a non-empty string" });
|
|
24821
26457
|
}
|
|
24822
|
-
if (
|
|
26458
|
+
if (!isFormFieldType(raw.type)) {
|
|
24823
26459
|
issues.push({
|
|
24824
26460
|
path: `${path}.type`,
|
|
24825
|
-
message: `field.type must be one of ${
|
|
26461
|
+
message: `field.type must be one of ${FORM_FIELD_TYPES.join(", ")}`
|
|
24826
26462
|
});
|
|
24827
26463
|
}
|
|
24828
26464
|
requireString(raw, "labelKey", path, issues);
|
|
@@ -24915,7 +26551,6 @@ function RenderNode({ node }) {
|
|
|
24915
26551
|
formId: node.formId,
|
|
24916
26552
|
fields: node.fields,
|
|
24917
26553
|
endpoint: node.endpoint,
|
|
24918
|
-
endpointKey: node.endpointKey,
|
|
24919
26554
|
className: node.className
|
|
24920
26555
|
}
|
|
24921
26556
|
);
|
|
@@ -25071,10 +26706,11 @@ function walk(node, out, repeaterCtx) {
|
|
|
25071
26706
|
case "form":
|
|
25072
26707
|
out.keys.add(`form.${node.formId}.submit`);
|
|
25073
26708
|
out.keys.add(`form.${node.formId}.success`);
|
|
25074
|
-
|
|
25075
|
-
for (const field of node.fields) {
|
|
26709
|
+
out.keys.add(`form.${node.formId}.error`);
|
|
26710
|
+
for (const field of node.fields ?? []) {
|
|
25076
26711
|
out.keys.add(field.labelKey);
|
|
25077
26712
|
if (field.placeholderKey) out.keys.add(field.placeholderKey);
|
|
26713
|
+
if (field.optionsKey) out.keys.add(field.optionsKey);
|
|
25078
26714
|
}
|
|
25079
26715
|
return;
|
|
25080
26716
|
case "repeater":
|
|
@@ -25128,8 +26764,11 @@ export {
|
|
|
25128
26764
|
AuthGate,
|
|
25129
26765
|
BLOCK_REGISTRY,
|
|
25130
26766
|
BLOCK_TYPES,
|
|
26767
|
+
FORM_FIELD_TYPES,
|
|
26768
|
+
FORM_FIELD_TYPE_LABELS,
|
|
25131
26769
|
GuestEditProvider,
|
|
25132
26770
|
SETTO_BASE,
|
|
26771
|
+
SETTO_MODULES,
|
|
25133
26772
|
SettoAdminApp,
|
|
25134
26773
|
SettoAnimation,
|
|
25135
26774
|
SettoBlock,
|
|
@@ -25137,14 +26776,21 @@ export {
|
|
|
25137
26776
|
SettoForm,
|
|
25138
26777
|
SettoIcon,
|
|
25139
26778
|
SettoImage,
|
|
26779
|
+
SettoPreviewBar,
|
|
25140
26780
|
SettoProvider,
|
|
25141
26781
|
SettoRepeater,
|
|
25142
26782
|
SettoSection,
|
|
25143
26783
|
SettoVideo,
|
|
25144
26784
|
T,
|
|
26785
|
+
adminViewUrl,
|
|
25145
26786
|
extractContentKeys,
|
|
26787
|
+
formFieldsPrefix,
|
|
26788
|
+
isFormFieldType,
|
|
26789
|
+
isPreviewHost,
|
|
25146
26790
|
nodeChildren,
|
|
25147
26791
|
parseBlockTree,
|
|
26792
|
+
readLocaleFields,
|
|
26793
|
+
settoSubmissionUrl,
|
|
25148
26794
|
useGuestEdit,
|
|
25149
26795
|
useSectionTheme,
|
|
25150
26796
|
useSetto,
|