@sevenfold/setto-client 0.3.3 → 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 +16 -0
- package/dist/index.d.ts +2 -0
- package/dist/lib/urls.d.ts +7 -1
- package/dist/provider.d.ts +1 -1
- package/dist/setto-client.js +159 -75
- 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
|
*/
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
interface GuestEditContextValue {
|
|
3
|
+
active: boolean;
|
|
4
|
+
start: () => void;
|
|
5
|
+
stop: () => void;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
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
|
+
*/
|
|
12
|
+
export declare function GuestEditProvider({ children }: {
|
|
13
|
+
children: ReactNode;
|
|
14
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
15
|
+
export declare function useGuestEdit(): GuestEditContextValue;
|
|
16
|
+
export {};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { SettoProvider, useSetto } from './provider';
|
|
2
|
+
export { GuestEditProvider, useGuestEdit } from './guest-edit';
|
|
2
3
|
export { T } from './T';
|
|
3
4
|
export { SettoSection } from './SettoSection';
|
|
4
5
|
export { SettoBlock } from './SettoBlock';
|
|
@@ -10,6 +11,7 @@ export { SettoRepeater } from './SettoRepeater';
|
|
|
10
11
|
export type { SettoRepeaterProps } from './SettoRepeater';
|
|
11
12
|
export { useSectionTheme } from './use-section-theme';
|
|
12
13
|
export { SettoAdminApp } from './admin/App';
|
|
14
|
+
export { SETTO_BASE } from './lib/urls';
|
|
13
15
|
export { AuthGate } from './edit-mode/auth-gate';
|
|
14
16
|
export type { SettoConfig, BrandColor, DeploymentRow, SiteRow, ContentFile, PublishFile, PublishResult, } from './types';
|
|
15
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: [
|
|
@@ -22697,6 +22714,37 @@ function useSetto() {
|
|
|
22697
22714
|
}
|
|
22698
22715
|
return ctx;
|
|
22699
22716
|
}
|
|
22717
|
+
const GuestEditContext = createContext(null);
|
|
22718
|
+
function GuestEditProvider({ children }) {
|
|
22719
|
+
const { editMode, themeStore } = useSetto();
|
|
22720
|
+
const { selectSection } = useSectionEdit();
|
|
22721
|
+
const [active, setActive] = useState(false);
|
|
22722
|
+
useEffect(() => {
|
|
22723
|
+
if (editMode && active) setActive(false);
|
|
22724
|
+
}, [editMode, active]);
|
|
22725
|
+
useEffect(() => {
|
|
22726
|
+
document.documentElement.classList.toggle("setto-guest-editing", active);
|
|
22727
|
+
return () => document.documentElement.classList.remove("setto-guest-editing");
|
|
22728
|
+
}, [active]);
|
|
22729
|
+
const start = useCallback(() => setActive(true), []);
|
|
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
|
+
] });
|
|
22738
|
+
}
|
|
22739
|
+
function useGuestEdit() {
|
|
22740
|
+
const ctx = useContext(GuestEditContext);
|
|
22741
|
+
if (!ctx) {
|
|
22742
|
+
return { active: false, start: () => {
|
|
22743
|
+
}, stop: () => {
|
|
22744
|
+
} };
|
|
22745
|
+
}
|
|
22746
|
+
return ctx;
|
|
22747
|
+
}
|
|
22700
22748
|
const MARGIN = 10;
|
|
22701
22749
|
const OFFSET = 8;
|
|
22702
22750
|
const MIN_TOUCH = 44;
|
|
@@ -22814,6 +22862,8 @@ function rangeFromPoint(doc, x, y) {
|
|
|
22814
22862
|
function T({ k }) {
|
|
22815
22863
|
const { t, i18n } = useTranslation();
|
|
22816
22864
|
const { editMode, store } = useSetto();
|
|
22865
|
+
const guestEdit = useGuestEdit();
|
|
22866
|
+
const editable = editMode || guestEdit.active;
|
|
22817
22867
|
const ref = useRef(null);
|
|
22818
22868
|
const [focused, setFocused] = useState(false);
|
|
22819
22869
|
const [linkContext, setLinkContext] = useState(false);
|
|
@@ -22826,10 +22876,10 @@ function T({ k }) {
|
|
|
22826
22876
|
const value = store ? store.get(k, i18n.language) : t(k);
|
|
22827
22877
|
useEffect(() => {
|
|
22828
22878
|
const el = ref.current;
|
|
22829
|
-
if (!el || focused || !
|
|
22879
|
+
if (!el || focused || !editable) return;
|
|
22830
22880
|
if (el.textContent !== value) el.textContent = value;
|
|
22831
|
-
}, [value, focused,
|
|
22832
|
-
if (!
|
|
22881
|
+
}, [value, focused, editable]);
|
|
22882
|
+
if (!editable) return /* @__PURE__ */ jsx(Fragment, { children: value });
|
|
22833
22883
|
const handleMouseDown = (e) => {
|
|
22834
22884
|
e.stopPropagation();
|
|
22835
22885
|
const el = e.currentTarget;
|
|
@@ -22897,7 +22947,7 @@ function T({ k }) {
|
|
|
22897
22947
|
contentEditable: true,
|
|
22898
22948
|
suppressContentEditableWarning: true,
|
|
22899
22949
|
lang,
|
|
22900
|
-
spellCheck:
|
|
22950
|
+
spellCheck: false,
|
|
22901
22951
|
role: "textbox",
|
|
22902
22952
|
tabIndex: 0,
|
|
22903
22953
|
onFocus: (e) => {
|
|
@@ -22915,7 +22965,7 @@ function T({ k }) {
|
|
|
22915
22965
|
style: {
|
|
22916
22966
|
cursor: "text",
|
|
22917
22967
|
color: "inherit",
|
|
22918
|
-
outline: focused ? "2px solid #640AFF" : void 0,
|
|
22968
|
+
outline: focused ? editMode ? "2px solid #640AFF" : "2px solid #C4502A" : void 0,
|
|
22919
22969
|
outlineOffset: 2,
|
|
22920
22970
|
borderRadius: 2,
|
|
22921
22971
|
transition: "outline-color 120ms"
|
|
@@ -22950,9 +23000,9 @@ function isNestedThemeTarget(target, container) {
|
|
|
22950
23000
|
const hit = target.closest("[data-setto-section]");
|
|
22951
23001
|
return !!(hit && hit !== container);
|
|
22952
23002
|
}
|
|
22953
|
-
function handleThemeTargetClick(e,
|
|
23003
|
+
function handleThemeTargetClick(e, editable, themeId, selected, selectSection, onClick) {
|
|
22954
23004
|
onClick?.(e);
|
|
22955
|
-
if (!
|
|
23005
|
+
if (!editable || e.defaultPrevented) return;
|
|
22956
23006
|
const target = e.target;
|
|
22957
23007
|
if (isTextEditClick(target)) return;
|
|
22958
23008
|
if (target.closest("a[href], button, input, textarea, select")) return;
|
|
@@ -22960,11 +23010,11 @@ function handleThemeTargetClick(e, editMode, themeId, selected, selectSection, o
|
|
|
22960
23010
|
e.stopPropagation();
|
|
22961
23011
|
selectSection(selected ? null : themeId);
|
|
22962
23012
|
}
|
|
22963
|
-
function themeTargetEditStyle(
|
|
22964
|
-
if (!
|
|
23013
|
+
function themeTargetEditStyle(editable, selected, accent = "#640AFF") {
|
|
23014
|
+
if (!editable) return {};
|
|
22965
23015
|
return {
|
|
22966
23016
|
cursor: "pointer",
|
|
22967
|
-
outline: selected ?
|
|
23017
|
+
outline: selected ? `2px solid ${accent}` : void 0,
|
|
22968
23018
|
outlineOffset: selected ? -2 : void 0
|
|
22969
23019
|
};
|
|
22970
23020
|
}
|
|
@@ -22982,23 +23032,26 @@ function useSectionTheme(sectionId) {
|
|
|
22982
23032
|
const SettoSection = forwardRef(
|
|
22983
23033
|
function SettoSection2({ sectionId, className, style, children, onClick, ...rest }, ref) {
|
|
22984
23034
|
const { editMode } = useSetto();
|
|
23035
|
+
const guest = useGuestEdit();
|
|
23036
|
+
const editable = editMode || guest.active;
|
|
23037
|
+
const accent = editMode ? "#640AFF" : "#C4502A";
|
|
22985
23038
|
const { selectedId, selectSection } = useSectionEdit();
|
|
22986
23039
|
const colors = useSectionTheme(sectionId);
|
|
22987
23040
|
const selected = selectedId === sectionId;
|
|
22988
23041
|
const handleClick = useCallback(
|
|
22989
23042
|
(e) => {
|
|
22990
|
-
if (
|
|
23043
|
+
if (editable) {
|
|
22991
23044
|
const target = e.target;
|
|
22992
23045
|
if (isNestedThemeTarget(target, e.currentTarget)) return;
|
|
22993
23046
|
}
|
|
22994
|
-
handleThemeTargetClick(e,
|
|
23047
|
+
handleThemeTargetClick(e, editable, sectionId, selected, selectSection, onClick);
|
|
22995
23048
|
},
|
|
22996
|
-
[
|
|
23049
|
+
[editable, onClick, selectSection, selected, sectionId]
|
|
22997
23050
|
);
|
|
22998
23051
|
const mergedStyle = {
|
|
22999
23052
|
...style,
|
|
23000
23053
|
...colors.background ? { backgroundColor: colors.background } : {},
|
|
23001
|
-
...themeTargetEditStyle(
|
|
23054
|
+
...themeTargetEditStyle(editable, selected, accent)
|
|
23002
23055
|
};
|
|
23003
23056
|
return /* @__PURE__ */ jsx(
|
|
23004
23057
|
"section",
|
|
@@ -23017,6 +23070,9 @@ const SettoSection = forwardRef(
|
|
|
23017
23070
|
const SettoBlock = forwardRef(
|
|
23018
23071
|
function SettoBlock2({ blockId, className, style, children, onClick, ...rest }, ref) {
|
|
23019
23072
|
const { editMode } = useSetto();
|
|
23073
|
+
const guest = useGuestEdit();
|
|
23074
|
+
const editable = editMode || guest.active;
|
|
23075
|
+
const accent = editMode ? "#640AFF" : "#C4502A";
|
|
23020
23076
|
const { selectedId, selectSection } = useSectionEdit();
|
|
23021
23077
|
const colors = useSectionTheme(blockId);
|
|
23022
23078
|
const selected = selectedId === blockId;
|
|
@@ -23024,19 +23080,19 @@ const SettoBlock = forwardRef(
|
|
|
23024
23080
|
(e) => {
|
|
23025
23081
|
handleThemeTargetClick(
|
|
23026
23082
|
e,
|
|
23027
|
-
|
|
23083
|
+
editable,
|
|
23028
23084
|
blockId,
|
|
23029
23085
|
selected,
|
|
23030
23086
|
selectSection,
|
|
23031
23087
|
onClick
|
|
23032
23088
|
);
|
|
23033
23089
|
},
|
|
23034
|
-
[
|
|
23090
|
+
[editable, blockId, selected, selectSection, onClick]
|
|
23035
23091
|
);
|
|
23036
23092
|
const mergedStyle = {
|
|
23037
23093
|
...style,
|
|
23038
23094
|
...colors.background ? { backgroundColor: colors.background } : {},
|
|
23039
|
-
...themeTargetEditStyle(
|
|
23095
|
+
...themeTargetEditStyle(editable, selected, accent)
|
|
23040
23096
|
};
|
|
23041
23097
|
return /* @__PURE__ */ jsx(
|
|
23042
23098
|
"div",
|
|
@@ -23148,6 +23204,9 @@ function FloatingPopover({
|
|
|
23148
23204
|
function SettoIcon({ k, icons, size = 24, className, style }) {
|
|
23149
23205
|
const { t, i18n } = useTranslation();
|
|
23150
23206
|
const { editMode, store } = useSetto();
|
|
23207
|
+
const guest = useGuestEdit();
|
|
23208
|
+
const editable = editMode || guest.active;
|
|
23209
|
+
const accent = editMode ? "#640AFF" : "#C4502A";
|
|
23151
23210
|
const anchorRef = useRef(null);
|
|
23152
23211
|
const [open, setOpen] = useState(false);
|
|
23153
23212
|
const [search, setSearch] = useState("");
|
|
@@ -23159,7 +23218,7 @@ function SettoIcon({ k, icons, size = 24, className, style }) {
|
|
|
23159
23218
|
const iconName = store ? store.get(k, i18n.language) : t(k);
|
|
23160
23219
|
const Icon = icons[iconName] ?? icons[Object.keys(icons)[0] ?? ""] ?? null;
|
|
23161
23220
|
if (!Icon) return null;
|
|
23162
|
-
if (!
|
|
23221
|
+
if (!editable) {
|
|
23163
23222
|
return /* @__PURE__ */ jsx(Icon, { size, className, style });
|
|
23164
23223
|
}
|
|
23165
23224
|
const handleClick = (e) => {
|
|
@@ -23190,7 +23249,7 @@ function SettoIcon({ k, icons, size = 24, className, style }) {
|
|
|
23190
23249
|
style: {
|
|
23191
23250
|
display: "inline-flex",
|
|
23192
23251
|
cursor: "pointer",
|
|
23193
|
-
outline: open ?
|
|
23252
|
+
outline: open ? `2px solid ${accent}` : void 0,
|
|
23194
23253
|
outlineOffset: 2,
|
|
23195
23254
|
borderRadius: 4
|
|
23196
23255
|
},
|
|
@@ -23457,6 +23516,28 @@ function SettoImage({ srcKey, alt, className, style, ...rest }) {
|
|
|
23457
23516
|
)
|
|
23458
23517
|
] });
|
|
23459
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
|
+
}
|
|
23460
23541
|
function SettoRepeater({
|
|
23461
23542
|
itemsKey,
|
|
23462
23543
|
defaultItem,
|
|
@@ -23466,7 +23547,10 @@ function SettoRepeater({
|
|
|
23466
23547
|
}) {
|
|
23467
23548
|
const { i18n } = useTranslation();
|
|
23468
23549
|
const { editMode, store } = useSetto();
|
|
23550
|
+
const guest = useGuestEdit();
|
|
23551
|
+
const editable = editMode || guest.active;
|
|
23469
23552
|
const lng = i18n.language;
|
|
23553
|
+
useRepeaterStyles();
|
|
23470
23554
|
const [, force] = useState(0);
|
|
23471
23555
|
useEffect(() => {
|
|
23472
23556
|
if (!store) return;
|
|
@@ -23480,16 +23564,35 @@ function SettoRepeater({
|
|
|
23480
23564
|
if (keys.length <= 1) return;
|
|
23481
23565
|
store?.removeListItem(itemsKey, itemKey, lng);
|
|
23482
23566
|
};
|
|
23567
|
+
const canRemove = keys.length > 1;
|
|
23483
23568
|
return /* @__PURE__ */ jsxs("div", { className, "data-setto-repeater": true, children: [
|
|
23484
|
-
|
|
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(
|
|
23485
23589
|
"div",
|
|
23486
23590
|
{
|
|
23487
23591
|
"data-setto-ui": true,
|
|
23488
23592
|
style: {
|
|
23489
23593
|
display: "flex",
|
|
23490
23594
|
justifyContent: "flex-end",
|
|
23491
|
-
|
|
23492
|
-
gap: 8
|
|
23595
|
+
marginTop: 8
|
|
23493
23596
|
},
|
|
23494
23597
|
children: /* @__PURE__ */ jsxs(
|
|
23495
23598
|
"button",
|
|
@@ -23500,7 +23603,7 @@ function SettoRepeater({
|
|
|
23500
23603
|
e.stopPropagation();
|
|
23501
23604
|
handleAdd();
|
|
23502
23605
|
},
|
|
23503
|
-
style:
|
|
23606
|
+
style: addBtnStyle,
|
|
23504
23607
|
children: [
|
|
23505
23608
|
"+ Legg til ",
|
|
23506
23609
|
itemLabel
|
|
@@ -23508,37 +23611,10 @@ function SettoRepeater({
|
|
|
23508
23611
|
}
|
|
23509
23612
|
)
|
|
23510
23613
|
}
|
|
23511
|
-
) : null
|
|
23512
|
-
keys.map((itemKey, index) => /* @__PURE__ */ jsxs("div", { className: "setto-repeater-item", style: { position: "relative" }, children: [
|
|
23513
|
-
editMode ? /* @__PURE__ */ jsx(
|
|
23514
|
-
"button",
|
|
23515
|
-
{
|
|
23516
|
-
type: "button",
|
|
23517
|
-
"data-setto-ui": true,
|
|
23518
|
-
"aria-label": `Fjern ${itemLabel}`,
|
|
23519
|
-
disabled: keys.length <= 1,
|
|
23520
|
-
onClick: (e) => {
|
|
23521
|
-
e.stopPropagation();
|
|
23522
|
-
handleRemove(itemKey);
|
|
23523
|
-
},
|
|
23524
|
-
style: {
|
|
23525
|
-
...controlBtnStyle,
|
|
23526
|
-
position: "absolute",
|
|
23527
|
-
top: 0,
|
|
23528
|
-
right: 0,
|
|
23529
|
-
zIndex: 2,
|
|
23530
|
-
padding: "4px 8px",
|
|
23531
|
-
fontSize: 11,
|
|
23532
|
-
opacity: keys.length <= 1 ? 0.4 : 1
|
|
23533
|
-
},
|
|
23534
|
-
children: "× Fjern"
|
|
23535
|
-
}
|
|
23536
|
-
) : null,
|
|
23537
|
-
children(itemKey, index)
|
|
23538
|
-
] }, itemKey))
|
|
23614
|
+
) : null
|
|
23539
23615
|
] });
|
|
23540
23616
|
}
|
|
23541
|
-
const
|
|
23617
|
+
const addBtnStyle = {
|
|
23542
23618
|
padding: "6px 10px",
|
|
23543
23619
|
background: "#f7f7f7",
|
|
23544
23620
|
border: "1px solid #e0e0e0",
|
|
@@ -23548,22 +23624,27 @@ const controlBtnStyle = {
|
|
|
23548
23624
|
cursor: "pointer",
|
|
23549
23625
|
fontFamily: 'system-ui, -apple-system, "Segoe UI", sans-serif'
|
|
23550
23626
|
};
|
|
23551
|
-
|
|
23552
|
-
|
|
23553
|
-
|
|
23554
|
-
|
|
23555
|
-
|
|
23556
|
-
|
|
23557
|
-
|
|
23558
|
-
|
|
23559
|
-
|
|
23560
|
-
|
|
23561
|
-
|
|
23562
|
-
|
|
23563
|
-
|
|
23564
|
-
|
|
23565
|
-
|
|
23566
|
-
|
|
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
|
+
};
|
|
23567
23648
|
function authCallbackType() {
|
|
23568
23649
|
const hash = window.location.hash.replace(/^#/, "");
|
|
23569
23650
|
if (!hash) return null;
|
|
@@ -23844,7 +23925,7 @@ const loadingStyle = {
|
|
|
23844
23925
|
};
|
|
23845
23926
|
function deploymentAdminUrl(deploymentId) {
|
|
23846
23927
|
const u = new URL(window.location.href);
|
|
23847
|
-
u.pathname =
|
|
23928
|
+
u.pathname = SETTO_BASE;
|
|
23848
23929
|
u.search = "";
|
|
23849
23930
|
u.searchParams.set("deployment", deploymentId);
|
|
23850
23931
|
return u.pathname + u.search;
|
|
@@ -23957,7 +24038,7 @@ function DeploymentList({ siteId }) {
|
|
|
23957
24038
|
try {
|
|
23958
24039
|
await api.cancelDeployment(config.siteId, focusId);
|
|
23959
24040
|
setFocusId(null);
|
|
23960
|
-
window.history.replaceState(null, "",
|
|
24041
|
+
window.history.replaceState(null, "", SETTO_BASE);
|
|
23961
24042
|
} catch (err) {
|
|
23962
24043
|
setCancelError(err instanceof Error ? err.message : "Kunne ikke avbryte");
|
|
23963
24044
|
} finally {
|
|
@@ -24185,6 +24266,8 @@ function depDotStyle(status) {
|
|
|
24185
24266
|
}
|
|
24186
24267
|
export {
|
|
24187
24268
|
AuthGate,
|
|
24269
|
+
GuestEditProvider,
|
|
24270
|
+
SETTO_BASE,
|
|
24188
24271
|
SettoAdminApp,
|
|
24189
24272
|
SettoBlock,
|
|
24190
24273
|
SettoIcon,
|
|
@@ -24193,6 +24276,7 @@ export {
|
|
|
24193
24276
|
SettoRepeater,
|
|
24194
24277
|
SettoSection,
|
|
24195
24278
|
T,
|
|
24279
|
+
useGuestEdit,
|
|
24196
24280
|
useSectionTheme,
|
|
24197
24281
|
useSetto
|
|
24198
24282
|
};
|