@prenta/admin 2.4.0 → 2.6.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/CHANGELOG.md +13 -0
- package/dist/AdminRoot.d.ts.map +1 -1
- package/dist/AdminRoot.js +4 -0
- package/dist/AdminRoot.js.map +1 -1
- package/dist/__tests__/views/appearance-settings.render.test.js +3 -0
- package/dist/__tests__/views/appearance-settings.render.test.js.map +1 -1
- package/dist/__tests__/views/mcp-connect.render.test.d.ts +2 -0
- package/dist/__tests__/views/mcp-connect.render.test.d.ts.map +1 -0
- package/dist/__tests__/views/mcp-connect.render.test.js +55 -0
- package/dist/__tests__/views/mcp-connect.render.test.js.map +1 -0
- package/dist/__tests__/views/mcp-connect.test.d.ts +2 -0
- package/dist/__tests__/views/mcp-connect.test.d.ts.map +1 -0
- package/dist/__tests__/views/mcp-connect.test.js +37 -0
- package/dist/__tests__/views/mcp-connect.test.js.map +1 -0
- package/dist/lib/appearance-api.d.ts +8 -0
- package/dist/lib/appearance-api.d.ts.map +1 -1
- package/dist/lib/appearance-api.js.map +1 -1
- package/dist/prenta-admin.css +1 -1
- package/dist/router/admin-routes.d.ts.map +1 -1
- package/dist/router/admin-routes.js +1 -0
- package/dist/router/admin-routes.js.map +1 -1
- package/dist/views/ApiKeys.d.ts +1 -1
- package/dist/views/ApiKeys.d.ts.map +1 -1
- package/dist/views/ApiKeys.js +2 -2
- package/dist/views/ApiKeys.js.map +1 -1
- package/dist/views/McpConsent.d.ts +4 -0
- package/dist/views/McpConsent.d.ts.map +1 -0
- package/dist/views/McpConsent.js +48 -0
- package/dist/views/McpConsent.js.map +1 -0
- package/dist/views/Settings.d.ts.map +1 -1
- package/dist/views/Settings.js +5 -3
- package/dist/views/Settings.js.map +1 -1
- package/dist/views/page-editor/EditorCanvas.d.ts.map +1 -1
- package/dist/views/page-editor/EditorCanvas.js +2 -1
- package/dist/views/page-editor/EditorCanvas.js.map +1 -1
- package/dist/views/page-editor/SectionThemeRoot.d.ts +10 -0
- package/dist/views/page-editor/SectionThemeRoot.d.ts.map +1 -0
- package/dist/views/page-editor/SectionThemeRoot.js +27 -0
- package/dist/views/page-editor/SectionThemeRoot.js.map +1 -0
- package/dist/views/page-editor/sections/AuthorBioSection.js +1 -1
- package/dist/views/page-editor/sections/AuthorBioSection.js.map +1 -1
- package/dist/views/page-editor/sections/ByTheNumbersSection.js +1 -1
- package/dist/views/page-editor/sections/ByTheNumbersSection.js.map +1 -1
- package/dist/views/page-editor/sections/CtaBannerSection.js +1 -1
- package/dist/views/page-editor/sections/CtaBannerSection.js.map +1 -1
- package/dist/views/page-editor/sections/FeatureHighlightSection.js +2 -2
- package/dist/views/page-editor/sections/FeatureHighlightSection.js.map +1 -1
- package/dist/views/page-editor/sections/HeroBannerSection.js +1 -1
- package/dist/views/page-editor/sections/HeroBannerSection.js.map +1 -1
- package/dist/views/page-editor/sections/parts.d.ts.map +1 -1
- package/dist/views/page-editor/sections/parts.js +11 -15
- package/dist/views/page-editor/sections/parts.js.map +1 -1
- package/dist/views/post-editor/PostEditorCanvas.d.ts.map +1 -1
- package/dist/views/post-editor/PostEditorCanvas.js +3 -2
- package/dist/views/post-editor/PostEditorCanvas.js.map +1 -1
- package/dist/views/settings/AppearanceTab.d.ts.map +1 -1
- package/dist/views/settings/AppearanceTab.js +2 -1
- package/dist/views/settings/AppearanceTab.js.map +1 -1
- package/dist/views/settings/McpConnectTab.d.ts +9 -0
- package/dist/views/settings/McpConnectTab.d.ts.map +1 -0
- package/dist/views/settings/McpConnectTab.js +71 -0
- package/dist/views/settings/McpConnectTab.js.map +1 -0
- package/dist/views/settings/PhiSafeguardsCard.d.ts +9 -0
- package/dist/views/settings/PhiSafeguardsCard.d.ts.map +1 -0
- package/dist/views/settings/PhiSafeguardsCard.js +70 -0
- package/dist/views/settings/PhiSafeguardsCard.js.map +1 -0
- package/dist/views/settings/SecurityTab.d.ts.map +1 -1
- package/dist/views/settings/SecurityTab.js +2 -1
- package/dist/views/settings/SecurityTab.js.map +1 -1
- package/dist/views/settings/SiteThemeCard.d.ts +7 -0
- package/dist/views/settings/SiteThemeCard.d.ts.map +1 -0
- package/dist/views/settings/SiteThemeCard.js +23 -0
- package/dist/views/settings/SiteThemeCard.js.map +1 -0
- package/dist/views/settings/mcp-connect.d.ts +25 -0
- package/dist/views/settings/mcp-connect.d.ts.map +1 -0
- package/dist/views/settings/mcp-connect.js +166 -0
- package/dist/views/settings/mcp-connect.js.map +1 -0
- package/dist/views/settings/useAppearanceSettings.d.ts +3 -1
- package/dist/views/settings/useAppearanceSettings.d.ts.map +1 -1
- package/dist/views/settings/useAppearanceSettings.js +27 -1
- package/dist/views/settings/useAppearanceSettings.js.map +1 -1
- package/package.json +4 -4
- package/src/AdminRoot.tsx +5 -0
- package/src/__tests__/views/appearance-settings.render.test.tsx +8 -0
- package/src/__tests__/views/mcp-connect.render.test.tsx +64 -0
- package/src/__tests__/views/mcp-connect.test.ts +42 -0
- package/src/lib/appearance-api.ts +8 -0
- package/src/router/admin-routes.ts +1 -0
- package/src/views/ApiKeys.tsx +5 -4
- package/src/views/McpConsent.tsx +101 -0
- package/src/views/Settings.tsx +13 -0
- package/src/views/page-editor/EditorCanvas.tsx +2 -1
- package/src/views/page-editor/SectionThemeRoot.tsx +33 -0
- package/src/views/page-editor/sections/AuthorBioSection.tsx +2 -2
- package/src/views/page-editor/sections/ByTheNumbersSection.tsx +1 -1
- package/src/views/page-editor/sections/CtaBannerSection.tsx +1 -1
- package/src/views/page-editor/sections/FeatureHighlightSection.tsx +3 -3
- package/src/views/page-editor/sections/HeroBannerSection.tsx +2 -2
- package/src/views/page-editor/sections/parts.tsx +12 -15
- package/src/views/post-editor/PostEditorCanvas.tsx +21 -18
- package/src/views/settings/AppearanceTab.tsx +2 -0
- package/src/views/settings/McpConnectTab.tsx +212 -0
- package/src/views/settings/PhiSafeguardsCard.tsx +85 -0
- package/src/views/settings/SecurityTab.tsx +3 -0
- package/src/views/settings/SiteThemeCard.tsx +71 -0
- package/src/views/settings/mcp-connect.ts +209 -0
- package/src/views/settings/useAppearanceSettings.ts +39 -0
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Connection text for Settings → MCP. Each client gets one thing to copy.
|
|
3
|
+
* OAuth clients receive the remote URL. Editors that cannot sign in receive
|
|
4
|
+
* a stdio config with a placeholder key, never a real secret.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export type McpClientId =
|
|
8
|
+
| 'claude-code'
|
|
9
|
+
| 'claude'
|
|
10
|
+
| 'chatgpt'
|
|
11
|
+
| 'grok'
|
|
12
|
+
| 'cursor'
|
|
13
|
+
| 'vscode'
|
|
14
|
+
| 'other'
|
|
15
|
+
|
|
16
|
+
export interface McpClientGuide {
|
|
17
|
+
id: McpClientId
|
|
18
|
+
label: string
|
|
19
|
+
title: string
|
|
20
|
+
/** One sentence under the title. */
|
|
21
|
+
summary: string
|
|
22
|
+
/** Primary button. */
|
|
23
|
+
action: string
|
|
24
|
+
/** Exact text the primary button and the panel copy control place on the clipboard. */
|
|
25
|
+
copyText: string
|
|
26
|
+
/** Shown in the panel. Equals copyText except where the panel adds a label. */
|
|
27
|
+
panel: string
|
|
28
|
+
manual: string[]
|
|
29
|
+
/** This client needs a Content API key created on the API Keys tab. */
|
|
30
|
+
needsApiKey: boolean
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function mcpServerName(mcpUrl: string): string {
|
|
34
|
+
try {
|
|
35
|
+
const host = new URL(mcpUrl).hostname.replace(/^www\./, '')
|
|
36
|
+
const label = (host.split('.')[0] || 'site').toLowerCase().replace(/[^a-z0-9]+/g, '-')
|
|
37
|
+
return `${label.replace(/^-|-$/g, '') || 'site'}-prenta`
|
|
38
|
+
} catch {
|
|
39
|
+
return 'site-prenta'
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function mcpSiteOrigin(mcpUrl: string): string {
|
|
44
|
+
try {
|
|
45
|
+
return new URL(mcpUrl).origin
|
|
46
|
+
} catch {
|
|
47
|
+
return mcpUrl.replace(/\/mcp\/?$/, '')
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function mcpClientGuides(mcpUrl: string): McpClientGuide[] {
|
|
52
|
+
const name = mcpServerName(mcpUrl)
|
|
53
|
+
const origin = mcpSiteOrigin(mcpUrl)
|
|
54
|
+
const claudeCodePrompt = [
|
|
55
|
+
'Add this Prenta site as a remote MCP server.',
|
|
56
|
+
'',
|
|
57
|
+
'Connection details:',
|
|
58
|
+
`- Server URL: ${mcpUrl}`,
|
|
59
|
+
'- Transport: HTTP',
|
|
60
|
+
`- Server name: ${name}`,
|
|
61
|
+
'',
|
|
62
|
+
'Do not store an API key. This site signs in with OAuth. After the server is added, start the sign-in flow so I can approve it in the browser.',
|
|
63
|
+
'',
|
|
64
|
+
`Command: claude mcp add --transport http ${name} ${mcpUrl}`,
|
|
65
|
+
].join('\n')
|
|
66
|
+
|
|
67
|
+
const cursorConfig = JSON.stringify(
|
|
68
|
+
{
|
|
69
|
+
mcpServers: {
|
|
70
|
+
[name]: {
|
|
71
|
+
command: 'npx',
|
|
72
|
+
args: ['-y', '@prenta/mcp-server'],
|
|
73
|
+
env: {
|
|
74
|
+
PRENTA_BASE_URL: origin,
|
|
75
|
+
PRENTA_API_KEY: 'act_sk_...',
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
null,
|
|
81
|
+
2,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
const vscodeConfig = JSON.stringify(
|
|
85
|
+
{
|
|
86
|
+
servers: {
|
|
87
|
+
[name]: {
|
|
88
|
+
type: 'stdio',
|
|
89
|
+
command: 'npx',
|
|
90
|
+
args: ['-y', '@prenta/mcp-server'],
|
|
91
|
+
env: {
|
|
92
|
+
PRENTA_BASE_URL: origin,
|
|
93
|
+
PRENTA_API_KEY: 'act_sk_...',
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
null,
|
|
99
|
+
2,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
const bearer = 'Authorization: Bearer act_sk_...'
|
|
103
|
+
|
|
104
|
+
return [
|
|
105
|
+
{
|
|
106
|
+
id: 'claude-code',
|
|
107
|
+
label: 'Claude Code',
|
|
108
|
+
title: 'Connect this site to Claude Code',
|
|
109
|
+
summary:
|
|
110
|
+
'Paste this prompt into Claude Code. It adds the server, then this site asks you to approve access.',
|
|
111
|
+
action: 'Copy prompt',
|
|
112
|
+
copyText: claudeCodePrompt,
|
|
113
|
+
panel: claudeCodePrompt,
|
|
114
|
+
manual: [
|
|
115
|
+
`Run: claude mcp add --transport http ${name} ${mcpUrl}`,
|
|
116
|
+
'Approve the sign-in page this site opens. Do not paste an API key into the command.',
|
|
117
|
+
],
|
|
118
|
+
needsApiKey: false,
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
id: 'claude',
|
|
122
|
+
label: 'Claude',
|
|
123
|
+
title: 'Connect this site to Claude',
|
|
124
|
+
summary: 'Claude on the web, Desktop, and Cowork use this URL and sign in here.',
|
|
125
|
+
action: 'Copy URL',
|
|
126
|
+
copyText: mcpUrl,
|
|
127
|
+
panel: mcpUrl,
|
|
128
|
+
manual: [
|
|
129
|
+
'Open Claude → Settings → Connectors → Add custom connector.',
|
|
130
|
+
'Paste the URL and continue. This site opens so you can allow access.',
|
|
131
|
+
],
|
|
132
|
+
needsApiKey: false,
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
id: 'chatgpt',
|
|
136
|
+
label: 'ChatGPT',
|
|
137
|
+
title: 'Connect this site to ChatGPT',
|
|
138
|
+
summary: 'ChatGPT signs in here. It cannot use an API key.',
|
|
139
|
+
action: 'Copy URL',
|
|
140
|
+
copyText: mcpUrl,
|
|
141
|
+
panel: mcpUrl,
|
|
142
|
+
manual: [
|
|
143
|
+
'Open ChatGPT → Settings → Connectors and add a custom connector.',
|
|
144
|
+
'Paste the URL. ChatGPT sends you back here to approve access.',
|
|
145
|
+
],
|
|
146
|
+
needsApiKey: false,
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
id: 'grok',
|
|
150
|
+
label: 'Grok',
|
|
151
|
+
title: 'Connect this site to Grok',
|
|
152
|
+
summary:
|
|
153
|
+
'Grok on the web signs in here. The Grok CLI and the xAI API send a Content API key instead.',
|
|
154
|
+
action: 'Copy URL',
|
|
155
|
+
copyText: mcpUrl,
|
|
156
|
+
panel: mcpUrl,
|
|
157
|
+
manual: [
|
|
158
|
+
'On grok.com, add a connector and paste the URL, then approve access on this site.',
|
|
159
|
+
`For the Grok CLI or the xAI API, create a Content API key and send ${bearer}. Include the word Bearer.`,
|
|
160
|
+
],
|
|
161
|
+
needsApiKey: true,
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
id: 'cursor',
|
|
165
|
+
label: 'Cursor',
|
|
166
|
+
title: 'Connect this site to Cursor',
|
|
167
|
+
summary:
|
|
168
|
+
'Cursor runs the local server. Create a Content API key and replace the placeholder.',
|
|
169
|
+
action: 'Copy config',
|
|
170
|
+
copyText: cursorConfig,
|
|
171
|
+
panel: cursorConfig,
|
|
172
|
+
manual: [
|
|
173
|
+
'Open Cursor → Settings → MCP and add a new server, or paste this into .cursor/mcp.json.',
|
|
174
|
+
'Create a Content API key and replace act_sk_... . Do not commit the file.',
|
|
175
|
+
],
|
|
176
|
+
needsApiKey: true,
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
id: 'vscode',
|
|
180
|
+
label: 'VS Code',
|
|
181
|
+
title: 'Connect this site to VS Code',
|
|
182
|
+
summary:
|
|
183
|
+
'Save this as .vscode/mcp.json. Create a Content API key and replace the placeholder.',
|
|
184
|
+
action: 'Copy config',
|
|
185
|
+
copyText: vscodeConfig,
|
|
186
|
+
panel: vscodeConfig,
|
|
187
|
+
manual: [
|
|
188
|
+
'Save the config as .vscode/mcp.json.',
|
|
189
|
+
'Create a Content API key and replace act_sk_... . Do not commit the file.',
|
|
190
|
+
],
|
|
191
|
+
needsApiKey: true,
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
id: 'other',
|
|
195
|
+
label: 'Other',
|
|
196
|
+
title: 'Connect another client',
|
|
197
|
+
summary:
|
|
198
|
+
'Clients that can sign in use the URL. Clients that can set a header use a Content API key.',
|
|
199
|
+
action: 'Copy URL',
|
|
200
|
+
copyText: mcpUrl,
|
|
201
|
+
panel: mcpUrl,
|
|
202
|
+
manual: [
|
|
203
|
+
'Point the client at the server URL.',
|
|
204
|
+
'If it signs in, approve access on this site. If it sends a header, use a Content API key as Authorization: Bearer act_sk_... . Do not put the key in the query string.',
|
|
205
|
+
],
|
|
206
|
+
needsApiKey: true,
|
|
207
|
+
},
|
|
208
|
+
]
|
|
209
|
+
}
|
|
@@ -5,6 +5,8 @@ import {
|
|
|
5
5
|
logoShapeWarning,
|
|
6
6
|
resolveAdminBrand,
|
|
7
7
|
resolvePublicBrand,
|
|
8
|
+
parsePublicTheme,
|
|
9
|
+
publicThemeFieldError,
|
|
8
10
|
validateAccentColor,
|
|
9
11
|
type AdminBrandingMode,
|
|
10
12
|
type BrandAssetLike,
|
|
@@ -13,6 +15,7 @@ import {
|
|
|
13
15
|
type ColorMode,
|
|
14
16
|
type LogoShape,
|
|
15
17
|
type ResolvedAdminBrand,
|
|
18
|
+
type PublicTheme,
|
|
16
19
|
type ResolvedPublicBrand,
|
|
17
20
|
type SidebarDensity,
|
|
18
21
|
} from '@prenta/core/appearance'
|
|
@@ -39,6 +42,7 @@ interface AppearanceForm {
|
|
|
39
42
|
density: SidebarDensity
|
|
40
43
|
font: string
|
|
41
44
|
reduceMotion: boolean
|
|
45
|
+
publicTheme: Required<PublicTheme>
|
|
42
46
|
}
|
|
43
47
|
|
|
44
48
|
/** Brand asset references + admin branding mode (the `brand` settings blob). */
|
|
@@ -136,6 +140,7 @@ export interface UseAppearanceSettings {
|
|
|
136
140
|
setDensity: (v: SidebarDensity) => void
|
|
137
141
|
setFont: (v: string) => void
|
|
138
142
|
setReduceMotion: (v: boolean) => void
|
|
143
|
+
setPublicTheme: (patch: Partial<PublicTheme>) => void
|
|
139
144
|
setBrandingMode: (v: AdminBrandingMode) => void
|
|
140
145
|
setAltText: (v: string) => void
|
|
141
146
|
uploadAsset: (field: BrandAssetField, file: File) => Promise<void>
|
|
@@ -147,12 +152,22 @@ export interface UseAppearanceSettings {
|
|
|
147
152
|
reload: () => void
|
|
148
153
|
}
|
|
149
154
|
|
|
155
|
+
const EMPTY_PUBLIC_THEME: Required<PublicTheme> = {
|
|
156
|
+
accent: '',
|
|
157
|
+
background: '',
|
|
158
|
+
foreground: '',
|
|
159
|
+
muted: '',
|
|
160
|
+
fontFamily: '',
|
|
161
|
+
radius: '',
|
|
162
|
+
}
|
|
163
|
+
|
|
150
164
|
const DEFAULT_FORM: AppearanceForm = {
|
|
151
165
|
colorMode: 'system',
|
|
152
166
|
accent: 'purple',
|
|
153
167
|
density: 'default',
|
|
154
168
|
font: 'System UI',
|
|
155
169
|
reduceMotion: false,
|
|
170
|
+
publicTheme: EMPTY_PUBLIC_THEME,
|
|
156
171
|
}
|
|
157
172
|
|
|
158
173
|
const DEFAULT_BRAND_FORM: BrandForm = { mode: 'inheritPublicBrand' }
|
|
@@ -263,12 +278,14 @@ export function useAppearanceSettings(
|
|
|
263
278
|
return
|
|
264
279
|
}
|
|
265
280
|
const d = res.data
|
|
281
|
+
const storedTheme = d.appearance.publicTheme
|
|
266
282
|
const nextAppearance: AppearanceForm = {
|
|
267
283
|
colorMode: d.appearance.adminColorModeDefault,
|
|
268
284
|
accent: d.appearance.adminAccentColor,
|
|
269
285
|
density: d.appearance.sidebarDensity,
|
|
270
286
|
font: d.appearance.contentFont,
|
|
271
287
|
reduceMotion: d.appearance.reduceMotion,
|
|
288
|
+
publicTheme: { ...EMPTY_PUBLIC_THEME, ...storedTheme },
|
|
272
289
|
}
|
|
273
290
|
const nextBrand: BrandForm = { ...d.brand, mode: d.brand?.mode ?? 'inheritPublicBrand' }
|
|
274
291
|
const nextAssets = parseAppearanceAssets(d.assets ?? [])
|
|
@@ -319,6 +336,15 @@ export function useAppearanceSettings(
|
|
|
319
336
|
(v: boolean) => update({ ...appearance, reduceMotion: v }, brand, assets),
|
|
320
337
|
[appearance, brand, assets, update],
|
|
321
338
|
)
|
|
339
|
+
const setPublicTheme = useCallback(
|
|
340
|
+
(patch: Partial<PublicTheme>) =>
|
|
341
|
+
update(
|
|
342
|
+
{ ...appearance, publicTheme: { ...appearance.publicTheme, ...patch } },
|
|
343
|
+
brand,
|
|
344
|
+
assets,
|
|
345
|
+
),
|
|
346
|
+
[appearance, brand, assets, update],
|
|
347
|
+
)
|
|
322
348
|
const setBrandingMode = useCallback(
|
|
323
349
|
(v: AdminBrandingMode) => update(appearance, { ...brand, mode: v }, assets),
|
|
324
350
|
[appearance, brand, assets, update],
|
|
@@ -434,6 +460,17 @@ export function useAppearanceSettings(
|
|
|
434
460
|
}
|
|
435
461
|
// Dark mode default but no dark logo.
|
|
436
462
|
const hasDark = Boolean(brand.publicDarkLogoAssetId || brand.adminDarkLogoAssetId)
|
|
463
|
+
for (const field of [
|
|
464
|
+
'accent',
|
|
465
|
+
'background',
|
|
466
|
+
'foreground',
|
|
467
|
+
'muted',
|
|
468
|
+
'fontFamily',
|
|
469
|
+
'radius',
|
|
470
|
+
] as const) {
|
|
471
|
+
const message = publicThemeFieldError(field, appearance.publicTheme[field])
|
|
472
|
+
if (message) out.push({ field: `publicTheme.${field}`, message, severity: 'error' })
|
|
473
|
+
}
|
|
437
474
|
if (appearance.colorMode === 'dark' && !hasDark) {
|
|
438
475
|
out.push({
|
|
439
476
|
field: 'colorMode',
|
|
@@ -488,6 +525,7 @@ export function useAppearanceSettings(
|
|
|
488
525
|
sidebarDensity: appearance.density,
|
|
489
526
|
contentFont: appearance.font,
|
|
490
527
|
reduceMotion: appearance.reduceMotion,
|
|
528
|
+
publicTheme: parsePublicTheme(appearance.publicTheme),
|
|
491
529
|
updatedAt: now,
|
|
492
530
|
}
|
|
493
531
|
const merged = {
|
|
@@ -536,6 +574,7 @@ export function useAppearanceSettings(
|
|
|
536
574
|
setDensity,
|
|
537
575
|
setFont,
|
|
538
576
|
setReduceMotion,
|
|
577
|
+
setPublicTheme,
|
|
539
578
|
setBrandingMode,
|
|
540
579
|
setAltText,
|
|
541
580
|
uploadAsset,
|