@olonjs/cli 3.0.81 → 3.0.83
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.
|
@@ -596,7 +596,7 @@ cat << 'END_OF_FILE_CONTENT' > "package.json"
|
|
|
596
596
|
"@tiptap/extension-link": "^2.11.5",
|
|
597
597
|
"@tiptap/react": "^2.11.5",
|
|
598
598
|
"@tiptap/starter-kit": "^2.11.5",
|
|
599
|
-
"@olonjs/core": "^1.0.
|
|
599
|
+
"@olonjs/core": "^1.0.71",
|
|
600
600
|
"clsx": "^2.1.1",
|
|
601
601
|
"lucide-react": "^0.474.0",
|
|
602
602
|
"react": "^19.0.0",
|
|
@@ -941,6 +941,7 @@ import themeData from '@/data/config/theme.json';
|
|
|
941
941
|
import menuData from '@/data/config/menu.json';
|
|
942
942
|
import { getFilePages } from '@/lib/getFilePages';
|
|
943
943
|
import { DopaDrawer } from '@/components/save-drawer/DopaDrawer';
|
|
944
|
+
import { Skeleton } from '@/components/ui/skeleton';
|
|
944
945
|
|
|
945
946
|
import tenantCss from './index.css?inline';
|
|
946
947
|
|
|
@@ -1194,6 +1195,16 @@ function cloudFingerprint(apiBase: string, apiKey: string): string {
|
|
|
1194
1195
|
return `${normalizeApiBase(apiBase)}::${apiKey.slice(-8)}`;
|
|
1195
1196
|
}
|
|
1196
1197
|
|
|
1198
|
+
function normalizeSlugForCache(slug: string): string {
|
|
1199
|
+
return (
|
|
1200
|
+
slug
|
|
1201
|
+
.trim()
|
|
1202
|
+
.toLowerCase()
|
|
1203
|
+
.replace(/[^a-z0-9/_-]/g, '-')
|
|
1204
|
+
.replace(/^\/+|\/+$/g, '') || 'home'
|
|
1205
|
+
);
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1197
1208
|
function readCachedCloudContent(fingerprint: string): CachedCloudContent | null {
|
|
1198
1209
|
try {
|
|
1199
1210
|
const raw = localStorage.getItem(CLOUD_CACHE_KEY);
|
|
@@ -1248,7 +1259,6 @@ function App() {
|
|
|
1248
1259
|
const activeCloudSaveController = useRef<AbortController | null>(null);
|
|
1249
1260
|
const contentLoadInFlight = useRef<Promise<void> | null>(null);
|
|
1250
1261
|
const pendingCloudSave = useRef<{ state: ProjectState; slug: string } | null>(null);
|
|
1251
|
-
const warmBootFromCloudCache = useRef(false);
|
|
1252
1262
|
const cloudApiCandidates = useMemo(
|
|
1253
1263
|
() => (isCloudMode && CLOUD_API_URL ? buildApiCandidates(CLOUD_API_URL) : []),
|
|
1254
1264
|
[isCloudMode, CLOUD_API_URL]
|
|
@@ -1288,26 +1298,16 @@ function App() {
|
|
|
1288
1298
|
const primaryApiBase = cloudApiCandidates[0] ?? normalizeApiBase(CLOUD_API_URL);
|
|
1289
1299
|
const fingerprint = cloudFingerprint(primaryApiBase, CLOUD_API_KEY);
|
|
1290
1300
|
const cached = readCachedCloudContent(fingerprint);
|
|
1301
|
+
const cachedPages = cached ? toPagesRecord(cached.pages) : null;
|
|
1302
|
+
const cachedSite = cached ? coerceSiteConfig(cached.siteConfig) : null;
|
|
1303
|
+
const hasCachedFallback = Boolean((cachedPages && Object.keys(cachedPages).length > 0) || cachedSite);
|
|
1291
1304
|
if (cached) {
|
|
1292
|
-
const cachedPages = toPagesRecord(cached.pages);
|
|
1293
|
-
const cachedSite = coerceSiteConfig(cached.siteConfig);
|
|
1294
|
-
if (cachedPages && Object.keys(cachedPages).length > 0) {
|
|
1295
|
-
setPages(cachedPages);
|
|
1296
|
-
}
|
|
1297
|
-
if (cachedSite) {
|
|
1298
|
-
setSiteConfig(cachedSite);
|
|
1299
|
-
}
|
|
1300
|
-
setContentMode('cloud');
|
|
1301
|
-
setContentFallback(null);
|
|
1302
|
-
warmBootFromCloudCache.current = true;
|
|
1303
|
-
setShowTopProgress(false);
|
|
1304
|
-
setHasInitialCloudResolved(true);
|
|
1305
1305
|
logBootstrapEvent('boot.cloud.cache_hit', { ageMs: Date.now() - cached.savedAt });
|
|
1306
|
-
} else {
|
|
1307
|
-
warmBootFromCloudCache.current = false;
|
|
1308
|
-
setShowTopProgress(true);
|
|
1309
|
-
setHasInitialCloudResolved(false);
|
|
1310
1306
|
}
|
|
1307
|
+
setContentMode('cloud');
|
|
1308
|
+
setContentFallback(null);
|
|
1309
|
+
setShowTopProgress(true);
|
|
1310
|
+
setHasInitialCloudResolved(false);
|
|
1311
1311
|
logBootstrapEvent('boot.start', { mode: 'cloud', apiCandidates: cloudApiCandidates.length });
|
|
1312
1312
|
|
|
1313
1313
|
const loadCloudContent = async () => {
|
|
@@ -1320,6 +1320,7 @@ function App() {
|
|
|
1320
1320
|
try {
|
|
1321
1321
|
const res = await fetch(`${apiBase}/content`, {
|
|
1322
1322
|
method: 'GET',
|
|
1323
|
+
cache: 'no-store',
|
|
1323
1324
|
headers: {
|
|
1324
1325
|
Authorization: `Bearer ${CLOUD_API_KEY}`,
|
|
1325
1326
|
},
|
|
@@ -1422,7 +1423,13 @@ function App() {
|
|
|
1422
1423
|
} catch (error: unknown) {
|
|
1423
1424
|
if (controller.signal.aborted) return;
|
|
1424
1425
|
const failure = toCloudLoadFailure(error);
|
|
1425
|
-
if (
|
|
1426
|
+
if (hasCachedFallback) {
|
|
1427
|
+
if (cachedPages && Object.keys(cachedPages).length > 0) {
|
|
1428
|
+
setPages(cachedPages);
|
|
1429
|
+
}
|
|
1430
|
+
if (cachedSite) {
|
|
1431
|
+
setSiteConfig(cachedSite);
|
|
1432
|
+
}
|
|
1426
1433
|
setContentMode('cloud');
|
|
1427
1434
|
setContentFallback({
|
|
1428
1435
|
reasonCode: 'CLOUD_REFRESH_FAILED',
|
|
@@ -1589,14 +1596,26 @@ function App() {
|
|
|
1589
1596
|
},
|
|
1590
1597
|
body: JSON.stringify({
|
|
1591
1598
|
slug,
|
|
1592
|
-
|
|
1593
|
-
|
|
1599
|
+
page: state.page,
|
|
1600
|
+
siteConfig: state.site,
|
|
1594
1601
|
}),
|
|
1595
1602
|
});
|
|
1596
1603
|
const body = (await res.json().catch(() => ({}))) as { error?: string; code?: string };
|
|
1597
1604
|
if (!res.ok) {
|
|
1598
1605
|
throw new Error(body.error || body.code || `Hot save failed: ${res.status}`);
|
|
1599
1606
|
}
|
|
1607
|
+
const keyFingerprint = cloudFingerprint(apiBase, CLOUD_API_KEY);
|
|
1608
|
+
const normalizedSlug = normalizeSlugForCache(slug);
|
|
1609
|
+
const existing = readCachedCloudContent(keyFingerprint);
|
|
1610
|
+
writeCachedCloudContent({
|
|
1611
|
+
keyFingerprint,
|
|
1612
|
+
savedAt: Date.now(),
|
|
1613
|
+
siteConfig: state.site ?? null,
|
|
1614
|
+
pages: {
|
|
1615
|
+
...(existing?.pages ?? {}),
|
|
1616
|
+
[normalizedSlug]: state.page,
|
|
1617
|
+
},
|
|
1618
|
+
});
|
|
1600
1619
|
},
|
|
1601
1620
|
showLegacySave: !isCloudMode,
|
|
1602
1621
|
showHotSave: isCloudMode,
|
|
@@ -1667,6 +1686,26 @@ function App() {
|
|
|
1667
1686
|
</div>
|
|
1668
1687
|
</>
|
|
1669
1688
|
) : null}
|
|
1689
|
+
{isCloudMode && !hasInitialCloudResolved ? (
|
|
1690
|
+
<div className="fixed inset-0 z-[1290] bg-background/80 backdrop-blur-sm">
|
|
1691
|
+
<div className="mx-auto w-full max-w-[1600px] p-6">
|
|
1692
|
+
<div className="grid gap-4 lg:grid-cols-[1fr_420px]">
|
|
1693
|
+
<div className="space-y-4">
|
|
1694
|
+
<Skeleton className="h-10 w-64" />
|
|
1695
|
+
<Skeleton className="h-[220px] w-full rounded-xl" />
|
|
1696
|
+
<Skeleton className="h-[220px] w-full rounded-xl" />
|
|
1697
|
+
</div>
|
|
1698
|
+
<div className="space-y-3 rounded-xl border border-border/50 bg-card/60 p-4">
|
|
1699
|
+
<Skeleton className="h-8 w-32" />
|
|
1700
|
+
<Skeleton className="h-5 w-full" />
|
|
1701
|
+
<Skeleton className="h-5 w-5/6" />
|
|
1702
|
+
<Skeleton className="h-5 w-4/6" />
|
|
1703
|
+
<Skeleton className="h-24 w-full rounded-lg" />
|
|
1704
|
+
</div>
|
|
1705
|
+
</div>
|
|
1706
|
+
</div>
|
|
1707
|
+
</div>
|
|
1708
|
+
) : null}
|
|
1670
1709
|
{shouldRenderEngine ? <JsonPagesEngine config={config} /> : null}
|
|
1671
1710
|
{isCloudMode && (contentMode === 'error' || contentFallback?.reasonCode === 'CLOUD_REFRESH_FAILED') ? (
|
|
1672
1711
|
<div
|
|
@@ -3467,7 +3506,7 @@ echo "Creating src/components/header/View.tsx..."
|
|
|
3467
3506
|
cat << 'END_OF_FILE_CONTENT' > "src/components/header/View.tsx"
|
|
3468
3507
|
import React, { useState, useEffect } from 'react';
|
|
3469
3508
|
import { cn } from '@/lib/utils';
|
|
3470
|
-
import type { MenuItem } from '@
|
|
3509
|
+
import type { MenuItem } from '@olonjs/core';
|
|
3471
3510
|
import type { HeaderData, HeaderSettings } from './types';
|
|
3472
3511
|
|
|
3473
3512
|
export const Header: React.FC<{
|
|
@@ -3891,7 +3930,7 @@ mkdir -p "src/components/image-break"
|
|
|
3891
3930
|
echo "Creating src/components/image-break/View.tsx..."
|
|
3892
3931
|
cat << 'END_OF_FILE_CONTENT' > "src/components/image-break/View.tsx"
|
|
3893
3932
|
import React from 'react';
|
|
3894
|
-
import { resolveAssetUrl, useConfig } from '@
|
|
3933
|
+
import { resolveAssetUrl, useConfig } from '@olonjs/core';
|
|
3895
3934
|
import type { ImageBreakData, ImageBreakSettings } from './types';
|
|
3896
3935
|
|
|
3897
3936
|
export const ImageBreak: React.FC<{ data: ImageBreakData; settings?: ImageBreakSettings }> = ({ data }) => {
|
|
@@ -7186,6 +7225,26 @@ export { Separator }
|
|
|
7186
7225
|
|
|
7187
7226
|
|
|
7188
7227
|
|
|
7228
|
+
END_OF_FILE_CONTENT
|
|
7229
|
+
echo "Creating src/components/ui/skeleton.tsx..."
|
|
7230
|
+
cat << 'END_OF_FILE_CONTENT' > "src/components/ui/skeleton.tsx"
|
|
7231
|
+
import { cn } from '@/lib/utils';
|
|
7232
|
+
import type { HTMLAttributes } from 'react';
|
|
7233
|
+
|
|
7234
|
+
function Skeleton({
|
|
7235
|
+
className,
|
|
7236
|
+
...props
|
|
7237
|
+
}: HTMLAttributes<HTMLDivElement>) {
|
|
7238
|
+
return (
|
|
7239
|
+
<div
|
|
7240
|
+
className={cn('animate-pulse rounded-md bg-muted', className)}
|
|
7241
|
+
{...props}
|
|
7242
|
+
/>
|
|
7243
|
+
);
|
|
7244
|
+
}
|
|
7245
|
+
|
|
7246
|
+
export { Skeleton };
|
|
7247
|
+
|
|
7189
7248
|
END_OF_FILE_CONTENT
|
|
7190
7249
|
echo "Creating src/components/ui/textarea.tsx..."
|
|
7191
7250
|
cat << 'END_OF_FILE_CONTENT' > "src/components/ui/textarea.tsx"
|
|
@@ -596,7 +596,7 @@ cat << 'END_OF_FILE_CONTENT' > "package.json"
|
|
|
596
596
|
"@tiptap/extension-link": "^2.11.5",
|
|
597
597
|
"@tiptap/react": "^2.11.5",
|
|
598
598
|
"@tiptap/starter-kit": "^2.11.5",
|
|
599
|
-
"@olonjs/core": "^1.0.
|
|
599
|
+
"@olonjs/core": "^1.0.71",
|
|
600
600
|
"clsx": "^2.1.1",
|
|
601
601
|
"lucide-react": "^0.474.0",
|
|
602
602
|
"react": "^19.0.0",
|
|
@@ -1074,6 +1074,30 @@ function App() {
|
|
|
1074
1074
|
const body = (await res.json().catch(() => ({}))) as { error?: string };
|
|
1075
1075
|
if (!res.ok) throw new Error(body.error ?? `Save to file failed: ${res.status}`);
|
|
1076
1076
|
},
|
|
1077
|
+
async hotSave(state: ProjectState, slug: string): Promise<void> {
|
|
1078
|
+
if (!isCloudMode || !CLOUD_API_URL || !CLOUD_API_KEY) {
|
|
1079
|
+
throw new Error('Cloud mode is not configured for hot save.');
|
|
1080
|
+
}
|
|
1081
|
+
const apiBase = CLOUD_API_URL.replace(/\/$/, '');
|
|
1082
|
+
const res = await fetch(`${apiBase}/hotSave`, {
|
|
1083
|
+
method: 'POST',
|
|
1084
|
+
headers: {
|
|
1085
|
+
'Content-Type': 'application/json',
|
|
1086
|
+
Authorization: `Bearer ${CLOUD_API_KEY}`,
|
|
1087
|
+
},
|
|
1088
|
+
body: JSON.stringify({
|
|
1089
|
+
slug,
|
|
1090
|
+
page: state.page,
|
|
1091
|
+
siteConfig: state.site,
|
|
1092
|
+
}),
|
|
1093
|
+
});
|
|
1094
|
+
const body = (await res.json().catch(() => ({}))) as { error?: string; code?: string };
|
|
1095
|
+
if (!res.ok) {
|
|
1096
|
+
throw new Error(body.error || body.code || `Hot save failed: ${res.status}`);
|
|
1097
|
+
}
|
|
1098
|
+
},
|
|
1099
|
+
showLegacySave: !isCloudMode,
|
|
1100
|
+
showHotSave: isCloudMode,
|
|
1077
1101
|
},
|
|
1078
1102
|
assets: {
|
|
1079
1103
|
assetsBaseUrl: '/assets',
|
|
@@ -2321,7 +2345,7 @@ cat << 'END_OF_FILE_CONTENT' > "src/components/contact-form/View.tsx"
|
|
|
2321
2345
|
import React, { useCallback, useState } from 'react';
|
|
2322
2346
|
import { useInView } from '@/lib/useInView';
|
|
2323
2347
|
import { useFormSubmit } from '@/lib/useFormSubmit';
|
|
2324
|
-
import { useConfig } from '@
|
|
2348
|
+
import { useConfig } from '@olonjs/core';
|
|
2325
2349
|
import type { ContactFormData, ContactFormSettings } from './types';
|
|
2326
2350
|
|
|
2327
2351
|
const inputCls = `
|
|
@@ -2695,7 +2719,7 @@ mkdir -p "src/components/cta-nature"
|
|
|
2695
2719
|
echo "Creating src/components/cta-nature/View.tsx..."
|
|
2696
2720
|
cat << 'END_OF_FILE_CONTENT' > "src/components/cta-nature/View.tsx"
|
|
2697
2721
|
import React from 'react';
|
|
2698
|
-
import { resolveAssetUrl, useConfig } from '@
|
|
2722
|
+
import { resolveAssetUrl, useConfig } from '@olonjs/core';
|
|
2699
2723
|
import type { CtaNatureData, CtaNatureSettings } from './types';
|
|
2700
2724
|
import type { SiteConfig } from '@/types';
|
|
2701
2725
|
import type { HeaderData } from '@/components/header';
|
|
@@ -3448,7 +3472,7 @@ mkdir -p "src/components/footer"
|
|
|
3448
3472
|
echo "Creating src/components/footer/View.tsx..."
|
|
3449
3473
|
cat << 'END_OF_FILE_CONTENT' > "src/components/footer/View.tsx"
|
|
3450
3474
|
import React, { useEffect, useState } from 'react';
|
|
3451
|
-
import { resolveAssetUrl, useConfig } from '@
|
|
3475
|
+
import { resolveAssetUrl, useConfig } from '@olonjs/core';
|
|
3452
3476
|
import type { FooterData, FooterSettings } from './types';
|
|
3453
3477
|
|
|
3454
3478
|
export const Footer: React.FC<{ data: FooterData; settings?: FooterSettings }> = ({ data }) => {
|
|
@@ -3888,9 +3912,9 @@ mkdir -p "src/components/header"
|
|
|
3888
3912
|
echo "Creating src/components/header/View.tsx..."
|
|
3889
3913
|
cat << 'END_OF_FILE_CONTENT' > "src/components/header/View.tsx"
|
|
3890
3914
|
import React, { useState, useEffect } from 'react';
|
|
3891
|
-
import { resolveAssetUrl, useConfig } from '@
|
|
3915
|
+
import { resolveAssetUrl, useConfig } from '@olonjs/core';
|
|
3892
3916
|
import type { HeaderData, HeaderSettings } from './types';
|
|
3893
|
-
import type { MenuItem } from '@
|
|
3917
|
+
import type { MenuItem } from '@olonjs/core';
|
|
3894
3918
|
|
|
3895
3919
|
interface HeaderProps {
|
|
3896
3920
|
data: HeaderData;
|
|
@@ -4303,7 +4327,7 @@ mkdir -p "src/components/image-break"
|
|
|
4303
4327
|
echo "Creating src/components/image-break/View.tsx..."
|
|
4304
4328
|
cat << 'END_OF_FILE_CONTENT' > "src/components/image-break/View.tsx"
|
|
4305
4329
|
import React from 'react';
|
|
4306
|
-
import { resolveAssetUrl, useConfig } from '@
|
|
4330
|
+
import { resolveAssetUrl, useConfig } from '@olonjs/core';
|
|
4307
4331
|
import type { ImageBreakData, ImageBreakSettings } from './types';
|
|
4308
4332
|
|
|
4309
4333
|
export const ImageBreak: React.FC<{ data: ImageBreakData; settings?: ImageBreakSettings }> = ({ data }) => {
|
|
@@ -596,7 +596,7 @@ cat << 'END_OF_FILE_CONTENT' > "package.json"
|
|
|
596
596
|
"@tiptap/extension-link": "^2.11.5",
|
|
597
597
|
"@tiptap/react": "^2.11.5",
|
|
598
598
|
"@tiptap/starter-kit": "^2.11.5",
|
|
599
|
-
"@olonjs/core": "^1.0.
|
|
599
|
+
"@olonjs/core": "^1.0.71",
|
|
600
600
|
"clsx": "^2.1.1",
|
|
601
601
|
"lucide-react": "^0.474.0",
|
|
602
602
|
"react": "^19.0.0",
|
|
@@ -941,6 +941,7 @@ import themeData from '@/data/config/theme.json';
|
|
|
941
941
|
import menuData from '@/data/config/menu.json';
|
|
942
942
|
import { getFilePages } from '@/lib/getFilePages';
|
|
943
943
|
import { DopaDrawer } from '@/components/save-drawer/DopaDrawer';
|
|
944
|
+
import { Skeleton } from '@/components/ui/skeleton';
|
|
944
945
|
|
|
945
946
|
import tenantCss from './index.css?inline';
|
|
946
947
|
|
|
@@ -1194,6 +1195,16 @@ function cloudFingerprint(apiBase: string, apiKey: string): string {
|
|
|
1194
1195
|
return `${normalizeApiBase(apiBase)}::${apiKey.slice(-8)}`;
|
|
1195
1196
|
}
|
|
1196
1197
|
|
|
1198
|
+
function normalizeSlugForCache(slug: string): string {
|
|
1199
|
+
return (
|
|
1200
|
+
slug
|
|
1201
|
+
.trim()
|
|
1202
|
+
.toLowerCase()
|
|
1203
|
+
.replace(/[^a-z0-9/_-]/g, '-')
|
|
1204
|
+
.replace(/^\/+|\/+$/g, '') || 'home'
|
|
1205
|
+
);
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1197
1208
|
function readCachedCloudContent(fingerprint: string): CachedCloudContent | null {
|
|
1198
1209
|
try {
|
|
1199
1210
|
const raw = localStorage.getItem(CLOUD_CACHE_KEY);
|
|
@@ -1248,7 +1259,6 @@ function App() {
|
|
|
1248
1259
|
const activeCloudSaveController = useRef<AbortController | null>(null);
|
|
1249
1260
|
const contentLoadInFlight = useRef<Promise<void> | null>(null);
|
|
1250
1261
|
const pendingCloudSave = useRef<{ state: ProjectState; slug: string } | null>(null);
|
|
1251
|
-
const warmBootFromCloudCache = useRef(false);
|
|
1252
1262
|
const cloudApiCandidates = useMemo(
|
|
1253
1263
|
() => (isCloudMode && CLOUD_API_URL ? buildApiCandidates(CLOUD_API_URL) : []),
|
|
1254
1264
|
[isCloudMode, CLOUD_API_URL]
|
|
@@ -1288,26 +1298,16 @@ function App() {
|
|
|
1288
1298
|
const primaryApiBase = cloudApiCandidates[0] ?? normalizeApiBase(CLOUD_API_URL);
|
|
1289
1299
|
const fingerprint = cloudFingerprint(primaryApiBase, CLOUD_API_KEY);
|
|
1290
1300
|
const cached = readCachedCloudContent(fingerprint);
|
|
1301
|
+
const cachedPages = cached ? toPagesRecord(cached.pages) : null;
|
|
1302
|
+
const cachedSite = cached ? coerceSiteConfig(cached.siteConfig) : null;
|
|
1303
|
+
const hasCachedFallback = Boolean((cachedPages && Object.keys(cachedPages).length > 0) || cachedSite);
|
|
1291
1304
|
if (cached) {
|
|
1292
|
-
const cachedPages = toPagesRecord(cached.pages);
|
|
1293
|
-
const cachedSite = coerceSiteConfig(cached.siteConfig);
|
|
1294
|
-
if (cachedPages && Object.keys(cachedPages).length > 0) {
|
|
1295
|
-
setPages(cachedPages);
|
|
1296
|
-
}
|
|
1297
|
-
if (cachedSite) {
|
|
1298
|
-
setSiteConfig(cachedSite);
|
|
1299
|
-
}
|
|
1300
|
-
setContentMode('cloud');
|
|
1301
|
-
setContentFallback(null);
|
|
1302
|
-
warmBootFromCloudCache.current = true;
|
|
1303
|
-
setShowTopProgress(false);
|
|
1304
|
-
setHasInitialCloudResolved(true);
|
|
1305
1305
|
logBootstrapEvent('boot.cloud.cache_hit', { ageMs: Date.now() - cached.savedAt });
|
|
1306
|
-
} else {
|
|
1307
|
-
warmBootFromCloudCache.current = false;
|
|
1308
|
-
setShowTopProgress(true);
|
|
1309
|
-
setHasInitialCloudResolved(false);
|
|
1310
1306
|
}
|
|
1307
|
+
setContentMode('cloud');
|
|
1308
|
+
setContentFallback(null);
|
|
1309
|
+
setShowTopProgress(true);
|
|
1310
|
+
setHasInitialCloudResolved(false);
|
|
1311
1311
|
logBootstrapEvent('boot.start', { mode: 'cloud', apiCandidates: cloudApiCandidates.length });
|
|
1312
1312
|
|
|
1313
1313
|
const loadCloudContent = async () => {
|
|
@@ -1320,6 +1320,7 @@ function App() {
|
|
|
1320
1320
|
try {
|
|
1321
1321
|
const res = await fetch(`${apiBase}/content`, {
|
|
1322
1322
|
method: 'GET',
|
|
1323
|
+
cache: 'no-store',
|
|
1323
1324
|
headers: {
|
|
1324
1325
|
Authorization: `Bearer ${CLOUD_API_KEY}`,
|
|
1325
1326
|
},
|
|
@@ -1422,7 +1423,13 @@ function App() {
|
|
|
1422
1423
|
} catch (error: unknown) {
|
|
1423
1424
|
if (controller.signal.aborted) return;
|
|
1424
1425
|
const failure = toCloudLoadFailure(error);
|
|
1425
|
-
if (
|
|
1426
|
+
if (hasCachedFallback) {
|
|
1427
|
+
if (cachedPages && Object.keys(cachedPages).length > 0) {
|
|
1428
|
+
setPages(cachedPages);
|
|
1429
|
+
}
|
|
1430
|
+
if (cachedSite) {
|
|
1431
|
+
setSiteConfig(cachedSite);
|
|
1432
|
+
}
|
|
1426
1433
|
setContentMode('cloud');
|
|
1427
1434
|
setContentFallback({
|
|
1428
1435
|
reasonCode: 'CLOUD_REFRESH_FAILED',
|
|
@@ -1589,14 +1596,26 @@ function App() {
|
|
|
1589
1596
|
},
|
|
1590
1597
|
body: JSON.stringify({
|
|
1591
1598
|
slug,
|
|
1592
|
-
|
|
1593
|
-
|
|
1599
|
+
page: state.page,
|
|
1600
|
+
siteConfig: state.site,
|
|
1594
1601
|
}),
|
|
1595
1602
|
});
|
|
1596
1603
|
const body = (await res.json().catch(() => ({}))) as { error?: string; code?: string };
|
|
1597
1604
|
if (!res.ok) {
|
|
1598
1605
|
throw new Error(body.error || body.code || `Hot save failed: ${res.status}`);
|
|
1599
1606
|
}
|
|
1607
|
+
const keyFingerprint = cloudFingerprint(apiBase, CLOUD_API_KEY);
|
|
1608
|
+
const normalizedSlug = normalizeSlugForCache(slug);
|
|
1609
|
+
const existing = readCachedCloudContent(keyFingerprint);
|
|
1610
|
+
writeCachedCloudContent({
|
|
1611
|
+
keyFingerprint,
|
|
1612
|
+
savedAt: Date.now(),
|
|
1613
|
+
siteConfig: state.site ?? null,
|
|
1614
|
+
pages: {
|
|
1615
|
+
...(existing?.pages ?? {}),
|
|
1616
|
+
[normalizedSlug]: state.page,
|
|
1617
|
+
},
|
|
1618
|
+
});
|
|
1600
1619
|
},
|
|
1601
1620
|
showLegacySave: !isCloudMode,
|
|
1602
1621
|
showHotSave: isCloudMode,
|
|
@@ -1667,6 +1686,26 @@ function App() {
|
|
|
1667
1686
|
</div>
|
|
1668
1687
|
</>
|
|
1669
1688
|
) : null}
|
|
1689
|
+
{isCloudMode && !hasInitialCloudResolved ? (
|
|
1690
|
+
<div className="fixed inset-0 z-[1290] bg-background/80 backdrop-blur-sm">
|
|
1691
|
+
<div className="mx-auto w-full max-w-[1600px] p-6">
|
|
1692
|
+
<div className="grid gap-4 lg:grid-cols-[1fr_420px]">
|
|
1693
|
+
<div className="space-y-4">
|
|
1694
|
+
<Skeleton className="h-10 w-64" />
|
|
1695
|
+
<Skeleton className="h-[220px] w-full rounded-xl" />
|
|
1696
|
+
<Skeleton className="h-[220px] w-full rounded-xl" />
|
|
1697
|
+
</div>
|
|
1698
|
+
<div className="space-y-3 rounded-xl border border-border/50 bg-card/60 p-4">
|
|
1699
|
+
<Skeleton className="h-8 w-32" />
|
|
1700
|
+
<Skeleton className="h-5 w-full" />
|
|
1701
|
+
<Skeleton className="h-5 w-5/6" />
|
|
1702
|
+
<Skeleton className="h-5 w-4/6" />
|
|
1703
|
+
<Skeleton className="h-24 w-full rounded-lg" />
|
|
1704
|
+
</div>
|
|
1705
|
+
</div>
|
|
1706
|
+
</div>
|
|
1707
|
+
</div>
|
|
1708
|
+
) : null}
|
|
1670
1709
|
{shouldRenderEngine ? <JsonPagesEngine config={config} /> : null}
|
|
1671
1710
|
{isCloudMode && (contentMode === 'error' || contentFallback?.reasonCode === 'CLOUD_REFRESH_FAILED') ? (
|
|
1672
1711
|
<div
|
|
@@ -3467,7 +3506,7 @@ echo "Creating src/components/header/View.tsx..."
|
|
|
3467
3506
|
cat << 'END_OF_FILE_CONTENT' > "src/components/header/View.tsx"
|
|
3468
3507
|
import React, { useState, useEffect } from 'react';
|
|
3469
3508
|
import { cn } from '@/lib/utils';
|
|
3470
|
-
import type { MenuItem } from '@
|
|
3509
|
+
import type { MenuItem } from '@olonjs/core';
|
|
3471
3510
|
import type { HeaderData, HeaderSettings } from './types';
|
|
3472
3511
|
|
|
3473
3512
|
export const Header: React.FC<{
|
|
@@ -3891,7 +3930,7 @@ mkdir -p "src/components/image-break"
|
|
|
3891
3930
|
echo "Creating src/components/image-break/View.tsx..."
|
|
3892
3931
|
cat << 'END_OF_FILE_CONTENT' > "src/components/image-break/View.tsx"
|
|
3893
3932
|
import React from 'react';
|
|
3894
|
-
import { resolveAssetUrl, useConfig } from '@
|
|
3933
|
+
import { resolveAssetUrl, useConfig } from '@olonjs/core';
|
|
3895
3934
|
import type { ImageBreakData, ImageBreakSettings } from './types';
|
|
3896
3935
|
|
|
3897
3936
|
export const ImageBreak: React.FC<{ data: ImageBreakData; settings?: ImageBreakSettings }> = ({ data }) => {
|
|
@@ -7186,6 +7225,26 @@ export { Separator }
|
|
|
7186
7225
|
|
|
7187
7226
|
|
|
7188
7227
|
|
|
7228
|
+
END_OF_FILE_CONTENT
|
|
7229
|
+
echo "Creating src/components/ui/skeleton.tsx..."
|
|
7230
|
+
cat << 'END_OF_FILE_CONTENT' > "src/components/ui/skeleton.tsx"
|
|
7231
|
+
import { cn } from '@/lib/utils';
|
|
7232
|
+
import type { HTMLAttributes } from 'react';
|
|
7233
|
+
|
|
7234
|
+
function Skeleton({
|
|
7235
|
+
className,
|
|
7236
|
+
...props
|
|
7237
|
+
}: HTMLAttributes<HTMLDivElement>) {
|
|
7238
|
+
return (
|
|
7239
|
+
<div
|
|
7240
|
+
className={cn('animate-pulse rounded-md bg-muted', className)}
|
|
7241
|
+
{...props}
|
|
7242
|
+
/>
|
|
7243
|
+
);
|
|
7244
|
+
}
|
|
7245
|
+
|
|
7246
|
+
export { Skeleton };
|
|
7247
|
+
|
|
7189
7248
|
END_OF_FILE_CONTENT
|
|
7190
7249
|
echo "Creating src/components/ui/textarea.tsx..."
|
|
7191
7250
|
cat << 'END_OF_FILE_CONTENT' > "src/components/ui/textarea.tsx"
|