first commit
Some checks failed
Build and Push Docker Image / build (push) Has been cancelled
Sync to CNB / sync (push) Has been cancelled
Delete old workflow runs / del_runs (push) Has been cancelled
Upstream Sync / Sync latest commits from upstream repo (push) Has been cancelled

This commit is contained in:
2026-01-30 16:57:44 +08:00
commit 3d175d75af
119 changed files with 35834 additions and 0 deletions

161
app/regex/page.tsx Normal file
View File

@@ -0,0 +1,161 @@
"use client";
import { useState, useEffect } from "react";
import { Code, AlertCircle, CheckCircle2 } from "lucide-react";
import { Textarea } from "@/components/ui/textarea";
import { Input } from "@/components/ui/input";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Switch } from "@/components/ui/switch";
import { cn } from "@/lib/utils";
export default function RegexPage() {
const [pattern, setPattern] = useState("([a-zA-Z0-9._%-]+)@([a-zA-Z0-9.-]+\\.[a-zA-Z]{2,6})");
const [flags, setFlags] = useState({
global: true,
ignoreCase: false,
multiline: false
});
const [text, setText] = useState("My email is example@mail.com and work@company.org.");
const [matches, setMatches] = useState<any[]>([]);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
try {
if (!pattern) {
setMatches([]);
setError(null);
return;
}
const flagStr = (flags.global ? 'g' : '') + (flags.ignoreCase ? 'i' : '') + (flags.multiline ? 'm' : '');
const regex = new RegExp(pattern, flagStr);
const allMatches = [];
let match;
if (flags.global) {
while ((match = regex.exec(text)) !== null) {
allMatches.push(match);
if (match.index === regex.lastIndex) regex.lastIndex++; // Prevent infinite loop for zero-width matches
}
} else {
match = regex.exec(text);
if (match) allMatches.push(match);
}
setMatches(allMatches);
setError(null);
} catch (e: any) {
setError(e.message);
setMatches([]);
}
}, [pattern, flags, text]);
return (
<div className="space-y-6 animate-in fade-in slide-in-from-bottom-4 duration-500">
<div className="flex items-center space-x-4 border-b pb-4">
<div className="flex h-12 w-12 items-center justify-center rounded-xl bg-linear-to-br from-blue-600 to-cyan-600 shadow-lg">
<Code className="h-6 w-6 text-white" />
</div>
<div>
<h1 className="text-2xl font-bold tracking-tight"></h1>
<p className="text-muted-foreground"></p>
</div>
</div>
<div className="grid gap-6">
<Card>
<CardHeader>
<CardTitle className="text-base"></CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex gap-4 flex-col md:flex-row">
<div className="flex-1 relative">
<div className="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground font-mono">/</div>
<Input
value={pattern}
onChange={(e) => setPattern(e.target.value)}
className={cn("pl-6 pr-10 font-mono", error && "border-destructive focus-visible:ring-destructive")}
placeholder="输入正则表达式模式..."
/>
<div className="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground font-mono">/</div>
</div>
<div className="flex items-center gap-4 px-2">
<FlagToggle label="g" active={flags.global} onToggle={(v) => setFlags({...flags, global: v})} title="全局匹配" />
<FlagToggle label="i" active={flags.ignoreCase} onToggle={(v) => setFlags({...flags, ignoreCase: v})} title="忽略大小写" />
<FlagToggle label="m" active={flags.multiline} onToggle={(v) => setFlags({...flags, multiline: v})} title="多行模式" />
</div>
</div>
{error && (
<div className="p-3 rounded-md bg-destructive/10 text-destructive text-sm flex items-center gap-2">
<AlertCircle className="h-4 w-4" />
{error}
</div>
)}
</CardContent>
</Card>
<div className="grid lg:grid-cols-2 gap-6">
<Card>
<CardHeader>
<CardTitle className="text-base"></CardTitle>
</CardHeader>
<CardContent>
<Textarea
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="请在此输入需要匹配的文本..."
className="min-h-50 font-mono leading-relaxed"
/>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-base"> ({matches.length})</CardTitle>
{matches.length > 0 && <CheckCircle2 className="h-4 w-4 text-emerald-500" />}
</CardHeader>
<CardContent className="space-y-3 max-h-50 overflow-y-auto">
{matches.length === 0 ? (
<div className="text-center py-8 text-muted-foreground text-sm italic">
</div>
) : (
matches.map((m, i) => (
<div key={i} className="p-3 rounded-md bg-muted/50 border space-y-1">
<div className="flex justify-between text-[10px] text-muted-foreground">
<span>Match {i + 1}</span>
<span>Index: {m.index}</span>
</div>
<div className="font-mono text-sm break-all">
{m[0]}
</div>
{m.length > 1 && (
<div className="pt-2 border-t mt-2 space-y-1">
{m.slice(1).map((group: string, gi: number) => (
<div key={gi} className="flex gap-2 text-xs">
<span className="text-muted-foreground font-mono">Group {gi + 1}:</span>
<span className="font-mono text-primary">{group || "(empty)"}</span>
</div>
))}
</div>
)}
</div>
))
)}
</CardContent>
</Card>
</div>
</div>
</div>
);
}
function FlagToggle({ label, active, onToggle, title }: { label: string; active: boolean; onToggle: (v: boolean) => void; title: string }) {
return (
<div className="flex flex-col items-center gap-1" title={title}>
<span className="text-[10px] font-bold text-muted-foreground font-mono">{label}</span>
<Switch checked={active} onCheckedChange={onToggle} />
</div>
);
}