mirror of
https://github.com/czl9707/build-your-own-openclaw.git
synced 2026-08-14 00:47:59 +00:00
Fix/ Step Comparing Step Restriction (#5)
* feat: add learning website with Next.js Add interactive web interface for the Build Your Own OpenClaw tutorial: - Next.js app with TypeScript and Tailwind CSS - Theme provider with dark/light mode toggle - Step card components with diff visualization - Utilities for loading step metadata and file diffs * chore: ignore worktrees directory * feat(web): implement learning website with step navigation and diff viewer - Landing page with hero section and steps overview grouped by phase - Step detail pages with README rendering and syntax highlighting (Shiki) - Diff comparison pages with side-by-side GitHub-style viewer - Custom 404 Not Found page - Components: ReadmeRenderer, CodeBlock, DiffViewer, DiffSelector - Generates 175 static pages (18 steps + 153 diff combinations) * feat(web): enhance UI with zane-portfolio theme and improved UX - Update color scheme to match zane-portfolio style (warm beige light, pure black dark) - Add scrollbar styling for light/dark modes - Fix card border visibility in dark mode - Add GitHub CTA section with clone command and copy button - Add star on GitHub call-to-action - Use favicons from zane-portfolio - Improve diff viewer with scroll sync and collapsible files - Add sticky header with file navigation dropdown - Rewrite README links to GitHub and internal step pages - Remove default create-next-app template files * ci: add GitHub Pages deployment workflow - Add workflow to build and deploy Next.js static site - Configure custom domain (build-your-own-openclaw.kiyo-n-zane.com) - Trigger on push to main branch * fix config * fix config * fix config * fix web navigation bug
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
import { notFound } from 'next/navigation'
|
||||
import Link from 'next/link'
|
||||
import { getStep, getSteps } from '@/lib/steps'
|
||||
import { getChangedFiles, getUnchangedFiles, type FileDiff } from '@/lib/files'
|
||||
import { getChangedFiles, getUnchangedFiles } from '@/lib/files'
|
||||
import { DiffViewer } from '@/components/diff-viewer'
|
||||
import { FileNavDropdown } from '@/components/file-nav-dropdown'
|
||||
import { DiffPageSelector } from '@/components/diff-page-selector'
|
||||
import {
|
||||
Breadcrumb,
|
||||
BreadcrumbList,
|
||||
@@ -12,8 +13,6 @@ import {
|
||||
BreadcrumbPage,
|
||||
BreadcrumbSeparator,
|
||||
} from '@/components/ui/breadcrumb'
|
||||
import { H2, Muted } from '@/components/ui/typography'
|
||||
import { PlusIcon, MinusIcon } from 'lucide-react'
|
||||
|
||||
interface DiffPageProps {
|
||||
params: Promise<{
|
||||
@@ -29,10 +28,10 @@ export async function generateStaticParams() {
|
||||
// Generate all valid combinations where from < to
|
||||
for (let i = 0; i < steps.length - 1; i++) {
|
||||
for (let j = i + 1; j < steps.length; j++) {
|
||||
params.push({
|
||||
id: steps[i].id,
|
||||
to: steps[j].id,
|
||||
})
|
||||
params.push({
|
||||
id: steps[i].id,
|
||||
to: steps[j].id,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,21 +52,6 @@ export async function generateMetadata({ params }: DiffPageProps) {
|
||||
}
|
||||
}
|
||||
|
||||
// Status type for changed files (excludes 'unchanged')
|
||||
type ChangedStatus = 'added' | 'removed' | 'modified'
|
||||
|
||||
// Get status icon for file
|
||||
function getStatusIcon(status: ChangedStatus) {
|
||||
switch (status) {
|
||||
case 'added':
|
||||
return <PlusIcon className="size-3 text-green-500" />
|
||||
case 'removed':
|
||||
return <MinusIcon className="size-3 text-red-500" />
|
||||
case 'modified':
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export default async function DiffPage({ params }: DiffPageProps) {
|
||||
const { id, to } = await params
|
||||
const fromStep = getStep(id)
|
||||
@@ -112,15 +96,13 @@ export default async function DiffPage({ params }: DiffPageProps) {
|
||||
|
||||
{/* Sticky Page Header */}
|
||||
<div className="sticky top-14 z-40 -mx-4 px-4 py-4 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60 border-b mb-8">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<H2 className="mb-1">
|
||||
Step {fromStep.id} to Step {toStep.id}
|
||||
</H2>
|
||||
<Muted>
|
||||
Comparing "{fromStep.title}" with "{toStep.title}"
|
||||
</Muted>
|
||||
</div>
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<DiffPageSelector
|
||||
fromStep={fromStep}
|
||||
toStep={toStep}
|
||||
fromSteps={getSteps().filter((s) => parseInt(s.id) < parseInt(toStep.id))}
|
||||
toSteps={getSteps().filter((s) => parseInt(s.id) > parseInt(fromStep.id))}
|
||||
/>
|
||||
{changedFiles.length > 0 && (
|
||||
<FileNavDropdown
|
||||
files={changedFiles.map((file) => ({
|
||||
|
||||
@@ -5,7 +5,7 @@ import { ChevronLeft, ChevronRight } from 'lucide-react'
|
||||
|
||||
import { getStep, getSteps } from '@/lib/steps'
|
||||
import { ReadmeRenderer } from '@/components/readme-renderer'
|
||||
import { DiffSelector } from '@/components/diff-selector'
|
||||
import { StepDiffSelector } from '@/components/step-diff-selector'
|
||||
import {
|
||||
Breadcrumb,
|
||||
BreadcrumbItem,
|
||||
@@ -60,7 +60,7 @@ export default async function StepPage({ params }: PageProps) {
|
||||
const nextStep = currentIndex < steps.length - 1 ? steps[currentIndex + 1] : null
|
||||
|
||||
// Steps available for diff comparison (excluding current step)
|
||||
const diffTargets = steps.filter((s) => s.id !== step.id)
|
||||
const diffTargets = steps.filter((s) => parseInt(s.id) > parseInt(step.id))
|
||||
|
||||
return (
|
||||
<div className="container py-8">
|
||||
@@ -94,7 +94,7 @@ export default async function StepPage({ params }: PageProps) {
|
||||
</H1>
|
||||
|
||||
{/* Diff action dropdown */}
|
||||
<DiffSelector currentStepId={step.id} steps={diffTargets} />
|
||||
<StepDiffSelector currentStepId={step.id} steps={diffTargets} />
|
||||
</div>
|
||||
|
||||
{/* README Content */}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
'use client'
|
||||
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { DiffSelector } from '@/components/diff-selector'
|
||||
import type { Step } from '@/lib/steps'
|
||||
|
||||
interface DiffPageSelectorProps {
|
||||
fromStep: Step
|
||||
toStep: Step
|
||||
fromSteps: Step[]
|
||||
toSteps: Step[]
|
||||
}
|
||||
|
||||
export function DiffPageSelector({
|
||||
fromStep,
|
||||
toStep,
|
||||
fromSteps,
|
||||
toSteps,
|
||||
}: DiffPageSelectorProps) {
|
||||
const router = useRouter()
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<DiffSelector
|
||||
steps={fromSteps}
|
||||
value={fromStep.id}
|
||||
placeholder="From step..."
|
||||
onSelect={(newFromId) => router.push(`/steps/${newFromId}/diff/${toStep.id}`)}
|
||||
/>
|
||||
<span className="text-muted-foreground">→</span>
|
||||
<DiffSelector
|
||||
steps={toSteps}
|
||||
value={toStep.id}
|
||||
placeholder="To step..."
|
||||
onSelect={(newToId) => router.push(`/steps/${fromStep.id}/diff/${newToId}`)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
'use client'
|
||||
|
||||
import { useRouter } from 'next/navigation'
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
@@ -11,28 +10,43 @@ import {
|
||||
import type { Step } from '@/lib/steps'
|
||||
|
||||
interface DiffSelectorProps {
|
||||
currentStepId: string
|
||||
steps: Step[]
|
||||
value?: string
|
||||
placeholder?: string
|
||||
onSelect: (stepId: string) => void
|
||||
}
|
||||
|
||||
export function DiffSelector({ currentStepId, steps }: DiffSelectorProps) {
|
||||
const router = useRouter()
|
||||
|
||||
const handleValueChange = (value: string | null) => {
|
||||
if (value) {
|
||||
router.push(`/steps/${currentStepId}/diff/${value}`)
|
||||
export function DiffSelector({
|
||||
steps,
|
||||
value,
|
||||
placeholder = 'Compare with...',
|
||||
onSelect,
|
||||
}: DiffSelectorProps) {
|
||||
const handleValueChange = (newValue: string | null) => {
|
||||
if (newValue) {
|
||||
onSelect(newValue)
|
||||
}
|
||||
}
|
||||
|
||||
const selectedStep = steps.find((s) => s.id === value)
|
||||
|
||||
return (
|
||||
<Select onValueChange={handleValueChange}>
|
||||
<SelectTrigger className="min-w-64">
|
||||
<SelectValue placeholder="Compare with..." />
|
||||
<Select value={value} onValueChange={handleValueChange}>
|
||||
<SelectTrigger className="min-w-48">
|
||||
<SelectValue placeholder={placeholder}>
|
||||
{selectedStep && (
|
||||
<>
|
||||
<span className="font-mono text-xs">{selectedStep.id}:</span>{' '}
|
||||
<span className="text-xs">{selectedStep.title}</span>
|
||||
</>
|
||||
)}
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{steps.map((target) => (
|
||||
<SelectItem key={target.id} value={target.id}>
|
||||
Step {target.id}: {target.title}
|
||||
{steps.map((step) => (
|
||||
<SelectItem key={step.id} value={step.id}>
|
||||
<span className="font-mono text-xs">{step.id}:</span>{' '}
|
||||
<span className="text-xs">{step.title}</span>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
'use client'
|
||||
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { DiffSelector } from '@/components/diff-selector'
|
||||
import type { Step } from '@/lib/steps'
|
||||
|
||||
interface StepDiffSelectorProps {
|
||||
currentStepId: string
|
||||
steps: Step[]
|
||||
}
|
||||
|
||||
export function StepDiffSelector({ currentStepId, steps }: StepDiffSelectorProps) {
|
||||
const router = useRouter()
|
||||
|
||||
return (
|
||||
<DiffSelector
|
||||
steps={steps}
|
||||
onSelect={(targetId) => router.push(`/steps/${currentStepId}/diff/${targetId}`)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user