feat(website): reduce white screen time
This commit is contained in:
@@ -45,13 +45,24 @@
|
|||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root">
|
||||||
|
<div id="sk">
|
||||||
|
<p class="sk-text" id="sk-text">Loading...</p>
|
||||||
|
<style>
|
||||||
|
#sk{display:flex;align-items:center;justify-content:center;min-height:100vh;background:linear-gradient(135deg,#eff6ff,#f5f3ff)}
|
||||||
|
@media(prefers-color-scheme:dark){#sk{background:linear-gradient(135deg,#111827,#1f2937)}}
|
||||||
|
.sk-text{font:400 14px/1 system-ui,sans-serif;color:#94a3b8;animation:skf 2s ease-in-out infinite}
|
||||||
|
@keyframes skf{0%,100%{opacity:.6}50%{opacity:.3}}
|
||||||
|
</style>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<script type="module" src="./src/main.tsx"></script>
|
<script type="module" src="./src/main.tsx"></script>
|
||||||
<script>
|
<script>
|
||||||
// Dynamically update html lang attribute based on i18n detection
|
|
||||||
const updateHtmlLang = () => {
|
const updateHtmlLang = () => {
|
||||||
const lang = localStorage.getItem('i18nextLng') || navigator.language || 'zh-CN'
|
const lang = localStorage.getItem('i18nextLng') || navigator.language || 'zh-CN'
|
||||||
document.documentElement.lang = lang
|
document.documentElement.lang = lang
|
||||||
|
const el = document.getElementById('sk-text')
|
||||||
|
if (el) el.textContent = lang.startsWith('zh') ? '加载中...' : 'Loading...'
|
||||||
}
|
}
|
||||||
updateHtmlLang()
|
updateHtmlLang()
|
||||||
window.addEventListener('storage', updateHtmlLang)
|
window.addEventListener('storage', updateHtmlLang)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/* eslint-disable react-dom/no-dangerously-set-innerhtml */
|
/* eslint-disable react-dom/no-dangerously-set-innerhtml */
|
||||||
import { PageAgent } from 'page-agent'
|
import type { PageAgent as PageAgentType } from 'page-agent'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { Link, useSearchParams } from 'wouter'
|
import { Link, useSearchParams } from 'wouter'
|
||||||
|
|
||||||
@@ -16,6 +16,8 @@ import {
|
|||||||
} from '../../constants'
|
} from '../../constants'
|
||||||
import { useLanguage } from '../../i18n/context'
|
import { useLanguage } from '../../i18n/context'
|
||||||
|
|
||||||
|
const pageAgentModule = import('page-agent')
|
||||||
|
|
||||||
function getInjection(useCN?: boolean) {
|
function getInjection(useCN?: boolean) {
|
||||||
const cdn = useCN ? CDN_DEMO_CN_URL : CDN_DEMO_URL
|
const cdn = useCN ? CDN_DEMO_CN_URL : CDN_DEMO_URL
|
||||||
|
|
||||||
@@ -55,13 +57,19 @@ export default function HeroSection() {
|
|||||||
const [activeTab, setActiveTab] = useState<'try' | 'other'>(isOther ? 'other' : 'try')
|
const [activeTab, setActiveTab] = useState<'try' | 'other'>(isOther ? 'other' : 'try')
|
||||||
const [cdnSource, setCdnSource] = useState<'international' | 'china'>('international')
|
const [cdnSource, setCdnSource] = useState<'international' | 'china'>('international')
|
||||||
|
|
||||||
const handleExecute = async () => {
|
const [ready, setReady] = useState(false)
|
||||||
if (!task.trim()) return
|
useEffect(() => {
|
||||||
|
pageAgentModule.then(() => setReady(true))
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const handleExecute = async () => {
|
||||||
|
if (!task.trim() || !ready) return
|
||||||
|
|
||||||
|
const { PageAgent } = await pageAgentModule
|
||||||
const win = window as any
|
const win = window as any
|
||||||
|
|
||||||
if (!win.pageAgent || win.pageAgent.disposed) {
|
if (!win.pageAgent || win.pageAgent.disposed) {
|
||||||
win.pageAgent = new PageAgent({
|
win.pageAgent = new (PageAgent as typeof PageAgentType)({
|
||||||
interactiveBlacklist: [document.getElementById('root')!],
|
interactiveBlacklist: [document.getElementById('root')!],
|
||||||
language: language,
|
language: language,
|
||||||
|
|
||||||
@@ -208,10 +216,21 @@ export default function HeroSection() {
|
|||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
onClick={handleExecute}
|
onClick={handleExecute}
|
||||||
|
disabled={!ready}
|
||||||
className="absolute right-2 top-2 px-5 py-1.5 bg-linear-to-r from-blue-600 to-purple-600 text-white font-medium rounded-md hover:shadow-md transform hover:scale-105 transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed disabled:transform-none text-sm"
|
className="absolute right-2 top-2 px-5 py-1.5 bg-linear-to-r from-blue-600 to-purple-600 text-white font-medium rounded-md hover:shadow-md transform hover:scale-105 transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed disabled:transform-none text-sm"
|
||||||
data-page-agent-not-interactive
|
data-page-agent-not-interactive
|
||||||
>
|
>
|
||||||
{isZh ? '执行' : 'Run'}
|
{ready ? (
|
||||||
|
isZh ? (
|
||||||
|
'执行'
|
||||||
|
) : (
|
||||||
|
'Run'
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
<span className="animate-pulse">
|
||||||
|
{isZh ? '准备中...' : 'Preparing...'}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-gray-500 dark:text-gray-400 text-left">
|
<p className="text-xs text-gray-500 dark:text-gray-400 text-left">
|
||||||
|
|||||||
@@ -1,10 +1,18 @@
|
|||||||
import { Suspense, useLayoutEffect } from 'react'
|
import { Suspense, lazy, useLayoutEffect } from 'react'
|
||||||
import { Route, Switch, useLocation } from 'wouter'
|
import { Route, Switch, useLocation } from 'wouter'
|
||||||
|
|
||||||
import Footer from './components/Footer'
|
import Footer from './components/Footer'
|
||||||
import Header from './components/Header'
|
import Header from './components/Header'
|
||||||
|
import { useLanguage } from './i18n/context'
|
||||||
import HomePage from './pages/Home'
|
import HomePage from './pages/Home'
|
||||||
import DocsPages from './pages/docs/index'
|
import DocsLayout from './pages/docs/Layout'
|
||||||
|
|
||||||
|
const DocsPages = lazy(() => import('./pages/docs/index'))
|
||||||
|
|
||||||
|
// Prefetch docs chunk during idle time so navigation feels instant
|
||||||
|
if (typeof requestIdleCallback !== 'undefined') {
|
||||||
|
requestIdleCallback(() => import('./pages/docs/index'))
|
||||||
|
}
|
||||||
|
|
||||||
function ScrollToTop() {
|
function ScrollToTop() {
|
||||||
const [pathname] = useLocation()
|
const [pathname] = useLocation()
|
||||||
@@ -14,6 +22,18 @@ function ScrollToTop() {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function DocsLoadingFallback() {
|
||||||
|
const { isZh } = useLanguage()
|
||||||
|
return (
|
||||||
|
<DocsLayout>
|
||||||
|
<div className="flex items-center gap-3 py-12 text-gray-500 dark:text-gray-400">
|
||||||
|
<div className="w-5 h-5 border-2 border-blue-500 border-t-transparent rounded-full animate-spin" />
|
||||||
|
{isZh ? '文档加载中...' : 'Loading documentation...'}
|
||||||
|
</div>
|
||||||
|
</DocsLayout>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export default function Router() {
|
export default function Router() {
|
||||||
return (
|
return (
|
||||||
<div className="flex min-h-screen flex-col">
|
<div className="flex min-h-screen flex-col">
|
||||||
@@ -32,7 +52,9 @@ export default function Router() {
|
|||||||
|
|
||||||
<Route path="/docs" nest>
|
<Route path="/docs" nest>
|
||||||
<div className="flex-1 bg-white dark:bg-gray-900">
|
<div className="flex-1 bg-white dark:bg-gray-900">
|
||||||
<DocsPages />
|
<Suspense fallback={<DocsLoadingFallback />}>
|
||||||
|
<DocsPages />
|
||||||
|
</Suspense>
|
||||||
</div>
|
</div>
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
|
|||||||
@@ -64,6 +64,12 @@ export default defineConfig(({ mode }) => ({
|
|||||||
if (message.code === 'EVAL') return
|
if (message.code === 'EVAL') return
|
||||||
handler(message)
|
handler(message)
|
||||||
},
|
},
|
||||||
|
output: {
|
||||||
|
manualChunks: {
|
||||||
|
vendor: ['react', 'react-dom', 'wouter'],
|
||||||
|
'page-agent': ['page-agent'],
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
|
|||||||
Reference in New Issue
Block a user