@sevenfold/setto-client 0.3.4 → 0.4.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 +28 -16
- package/dist/SettoRepeater.d.ts +2 -1
- package/dist/admin/App.d.ts +2 -2
- package/dist/guest-edit.d.ts +3 -3
- package/dist/index.d.ts +1 -0
- package/dist/lib/urls.d.ts +7 -1
- package/dist/provider.d.ts +1 -1
- package/dist/setto-client.js +132 -77
- package/dist/setto-client.js.map +1 -1
- package/dist/theme-target-utils.d.ts +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -67,7 +67,9 @@ createRoot(document.getElementById('root')!).render(
|
|
|
67
67
|
);
|
|
68
68
|
```
|
|
69
69
|
|
|
70
|
-
### 2. Mount the
|
|
70
|
+
### 2. Mount the Setto route
|
|
71
|
+
|
|
72
|
+
Editors reach edit mode by visiting `sitenavn.no/setto`.
|
|
71
73
|
|
|
72
74
|
```tsx
|
|
73
75
|
// App.tsx
|
|
@@ -75,7 +77,7 @@ import { SettoAdminApp } from '@setto/client';
|
|
|
75
77
|
|
|
76
78
|
<Routes>
|
|
77
79
|
<Route path="/" element={<Home />} />
|
|
78
|
-
<Route path="/
|
|
80
|
+
<Route path="/setto/*" element={<SettoAdminApp />} />
|
|
79
81
|
</Routes>
|
|
80
82
|
```
|
|
81
83
|
|
|
@@ -129,7 +131,7 @@ Edit mode activates when **both** are true:
|
|
|
129
131
|
1. Authenticated Supabase session.
|
|
130
132
|
2. URL contains `?setto=edit`.
|
|
131
133
|
|
|
132
|
-
The `/
|
|
134
|
+
The `/setto` dashboard does **not** activate edit mode. Use **Begynn å redigere**, which navigates to `/?setto=edit`.
|
|
133
135
|
|
|
134
136
|
### What editors see
|
|
135
137
|
|
|
@@ -272,25 +274,35 @@ Click a block's background to edit **that block's** colours. Click the section p
|
|
|
272
274
|
|
|
273
275
|
## Server setup (setto-server)
|
|
274
276
|
|
|
275
|
-
|
|
277
|
+
Site configuration is split in two:
|
|
278
|
+
|
|
279
|
+
**1. Supabase `sites` row — control plane (where + routing).** Set once when the site is registered (platform admin → **Ny side**):
|
|
276
280
|
|
|
277
281
|
| Column | Example |
|
|
278
282
|
|--------|---------|
|
|
279
|
-
| `id` | `carryon-no` |
|
|
283
|
+
| `id` | `carryon-no` (must match `siteId` in `SettoProvider`) |
|
|
280
284
|
| `repo_owner` / `repo_name` / `branch` | GitHub target |
|
|
281
|
-
| `
|
|
285
|
+
| `vercel_project_id` | `prj_…` (used to route Vercel webhooks) |
|
|
282
286
|
|
|
283
|
-
|
|
287
|
+
**2. `setto.config.json` in the site repo root — content shape.** setto-server reads this from GitHub (cached, with a DB fallback):
|
|
284
288
|
|
|
285
|
-
```
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
+
```json
|
|
290
|
+
{
|
|
291
|
+
"displayName": "Carry On",
|
|
292
|
+
"contentPaths": [
|
|
293
|
+
"src/i18n/locales/no.json",
|
|
294
|
+
"src/i18n/locales/en.json",
|
|
295
|
+
"src/theme/sections.json",
|
|
296
|
+
"public/images/setto/"
|
|
297
|
+
],
|
|
298
|
+
"allowedOrigins": ["https://carryon.no", "http://localhost:3000"]
|
|
299
|
+
}
|
|
289
300
|
```
|
|
290
301
|
|
|
291
|
-
|
|
302
|
+
- `contentPaths` is the publish whitelist — only these paths can be committed. Add new content files here before publishing them. `setto.config.json` itself is intentionally not in the list, so editors can never widen their own access.
|
|
303
|
+
- `allowedOrigins` is the CORS allow-list; the first entry is also used as the editor-invite activation domain.
|
|
292
304
|
|
|
293
|
-
|
|
305
|
+
Keeping this in the repo means content shape lives with the code that defines it, and no DB change is needed when you add a content file — just commit the config. The legacy `content_paths` / `allowed_origins` / `display_name` DB columns remain as a fallback for sites without the file.
|
|
294
306
|
|
|
295
307
|
---
|
|
296
308
|
|
|
@@ -308,9 +320,9 @@ Drafts are cleared after a successful publish.
|
|
|
308
320
|
|
|
309
321
|
## Admin app (`SettoAdminApp`)
|
|
310
322
|
|
|
311
|
-
Route: `/
|
|
323
|
+
Route: `/setto/*`
|
|
312
324
|
|
|
313
|
-
Provides Supabase email/password login (invite-only — no self-service sign-up), password reset, and a dashboard with a link to start editing (`/?setto=edit`). Invite links from Supabase land on `/
|
|
325
|
+
Provides Supabase email/password login (invite-only — no self-service sign-up), password reset, and a dashboard with a link to start editing (`/?setto=edit`). Invite links from Supabase land on `/setto` to set a password. Does not render the inline editor itself.
|
|
314
326
|
|
|
315
327
|
---
|
|
316
328
|
|
|
@@ -353,7 +365,7 @@ Produces `dist/setto-client.js` and `.d.ts` via Vite library mode. Only needed b
|
|
|
353
365
|
| `SettoSection` | Section wrapper + edit selection |
|
|
354
366
|
| `SettoBlock` | Nested card/panel with its own colour toolbar |
|
|
355
367
|
| `useSectionTheme` | Read section colour tokens |
|
|
356
|
-
| `SettoAdminApp` | `/
|
|
368
|
+
| `SettoAdminApp` | `/setto` login + dashboard |
|
|
357
369
|
| `AuthGate` | Standalone login wrapper |
|
|
358
370
|
| `BrandColor`, `SectionSchema`, `SettoConfig` | Types for host config |
|
|
359
371
|
|
package/dist/SettoRepeater.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export interface SettoRepeaterProps {
|
|
|
10
10
|
className?: string;
|
|
11
11
|
}
|
|
12
12
|
/**
|
|
13
|
-
* Renders a dynamic list from i18n. In edit mode, editors can add
|
|
13
|
+
* Renders a dynamic list from i18n. In edit mode, editors can add items (button
|
|
14
|
+
* at the end of the list) and remove items (round × revealed on hover).
|
|
14
15
|
*/
|
|
15
16
|
export declare function SettoRepeater({ itemsKey, defaultItem, itemLabel, children, className, }: SettoRepeaterProps): import("react/jsx-runtime").JSX.Element;
|
package/dist/admin/App.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Drop-in admin SPA. Mount under a route like `<Route path="/
|
|
2
|
+
* Drop-in admin SPA. Mount under a route like `<Route path="/setto/*" .../>`.
|
|
3
3
|
*
|
|
4
4
|
* Behaviour after login:
|
|
5
5
|
* - Redirects to `/?setto=edit` on the site home (edit mode active).
|
|
6
6
|
* - Shows the dashboard only when the user lacks access to this site, or on
|
|
7
|
-
* `/
|
|
7
|
+
* `/setto?deployment=…` for publish progress / history.
|
|
8
8
|
*
|
|
9
9
|
* Editing happens on the public site at `/?setto=edit`.
|
|
10
10
|
*/
|
package/dist/guest-edit.d.ts
CHANGED
|
@@ -5,9 +5,9 @@ interface GuestEditContextValue {
|
|
|
5
5
|
stop: () => void;
|
|
6
6
|
}
|
|
7
7
|
/**
|
|
8
|
-
* Lets visitors inline-edit
|
|
9
|
-
*
|
|
10
|
-
* always takes precedence.
|
|
8
|
+
* Lets visitors inline-edit a marketing site (e.g. setto.no) without auth —
|
|
9
|
+
* text, section/block colours, icons and list items. Changes are session-only
|
|
10
|
+
* and never published. Real Setto edit mode always takes precedence.
|
|
11
11
|
*/
|
|
12
12
|
export declare function GuestEditProvider({ children }: {
|
|
13
13
|
children: ReactNode;
|
package/dist/index.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ export { SettoRepeater } from './SettoRepeater';
|
|
|
11
11
|
export type { SettoRepeaterProps } from './SettoRepeater';
|
|
12
12
|
export { useSectionTheme } from './use-section-theme';
|
|
13
13
|
export { SettoAdminApp } from './admin/App';
|
|
14
|
+
export { SETTO_BASE } from './lib/urls';
|
|
14
15
|
export { AuthGate } from './edit-mode/auth-gate';
|
|
15
16
|
export type { SettoConfig, BrandColor, DeploymentRow, SiteRow, ContentFile, PublishFile, PublishResult, } from './types';
|
|
16
17
|
export type { SectionSchema, SectionColorField } from './section-schema';
|
package/dist/lib/urls.d.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base path where the Setto login + dashboard SPA is mounted on the host site.
|
|
3
|
+
* Editors reach edit mode by visiting `sitenavn.no/setto`. The host app must
|
|
4
|
+
* mount `<SettoAdminApp>` at this path (e.g. `<Route path="/setto/*" …>`).
|
|
5
|
+
*/
|
|
6
|
+
export declare const SETTO_BASE = "/setto";
|
|
1
7
|
/** Site home with inline edit mode active. */
|
|
2
8
|
export declare function editModeUrl(pathname?: string): string;
|
|
3
9
|
export declare function isAdminRoute(): boolean;
|
|
4
|
-
/**
|
|
10
|
+
/** Setto deployment progress panel (`/setto?deployment=…`). */
|
|
5
11
|
export declare function isAdminDeploymentView(): boolean;
|
|
6
12
|
export declare function adminRedirectUrl(): string;
|
package/dist/provider.d.ts
CHANGED
|
@@ -34,7 +34,7 @@ export interface SettoProviderProps {
|
|
|
34
34
|
* 1. There is an authenticated Supabase session.
|
|
35
35
|
* 2. The URL contains `?setto=edit`.
|
|
36
36
|
*
|
|
37
|
-
* The /
|
|
37
|
+
* The /setto dashboard does not activate edit mode — use "Begynn å redigere"
|
|
38
38
|
* which navigates to `/?setto=edit`.
|
|
39
39
|
*/
|
|
40
40
|
export declare function SettoProvider({ config, children }: SettoProviderProps): import("react/jsx-runtime").JSX.Element;
|
package/dist/setto-client.js
CHANGED
|
@@ -21147,17 +21147,17 @@ class ThemeStore {
|
|
|
21147
21147
|
}
|
|
21148
21148
|
}
|
|
21149
21149
|
const TOOLBAR_HEIGHT = 44;
|
|
21150
|
-
const STYLE_ID = "setto-edit-layout";
|
|
21150
|
+
const STYLE_ID$1 = "setto-edit-layout";
|
|
21151
21151
|
const HTML_CLASS = "setto-edit-mode";
|
|
21152
21152
|
function useSettoDocumentLayout(active) {
|
|
21153
21153
|
useEffect(() => {
|
|
21154
21154
|
const html = document.documentElement;
|
|
21155
21155
|
html.classList.add(HTML_CLASS);
|
|
21156
21156
|
html.style.setProperty("--setto-toolbar-height", `${TOOLBAR_HEIGHT}px`);
|
|
21157
|
-
let styleEl = document.getElementById(STYLE_ID);
|
|
21157
|
+
let styleEl = document.getElementById(STYLE_ID$1);
|
|
21158
21158
|
if (!styleEl) {
|
|
21159
21159
|
styleEl = document.createElement("style");
|
|
21160
|
-
styleEl.id = STYLE_ID;
|
|
21160
|
+
styleEl.id = STYLE_ID$1;
|
|
21161
21161
|
styleEl.textContent = `
|
|
21162
21162
|
html.setto-edit-mode {
|
|
21163
21163
|
width: 100%;
|
|
@@ -22150,6 +22150,23 @@ function useEditBaseline() {
|
|
|
22150
22150
|
};
|
|
22151
22151
|
}, [session, store, config.siteId]);
|
|
22152
22152
|
}
|
|
22153
|
+
const SETTO_BASE = "/setto";
|
|
22154
|
+
function editModeUrl(pathname = "/") {
|
|
22155
|
+
const u = new URL(window.location.href);
|
|
22156
|
+
u.pathname = pathname;
|
|
22157
|
+
u.search = "";
|
|
22158
|
+
u.searchParams.set("setto", "edit");
|
|
22159
|
+
return u.toString();
|
|
22160
|
+
}
|
|
22161
|
+
function isAdminRoute() {
|
|
22162
|
+
return window.location.pathname.startsWith(SETTO_BASE);
|
|
22163
|
+
}
|
|
22164
|
+
function isAdminDeploymentView() {
|
|
22165
|
+
return isAdminRoute() && new URLSearchParams(window.location.search).has("deployment");
|
|
22166
|
+
}
|
|
22167
|
+
function adminRedirectUrl() {
|
|
22168
|
+
return `${window.location.origin}${SETTO_BASE}`;
|
|
22169
|
+
}
|
|
22153
22170
|
function EditToolbar() {
|
|
22154
22171
|
const { store, themeStore, assetStore, api, config, supabase } = useSetto();
|
|
22155
22172
|
const { i18n } = useTranslation();
|
|
@@ -22364,7 +22381,7 @@ function EditToolbar() {
|
|
|
22364
22381
|
};
|
|
22365
22382
|
const goToHistory = () => {
|
|
22366
22383
|
setMenuOpen(false);
|
|
22367
|
-
window.location.href =
|
|
22384
|
+
window.location.href = SETTO_BASE;
|
|
22368
22385
|
};
|
|
22369
22386
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
22370
22387
|
/* @__PURE__ */ jsxs("header", { style: barStyle, role: "toolbar", "aria-label": "Setto editor", children: [
|
|
@@ -22397,7 +22414,7 @@ function EditToolbar() {
|
|
|
22397
22414
|
{
|
|
22398
22415
|
status: deploymentRow.status,
|
|
22399
22416
|
compact: true,
|
|
22400
|
-
href:
|
|
22417
|
+
href: `${SETTO_BASE}?deployment=${encodeURIComponent(activeDeployment)}`
|
|
22401
22418
|
}
|
|
22402
22419
|
) : null,
|
|
22403
22420
|
/* @__PURE__ */ jsxs("div", { ref: menuRef, style: { position: "relative" }, children: [
|
|
@@ -22699,7 +22716,8 @@ function useSetto() {
|
|
|
22699
22716
|
}
|
|
22700
22717
|
const GuestEditContext = createContext(null);
|
|
22701
22718
|
function GuestEditProvider({ children }) {
|
|
22702
|
-
const { editMode } = useSetto();
|
|
22719
|
+
const { editMode, themeStore } = useSetto();
|
|
22720
|
+
const { selectSection } = useSectionEdit();
|
|
22703
22721
|
const [active, setActive] = useState(false);
|
|
22704
22722
|
useEffect(() => {
|
|
22705
22723
|
if (editMode && active) setActive(false);
|
|
@@ -22709,8 +22727,14 @@ function GuestEditProvider({ children }) {
|
|
|
22709
22727
|
return () => document.documentElement.classList.remove("setto-guest-editing");
|
|
22710
22728
|
}, [active]);
|
|
22711
22729
|
const start = useCallback(() => setActive(true), []);
|
|
22712
|
-
const stop = useCallback(() =>
|
|
22713
|
-
|
|
22730
|
+
const stop = useCallback(() => {
|
|
22731
|
+
setActive(false);
|
|
22732
|
+
selectSection(null);
|
|
22733
|
+
}, [selectSection]);
|
|
22734
|
+
return /* @__PURE__ */ jsxs(GuestEditContext.Provider, { value: { active, start, stop }, children: [
|
|
22735
|
+
children,
|
|
22736
|
+
active && themeStore ? createPortal(/* @__PURE__ */ jsx(SectionToolbar, {}), document.body) : null
|
|
22737
|
+
] });
|
|
22714
22738
|
}
|
|
22715
22739
|
function useGuestEdit() {
|
|
22716
22740
|
const ctx = useContext(GuestEditContext);
|
|
@@ -22852,9 +22876,9 @@ function T({ k }) {
|
|
|
22852
22876
|
const value = store ? store.get(k, i18n.language) : t(k);
|
|
22853
22877
|
useEffect(() => {
|
|
22854
22878
|
const el = ref.current;
|
|
22855
|
-
if (!el || focused || !
|
|
22879
|
+
if (!el || focused || !editable) return;
|
|
22856
22880
|
if (el.textContent !== value) el.textContent = value;
|
|
22857
|
-
}, [value, focused,
|
|
22881
|
+
}, [value, focused, editable]);
|
|
22858
22882
|
if (!editable) return /* @__PURE__ */ jsx(Fragment, { children: value });
|
|
22859
22883
|
const handleMouseDown = (e) => {
|
|
22860
22884
|
e.stopPropagation();
|
|
@@ -22901,7 +22925,6 @@ function T({ k }) {
|
|
|
22901
22925
|
commit(e.currentTarget);
|
|
22902
22926
|
};
|
|
22903
22927
|
const handleInput = (e) => {
|
|
22904
|
-
if (!editMode) return;
|
|
22905
22928
|
store?.set(k, i18n.language, e.currentTarget.textContent ?? "");
|
|
22906
22929
|
};
|
|
22907
22930
|
const handlePaste = (e) => {
|
|
@@ -22924,7 +22947,7 @@ function T({ k }) {
|
|
|
22924
22947
|
contentEditable: true,
|
|
22925
22948
|
suppressContentEditableWarning: true,
|
|
22926
22949
|
lang,
|
|
22927
|
-
spellCheck:
|
|
22950
|
+
spellCheck: false,
|
|
22928
22951
|
role: "textbox",
|
|
22929
22952
|
tabIndex: 0,
|
|
22930
22953
|
onFocus: (e) => {
|
|
@@ -22977,9 +23000,9 @@ function isNestedThemeTarget(target, container) {
|
|
|
22977
23000
|
const hit = target.closest("[data-setto-section]");
|
|
22978
23001
|
return !!(hit && hit !== container);
|
|
22979
23002
|
}
|
|
22980
|
-
function handleThemeTargetClick(e,
|
|
23003
|
+
function handleThemeTargetClick(e, editable, themeId, selected, selectSection, onClick) {
|
|
22981
23004
|
onClick?.(e);
|
|
22982
|
-
if (!
|
|
23005
|
+
if (!editable || e.defaultPrevented) return;
|
|
22983
23006
|
const target = e.target;
|
|
22984
23007
|
if (isTextEditClick(target)) return;
|
|
22985
23008
|
if (target.closest("a[href], button, input, textarea, select")) return;
|
|
@@ -22987,11 +23010,11 @@ function handleThemeTargetClick(e, editMode, themeId, selected, selectSection, o
|
|
|
22987
23010
|
e.stopPropagation();
|
|
22988
23011
|
selectSection(selected ? null : themeId);
|
|
22989
23012
|
}
|
|
22990
|
-
function themeTargetEditStyle(
|
|
22991
|
-
if (!
|
|
23013
|
+
function themeTargetEditStyle(editable, selected, accent = "#640AFF") {
|
|
23014
|
+
if (!editable) return {};
|
|
22992
23015
|
return {
|
|
22993
23016
|
cursor: "pointer",
|
|
22994
|
-
outline: selected ?
|
|
23017
|
+
outline: selected ? `2px solid ${accent}` : void 0,
|
|
22995
23018
|
outlineOffset: selected ? -2 : void 0
|
|
22996
23019
|
};
|
|
22997
23020
|
}
|
|
@@ -23009,23 +23032,26 @@ function useSectionTheme(sectionId) {
|
|
|
23009
23032
|
const SettoSection = forwardRef(
|
|
23010
23033
|
function SettoSection2({ sectionId, className, style, children, onClick, ...rest }, ref) {
|
|
23011
23034
|
const { editMode } = useSetto();
|
|
23035
|
+
const guest = useGuestEdit();
|
|
23036
|
+
const editable = editMode || guest.active;
|
|
23037
|
+
const accent = editMode ? "#640AFF" : "#C4502A";
|
|
23012
23038
|
const { selectedId, selectSection } = useSectionEdit();
|
|
23013
23039
|
const colors = useSectionTheme(sectionId);
|
|
23014
23040
|
const selected = selectedId === sectionId;
|
|
23015
23041
|
const handleClick = useCallback(
|
|
23016
23042
|
(e) => {
|
|
23017
|
-
if (
|
|
23043
|
+
if (editable) {
|
|
23018
23044
|
const target = e.target;
|
|
23019
23045
|
if (isNestedThemeTarget(target, e.currentTarget)) return;
|
|
23020
23046
|
}
|
|
23021
|
-
handleThemeTargetClick(e,
|
|
23047
|
+
handleThemeTargetClick(e, editable, sectionId, selected, selectSection, onClick);
|
|
23022
23048
|
},
|
|
23023
|
-
[
|
|
23049
|
+
[editable, onClick, selectSection, selected, sectionId]
|
|
23024
23050
|
);
|
|
23025
23051
|
const mergedStyle = {
|
|
23026
23052
|
...style,
|
|
23027
23053
|
...colors.background ? { backgroundColor: colors.background } : {},
|
|
23028
|
-
...themeTargetEditStyle(
|
|
23054
|
+
...themeTargetEditStyle(editable, selected, accent)
|
|
23029
23055
|
};
|
|
23030
23056
|
return /* @__PURE__ */ jsx(
|
|
23031
23057
|
"section",
|
|
@@ -23044,6 +23070,9 @@ const SettoSection = forwardRef(
|
|
|
23044
23070
|
const SettoBlock = forwardRef(
|
|
23045
23071
|
function SettoBlock2({ blockId, className, style, children, onClick, ...rest }, ref) {
|
|
23046
23072
|
const { editMode } = useSetto();
|
|
23073
|
+
const guest = useGuestEdit();
|
|
23074
|
+
const editable = editMode || guest.active;
|
|
23075
|
+
const accent = editMode ? "#640AFF" : "#C4502A";
|
|
23047
23076
|
const { selectedId, selectSection } = useSectionEdit();
|
|
23048
23077
|
const colors = useSectionTheme(blockId);
|
|
23049
23078
|
const selected = selectedId === blockId;
|
|
@@ -23051,19 +23080,19 @@ const SettoBlock = forwardRef(
|
|
|
23051
23080
|
(e) => {
|
|
23052
23081
|
handleThemeTargetClick(
|
|
23053
23082
|
e,
|
|
23054
|
-
|
|
23083
|
+
editable,
|
|
23055
23084
|
blockId,
|
|
23056
23085
|
selected,
|
|
23057
23086
|
selectSection,
|
|
23058
23087
|
onClick
|
|
23059
23088
|
);
|
|
23060
23089
|
},
|
|
23061
|
-
[
|
|
23090
|
+
[editable, blockId, selected, selectSection, onClick]
|
|
23062
23091
|
);
|
|
23063
23092
|
const mergedStyle = {
|
|
23064
23093
|
...style,
|
|
23065
23094
|
...colors.background ? { backgroundColor: colors.background } : {},
|
|
23066
|
-
...themeTargetEditStyle(
|
|
23095
|
+
...themeTargetEditStyle(editable, selected, accent)
|
|
23067
23096
|
};
|
|
23068
23097
|
return /* @__PURE__ */ jsx(
|
|
23069
23098
|
"div",
|
|
@@ -23175,6 +23204,9 @@ function FloatingPopover({
|
|
|
23175
23204
|
function SettoIcon({ k, icons, size = 24, className, style }) {
|
|
23176
23205
|
const { t, i18n } = useTranslation();
|
|
23177
23206
|
const { editMode, store } = useSetto();
|
|
23207
|
+
const guest = useGuestEdit();
|
|
23208
|
+
const editable = editMode || guest.active;
|
|
23209
|
+
const accent = editMode ? "#640AFF" : "#C4502A";
|
|
23178
23210
|
const anchorRef = useRef(null);
|
|
23179
23211
|
const [open, setOpen] = useState(false);
|
|
23180
23212
|
const [search, setSearch] = useState("");
|
|
@@ -23186,7 +23218,7 @@ function SettoIcon({ k, icons, size = 24, className, style }) {
|
|
|
23186
23218
|
const iconName = store ? store.get(k, i18n.language) : t(k);
|
|
23187
23219
|
const Icon = icons[iconName] ?? icons[Object.keys(icons)[0] ?? ""] ?? null;
|
|
23188
23220
|
if (!Icon) return null;
|
|
23189
|
-
if (!
|
|
23221
|
+
if (!editable) {
|
|
23190
23222
|
return /* @__PURE__ */ jsx(Icon, { size, className, style });
|
|
23191
23223
|
}
|
|
23192
23224
|
const handleClick = (e) => {
|
|
@@ -23217,7 +23249,7 @@ function SettoIcon({ k, icons, size = 24, className, style }) {
|
|
|
23217
23249
|
style: {
|
|
23218
23250
|
display: "inline-flex",
|
|
23219
23251
|
cursor: "pointer",
|
|
23220
|
-
outline: open ?
|
|
23252
|
+
outline: open ? `2px solid ${accent}` : void 0,
|
|
23221
23253
|
outlineOffset: 2,
|
|
23222
23254
|
borderRadius: 4
|
|
23223
23255
|
},
|
|
@@ -23484,6 +23516,28 @@ function SettoImage({ srcKey, alt, className, style, ...rest }) {
|
|
|
23484
23516
|
)
|
|
23485
23517
|
] });
|
|
23486
23518
|
}
|
|
23519
|
+
const STYLE_ID = "setto-repeater-styles";
|
|
23520
|
+
function useRepeaterStyles() {
|
|
23521
|
+
useEffect(() => {
|
|
23522
|
+
if (document.getElementById(STYLE_ID)) return;
|
|
23523
|
+
const el = document.createElement("style");
|
|
23524
|
+
el.id = STYLE_ID;
|
|
23525
|
+
el.textContent = `
|
|
23526
|
+
.setto-repeater-remove {
|
|
23527
|
+
opacity: 0;
|
|
23528
|
+
transition: opacity 120ms ease;
|
|
23529
|
+
}
|
|
23530
|
+
.setto-repeater-item:hover .setto-repeater-remove,
|
|
23531
|
+
.setto-repeater-remove:focus-visible {
|
|
23532
|
+
opacity: 1;
|
|
23533
|
+
}
|
|
23534
|
+
@media (hover: none) {
|
|
23535
|
+
.setto-repeater-remove { opacity: 1; }
|
|
23536
|
+
}
|
|
23537
|
+
`;
|
|
23538
|
+
document.head.appendChild(el);
|
|
23539
|
+
}, []);
|
|
23540
|
+
}
|
|
23487
23541
|
function SettoRepeater({
|
|
23488
23542
|
itemsKey,
|
|
23489
23543
|
defaultItem,
|
|
@@ -23493,7 +23547,10 @@ function SettoRepeater({
|
|
|
23493
23547
|
}) {
|
|
23494
23548
|
const { i18n } = useTranslation();
|
|
23495
23549
|
const { editMode, store } = useSetto();
|
|
23550
|
+
const guest = useGuestEdit();
|
|
23551
|
+
const editable = editMode || guest.active;
|
|
23496
23552
|
const lng = i18n.language;
|
|
23553
|
+
useRepeaterStyles();
|
|
23497
23554
|
const [, force] = useState(0);
|
|
23498
23555
|
useEffect(() => {
|
|
23499
23556
|
if (!store) return;
|
|
@@ -23507,16 +23564,35 @@ function SettoRepeater({
|
|
|
23507
23564
|
if (keys.length <= 1) return;
|
|
23508
23565
|
store?.removeListItem(itemsKey, itemKey, lng);
|
|
23509
23566
|
};
|
|
23567
|
+
const canRemove = keys.length > 1;
|
|
23510
23568
|
return /* @__PURE__ */ jsxs("div", { className, "data-setto-repeater": true, children: [
|
|
23511
|
-
|
|
23569
|
+
keys.map((itemKey, index) => /* @__PURE__ */ jsxs("div", { className: "setto-repeater-item", style: { position: "relative" }, children: [
|
|
23570
|
+
editable && canRemove ? /* @__PURE__ */ jsx(
|
|
23571
|
+
"button",
|
|
23572
|
+
{
|
|
23573
|
+
type: "button",
|
|
23574
|
+
"data-setto-ui": true,
|
|
23575
|
+
className: "setto-repeater-remove",
|
|
23576
|
+
"aria-label": `Fjern ${itemLabel}`,
|
|
23577
|
+
title: `Fjern ${itemLabel}`,
|
|
23578
|
+
onClick: (e) => {
|
|
23579
|
+
e.stopPropagation();
|
|
23580
|
+
handleRemove(itemKey);
|
|
23581
|
+
},
|
|
23582
|
+
style: removeBtnStyle,
|
|
23583
|
+
children: "×"
|
|
23584
|
+
}
|
|
23585
|
+
) : null,
|
|
23586
|
+
children(itemKey, index)
|
|
23587
|
+
] }, itemKey)),
|
|
23588
|
+
editable ? /* @__PURE__ */ jsx(
|
|
23512
23589
|
"div",
|
|
23513
23590
|
{
|
|
23514
23591
|
"data-setto-ui": true,
|
|
23515
23592
|
style: {
|
|
23516
23593
|
display: "flex",
|
|
23517
23594
|
justifyContent: "flex-end",
|
|
23518
|
-
|
|
23519
|
-
gap: 8
|
|
23595
|
+
marginTop: 8
|
|
23520
23596
|
},
|
|
23521
23597
|
children: /* @__PURE__ */ jsxs(
|
|
23522
23598
|
"button",
|
|
@@ -23527,7 +23603,7 @@ function SettoRepeater({
|
|
|
23527
23603
|
e.stopPropagation();
|
|
23528
23604
|
handleAdd();
|
|
23529
23605
|
},
|
|
23530
|
-
style:
|
|
23606
|
+
style: addBtnStyle,
|
|
23531
23607
|
children: [
|
|
23532
23608
|
"+ Legg til ",
|
|
23533
23609
|
itemLabel
|
|
@@ -23535,37 +23611,10 @@ function SettoRepeater({
|
|
|
23535
23611
|
}
|
|
23536
23612
|
)
|
|
23537
23613
|
}
|
|
23538
|
-
) : null
|
|
23539
|
-
keys.map((itemKey, index) => /* @__PURE__ */ jsxs("div", { className: "setto-repeater-item", style: { position: "relative" }, children: [
|
|
23540
|
-
editMode ? /* @__PURE__ */ jsx(
|
|
23541
|
-
"button",
|
|
23542
|
-
{
|
|
23543
|
-
type: "button",
|
|
23544
|
-
"data-setto-ui": true,
|
|
23545
|
-
"aria-label": `Fjern ${itemLabel}`,
|
|
23546
|
-
disabled: keys.length <= 1,
|
|
23547
|
-
onClick: (e) => {
|
|
23548
|
-
e.stopPropagation();
|
|
23549
|
-
handleRemove(itemKey);
|
|
23550
|
-
},
|
|
23551
|
-
style: {
|
|
23552
|
-
...controlBtnStyle,
|
|
23553
|
-
position: "absolute",
|
|
23554
|
-
top: 0,
|
|
23555
|
-
right: 0,
|
|
23556
|
-
zIndex: 2,
|
|
23557
|
-
padding: "4px 8px",
|
|
23558
|
-
fontSize: 11,
|
|
23559
|
-
opacity: keys.length <= 1 ? 0.4 : 1
|
|
23560
|
-
},
|
|
23561
|
-
children: "× Fjern"
|
|
23562
|
-
}
|
|
23563
|
-
) : null,
|
|
23564
|
-
children(itemKey, index)
|
|
23565
|
-
] }, itemKey))
|
|
23614
|
+
) : null
|
|
23566
23615
|
] });
|
|
23567
23616
|
}
|
|
23568
|
-
const
|
|
23617
|
+
const addBtnStyle = {
|
|
23569
23618
|
padding: "6px 10px",
|
|
23570
23619
|
background: "#f7f7f7",
|
|
23571
23620
|
border: "1px solid #e0e0e0",
|
|
@@ -23575,22 +23624,27 @@ const controlBtnStyle = {
|
|
|
23575
23624
|
cursor: "pointer",
|
|
23576
23625
|
fontFamily: 'system-ui, -apple-system, "Segoe UI", sans-serif'
|
|
23577
23626
|
};
|
|
23578
|
-
|
|
23579
|
-
|
|
23580
|
-
|
|
23581
|
-
|
|
23582
|
-
|
|
23583
|
-
|
|
23584
|
-
|
|
23585
|
-
|
|
23586
|
-
|
|
23587
|
-
|
|
23588
|
-
|
|
23589
|
-
|
|
23590
|
-
|
|
23591
|
-
|
|
23592
|
-
|
|
23593
|
-
|
|
23627
|
+
const removeBtnStyle = {
|
|
23628
|
+
position: "absolute",
|
|
23629
|
+
top: 4,
|
|
23630
|
+
right: 4,
|
|
23631
|
+
zIndex: 2,
|
|
23632
|
+
display: "flex",
|
|
23633
|
+
alignItems: "center",
|
|
23634
|
+
justifyContent: "center",
|
|
23635
|
+
width: 24,
|
|
23636
|
+
height: 24,
|
|
23637
|
+
padding: 0,
|
|
23638
|
+
background: "#ffffff",
|
|
23639
|
+
border: "1px solid #e0e0e0",
|
|
23640
|
+
borderRadius: "50%",
|
|
23641
|
+
fontSize: 16,
|
|
23642
|
+
lineHeight: 1,
|
|
23643
|
+
color: "#444",
|
|
23644
|
+
cursor: "pointer",
|
|
23645
|
+
boxShadow: "0 1px 4px rgba(0,0,0,0.18)",
|
|
23646
|
+
fontFamily: 'system-ui, -apple-system, "Segoe UI", sans-serif'
|
|
23647
|
+
};
|
|
23594
23648
|
function authCallbackType() {
|
|
23595
23649
|
const hash = window.location.hash.replace(/^#/, "");
|
|
23596
23650
|
if (!hash) return null;
|
|
@@ -23871,7 +23925,7 @@ const loadingStyle = {
|
|
|
23871
23925
|
};
|
|
23872
23926
|
function deploymentAdminUrl(deploymentId) {
|
|
23873
23927
|
const u = new URL(window.location.href);
|
|
23874
|
-
u.pathname =
|
|
23928
|
+
u.pathname = SETTO_BASE;
|
|
23875
23929
|
u.search = "";
|
|
23876
23930
|
u.searchParams.set("deployment", deploymentId);
|
|
23877
23931
|
return u.pathname + u.search;
|
|
@@ -23984,7 +24038,7 @@ function DeploymentList({ siteId }) {
|
|
|
23984
24038
|
try {
|
|
23985
24039
|
await api.cancelDeployment(config.siteId, focusId);
|
|
23986
24040
|
setFocusId(null);
|
|
23987
|
-
window.history.replaceState(null, "",
|
|
24041
|
+
window.history.replaceState(null, "", SETTO_BASE);
|
|
23988
24042
|
} catch (err) {
|
|
23989
24043
|
setCancelError(err instanceof Error ? err.message : "Kunne ikke avbryte");
|
|
23990
24044
|
} finally {
|
|
@@ -24213,6 +24267,7 @@ function depDotStyle(status) {
|
|
|
24213
24267
|
export {
|
|
24214
24268
|
AuthGate,
|
|
24215
24269
|
GuestEditProvider,
|
|
24270
|
+
SETTO_BASE,
|
|
24216
24271
|
SettoAdminApp,
|
|
24217
24272
|
SettoBlock,
|
|
24218
24273
|
SettoIcon,
|