@sevenfold/setto-client 0.1.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 +369 -0
- package/dist/SettoBlock.d.ts +10 -0
- package/dist/SettoSection.d.ts +11 -0
- package/dist/T.d.ts +9 -0
- package/dist/admin/App.d.ts +12 -0
- package/dist/edit-mode/auth-gate.d.ts +10 -0
- package/dist/edit-mode/build-status.d.ts +13 -0
- package/dist/edit-mode/constants.d.ts +2 -0
- package/dist/edit-mode/document-layout.d.ts +5 -0
- package/dist/edit-mode/link-hint.d.ts +5 -0
- package/dist/edit-mode/mount.d.ts +14 -0
- package/dist/edit-mode/publish-utils.d.ts +4 -0
- package/dist/edit-mode/section-toolbar.d.ts +1 -0
- package/dist/edit-mode/toolbar.d.ts +1 -0
- package/dist/edit-mode/use-edit-baseline.d.ts +2 -0
- package/dist/index.d.ts +9 -0
- package/dist/lib/api.d.ts +15 -0
- package/dist/lib/color-utils.d.ts +4 -0
- package/dist/lib/i18n-store.d.ts +75 -0
- package/dist/lib/supabase.d.ts +9 -0
- package/dist/lib/theme-store.d.ts +35 -0
- package/dist/provider.d.ts +40 -0
- package/dist/section-context.d.ts +10 -0
- package/dist/section-schema.d.ts +10 -0
- package/dist/setto-client.js +22378 -0
- package/dist/setto-client.js.map +1 -0
- package/dist/theme-target-utils.d.ts +7 -0
- package/dist/types.d.ts +62 -0
- package/dist/use-section-theme.d.ts +2 -0
- package/package.json +49 -0
package/README.md
ADDED
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
# @setto/client
|
|
2
|
+
|
|
3
|
+
React client library for Setto — git-based inline CMS for Vite + i18next apps.
|
|
4
|
+
|
|
5
|
+
Published on npm as **`@sevenfold/setto-client`** (Sevenfold org). Import as `@setto/client` in app code.
|
|
6
|
+
|
|
7
|
+
Editors authenticate via Supabase, edit text inline on the live page, pick section colours from the site brand palette, and publish changes back to GitHub. setto-server commits whitelisted files and tracks the Vercel deployment.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
bun add @setto/client@npm:@sevenfold/setto-client
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Or add directly to `package.json`:
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
"@setto/client": "npm:@sevenfold/setto-client@^0.1.0"
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Peer deps: `react`, `react-dom`, `react-i18next`, `i18next`.
|
|
22
|
+
|
|
23
|
+
For local monorepo development alongside `carryon.no`, a `file:` link also works — see [Local development](#local-development-sevenfold-monorepo).
|
|
24
|
+
|
|
25
|
+
### Publishing a new version
|
|
26
|
+
|
|
27
|
+
1. Bump `version` in `package.json`
|
|
28
|
+
2. Commit and push a tag: `git tag v0.1.1 && git push origin v0.1.1`
|
|
29
|
+
3. GitHub Actions publishes to npm automatically
|
|
30
|
+
|
|
31
|
+
Requires an `NPM_TOKEN` secret in the GitHub repo (Granular Access Token from npmjs.com with write access to `@sevenfold`).
|
|
32
|
+
|
|
33
|
+
Or trigger manually via **Actions → Publish to npm → Run workflow**.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Quick start
|
|
38
|
+
|
|
39
|
+
### 1. Wrap the app
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
// main.tsx
|
|
43
|
+
import { SettoProvider } from '@setto/client';
|
|
44
|
+
import sectionsTheme from './theme/sections.json';
|
|
45
|
+
import { brandColors } from './theme/brand-colors';
|
|
46
|
+
import { sectionSchemas } from './theme/section-schemas';
|
|
47
|
+
|
|
48
|
+
createRoot(document.getElementById('root')!).render(
|
|
49
|
+
<SettoProvider
|
|
50
|
+
config={{
|
|
51
|
+
siteId: 'my-site', // slug in setto-server `sites` table
|
|
52
|
+
apiUrl: import.meta.env.VITE_SETTO_API_URL,
|
|
53
|
+
supabase: {
|
|
54
|
+
url: import.meta.env.VITE_SUPABASE_URL,
|
|
55
|
+
anonKey: import.meta.env.VITE_SUPABASE_ANON_KEY,
|
|
56
|
+
},
|
|
57
|
+
theme: sectionsTheme, // bundled defaults for section colours
|
|
58
|
+
themePath: 'src/theme/sections.json', // GitHub path — must be in content_paths
|
|
59
|
+
brandColors, // palette for the colour toolbar
|
|
60
|
+
sectionSchemas, // which fields each section exposes
|
|
61
|
+
}}
|
|
62
|
+
>
|
|
63
|
+
<BrowserRouter>
|
|
64
|
+
<App />
|
|
65
|
+
</BrowserRouter>
|
|
66
|
+
</SettoProvider>,
|
|
67
|
+
);
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### 2. Mount the admin route
|
|
71
|
+
|
|
72
|
+
```tsx
|
|
73
|
+
// App.tsx
|
|
74
|
+
import { SettoAdminApp } from '@setto/client';
|
|
75
|
+
|
|
76
|
+
<Routes>
|
|
77
|
+
<Route path="/" element={<Home />} />
|
|
78
|
+
<Route path="/admin/*" element={<SettoAdminApp />} />
|
|
79
|
+
</Routes>
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### 3. Replace copy with `<T>`
|
|
83
|
+
|
|
84
|
+
```tsx
|
|
85
|
+
import { T } from '@setto/client';
|
|
86
|
+
|
|
87
|
+
<h1><T k="hero.headline" /></h1>
|
|
88
|
+
<p><T k="hero.subheadline" /></p>
|
|
89
|
+
|
|
90
|
+
{/* Dynamic keys work too */}
|
|
91
|
+
<T k={`faq.items.${item.key}.q`} />
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Keep using `t()` for non-visible strings (placeholders, `aria-label`, `alt`) — those are not inline-editable in v0.
|
|
95
|
+
|
|
96
|
+
### 4. Wire section colours (optional)
|
|
97
|
+
|
|
98
|
+
```tsx
|
|
99
|
+
import { SettoSection, useSectionTheme } from '@setto/client';
|
|
100
|
+
|
|
101
|
+
function ValuesSection() {
|
|
102
|
+
const colors = useSectionTheme('values');
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<SettoSection sectionId="values" id="verdier" className="py-20">
|
|
106
|
+
<span style={{ color: colors.label }}><T k="values.label" /></span>
|
|
107
|
+
<h2 style={{ color: colors.heading }}><T k="values.headline" /></h2>
|
|
108
|
+
</SettoSection>
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Environment variables (host app)
|
|
116
|
+
|
|
117
|
+
| Variable | Purpose |
|
|
118
|
+
|----------|---------|
|
|
119
|
+
| `VITE_SUPABASE_URL` | Supabase project URL |
|
|
120
|
+
| `VITE_SUPABASE_ANON_KEY` | Supabase anon key (public) |
|
|
121
|
+
| `VITE_SETTO_API_URL` | setto-server base URL, e.g. `http://localhost:3001` |
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Edit mode
|
|
126
|
+
|
|
127
|
+
Edit mode activates when **both** are true:
|
|
128
|
+
|
|
129
|
+
1. Authenticated Supabase session.
|
|
130
|
+
2. URL contains `?setto=edit`.
|
|
131
|
+
|
|
132
|
+
The `/admin` dashboard does **not** activate edit mode. Use **Begynn å redigere**, which navigates to `/?setto=edit`.
|
|
133
|
+
|
|
134
|
+
### What editors see
|
|
135
|
+
|
|
136
|
+
| Action | How |
|
|
137
|
+
|--------|-----|
|
|
138
|
+
| Edit text | Click any `<T>` element — it becomes `contentEditable` |
|
|
139
|
+
| Edit section colours | Click a section or block background (not text) |
|
|
140
|
+
| Follow a link | Ctrl/Cmd + click on nav links |
|
|
141
|
+
| Publish | Top toolbar → **Publiser** |
|
|
142
|
+
| Exit | Top toolbar → **Avslutt** (removes `?setto=edit`) |
|
|
143
|
+
|
|
144
|
+
A fixed toolbar sits at the top of the viewport. When you click a section or block, a compact colour toolbar appears above it, centred and sized to its contents. Click again or press Escape to dismiss.
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## Text editing (`<T>`)
|
|
149
|
+
|
|
150
|
+
`<T k="dotted.key" />` renders the i18next string for the active language.
|
|
151
|
+
|
|
152
|
+
In edit mode the text is inline-editable. Changes are stored in an in-memory draft layer (`I18nStore`) and applied to the live i18next bundle immediately so the page re-renders.
|
|
153
|
+
|
|
154
|
+
On publish, the full locale bundles are serialised to JSON and committed to GitHub:
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
src/i18n/locales/no.json
|
|
158
|
+
src/i18n/locales/en.json
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
When edit mode starts, setto-server loads the current files from GitHub as the baseline (`GET /sites/:id/content`).
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## Section colours
|
|
166
|
+
|
|
167
|
+
Section colours live in a separate JSON file (not in i18n):
|
|
168
|
+
|
|
169
|
+
```
|
|
170
|
+
src/theme/sections.json
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Example:
|
|
174
|
+
|
|
175
|
+
```json
|
|
176
|
+
{
|
|
177
|
+
"values": {
|
|
178
|
+
"background": "#640AFF",
|
|
179
|
+
"label": "#C9C0DA",
|
|
180
|
+
"heading": "#FFFFFF",
|
|
181
|
+
"icon": "#FFFFFF",
|
|
182
|
+
"cardTitle": "#FFFFFF",
|
|
183
|
+
"cardDesc": "#C9C0DA"
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Brand palette (`brandColors`)
|
|
189
|
+
|
|
190
|
+
Editors do **not** get a free-form colour picker. Each colour field shows the **current swatch**; clicking it opens a menu of predefined brand colours:
|
|
191
|
+
|
|
192
|
+
```ts
|
|
193
|
+
// theme/brand-colors.ts
|
|
194
|
+
import type { BrandColor } from '@setto/client';
|
|
195
|
+
|
|
196
|
+
export const brandColors: BrandColor[] = [
|
|
197
|
+
{ label: 'Beige', value: '#E6DCCF' },
|
|
198
|
+
{ label: 'Oliven', value: '#362F00' },
|
|
199
|
+
{ label: 'Lilla', value: '#640AFF' },
|
|
200
|
+
// …
|
|
201
|
+
];
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
**Rules for integrators:**
|
|
205
|
+
|
|
206
|
+
- List every colour token editors should be able to pick.
|
|
207
|
+
- Values must match the palette used in Tailwind/CSS on the site (same hex values).
|
|
208
|
+
- Use solid hex colours in `sections.json` — rgba values won't match swatches.
|
|
209
|
+
- When you add a new brand token to Tailwind, add it to `brandColors` too.
|
|
210
|
+
|
|
211
|
+
### Section schemas (`sectionSchemas`)
|
|
212
|
+
|
|
213
|
+
Define which colour fields each section exposes in the toolbar:
|
|
214
|
+
|
|
215
|
+
```ts
|
|
216
|
+
// theme/section-schemas.ts
|
|
217
|
+
import type { SectionSchema } from '@setto/client';
|
|
218
|
+
|
|
219
|
+
export const sectionSchemas: Record<string, SectionSchema> = {
|
|
220
|
+
values: {
|
|
221
|
+
label: 'Verdier',
|
|
222
|
+
fields: [
|
|
223
|
+
{ key: 'background', label: 'Bakgrunn' },
|
|
224
|
+
{ key: 'label', label: 'Etikett' },
|
|
225
|
+
{ key: 'heading', label: 'Overskrift' },
|
|
226
|
+
// keys must match properties used in useSectionTheme('values')
|
|
227
|
+
],
|
|
228
|
+
},
|
|
229
|
+
};
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
The `sectionId` prop on `<SettoSection>` must match a key in both `sectionSchemas` and `sections.json`.
|
|
233
|
+
|
|
234
|
+
### Applying colours in components
|
|
235
|
+
|
|
236
|
+
Read tokens with `useSectionTheme(sectionId)` and apply via inline `style` (or CSS variables you control):
|
|
237
|
+
|
|
238
|
+
```tsx
|
|
239
|
+
const colors = useSectionTheme('hero');
|
|
240
|
+
|
|
241
|
+
<SettoSection sectionId="hero" style={{ /* background applied automatically */ }}>
|
|
242
|
+
<h1 style={{ color: colors.heading }}><T k="hero.headline" /></h1>
|
|
243
|
+
</SettoSection>
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
`<SettoSection>` always applies `colors.background` as `backgroundColor`. Other tokens are your responsibility.
|
|
247
|
+
|
|
248
|
+
### Nested blocks (`<SettoBlock>`)
|
|
249
|
+
|
|
250
|
+
For cards or panels inside a section, wrap each in `<SettoBlock>` with its own theme key:
|
|
251
|
+
|
|
252
|
+
```tsx
|
|
253
|
+
import { SettoSection, SettoBlock, useSectionTheme } from '@setto/client';
|
|
254
|
+
|
|
255
|
+
function InnovationSection() {
|
|
256
|
+
const card = useSectionTheme('innovationCard');
|
|
257
|
+
|
|
258
|
+
return (
|
|
259
|
+
<SettoSection sectionId="innovation" className="py-20">
|
|
260
|
+
<SettoBlock blockId="innovationCard" className="p-8">
|
|
261
|
+
<span style={{ color: card.label }}><T k="innovation.label" /></span>
|
|
262
|
+
<h3 style={{ color: card.heading }}><T k="innovation.headline" /></h3>
|
|
263
|
+
</SettoBlock>
|
|
264
|
+
</SettoSection>
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
Click a block's background to edit **that block's** colours. Click the section padding to edit the **section** background. Add matching keys to `sections.json` and `sectionSchemas` for each block.
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
273
|
+
## Server setup (setto-server)
|
|
274
|
+
|
|
275
|
+
Each site row in Supabase needs:
|
|
276
|
+
|
|
277
|
+
| Column | Example |
|
|
278
|
+
|--------|---------|
|
|
279
|
+
| `id` | `carryon-no` |
|
|
280
|
+
| `repo_owner` / `repo_name` / `branch` | GitHub target |
|
|
281
|
+
| `content_paths` | Whitelist of publishable file paths |
|
|
282
|
+
|
|
283
|
+
Example `content_paths`:
|
|
284
|
+
|
|
285
|
+
```
|
|
286
|
+
src/i18n/locales/no.json
|
|
287
|
+
src/i18n/locales/en.json
|
|
288
|
+
src/theme/sections.json
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
Only paths in this list can be committed. Add new content files here before publishing them.
|
|
292
|
+
|
|
293
|
+
Register allowed origins for CORS (`allowed_origins`).
|
|
294
|
+
|
|
295
|
+
---
|
|
296
|
+
|
|
297
|
+
## Publish flow
|
|
298
|
+
|
|
299
|
+
1. Editor clicks **Publiser** in the top toolbar.
|
|
300
|
+
2. Client serialises changed locale bundles + `sections.json` (if theme drafts exist).
|
|
301
|
+
3. `POST /sites/:siteId/publish` with `{ files: [{ path, content }] }`.
|
|
302
|
+
4. setto-server validates paths against `content_paths`, commits to GitHub.
|
|
303
|
+
5. A `deployments` row is inserted; the toolbar shows build status via Supabase Realtime + Vercel webhook.
|
|
304
|
+
|
|
305
|
+
Drafts are cleared after a successful publish.
|
|
306
|
+
|
|
307
|
+
---
|
|
308
|
+
|
|
309
|
+
## Admin app (`SettoAdminApp`)
|
|
310
|
+
|
|
311
|
+
Route: `/admin/*`
|
|
312
|
+
|
|
313
|
+
Provides Supabase email/password login and a dashboard with a link to start editing (`/?setto=edit`). Does not render the inline editor itself.
|
|
314
|
+
|
|
315
|
+
---
|
|
316
|
+
|
|
317
|
+
## Local development (Sevenfold monorepo)
|
|
318
|
+
|
|
319
|
+
When consumed by `carryon.no`, Vite aliases `@setto/client` to `src/index.ts` so changes hot-reload without a separate watch build.
|
|
320
|
+
|
|
321
|
+
Typical stack:
|
|
322
|
+
|
|
323
|
+
```bash
|
|
324
|
+
# Terminal 1 — Supabase
|
|
325
|
+
cd setto-server && supabase start
|
|
326
|
+
|
|
327
|
+
# Terminal 2 — API
|
|
328
|
+
cd setto-server && bun run dev # :3001
|
|
329
|
+
|
|
330
|
+
# Terminal 3 — Site
|
|
331
|
+
cd carryon.no && bun run dev # :3000
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
---
|
|
335
|
+
|
|
336
|
+
## Build (library)
|
|
337
|
+
|
|
338
|
+
```bash
|
|
339
|
+
bun install
|
|
340
|
+
bun run build
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
Produces `dist/setto-client.js` and `.d.ts` via Vite library mode. Only needed before publishing to npm.
|
|
344
|
+
|
|
345
|
+
---
|
|
346
|
+
|
|
347
|
+
## API surface
|
|
348
|
+
|
|
349
|
+
| Export | Purpose |
|
|
350
|
+
|--------|---------|
|
|
351
|
+
| `SettoProvider` / `useSetto` | Context, auth, stores, edit mode flag |
|
|
352
|
+
| `T` | Inline-editable translation |
|
|
353
|
+
| `SettoSection` | Section wrapper + edit selection |
|
|
354
|
+
| `SettoBlock` | Nested card/panel with its own colour toolbar |
|
|
355
|
+
| `useSectionTheme` | Read section colour tokens |
|
|
356
|
+
| `SettoAdminApp` | `/admin` login + dashboard |
|
|
357
|
+
| `AuthGate` | Standalone login wrapper |
|
|
358
|
+
| `BrandColor`, `SectionSchema`, `SettoConfig` | Types for host config |
|
|
359
|
+
|
|
360
|
+
---
|
|
361
|
+
|
|
362
|
+
## Limitations (v0)
|
|
363
|
+
|
|
364
|
+
- `<T>` supports text content only — not HTML attributes (`placeholder`, `alt`, `aria-label`).
|
|
365
|
+
- No list/repeater UI (cannot add a new FAQ row from the editor).
|
|
366
|
+
- Drafts are in-memory — refresh discards unpublished changes.
|
|
367
|
+
- Section colour toolbar only offers `brandColors` — no custom hex/rgba input.
|
|
368
|
+
- CTA card colours and nested component colours are not section-themeable yet.
|
|
369
|
+
- Single editor per site (no concurrent-edit conflict handling).
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type ComponentPropsWithoutRef } from 'react';
|
|
2
|
+
export interface SettoBlockProps extends ComponentPropsWithoutRef<'div'> {
|
|
3
|
+
/** Theme key in sections.json, e.g. 'innovationCard'. */
|
|
4
|
+
blockId: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Themeable block (card, panel) inside a section. Click the block chrome to
|
|
8
|
+
* edit its colours; click text to edit copy.
|
|
9
|
+
*/
|
|
10
|
+
export declare const SettoBlock: import("react").ForwardRefExoticComponent<SettoBlockProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type ComponentPropsWithoutRef } from 'react';
|
|
2
|
+
export interface SettoSectionProps extends ComponentPropsWithoutRef<'section'> {
|
|
3
|
+
/** Theme key, e.g. 'values'. */
|
|
4
|
+
sectionId: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Wraps a page section with theme-driven background colour and edit-mode
|
|
8
|
+
* selection. Click the section chrome (not editable text or nested blocks)
|
|
9
|
+
* to open the colour toolbar.
|
|
10
|
+
*/
|
|
11
|
+
export declare const SettoSection: import("react").ForwardRefExoticComponent<SettoSectionProps & import("react").RefAttributes<HTMLElement>>;
|
package/dist/T.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface TProps {
|
|
2
|
+
/** The dotted translation key, e.g. 'hero.headline'. */
|
|
3
|
+
k: string;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Renders a translation key. In edit mode the text is contentEditable inline;
|
|
7
|
+
* changes flow into the draft store for the active language.
|
|
8
|
+
*/
|
|
9
|
+
export declare function T({ k }: TProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Drop-in admin SPA. Mount under a route like `<Route path="/admin/*" .../>`.
|
|
3
|
+
*
|
|
4
|
+
* Behaviour after login:
|
|
5
|
+
* - Lists sites the user is a member of (read via Supabase RLS).
|
|
6
|
+
* - Single-site sites: redirect to `/?setto=edit` automatically.
|
|
7
|
+
* - Multi-site: show a picker.
|
|
8
|
+
*
|
|
9
|
+
* While the user is on `/admin`, this component shows the site dashboard.
|
|
10
|
+
* Editing happens on the public site at `/?setto=edit` via "Begynn å redigere".
|
|
11
|
+
*/
|
|
12
|
+
export declare function SettoAdminApp(): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
export interface AuthGateProps {
|
|
3
|
+
/** Rendered after a session is established. */
|
|
4
|
+
children: ReactNode;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Wraps `children` with a Supabase email/password login screen. Returns the
|
|
8
|
+
* children only when a session exists.
|
|
9
|
+
*/
|
|
10
|
+
export declare function AuthGate({ children }: AuthGateProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { SupabaseClient } from '@supabase/supabase-js';
|
|
2
|
+
import type { DeploymentRow } from '../types';
|
|
3
|
+
interface BuildStatusProps {
|
|
4
|
+
supabase: SupabaseClient;
|
|
5
|
+
deploymentId: string;
|
|
6
|
+
onDone?: (status: DeploymentRow['status']) => void;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Subscribes to a single row in `deployments` via Supabase Realtime.
|
|
10
|
+
* Renders the three-step progress and an error state on failure.
|
|
11
|
+
*/
|
|
12
|
+
export declare function BuildStatus({ supabase, deploymentId, onDone }: BuildStatusProps): import("react/jsx-runtime").JSX.Element;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import type { ThemeStore } from '../lib/theme-store';
|
|
3
|
+
interface EditModeShellProps {
|
|
4
|
+
children: ReactNode;
|
|
5
|
+
themeStore: ThemeStore | null;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Edit mode: full-width page with a fixed toolbar at the top. Text is edited
|
|
9
|
+
* inline via contentEditable on `<T>` — no sidebar.
|
|
10
|
+
*/
|
|
11
|
+
export declare function EditModeShell({ children, themeStore }: EditModeShellProps): import("react/jsx-runtime").JSX.Element;
|
|
12
|
+
/** @deprecated Use EditModeShell */
|
|
13
|
+
export declare const EditModeMount: typeof EditModeShell;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function guessLangFromPath(path: string, languages: string[]): string | null;
|
|
2
|
+
export declare function pathForLang(_siteId: string, lng: string, _languages: string[]): string;
|
|
3
|
+
export declare function pathForTheme(_siteId: string, themePath?: string): string;
|
|
4
|
+
export declare function isThemePath(path: string, themePath?: string): boolean;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function SectionToolbar(): import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function EditToolbar(): import("react/jsx-runtime").JSX.Element;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { SettoProvider, useSetto } from './provider';
|
|
2
|
+
export { T } from './T';
|
|
3
|
+
export { SettoSection } from './SettoSection';
|
|
4
|
+
export { SettoBlock } from './SettoBlock';
|
|
5
|
+
export { useSectionTheme } from './use-section-theme';
|
|
6
|
+
export { SettoAdminApp } from './admin/App';
|
|
7
|
+
export { AuthGate } from './edit-mode/auth-gate';
|
|
8
|
+
export type { SettoConfig, BrandColor, DeploymentRow, SiteRow, ContentFile, PublishResult, } from './types';
|
|
9
|
+
export type { SectionSchema, SectionColorField } from './section-schema';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { SupabaseClient } from '@supabase/supabase-js';
|
|
2
|
+
import type { ContentFile, PublishResult } from '../types';
|
|
3
|
+
export declare function createApi(args: {
|
|
4
|
+
apiUrl: string;
|
|
5
|
+
supabase: SupabaseClient;
|
|
6
|
+
}): {
|
|
7
|
+
getContent(siteId: string): Promise<{
|
|
8
|
+
files: ContentFile[];
|
|
9
|
+
}>;
|
|
10
|
+
publish(siteId: string, files: Array<{
|
|
11
|
+
path: string;
|
|
12
|
+
content: string;
|
|
13
|
+
}>, message?: string): Promise<PublishResult>;
|
|
14
|
+
};
|
|
15
|
+
export type SettoApi = ReturnType<typeof createApi>;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Returns true when two CSS colour strings represent the same sRGB value. */
|
|
2
|
+
export declare function colorsMatch(a: string, b: string): boolean;
|
|
3
|
+
/** Pick a visible border colour for a swatch on white UI chrome. */
|
|
4
|
+
export declare function swatchBorder(color: string): string;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { i18n as I18nType } from 'i18next';
|
|
2
|
+
/**
|
|
3
|
+
* Tracks an in-memory layer of edits on top of a loaded i18next instance.
|
|
4
|
+
*
|
|
5
|
+
* On disk, a site has one JSON file per language under `content_paths`. At
|
|
6
|
+
* runtime, i18next holds those JSONs as resource bundles in
|
|
7
|
+
* `i18n.store.data[lng].translation`.
|
|
8
|
+
*
|
|
9
|
+
* The draft store mutates the resource bundles directly so the page re-renders
|
|
10
|
+
* with the editor's text immediately. When the user publishes, we serialise
|
|
11
|
+
* the merged resource bundles back to JSON and POST them to setto-server.
|
|
12
|
+
*/
|
|
13
|
+
export interface DraftEntry {
|
|
14
|
+
/** Dotted key path, e.g. 'hero.headline'. */
|
|
15
|
+
key: string;
|
|
16
|
+
/** Language code, e.g. 'no'. */
|
|
17
|
+
lng: string;
|
|
18
|
+
/** Value before any edits in this session — used for diff and revert. */
|
|
19
|
+
original: string;
|
|
20
|
+
/** Current draft value. */
|
|
21
|
+
current: string;
|
|
22
|
+
}
|
|
23
|
+
export interface I18nStoreSnapshot {
|
|
24
|
+
/** All edits made in this session, keyed by `${lng}::${key}`. */
|
|
25
|
+
drafts: Map<string, DraftEntry>;
|
|
26
|
+
/** Bumps every time the store changes; React components subscribe to this. */
|
|
27
|
+
version: number;
|
|
28
|
+
}
|
|
29
|
+
/** Stable empty snapshot for useSyncExternalStore when the store is not ready. */
|
|
30
|
+
export declare const EMPTY_I18N_STORE_SNAPSHOT: I18nStoreSnapshot;
|
|
31
|
+
type Listener = (snap: I18nStoreSnapshot) => void;
|
|
32
|
+
export declare class I18nStore {
|
|
33
|
+
private drafts;
|
|
34
|
+
private version;
|
|
35
|
+
private listeners;
|
|
36
|
+
/** Stable namespace used by react-i18next defaults. */
|
|
37
|
+
private ns;
|
|
38
|
+
private i18n;
|
|
39
|
+
/** Cached reference — useSyncExternalStore requires referential stability. */
|
|
40
|
+
private cachedSnapshot;
|
|
41
|
+
constructor(i18n: I18nType, options?: {
|
|
42
|
+
namespace?: string;
|
|
43
|
+
});
|
|
44
|
+
snapshot(): I18nStoreSnapshot;
|
|
45
|
+
subscribe(listener: Listener): () => void;
|
|
46
|
+
/**
|
|
47
|
+
* Returns the current rendered value for a key in a given language,
|
|
48
|
+
* reading through any draft layer.
|
|
49
|
+
*/
|
|
50
|
+
get(key: string, lng: string): string;
|
|
51
|
+
/** Returns the original (pre-edit) value for a key. */
|
|
52
|
+
original(key: string, lng: string): string;
|
|
53
|
+
/**
|
|
54
|
+
* Records an edit. Mutates the live i18next bundle so consumers re-render.
|
|
55
|
+
* If the value is set back to the original, the draft entry is cleared.
|
|
56
|
+
*/
|
|
57
|
+
set(key: string, lng: string, value: string): void;
|
|
58
|
+
/** Resets a single draft back to its original value. */
|
|
59
|
+
revert(key: string, lng: string): void;
|
|
60
|
+
/** Resets every draft. */
|
|
61
|
+
revertAll(): void;
|
|
62
|
+
/** Marks the current state as the new baseline (after a successful publish). */
|
|
63
|
+
commit(): void;
|
|
64
|
+
/** Number of pending draft entries across all languages. */
|
|
65
|
+
size(): number;
|
|
66
|
+
/**
|
|
67
|
+
* Serialises the full resource bundle per language as it currently looks in
|
|
68
|
+
* i18next (with drafts applied). Used as the payload for /publish.
|
|
69
|
+
*
|
|
70
|
+
* The caller maps language → file path via `sites.content_paths`.
|
|
71
|
+
*/
|
|
72
|
+
serialiseBundles(): Record<string, unknown>;
|
|
73
|
+
private bump;
|
|
74
|
+
}
|
|
75
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type SupabaseClient } from '@supabase/supabase-js';
|
|
2
|
+
/**
|
|
3
|
+
* Returns a Supabase client for a given (url, anonKey) pair.
|
|
4
|
+
* Memoised so we never create more than one client per provider mount.
|
|
5
|
+
*/
|
|
6
|
+
export declare function getSupabase(config: {
|
|
7
|
+
url: string;
|
|
8
|
+
anonKey: string;
|
|
9
|
+
}): SupabaseClient;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** Section colour theme — keyed by section id, then colour token. */
|
|
2
|
+
export type SectionsTheme = Record<string, Record<string, string>>;
|
|
3
|
+
export interface ThemeDraftEntry {
|
|
4
|
+
sectionId: string;
|
|
5
|
+
field: string;
|
|
6
|
+
original: string;
|
|
7
|
+
current: string;
|
|
8
|
+
}
|
|
9
|
+
export interface ThemeStoreSnapshot {
|
|
10
|
+
drafts: Map<string, ThemeDraftEntry>;
|
|
11
|
+
version: number;
|
|
12
|
+
}
|
|
13
|
+
export declare const EMPTY_THEME_STORE_SNAPSHOT: ThemeStoreSnapshot;
|
|
14
|
+
type Listener = (snap: ThemeStoreSnapshot) => void;
|
|
15
|
+
export declare class ThemeStore {
|
|
16
|
+
private data;
|
|
17
|
+
private baseline;
|
|
18
|
+
private drafts;
|
|
19
|
+
private version;
|
|
20
|
+
private listeners;
|
|
21
|
+
private cachedSnapshot;
|
|
22
|
+
constructor(initial: SectionsTheme);
|
|
23
|
+
snapshot(): ThemeStoreSnapshot;
|
|
24
|
+
subscribe(listener: Listener): () => void;
|
|
25
|
+
getSection(sectionId: string): Record<string, string>;
|
|
26
|
+
get(sectionId: string, field: string): string;
|
|
27
|
+
set(sectionId: string, field: string, value: string): void;
|
|
28
|
+
/** Replace baseline from GitHub without creating drafts. */
|
|
29
|
+
loadBaseline(next: SectionsTheme): void;
|
|
30
|
+
commit(): void;
|
|
31
|
+
size(): number;
|
|
32
|
+
serialise(): SectionsTheme;
|
|
33
|
+
private bump;
|
|
34
|
+
}
|
|
35
|
+
export {};
|