@olonjs/cli 3.0.82 → 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
|
|
@@ -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',
|
|
@@ -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
|
|
@@ -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"
|