@sevenfold/setto-client 0.6.1 → 0.7.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 +62 -10
- package/dist/SettoRepeater.d.ts +17 -0
- package/dist/SettoSection.d.ts +8 -0
- 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 +7 -2
- package/dist/index.d.ts +5 -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/setto-client.js +2642 -1103
- package/dist/setto-client.js.map +1 -1
- 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, useEffect, useSyncExternalStore,
|
|
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 };
|
|
@@ -21175,21 +21318,38 @@ const EMPTY = {
|
|
|
21175
21318
|
};
|
|
21176
21319
|
function useSiteAccess(supabase, session, siteId) {
|
|
21177
21320
|
const [state, setState] = useState(EMPTY);
|
|
21321
|
+
const cachedUserIdRef = useRef(null);
|
|
21178
21322
|
useEffect(() => {
|
|
21179
21323
|
if (!session) {
|
|
21324
|
+
cachedUserIdRef.current = null;
|
|
21180
21325
|
setState({ sites: null, currentSite: null, loading: false, error: null });
|
|
21181
21326
|
return;
|
|
21182
21327
|
}
|
|
21328
|
+
const userId = session.user.id;
|
|
21329
|
+
const sameUser = cachedUserIdRef.current === userId;
|
|
21183
21330
|
let cancelled = false;
|
|
21184
|
-
setState((prev) =>
|
|
21331
|
+
setState((prev) => {
|
|
21332
|
+
if (sameUser && prev.sites !== null && !prev.loading) {
|
|
21333
|
+
return prev;
|
|
21334
|
+
}
|
|
21335
|
+
return {
|
|
21336
|
+
...prev,
|
|
21337
|
+
loading: true,
|
|
21338
|
+
error: null,
|
|
21339
|
+
sites: sameUser ? prev.sites : null,
|
|
21340
|
+
currentSite: sameUser ? prev.currentSite : null
|
|
21341
|
+
};
|
|
21342
|
+
});
|
|
21185
21343
|
supabase.from("sites").select("*").then(({ data, error }) => {
|
|
21186
21344
|
if (cancelled) return;
|
|
21187
21345
|
if (error) {
|
|
21346
|
+
cachedUserIdRef.current = userId;
|
|
21188
21347
|
setState({ sites: [], currentSite: null, loading: false, error: error.message });
|
|
21189
21348
|
return;
|
|
21190
21349
|
}
|
|
21191
21350
|
const rows = data ?? [];
|
|
21192
21351
|
const current = rows.find((s) => s.id === siteId) ?? null;
|
|
21352
|
+
cachedUserIdRef.current = userId;
|
|
21193
21353
|
setState({ sites: rows, currentSite: current, loading: false, error: null });
|
|
21194
21354
|
});
|
|
21195
21355
|
return () => {
|
|
@@ -21211,20 +21371,30 @@ function isAdminRoute() {
|
|
|
21211
21371
|
function isAdminDeploymentView() {
|
|
21212
21372
|
return isAdminRoute() && new URLSearchParams(window.location.search).has("deployment");
|
|
21213
21373
|
}
|
|
21374
|
+
function adminViewUrl(view) {
|
|
21375
|
+
return `${SETTO_BASE}?view=${encodeURIComponent(view)}`;
|
|
21376
|
+
}
|
|
21377
|
+
function currentAdminView() {
|
|
21378
|
+
return new URLSearchParams(window.location.search).get("view");
|
|
21379
|
+
}
|
|
21380
|
+
function isAdminPanelView() {
|
|
21381
|
+
return isAdminDeploymentView() || isAdminRoute() && currentAdminView() !== null;
|
|
21382
|
+
}
|
|
21214
21383
|
const FAB_SIZE = 52;
|
|
21215
21384
|
const FAB_INSET = 16;
|
|
21216
|
-
const STYLE_ID$
|
|
21385
|
+
const STYLE_ID$3 = "setto-edit-layout";
|
|
21217
21386
|
const HTML_CLASS = "setto-edit-mode";
|
|
21218
21387
|
function useSettoDocumentLayout(active) {
|
|
21219
21388
|
useEffect(() => {
|
|
21389
|
+
if (!active) return;
|
|
21220
21390
|
const html = document.documentElement;
|
|
21221
21391
|
html.classList.add(HTML_CLASS);
|
|
21222
21392
|
html.style.setProperty("--setto-fab-size", `${FAB_SIZE}px`);
|
|
21223
21393
|
html.style.setProperty("--setto-fab-inset", `${FAB_INSET}px`);
|
|
21224
|
-
let styleEl = document.getElementById(STYLE_ID$
|
|
21394
|
+
let styleEl = document.getElementById(STYLE_ID$3);
|
|
21225
21395
|
if (!styleEl) {
|
|
21226
21396
|
styleEl = document.createElement("style");
|
|
21227
|
-
styleEl.id = STYLE_ID$
|
|
21397
|
+
styleEl.id = STYLE_ID$3;
|
|
21228
21398
|
styleEl.textContent = `
|
|
21229
21399
|
html.setto-edit-mode {
|
|
21230
21400
|
width: 100%;
|
|
@@ -21870,10 +22040,10 @@ function EditOnboardingDialog() {
|
|
|
21870
22040
|
]
|
|
21871
22041
|
}
|
|
21872
22042
|
),
|
|
21873
|
-
/* @__PURE__ */ jsx("button", { type: "button", onClick: close, style: primaryBtnStyle$
|
|
22043
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: close, style: primaryBtnStyle$2, children: "Forstått" })
|
|
21874
22044
|
] });
|
|
21875
22045
|
}
|
|
21876
|
-
const primaryBtnStyle$
|
|
22046
|
+
const primaryBtnStyle$2 = {
|
|
21877
22047
|
background: "#640AFF",
|
|
21878
22048
|
color: "#fff",
|
|
21879
22049
|
border: "none",
|
|
@@ -22112,14 +22282,14 @@ function PublishDialog({
|
|
|
22112
22282
|
return /* @__PURE__ */ jsx(EditOverlay, { onClose: void 0, children: isReady ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
22113
22283
|
/* @__PURE__ */ jsx("h2", { style: { margin: "0 0 12px", fontSize: 18, fontWeight: 600 }, children: "Publisert" }),
|
|
22114
22284
|
/* @__PURE__ */ jsx("p", { style: { margin: "0 0 20px", fontSize: 14, lineHeight: 1.5, color: "#444" }, children: "Endringene dine er live." }),
|
|
22115
|
-
/* @__PURE__ */ jsx("button", { type: "button", onClick: onComplete, style: primaryBtnStyle, children: "OK" })
|
|
22285
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: onComplete, style: primaryBtnStyle$1, children: "OK" })
|
|
22116
22286
|
] }) : errored ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
22117
22287
|
/* @__PURE__ */ jsx("h2", { style: { margin: "0 0 12px", fontSize: 18, fontWeight: 600, color: "#dc2626" }, children: "Publisering feilet" }),
|
|
22118
22288
|
/* @__PURE__ */ jsxs("p", { style: { margin: "0 0 20px", fontSize: 14, lineHeight: 1.5, color: "#444" }, children: [
|
|
22119
22289
|
progressStatus === "error" ? "Bygg feilet." : "Avbrutt.",
|
|
22120
22290
|
row?.error_message ? ` ${row.error_message}` : ""
|
|
22121
22291
|
] }),
|
|
22122
|
-
/* @__PURE__ */ jsx("button", { type: "button", onClick: onComplete, style: secondaryBtnStyle, children: "OK" })
|
|
22292
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: onComplete, style: secondaryBtnStyle$1, children: "OK" })
|
|
22123
22293
|
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
22124
22294
|
/* @__PURE__ */ jsx("h2", { style: { margin: "0 0 16px", fontSize: 16, fontWeight: 600 }, children: "Publiserer endringer" }),
|
|
22125
22295
|
/* @__PURE__ */ jsx("div", { style: { marginBottom: 20 }, children: /* @__PURE__ */ jsx(
|
|
@@ -22129,10 +22299,10 @@ function PublishDialog({
|
|
|
22129
22299
|
buildingElapsed: isBuilding ? elapsed : void 0
|
|
22130
22300
|
}
|
|
22131
22301
|
) }),
|
|
22132
|
-
/* @__PURE__ */ jsx("button", { type: "button", onClick: onClose, style: secondaryBtnStyle, children: "Lukk" })
|
|
22302
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: onClose, style: secondaryBtnStyle$1, children: "Lukk" })
|
|
22133
22303
|
] }) });
|
|
22134
22304
|
}
|
|
22135
|
-
const primaryBtnStyle = {
|
|
22305
|
+
const primaryBtnStyle$1 = {
|
|
22136
22306
|
background: "#640AFF",
|
|
22137
22307
|
color: "#fff",
|
|
22138
22308
|
border: "none",
|
|
@@ -22143,8 +22313,8 @@ const primaryBtnStyle = {
|
|
|
22143
22313
|
cursor: "pointer",
|
|
22144
22314
|
width: "100%"
|
|
22145
22315
|
};
|
|
22146
|
-
const secondaryBtnStyle = {
|
|
22147
|
-
...primaryBtnStyle,
|
|
22316
|
+
const secondaryBtnStyle$1 = {
|
|
22317
|
+
...primaryBtnStyle$1,
|
|
22148
22318
|
background: "transparent",
|
|
22149
22319
|
color: "#666",
|
|
22150
22320
|
border: "1px solid #ddd"
|
|
@@ -22198,702 +22368,404 @@ function useEditBaseline() {
|
|
|
22198
22368
|
};
|
|
22199
22369
|
}, [session, store, config.siteId]);
|
|
22200
22370
|
}
|
|
22201
|
-
|
|
22202
|
-
|
|
22203
|
-
|
|
22204
|
-
|
|
22205
|
-
|
|
22206
|
-
}
|
|
22207
|
-
|
|
22208
|
-
|
|
22209
|
-
|
|
22210
|
-
|
|
22211
|
-
|
|
22212
|
-
|
|
22213
|
-
|
|
22214
|
-
|
|
22215
|
-
|
|
22216
|
-
|
|
22371
|
+
const shellStyle$1 = {
|
|
22372
|
+
minHeight: "100dvh",
|
|
22373
|
+
background: "#0a0a0d",
|
|
22374
|
+
color: "#fff",
|
|
22375
|
+
fontFamily: 'system-ui, -apple-system, "Segoe UI", sans-serif'
|
|
22376
|
+
};
|
|
22377
|
+
const headerStyle = {
|
|
22378
|
+
display: "flex",
|
|
22379
|
+
alignItems: "center",
|
|
22380
|
+
justifyContent: "space-between",
|
|
22381
|
+
padding: "16px 24px",
|
|
22382
|
+
borderBottom: "1px solid #1c1c22"
|
|
22383
|
+
};
|
|
22384
|
+
const mainStyle = {
|
|
22385
|
+
maxWidth: 720,
|
|
22386
|
+
margin: "0 auto",
|
|
22387
|
+
padding: 24,
|
|
22388
|
+
display: "flex",
|
|
22389
|
+
flexDirection: "column",
|
|
22390
|
+
gap: 16
|
|
22391
|
+
};
|
|
22392
|
+
const cardStyle = {
|
|
22393
|
+
background: "#15151a",
|
|
22394
|
+
borderRadius: 12,
|
|
22395
|
+
padding: 20,
|
|
22396
|
+
display: "flex",
|
|
22397
|
+
flexDirection: "column",
|
|
22398
|
+
gap: 12
|
|
22399
|
+
};
|
|
22400
|
+
const panelStyle = {
|
|
22401
|
+
background: "#1a1a20",
|
|
22402
|
+
borderRadius: 8,
|
|
22403
|
+
padding: 14,
|
|
22404
|
+
display: "flex",
|
|
22405
|
+
flexDirection: "column",
|
|
22406
|
+
gap: 10
|
|
22407
|
+
};
|
|
22408
|
+
const rowStyle = {
|
|
22409
|
+
display: "flex",
|
|
22410
|
+
alignItems: "center",
|
|
22411
|
+
gap: 10,
|
|
22412
|
+
background: "#1a1a20",
|
|
22413
|
+
padding: "6px 10px",
|
|
22414
|
+
borderRadius: 6
|
|
22415
|
+
};
|
|
22416
|
+
const mutedStyle = {
|
|
22417
|
+
color: "#888",
|
|
22418
|
+
fontSize: 13,
|
|
22419
|
+
margin: 0
|
|
22420
|
+
};
|
|
22421
|
+
const errStyle = {
|
|
22422
|
+
color: "#ff7a7a",
|
|
22423
|
+
fontSize: 13,
|
|
22424
|
+
margin: 0
|
|
22425
|
+
};
|
|
22426
|
+
const subheadingStyle = {
|
|
22427
|
+
margin: 0,
|
|
22428
|
+
fontSize: 14,
|
|
22429
|
+
color: "#aaa"
|
|
22430
|
+
};
|
|
22431
|
+
const primaryBtnStyle = {
|
|
22432
|
+
display: "inline-block",
|
|
22433
|
+
background: "#640AFF",
|
|
22434
|
+
color: "#fff",
|
|
22435
|
+
border: "none",
|
|
22436
|
+
borderRadius: 6,
|
|
22437
|
+
padding: "10px 14px",
|
|
22438
|
+
fontSize: 14,
|
|
22439
|
+
fontWeight: 500,
|
|
22440
|
+
textDecoration: "none",
|
|
22441
|
+
cursor: "pointer"
|
|
22442
|
+
};
|
|
22443
|
+
const secondaryBtnStyle = {
|
|
22444
|
+
background: "transparent",
|
|
22445
|
+
color: "#bda6ff",
|
|
22446
|
+
border: "1px solid #2a2a32",
|
|
22447
|
+
borderRadius: 6,
|
|
22448
|
+
padding: "6px 10px",
|
|
22449
|
+
fontSize: 12,
|
|
22450
|
+
cursor: "pointer"
|
|
22451
|
+
};
|
|
22452
|
+
const dangerBtnStyle = {
|
|
22453
|
+
alignSelf: "flex-start",
|
|
22454
|
+
background: "transparent",
|
|
22455
|
+
color: "#ff7a7a",
|
|
22456
|
+
border: "1px solid #4a3030",
|
|
22457
|
+
borderRadius: 6,
|
|
22458
|
+
padding: "8px 14px",
|
|
22459
|
+
fontSize: 13,
|
|
22460
|
+
cursor: "pointer"
|
|
22461
|
+
};
|
|
22462
|
+
const inputStyle$2 = {
|
|
22463
|
+
width: "100%",
|
|
22464
|
+
boxSizing: "border-box",
|
|
22465
|
+
background: "#0f0f13",
|
|
22466
|
+
color: "#fff",
|
|
22467
|
+
border: "1px solid #2a2a32",
|
|
22468
|
+
borderRadius: 6,
|
|
22469
|
+
padding: "8px 10px",
|
|
22470
|
+
fontSize: 13,
|
|
22471
|
+
fontFamily: "inherit"
|
|
22472
|
+
};
|
|
22473
|
+
const tabBarStyle = {
|
|
22474
|
+
display: "flex",
|
|
22475
|
+
gap: 4,
|
|
22476
|
+
borderBottom: "1px solid #1c1c22",
|
|
22477
|
+
paddingBottom: 0
|
|
22478
|
+
};
|
|
22479
|
+
function tabStyle(active) {
|
|
22480
|
+
return {
|
|
22481
|
+
background: "transparent",
|
|
22482
|
+
border: "none",
|
|
22483
|
+
borderBottom: active ? "2px solid #640AFF" : "2px solid transparent",
|
|
22484
|
+
color: active ? "#fff" : "#888",
|
|
22485
|
+
padding: "8px 12px",
|
|
22486
|
+
fontSize: 13,
|
|
22487
|
+
fontWeight: 500,
|
|
22488
|
+
cursor: "pointer",
|
|
22489
|
+
marginBottom: -1
|
|
22490
|
+
};
|
|
22491
|
+
}
|
|
22492
|
+
function FormsView({ siteId }) {
|
|
22493
|
+
return /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 16 }, children: [
|
|
22494
|
+
/* @__PURE__ */ jsx(RecipientsPanel, { siteId }),
|
|
22495
|
+
/* @__PURE__ */ jsx(SubmissionList, { siteId })
|
|
22496
|
+
] });
|
|
22497
|
+
}
|
|
22498
|
+
function RecipientsPanel({ siteId }) {
|
|
22499
|
+
const { api } = useSetto();
|
|
22500
|
+
const [settings, setSettings] = useState(null);
|
|
22501
|
+
const [editing, setEditing] = useState(false);
|
|
22502
|
+
const [draft, setDraft] = useState("");
|
|
22503
|
+
const [saving, setSaving] = useState(false);
|
|
22504
|
+
const [error, setError] = useState(null);
|
|
22505
|
+
useEffect(() => {
|
|
22506
|
+
let cancelled = false;
|
|
22507
|
+
api.forms.getSettings(siteId).then((s) => {
|
|
22508
|
+
if (!cancelled) setSettings(s);
|
|
22509
|
+
}).catch((err) => {
|
|
22510
|
+
if (!cancelled) setError(err instanceof Error ? err.message : "Kunne ikke hente innstillinger");
|
|
22511
|
+
});
|
|
22512
|
+
return () => {
|
|
22513
|
+
cancelled = true;
|
|
22514
|
+
};
|
|
22515
|
+
}, [api, siteId]);
|
|
22516
|
+
const startEditing = () => {
|
|
22517
|
+
setDraft((settings?.recipients ?? []).join("\n"));
|
|
22518
|
+
setEditing(true);
|
|
22519
|
+
setError(null);
|
|
22520
|
+
};
|
|
22521
|
+
const save = async (recipients) => {
|
|
22522
|
+
setSaving(true);
|
|
22523
|
+
setError(null);
|
|
22524
|
+
try {
|
|
22525
|
+
const next = await api.forms.saveSettings(siteId, recipients);
|
|
22526
|
+
setSettings(next);
|
|
22527
|
+
setEditing(false);
|
|
22528
|
+
} catch (err) {
|
|
22529
|
+
setError(err instanceof Error ? err.message : "Kunne ikke lagre");
|
|
22530
|
+
} finally {
|
|
22531
|
+
setSaving(false);
|
|
22532
|
+
}
|
|
22533
|
+
};
|
|
22534
|
+
const parseDraft = () => draft.split(/[\n,;]+/).map((s) => s.trim()).filter(Boolean);
|
|
22535
|
+
return /* @__PURE__ */ jsxs("section", { style: panelStyle, children: [
|
|
22536
|
+
/* @__PURE__ */ jsx("h3", { style: { margin: 0, fontSize: 15 }, children: "Hvem får e-post?" }),
|
|
22537
|
+
error ? /* @__PURE__ */ jsx("p", { style: errStyle, children: error }) : null,
|
|
22538
|
+
settings === null && !error ? /* @__PURE__ */ jsx("p", { style: mutedStyle, children: "Laster …" }) : null,
|
|
22539
|
+
settings && !editing ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
22540
|
+
/* @__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." }) }),
|
|
22541
|
+
/* @__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"}.` }),
|
|
22542
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8 }, children: [
|
|
22543
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: startEditing, style: secondaryBtnStyle, children: "Endre mottakere" }),
|
|
22544
|
+
settings.recipients.length > 0 ? /* @__PURE__ */ jsx("button", { type: "button", onClick: () => void save([]), disabled: saving, style: secondaryBtnStyle, children: "Bruk standard" }) : null
|
|
22545
|
+
] })
|
|
22546
|
+
] }) : null,
|
|
22547
|
+
settings && editing ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
22548
|
+
/* @__PURE__ */ jsxs("label", { style: { display: "grid", gap: 6 }, children: [
|
|
22549
|
+
/* @__PURE__ */ jsx("span", { style: mutedStyle, children: "E-postadresser, én per linje. Tomt betyr standard (eierne)." }),
|
|
22217
22550
|
/* @__PURE__ */ jsx(
|
|
22218
|
-
"
|
|
22551
|
+
"textarea",
|
|
22219
22552
|
{
|
|
22220
|
-
|
|
22221
|
-
|
|
22222
|
-
|
|
22223
|
-
|
|
22224
|
-
|
|
22225
|
-
|
|
22226
|
-
|
|
22227
|
-
|
|
22553
|
+
value: draft,
|
|
22554
|
+
rows: 3,
|
|
22555
|
+
onChange: (e) => setDraft(e.target.value),
|
|
22556
|
+
style: { ...inputStyle$2, resize: "vertical" },
|
|
22557
|
+
placeholder: settings.defaultRecipients.join("\n"),
|
|
22558
|
+
autoFocus: true
|
|
22559
|
+
}
|
|
22560
|
+
)
|
|
22561
|
+
] }),
|
|
22562
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8 }, children: [
|
|
22563
|
+
/* @__PURE__ */ jsx(
|
|
22564
|
+
"button",
|
|
22565
|
+
{
|
|
22566
|
+
type: "button",
|
|
22567
|
+
onClick: () => void save(parseDraft()),
|
|
22568
|
+
disabled: saving,
|
|
22569
|
+
style: { ...primaryBtnStyle, padding: "8px 12px", fontSize: 13 },
|
|
22570
|
+
children: saving ? "Lagrer …" : "Lagre"
|
|
22228
22571
|
}
|
|
22229
22572
|
),
|
|
22230
|
-
/* @__PURE__ */ jsx("
|
|
22231
|
-
]
|
|
22232
|
-
}
|
|
22233
|
-
);
|
|
22573
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => setEditing(false), disabled: saving, style: secondaryBtnStyle, children: "Avbryt" })
|
|
22574
|
+
] })
|
|
22575
|
+
] }) : null
|
|
22576
|
+
] });
|
|
22234
22577
|
}
|
|
22235
|
-
const
|
|
22236
|
-
|
|
22237
|
-
const
|
|
22238
|
-
|
|
22239
|
-
const { store, themeStore, assetStore, api, config, supabase } = useSetto();
|
|
22240
|
-
const { i18n } = useTranslation();
|
|
22241
|
-
useEditBaseline();
|
|
22242
|
-
const snap = useSyncExternalStore(
|
|
22243
|
-
(cb) => store?.subscribe(() => cb()) ?? (() => {
|
|
22244
|
-
}),
|
|
22245
|
-
() => store?.snapshot() ?? EMPTY_I18N_STORE_SNAPSHOT,
|
|
22246
|
-
() => EMPTY_I18N_STORE_SNAPSHOT
|
|
22247
|
-
);
|
|
22248
|
-
useSyncExternalStore(
|
|
22249
|
-
(cb) => themeStore?.subscribe(() => cb()) ?? (() => {
|
|
22250
|
-
}),
|
|
22251
|
-
() => themeStore?.snapshot().version ?? 0,
|
|
22252
|
-
() => 0
|
|
22253
|
-
);
|
|
22254
|
-
useSyncExternalStore(
|
|
22255
|
-
(cb) => assetStore?.subscribe(() => cb()) ?? (() => {
|
|
22256
|
-
}),
|
|
22257
|
-
() => assetStore?.snapshot().version ?? 0,
|
|
22258
|
-
() => 0
|
|
22259
|
-
);
|
|
22260
|
-
const [publishing, setPublishing] = useState(false);
|
|
22261
|
-
const [activeDeployment, setActiveDeployment] = useState(null);
|
|
22262
|
-
const [publishDialogOpen, setPublishDialogOpen] = useState(false);
|
|
22578
|
+
const PAGE = 50;
|
|
22579
|
+
function SubmissionList({ siteId }) {
|
|
22580
|
+
const { supabase, api } = useSetto();
|
|
22581
|
+
const [rows, setRows] = useState(null);
|
|
22263
22582
|
const [error, setError] = useState(null);
|
|
22264
|
-
const [
|
|
22265
|
-
const [
|
|
22266
|
-
const [
|
|
22267
|
-
const menuRef = useRef(null);
|
|
22268
|
-
const publishedNoticeTimer = useRef(null);
|
|
22269
|
-
const { row: deploymentRow, status: deploymentStatus } = useDeploymentStatus(
|
|
22270
|
-
supabase,
|
|
22271
|
-
activeDeployment
|
|
22272
|
-
);
|
|
22583
|
+
const [openId, setOpenId] = useState(null);
|
|
22584
|
+
const [hasMore, setHasMore] = useState(false);
|
|
22585
|
+
const [loadingMore, setLoadingMore] = useState(false);
|
|
22273
22586
|
useEffect(() => {
|
|
22274
22587
|
let cancelled = false;
|
|
22275
|
-
|
|
22276
|
-
|
|
22277
|
-
|
|
22278
|
-
|
|
22279
|
-
|
|
22280
|
-
|
|
22281
|
-
|
|
22282
|
-
|
|
22588
|
+
supabase.from("form_submissions").select("*").eq("site_id", siteId).order("created_at", { ascending: false }).limit(PAGE).then(({ data, error: err }) => {
|
|
22589
|
+
if (cancelled) return;
|
|
22590
|
+
if (err) setError(err.message);
|
|
22591
|
+
const list = data ?? [];
|
|
22592
|
+
setRows(list);
|
|
22593
|
+
setHasMore(list.length === PAGE);
|
|
22594
|
+
});
|
|
22595
|
+
const channel = supabase.channel(`setto-site-forms-${siteId}`).on(
|
|
22596
|
+
"postgres_changes",
|
|
22597
|
+
{ event: "*", schema: "public", table: "form_submissions", filter: `site_id=eq.${siteId}` },
|
|
22598
|
+
(payload) => {
|
|
22599
|
+
const next = payload.new;
|
|
22600
|
+
if (!next || !next.id) return;
|
|
22601
|
+
setRows((prev) => {
|
|
22602
|
+
if (!prev) return [next];
|
|
22603
|
+
const without = prev.filter((r) => r.id !== next.id);
|
|
22604
|
+
return [next, ...without].sort((a, b) => b.created_at.localeCompare(a.created_at));
|
|
22605
|
+
});
|
|
22283
22606
|
}
|
|
22284
|
-
|
|
22285
|
-
void resume();
|
|
22607
|
+
).subscribe();
|
|
22286
22608
|
return () => {
|
|
22287
22609
|
cancelled = true;
|
|
22610
|
+
supabase.removeChannel(channel);
|
|
22288
22611
|
};
|
|
22289
|
-
}, [
|
|
22290
|
-
const
|
|
22291
|
-
|
|
22292
|
-
|
|
22293
|
-
|
|
22294
|
-
|
|
22295
|
-
|
|
22296
|
-
|
|
22297
|
-
|
|
22298
|
-
if (error || showToolbarProgress || showPublishedNotice) {
|
|
22299
|
-
setExpanded(true);
|
|
22300
|
-
}
|
|
22301
|
-
}, [error, showToolbarProgress, showPublishedNotice]);
|
|
22302
|
-
const toggleExpanded = () => {
|
|
22303
|
-
setExpanded((prev) => {
|
|
22304
|
-
const next = !prev;
|
|
22305
|
-
if (!next) setMenuOpen(false);
|
|
22306
|
-
return next;
|
|
22307
|
-
});
|
|
22308
|
-
};
|
|
22309
|
-
useEffect(() => {
|
|
22310
|
-
return () => {
|
|
22311
|
-
if (publishedNoticeTimer.current) clearTimeout(publishedNoticeTimer.current);
|
|
22312
|
-
};
|
|
22313
|
-
}, []);
|
|
22314
|
-
useEffect(() => {
|
|
22315
|
-
if (!activeDeployment || !deploymentRow) return;
|
|
22316
|
-
const { status } = deploymentRow;
|
|
22317
|
-
if (status === "ready") {
|
|
22318
|
-
if (publishDialogOpen) return;
|
|
22319
|
-
setPublishedNotice(true);
|
|
22320
|
-
setActiveDeployment(null);
|
|
22321
|
-
if (publishedNoticeTimer.current) clearTimeout(publishedNoticeTimer.current);
|
|
22322
|
-
publishedNoticeTimer.current = setTimeout(() => setPublishedNotice(false), 6e3);
|
|
22323
|
-
return;
|
|
22324
|
-
}
|
|
22325
|
-
if (publishDialogOpen) return;
|
|
22326
|
-
if (status === "error" || status === "canceled") {
|
|
22327
|
-
setError(
|
|
22328
|
-
status === "canceled" ? "Publisering avbrutt." : deploymentRow.error_message?.includes("webhook") ? "Publisering utløpt (ingen webhook)." : "Publisering feilet."
|
|
22329
|
-
);
|
|
22330
|
-
setActiveDeployment(null);
|
|
22612
|
+
}, [supabase, siteId]);
|
|
22613
|
+
const loadMore = async () => {
|
|
22614
|
+
if (!rows || rows.length === 0) return;
|
|
22615
|
+
setLoadingMore(true);
|
|
22616
|
+
const oldest = rows[rows.length - 1].created_at;
|
|
22617
|
+
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);
|
|
22618
|
+
setLoadingMore(false);
|
|
22619
|
+
if (err) {
|
|
22620
|
+
setError(err.message);
|
|
22331
22621
|
return;
|
|
22332
22622
|
}
|
|
22333
|
-
|
|
22334
|
-
|
|
22335
|
-
|
|
22336
|
-
|
|
22337
|
-
|
|
22338
|
-
|
|
22339
|
-
if (!activeDeployment) return;
|
|
22340
|
-
const syncActiveDeployment = async () => {
|
|
22341
|
-
const { data } = await supabase.from("deployments").select("status").eq("id", activeDeployment).maybeSingle();
|
|
22342
|
-
if (!data) {
|
|
22343
|
-
setActiveDeployment(null);
|
|
22344
|
-
return;
|
|
22345
|
-
}
|
|
22346
|
-
const status = data.status;
|
|
22347
|
-
if (status === "ready") {
|
|
22348
|
-
if (!publishDialogOpen) {
|
|
22349
|
-
setPublishedNotice(true);
|
|
22350
|
-
if (publishedNoticeTimer.current) clearTimeout(publishedNoticeTimer.current);
|
|
22351
|
-
publishedNoticeTimer.current = setTimeout(() => setPublishedNotice(false), 6e3);
|
|
22352
|
-
}
|
|
22353
|
-
setActiveDeployment(null);
|
|
22354
|
-
return;
|
|
22355
|
-
}
|
|
22356
|
-
if (!isDeploymentInProgress(status)) {
|
|
22357
|
-
if (status === "canceled") setError("Publisering avbrutt.");
|
|
22358
|
-
else if (status === "error") setError("Publisering feilet.");
|
|
22359
|
-
setActiveDeployment(null);
|
|
22360
|
-
}
|
|
22361
|
-
};
|
|
22362
|
-
const onVisible = () => {
|
|
22363
|
-
if (document.visibilityState === "visible") void syncActiveDeployment();
|
|
22364
|
-
};
|
|
22365
|
-
window.addEventListener("focus", syncActiveDeployment);
|
|
22366
|
-
document.addEventListener("visibilitychange", onVisible);
|
|
22367
|
-
return () => {
|
|
22368
|
-
window.removeEventListener("focus", syncActiveDeployment);
|
|
22369
|
-
document.removeEventListener("visibilitychange", onVisible);
|
|
22370
|
-
};
|
|
22371
|
-
}, [activeDeployment, publishDialogOpen, supabase]);
|
|
22372
|
-
const languages = i18n.languages?.length ? Array.from(new Set(i18n.languages)) : ["no", "en"];
|
|
22373
|
-
useEffect(() => {
|
|
22374
|
-
if (!menuOpen) return;
|
|
22375
|
-
const close = (e) => {
|
|
22376
|
-
if (menuRef.current && !menuRef.current.contains(e.target)) {
|
|
22377
|
-
setMenuOpen(false);
|
|
22378
|
-
}
|
|
22379
|
-
};
|
|
22380
|
-
document.addEventListener("mousedown", close);
|
|
22381
|
-
return () => document.removeEventListener("mousedown", close);
|
|
22382
|
-
}, [menuOpen]);
|
|
22383
|
-
const publish = async () => {
|
|
22384
|
-
if (!store) return;
|
|
22385
|
-
setPublishing(true);
|
|
22386
|
-
setPublishDialogOpen(true);
|
|
22387
|
-
setError(null);
|
|
22388
|
-
setPublishedNotice(false);
|
|
22623
|
+
const more = data ?? [];
|
|
22624
|
+
setRows((prev) => [...prev ?? [], ...more]);
|
|
22625
|
+
setHasMore(more.length === PAGE);
|
|
22626
|
+
};
|
|
22627
|
+
const remove2 = async (id) => {
|
|
22628
|
+
if (!window.confirm("Slette denne henvendelsen? Dette kan ikke angres.")) return;
|
|
22389
22629
|
try {
|
|
22390
|
-
|
|
22391
|
-
|
|
22392
|
-
|
|
22393
|
-
content: JSON.stringify(data, null, 2) + "\n"
|
|
22394
|
-
}));
|
|
22395
|
-
if (themeStore && themeDraftCount > 0) {
|
|
22396
|
-
files.push({
|
|
22397
|
-
path: pathForTheme(config.siteId, config.themePath),
|
|
22398
|
-
content: JSON.stringify(themeStore.serialise(), null, 2) + "\n"
|
|
22399
|
-
});
|
|
22400
|
-
}
|
|
22401
|
-
if (assetStore && assetDraftCount > 0) {
|
|
22402
|
-
const result2 = await api.publish(config.siteId, files, {
|
|
22403
|
-
assets: assetStore.pendingAssets()
|
|
22404
|
-
});
|
|
22405
|
-
store.commit();
|
|
22406
|
-
themeStore?.commit();
|
|
22407
|
-
assetStore.commit();
|
|
22408
|
-
let deploymentId2 = result2.deploymentId;
|
|
22409
|
-
if (!deploymentId2 && result2.trackingDeferred) {
|
|
22410
|
-
deploymentId2 = await waitForDeploymentByCommit(
|
|
22411
|
-
supabase,
|
|
22412
|
-
config.siteId,
|
|
22413
|
-
result2.commitSha
|
|
22414
|
-
);
|
|
22415
|
-
}
|
|
22416
|
-
if (deploymentId2) {
|
|
22417
|
-
setActiveDeployment(deploymentId2);
|
|
22418
|
-
} else {
|
|
22419
|
-
setPublishDialogOpen(false);
|
|
22420
|
-
setPublishedNotice(true);
|
|
22421
|
-
}
|
|
22422
|
-
return;
|
|
22423
|
-
}
|
|
22424
|
-
const result = await api.publish(config.siteId, files);
|
|
22425
|
-
store.commit();
|
|
22426
|
-
themeStore?.commit();
|
|
22427
|
-
assetStore?.commit();
|
|
22428
|
-
let deploymentId = result.deploymentId;
|
|
22429
|
-
if (!deploymentId && result.trackingDeferred) {
|
|
22430
|
-
deploymentId = await waitForDeploymentByCommit(
|
|
22431
|
-
supabase,
|
|
22432
|
-
config.siteId,
|
|
22433
|
-
result.commitSha
|
|
22434
|
-
);
|
|
22435
|
-
}
|
|
22436
|
-
if (deploymentId) {
|
|
22437
|
-
setActiveDeployment(deploymentId);
|
|
22438
|
-
} else {
|
|
22439
|
-
setPublishDialogOpen(false);
|
|
22440
|
-
setPublishedNotice(true);
|
|
22441
|
-
}
|
|
22630
|
+
await api.forms.deleteSubmission(siteId, id);
|
|
22631
|
+
setRows((prev) => (prev ?? []).filter((r) => r.id !== id));
|
|
22632
|
+
if (openId === id) setOpenId(null);
|
|
22442
22633
|
} catch (err) {
|
|
22443
|
-
setError(err instanceof Error ? err.message : "
|
|
22444
|
-
setPublishDialogOpen(false);
|
|
22445
|
-
} finally {
|
|
22446
|
-
setPublishing(false);
|
|
22634
|
+
setError(err instanceof Error ? err.message : "Kunne ikke slette");
|
|
22447
22635
|
}
|
|
22448
22636
|
};
|
|
22449
|
-
|
|
22450
|
-
|
|
22451
|
-
|
|
22452
|
-
|
|
22453
|
-
|
|
22454
|
-
|
|
22455
|
-
|
|
22456
|
-
|
|
22457
|
-
|
|
22458
|
-
|
|
22459
|
-
|
|
22460
|
-
|
|
22461
|
-
|
|
22462
|
-
|
|
22463
|
-
|
|
22464
|
-
|
|
22465
|
-
|
|
22466
|
-
|
|
22467
|
-
|
|
22468
|
-
|
|
22469
|
-
|
|
22470
|
-
|
|
22471
|
-
|
|
22472
|
-
|
|
22473
|
-
|
|
22474
|
-
|
|
22475
|
-
return /* @__PURE__ */ jsxs(
|
|
22637
|
+
if (!rows) return /* @__PURE__ */ jsx("p", { style: mutedStyle, children: "Laster henvendelser …" });
|
|
22638
|
+
return /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 12 }, children: [
|
|
22639
|
+
/* @__PURE__ */ jsx("h3", { style: subheadingStyle, children: "Henvendelser" }),
|
|
22640
|
+
error ? /* @__PURE__ */ jsx("p", { style: errStyle, children: error }) : null,
|
|
22641
|
+
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(
|
|
22642
|
+
SubmissionItem,
|
|
22643
|
+
{
|
|
22644
|
+
row,
|
|
22645
|
+
open: openId === row.id,
|
|
22646
|
+
onToggle: () => setOpenId(openId === row.id ? null : row.id),
|
|
22647
|
+
onDelete: () => void remove2(row.id)
|
|
22648
|
+
},
|
|
22649
|
+
row.id
|
|
22650
|
+
)) }),
|
|
22651
|
+
hasMore ? /* @__PURE__ */ jsx("button", { type: "button", onClick: () => void loadMore(), disabled: loadingMore, style: secondaryBtnStyle, children: loadingMore ? "Laster …" : "Vis eldre" }) : null
|
|
22652
|
+
] });
|
|
22653
|
+
}
|
|
22654
|
+
function SubmissionItem({
|
|
22655
|
+
row,
|
|
22656
|
+
open,
|
|
22657
|
+
onToggle,
|
|
22658
|
+
onDelete
|
|
22659
|
+
}) {
|
|
22660
|
+
const fields = submissionFields(row);
|
|
22661
|
+
const sender = senderOf(fields);
|
|
22662
|
+
const title = row.meta.title ?? row.form_id;
|
|
22663
|
+
return /* @__PURE__ */ jsxs("li", { children: [
|
|
22476
22664
|
/* @__PURE__ */ jsxs(
|
|
22477
|
-
"
|
|
22665
|
+
"button",
|
|
22478
22666
|
{
|
|
22479
|
-
|
|
22480
|
-
|
|
22481
|
-
|
|
22482
|
-
|
|
22483
|
-
...expanded ? expandedContainerStyle : {}
|
|
22484
|
-
},
|
|
22667
|
+
type: "button",
|
|
22668
|
+
onClick: onToggle,
|
|
22669
|
+
"aria-expanded": open,
|
|
22670
|
+
style: { ...rowStyle, width: "100%", textAlign: "left", color: "inherit", border: "none", cursor: "pointer", font: "inherit" },
|
|
22485
22671
|
children: [
|
|
22486
|
-
/* @__PURE__ */ jsx(
|
|
22487
|
-
|
|
22488
|
-
{
|
|
22489
|
-
|
|
22490
|
-
"
|
|
22491
|
-
|
|
22492
|
-
title: "Setto",
|
|
22493
|
-
style: markButtonStyle,
|
|
22494
|
-
onClick: toggleExpanded,
|
|
22495
|
-
children: /* @__PURE__ */ jsx(SettoMark, { size: 28 })
|
|
22496
|
-
}
|
|
22497
|
-
),
|
|
22498
|
-
expanded ? /* @__PURE__ */ jsxs("div", { style: contentStyle, children: [
|
|
22499
|
-
error ? /* @__PURE__ */ jsx("span", { style: errorStyle, children: error }) : null,
|
|
22500
|
-
hasDrafts && !publishing && activeDeployment === null ? /* @__PURE__ */ jsx("button", { type: "button", onClick: publish, style: publishBtnStyle, children: "Publiser" }) : null,
|
|
22501
|
-
showPublishedNotice ? /* @__PURE__ */ jsxs(
|
|
22502
|
-
"button",
|
|
22503
|
-
{
|
|
22504
|
-
type: "button",
|
|
22505
|
-
onClick: dismissPublishedNotice,
|
|
22506
|
-
style: publishedNoticeStyle,
|
|
22507
|
-
title: "Lukk",
|
|
22508
|
-
children: [
|
|
22509
|
-
/* @__PURE__ */ jsx(PublishedCheckIcon, {}),
|
|
22510
|
-
"Publisert"
|
|
22511
|
-
]
|
|
22512
|
-
}
|
|
22513
|
-
) : null,
|
|
22514
|
-
showToolbarProgress && deploymentRow ? /* @__PURE__ */ jsx(
|
|
22515
|
-
PublishProgressBar,
|
|
22516
|
-
{
|
|
22517
|
-
status: deploymentRow.status,
|
|
22518
|
-
compact: true,
|
|
22519
|
-
href: `${SETTO_BASE}?deployment=${encodeURIComponent(activeDeployment)}`
|
|
22520
|
-
}
|
|
22521
|
-
) : null,
|
|
22522
|
-
!hasDrafts && !error && !showPublishedNotice && !showToolbarProgress && !publishing ? /* @__PURE__ */ jsx("span", { style: emptyStateStyle, children: "Ingen endringer" }) : null,
|
|
22523
|
-
/* @__PURE__ */ jsxs("div", { ref: menuRef, style: { position: "relative" }, children: [
|
|
22524
|
-
/* @__PURE__ */ jsx(
|
|
22525
|
-
"button",
|
|
22526
|
-
{
|
|
22527
|
-
type: "button",
|
|
22528
|
-
onClick: () => setMenuOpen((o) => !o),
|
|
22529
|
-
"aria-label": "Meny",
|
|
22530
|
-
"aria-expanded": menuOpen,
|
|
22531
|
-
style: {
|
|
22532
|
-
...menuBtnStyle,
|
|
22533
|
-
background: menuOpen ? "rgba(242, 235, 223, 0.16)" : "transparent"
|
|
22534
|
-
},
|
|
22535
|
-
onMouseEnter: (e) => {
|
|
22536
|
-
e.currentTarget.style.background = "rgba(242, 235, 223, 0.16)";
|
|
22537
|
-
},
|
|
22538
|
-
onMouseLeave: (e) => {
|
|
22539
|
-
e.currentTarget.style.background = menuOpen ? "rgba(242, 235, 223, 0.16)" : "transparent";
|
|
22540
|
-
},
|
|
22541
|
-
children: "⋮"
|
|
22542
|
-
}
|
|
22543
|
-
),
|
|
22544
|
-
menuOpen ? /* @__PURE__ */ jsxs("div", { style: dropdownStyle, role: "menu", children: [
|
|
22545
|
-
/* @__PURE__ */ jsx(
|
|
22546
|
-
MenuItem,
|
|
22547
|
-
{
|
|
22548
|
-
onClick: cancelDrafts,
|
|
22549
|
-
icon: /* @__PURE__ */ jsx(UndoIcon, {}),
|
|
22550
|
-
disabled: !hasDrafts,
|
|
22551
|
-
children: "Avbryt"
|
|
22552
|
-
}
|
|
22553
|
-
),
|
|
22554
|
-
/* @__PURE__ */ jsx(MenuItem, { onClick: goToHistory, icon: /* @__PURE__ */ jsx(HistoryIcon, {}), children: "Historikk" }),
|
|
22555
|
-
/* @__PURE__ */ jsx(MenuItem, { onClick: signOut, icon: /* @__PURE__ */ jsx(LogOutIcon, {}), children: "Logg ut" })
|
|
22556
|
-
] }) : null
|
|
22672
|
+
/* @__PURE__ */ jsx("span", { style: statusDotStyle(row.email_status), title: emailStatusLabel(row) }),
|
|
22673
|
+
/* @__PURE__ */ jsxs("span", { style: { flex: 1, fontSize: 13, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: [
|
|
22674
|
+
sender ?? /* @__PURE__ */ jsx("span", { style: { color: "#aaa" }, children: "Uten navn" }),
|
|
22675
|
+
/* @__PURE__ */ jsxs("span", { style: { color: "#888" }, children: [
|
|
22676
|
+
" · ",
|
|
22677
|
+
title
|
|
22557
22678
|
] })
|
|
22558
|
-
] })
|
|
22679
|
+
] }),
|
|
22680
|
+
/* @__PURE__ */ jsx("time", { style: { fontSize: 11, color: "#888", flexShrink: 0 }, children: formatWhen(row.created_at) })
|
|
22559
22681
|
]
|
|
22560
22682
|
}
|
|
22561
22683
|
),
|
|
22562
|
-
/* @__PURE__ */
|
|
22563
|
-
|
|
22564
|
-
|
|
22565
|
-
|
|
22566
|
-
|
|
22567
|
-
|
|
22568
|
-
|
|
22569
|
-
|
|
22570
|
-
|
|
22571
|
-
|
|
22572
|
-
|
|
22573
|
-
|
|
22684
|
+
open ? /* @__PURE__ */ jsxs("div", { style: { ...panelStyle, marginTop: 4, gap: 12 }, children: [
|
|
22685
|
+
/* @__PURE__ */ jsx("dl", { style: { margin: 0, display: "grid", gap: 10 }, children: fields.map((f) => /* @__PURE__ */ jsxs("div", { children: [
|
|
22686
|
+
/* @__PURE__ */ jsx("dt", { style: { fontSize: 11, color: "#888", textTransform: "uppercase", letterSpacing: "0.04em" }, children: f.label }),
|
|
22687
|
+
/* @__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 })
|
|
22688
|
+
] }, f.name)) }),
|
|
22689
|
+
/* @__PURE__ */ jsxs("p", { style: mutedStyle, children: [
|
|
22690
|
+
emailStatusLabel(row),
|
|
22691
|
+
row.recipients.length > 0 ? ` til ${row.recipients.join(", ")}` : "",
|
|
22692
|
+
row.email_error ? ` — ${row.email_error}` : "",
|
|
22693
|
+
row.meta.page ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
22694
|
+
" · ",
|
|
22695
|
+
/* @__PURE__ */ jsx("a", { href: row.meta.page, style: { color: "#bda6ff" }, target: "_blank", rel: "noreferrer", children: "Sendt fra siden" })
|
|
22696
|
+
] }) : null
|
|
22697
|
+
] }),
|
|
22698
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: onDelete, style: dangerBtnStyle, children: "Slett henvendelse" })
|
|
22699
|
+
] }) : null
|
|
22574
22700
|
] });
|
|
22575
22701
|
}
|
|
22576
|
-
|
|
22577
|
-
const
|
|
22578
|
-
|
|
22579
|
-
|
|
22580
|
-
|
|
22581
|
-
|
|
22582
|
-
|
|
22583
|
-
|
|
22584
|
-
|
|
22585
|
-
|
|
22586
|
-
|
|
22587
|
-
|
|
22588
|
-
|
|
22589
|
-
|
|
22590
|
-
|
|
22591
|
-
|
|
22592
|
-
|
|
22593
|
-
|
|
22594
|
-
}
|
|
22595
|
-
|
|
22596
|
-
|
|
22597
|
-
|
|
22598
|
-
};
|
|
22599
|
-
const markButtonStyle = {
|
|
22600
|
-
display: "inline-flex",
|
|
22601
|
-
alignItems: "center",
|
|
22602
|
-
justifyContent: "center",
|
|
22603
|
-
flexShrink: 0,
|
|
22604
|
-
width: FAB_SIZE,
|
|
22605
|
-
height: FAB_SIZE,
|
|
22606
|
-
background: "transparent",
|
|
22607
|
-
border: "none",
|
|
22608
|
-
padding: 0,
|
|
22609
|
-
cursor: "pointer",
|
|
22610
|
-
color: "inherit"
|
|
22611
|
-
};
|
|
22612
|
-
const contentStyle = {
|
|
22613
|
-
display: "flex",
|
|
22614
|
-
alignItems: "center",
|
|
22615
|
-
gap: 6,
|
|
22616
|
-
paddingLeft: 4,
|
|
22617
|
-
whiteSpace: "nowrap"
|
|
22618
|
-
};
|
|
22619
|
-
const errorStyle = {
|
|
22620
|
-
color: "#ffb4a8",
|
|
22621
|
-
fontSize: 12,
|
|
22622
|
-
maxWidth: 240,
|
|
22623
|
-
overflow: "hidden",
|
|
22624
|
-
textOverflow: "ellipsis"
|
|
22625
|
-
};
|
|
22626
|
-
const publishedNoticeStyle = {
|
|
22627
|
-
display: "inline-flex",
|
|
22628
|
-
alignItems: "center",
|
|
22629
|
-
gap: 5,
|
|
22630
|
-
padding: "4px 10px",
|
|
22631
|
-
borderRadius: 999,
|
|
22632
|
-
border: "1px solid rgba(242, 235, 223, 0.25)",
|
|
22633
|
-
background: "rgba(242, 235, 223, 0.08)",
|
|
22634
|
-
color: CREMA,
|
|
22635
|
-
fontSize: 12,
|
|
22636
|
-
fontWeight: 500,
|
|
22637
|
-
cursor: "pointer",
|
|
22638
|
-
whiteSpace: "nowrap"
|
|
22639
|
-
};
|
|
22640
|
-
const publishBtnStyle = {
|
|
22641
|
-
background: TERRACOTTA,
|
|
22642
|
-
color: CREMA,
|
|
22643
|
-
border: "none",
|
|
22644
|
-
borderRadius: 999,
|
|
22645
|
-
padding: "6px 14px",
|
|
22646
|
-
fontSize: 13,
|
|
22647
|
-
fontWeight: 600,
|
|
22648
|
-
cursor: "pointer",
|
|
22649
|
-
whiteSpace: "nowrap"
|
|
22650
|
-
};
|
|
22651
|
-
const menuBtnStyle = {
|
|
22652
|
-
background: "transparent",
|
|
22653
|
-
color: CREMA,
|
|
22654
|
-
border: "none",
|
|
22655
|
-
borderRadius: 999,
|
|
22656
|
-
width: 32,
|
|
22657
|
-
height: 32,
|
|
22658
|
-
display: "inline-flex",
|
|
22659
|
-
alignItems: "center",
|
|
22660
|
-
justifyContent: "center",
|
|
22661
|
-
fontSize: 22,
|
|
22662
|
-
lineHeight: 1,
|
|
22663
|
-
cursor: "pointer",
|
|
22664
|
-
fontWeight: 700
|
|
22665
|
-
};
|
|
22666
|
-
const emptyStateStyle = {
|
|
22667
|
-
color: "rgba(242, 235, 223, 0.55)",
|
|
22668
|
-
fontSize: 13,
|
|
22669
|
-
whiteSpace: "nowrap",
|
|
22670
|
-
padding: "0 4px"
|
|
22671
|
-
};
|
|
22672
|
-
const dropdownStyle = {
|
|
22673
|
-
position: "absolute",
|
|
22674
|
-
bottom: "100%",
|
|
22675
|
-
left: 0,
|
|
22676
|
-
marginBottom: 8,
|
|
22677
|
-
background: "#fff",
|
|
22678
|
-
border: "1px solid #e8e8e8",
|
|
22679
|
-
borderRadius: 10,
|
|
22680
|
-
boxShadow: "0 12px 32px rgba(33, 23, 17, 0.18)",
|
|
22681
|
-
minWidth: 160,
|
|
22682
|
-
overflow: "hidden",
|
|
22683
|
-
zIndex: 1
|
|
22684
|
-
};
|
|
22685
|
-
const menuItemStyle = {
|
|
22686
|
-
display: "flex",
|
|
22687
|
-
alignItems: "center",
|
|
22688
|
-
gap: 8,
|
|
22689
|
-
width: "100%",
|
|
22690
|
-
background: "transparent",
|
|
22691
|
-
border: "none",
|
|
22692
|
-
padding: "10px 14px",
|
|
22693
|
-
fontSize: 13,
|
|
22694
|
-
textAlign: "left",
|
|
22695
|
-
cursor: "pointer",
|
|
22696
|
-
color: "#1a1a1a"
|
|
22697
|
-
};
|
|
22698
|
-
function MenuItem({
|
|
22699
|
-
onClick,
|
|
22700
|
-
icon,
|
|
22701
|
-
children,
|
|
22702
|
-
disabled = false
|
|
22703
|
-
}) {
|
|
22704
|
-
return /* @__PURE__ */ jsxs(
|
|
22705
|
-
"button",
|
|
22706
|
-
{
|
|
22707
|
-
type: "button",
|
|
22708
|
-
onClick,
|
|
22709
|
-
disabled,
|
|
22710
|
-
style: { ...menuItemStyle, opacity: disabled ? 0.4 : 1, cursor: disabled ? "not-allowed" : "pointer" },
|
|
22711
|
-
role: "menuitem",
|
|
22712
|
-
onMouseEnter: (e) => {
|
|
22713
|
-
if (!disabled) e.currentTarget.style.background = "#f5f5f5";
|
|
22714
|
-
},
|
|
22715
|
-
onMouseLeave: (e) => {
|
|
22716
|
-
e.currentTarget.style.background = "transparent";
|
|
22717
|
-
},
|
|
22718
|
-
children: [
|
|
22719
|
-
/* @__PURE__ */ jsx("span", { style: menuIconWrapStyle, "aria-hidden": true, children: icon }),
|
|
22720
|
-
children
|
|
22721
|
-
]
|
|
22702
|
+
function submissionFields(row) {
|
|
22703
|
+
const flat = [];
|
|
22704
|
+
flatten(row.data, "", flat);
|
|
22705
|
+
const rank = new Map((row.field_order ?? []).map((n, i) => [n, i]));
|
|
22706
|
+
const rankOf = (name) => rank.get(name) ?? rank.get(name.split(".")[0]) ?? Number.MAX_SAFE_INTEGER;
|
|
22707
|
+
return flat.map((f, i) => ({ f, i })).sort((a, b) => rankOf(a.f.name) - rankOf(b.f.name) || a.i - b.i).map(({ f }) => ({
|
|
22708
|
+
name: f.name,
|
|
22709
|
+
label: row.labels?.[f.name] ?? row.labels?.[f.name.split(".")[0]] ?? prettify(f.name),
|
|
22710
|
+
value: f.value
|
|
22711
|
+
}));
|
|
22712
|
+
}
|
|
22713
|
+
function flatten(value, path, out) {
|
|
22714
|
+
if (value === null || value === void 0) {
|
|
22715
|
+
out.push({ name: path, value: "" });
|
|
22716
|
+
} else if (typeof value === "boolean") {
|
|
22717
|
+
out.push({ name: path, value: value ? "Ja" : "Nei" });
|
|
22718
|
+
} else if (typeof value === "string" || typeof value === "number") {
|
|
22719
|
+
out.push({ name: path, value: String(value) });
|
|
22720
|
+
} else if (Array.isArray(value)) {
|
|
22721
|
+
if (value.every((v) => typeof v !== "object" || v === null)) {
|
|
22722
|
+
out.push({ name: path, value: value.map((v) => v == null ? "" : String(v)).filter(Boolean).join(", ") });
|
|
22723
|
+
} else {
|
|
22724
|
+
value.forEach((item, i) => flatten(item, `${path}.${i + 1}`, out));
|
|
22722
22725
|
}
|
|
22723
|
-
)
|
|
22724
|
-
|
|
22725
|
-
|
|
22726
|
-
display: "flex",
|
|
22727
|
-
alignItems: "center",
|
|
22728
|
-
justifyContent: "center",
|
|
22729
|
-
flexShrink: 0,
|
|
22730
|
-
color: "#666"
|
|
22731
|
-
};
|
|
22732
|
-
function PublishedCheckIcon() {
|
|
22733
|
-
return /* @__PURE__ */ jsx(
|
|
22734
|
-
"svg",
|
|
22735
|
-
{
|
|
22736
|
-
width: "14",
|
|
22737
|
-
height: "14",
|
|
22738
|
-
viewBox: "0 0 24 24",
|
|
22739
|
-
fill: "none",
|
|
22740
|
-
stroke: "currentColor",
|
|
22741
|
-
strokeWidth: "2.5",
|
|
22742
|
-
strokeLinecap: "round",
|
|
22743
|
-
strokeLinejoin: "round",
|
|
22744
|
-
"aria-hidden": true,
|
|
22745
|
-
children: /* @__PURE__ */ jsx("polyline", { points: "20 6 9 17 4 12" })
|
|
22726
|
+
} else if (typeof value === "object") {
|
|
22727
|
+
for (const [k, v] of Object.entries(value)) {
|
|
22728
|
+
flatten(v, path ? `${path}.${k}` : k, out);
|
|
22746
22729
|
}
|
|
22747
|
-
|
|
22730
|
+
}
|
|
22748
22731
|
}
|
|
22749
|
-
function
|
|
22750
|
-
|
|
22751
|
-
|
|
22752
|
-
|
|
22753
|
-
] });
|
|
22732
|
+
function prettify(name) {
|
|
22733
|
+
const leaf = name.split(".").pop() ?? name;
|
|
22734
|
+
const words = leaf.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_\-.]+/g, " ").trim().toLowerCase();
|
|
22735
|
+
return words ? words[0].toUpperCase() + words.slice(1) : name;
|
|
22754
22736
|
}
|
|
22755
|
-
|
|
22756
|
-
|
|
22757
|
-
|
|
22758
|
-
|
|
22759
|
-
|
|
22760
|
-
|
|
22737
|
+
const EMAIL_RE$1 = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/;
|
|
22738
|
+
function senderOf(fields) {
|
|
22739
|
+
const named = fields.find(
|
|
22740
|
+
(f) => /(^|[._-])(navn|name|fullname|full_name|fornavn|firstname|first_name)$/i.test(f.name) && f.value && !EMAIL_RE$1.test(f.value)
|
|
22741
|
+
);
|
|
22742
|
+
if (named) return named.value.slice(0, 80);
|
|
22743
|
+
const email = fields.find((f) => EMAIL_RE$1.test(f.value));
|
|
22744
|
+
if (email) return email.value;
|
|
22745
|
+
const first = fields.find((f) => f.value !== "");
|
|
22746
|
+
return first ? first.value.slice(0, 80) : null;
|
|
22747
|
+
}
|
|
22748
|
+
function emailStatusLabel(row) {
|
|
22749
|
+
switch (row.email_status) {
|
|
22750
|
+
case "sent":
|
|
22751
|
+
return "E-post sendt";
|
|
22752
|
+
case "failed":
|
|
22753
|
+
return "E-post feilet";
|
|
22754
|
+
case "skipped":
|
|
22755
|
+
return "Ingen mottakere";
|
|
22756
|
+
case "pending":
|
|
22757
|
+
return "Sender e-post";
|
|
22758
|
+
}
|
|
22761
22759
|
}
|
|
22762
|
-
function
|
|
22763
|
-
|
|
22764
|
-
|
|
22765
|
-
|
|
22766
|
-
|
|
22760
|
+
function formatWhen(iso) {
|
|
22761
|
+
const d = new Date(iso);
|
|
22762
|
+
const now = /* @__PURE__ */ new Date();
|
|
22763
|
+
const sameDay = d.toDateString() === now.toDateString();
|
|
22764
|
+
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" });
|
|
22767
22765
|
}
|
|
22768
|
-
function
|
|
22769
|
-
|
|
22770
|
-
|
|
22771
|
-
const [hintRoot, setHintRoot] = useState(null);
|
|
22772
|
-
const [sectionToolbarRoot, setSectionToolbarRoot] = useState(null);
|
|
22773
|
-
useEffect(() => {
|
|
22774
|
-
const toolbar = document.createElement("div");
|
|
22775
|
-
toolbar.id = "setto-toolbar-root";
|
|
22776
|
-
document.body.appendChild(toolbar);
|
|
22777
|
-
const hint = document.createElement("div");
|
|
22778
|
-
hint.id = "setto-hint-root";
|
|
22779
|
-
hint.style.pointerEvents = "none";
|
|
22780
|
-
document.body.appendChild(hint);
|
|
22781
|
-
const sectionBar = document.createElement("div");
|
|
22782
|
-
sectionBar.id = "setto-section-toolbar-root";
|
|
22783
|
-
sectionBar.style.pointerEvents = "none";
|
|
22784
|
-
document.body.appendChild(sectionBar);
|
|
22785
|
-
setToolbarRoot(toolbar);
|
|
22786
|
-
setHintRoot(hint);
|
|
22787
|
-
setSectionToolbarRoot(sectionBar);
|
|
22788
|
-
return () => {
|
|
22789
|
-
toolbar.remove();
|
|
22790
|
-
hint.remove();
|
|
22791
|
-
sectionBar.remove();
|
|
22792
|
-
};
|
|
22793
|
-
}, []);
|
|
22794
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
22795
|
-
children,
|
|
22796
|
-
toolbarRoot ? createPortal(/* @__PURE__ */ jsx(EditToolbar, {}), toolbarRoot) : null,
|
|
22797
|
-
hintRoot ? createPortal(/* @__PURE__ */ jsx(EditLinkHint, {}), hintRoot) : null,
|
|
22798
|
-
sectionToolbarRoot && themeStore ? createPortal(/* @__PURE__ */ jsx(SectionToolbar, {}), sectionToolbarRoot) : null
|
|
22799
|
-
] });
|
|
22800
|
-
}
|
|
22801
|
-
const SettoContext = createContext(null);
|
|
22802
|
-
function SettoProvider({ config, children }) {
|
|
22803
|
-
const supabase = useMemo(() => getSupabase(config.supabase), [config.supabase]);
|
|
22804
|
-
const api = useMemo(() => createApi({ apiUrl: config.apiUrl, supabase }), [config.apiUrl, supabase]);
|
|
22805
|
-
const [session, setSession] = useState(null);
|
|
22806
|
-
const [authLoading, setAuthLoading] = useState(true);
|
|
22807
|
-
const [onAdminRoute, setOnAdminRoute] = useState(
|
|
22808
|
-
() => typeof window === "undefined" ? false : isAdminRoute()
|
|
22809
|
-
);
|
|
22810
|
-
useEffect(() => {
|
|
22811
|
-
let cancelled = false;
|
|
22812
|
-
supabase.auth.getSession().then(({ data }) => {
|
|
22813
|
-
if (cancelled) return;
|
|
22814
|
-
setSession(data.session ?? null);
|
|
22815
|
-
setAuthLoading(false);
|
|
22816
|
-
});
|
|
22817
|
-
const { data: sub } = supabase.auth.onAuthStateChange((_event, session2) => {
|
|
22818
|
-
setSession(session2);
|
|
22819
|
-
});
|
|
22820
|
-
return () => {
|
|
22821
|
-
cancelled = true;
|
|
22822
|
-
sub.subscription.unsubscribe();
|
|
22823
|
-
};
|
|
22824
|
-
}, [supabase]);
|
|
22825
|
-
useEffect(() => {
|
|
22826
|
-
const compute = () => setOnAdminRoute(isAdminRoute());
|
|
22827
|
-
compute();
|
|
22828
|
-
window.addEventListener("popstate", compute);
|
|
22829
|
-
return () => window.removeEventListener("popstate", compute);
|
|
22830
|
-
}, []);
|
|
22831
|
-
const access = useSiteAccess(supabase, session, config.siteId);
|
|
22832
|
-
const { i18n } = useTranslation();
|
|
22833
|
-
const storeRef = useRef(null);
|
|
22834
|
-
const themeStoreRef = useRef(null);
|
|
22835
|
-
const assetStoreRef = useRef(null);
|
|
22836
|
-
const [storeReady, setStoreReady] = useState(false);
|
|
22837
|
-
useEffect(() => {
|
|
22838
|
-
if (!i18n) return;
|
|
22839
|
-
storeRef.current = new I18nStore(i18n);
|
|
22840
|
-
themeStoreRef.current = new ThemeStore(config.theme ?? {});
|
|
22841
|
-
assetStoreRef.current = new AssetStore();
|
|
22842
|
-
setStoreReady(true);
|
|
22843
|
-
}, [i18n, config.theme]);
|
|
22844
|
-
const editMode = !!session && !!access.currentSite && !onAdminRoute && !authLoading && !access.loading;
|
|
22845
|
-
const value = {
|
|
22846
|
-
config,
|
|
22847
|
-
supabase,
|
|
22848
|
-
api,
|
|
22849
|
-
session,
|
|
22850
|
-
authLoading,
|
|
22851
|
-
editMode,
|
|
22852
|
-
currentSite: access.currentSite,
|
|
22853
|
-
sites: access.sites,
|
|
22854
|
-
store: storeReady ? storeRef.current : null,
|
|
22855
|
-
themeStore: storeReady ? themeStoreRef.current : null,
|
|
22856
|
-
assetStore: storeReady ? assetStoreRef.current : null
|
|
22857
|
-
};
|
|
22858
|
-
return /* @__PURE__ */ jsx(SettoContext.Provider, { value, children: /* @__PURE__ */ jsx(SectionEditProvider, { children: editMode && value.store ? /* @__PURE__ */ jsx(EditModeShell, { themeStore: value.themeStore, children }) : children }) });
|
|
22859
|
-
}
|
|
22860
|
-
function useSetto() {
|
|
22861
|
-
const ctx = useContext(SettoContext);
|
|
22862
|
-
if (!ctx) {
|
|
22863
|
-
throw new Error("useSetto() must be used inside <SettoProvider>");
|
|
22864
|
-
}
|
|
22865
|
-
return ctx;
|
|
22866
|
-
}
|
|
22867
|
-
const GuestEditContext = createContext(null);
|
|
22868
|
-
function GuestEditProvider({ children }) {
|
|
22869
|
-
const { editMode, themeStore } = useSetto();
|
|
22870
|
-
const { selectSection } = useSectionEdit();
|
|
22871
|
-
const [active, setActive] = useState(false);
|
|
22872
|
-
useEffect(() => {
|
|
22873
|
-
if (editMode && active) setActive(false);
|
|
22874
|
-
}, [editMode, active]);
|
|
22875
|
-
useEffect(() => {
|
|
22876
|
-
document.documentElement.classList.toggle("setto-guest-editing", active);
|
|
22877
|
-
return () => document.documentElement.classList.remove("setto-guest-editing");
|
|
22878
|
-
}, [active]);
|
|
22879
|
-
const start = useCallback(() => setActive(true), []);
|
|
22880
|
-
const stop = useCallback(() => {
|
|
22881
|
-
setActive(false);
|
|
22882
|
-
selectSection(null);
|
|
22883
|
-
}, [selectSection]);
|
|
22884
|
-
return /* @__PURE__ */ jsxs(GuestEditContext.Provider, { value: { active, start, stop }, children: [
|
|
22885
|
-
children,
|
|
22886
|
-
active && themeStore ? createPortal(/* @__PURE__ */ jsx(SectionToolbar, {}), document.body) : null
|
|
22887
|
-
] });
|
|
22888
|
-
}
|
|
22889
|
-
function useGuestEdit() {
|
|
22890
|
-
const ctx = useContext(GuestEditContext);
|
|
22891
|
-
if (!ctx) {
|
|
22892
|
-
return { active: false, start: () => {
|
|
22893
|
-
}, stop: () => {
|
|
22894
|
-
} };
|
|
22895
|
-
}
|
|
22896
|
-
return ctx;
|
|
22766
|
+
function statusDotStyle(status) {
|
|
22767
|
+
const colour = status === "sent" ? "#34d399" : status === "failed" ? "#ff7a7a" : status === "skipped" ? "#fbbf24" : "#888";
|
|
22768
|
+
return { width: 8, height: 8, borderRadius: "50%", background: colour, flexShrink: 0 };
|
|
22897
22769
|
}
|
|
22898
22770
|
const MARGIN = 10;
|
|
22899
22771
|
const OFFSET = 8;
|
|
@@ -23009,6 +22881,37 @@ function rangeFromPoint(doc, x, y) {
|
|
|
23009
22881
|
range.collapse(true);
|
|
23010
22882
|
return range;
|
|
23011
22883
|
}
|
|
22884
|
+
const GuestEditContext = createContext(null);
|
|
22885
|
+
function GuestEditProvider({ children }) {
|
|
22886
|
+
const { editMode, themeStore } = useSetto();
|
|
22887
|
+
const { selectSection } = useSectionEdit();
|
|
22888
|
+
const [active, setActive] = useState(false);
|
|
22889
|
+
useEffect(() => {
|
|
22890
|
+
if (editMode && active) setActive(false);
|
|
22891
|
+
}, [editMode, active]);
|
|
22892
|
+
useEffect(() => {
|
|
22893
|
+
document.documentElement.classList.toggle("setto-guest-editing", active);
|
|
22894
|
+
return () => document.documentElement.classList.remove("setto-guest-editing");
|
|
22895
|
+
}, [active]);
|
|
22896
|
+
const start = useCallback(() => setActive(true), []);
|
|
22897
|
+
const stop = useCallback(() => {
|
|
22898
|
+
setActive(false);
|
|
22899
|
+
selectSection(null);
|
|
22900
|
+
}, [selectSection]);
|
|
22901
|
+
return /* @__PURE__ */ jsxs(GuestEditContext.Provider, { value: { active, start, stop }, children: [
|
|
22902
|
+
children,
|
|
22903
|
+
active && themeStore ? createPortal(/* @__PURE__ */ jsx(SectionToolbar, {}), document.body) : null
|
|
22904
|
+
] });
|
|
22905
|
+
}
|
|
22906
|
+
function useGuestEdit() {
|
|
22907
|
+
const ctx = useContext(GuestEditContext);
|
|
22908
|
+
if (!ctx) {
|
|
22909
|
+
return { active: false, start: () => {
|
|
22910
|
+
}, stop: () => {
|
|
22911
|
+
} };
|
|
22912
|
+
}
|
|
22913
|
+
return ctx;
|
|
22914
|
+
}
|
|
23012
22915
|
function T({ k }) {
|
|
23013
22916
|
const { t, i18n } = useTranslation();
|
|
23014
22917
|
const { editMode, store } = useSetto();
|
|
@@ -23125,11 +23028,1858 @@ function T({ k }) {
|
|
|
23125
23028
|
showLinkChip ? /* @__PURE__ */ jsx(LinkActionChip, { anchorRef: ref, visible: focused }) : null
|
|
23126
23029
|
] });
|
|
23127
23030
|
}
|
|
23128
|
-
function toBcp47(code) {
|
|
23129
|
-
const base = (code.split("-")[0] ?? code).toLowerCase();
|
|
23130
|
-
if (base === "no" || base === "nb" || base === "nn") return "nb-NO";
|
|
23131
|
-
if (base === "en") return "en";
|
|
23132
|
-
return code;
|
|
23031
|
+
function toBcp47(code) {
|
|
23032
|
+
const base = (code.split("-")[0] ?? code).toLowerCase();
|
|
23033
|
+
if (base === "no" || base === "nb" || base === "nn") return "nb-NO";
|
|
23034
|
+
if (base === "en") return "en";
|
|
23035
|
+
return code;
|
|
23036
|
+
}
|
|
23037
|
+
const FORM_FIELD_TYPES = [
|
|
23038
|
+
"text",
|
|
23039
|
+
"email",
|
|
23040
|
+
"tel",
|
|
23041
|
+
"number",
|
|
23042
|
+
"url",
|
|
23043
|
+
"date",
|
|
23044
|
+
"textarea",
|
|
23045
|
+
"select",
|
|
23046
|
+
"radio",
|
|
23047
|
+
"checkbox",
|
|
23048
|
+
"checkboxes"
|
|
23049
|
+
];
|
|
23050
|
+
const FORM_FIELD_TYPE_LABELS = {
|
|
23051
|
+
text: "Kort tekst",
|
|
23052
|
+
email: "E-post",
|
|
23053
|
+
tel: "Telefon",
|
|
23054
|
+
number: "Tall",
|
|
23055
|
+
url: "Nettadresse",
|
|
23056
|
+
date: "Dato",
|
|
23057
|
+
textarea: "Lang tekst",
|
|
23058
|
+
select: "Nedtrekksliste",
|
|
23059
|
+
radio: "Ett valg",
|
|
23060
|
+
checkbox: "Avkrysning",
|
|
23061
|
+
checkboxes: "Flere valg"
|
|
23062
|
+
};
|
|
23063
|
+
function isFormFieldType(value) {
|
|
23064
|
+
return typeof value === "string" && FORM_FIELD_TYPES.includes(value);
|
|
23065
|
+
}
|
|
23066
|
+
function fieldTypeHasOptions(type) {
|
|
23067
|
+
return type === "select" || type === "radio" || type === "checkboxes";
|
|
23068
|
+
}
|
|
23069
|
+
function fieldTypeHasPlaceholder(type) {
|
|
23070
|
+
return !fieldTypeHasOptions(type) && type !== "checkbox" && type !== "date";
|
|
23071
|
+
}
|
|
23072
|
+
function formFieldsPrefix(formId) {
|
|
23073
|
+
return `form.${formId}.fields`;
|
|
23074
|
+
}
|
|
23075
|
+
function parseOptions(value) {
|
|
23076
|
+
if (!value) return [];
|
|
23077
|
+
return value.split("\n").map((o) => o.trim()).filter((o) => o !== "");
|
|
23078
|
+
}
|
|
23079
|
+
function readString(item, prop) {
|
|
23080
|
+
const value = item[prop];
|
|
23081
|
+
return typeof value === "string" ? value : "";
|
|
23082
|
+
}
|
|
23083
|
+
function readLocaleFields(i18n, lng, formId, ns = "translation") {
|
|
23084
|
+
const prefix = formFieldsPrefix(formId);
|
|
23085
|
+
const candidates = [lng, ...i18n.languages ?? []].filter((l, i, all) => all.indexOf(l) === i);
|
|
23086
|
+
let bundle;
|
|
23087
|
+
let list;
|
|
23088
|
+
for (const candidate of candidates) {
|
|
23089
|
+
const b = i18n.getResourceBundle(candidate, ns);
|
|
23090
|
+
const l = getNested(b, prefix);
|
|
23091
|
+
if (l && typeof l === "object" && !Array.isArray(l)) {
|
|
23092
|
+
bundle = b;
|
|
23093
|
+
list = l;
|
|
23094
|
+
break;
|
|
23095
|
+
}
|
|
23096
|
+
}
|
|
23097
|
+
if (!list) return null;
|
|
23098
|
+
const fields = [];
|
|
23099
|
+
for (const itemKey of collectKeysUnder(bundle, prefix)) {
|
|
23100
|
+
const item = list[itemKey];
|
|
23101
|
+
if (!item || typeof item !== "object") continue;
|
|
23102
|
+
const record = item;
|
|
23103
|
+
const name = readString(record, "name").trim();
|
|
23104
|
+
if (!name) continue;
|
|
23105
|
+
const type = isFormFieldType(record.type) ? record.type : "text";
|
|
23106
|
+
fields.push({
|
|
23107
|
+
itemKey,
|
|
23108
|
+
name,
|
|
23109
|
+
type,
|
|
23110
|
+
labelKey: `${prefix}.${itemKey}.label`,
|
|
23111
|
+
label: readString(record, "label"),
|
|
23112
|
+
placeholder: readString(record, "placeholder"),
|
|
23113
|
+
required: readString(record, "required") === "true",
|
|
23114
|
+
options: parseOptions(readString(record, "options"))
|
|
23115
|
+
});
|
|
23116
|
+
}
|
|
23117
|
+
return fields;
|
|
23118
|
+
}
|
|
23119
|
+
function resolvePropFields(fields, t) {
|
|
23120
|
+
return fields.map((field) => ({
|
|
23121
|
+
name: field.name,
|
|
23122
|
+
type: isFormFieldType(field.type) ? field.type : "text",
|
|
23123
|
+
labelKey: field.labelKey,
|
|
23124
|
+
label: t(field.labelKey, { defaultValue: "" }),
|
|
23125
|
+
placeholder: field.placeholderKey ? t(field.placeholderKey, { defaultValue: "" }) : "",
|
|
23126
|
+
required: field.required === true,
|
|
23127
|
+
options: field.optionsKey ? parseOptions(t(field.optionsKey, { defaultValue: "" })) : []
|
|
23128
|
+
}));
|
|
23129
|
+
}
|
|
23130
|
+
function slugifyFieldName(label) {
|
|
23131
|
+
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);
|
|
23132
|
+
return slug || "felt";
|
|
23133
|
+
}
|
|
23134
|
+
function uniqueFieldName(base, taken) {
|
|
23135
|
+
const used = new Set(taken);
|
|
23136
|
+
if (!used.has(base)) return base;
|
|
23137
|
+
let n = 2;
|
|
23138
|
+
while (used.has(`${base}-${n}`)) n += 1;
|
|
23139
|
+
return `${base}-${n}`;
|
|
23140
|
+
}
|
|
23141
|
+
function emptyValue(type) {
|
|
23142
|
+
if (type === "checkbox") return false;
|
|
23143
|
+
if (type === "checkboxes") return [];
|
|
23144
|
+
return "";
|
|
23145
|
+
}
|
|
23146
|
+
function isBlank(value) {
|
|
23147
|
+
if (value === void 0) return true;
|
|
23148
|
+
if (typeof value === "string") return value.trim() === "";
|
|
23149
|
+
if (typeof value === "boolean") return !value;
|
|
23150
|
+
return value.length === 0;
|
|
23151
|
+
}
|
|
23152
|
+
const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/;
|
|
23153
|
+
const URL_RE = /^(https?:\/\/)?[^\s/$.?#].[^\s]*$/i;
|
|
23154
|
+
const FIELD_ERROR_DEFAULTS = {
|
|
23155
|
+
required: "Må fylles ut",
|
|
23156
|
+
email: "Skriv inn en gyldig e-postadresse",
|
|
23157
|
+
url: "Skriv inn en gyldig nettadresse",
|
|
23158
|
+
number: "Skriv inn et tall"
|
|
23159
|
+
};
|
|
23160
|
+
function validateValues(fields, values) {
|
|
23161
|
+
const errors = {};
|
|
23162
|
+
for (const field of fields) {
|
|
23163
|
+
const value = values[field.name];
|
|
23164
|
+
if (field.required && isBlank(value)) {
|
|
23165
|
+
errors[field.name] = "required";
|
|
23166
|
+
continue;
|
|
23167
|
+
}
|
|
23168
|
+
if (typeof value !== "string" || value.trim() === "") continue;
|
|
23169
|
+
if (field.type === "email" && !EMAIL_RE.test(value.trim())) errors[field.name] = "email";
|
|
23170
|
+
if (field.type === "url" && !URL_RE.test(value.trim())) errors[field.name] = "url";
|
|
23171
|
+
if (field.type === "number" && Number.isNaN(Number(value.trim()))) errors[field.name] = "number";
|
|
23172
|
+
}
|
|
23173
|
+
return errors;
|
|
23174
|
+
}
|
|
23175
|
+
function buildEnvelope(fields, values, extra) {
|
|
23176
|
+
const data = {};
|
|
23177
|
+
const labels = {};
|
|
23178
|
+
for (const field of fields) {
|
|
23179
|
+
const value = values[field.name] ?? emptyValue(field.type);
|
|
23180
|
+
data[field.name] = typeof value === "string" ? value.trim() : value;
|
|
23181
|
+
if (field.label.trim()) labels[field.name] = field.label.trim();
|
|
23182
|
+
}
|
|
23183
|
+
return {
|
|
23184
|
+
data,
|
|
23185
|
+
labels,
|
|
23186
|
+
order: fields.map((f) => f.name),
|
|
23187
|
+
...extra.title ? { title: extra.title } : {},
|
|
23188
|
+
...extra.page ? { page: extra.page } : {},
|
|
23189
|
+
hp: extra.hp,
|
|
23190
|
+
elapsed: Math.max(0, Math.round(extra.elapsed))
|
|
23191
|
+
};
|
|
23192
|
+
}
|
|
23193
|
+
function settoSubmissionUrl(apiUrl, siteId, formId) {
|
|
23194
|
+
return `${apiUrl.replace(/\/+$/, "")}/sites/${encodeURIComponent(siteId)}/forms/${encodeURIComponent(formId)}/submissions`;
|
|
23195
|
+
}
|
|
23196
|
+
function FloatingPopover({
|
|
23197
|
+
open,
|
|
23198
|
+
anchorRef,
|
|
23199
|
+
onClose,
|
|
23200
|
+
ariaLabel,
|
|
23201
|
+
children,
|
|
23202
|
+
style,
|
|
23203
|
+
minWidth = 280
|
|
23204
|
+
}) {
|
|
23205
|
+
const popoverRef = useRef(null);
|
|
23206
|
+
const [pos, setPos] = useState(null);
|
|
23207
|
+
useLayoutEffect(() => {
|
|
23208
|
+
if (!open || !anchorRef.current) {
|
|
23209
|
+
setPos(null);
|
|
23210
|
+
return;
|
|
23211
|
+
}
|
|
23212
|
+
const update = () => {
|
|
23213
|
+
const anchor = anchorRef.current;
|
|
23214
|
+
const pop = popoverRef.current;
|
|
23215
|
+
if (!anchor) return;
|
|
23216
|
+
const rect = anchor.getBoundingClientRect();
|
|
23217
|
+
const popoverH = pop?.offsetHeight ?? 200;
|
|
23218
|
+
const popoverW = pop?.offsetWidth ?? minWidth;
|
|
23219
|
+
let top = rect.bottom + 6;
|
|
23220
|
+
let left = rect.left;
|
|
23221
|
+
if (left + popoverW > window.innerWidth - 8) {
|
|
23222
|
+
left = window.innerWidth - popoverW - 8;
|
|
23223
|
+
}
|
|
23224
|
+
if (left < 8) left = 8;
|
|
23225
|
+
if (top + popoverH > window.innerHeight - 8) {
|
|
23226
|
+
top = rect.top - popoverH - 6;
|
|
23227
|
+
}
|
|
23228
|
+
if (top < 8) top = rect.bottom + 6;
|
|
23229
|
+
setPos({ top, left });
|
|
23230
|
+
};
|
|
23231
|
+
update();
|
|
23232
|
+
requestAnimationFrame(update);
|
|
23233
|
+
window.addEventListener("scroll", update, true);
|
|
23234
|
+
window.addEventListener("resize", update);
|
|
23235
|
+
return () => {
|
|
23236
|
+
window.removeEventListener("scroll", update, true);
|
|
23237
|
+
window.removeEventListener("resize", update);
|
|
23238
|
+
};
|
|
23239
|
+
}, [open, anchorRef, minWidth]);
|
|
23240
|
+
useEffect(() => {
|
|
23241
|
+
if (!open) return;
|
|
23242
|
+
const onKey = (e) => {
|
|
23243
|
+
if (e.key === "Escape") onClose();
|
|
23244
|
+
};
|
|
23245
|
+
window.addEventListener("keydown", onKey);
|
|
23246
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
23247
|
+
}, [open, onClose]);
|
|
23248
|
+
useEffect(() => {
|
|
23249
|
+
if (!open) return;
|
|
23250
|
+
const close = (e) => {
|
|
23251
|
+
const target = e.target;
|
|
23252
|
+
if (anchorRef.current?.contains(target)) return;
|
|
23253
|
+
if (popoverRef.current?.contains(target)) return;
|
|
23254
|
+
onClose();
|
|
23255
|
+
};
|
|
23256
|
+
document.addEventListener("mousedown", close);
|
|
23257
|
+
return () => document.removeEventListener("mousedown", close);
|
|
23258
|
+
}, [open, onClose, anchorRef]);
|
|
23259
|
+
if (!open) return null;
|
|
23260
|
+
return createPortal(
|
|
23261
|
+
/* @__PURE__ */ jsx(
|
|
23262
|
+
"div",
|
|
23263
|
+
{
|
|
23264
|
+
ref: popoverRef,
|
|
23265
|
+
role: "dialog",
|
|
23266
|
+
"aria-label": ariaLabel,
|
|
23267
|
+
"data-setto-ui": true,
|
|
23268
|
+
style: {
|
|
23269
|
+
position: "fixed",
|
|
23270
|
+
top: pos?.top ?? -9999,
|
|
23271
|
+
left: pos?.left ?? 0,
|
|
23272
|
+
visibility: pos ? "visible" : "hidden",
|
|
23273
|
+
zIndex: 2147483647,
|
|
23274
|
+
background: "#fff",
|
|
23275
|
+
border: "1px solid #e0e0e0",
|
|
23276
|
+
borderRadius: 8,
|
|
23277
|
+
boxShadow: "0 8px 24px rgba(0,0,0,0.15)",
|
|
23278
|
+
fontFamily: 'system-ui, -apple-system, "Segoe UI", sans-serif',
|
|
23279
|
+
...style
|
|
23280
|
+
},
|
|
23281
|
+
onMouseDown: (e) => e.stopPropagation(),
|
|
23282
|
+
onClick: (e) => e.stopPropagation(),
|
|
23283
|
+
children
|
|
23284
|
+
}
|
|
23285
|
+
),
|
|
23286
|
+
document.body
|
|
23287
|
+
);
|
|
23288
|
+
}
|
|
23289
|
+
const STYLE_ID$2 = "setto-form-edit-styles";
|
|
23290
|
+
function useFormEditStyles() {
|
|
23291
|
+
useEffect(() => {
|
|
23292
|
+
if (document.getElementById(STYLE_ID$2)) return;
|
|
23293
|
+
const el = document.createElement("style");
|
|
23294
|
+
el.id = STYLE_ID$2;
|
|
23295
|
+
el.textContent = `
|
|
23296
|
+
.setto-form-field-tools {
|
|
23297
|
+
opacity: 0;
|
|
23298
|
+
transition: opacity 120ms ease;
|
|
23299
|
+
}
|
|
23300
|
+
.setto-form__field:hover .setto-form-field-tools,
|
|
23301
|
+
.setto-form__field:focus-within .setto-form-field-tools,
|
|
23302
|
+
.setto-form-field-tools[data-open] {
|
|
23303
|
+
opacity: 1;
|
|
23304
|
+
}
|
|
23305
|
+
@media (hover: none) {
|
|
23306
|
+
.setto-form-field-tools { opacity: 1; }
|
|
23307
|
+
}
|
|
23308
|
+
`;
|
|
23309
|
+
document.head.appendChild(el);
|
|
23310
|
+
}, []);
|
|
23311
|
+
}
|
|
23312
|
+
function FormFieldTools({
|
|
23313
|
+
field,
|
|
23314
|
+
index,
|
|
23315
|
+
count,
|
|
23316
|
+
autoOpen,
|
|
23317
|
+
onMove,
|
|
23318
|
+
onRemove,
|
|
23319
|
+
onChange
|
|
23320
|
+
}) {
|
|
23321
|
+
useFormEditStyles();
|
|
23322
|
+
const [open, setOpen] = useState(autoOpen === true);
|
|
23323
|
+
const gearRef = useRef(null);
|
|
23324
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
23325
|
+
/* @__PURE__ */ jsxs(
|
|
23326
|
+
"div",
|
|
23327
|
+
{
|
|
23328
|
+
"data-setto-ui": true,
|
|
23329
|
+
className: "setto-form-field-tools",
|
|
23330
|
+
style: toolsStyle,
|
|
23331
|
+
...open ? { "data-open": "" } : {},
|
|
23332
|
+
onMouseDown: (e) => e.stopPropagation(),
|
|
23333
|
+
onClick: (e) => e.stopPropagation(),
|
|
23334
|
+
children: [
|
|
23335
|
+
/* @__PURE__ */ jsx(ToolButton, { label: "Flytt opp", disabled: index === 0, onClick: () => onMove(-1), children: /* @__PURE__ */ jsx(ArrowIcon, { direction: "up" }) }),
|
|
23336
|
+
/* @__PURE__ */ jsx(
|
|
23337
|
+
ToolButton,
|
|
23338
|
+
{
|
|
23339
|
+
label: "Flytt ned",
|
|
23340
|
+
disabled: index === count - 1,
|
|
23341
|
+
onClick: () => onMove(1),
|
|
23342
|
+
children: /* @__PURE__ */ jsx(ArrowIcon, { direction: "down" })
|
|
23343
|
+
}
|
|
23344
|
+
),
|
|
23345
|
+
/* @__PURE__ */ jsx(
|
|
23346
|
+
ToolButton,
|
|
23347
|
+
{
|
|
23348
|
+
ref: gearRef,
|
|
23349
|
+
label: "Feltinnstillinger",
|
|
23350
|
+
active: open,
|
|
23351
|
+
onClick: () => setOpen((o) => !o),
|
|
23352
|
+
children: /* @__PURE__ */ jsx(GearIcon$1, {})
|
|
23353
|
+
}
|
|
23354
|
+
),
|
|
23355
|
+
/* @__PURE__ */ jsx(ToolButton, { label: "Fjern felt", disabled: count <= 1, onClick: onRemove, danger: true, children: "×" })
|
|
23356
|
+
]
|
|
23357
|
+
}
|
|
23358
|
+
),
|
|
23359
|
+
/* @__PURE__ */ jsx(
|
|
23360
|
+
FloatingPopover,
|
|
23361
|
+
{
|
|
23362
|
+
open,
|
|
23363
|
+
anchorRef: gearRef,
|
|
23364
|
+
onClose: () => setOpen(false),
|
|
23365
|
+
ariaLabel: "Feltinnstillinger",
|
|
23366
|
+
minWidth: 300,
|
|
23367
|
+
style: { width: 300, padding: 14 },
|
|
23368
|
+
children: /* @__PURE__ */ jsx(FieldSettings, { field, onChange })
|
|
23369
|
+
}
|
|
23370
|
+
)
|
|
23371
|
+
] });
|
|
23372
|
+
}
|
|
23373
|
+
function FieldSettings({ field, onChange }) {
|
|
23374
|
+
const [label, setLabel] = useState(field.label);
|
|
23375
|
+
const [placeholder, setPlaceholder] = useState(field.placeholder);
|
|
23376
|
+
const [options, setOptions] = useState(field.options.join("\n"));
|
|
23377
|
+
const [name, setName] = useState(field.name);
|
|
23378
|
+
useEffect(() => setLabel(field.label), [field.label]);
|
|
23379
|
+
useEffect(() => setPlaceholder(field.placeholder), [field.placeholder]);
|
|
23380
|
+
useEffect(() => setName(field.name), [field.name]);
|
|
23381
|
+
return /* @__PURE__ */ jsxs("div", { style: { display: "grid", gap: 10, fontSize: 13, color: "#222" }, children: [
|
|
23382
|
+
/* @__PURE__ */ jsx(Row, { label: "Type", children: /* @__PURE__ */ jsx(
|
|
23383
|
+
"select",
|
|
23384
|
+
{
|
|
23385
|
+
value: field.type,
|
|
23386
|
+
onChange: (e) => onChange({ type: e.target.value }),
|
|
23387
|
+
style: inputStyle$1,
|
|
23388
|
+
children: FORM_FIELD_TYPES.map((type) => /* @__PURE__ */ jsx("option", { value: type, children: FORM_FIELD_TYPE_LABELS[type] }, type))
|
|
23389
|
+
}
|
|
23390
|
+
) }),
|
|
23391
|
+
/* @__PURE__ */ jsx(Row, { label: "Etikett", children: /* @__PURE__ */ jsx(
|
|
23392
|
+
"input",
|
|
23393
|
+
{
|
|
23394
|
+
type: "text",
|
|
23395
|
+
value: label,
|
|
23396
|
+
onChange: (e) => setLabel(e.target.value),
|
|
23397
|
+
onBlur: () => label !== field.label && onChange({ label }),
|
|
23398
|
+
style: inputStyle$1
|
|
23399
|
+
}
|
|
23400
|
+
) }),
|
|
23401
|
+
fieldTypeHasPlaceholder(field.type) ? /* @__PURE__ */ jsx(Row, { label: "Plassholder", children: /* @__PURE__ */ jsx(
|
|
23402
|
+
"input",
|
|
23403
|
+
{
|
|
23404
|
+
type: "text",
|
|
23405
|
+
value: placeholder,
|
|
23406
|
+
onChange: (e) => setPlaceholder(e.target.value),
|
|
23407
|
+
onBlur: () => placeholder !== field.placeholder && onChange({ placeholder }),
|
|
23408
|
+
style: inputStyle$1
|
|
23409
|
+
}
|
|
23410
|
+
) }) : null,
|
|
23411
|
+
fieldTypeHasOptions(field.type) ? /* @__PURE__ */ jsx(Row, { label: "Alternativer", hint: "Ett per linje", children: /* @__PURE__ */ jsx(
|
|
23412
|
+
"textarea",
|
|
23413
|
+
{
|
|
23414
|
+
value: options,
|
|
23415
|
+
rows: 4,
|
|
23416
|
+
onChange: (e) => setOptions(e.target.value),
|
|
23417
|
+
onBlur: () => options !== field.options.join("\n") && onChange({ options }),
|
|
23418
|
+
style: { ...inputStyle$1, resize: "vertical", fontFamily: "inherit" }
|
|
23419
|
+
}
|
|
23420
|
+
) }) : null,
|
|
23421
|
+
/* @__PURE__ */ jsxs("label", { style: { display: "flex", alignItems: "center", gap: 8, cursor: "pointer" }, children: [
|
|
23422
|
+
/* @__PURE__ */ jsx(
|
|
23423
|
+
"input",
|
|
23424
|
+
{
|
|
23425
|
+
type: "checkbox",
|
|
23426
|
+
checked: field.required,
|
|
23427
|
+
onChange: (e) => onChange({ required: e.target.checked })
|
|
23428
|
+
}
|
|
23429
|
+
),
|
|
23430
|
+
"Må fylles ut"
|
|
23431
|
+
] }),
|
|
23432
|
+
/* @__PURE__ */ jsx(Row, { label: "Teknisk navn", hint: "Brukes i e-posten og i historikken", children: /* @__PURE__ */ jsx(
|
|
23433
|
+
"input",
|
|
23434
|
+
{
|
|
23435
|
+
type: "text",
|
|
23436
|
+
value: name,
|
|
23437
|
+
onChange: (e) => setName(e.target.value),
|
|
23438
|
+
onBlur: () => {
|
|
23439
|
+
const next = name.trim();
|
|
23440
|
+
if (next && next !== field.name) onChange({ name: next });
|
|
23441
|
+
else setName(field.name);
|
|
23442
|
+
},
|
|
23443
|
+
style: { ...inputStyle$1, fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace" }
|
|
23444
|
+
}
|
|
23445
|
+
) })
|
|
23446
|
+
] });
|
|
23447
|
+
}
|
|
23448
|
+
function Row({ label, hint, children }) {
|
|
23449
|
+
return /* @__PURE__ */ jsxs("label", { style: { display: "grid", gap: 4 }, children: [
|
|
23450
|
+
/* @__PURE__ */ jsxs("span", { style: { fontSize: 12, color: "#666" }, children: [
|
|
23451
|
+
label,
|
|
23452
|
+
hint ? /* @__PURE__ */ jsxs("span", { style: { color: "#999" }, children: [
|
|
23453
|
+
" · ",
|
|
23454
|
+
hint
|
|
23455
|
+
] }) : null
|
|
23456
|
+
] }),
|
|
23457
|
+
children
|
|
23458
|
+
] });
|
|
23459
|
+
}
|
|
23460
|
+
function ToolButton({ label, onClick, disabled, active, danger, children, ref }) {
|
|
23461
|
+
return /* @__PURE__ */ jsx(
|
|
23462
|
+
"button",
|
|
23463
|
+
{
|
|
23464
|
+
ref,
|
|
23465
|
+
type: "button",
|
|
23466
|
+
"data-setto-ui": true,
|
|
23467
|
+
"aria-label": label,
|
|
23468
|
+
title: label,
|
|
23469
|
+
disabled,
|
|
23470
|
+
onClick,
|
|
23471
|
+
style: {
|
|
23472
|
+
...toolBtnStyle,
|
|
23473
|
+
...active ? { background: "#640AFF", color: "#fff", borderColor: "#640AFF" } : {},
|
|
23474
|
+
...danger ? { fontSize: 16 } : {},
|
|
23475
|
+
...disabled ? { opacity: 0.35, cursor: "default" } : {}
|
|
23476
|
+
},
|
|
23477
|
+
children
|
|
23478
|
+
}
|
|
23479
|
+
);
|
|
23480
|
+
}
|
|
23481
|
+
function ArrowIcon({ direction }) {
|
|
23482
|
+
return /* @__PURE__ */ jsx(
|
|
23483
|
+
"svg",
|
|
23484
|
+
{
|
|
23485
|
+
width: "12",
|
|
23486
|
+
height: "12",
|
|
23487
|
+
viewBox: "0 0 24 24",
|
|
23488
|
+
fill: "none",
|
|
23489
|
+
stroke: "currentColor",
|
|
23490
|
+
strokeWidth: "2.5",
|
|
23491
|
+
strokeLinecap: "round",
|
|
23492
|
+
strokeLinejoin: "round",
|
|
23493
|
+
"aria-hidden": "true",
|
|
23494
|
+
style: direction === "down" ? { transform: "rotate(180deg)" } : void 0,
|
|
23495
|
+
children: /* @__PURE__ */ jsx("path", { d: "M12 19V5M5 12l7-7 7 7" })
|
|
23496
|
+
}
|
|
23497
|
+
);
|
|
23498
|
+
}
|
|
23499
|
+
function GearIcon$1() {
|
|
23500
|
+
return /* @__PURE__ */ jsxs(
|
|
23501
|
+
"svg",
|
|
23502
|
+
{
|
|
23503
|
+
width: "13",
|
|
23504
|
+
height: "13",
|
|
23505
|
+
viewBox: "0 0 24 24",
|
|
23506
|
+
fill: "none",
|
|
23507
|
+
stroke: "currentColor",
|
|
23508
|
+
strokeWidth: "2",
|
|
23509
|
+
strokeLinecap: "round",
|
|
23510
|
+
strokeLinejoin: "round",
|
|
23511
|
+
"aria-hidden": "true",
|
|
23512
|
+
children: [
|
|
23513
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "3" }),
|
|
23514
|
+
/* @__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" })
|
|
23515
|
+
]
|
|
23516
|
+
}
|
|
23517
|
+
);
|
|
23518
|
+
}
|
|
23519
|
+
const toolsStyle = {
|
|
23520
|
+
position: "absolute",
|
|
23521
|
+
top: -12,
|
|
23522
|
+
right: 0,
|
|
23523
|
+
zIndex: 2,
|
|
23524
|
+
display: "flex",
|
|
23525
|
+
gap: 4,
|
|
23526
|
+
padding: 3,
|
|
23527
|
+
background: "#ffffff",
|
|
23528
|
+
border: "1px solid #e0e0e0",
|
|
23529
|
+
borderRadius: 999,
|
|
23530
|
+
boxShadow: "0 1px 4px rgba(0,0,0,0.18)",
|
|
23531
|
+
fontFamily: 'system-ui, -apple-system, "Segoe UI", sans-serif'
|
|
23532
|
+
};
|
|
23533
|
+
const toolBtnStyle = {
|
|
23534
|
+
display: "flex",
|
|
23535
|
+
alignItems: "center",
|
|
23536
|
+
justifyContent: "center",
|
|
23537
|
+
width: 24,
|
|
23538
|
+
height: 24,
|
|
23539
|
+
padding: 0,
|
|
23540
|
+
background: "transparent",
|
|
23541
|
+
border: "1px solid transparent",
|
|
23542
|
+
borderRadius: "50%",
|
|
23543
|
+
fontSize: 13,
|
|
23544
|
+
lineHeight: 1,
|
|
23545
|
+
color: "#444",
|
|
23546
|
+
cursor: "pointer"
|
|
23547
|
+
};
|
|
23548
|
+
const inputStyle$1 = {
|
|
23549
|
+
width: "100%",
|
|
23550
|
+
boxSizing: "border-box",
|
|
23551
|
+
padding: "6px 8px",
|
|
23552
|
+
border: "1px solid #d8d8d8",
|
|
23553
|
+
borderRadius: 6,
|
|
23554
|
+
fontSize: 13,
|
|
23555
|
+
color: "#222",
|
|
23556
|
+
background: "#fff",
|
|
23557
|
+
fontFamily: "inherit"
|
|
23558
|
+
};
|
|
23559
|
+
const HONEYPOT_NAME = "website";
|
|
23560
|
+
const warnedEndpointKeys = /* @__PURE__ */ new Set();
|
|
23561
|
+
function cx(...parts) {
|
|
23562
|
+
return parts.filter(Boolean).join(" ");
|
|
23563
|
+
}
|
|
23564
|
+
function SettoForm({
|
|
23565
|
+
formId,
|
|
23566
|
+
fields: propFields,
|
|
23567
|
+
endpoint,
|
|
23568
|
+
endpointKey,
|
|
23569
|
+
className,
|
|
23570
|
+
classNames = {}
|
|
23571
|
+
}) {
|
|
23572
|
+
const { t, i18n } = useTranslation();
|
|
23573
|
+
const { config, editMode, store } = useSetto();
|
|
23574
|
+
const guestEdit = useGuestEdit();
|
|
23575
|
+
const editable = editMode || guestEdit.active;
|
|
23576
|
+
const lng = i18n.language;
|
|
23577
|
+
const uid = useId();
|
|
23578
|
+
useFormStyles();
|
|
23579
|
+
const [, force] = useState(0);
|
|
23580
|
+
useEffect(() => {
|
|
23581
|
+
if (!store) return;
|
|
23582
|
+
return store.subscribe(() => force((x) => x + 1));
|
|
23583
|
+
}, [store]);
|
|
23584
|
+
useEffect(() => {
|
|
23585
|
+
if (endpointKey && !warnedEndpointKeys.has(formId)) {
|
|
23586
|
+
warnedEndpointKeys.add(formId);
|
|
23587
|
+
console.warn(
|
|
23588
|
+
`[setto] <SettoForm formId="${formId}"> passes endpointKey, which is no longer used. Forms submit to Setto; pass \`endpoint\` for a custom backend.`
|
|
23589
|
+
);
|
|
23590
|
+
}
|
|
23591
|
+
}, [endpointKey, formId]);
|
|
23592
|
+
const localeFields = readLocaleFields(i18n, lng, formId);
|
|
23593
|
+
const fields = localeFields ?? resolvePropFields(propFields ?? [], t);
|
|
23594
|
+
const fromLocale = localeFields !== null;
|
|
23595
|
+
const prefix = formFieldsPrefix(formId);
|
|
23596
|
+
const [values, setValues] = useState({});
|
|
23597
|
+
const [errors, setErrors] = useState({});
|
|
23598
|
+
const [status, setStatus] = useState("idle");
|
|
23599
|
+
const [justAdded, setJustAdded] = useState(null);
|
|
23600
|
+
const formRef = useRef(null);
|
|
23601
|
+
const successRef = useRef(null);
|
|
23602
|
+
const honeypotRef = useRef(null);
|
|
23603
|
+
const startedAt = useRef(null);
|
|
23604
|
+
useEffect(() => {
|
|
23605
|
+
if (status === "success") successRef.current?.focus({ preventScroll: false });
|
|
23606
|
+
}, [status]);
|
|
23607
|
+
const valueOf = (field) => values[field.name] ?? emptyValue(field.type);
|
|
23608
|
+
const setValue = (name, value) => {
|
|
23609
|
+
setValues((prev) => ({ ...prev, [name]: value }));
|
|
23610
|
+
setErrors((prev) => {
|
|
23611
|
+
if (!(name in prev)) return prev;
|
|
23612
|
+
const next = { ...prev };
|
|
23613
|
+
delete next[name];
|
|
23614
|
+
return next;
|
|
23615
|
+
});
|
|
23616
|
+
};
|
|
23617
|
+
const markStarted = () => {
|
|
23618
|
+
if (startedAt.current === null) startedAt.current = performance.now();
|
|
23619
|
+
};
|
|
23620
|
+
const submitKey = `form.${formId}.submit`;
|
|
23621
|
+
const successKey = `form.${formId}.success`;
|
|
23622
|
+
const errorKey = `form.${formId}.error`;
|
|
23623
|
+
const title = t(`form.${formId}.title`, { defaultValue: "" }) || void 0;
|
|
23624
|
+
const onSubmit = async (e) => {
|
|
23625
|
+
e.preventDefault();
|
|
23626
|
+
if (status === "submitting") return;
|
|
23627
|
+
const nextErrors = validateValues(fields, values);
|
|
23628
|
+
setErrors(nextErrors);
|
|
23629
|
+
const firstInvalid = fields.find((f) => nextErrors[f.name]);
|
|
23630
|
+
if (firstInvalid) {
|
|
23631
|
+
const el = formRef.current?.querySelector(
|
|
23632
|
+
`[data-setto-field="${cssEscape(firstInvalid.name)}"] [name]`
|
|
23633
|
+
);
|
|
23634
|
+
el?.focus();
|
|
23635
|
+
return;
|
|
23636
|
+
}
|
|
23637
|
+
setStatus("submitting");
|
|
23638
|
+
const elapsed = startedAt.current === null ? 0 : performance.now() - startedAt.current;
|
|
23639
|
+
const envelope = buildEnvelope(fields, values, {
|
|
23640
|
+
title,
|
|
23641
|
+
page: typeof window !== "undefined" ? window.location.href : void 0,
|
|
23642
|
+
hp: honeypotRef.current?.value ?? "",
|
|
23643
|
+
elapsed
|
|
23644
|
+
});
|
|
23645
|
+
try {
|
|
23646
|
+
const url = endpoint || settoSubmissionUrl(config.apiUrl, config.siteId, formId);
|
|
23647
|
+
const res = await fetch(url, {
|
|
23648
|
+
method: "POST",
|
|
23649
|
+
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
|
23650
|
+
body: JSON.stringify(endpoint ? envelope.data : envelope)
|
|
23651
|
+
});
|
|
23652
|
+
if (!res.ok) throw new Error(`Form submission failed: ${res.status}`);
|
|
23653
|
+
setStatus("success");
|
|
23654
|
+
setValues({});
|
|
23655
|
+
startedAt.current = null;
|
|
23656
|
+
} catch (err) {
|
|
23657
|
+
console.error("[setto] form submission failed", err);
|
|
23658
|
+
setStatus("error");
|
|
23659
|
+
}
|
|
23660
|
+
};
|
|
23661
|
+
const ensureLocaleFields = () => {
|
|
23662
|
+
const keys = /* @__PURE__ */ new Map();
|
|
23663
|
+
if (!store) return keys;
|
|
23664
|
+
if (fromLocale) {
|
|
23665
|
+
for (const f of fields) if (f.itemKey) keys.set(f.name, f.itemKey);
|
|
23666
|
+
return keys;
|
|
23667
|
+
}
|
|
23668
|
+
const langs = i18n.languages?.length ? i18n.languages : [lng];
|
|
23669
|
+
for (const prop of propFields ?? []) {
|
|
23670
|
+
const itemKey = store.addListItem(prefix, lng, {
|
|
23671
|
+
name: prop.name,
|
|
23672
|
+
type: prop.type,
|
|
23673
|
+
...prop.required ? { required: "true" } : {}
|
|
23674
|
+
});
|
|
23675
|
+
for (const lang of langs) {
|
|
23676
|
+
const ft = i18n.getFixedT(lang);
|
|
23677
|
+
const label = ft(prop.labelKey, { defaultValue: "" });
|
|
23678
|
+
if (label) store.set(`${prefix}.${itemKey}.label`, lang, label);
|
|
23679
|
+
const placeholder = prop.placeholderKey ? ft(prop.placeholderKey, { defaultValue: "" }) : "";
|
|
23680
|
+
if (placeholder) store.set(`${prefix}.${itemKey}.placeholder`, lang, placeholder);
|
|
23681
|
+
const options = prop.optionsKey ? ft(prop.optionsKey, { defaultValue: "" }) : "";
|
|
23682
|
+
if (options) store.set(`${prefix}.${itemKey}.options`, lang, options);
|
|
23683
|
+
}
|
|
23684
|
+
keys.set(prop.name, itemKey);
|
|
23685
|
+
}
|
|
23686
|
+
return keys;
|
|
23687
|
+
};
|
|
23688
|
+
const itemKeyFor = (field) => field.itemKey ?? ensureLocaleFields().get(field.name);
|
|
23689
|
+
const allLanguages = () => i18n.languages?.length ? [...i18n.languages] : [lng];
|
|
23690
|
+
const setEverywhere = (key, value) => {
|
|
23691
|
+
for (const lang of allLanguages()) {
|
|
23692
|
+
if (value === void 0 || value === "") store.delete(key, lang);
|
|
23693
|
+
else store.set(key, lang, value);
|
|
23694
|
+
}
|
|
23695
|
+
};
|
|
23696
|
+
const addField = () => {
|
|
23697
|
+
if (!store) return;
|
|
23698
|
+
store.batch(() => {
|
|
23699
|
+
ensureLocaleFields();
|
|
23700
|
+
const taken = fields.map((f) => f.name);
|
|
23701
|
+
const itemKey = store.addListItem(prefix, lng, {
|
|
23702
|
+
name: uniqueFieldName("felt", taken),
|
|
23703
|
+
type: "text"
|
|
23704
|
+
});
|
|
23705
|
+
for (const lang of allLanguages()) store.set(`${prefix}.${itemKey}.label`, lang, "Nytt felt");
|
|
23706
|
+
setJustAdded(itemKey);
|
|
23707
|
+
});
|
|
23708
|
+
};
|
|
23709
|
+
const moveField = (field, direction) => {
|
|
23710
|
+
if (!store) return;
|
|
23711
|
+
store.batch(() => {
|
|
23712
|
+
const itemKey = itemKeyFor(field);
|
|
23713
|
+
if (itemKey) store.moveListItem(prefix, itemKey, direction, lng);
|
|
23714
|
+
});
|
|
23715
|
+
};
|
|
23716
|
+
const removeField = (field) => {
|
|
23717
|
+
if (!store || fields.length <= 1) return;
|
|
23718
|
+
store.batch(() => {
|
|
23719
|
+
const itemKey = itemKeyFor(field);
|
|
23720
|
+
if (itemKey) store.removeListItem(prefix, itemKey, lng);
|
|
23721
|
+
});
|
|
23722
|
+
};
|
|
23723
|
+
const updateField = (field, patch) => {
|
|
23724
|
+
if (!store) return;
|
|
23725
|
+
store.batch(() => {
|
|
23726
|
+
const itemKey = itemKeyFor(field);
|
|
23727
|
+
if (!itemKey) return;
|
|
23728
|
+
const item = `${prefix}.${itemKey}`;
|
|
23729
|
+
const others = fields.filter((f) => f !== field).map((f) => f.name);
|
|
23730
|
+
if (patch.type !== void 0) setEverywhere(`${item}.type`, patch.type);
|
|
23731
|
+
if (patch.required !== void 0) {
|
|
23732
|
+
setEverywhere(`${item}.required`, patch.required ? "true" : void 0);
|
|
23733
|
+
}
|
|
23734
|
+
if (patch.name !== void 0) {
|
|
23735
|
+
setEverywhere(`${item}.name`, uniqueFieldName(slugifyFieldName(patch.name), others));
|
|
23736
|
+
}
|
|
23737
|
+
if (patch.label !== void 0) {
|
|
23738
|
+
store.set(`${item}.label`, lng, patch.label);
|
|
23739
|
+
if (patch.name === void 0 && /^felt(-\d+)?$/.test(field.name) && patch.label.trim()) {
|
|
23740
|
+
setEverywhere(`${item}.name`, uniqueFieldName(slugifyFieldName(patch.label), others));
|
|
23741
|
+
}
|
|
23742
|
+
}
|
|
23743
|
+
if (patch.placeholder !== void 0) {
|
|
23744
|
+
if (patch.placeholder) store.set(`${item}.placeholder`, lng, patch.placeholder);
|
|
23745
|
+
else store.delete(`${item}.placeholder`, lng);
|
|
23746
|
+
}
|
|
23747
|
+
if (patch.options !== void 0) {
|
|
23748
|
+
if (patch.options.trim()) store.set(`${item}.options`, lng, patch.options);
|
|
23749
|
+
else store.delete(`${item}.options`, lng);
|
|
23750
|
+
}
|
|
23751
|
+
});
|
|
23752
|
+
};
|
|
23753
|
+
if (status === "success") {
|
|
23754
|
+
return /* @__PURE__ */ jsxs(
|
|
23755
|
+
"div",
|
|
23756
|
+
{
|
|
23757
|
+
ref: successRef,
|
|
23758
|
+
tabIndex: -1,
|
|
23759
|
+
role: "status",
|
|
23760
|
+
"aria-live": "polite",
|
|
23761
|
+
"data-setto-form": formId,
|
|
23762
|
+
"data-setto-form-status": "success",
|
|
23763
|
+
className: cx("setto-form__success", className, classNames.success),
|
|
23764
|
+
children: [
|
|
23765
|
+
/* @__PURE__ */ jsx(CheckIcon, {}),
|
|
23766
|
+
/* @__PURE__ */ jsx("p", { className: "setto-form__success-text", children: /* @__PURE__ */ jsx(T, { k: successKey }) }),
|
|
23767
|
+
/* @__PURE__ */ jsx(
|
|
23768
|
+
"button",
|
|
23769
|
+
{
|
|
23770
|
+
type: "button",
|
|
23771
|
+
className: "setto-form__again",
|
|
23772
|
+
onClick: () => {
|
|
23773
|
+
setStatus("idle");
|
|
23774
|
+
setErrors({});
|
|
23775
|
+
},
|
|
23776
|
+
children: t("form.again", { defaultValue: "Send en ny melding" })
|
|
23777
|
+
}
|
|
23778
|
+
)
|
|
23779
|
+
]
|
|
23780
|
+
}
|
|
23781
|
+
);
|
|
23782
|
+
}
|
|
23783
|
+
const submitting = status === "submitting";
|
|
23784
|
+
return /* @__PURE__ */ jsxs(
|
|
23785
|
+
"form",
|
|
23786
|
+
{
|
|
23787
|
+
ref: formRef,
|
|
23788
|
+
"data-setto-form": formId,
|
|
23789
|
+
className: cx("setto-form", className, classNames.form),
|
|
23790
|
+
onSubmit,
|
|
23791
|
+
onFocusCapture: markStarted,
|
|
23792
|
+
onPointerDownCapture: markStarted,
|
|
23793
|
+
onKeyDownCapture: markStarted,
|
|
23794
|
+
noValidate: true,
|
|
23795
|
+
children: [
|
|
23796
|
+
fields.map((field, index) => {
|
|
23797
|
+
const id = `${uid}-${field.name}`;
|
|
23798
|
+
const error = errors[field.name];
|
|
23799
|
+
const errorId = error ? `${id}-error` : void 0;
|
|
23800
|
+
return /* @__PURE__ */ jsxs(
|
|
23801
|
+
"div",
|
|
23802
|
+
{
|
|
23803
|
+
"data-setto-field": field.name,
|
|
23804
|
+
className: cx("setto-form__field", `setto-form__field--${field.type}`, classNames.field),
|
|
23805
|
+
children: [
|
|
23806
|
+
/* @__PURE__ */ jsx(
|
|
23807
|
+
FieldControl,
|
|
23808
|
+
{
|
|
23809
|
+
field,
|
|
23810
|
+
id,
|
|
23811
|
+
value: valueOf(field),
|
|
23812
|
+
editable,
|
|
23813
|
+
invalid: error !== void 0,
|
|
23814
|
+
describedBy: errorId,
|
|
23815
|
+
classNames,
|
|
23816
|
+
placeholderFallback: t("form.choose", { defaultValue: "Velg …" }),
|
|
23817
|
+
onChange: (value) => setValue(field.name, value)
|
|
23818
|
+
}
|
|
23819
|
+
),
|
|
23820
|
+
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,
|
|
23821
|
+
editable && store ? /* @__PURE__ */ jsx(
|
|
23822
|
+
FormFieldTools,
|
|
23823
|
+
{
|
|
23824
|
+
field,
|
|
23825
|
+
index,
|
|
23826
|
+
count: fields.length,
|
|
23827
|
+
autoOpen: justAdded !== null && justAdded === field.itemKey,
|
|
23828
|
+
onMove: (direction) => moveField(field, direction),
|
|
23829
|
+
onRemove: () => removeField(field),
|
|
23830
|
+
onChange: (patch) => updateField(field, patch)
|
|
23831
|
+
}
|
|
23832
|
+
) : null
|
|
23833
|
+
]
|
|
23834
|
+
},
|
|
23835
|
+
field.itemKey ?? field.name
|
|
23836
|
+
);
|
|
23837
|
+
}),
|
|
23838
|
+
editable && store ? /* @__PURE__ */ jsx(
|
|
23839
|
+
"button",
|
|
23840
|
+
{
|
|
23841
|
+
type: "button",
|
|
23842
|
+
"data-setto-ui": true,
|
|
23843
|
+
className: "setto-form__add-field",
|
|
23844
|
+
style: addFieldStyle,
|
|
23845
|
+
onClick: addField,
|
|
23846
|
+
children: "+ Legg til felt"
|
|
23847
|
+
}
|
|
23848
|
+
) : null,
|
|
23849
|
+
/* @__PURE__ */ jsxs("div", { className: "setto-form__hp", "aria-hidden": "true", children: [
|
|
23850
|
+
/* @__PURE__ */ jsx("label", { htmlFor: `${uid}-${HONEYPOT_NAME}`, children: "Nettside" }),
|
|
23851
|
+
/* @__PURE__ */ jsx(
|
|
23852
|
+
"input",
|
|
23853
|
+
{
|
|
23854
|
+
ref: honeypotRef,
|
|
23855
|
+
id: `${uid}-${HONEYPOT_NAME}`,
|
|
23856
|
+
type: "text",
|
|
23857
|
+
name: HONEYPOT_NAME,
|
|
23858
|
+
tabIndex: -1,
|
|
23859
|
+
autoComplete: "off",
|
|
23860
|
+
defaultValue: ""
|
|
23861
|
+
}
|
|
23862
|
+
)
|
|
23863
|
+
] }),
|
|
23864
|
+
/* @__PURE__ */ jsxs("div", { className: cx("setto-form__actions", classNames.actions), children: [
|
|
23865
|
+
/* @__PURE__ */ jsx(
|
|
23866
|
+
"button",
|
|
23867
|
+
{
|
|
23868
|
+
type: "submit",
|
|
23869
|
+
className: cx("setto-form__button", classNames.button),
|
|
23870
|
+
disabled: submitting,
|
|
23871
|
+
"aria-busy": submitting || void 0,
|
|
23872
|
+
children: submitting ? t("form.sending", { defaultValue: "Sender …" }) : /* @__PURE__ */ jsx(T, { k: submitKey })
|
|
23873
|
+
}
|
|
23874
|
+
),
|
|
23875
|
+
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
|
|
23876
|
+
] }),
|
|
23877
|
+
editable ? /* @__PURE__ */ jsxs("div", { className: "setto-form__edit-previews", style: previewStyle, children: [
|
|
23878
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
23879
|
+
/* @__PURE__ */ jsx("span", { style: previewHintStyle, children: "Etter innsending vises:" }),
|
|
23880
|
+
" ",
|
|
23881
|
+
/* @__PURE__ */ jsx(T, { k: successKey })
|
|
23882
|
+
] }),
|
|
23883
|
+
status !== "error" ? /* @__PURE__ */ jsxs("div", { children: [
|
|
23884
|
+
/* @__PURE__ */ jsx("span", { style: previewHintStyle, children: "Ved feil vises:" }),
|
|
23885
|
+
" ",
|
|
23886
|
+
/* @__PURE__ */ jsx(T, { k: errorKey })
|
|
23887
|
+
] }) : null
|
|
23888
|
+
] }) : null
|
|
23889
|
+
]
|
|
23890
|
+
}
|
|
23891
|
+
);
|
|
23892
|
+
}
|
|
23893
|
+
function FieldControl({
|
|
23894
|
+
field,
|
|
23895
|
+
id,
|
|
23896
|
+
value,
|
|
23897
|
+
editable,
|
|
23898
|
+
invalid,
|
|
23899
|
+
describedBy,
|
|
23900
|
+
classNames,
|
|
23901
|
+
placeholderFallback,
|
|
23902
|
+
onChange
|
|
23903
|
+
}) {
|
|
23904
|
+
const labelId = `${id}-label`;
|
|
23905
|
+
const aria = {
|
|
23906
|
+
"aria-invalid": invalid || void 0,
|
|
23907
|
+
"aria-describedby": describedBy,
|
|
23908
|
+
required: field.required || void 0
|
|
23909
|
+
};
|
|
23910
|
+
const labelText = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
23911
|
+
/* @__PURE__ */ jsx(T, { k: field.labelKey }),
|
|
23912
|
+
field.required ? /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "setto-form__required", children: "*" }) : null
|
|
23913
|
+
] });
|
|
23914
|
+
if (field.type === "checkbox") {
|
|
23915
|
+
const checked = value === true;
|
|
23916
|
+
return /* @__PURE__ */ jsxs(
|
|
23917
|
+
FieldLabel,
|
|
23918
|
+
{
|
|
23919
|
+
editable,
|
|
23920
|
+
htmlFor: id,
|
|
23921
|
+
id: labelId,
|
|
23922
|
+
className: cx("setto-form__check", classNames.checkbox),
|
|
23923
|
+
children: [
|
|
23924
|
+
/* @__PURE__ */ jsx(
|
|
23925
|
+
"input",
|
|
23926
|
+
{
|
|
23927
|
+
id,
|
|
23928
|
+
type: "checkbox",
|
|
23929
|
+
name: field.name,
|
|
23930
|
+
checked,
|
|
23931
|
+
onChange: (e) => onChange(e.target.checked),
|
|
23932
|
+
...editable ? { "aria-labelledby": labelId } : {},
|
|
23933
|
+
...aria
|
|
23934
|
+
}
|
|
23935
|
+
),
|
|
23936
|
+
/* @__PURE__ */ jsx("span", { children: labelText })
|
|
23937
|
+
]
|
|
23938
|
+
}
|
|
23939
|
+
);
|
|
23940
|
+
}
|
|
23941
|
+
if (field.type === "radio" || field.type === "checkboxes") {
|
|
23942
|
+
const multiple = field.type === "checkboxes";
|
|
23943
|
+
const selected = multiple ? Array.isArray(value) ? value : [] : value;
|
|
23944
|
+
const toggle = (option, on) => {
|
|
23945
|
+
if (!multiple) {
|
|
23946
|
+
onChange(option);
|
|
23947
|
+
return;
|
|
23948
|
+
}
|
|
23949
|
+
const set = new Set(Array.isArray(selected) ? selected : []);
|
|
23950
|
+
if (on) set.add(option);
|
|
23951
|
+
else set.delete(option);
|
|
23952
|
+
onChange(field.options.filter((o) => set.has(o)));
|
|
23953
|
+
};
|
|
23954
|
+
return /* @__PURE__ */ jsxs(
|
|
23955
|
+
"fieldset",
|
|
23956
|
+
{
|
|
23957
|
+
className: "setto-form__fieldset",
|
|
23958
|
+
role: multiple ? void 0 : "radiogroup",
|
|
23959
|
+
"aria-invalid": invalid || void 0,
|
|
23960
|
+
"aria-describedby": describedBy,
|
|
23961
|
+
"aria-required": !multiple && field.required ? true : void 0,
|
|
23962
|
+
children: [
|
|
23963
|
+
/* @__PURE__ */ jsx("legend", { className: cx("setto-form__legend", classNames.label), children: labelText }),
|
|
23964
|
+
/* @__PURE__ */ jsx("div", { className: cx("setto-form__options", classNames.options), children: field.options.map((option, i) => {
|
|
23965
|
+
const optionId = `${id}-${i}`;
|
|
23966
|
+
const on = multiple ? selected.includes(option) : selected === option;
|
|
23967
|
+
return /* @__PURE__ */ jsxs("label", { htmlFor: optionId, className: cx("setto-form__option", classNames.option), children: [
|
|
23968
|
+
/* @__PURE__ */ jsx(
|
|
23969
|
+
"input",
|
|
23970
|
+
{
|
|
23971
|
+
id: optionId,
|
|
23972
|
+
type: multiple ? "checkbox" : "radio",
|
|
23973
|
+
name: field.name,
|
|
23974
|
+
value: option,
|
|
23975
|
+
checked: on,
|
|
23976
|
+
required: !multiple && field.required ? true : void 0,
|
|
23977
|
+
onChange: (e) => toggle(option, e.target.checked)
|
|
23978
|
+
}
|
|
23979
|
+
),
|
|
23980
|
+
/* @__PURE__ */ jsx("span", { children: option })
|
|
23981
|
+
] }, option);
|
|
23982
|
+
}) })
|
|
23983
|
+
]
|
|
23984
|
+
}
|
|
23985
|
+
);
|
|
23986
|
+
}
|
|
23987
|
+
const text = typeof value === "string" ? value : "";
|
|
23988
|
+
const labelled = editable ? { "aria-labelledby": labelId } : {};
|
|
23989
|
+
let control;
|
|
23990
|
+
if (field.type === "textarea") {
|
|
23991
|
+
control = /* @__PURE__ */ jsx(
|
|
23992
|
+
"textarea",
|
|
23993
|
+
{
|
|
23994
|
+
id,
|
|
23995
|
+
name: field.name,
|
|
23996
|
+
value: text,
|
|
23997
|
+
rows: 5,
|
|
23998
|
+
placeholder: field.placeholder || void 0,
|
|
23999
|
+
onChange: (e) => onChange(e.target.value),
|
|
24000
|
+
className: cx("setto-form__textarea", classNames.textarea),
|
|
24001
|
+
...labelled,
|
|
24002
|
+
...aria
|
|
24003
|
+
}
|
|
24004
|
+
);
|
|
24005
|
+
} else if (field.type === "select") {
|
|
24006
|
+
control = /* @__PURE__ */ jsxs(
|
|
24007
|
+
"select",
|
|
24008
|
+
{
|
|
24009
|
+
id,
|
|
24010
|
+
name: field.name,
|
|
24011
|
+
value: text,
|
|
24012
|
+
onChange: (e) => onChange(e.target.value),
|
|
24013
|
+
className: cx("setto-form__select", classNames.select),
|
|
24014
|
+
...labelled,
|
|
24015
|
+
...aria,
|
|
24016
|
+
children: [
|
|
24017
|
+
/* @__PURE__ */ jsx("option", { value: "", disabled: field.required, children: field.placeholder || placeholderFallback }),
|
|
24018
|
+
field.options.map((option) => /* @__PURE__ */ jsx("option", { value: option, children: option }, option))
|
|
24019
|
+
]
|
|
24020
|
+
}
|
|
24021
|
+
);
|
|
24022
|
+
} else {
|
|
24023
|
+
control = /* @__PURE__ */ jsx(
|
|
24024
|
+
"input",
|
|
24025
|
+
{
|
|
24026
|
+
id,
|
|
24027
|
+
type: inputType(field),
|
|
24028
|
+
name: field.name,
|
|
24029
|
+
value: text,
|
|
24030
|
+
placeholder: field.placeholder || void 0,
|
|
24031
|
+
inputMode: inputMode(field),
|
|
24032
|
+
autoComplete: autoCompleteFor(field),
|
|
24033
|
+
onChange: (e) => onChange(e.target.value),
|
|
24034
|
+
className: cx("setto-form__input", classNames.input),
|
|
24035
|
+
...labelled,
|
|
24036
|
+
...aria
|
|
24037
|
+
}
|
|
24038
|
+
);
|
|
24039
|
+
}
|
|
24040
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
24041
|
+
/* @__PURE__ */ jsx(
|
|
24042
|
+
FieldLabel,
|
|
24043
|
+
{
|
|
24044
|
+
editable,
|
|
24045
|
+
htmlFor: id,
|
|
24046
|
+
id: labelId,
|
|
24047
|
+
className: cx("setto-form__label", classNames.label),
|
|
24048
|
+
children: labelText
|
|
24049
|
+
}
|
|
24050
|
+
),
|
|
24051
|
+
control
|
|
24052
|
+
] });
|
|
24053
|
+
}
|
|
24054
|
+
function FieldLabel({
|
|
24055
|
+
editable,
|
|
24056
|
+
htmlFor,
|
|
24057
|
+
id,
|
|
24058
|
+
className,
|
|
24059
|
+
children
|
|
24060
|
+
}) {
|
|
24061
|
+
if (editable) {
|
|
24062
|
+
return /* @__PURE__ */ jsx("span", { id, className, children });
|
|
24063
|
+
}
|
|
24064
|
+
return /* @__PURE__ */ jsx("label", { htmlFor, className, children });
|
|
24065
|
+
}
|
|
24066
|
+
function inputType(field) {
|
|
24067
|
+
switch (field.type) {
|
|
24068
|
+
case "email":
|
|
24069
|
+
case "tel":
|
|
24070
|
+
case "url":
|
|
24071
|
+
case "date":
|
|
24072
|
+
return field.type;
|
|
24073
|
+
case "number":
|
|
24074
|
+
return "text";
|
|
24075
|
+
default:
|
|
24076
|
+
return "text";
|
|
24077
|
+
}
|
|
24078
|
+
}
|
|
24079
|
+
function inputMode(field) {
|
|
24080
|
+
switch (field.type) {
|
|
24081
|
+
case "email":
|
|
24082
|
+
return "email";
|
|
24083
|
+
case "tel":
|
|
24084
|
+
return "tel";
|
|
24085
|
+
case "url":
|
|
24086
|
+
return "url";
|
|
24087
|
+
case "number":
|
|
24088
|
+
return "decimal";
|
|
24089
|
+
default:
|
|
24090
|
+
return void 0;
|
|
24091
|
+
}
|
|
24092
|
+
}
|
|
24093
|
+
function autoCompleteFor(field) {
|
|
24094
|
+
if (field.type === "email") return "email";
|
|
24095
|
+
if (field.type === "tel") return "tel";
|
|
24096
|
+
if (field.type === "url") return "url";
|
|
24097
|
+
if (/^(navn|name|fullt?[-_]?navn|full[-_]?name)$/i.test(field.name)) return "name";
|
|
24098
|
+
if (/^(firma|bedrift|company|organisasjon|organization)$/i.test(field.name)) return "organization";
|
|
24099
|
+
return void 0;
|
|
24100
|
+
}
|
|
24101
|
+
function cssEscape(value) {
|
|
24102
|
+
return typeof CSS !== "undefined" && typeof CSS.escape === "function" ? CSS.escape(value) : value.replace(/["\\]/g, "\\$&");
|
|
24103
|
+
}
|
|
24104
|
+
function CheckIcon() {
|
|
24105
|
+
return /* @__PURE__ */ jsxs(
|
|
24106
|
+
"svg",
|
|
24107
|
+
{
|
|
24108
|
+
className: "setto-form__success-icon",
|
|
24109
|
+
viewBox: "0 0 40 40",
|
|
24110
|
+
fill: "none",
|
|
24111
|
+
"aria-hidden": "true",
|
|
24112
|
+
focusable: "false",
|
|
24113
|
+
children: [
|
|
24114
|
+
/* @__PURE__ */ jsx("circle", { cx: "20", cy: "20", r: "19", stroke: "currentColor", strokeWidth: "2" }),
|
|
24115
|
+
/* @__PURE__ */ jsx(
|
|
24116
|
+
"path",
|
|
24117
|
+
{
|
|
24118
|
+
d: "M12 20.5l5.5 5.5L28 15",
|
|
24119
|
+
stroke: "currentColor",
|
|
24120
|
+
strokeWidth: "2.5",
|
|
24121
|
+
strokeLinecap: "round",
|
|
24122
|
+
strokeLinejoin: "round"
|
|
24123
|
+
}
|
|
24124
|
+
)
|
|
24125
|
+
]
|
|
24126
|
+
}
|
|
24127
|
+
);
|
|
24128
|
+
}
|
|
24129
|
+
const STYLE_ID$1 = "setto-form-styles";
|
|
24130
|
+
const BASE_CSS = `
|
|
24131
|
+
:where(.setto-form){display:grid;gap:1.1rem;position:relative}
|
|
24132
|
+
:where(.setto-form__field){display:grid;gap:.4rem;position:relative}
|
|
24133
|
+
:where(.setto-form__label,.setto-form__legend){font-weight:500;line-height:1.3;padding:0}
|
|
24134
|
+
:where(.setto-form__required){opacity:.6;margin-left:.15em}
|
|
24135
|
+
: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}
|
|
24136
|
+
:where(.setto-form__input,.setto-form__textarea,.setto-form__select)::placeholder{color:inherit;opacity:.5}
|
|
24137
|
+
:where(.setto-form__input,.setto-form__textarea,.setto-form__select):focus-visible{outline:2px solid currentColor;outline-offset:1px}
|
|
24138
|
+
:where(.setto-form__textarea){min-height:7.5rem;resize:vertical}
|
|
24139
|
+
:where(.setto-form__fieldset){border:0;padding:0;margin:0;min-width:0;display:grid;gap:.5rem}
|
|
24140
|
+
:where(.setto-form__options){display:grid;gap:.5rem}
|
|
24141
|
+
:where(.setto-form__option,.setto-form__check){display:flex;gap:.6rem;align-items:flex-start;line-height:1.4;cursor:pointer;font-weight:400}
|
|
24142
|
+
:where(.setto-form__option input,.setto-form__check input){margin:.25em 0 0;flex:none;accent-color:currentColor}
|
|
24143
|
+
:where(.setto-form__field-error,.setto-form__error){font-size:.875rem;color:#b42318;margin:0}
|
|
24144
|
+
:where(.setto-form [aria-invalid="true"]){border-color:#b42318}
|
|
24145
|
+
:where(.setto-form__actions){display:flex;flex-wrap:wrap;gap:.75rem 1rem;align-items:center}
|
|
24146
|
+
: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}
|
|
24147
|
+
:where(.setto-form__button:disabled){opacity:.6;cursor:progress}
|
|
24148
|
+
:where(.setto-form__success){display:grid;gap:.75rem;justify-items:start;outline:none}
|
|
24149
|
+
:where(.setto-form__success-icon){width:2.5rem;height:2.5rem;color:#0f7b4f}
|
|
24150
|
+
:where(.setto-form__success-text){margin:0;font-size:1.05em}
|
|
24151
|
+
: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}
|
|
24152
|
+
:where(.setto-form__hp){position:absolute;left:-10000px;top:auto;width:1px;height:1px;overflow:hidden;opacity:0;pointer-events:none}
|
|
24153
|
+
`;
|
|
24154
|
+
function useFormStyles() {
|
|
24155
|
+
useInsertionEffect(() => {
|
|
24156
|
+
if (typeof document === "undefined" || document.getElementById(STYLE_ID$1)) return;
|
|
24157
|
+
const el = document.createElement("style");
|
|
24158
|
+
el.id = STYLE_ID$1;
|
|
24159
|
+
el.textContent = BASE_CSS;
|
|
24160
|
+
document.head.appendChild(el);
|
|
24161
|
+
}, []);
|
|
24162
|
+
}
|
|
24163
|
+
const addFieldStyle = {
|
|
24164
|
+
justifySelf: "start",
|
|
24165
|
+
padding: "6px 12px",
|
|
24166
|
+
border: "1px dashed #999",
|
|
24167
|
+
borderRadius: 999,
|
|
24168
|
+
background: "rgba(255,255,255,0.85)",
|
|
24169
|
+
color: "#333",
|
|
24170
|
+
fontSize: 13,
|
|
24171
|
+
fontFamily: 'system-ui, -apple-system, "Segoe UI", sans-serif',
|
|
24172
|
+
cursor: "pointer"
|
|
24173
|
+
};
|
|
24174
|
+
const previewStyle = {
|
|
24175
|
+
display: "grid",
|
|
24176
|
+
gap: 6,
|
|
24177
|
+
padding: "10px 12px",
|
|
24178
|
+
border: "1px dashed rgba(100,10,255,0.35)",
|
|
24179
|
+
borderRadius: 8,
|
|
24180
|
+
fontSize: "0.9em",
|
|
24181
|
+
lineHeight: 1.4
|
|
24182
|
+
};
|
|
24183
|
+
const previewHintStyle = {
|
|
24184
|
+
color: "#640AFF",
|
|
24185
|
+
fontSize: 11,
|
|
24186
|
+
fontFamily: 'system-ui, -apple-system, "Segoe UI", sans-serif',
|
|
24187
|
+
textTransform: "uppercase",
|
|
24188
|
+
letterSpacing: "0.04em",
|
|
24189
|
+
marginRight: 4
|
|
24190
|
+
};
|
|
24191
|
+
const formsModule = {
|
|
24192
|
+
id: "forms",
|
|
24193
|
+
label: "Skjemaer",
|
|
24194
|
+
AdminView: FormsView
|
|
24195
|
+
};
|
|
24196
|
+
const SETTO_MODULES = [formsModule];
|
|
24197
|
+
function findModule(id) {
|
|
24198
|
+
return id ? SETTO_MODULES.find((m) => m.id === id) : void 0;
|
|
24199
|
+
}
|
|
24200
|
+
function SettoMark({
|
|
24201
|
+
size = 24,
|
|
24202
|
+
letterColor = "#F2EBDF",
|
|
24203
|
+
dotColor = "#C4502A",
|
|
24204
|
+
style
|
|
24205
|
+
}) {
|
|
24206
|
+
return /* @__PURE__ */ jsxs(
|
|
24207
|
+
"svg",
|
|
24208
|
+
{
|
|
24209
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
24210
|
+
viewBox: "0 0 64 64",
|
|
24211
|
+
width: size,
|
|
24212
|
+
height: size,
|
|
24213
|
+
"aria-hidden": true,
|
|
24214
|
+
style: { display: "block", ...style },
|
|
24215
|
+
children: [
|
|
24216
|
+
/* @__PURE__ */ jsx(
|
|
24217
|
+
"text",
|
|
24218
|
+
{
|
|
24219
|
+
x: "32",
|
|
24220
|
+
y: "46",
|
|
24221
|
+
textAnchor: "middle",
|
|
24222
|
+
fontFamily: "Georgia, serif",
|
|
24223
|
+
fontStyle: "italic",
|
|
24224
|
+
fontSize: "42",
|
|
24225
|
+
fill: letterColor,
|
|
24226
|
+
children: "S"
|
|
24227
|
+
}
|
|
24228
|
+
),
|
|
24229
|
+
/* @__PURE__ */ jsx("circle", { cx: "49", cy: "17", r: "5", fill: dotColor })
|
|
24230
|
+
]
|
|
24231
|
+
}
|
|
24232
|
+
);
|
|
24233
|
+
}
|
|
24234
|
+
const BLEKK = "#211711";
|
|
24235
|
+
const CREMA = "#F2EBDF";
|
|
24236
|
+
const TERRACOTTA = "#C4502A";
|
|
24237
|
+
function EditToolbar() {
|
|
24238
|
+
const { store, themeStore, assetStore, api, config, supabase } = useSetto();
|
|
24239
|
+
const { i18n } = useTranslation();
|
|
24240
|
+
useEditBaseline();
|
|
24241
|
+
const snap = useSyncExternalStore(
|
|
24242
|
+
(cb) => store?.subscribe(() => cb()) ?? (() => {
|
|
24243
|
+
}),
|
|
24244
|
+
() => store?.snapshot() ?? EMPTY_I18N_STORE_SNAPSHOT,
|
|
24245
|
+
() => EMPTY_I18N_STORE_SNAPSHOT
|
|
24246
|
+
);
|
|
24247
|
+
useSyncExternalStore(
|
|
24248
|
+
(cb) => themeStore?.subscribe(() => cb()) ?? (() => {
|
|
24249
|
+
}),
|
|
24250
|
+
() => themeStore?.snapshot().version ?? 0,
|
|
24251
|
+
() => 0
|
|
24252
|
+
);
|
|
24253
|
+
useSyncExternalStore(
|
|
24254
|
+
(cb) => assetStore?.subscribe(() => cb()) ?? (() => {
|
|
24255
|
+
}),
|
|
24256
|
+
() => assetStore?.snapshot().version ?? 0,
|
|
24257
|
+
() => 0
|
|
24258
|
+
);
|
|
24259
|
+
const [publishing, setPublishing] = useState(false);
|
|
24260
|
+
const [activeDeployment, setActiveDeployment] = useState(null);
|
|
24261
|
+
const [publishDialogOpen, setPublishDialogOpen] = useState(false);
|
|
24262
|
+
const [error, setError] = useState(null);
|
|
24263
|
+
const [publishedNotice, setPublishedNotice] = useState(false);
|
|
24264
|
+
const [menuOpen, setMenuOpen] = useState(false);
|
|
24265
|
+
const [expanded, setExpanded] = useState(false);
|
|
24266
|
+
const menuRef = useRef(null);
|
|
24267
|
+
const publishedNoticeTimer = useRef(null);
|
|
24268
|
+
const { row: deploymentRow, status: deploymentStatus } = useDeploymentStatus(
|
|
24269
|
+
supabase,
|
|
24270
|
+
activeDeployment
|
|
24271
|
+
);
|
|
24272
|
+
useEffect(() => {
|
|
24273
|
+
let cancelled = false;
|
|
24274
|
+
const resume = async () => {
|
|
24275
|
+
try {
|
|
24276
|
+
await api.expireStaleDeployments(config.siteId);
|
|
24277
|
+
} catch {
|
|
24278
|
+
}
|
|
24279
|
+
const id = await fetchInProgressDeploymentId(supabase, config.siteId);
|
|
24280
|
+
if (!cancelled && id) {
|
|
24281
|
+
setActiveDeployment((current) => current ?? id);
|
|
24282
|
+
}
|
|
24283
|
+
};
|
|
24284
|
+
void resume();
|
|
24285
|
+
return () => {
|
|
24286
|
+
cancelled = true;
|
|
24287
|
+
};
|
|
24288
|
+
}, [api, supabase, config.siteId]);
|
|
24289
|
+
const textDraftCount = snap.drafts.size;
|
|
24290
|
+
const themeDraftCount = themeStore?.size() ?? 0;
|
|
24291
|
+
const assetDraftCount = assetStore?.size() ?? 0;
|
|
24292
|
+
const draftCount = textDraftCount + themeDraftCount + assetDraftCount;
|
|
24293
|
+
const hasDrafts = draftCount > 0;
|
|
24294
|
+
const showToolbarProgress = activeDeployment !== null && !publishDialogOpen && deploymentRow !== null && isDeploymentInProgress(deploymentRow.status) && !isDeploymentStale(deploymentRow);
|
|
24295
|
+
const showPublishedNotice = publishedNotice && !publishDialogOpen;
|
|
24296
|
+
useEffect(() => {
|
|
24297
|
+
if (error || showToolbarProgress || showPublishedNotice) {
|
|
24298
|
+
setExpanded(true);
|
|
24299
|
+
}
|
|
24300
|
+
}, [error, showToolbarProgress, showPublishedNotice]);
|
|
24301
|
+
const toggleExpanded = () => {
|
|
24302
|
+
setExpanded((prev) => {
|
|
24303
|
+
const next = !prev;
|
|
24304
|
+
if (!next) setMenuOpen(false);
|
|
24305
|
+
return next;
|
|
24306
|
+
});
|
|
24307
|
+
};
|
|
24308
|
+
useEffect(() => {
|
|
24309
|
+
return () => {
|
|
24310
|
+
if (publishedNoticeTimer.current) clearTimeout(publishedNoticeTimer.current);
|
|
24311
|
+
};
|
|
24312
|
+
}, []);
|
|
24313
|
+
useEffect(() => {
|
|
24314
|
+
if (!activeDeployment || !deploymentRow) return;
|
|
24315
|
+
const { status } = deploymentRow;
|
|
24316
|
+
if (status === "ready") {
|
|
24317
|
+
if (publishDialogOpen) return;
|
|
24318
|
+
setPublishedNotice(true);
|
|
24319
|
+
setActiveDeployment(null);
|
|
24320
|
+
if (publishedNoticeTimer.current) clearTimeout(publishedNoticeTimer.current);
|
|
24321
|
+
publishedNoticeTimer.current = setTimeout(() => setPublishedNotice(false), 6e3);
|
|
24322
|
+
return;
|
|
24323
|
+
}
|
|
24324
|
+
if (publishDialogOpen) return;
|
|
24325
|
+
if (status === "error" || status === "canceled") {
|
|
24326
|
+
setError(
|
|
24327
|
+
status === "canceled" ? "Publisering avbrutt." : deploymentRow.error_message?.includes("webhook") ? "Publisering utløpt (ingen webhook)." : "Publisering feilet."
|
|
24328
|
+
);
|
|
24329
|
+
setActiveDeployment(null);
|
|
24330
|
+
return;
|
|
24331
|
+
}
|
|
24332
|
+
if (isDeploymentStale(deploymentRow)) {
|
|
24333
|
+
setError("Publisering utløpt (ingen webhook).");
|
|
24334
|
+
setActiveDeployment(null);
|
|
24335
|
+
}
|
|
24336
|
+
}, [activeDeployment, deploymentRow, publishDialogOpen]);
|
|
24337
|
+
useEffect(() => {
|
|
24338
|
+
if (!activeDeployment) return;
|
|
24339
|
+
const syncActiveDeployment = async () => {
|
|
24340
|
+
const { data } = await supabase.from("deployments").select("status").eq("id", activeDeployment).maybeSingle();
|
|
24341
|
+
if (!data) {
|
|
24342
|
+
setActiveDeployment(null);
|
|
24343
|
+
return;
|
|
24344
|
+
}
|
|
24345
|
+
const status = data.status;
|
|
24346
|
+
if (status === "ready") {
|
|
24347
|
+
if (!publishDialogOpen) {
|
|
24348
|
+
setPublishedNotice(true);
|
|
24349
|
+
if (publishedNoticeTimer.current) clearTimeout(publishedNoticeTimer.current);
|
|
24350
|
+
publishedNoticeTimer.current = setTimeout(() => setPublishedNotice(false), 6e3);
|
|
24351
|
+
}
|
|
24352
|
+
setActiveDeployment(null);
|
|
24353
|
+
return;
|
|
24354
|
+
}
|
|
24355
|
+
if (!isDeploymentInProgress(status)) {
|
|
24356
|
+
if (status === "canceled") setError("Publisering avbrutt.");
|
|
24357
|
+
else if (status === "error") setError("Publisering feilet.");
|
|
24358
|
+
setActiveDeployment(null);
|
|
24359
|
+
}
|
|
24360
|
+
};
|
|
24361
|
+
const onVisible = () => {
|
|
24362
|
+
if (document.visibilityState === "visible") void syncActiveDeployment();
|
|
24363
|
+
};
|
|
24364
|
+
window.addEventListener("focus", syncActiveDeployment);
|
|
24365
|
+
document.addEventListener("visibilitychange", onVisible);
|
|
24366
|
+
return () => {
|
|
24367
|
+
window.removeEventListener("focus", syncActiveDeployment);
|
|
24368
|
+
document.removeEventListener("visibilitychange", onVisible);
|
|
24369
|
+
};
|
|
24370
|
+
}, [activeDeployment, publishDialogOpen, supabase]);
|
|
24371
|
+
const languages = i18n.languages?.length ? Array.from(new Set(i18n.languages)) : ["no", "en"];
|
|
24372
|
+
useEffect(() => {
|
|
24373
|
+
if (!menuOpen) return;
|
|
24374
|
+
const close = (e) => {
|
|
24375
|
+
if (menuRef.current && !menuRef.current.contains(e.target)) {
|
|
24376
|
+
setMenuOpen(false);
|
|
24377
|
+
}
|
|
24378
|
+
};
|
|
24379
|
+
document.addEventListener("mousedown", close);
|
|
24380
|
+
return () => document.removeEventListener("mousedown", close);
|
|
24381
|
+
}, [menuOpen]);
|
|
24382
|
+
const publish = async () => {
|
|
24383
|
+
if (!store) return;
|
|
24384
|
+
setPublishing(true);
|
|
24385
|
+
setPublishDialogOpen(true);
|
|
24386
|
+
setError(null);
|
|
24387
|
+
setPublishedNotice(false);
|
|
24388
|
+
try {
|
|
24389
|
+
const bundles = store.serialiseBundles();
|
|
24390
|
+
const files = Object.entries(bundles).map(([lng, data]) => ({
|
|
24391
|
+
path: pathForLang(config.siteId, lng, languages),
|
|
24392
|
+
content: JSON.stringify(data, null, 2) + "\n"
|
|
24393
|
+
}));
|
|
24394
|
+
if (themeStore && themeDraftCount > 0) {
|
|
24395
|
+
files.push({
|
|
24396
|
+
path: pathForTheme(config.siteId, config.themePath),
|
|
24397
|
+
content: JSON.stringify(themeStore.serialise(), null, 2) + "\n"
|
|
24398
|
+
});
|
|
24399
|
+
}
|
|
24400
|
+
if (assetStore && assetDraftCount > 0) {
|
|
24401
|
+
const result2 = await api.publish(config.siteId, files, {
|
|
24402
|
+
assets: assetStore.pendingAssets()
|
|
24403
|
+
});
|
|
24404
|
+
store.commit();
|
|
24405
|
+
themeStore?.commit();
|
|
24406
|
+
assetStore.commit();
|
|
24407
|
+
let deploymentId2 = result2.deploymentId;
|
|
24408
|
+
if (!deploymentId2 && result2.trackingDeferred) {
|
|
24409
|
+
deploymentId2 = await waitForDeploymentByCommit(
|
|
24410
|
+
supabase,
|
|
24411
|
+
config.siteId,
|
|
24412
|
+
result2.commitSha
|
|
24413
|
+
);
|
|
24414
|
+
}
|
|
24415
|
+
if (deploymentId2) {
|
|
24416
|
+
setActiveDeployment(deploymentId2);
|
|
24417
|
+
} else {
|
|
24418
|
+
setPublishDialogOpen(false);
|
|
24419
|
+
setPublishedNotice(true);
|
|
24420
|
+
}
|
|
24421
|
+
return;
|
|
24422
|
+
}
|
|
24423
|
+
const result = await api.publish(config.siteId, files);
|
|
24424
|
+
store.commit();
|
|
24425
|
+
themeStore?.commit();
|
|
24426
|
+
assetStore?.commit();
|
|
24427
|
+
let deploymentId = result.deploymentId;
|
|
24428
|
+
if (!deploymentId && result.trackingDeferred) {
|
|
24429
|
+
deploymentId = await waitForDeploymentByCommit(
|
|
24430
|
+
supabase,
|
|
24431
|
+
config.siteId,
|
|
24432
|
+
result.commitSha
|
|
24433
|
+
);
|
|
24434
|
+
}
|
|
24435
|
+
if (deploymentId) {
|
|
24436
|
+
setActiveDeployment(deploymentId);
|
|
24437
|
+
} else {
|
|
24438
|
+
setPublishDialogOpen(false);
|
|
24439
|
+
setPublishedNotice(true);
|
|
24440
|
+
}
|
|
24441
|
+
} catch (err) {
|
|
24442
|
+
setError(err instanceof Error ? err.message : "unknown error");
|
|
24443
|
+
setPublishDialogOpen(false);
|
|
24444
|
+
} finally {
|
|
24445
|
+
setPublishing(false);
|
|
24446
|
+
}
|
|
24447
|
+
};
|
|
24448
|
+
const completePublish = () => {
|
|
24449
|
+
setPublishDialogOpen(false);
|
|
24450
|
+
setActiveDeployment(null);
|
|
24451
|
+
setPublishedNotice(false);
|
|
24452
|
+
if (publishedNoticeTimer.current) clearTimeout(publishedNoticeTimer.current);
|
|
24453
|
+
};
|
|
24454
|
+
const dismissPublishedNotice = () => {
|
|
24455
|
+
setPublishedNotice(false);
|
|
24456
|
+
if (publishedNoticeTimer.current) clearTimeout(publishedNoticeTimer.current);
|
|
24457
|
+
};
|
|
24458
|
+
const cancelDrafts = () => {
|
|
24459
|
+
setMenuOpen(false);
|
|
24460
|
+
store?.revertAll();
|
|
24461
|
+
themeStore?.revertAll();
|
|
24462
|
+
assetStore?.revertAll();
|
|
24463
|
+
setError(null);
|
|
24464
|
+
setExpanded(false);
|
|
24465
|
+
};
|
|
24466
|
+
const signOut = () => {
|
|
24467
|
+
setMenuOpen(false);
|
|
24468
|
+
void supabase.auth.signOut();
|
|
24469
|
+
};
|
|
24470
|
+
const goToHistory = () => {
|
|
24471
|
+
setMenuOpen(false);
|
|
24472
|
+
window.location.href = SETTO_BASE;
|
|
24473
|
+
};
|
|
24474
|
+
const goToSettings = () => {
|
|
24475
|
+
setMenuOpen(false);
|
|
24476
|
+
window.location.href = adminViewUrl(SETTO_MODULES[0]?.id ?? "forms");
|
|
24477
|
+
};
|
|
24478
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
24479
|
+
/* @__PURE__ */ jsxs(
|
|
24480
|
+
"div",
|
|
24481
|
+
{
|
|
24482
|
+
role: "toolbar",
|
|
24483
|
+
"aria-label": "Setto editor",
|
|
24484
|
+
style: {
|
|
24485
|
+
...containerStyleFor(hasDrafts),
|
|
24486
|
+
...expanded ? expandedContainerStyle : {}
|
|
24487
|
+
},
|
|
24488
|
+
children: [
|
|
24489
|
+
/* @__PURE__ */ jsx(
|
|
24490
|
+
"button",
|
|
24491
|
+
{
|
|
24492
|
+
type: "button",
|
|
24493
|
+
"aria-label": expanded ? "Lukk Setto-meny" : "Åpne Setto-meny",
|
|
24494
|
+
"aria-expanded": expanded,
|
|
24495
|
+
title: "Setto",
|
|
24496
|
+
style: markButtonStyle,
|
|
24497
|
+
onClick: toggleExpanded,
|
|
24498
|
+
children: /* @__PURE__ */ jsx(SettoMark, { size: 28 })
|
|
24499
|
+
}
|
|
24500
|
+
),
|
|
24501
|
+
expanded ? /* @__PURE__ */ jsxs("div", { style: contentStyle, children: [
|
|
24502
|
+
error ? /* @__PURE__ */ jsx("span", { style: errorStyle, children: error }) : null,
|
|
24503
|
+
hasDrafts && !publishing && activeDeployment === null ? /* @__PURE__ */ jsx("button", { type: "button", onClick: publish, style: publishBtnStyle, children: "Publiser" }) : null,
|
|
24504
|
+
showPublishedNotice ? /* @__PURE__ */ jsxs(
|
|
24505
|
+
"button",
|
|
24506
|
+
{
|
|
24507
|
+
type: "button",
|
|
24508
|
+
onClick: dismissPublishedNotice,
|
|
24509
|
+
style: publishedNoticeStyle,
|
|
24510
|
+
title: "Lukk",
|
|
24511
|
+
children: [
|
|
24512
|
+
/* @__PURE__ */ jsx(PublishedCheckIcon, {}),
|
|
24513
|
+
"Publisert"
|
|
24514
|
+
]
|
|
24515
|
+
}
|
|
24516
|
+
) : null,
|
|
24517
|
+
showToolbarProgress && deploymentRow ? /* @__PURE__ */ jsx(
|
|
24518
|
+
PublishProgressBar,
|
|
24519
|
+
{
|
|
24520
|
+
status: deploymentRow.status,
|
|
24521
|
+
compact: true,
|
|
24522
|
+
href: `${SETTO_BASE}?deployment=${encodeURIComponent(activeDeployment)}`
|
|
24523
|
+
}
|
|
24524
|
+
) : null,
|
|
24525
|
+
!hasDrafts && !error && !showPublishedNotice && !showToolbarProgress && !publishing ? /* @__PURE__ */ jsx("span", { style: emptyStateStyle, children: "Ingen endringer" }) : null,
|
|
24526
|
+
/* @__PURE__ */ jsxs("div", { ref: menuRef, style: { position: "relative" }, children: [
|
|
24527
|
+
/* @__PURE__ */ jsx(
|
|
24528
|
+
"button",
|
|
24529
|
+
{
|
|
24530
|
+
type: "button",
|
|
24531
|
+
onClick: () => setMenuOpen((o) => !o),
|
|
24532
|
+
"aria-label": "Meny",
|
|
24533
|
+
"aria-expanded": menuOpen,
|
|
24534
|
+
style: {
|
|
24535
|
+
...menuBtnStyle,
|
|
24536
|
+
background: menuOpen ? "rgba(242, 235, 223, 0.16)" : "transparent"
|
|
24537
|
+
},
|
|
24538
|
+
onMouseEnter: (e) => {
|
|
24539
|
+
e.currentTarget.style.background = "rgba(242, 235, 223, 0.16)";
|
|
24540
|
+
},
|
|
24541
|
+
onMouseLeave: (e) => {
|
|
24542
|
+
e.currentTarget.style.background = menuOpen ? "rgba(242, 235, 223, 0.16)" : "transparent";
|
|
24543
|
+
},
|
|
24544
|
+
children: "⋮"
|
|
24545
|
+
}
|
|
24546
|
+
),
|
|
24547
|
+
menuOpen ? /* @__PURE__ */ jsxs("div", { style: dropdownStyle, role: "menu", children: [
|
|
24548
|
+
/* @__PURE__ */ jsx(
|
|
24549
|
+
MenuItem,
|
|
24550
|
+
{
|
|
24551
|
+
onClick: cancelDrafts,
|
|
24552
|
+
icon: /* @__PURE__ */ jsx(UndoIcon, {}),
|
|
24553
|
+
disabled: !hasDrafts,
|
|
24554
|
+
children: "Avbryt"
|
|
24555
|
+
}
|
|
24556
|
+
),
|
|
24557
|
+
/* @__PURE__ */ jsx(MenuItem, { onClick: goToHistory, icon: /* @__PURE__ */ jsx(HistoryIcon, {}), children: "Historikk" }),
|
|
24558
|
+
/* @__PURE__ */ jsx(MenuItem, { onClick: goToSettings, icon: /* @__PURE__ */ jsx(GearIcon, {}), children: "Innstillinger" }),
|
|
24559
|
+
/* @__PURE__ */ jsx(MenuItem, { onClick: signOut, icon: /* @__PURE__ */ jsx(LogOutIcon, {}), children: "Logg ut" })
|
|
24560
|
+
] }) : null
|
|
24561
|
+
] })
|
|
24562
|
+
] }) : null
|
|
24563
|
+
]
|
|
24564
|
+
}
|
|
24565
|
+
),
|
|
24566
|
+
/* @__PURE__ */ jsx(EditOnboardingDialog, {}),
|
|
24567
|
+
publishDialogOpen && (publishing || activeDeployment !== null) ? /* @__PURE__ */ jsx(
|
|
24568
|
+
PublishDialog,
|
|
24569
|
+
{
|
|
24570
|
+
deploymentId: activeDeployment ?? "",
|
|
24571
|
+
row: deploymentRow,
|
|
24572
|
+
status: deploymentStatus ?? "pending",
|
|
24573
|
+
publishing,
|
|
24574
|
+
onClose: () => setPublishDialogOpen(false),
|
|
24575
|
+
onComplete: completePublish
|
|
24576
|
+
}
|
|
24577
|
+
) : null
|
|
24578
|
+
] });
|
|
24579
|
+
}
|
|
24580
|
+
const EASE_OUT_EXPO = "cubic-bezier(0.16, 1, 0.3, 1)";
|
|
24581
|
+
const FAB_SHADOW = "0 8px 24px rgba(33, 23, 17, 0.35), 0 2px 6px rgba(33, 23, 17, 0.2)";
|
|
24582
|
+
function containerStyleFor(hasDrafts) {
|
|
24583
|
+
return {
|
|
24584
|
+
display: "inline-flex",
|
|
24585
|
+
alignItems: "center",
|
|
24586
|
+
height: FAB_SIZE,
|
|
24587
|
+
width: FAB_SIZE,
|
|
24588
|
+
padding: 0,
|
|
24589
|
+
background: BLEKK,
|
|
24590
|
+
color: CREMA,
|
|
24591
|
+
borderRadius: FAB_SIZE / 2,
|
|
24592
|
+
boxShadow: hasDrafts ? `0 0 0 2px ${TERRACOTTA}, ${FAB_SHADOW}` : FAB_SHADOW,
|
|
24593
|
+
fontFamily: 'system-ui, -apple-system, "Segoe UI", sans-serif',
|
|
24594
|
+
fontSize: 13,
|
|
24595
|
+
overflow: "visible",
|
|
24596
|
+
transition: `width 500ms ${EASE_OUT_EXPO}, padding 500ms ${EASE_OUT_EXPO}, box-shadow 300ms ${EASE_OUT_EXPO}`
|
|
24597
|
+
};
|
|
24598
|
+
}
|
|
24599
|
+
const expandedContainerStyle = {
|
|
24600
|
+
width: "auto",
|
|
24601
|
+
paddingRight: 10
|
|
24602
|
+
};
|
|
24603
|
+
const markButtonStyle = {
|
|
24604
|
+
display: "inline-flex",
|
|
24605
|
+
alignItems: "center",
|
|
24606
|
+
justifyContent: "center",
|
|
24607
|
+
flexShrink: 0,
|
|
24608
|
+
width: FAB_SIZE,
|
|
24609
|
+
height: FAB_SIZE,
|
|
24610
|
+
background: "transparent",
|
|
24611
|
+
border: "none",
|
|
24612
|
+
padding: 0,
|
|
24613
|
+
cursor: "pointer",
|
|
24614
|
+
color: "inherit"
|
|
24615
|
+
};
|
|
24616
|
+
const contentStyle = {
|
|
24617
|
+
display: "flex",
|
|
24618
|
+
alignItems: "center",
|
|
24619
|
+
gap: 6,
|
|
24620
|
+
paddingLeft: 4,
|
|
24621
|
+
whiteSpace: "nowrap"
|
|
24622
|
+
};
|
|
24623
|
+
const errorStyle = {
|
|
24624
|
+
color: "#ffb4a8",
|
|
24625
|
+
fontSize: 12,
|
|
24626
|
+
maxWidth: 240,
|
|
24627
|
+
overflow: "hidden",
|
|
24628
|
+
textOverflow: "ellipsis"
|
|
24629
|
+
};
|
|
24630
|
+
const publishedNoticeStyle = {
|
|
24631
|
+
display: "inline-flex",
|
|
24632
|
+
alignItems: "center",
|
|
24633
|
+
gap: 5,
|
|
24634
|
+
padding: "4px 10px",
|
|
24635
|
+
borderRadius: 999,
|
|
24636
|
+
border: "1px solid rgba(242, 235, 223, 0.25)",
|
|
24637
|
+
background: "rgba(242, 235, 223, 0.08)",
|
|
24638
|
+
color: CREMA,
|
|
24639
|
+
fontSize: 12,
|
|
24640
|
+
fontWeight: 500,
|
|
24641
|
+
cursor: "pointer",
|
|
24642
|
+
whiteSpace: "nowrap"
|
|
24643
|
+
};
|
|
24644
|
+
const publishBtnStyle = {
|
|
24645
|
+
background: TERRACOTTA,
|
|
24646
|
+
color: CREMA,
|
|
24647
|
+
border: "none",
|
|
24648
|
+
borderRadius: 999,
|
|
24649
|
+
padding: "6px 14px",
|
|
24650
|
+
fontSize: 13,
|
|
24651
|
+
fontWeight: 600,
|
|
24652
|
+
cursor: "pointer",
|
|
24653
|
+
whiteSpace: "nowrap"
|
|
24654
|
+
};
|
|
24655
|
+
const menuBtnStyle = {
|
|
24656
|
+
background: "transparent",
|
|
24657
|
+
color: CREMA,
|
|
24658
|
+
border: "none",
|
|
24659
|
+
borderRadius: 999,
|
|
24660
|
+
width: 32,
|
|
24661
|
+
height: 32,
|
|
24662
|
+
display: "inline-flex",
|
|
24663
|
+
alignItems: "center",
|
|
24664
|
+
justifyContent: "center",
|
|
24665
|
+
fontSize: 22,
|
|
24666
|
+
lineHeight: 1,
|
|
24667
|
+
cursor: "pointer",
|
|
24668
|
+
fontWeight: 700
|
|
24669
|
+
};
|
|
24670
|
+
const emptyStateStyle = {
|
|
24671
|
+
color: "rgba(242, 235, 223, 0.55)",
|
|
24672
|
+
fontSize: 13,
|
|
24673
|
+
whiteSpace: "nowrap",
|
|
24674
|
+
padding: "0 4px"
|
|
24675
|
+
};
|
|
24676
|
+
const dropdownStyle = {
|
|
24677
|
+
position: "absolute",
|
|
24678
|
+
bottom: "100%",
|
|
24679
|
+
left: 0,
|
|
24680
|
+
marginBottom: 8,
|
|
24681
|
+
background: "#fff",
|
|
24682
|
+
border: "1px solid #e8e8e8",
|
|
24683
|
+
borderRadius: 10,
|
|
24684
|
+
boxShadow: "0 12px 32px rgba(33, 23, 17, 0.18)",
|
|
24685
|
+
minWidth: 160,
|
|
24686
|
+
overflow: "hidden",
|
|
24687
|
+
zIndex: 1
|
|
24688
|
+
};
|
|
24689
|
+
const menuItemStyle = {
|
|
24690
|
+
display: "flex",
|
|
24691
|
+
alignItems: "center",
|
|
24692
|
+
gap: 8,
|
|
24693
|
+
width: "100%",
|
|
24694
|
+
background: "transparent",
|
|
24695
|
+
border: "none",
|
|
24696
|
+
padding: "10px 14px",
|
|
24697
|
+
fontSize: 13,
|
|
24698
|
+
textAlign: "left",
|
|
24699
|
+
cursor: "pointer",
|
|
24700
|
+
color: "#1a1a1a"
|
|
24701
|
+
};
|
|
24702
|
+
function MenuItem({
|
|
24703
|
+
onClick,
|
|
24704
|
+
icon,
|
|
24705
|
+
children,
|
|
24706
|
+
disabled = false
|
|
24707
|
+
}) {
|
|
24708
|
+
return /* @__PURE__ */ jsxs(
|
|
24709
|
+
"button",
|
|
24710
|
+
{
|
|
24711
|
+
type: "button",
|
|
24712
|
+
onClick,
|
|
24713
|
+
disabled,
|
|
24714
|
+
style: { ...menuItemStyle, opacity: disabled ? 0.4 : 1, cursor: disabled ? "not-allowed" : "pointer" },
|
|
24715
|
+
role: "menuitem",
|
|
24716
|
+
onMouseEnter: (e) => {
|
|
24717
|
+
if (!disabled) e.currentTarget.style.background = "#f5f5f5";
|
|
24718
|
+
},
|
|
24719
|
+
onMouseLeave: (e) => {
|
|
24720
|
+
e.currentTarget.style.background = "transparent";
|
|
24721
|
+
},
|
|
24722
|
+
children: [
|
|
24723
|
+
/* @__PURE__ */ jsx("span", { style: menuIconWrapStyle, "aria-hidden": true, children: icon }),
|
|
24724
|
+
children
|
|
24725
|
+
]
|
|
24726
|
+
}
|
|
24727
|
+
);
|
|
24728
|
+
}
|
|
24729
|
+
const menuIconWrapStyle = {
|
|
24730
|
+
display: "flex",
|
|
24731
|
+
alignItems: "center",
|
|
24732
|
+
justifyContent: "center",
|
|
24733
|
+
flexShrink: 0,
|
|
24734
|
+
color: "#666"
|
|
24735
|
+
};
|
|
24736
|
+
function PublishedCheckIcon() {
|
|
24737
|
+
return /* @__PURE__ */ jsx(
|
|
24738
|
+
"svg",
|
|
24739
|
+
{
|
|
24740
|
+
width: "14",
|
|
24741
|
+
height: "14",
|
|
24742
|
+
viewBox: "0 0 24 24",
|
|
24743
|
+
fill: "none",
|
|
24744
|
+
stroke: "currentColor",
|
|
24745
|
+
strokeWidth: "2.5",
|
|
24746
|
+
strokeLinecap: "round",
|
|
24747
|
+
strokeLinejoin: "round",
|
|
24748
|
+
"aria-hidden": true,
|
|
24749
|
+
children: /* @__PURE__ */ jsx("polyline", { points: "20 6 9 17 4 12" })
|
|
24750
|
+
}
|
|
24751
|
+
);
|
|
24752
|
+
}
|
|
24753
|
+
function HistoryIcon() {
|
|
24754
|
+
return /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
24755
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10" }),
|
|
24756
|
+
/* @__PURE__ */ jsx("polyline", { points: "12 6 12 12 16 14" })
|
|
24757
|
+
] });
|
|
24758
|
+
}
|
|
24759
|
+
function GearIcon() {
|
|
24760
|
+
return /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
24761
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "3" }),
|
|
24762
|
+
/* @__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" })
|
|
24763
|
+
] });
|
|
24764
|
+
}
|
|
24765
|
+
function LogOutIcon() {
|
|
24766
|
+
return /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
24767
|
+
/* @__PURE__ */ jsx("path", { d: "M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" }),
|
|
24768
|
+
/* @__PURE__ */ jsx("polyline", { points: "16 17 21 12 16 7" }),
|
|
24769
|
+
/* @__PURE__ */ jsx("line", { x1: "21", y1: "12", x2: "9", y2: "12" })
|
|
24770
|
+
] });
|
|
24771
|
+
}
|
|
24772
|
+
function UndoIcon() {
|
|
24773
|
+
return /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
24774
|
+
/* @__PURE__ */ jsx("path", { d: "M3 7v6h6" }),
|
|
24775
|
+
/* @__PURE__ */ jsx("path", { d: "M21 17a9 9 0 0 0-15-6.7L3 13" })
|
|
24776
|
+
] });
|
|
24777
|
+
}
|
|
24778
|
+
function EditModeShell({ children, themeStore, active }) {
|
|
24779
|
+
useSettoDocumentLayout(active);
|
|
24780
|
+
const [toolbarRoot, setToolbarRoot] = useState(null);
|
|
24781
|
+
const [hintRoot, setHintRoot] = useState(null);
|
|
24782
|
+
const [sectionToolbarRoot, setSectionToolbarRoot] = useState(null);
|
|
24783
|
+
useEffect(() => {
|
|
24784
|
+
if (!active) {
|
|
24785
|
+
setToolbarRoot(null);
|
|
24786
|
+
setHintRoot(null);
|
|
24787
|
+
setSectionToolbarRoot(null);
|
|
24788
|
+
return;
|
|
24789
|
+
}
|
|
24790
|
+
const toolbar = document.createElement("div");
|
|
24791
|
+
toolbar.id = "setto-toolbar-root";
|
|
24792
|
+
document.body.appendChild(toolbar);
|
|
24793
|
+
const hint = document.createElement("div");
|
|
24794
|
+
hint.id = "setto-hint-root";
|
|
24795
|
+
hint.style.pointerEvents = "none";
|
|
24796
|
+
document.body.appendChild(hint);
|
|
24797
|
+
const sectionBar = document.createElement("div");
|
|
24798
|
+
sectionBar.id = "setto-section-toolbar-root";
|
|
24799
|
+
sectionBar.style.pointerEvents = "none";
|
|
24800
|
+
document.body.appendChild(sectionBar);
|
|
24801
|
+
setToolbarRoot(toolbar);
|
|
24802
|
+
setHintRoot(hint);
|
|
24803
|
+
setSectionToolbarRoot(sectionBar);
|
|
24804
|
+
return () => {
|
|
24805
|
+
toolbar.remove();
|
|
24806
|
+
hint.remove();
|
|
24807
|
+
sectionBar.remove();
|
|
24808
|
+
};
|
|
24809
|
+
}, [active]);
|
|
24810
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
24811
|
+
children,
|
|
24812
|
+
active && toolbarRoot ? createPortal(/* @__PURE__ */ jsx(EditToolbar, {}), toolbarRoot) : null,
|
|
24813
|
+
active && hintRoot ? createPortal(/* @__PURE__ */ jsx(EditLinkHint, {}), hintRoot) : null,
|
|
24814
|
+
active && sectionToolbarRoot && themeStore ? createPortal(/* @__PURE__ */ jsx(SectionToolbar, {}), sectionToolbarRoot) : null
|
|
24815
|
+
] });
|
|
24816
|
+
}
|
|
24817
|
+
const SettoContext = createContext(null);
|
|
24818
|
+
function SettoProvider({ config, children }) {
|
|
24819
|
+
const supabase = useMemo(() => getSupabase(config.supabase), [config.supabase]);
|
|
24820
|
+
const api = useMemo(() => createApi({ apiUrl: config.apiUrl, supabase }), [config.apiUrl, supabase]);
|
|
24821
|
+
const [session, setSession] = useState(null);
|
|
24822
|
+
const [authLoading, setAuthLoading] = useState(true);
|
|
24823
|
+
const [onAdminRoute, setOnAdminRoute] = useState(
|
|
24824
|
+
() => typeof window === "undefined" ? false : isAdminRoute()
|
|
24825
|
+
);
|
|
24826
|
+
useEffect(() => {
|
|
24827
|
+
let cancelled = false;
|
|
24828
|
+
supabase.auth.getSession().then(({ data }) => {
|
|
24829
|
+
if (cancelled) return;
|
|
24830
|
+
setSession(data.session ?? null);
|
|
24831
|
+
setAuthLoading(false);
|
|
24832
|
+
});
|
|
24833
|
+
const { data: sub } = supabase.auth.onAuthStateChange((_event, session2) => {
|
|
24834
|
+
setSession(session2);
|
|
24835
|
+
});
|
|
24836
|
+
return () => {
|
|
24837
|
+
cancelled = true;
|
|
24838
|
+
sub.subscription.unsubscribe();
|
|
24839
|
+
};
|
|
24840
|
+
}, [supabase]);
|
|
24841
|
+
useEffect(() => {
|
|
24842
|
+
const compute = () => setOnAdminRoute(isAdminRoute());
|
|
24843
|
+
compute();
|
|
24844
|
+
window.addEventListener("popstate", compute);
|
|
24845
|
+
return () => window.removeEventListener("popstate", compute);
|
|
24846
|
+
}, []);
|
|
24847
|
+
const access = useSiteAccess(supabase, session, config.siteId);
|
|
24848
|
+
const { i18n } = useTranslation();
|
|
24849
|
+
const storeRef = useRef(null);
|
|
24850
|
+
const themeStoreRef = useRef(null);
|
|
24851
|
+
const assetStoreRef = useRef(null);
|
|
24852
|
+
const [storeReady, setStoreReady] = useState(false);
|
|
24853
|
+
useEffect(() => {
|
|
24854
|
+
if (!i18n) return;
|
|
24855
|
+
storeRef.current = new I18nStore(i18n);
|
|
24856
|
+
themeStoreRef.current = new ThemeStore(config.theme ?? {});
|
|
24857
|
+
assetStoreRef.current = new AssetStore();
|
|
24858
|
+
setStoreReady(true);
|
|
24859
|
+
}, [i18n, config.theme]);
|
|
24860
|
+
const editMode = !!session && !!access.currentSite && !onAdminRoute && !authLoading && !access.loading;
|
|
24861
|
+
const value = {
|
|
24862
|
+
config,
|
|
24863
|
+
supabase,
|
|
24864
|
+
api,
|
|
24865
|
+
session,
|
|
24866
|
+
authLoading,
|
|
24867
|
+
editMode,
|
|
24868
|
+
currentSite: access.currentSite,
|
|
24869
|
+
sites: access.sites,
|
|
24870
|
+
store: storeReady ? storeRef.current : null,
|
|
24871
|
+
themeStore: storeReady ? themeStoreRef.current : null,
|
|
24872
|
+
assetStore: storeReady ? assetStoreRef.current : null
|
|
24873
|
+
};
|
|
24874
|
+
const editShellActive = editMode && !!value.store;
|
|
24875
|
+
return /* @__PURE__ */ jsx(SettoContext.Provider, { value, children: /* @__PURE__ */ jsx(SectionEditProvider, { children: /* @__PURE__ */ jsx(EditModeShell, { active: editShellActive, themeStore: value.themeStore, children }) }) });
|
|
24876
|
+
}
|
|
24877
|
+
function useSetto() {
|
|
24878
|
+
const ctx = useContext(SettoContext);
|
|
24879
|
+
if (!ctx) {
|
|
24880
|
+
throw new Error("useSetto() must be used inside <SettoProvider>");
|
|
24881
|
+
}
|
|
24882
|
+
return ctx;
|
|
23133
24883
|
}
|
|
23134
24884
|
function isTextEditClick(target) {
|
|
23135
24885
|
if (target.closest("[data-setto-key]")) return true;
|
|
@@ -23207,6 +24957,7 @@ const SettoSection = forwardRef(
|
|
|
23207
24957
|
"section",
|
|
23208
24958
|
{
|
|
23209
24959
|
ref,
|
|
24960
|
+
id: sectionId,
|
|
23210
24961
|
"data-setto-section": sectionId,
|
|
23211
24962
|
className,
|
|
23212
24963
|
style: mergedStyle,
|
|
@@ -23258,99 +25009,6 @@ const SettoBlock = forwardRef(
|
|
|
23258
25009
|
);
|
|
23259
25010
|
}
|
|
23260
25011
|
);
|
|
23261
|
-
function FloatingPopover({
|
|
23262
|
-
open,
|
|
23263
|
-
anchorRef,
|
|
23264
|
-
onClose,
|
|
23265
|
-
ariaLabel,
|
|
23266
|
-
children,
|
|
23267
|
-
style,
|
|
23268
|
-
minWidth = 280
|
|
23269
|
-
}) {
|
|
23270
|
-
const popoverRef = useRef(null);
|
|
23271
|
-
const [pos, setPos] = useState(null);
|
|
23272
|
-
useLayoutEffect(() => {
|
|
23273
|
-
if (!open || !anchorRef.current) {
|
|
23274
|
-
setPos(null);
|
|
23275
|
-
return;
|
|
23276
|
-
}
|
|
23277
|
-
const update = () => {
|
|
23278
|
-
const anchor = anchorRef.current;
|
|
23279
|
-
const pop = popoverRef.current;
|
|
23280
|
-
if (!anchor) return;
|
|
23281
|
-
const rect = anchor.getBoundingClientRect();
|
|
23282
|
-
const popoverH = pop?.offsetHeight ?? 200;
|
|
23283
|
-
const popoverW = pop?.offsetWidth ?? minWidth;
|
|
23284
|
-
let top = rect.bottom + 6;
|
|
23285
|
-
let left = rect.left;
|
|
23286
|
-
if (left + popoverW > window.innerWidth - 8) {
|
|
23287
|
-
left = window.innerWidth - popoverW - 8;
|
|
23288
|
-
}
|
|
23289
|
-
if (left < 8) left = 8;
|
|
23290
|
-
if (top + popoverH > window.innerHeight - 8) {
|
|
23291
|
-
top = rect.top - popoverH - 6;
|
|
23292
|
-
}
|
|
23293
|
-
if (top < 8) top = rect.bottom + 6;
|
|
23294
|
-
setPos({ top, left });
|
|
23295
|
-
};
|
|
23296
|
-
update();
|
|
23297
|
-
requestAnimationFrame(update);
|
|
23298
|
-
window.addEventListener("scroll", update, true);
|
|
23299
|
-
window.addEventListener("resize", update);
|
|
23300
|
-
return () => {
|
|
23301
|
-
window.removeEventListener("scroll", update, true);
|
|
23302
|
-
window.removeEventListener("resize", update);
|
|
23303
|
-
};
|
|
23304
|
-
}, [open, anchorRef, minWidth]);
|
|
23305
|
-
useEffect(() => {
|
|
23306
|
-
if (!open) return;
|
|
23307
|
-
const onKey = (e) => {
|
|
23308
|
-
if (e.key === "Escape") onClose();
|
|
23309
|
-
};
|
|
23310
|
-
window.addEventListener("keydown", onKey);
|
|
23311
|
-
return () => window.removeEventListener("keydown", onKey);
|
|
23312
|
-
}, [open, onClose]);
|
|
23313
|
-
useEffect(() => {
|
|
23314
|
-
if (!open) return;
|
|
23315
|
-
const close = (e) => {
|
|
23316
|
-
const target = e.target;
|
|
23317
|
-
if (anchorRef.current?.contains(target)) return;
|
|
23318
|
-
if (popoverRef.current?.contains(target)) return;
|
|
23319
|
-
onClose();
|
|
23320
|
-
};
|
|
23321
|
-
document.addEventListener("mousedown", close);
|
|
23322
|
-
return () => document.removeEventListener("mousedown", close);
|
|
23323
|
-
}, [open, onClose, anchorRef]);
|
|
23324
|
-
if (!open) return null;
|
|
23325
|
-
return createPortal(
|
|
23326
|
-
/* @__PURE__ */ jsx(
|
|
23327
|
-
"div",
|
|
23328
|
-
{
|
|
23329
|
-
ref: popoverRef,
|
|
23330
|
-
role: "dialog",
|
|
23331
|
-
"aria-label": ariaLabel,
|
|
23332
|
-
"data-setto-ui": true,
|
|
23333
|
-
style: {
|
|
23334
|
-
position: "fixed",
|
|
23335
|
-
top: pos?.top ?? -9999,
|
|
23336
|
-
left: pos?.left ?? 0,
|
|
23337
|
-
visibility: pos ? "visible" : "hidden",
|
|
23338
|
-
zIndex: 2147483647,
|
|
23339
|
-
background: "#fff",
|
|
23340
|
-
border: "1px solid #e0e0e0",
|
|
23341
|
-
borderRadius: 8,
|
|
23342
|
-
boxShadow: "0 8px 24px rgba(0,0,0,0.15)",
|
|
23343
|
-
fontFamily: 'system-ui, -apple-system, "Segoe UI", sans-serif',
|
|
23344
|
-
...style
|
|
23345
|
-
},
|
|
23346
|
-
onMouseDown: (e) => e.stopPropagation(),
|
|
23347
|
-
onClick: (e) => e.stopPropagation(),
|
|
23348
|
-
children
|
|
23349
|
-
}
|
|
23350
|
-
),
|
|
23351
|
-
document.body
|
|
23352
|
-
);
|
|
23353
|
-
}
|
|
23354
25012
|
function SettoIcon({ k, icons, size = 24, className, style }) {
|
|
23355
25013
|
const { t, i18n } = useTranslation();
|
|
23356
25014
|
const { editMode, store } = useSetto();
|
|
@@ -23716,25 +25374,27 @@ function SettoRepeater({
|
|
|
23716
25374
|
};
|
|
23717
25375
|
const canRemove = keys.length > 1;
|
|
23718
25376
|
return /* @__PURE__ */ jsxs("div", { className, "data-setto-repeater": true, children: [
|
|
23719
|
-
keys.map(
|
|
23720
|
-
|
|
23721
|
-
|
|
23722
|
-
|
|
23723
|
-
|
|
23724
|
-
|
|
23725
|
-
|
|
23726
|
-
|
|
23727
|
-
|
|
23728
|
-
|
|
23729
|
-
e
|
|
23730
|
-
|
|
23731
|
-
|
|
23732
|
-
|
|
23733
|
-
|
|
23734
|
-
|
|
23735
|
-
|
|
23736
|
-
|
|
23737
|
-
|
|
25377
|
+
keys.map(
|
|
25378
|
+
(itemKey, index) => editable ? /* @__PURE__ */ jsxs("div", { className: "setto-repeater-item", style: { position: "relative" }, children: [
|
|
25379
|
+
canRemove ? /* @__PURE__ */ jsx(
|
|
25380
|
+
"button",
|
|
25381
|
+
{
|
|
25382
|
+
type: "button",
|
|
25383
|
+
"data-setto-ui": true,
|
|
25384
|
+
className: "setto-repeater-remove",
|
|
25385
|
+
"aria-label": `Fjern ${itemLabel}`,
|
|
25386
|
+
title: `Fjern ${itemLabel}`,
|
|
25387
|
+
onClick: (e) => {
|
|
25388
|
+
e.stopPropagation();
|
|
25389
|
+
handleRemove(itemKey);
|
|
25390
|
+
},
|
|
25391
|
+
style: removeBtnStyle,
|
|
25392
|
+
children: "×"
|
|
25393
|
+
}
|
|
25394
|
+
) : null,
|
|
25395
|
+
children(itemKey, index)
|
|
25396
|
+
] }, itemKey) : /* @__PURE__ */ jsx(Fragment$1, { children: children(itemKey, index) }, itemKey)
|
|
25397
|
+
),
|
|
23738
25398
|
editable ? /* @__PURE__ */ jsx(
|
|
23739
25399
|
"div",
|
|
23740
25400
|
{
|
|
@@ -23784,95 +25444,17 @@ const removeBtnStyle = {
|
|
|
23784
25444
|
justifyContent: "center",
|
|
23785
25445
|
width: 24,
|
|
23786
25446
|
height: 24,
|
|
23787
|
-
padding: 0,
|
|
23788
|
-
background: "#ffffff",
|
|
23789
|
-
border: "1px solid #e0e0e0",
|
|
23790
|
-
borderRadius: "50%",
|
|
23791
|
-
fontSize: 16,
|
|
23792
|
-
lineHeight: 1,
|
|
23793
|
-
color: "#444",
|
|
23794
|
-
cursor: "pointer",
|
|
23795
|
-
boxShadow: "0 1px 4px rgba(0,0,0,0.18)",
|
|
23796
|
-
fontFamily: 'system-ui, -apple-system, "Segoe UI", sans-serif'
|
|
23797
|
-
};
|
|
23798
|
-
function SettoForm({
|
|
23799
|
-
formId,
|
|
23800
|
-
fields,
|
|
23801
|
-
endpoint,
|
|
23802
|
-
endpointKey,
|
|
23803
|
-
className
|
|
23804
|
-
}) {
|
|
23805
|
-
const { t } = useTranslation();
|
|
23806
|
-
const [status, setStatus] = useState("idle");
|
|
23807
|
-
const [values, setValues] = useState(
|
|
23808
|
-
() => Object.fromEntries(fields.map((f) => [f.name, ""]))
|
|
23809
|
-
);
|
|
23810
|
-
const setField = useCallback(
|
|
23811
|
-
(name, value) => setValues((prev) => ({ ...prev, [name]: value })),
|
|
23812
|
-
[]
|
|
23813
|
-
);
|
|
23814
|
-
const resolveEndpoint = () => {
|
|
23815
|
-
if (endpoint) return endpoint;
|
|
23816
|
-
if (endpointKey) {
|
|
23817
|
-
const resolved = t(endpointKey, { defaultValue: "" });
|
|
23818
|
-
return resolved && resolved !== endpointKey ? resolved : null;
|
|
23819
|
-
}
|
|
23820
|
-
return null;
|
|
23821
|
-
};
|
|
23822
|
-
const onSubmit = async (e) => {
|
|
23823
|
-
e.preventDefault();
|
|
23824
|
-
const url = resolveEndpoint();
|
|
23825
|
-
if (!url) {
|
|
23826
|
-
setStatus("error");
|
|
23827
|
-
return;
|
|
23828
|
-
}
|
|
23829
|
-
setStatus("submitting");
|
|
23830
|
-
try {
|
|
23831
|
-
const res = await fetch(url, {
|
|
23832
|
-
method: "POST",
|
|
23833
|
-
headers: { "Content-Type": "application/json" },
|
|
23834
|
-
body: JSON.stringify(values)
|
|
23835
|
-
});
|
|
23836
|
-
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
23837
|
-
setStatus("success");
|
|
23838
|
-
setValues(
|
|
23839
|
-
(prev) => Object.fromEntries(Object.keys(prev).map((k) => [k, ""]))
|
|
23840
|
-
);
|
|
23841
|
-
} catch {
|
|
23842
|
-
setStatus("error");
|
|
23843
|
-
}
|
|
23844
|
-
};
|
|
23845
|
-
if (status === "success") {
|
|
23846
|
-
return /* @__PURE__ */ jsx("div", { className, "data-setto-form-status": "success", children: /* @__PURE__ */ jsx("p", { children: /* @__PURE__ */ jsx(T, { k: `form.${formId}.success` }) }) });
|
|
23847
|
-
}
|
|
23848
|
-
return /* @__PURE__ */ jsxs("form", { className, onSubmit, "data-setto-form": formId, noValidate: true, children: [
|
|
23849
|
-
fields.map((field) => /* @__PURE__ */ jsxs("label", { "data-setto-form-field": field.name, children: [
|
|
23850
|
-
/* @__PURE__ */ jsx("span", { children: /* @__PURE__ */ jsx(T, { k: field.labelKey }) }),
|
|
23851
|
-
field.type === "textarea" ? /* @__PURE__ */ jsx(
|
|
23852
|
-
"textarea",
|
|
23853
|
-
{
|
|
23854
|
-
name: field.name,
|
|
23855
|
-
required: field.required,
|
|
23856
|
-
value: values[field.name] ?? "",
|
|
23857
|
-
placeholder: field.placeholderKey ? t(field.placeholderKey, { defaultValue: "" }) : void 0,
|
|
23858
|
-
onChange: (e) => setField(field.name, e.target.value)
|
|
23859
|
-
}
|
|
23860
|
-
) : /* @__PURE__ */ jsx(
|
|
23861
|
-
"input",
|
|
23862
|
-
{
|
|
23863
|
-
type: field.type,
|
|
23864
|
-
name: field.name,
|
|
23865
|
-
required: field.required,
|
|
23866
|
-
value: values[field.name] ?? "",
|
|
23867
|
-
placeholder: field.placeholderKey ? t(field.placeholderKey, { defaultValue: "" }) : void 0,
|
|
23868
|
-
onChange: (e) => setField(field.name, e.target.value)
|
|
23869
|
-
}
|
|
23870
|
-
)
|
|
23871
|
-
] }, field.name)),
|
|
23872
|
-
/* @__PURE__ */ jsx("button", { type: "submit", disabled: status === "submitting", children: /* @__PURE__ */ jsx(T, { k: `form.${formId}.submit` }) }),
|
|
23873
|
-
status === "error" ? /* @__PURE__ */ jsx("p", { "data-setto-form-status": "error", role: "alert", children: /* @__PURE__ */ jsx(T, { k: `form.${formId}.error` }) }) : null
|
|
23874
|
-
] });
|
|
23875
|
-
}
|
|
25447
|
+
padding: 0,
|
|
25448
|
+
background: "#ffffff",
|
|
25449
|
+
border: "1px solid #e0e0e0",
|
|
25450
|
+
borderRadius: "50%",
|
|
25451
|
+
fontSize: 16,
|
|
25452
|
+
lineHeight: 1,
|
|
25453
|
+
color: "#444",
|
|
25454
|
+
cursor: "pointer",
|
|
25455
|
+
boxShadow: "0 1px 4px rgba(0,0,0,0.18)",
|
|
25456
|
+
fontFamily: 'system-ui, -apple-system, "Segoe UI", sans-serif'
|
|
25457
|
+
};
|
|
23876
25458
|
function SettoVideo({ srcKey, posterKey, title, className }) {
|
|
23877
25459
|
const { t, i18n } = useTranslation();
|
|
23878
25460
|
const { store } = useSetto();
|
|
@@ -24138,7 +25720,7 @@ function AuthGate({ children }) {
|
|
|
24138
25720
|
};
|
|
24139
25721
|
if (mode === "set_password") {
|
|
24140
25722
|
const callback = authCallbackType();
|
|
24141
|
-
return /* @__PURE__ */ jsx("div", { style: shellStyle
|
|
25723
|
+
return /* @__PURE__ */ jsx("div", { style: shellStyle, children: /* @__PURE__ */ jsxs("form", { onSubmit: submitSetPassword, style: formStyle, children: [
|
|
24142
25724
|
/* @__PURE__ */ jsx("h1", { style: { margin: 0, fontSize: 22 }, children: "Setto" }),
|
|
24143
25725
|
/* @__PURE__ */ jsx("p", { style: { margin: 0, color: "#888", fontSize: 13 }, children: callback === "invite" ? "Velg et passord for å fullføre invitasjonen." : "Velg et nytt passord." }),
|
|
24144
25726
|
/* @__PURE__ */ jsx(
|
|
@@ -24173,7 +25755,7 @@ function AuthGate({ children }) {
|
|
|
24173
25755
|
] }) });
|
|
24174
25756
|
}
|
|
24175
25757
|
if (mode === "forgot") {
|
|
24176
|
-
return /* @__PURE__ */ jsx("div", { style: shellStyle
|
|
25758
|
+
return /* @__PURE__ */ jsx("div", { style: shellStyle, children: /* @__PURE__ */ jsxs("form", { onSubmit: submitForgot, style: formStyle, children: [
|
|
24177
25759
|
/* @__PURE__ */ jsx("h1", { style: { margin: 0, fontSize: 22 }, children: "Setto" }),
|
|
24178
25760
|
/* @__PURE__ */ jsx("p", { style: { margin: 0, color: "#888", fontSize: 13 }, children: "Skriv inn e-posten din, så sender vi en lenke for å nullstille passordet." }),
|
|
24179
25761
|
/* @__PURE__ */ jsx(
|
|
@@ -24206,7 +25788,7 @@ function AuthGate({ children }) {
|
|
|
24206
25788
|
)
|
|
24207
25789
|
] }) });
|
|
24208
25790
|
}
|
|
24209
|
-
return /* @__PURE__ */ jsx("div", { style: shellStyle
|
|
25791
|
+
return /* @__PURE__ */ jsx("div", { style: shellStyle, children: /* @__PURE__ */ jsxs("form", { onSubmit: submitSignIn, style: formStyle, children: [
|
|
24210
25792
|
/* @__PURE__ */ jsx("h1", { style: { margin: 0, fontSize: 22 }, children: "Setto" }),
|
|
24211
25793
|
/* @__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." }),
|
|
24212
25794
|
/* @__PURE__ */ jsx(
|
|
@@ -24251,7 +25833,7 @@ function AuthGate({ children }) {
|
|
|
24251
25833
|
)
|
|
24252
25834
|
] }) });
|
|
24253
25835
|
}
|
|
24254
|
-
const shellStyle
|
|
25836
|
+
const shellStyle = {
|
|
24255
25837
|
minHeight: "100dvh",
|
|
24256
25838
|
display: "flex",
|
|
24257
25839
|
alignItems: "center",
|
|
@@ -24296,7 +25878,7 @@ const linkStyle = {
|
|
|
24296
25878
|
cursor: "pointer"
|
|
24297
25879
|
};
|
|
24298
25880
|
const loadingStyle = {
|
|
24299
|
-
...shellStyle
|
|
25881
|
+
...shellStyle,
|
|
24300
25882
|
fontSize: 14,
|
|
24301
25883
|
color: "#888"
|
|
24302
25884
|
};
|
|
@@ -24310,72 +25892,7 @@ function deploymentAdminUrl(deploymentId) {
|
|
|
24310
25892
|
function focusedDeploymentId() {
|
|
24311
25893
|
return new URLSearchParams(window.location.search).get("deployment");
|
|
24312
25894
|
}
|
|
24313
|
-
function
|
|
24314
|
-
return /* @__PURE__ */ jsx(AuthGate, { children: /* @__PURE__ */ jsx(SiteList, {}) });
|
|
24315
|
-
}
|
|
24316
|
-
function SiteList() {
|
|
24317
|
-
const { supabase, session, sites, currentSite, config } = useSetto();
|
|
24318
|
-
const [error, setError] = useState(null);
|
|
24319
|
-
const redirectAfterSignIn = useRef(false);
|
|
24320
|
-
useEffect(() => {
|
|
24321
|
-
const { data: sub } = supabase.auth.onAuthStateChange((event) => {
|
|
24322
|
-
if (event === "SIGNED_IN" && !isAdminDeploymentView()) {
|
|
24323
|
-
redirectAfterSignIn.current = true;
|
|
24324
|
-
}
|
|
24325
|
-
});
|
|
24326
|
-
return () => sub.subscription.unsubscribe();
|
|
24327
|
-
}, [supabase]);
|
|
24328
|
-
useEffect(() => {
|
|
24329
|
-
if (!redirectAfterSignIn.current || !session || sites === null || isAdminDeploymentView()) {
|
|
24330
|
-
return;
|
|
24331
|
-
}
|
|
24332
|
-
if (currentSite) {
|
|
24333
|
-
redirectAfterSignIn.current = false;
|
|
24334
|
-
window.location.replace(siteHomeUrl());
|
|
24335
|
-
return;
|
|
24336
|
-
}
|
|
24337
|
-
redirectAfterSignIn.current = false;
|
|
24338
|
-
}, [session, sites, currentSite]);
|
|
24339
|
-
if (redirectAfterSignIn.current && session && (sites === null || currentSite)) {
|
|
24340
|
-
return /* @__PURE__ */ jsx("div", { style: loadingRedirectStyle, children: "Åpner redigering …" });
|
|
24341
|
-
}
|
|
24342
|
-
return /* @__PURE__ */ jsxs("div", { style: shellStyle, children: [
|
|
24343
|
-
/* @__PURE__ */ jsxs("header", { style: headerStyle, children: [
|
|
24344
|
-
/* @__PURE__ */ jsx("h1", { style: { margin: 0, fontSize: 22 }, children: "Setto" }),
|
|
24345
|
-
/* @__PURE__ */ jsx(SignOutButton, {})
|
|
24346
|
-
] }),
|
|
24347
|
-
/* @__PURE__ */ jsxs("main", { style: mainStyle, children: [
|
|
24348
|
-
error ? /* @__PURE__ */ jsx("p", { style: errStyle, children: error }) : null,
|
|
24349
|
-
sites === null ? /* @__PURE__ */ jsx("p", { style: mutedStyle, children: "Laster sider…" }) : null,
|
|
24350
|
-
currentSite ? /* @__PURE__ */ jsxs("section", { style: cardStyle, children: [
|
|
24351
|
-
/* @__PURE__ */ jsx("h2", { style: { margin: 0, fontSize: 18 }, children: currentSite.display_name }),
|
|
24352
|
-
/* @__PURE__ */ jsxs("p", { style: mutedStyle, children: [
|
|
24353
|
-
currentSite.repo_owner,
|
|
24354
|
-
"/",
|
|
24355
|
-
currentSite.repo_name,
|
|
24356
|
-
" · branch ",
|
|
24357
|
-
currentSite.branch
|
|
24358
|
-
] }),
|
|
24359
|
-
/* @__PURE__ */ jsx("div", { style: { display: "flex", gap: 8 }, children: /* @__PURE__ */ jsx("a", { href: siteHomeUrl(), style: primaryBtnLinkStyle, children: "Begynn å redigere" }) }),
|
|
24360
|
-
/* @__PURE__ */ jsx(DeploymentList, { siteId: currentSite.id, onError: setError })
|
|
24361
|
-
] }) : sites && sites.length === 0 ? /* @__PURE__ */ jsx("p", { style: mutedStyle, children: "Du er ikke medlem av noen sider ennå. Be administratoren legge deg til." }) : null,
|
|
24362
|
-
sites && !currentSite && sites.length > 0 ? /* @__PURE__ */ jsxs("section", { style: cardStyle, children: [
|
|
24363
|
-
/* @__PURE__ */ jsx("h2", { style: { margin: 0, fontSize: 16 }, children: "Andre sider du har tilgang til" }),
|
|
24364
|
-
/* @__PURE__ */ jsx("ul", { style: { listStyle: "none", padding: 0, margin: 0 }, children: sites.map((s) => /* @__PURE__ */ jsxs("li", { style: { padding: "6px 0" }, children: [
|
|
24365
|
-
/* @__PURE__ */ jsx("code", { style: { color: "#bda6ff" }, children: s.id }),
|
|
24366
|
-
" — ",
|
|
24367
|
-
s.display_name
|
|
24368
|
-
] }, s.id)) }),
|
|
24369
|
-
/* @__PURE__ */ jsxs("p", { style: mutedStyle, children: [
|
|
24370
|
-
"Du er logget inn på denne siden (",
|
|
24371
|
-
config.siteId,
|
|
24372
|
-
"), men er ikke medlem her. Spør administrator om tilgang."
|
|
24373
|
-
] })
|
|
24374
|
-
] }) : null
|
|
24375
|
-
] })
|
|
24376
|
-
] });
|
|
24377
|
-
}
|
|
24378
|
-
function DeploymentList({
|
|
25895
|
+
function DeploymentsView({
|
|
24379
25896
|
siteId,
|
|
24380
25897
|
onError
|
|
24381
25898
|
}) {
|
|
@@ -24436,7 +25953,7 @@ function DeploymentList({
|
|
|
24436
25953
|
const showFocusPanel = focusId !== null && (focusRow === null || isDeploymentInProgress(focusRow.status));
|
|
24437
25954
|
if (!rows) return /* @__PURE__ */ jsx("p", { style: mutedStyle, children: "Laster deploys…" });
|
|
24438
25955
|
return /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 12 }, children: [
|
|
24439
|
-
showFocusPanel ? /* @__PURE__ */ jsxs("section", { style:
|
|
25956
|
+
showFocusPanel ? /* @__PURE__ */ jsxs("section", { style: panelStyle, children: [
|
|
24440
25957
|
/* @__PURE__ */ jsx("h3", { style: { margin: 0, fontSize: 15 }, children: "Publisering pågår" }),
|
|
24441
25958
|
focusRow ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
24442
25959
|
/* @__PURE__ */ jsx(PublishProgressBar, { status: focusRow.status }),
|
|
@@ -24460,14 +25977,14 @@ function DeploymentList({
|
|
|
24460
25977
|
type: "button",
|
|
24461
25978
|
onClick: () => void cancelDeployment(),
|
|
24462
25979
|
disabled: canceling,
|
|
24463
|
-
style:
|
|
25980
|
+
style: dangerBtnStyle,
|
|
24464
25981
|
children: canceling ? "Avbryter …" : "Avbryt publisering"
|
|
24465
25982
|
}
|
|
24466
25983
|
)
|
|
24467
25984
|
] }) : /* @__PURE__ */ jsx("p", { style: mutedStyle, children: "Laster status …" })
|
|
24468
25985
|
] }) : null,
|
|
24469
25986
|
rows.length === 0 ? /* @__PURE__ */ jsx("p", { style: mutedStyle, children: "Ingen deploys ennå." }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
24470
|
-
/* @__PURE__ */ jsx("h3", { style:
|
|
25987
|
+
/* @__PURE__ */ jsx("h3", { style: subheadingStyle, children: "Historikk" }),
|
|
24471
25988
|
/* @__PURE__ */ jsx("ul", { style: { listStyle: "none", padding: 0, margin: 0, display: "flex", flexDirection: "column", gap: 6 }, children: rows.map((r) => {
|
|
24472
25989
|
const focused = r.id === focusId;
|
|
24473
25990
|
const inProgress = isDeploymentInProgress(r.status);
|
|
@@ -24476,7 +25993,7 @@ function DeploymentList({
|
|
|
24476
25993
|
{
|
|
24477
25994
|
href: inProgress ? deploymentAdminUrl(r.id) : void 0,
|
|
24478
25995
|
style: {
|
|
24479
|
-
...
|
|
25996
|
+
...rowStyle,
|
|
24480
25997
|
textDecoration: "none",
|
|
24481
25998
|
color: "inherit",
|
|
24482
25999
|
outline: focused ? "1px solid #640AFF" : void 0,
|
|
@@ -24513,9 +26030,120 @@ function statusLabel(row) {
|
|
|
24513
26030
|
return "Avbrutt";
|
|
24514
26031
|
}
|
|
24515
26032
|
}
|
|
26033
|
+
function depDotStyle(status) {
|
|
26034
|
+
const colour = status === "ready" ? "#34d399" : status === "error" || status === "canceled" ? "#ff7a7a" : status === "building" ? "#fbbf24" : "#888";
|
|
26035
|
+
return {
|
|
26036
|
+
width: 8,
|
|
26037
|
+
height: 8,
|
|
26038
|
+
borderRadius: "50%",
|
|
26039
|
+
background: colour,
|
|
26040
|
+
flexShrink: 0
|
|
26041
|
+
};
|
|
26042
|
+
}
|
|
26043
|
+
function SettoAdminApp() {
|
|
26044
|
+
return /* @__PURE__ */ jsx(AuthGate, { children: /* @__PURE__ */ jsx(SiteList, {}) });
|
|
26045
|
+
}
|
|
26046
|
+
const DEPLOYMENTS_VIEW = "deployments";
|
|
26047
|
+
function SiteList() {
|
|
26048
|
+
const { supabase, session, sites, currentSite, config } = useSetto();
|
|
26049
|
+
const [error, setError] = useState(null);
|
|
26050
|
+
const [view, setView] = useState(() => currentAdminView() ?? DEPLOYMENTS_VIEW);
|
|
26051
|
+
const redirectAfterSignIn = useRef(false);
|
|
26052
|
+
useEffect(() => {
|
|
26053
|
+
const { data: sub } = supabase.auth.onAuthStateChange((event) => {
|
|
26054
|
+
if (event === "SIGNED_IN" && !isAdminPanelView()) {
|
|
26055
|
+
redirectAfterSignIn.current = true;
|
|
26056
|
+
}
|
|
26057
|
+
});
|
|
26058
|
+
return () => sub.subscription.unsubscribe();
|
|
26059
|
+
}, [supabase]);
|
|
26060
|
+
useEffect(() => {
|
|
26061
|
+
if (!redirectAfterSignIn.current || !session || sites === null || isAdminPanelView()) {
|
|
26062
|
+
return;
|
|
26063
|
+
}
|
|
26064
|
+
if (currentSite) {
|
|
26065
|
+
redirectAfterSignIn.current = false;
|
|
26066
|
+
window.location.replace(siteHomeUrl());
|
|
26067
|
+
return;
|
|
26068
|
+
}
|
|
26069
|
+
redirectAfterSignIn.current = false;
|
|
26070
|
+
}, [session, sites, currentSite]);
|
|
26071
|
+
useEffect(() => {
|
|
26072
|
+
const onPopState = () => setView(currentAdminView() ?? DEPLOYMENTS_VIEW);
|
|
26073
|
+
window.addEventListener("popstate", onPopState);
|
|
26074
|
+
return () => window.removeEventListener("popstate", onPopState);
|
|
26075
|
+
}, []);
|
|
26076
|
+
const showView = (id) => {
|
|
26077
|
+
setView(id);
|
|
26078
|
+
setError(null);
|
|
26079
|
+
window.history.pushState(null, "", id === DEPLOYMENTS_VIEW ? SETTO_BASE : adminViewUrl(id));
|
|
26080
|
+
};
|
|
26081
|
+
if (redirectAfterSignIn.current && session && (sites === null || currentSite)) {
|
|
26082
|
+
return /* @__PURE__ */ jsx("div", { style: loadingRedirectStyle, children: "Åpner redigering …" });
|
|
26083
|
+
}
|
|
26084
|
+
const activeModule = findModule(view);
|
|
26085
|
+
return /* @__PURE__ */ jsxs("div", { style: shellStyle$1, children: [
|
|
26086
|
+
/* @__PURE__ */ jsxs("header", { style: headerStyle, children: [
|
|
26087
|
+
/* @__PURE__ */ jsx("h1", { style: { margin: 0, fontSize: 22 }, children: "Setto" }),
|
|
26088
|
+
/* @__PURE__ */ jsx(SignOutButton, {})
|
|
26089
|
+
] }),
|
|
26090
|
+
/* @__PURE__ */ jsxs("main", { style: mainStyle, children: [
|
|
26091
|
+
error ? /* @__PURE__ */ jsx("p", { style: errStyle, children: error }) : null,
|
|
26092
|
+
sites === null ? /* @__PURE__ */ jsx("p", { style: mutedStyle, children: "Laster sider…" }) : null,
|
|
26093
|
+
currentSite ? /* @__PURE__ */ jsxs("section", { style: cardStyle, children: [
|
|
26094
|
+
/* @__PURE__ */ jsx("h2", { style: { margin: 0, fontSize: 18 }, children: currentSite.display_name }),
|
|
26095
|
+
/* @__PURE__ */ jsxs("p", { style: mutedStyle, children: [
|
|
26096
|
+
currentSite.repo_owner,
|
|
26097
|
+
"/",
|
|
26098
|
+
currentSite.repo_name,
|
|
26099
|
+
" · branch ",
|
|
26100
|
+
currentSite.branch
|
|
26101
|
+
] }),
|
|
26102
|
+
/* @__PURE__ */ jsx("div", { style: { display: "flex", gap: 8 }, children: /* @__PURE__ */ jsx("a", { href: siteHomeUrl(), style: primaryBtnStyle, children: "Begynn å redigere" }) }),
|
|
26103
|
+
/* @__PURE__ */ jsxs("nav", { style: tabBarStyle, "aria-label": "Visninger", children: [
|
|
26104
|
+
/* @__PURE__ */ jsx(
|
|
26105
|
+
"button",
|
|
26106
|
+
{
|
|
26107
|
+
type: "button",
|
|
26108
|
+
style: tabStyle(!activeModule),
|
|
26109
|
+
"aria-current": !activeModule ? "page" : void 0,
|
|
26110
|
+
onClick: () => showView(DEPLOYMENTS_VIEW),
|
|
26111
|
+
children: "Publiseringer"
|
|
26112
|
+
}
|
|
26113
|
+
),
|
|
26114
|
+
SETTO_MODULES.map((m) => /* @__PURE__ */ jsx(
|
|
26115
|
+
"button",
|
|
26116
|
+
{
|
|
26117
|
+
type: "button",
|
|
26118
|
+
style: tabStyle(activeModule?.id === m.id),
|
|
26119
|
+
"aria-current": activeModule?.id === m.id ? "page" : void 0,
|
|
26120
|
+
onClick: () => showView(m.id),
|
|
26121
|
+
children: m.label
|
|
26122
|
+
},
|
|
26123
|
+
m.id
|
|
26124
|
+
))
|
|
26125
|
+
] }),
|
|
26126
|
+
activeModule ? /* @__PURE__ */ jsx(activeModule.AdminView, { siteId: currentSite.id }) : /* @__PURE__ */ jsx(DeploymentsView, { siteId: currentSite.id, onError: setError })
|
|
26127
|
+
] }) : sites && sites.length === 0 ? /* @__PURE__ */ jsx("p", { style: mutedStyle, children: "Du er ikke medlem av noen sider ennå. Be administratoren legge deg til." }) : null,
|
|
26128
|
+
sites && !currentSite && sites.length > 0 ? /* @__PURE__ */ jsxs("section", { style: cardStyle, children: [
|
|
26129
|
+
/* @__PURE__ */ jsx("h2", { style: { margin: 0, fontSize: 16 }, children: "Andre sider du har tilgang til" }),
|
|
26130
|
+
/* @__PURE__ */ jsx("ul", { style: { listStyle: "none", padding: 0, margin: 0 }, children: sites.map((s) => /* @__PURE__ */ jsxs("li", { style: { padding: "6px 0" }, children: [
|
|
26131
|
+
/* @__PURE__ */ jsx("code", { style: { color: "#bda6ff" }, children: s.id }),
|
|
26132
|
+
" — ",
|
|
26133
|
+
s.display_name
|
|
26134
|
+
] }, s.id)) }),
|
|
26135
|
+
/* @__PURE__ */ jsxs("p", { style: mutedStyle, children: [
|
|
26136
|
+
"Du er logget inn på denne siden (",
|
|
26137
|
+
config.siteId,
|
|
26138
|
+
"), men er ikke medlem her. Spør administrator om tilgang."
|
|
26139
|
+
] })
|
|
26140
|
+
] }) : null
|
|
26141
|
+
] })
|
|
26142
|
+
] });
|
|
26143
|
+
}
|
|
24516
26144
|
function SignOutButton() {
|
|
24517
26145
|
const { supabase } = useSetto();
|
|
24518
|
-
return /* @__PURE__ */ jsx("button", { onClick: () => supabase.auth.signOut(), style:
|
|
26146
|
+
return /* @__PURE__ */ jsx("button", { onClick: () => supabase.auth.signOut(), style: secondaryBtnStyle, children: "Logg ut" });
|
|
24519
26147
|
}
|
|
24520
26148
|
const loadingRedirectStyle = {
|
|
24521
26149
|
minHeight: "100dvh",
|
|
@@ -24527,101 +26155,6 @@ const loadingRedirectStyle = {
|
|
|
24527
26155
|
fontSize: 14,
|
|
24528
26156
|
fontFamily: 'system-ui, -apple-system, "Segoe UI", sans-serif'
|
|
24529
26157
|
};
|
|
24530
|
-
const shellStyle = {
|
|
24531
|
-
minHeight: "100dvh",
|
|
24532
|
-
background: "#0a0a0d",
|
|
24533
|
-
color: "#fff",
|
|
24534
|
-
fontFamily: 'system-ui, -apple-system, "Segoe UI", sans-serif'
|
|
24535
|
-
};
|
|
24536
|
-
const headerStyle = {
|
|
24537
|
-
display: "flex",
|
|
24538
|
-
alignItems: "center",
|
|
24539
|
-
justifyContent: "space-between",
|
|
24540
|
-
padding: "16px 24px",
|
|
24541
|
-
borderBottom: "1px solid #1c1c22"
|
|
24542
|
-
};
|
|
24543
|
-
const mainStyle = {
|
|
24544
|
-
maxWidth: 720,
|
|
24545
|
-
margin: "0 auto",
|
|
24546
|
-
padding: 24,
|
|
24547
|
-
display: "flex",
|
|
24548
|
-
flexDirection: "column",
|
|
24549
|
-
gap: 16
|
|
24550
|
-
};
|
|
24551
|
-
const cardStyle = {
|
|
24552
|
-
background: "#15151a",
|
|
24553
|
-
borderRadius: 12,
|
|
24554
|
-
padding: 20,
|
|
24555
|
-
display: "flex",
|
|
24556
|
-
flexDirection: "column",
|
|
24557
|
-
gap: 12
|
|
24558
|
-
};
|
|
24559
|
-
const mutedStyle = {
|
|
24560
|
-
color: "#888",
|
|
24561
|
-
fontSize: 13,
|
|
24562
|
-
margin: 0
|
|
24563
|
-
};
|
|
24564
|
-
const errStyle = {
|
|
24565
|
-
color: "#ff7a7a",
|
|
24566
|
-
fontSize: 13,
|
|
24567
|
-
margin: 0
|
|
24568
|
-
};
|
|
24569
|
-
const primaryBtnLinkStyle = {
|
|
24570
|
-
display: "inline-block",
|
|
24571
|
-
background: "#640AFF",
|
|
24572
|
-
color: "#fff",
|
|
24573
|
-
border: "none",
|
|
24574
|
-
borderRadius: 6,
|
|
24575
|
-
padding: "10px 14px",
|
|
24576
|
-
fontSize: 14,
|
|
24577
|
-
fontWeight: 500,
|
|
24578
|
-
textDecoration: "none"
|
|
24579
|
-
};
|
|
24580
|
-
const signOutBtnStyle = {
|
|
24581
|
-
background: "transparent",
|
|
24582
|
-
color: "#bda6ff",
|
|
24583
|
-
border: "1px solid #2a2a32",
|
|
24584
|
-
borderRadius: 6,
|
|
24585
|
-
padding: "6px 10px",
|
|
24586
|
-
fontSize: 12,
|
|
24587
|
-
cursor: "pointer"
|
|
24588
|
-
};
|
|
24589
|
-
const activeDepStyle = {
|
|
24590
|
-
background: "#1a1a20",
|
|
24591
|
-
borderRadius: 8,
|
|
24592
|
-
padding: 14,
|
|
24593
|
-
display: "flex",
|
|
24594
|
-
flexDirection: "column",
|
|
24595
|
-
gap: 10
|
|
24596
|
-
};
|
|
24597
|
-
const depRowStyle = {
|
|
24598
|
-
display: "flex",
|
|
24599
|
-
alignItems: "center",
|
|
24600
|
-
gap: 10,
|
|
24601
|
-
background: "#1a1a20",
|
|
24602
|
-
padding: "6px 10px",
|
|
24603
|
-
borderRadius: 6
|
|
24604
|
-
};
|
|
24605
|
-
const cancelBtnStyle = {
|
|
24606
|
-
alignSelf: "flex-start",
|
|
24607
|
-
background: "transparent",
|
|
24608
|
-
color: "#ff7a7a",
|
|
24609
|
-
border: "1px solid #4a3030",
|
|
24610
|
-
borderRadius: 6,
|
|
24611
|
-
padding: "8px 14px",
|
|
24612
|
-
fontSize: 13,
|
|
24613
|
-
cursor: "pointer"
|
|
24614
|
-
};
|
|
24615
|
-
function depDotStyle(status) {
|
|
24616
|
-
const colour = status === "ready" ? "#34d399" : status === "error" || status === "canceled" ? "#ff7a7a" : status === "building" ? "#fbbf24" : "#888";
|
|
24617
|
-
return {
|
|
24618
|
-
width: 8,
|
|
24619
|
-
height: 8,
|
|
24620
|
-
borderRadius: "50%",
|
|
24621
|
-
background: colour,
|
|
24622
|
-
flexShrink: 0
|
|
24623
|
-
};
|
|
24624
|
-
}
|
|
24625
26158
|
const BLOCK_REGISTRY = {
|
|
24626
26159
|
section: { allowedChildren: "any", themed: true, selectable: true },
|
|
24627
26160
|
block: { allowedChildren: "any", themed: true, selectable: true },
|
|
@@ -24650,7 +26183,6 @@ function nodeChildren(node) {
|
|
|
24650
26183
|
return [];
|
|
24651
26184
|
}
|
|
24652
26185
|
}
|
|
24653
|
-
const FIELD_TYPES = ["text", "email", "tel", "textarea"];
|
|
24654
26186
|
const ANIMATION_PRESETS = ["fadeIn", "slideUp", "stagger", "scrollReveal"];
|
|
24655
26187
|
function validateBlockTree(input) {
|
|
24656
26188
|
const issues = [];
|
|
@@ -24755,18 +26287,17 @@ function validateTypeSpecific(raw, type, path, issues) {
|
|
|
24755
26287
|
break;
|
|
24756
26288
|
case "form":
|
|
24757
26289
|
requireString(raw, "formId", path, issues);
|
|
24758
|
-
if (
|
|
24759
|
-
|
|
24760
|
-
|
|
24761
|
-
|
|
24762
|
-
|
|
24763
|
-
|
|
26290
|
+
if (raw.fields !== void 0) {
|
|
26291
|
+
if (!Array.isArray(raw.fields)) {
|
|
26292
|
+
issues.push({ path: `${path}.fields`, message: "form.fields must be an array" });
|
|
26293
|
+
break;
|
|
26294
|
+
}
|
|
26295
|
+
for (let i = 0; i < raw.fields.length; i++) {
|
|
26296
|
+
validateFormField(raw.fields[i], `${path}.fields[${i}]`, issues);
|
|
26297
|
+
}
|
|
24764
26298
|
}
|
|
24765
|
-
if (raw.endpoint
|
|
24766
|
-
issues.push({
|
|
24767
|
-
path,
|
|
24768
|
-
message: "form must declare either endpoint or endpointKey"
|
|
24769
|
-
});
|
|
26299
|
+
if (raw.endpoint !== void 0 && typeof raw.endpoint !== "string") {
|
|
26300
|
+
issues.push({ path: `${path}.endpoint`, message: "form.endpoint must be a string" });
|
|
24770
26301
|
}
|
|
24771
26302
|
break;
|
|
24772
26303
|
case "video":
|
|
@@ -24791,10 +26322,10 @@ function validateFormField(raw, path, issues) {
|
|
|
24791
26322
|
if (typeof raw.name !== "string" || raw.name.length === 0) {
|
|
24792
26323
|
issues.push({ path: `${path}.name`, message: "field.name must be a non-empty string" });
|
|
24793
26324
|
}
|
|
24794
|
-
if (
|
|
26325
|
+
if (!isFormFieldType(raw.type)) {
|
|
24795
26326
|
issues.push({
|
|
24796
26327
|
path: `${path}.type`,
|
|
24797
|
-
message: `field.type must be one of ${
|
|
26328
|
+
message: `field.type must be one of ${FORM_FIELD_TYPES.join(", ")}`
|
|
24798
26329
|
});
|
|
24799
26330
|
}
|
|
24800
26331
|
requireString(raw, "labelKey", path, issues);
|
|
@@ -24887,7 +26418,6 @@ function RenderNode({ node }) {
|
|
|
24887
26418
|
formId: node.formId,
|
|
24888
26419
|
fields: node.fields,
|
|
24889
26420
|
endpoint: node.endpoint,
|
|
24890
|
-
endpointKey: node.endpointKey,
|
|
24891
26421
|
className: node.className
|
|
24892
26422
|
}
|
|
24893
26423
|
);
|
|
@@ -25043,10 +26573,11 @@ function walk(node, out, repeaterCtx) {
|
|
|
25043
26573
|
case "form":
|
|
25044
26574
|
out.keys.add(`form.${node.formId}.submit`);
|
|
25045
26575
|
out.keys.add(`form.${node.formId}.success`);
|
|
25046
|
-
|
|
25047
|
-
for (const field of node.fields) {
|
|
26576
|
+
out.keys.add(`form.${node.formId}.error`);
|
|
26577
|
+
for (const field of node.fields ?? []) {
|
|
25048
26578
|
out.keys.add(field.labelKey);
|
|
25049
26579
|
if (field.placeholderKey) out.keys.add(field.placeholderKey);
|
|
26580
|
+
if (field.optionsKey) out.keys.add(field.optionsKey);
|
|
25050
26581
|
}
|
|
25051
26582
|
return;
|
|
25052
26583
|
case "repeater":
|
|
@@ -25100,8 +26631,11 @@ export {
|
|
|
25100
26631
|
AuthGate,
|
|
25101
26632
|
BLOCK_REGISTRY,
|
|
25102
26633
|
BLOCK_TYPES,
|
|
26634
|
+
FORM_FIELD_TYPES,
|
|
26635
|
+
FORM_FIELD_TYPE_LABELS,
|
|
25103
26636
|
GuestEditProvider,
|
|
25104
26637
|
SETTO_BASE,
|
|
26638
|
+
SETTO_MODULES,
|
|
25105
26639
|
SettoAdminApp,
|
|
25106
26640
|
SettoAnimation,
|
|
25107
26641
|
SettoBlock,
|
|
@@ -25114,9 +26648,14 @@ export {
|
|
|
25114
26648
|
SettoSection,
|
|
25115
26649
|
SettoVideo,
|
|
25116
26650
|
T,
|
|
26651
|
+
adminViewUrl,
|
|
25117
26652
|
extractContentKeys,
|
|
26653
|
+
formFieldsPrefix,
|
|
26654
|
+
isFormFieldType,
|
|
25118
26655
|
nodeChildren,
|
|
25119
26656
|
parseBlockTree,
|
|
26657
|
+
readLocaleFields,
|
|
26658
|
+
settoSubmissionUrl,
|
|
25120
26659
|
useGuestEdit,
|
|
25121
26660
|
useSectionTheme,
|
|
25122
26661
|
useSetto,
|