Add Thesisly research reader frontend and project design skills
This commit is contained in:
@@ -0,0 +1 @@
|
||||
export default function About() { return <main id="main" className="page-shell about-page"><div className="section-kicker">ABOUT THE INDEX</div><h1>让研究,<br />更容易被阅读。</h1><p>这里将论文标题、作者、摘要与原始出处放在同一个阅读空间里。</p><h2>当前数据范围</h2><p>未配置论文 HTTP API 时,页面使用 2026-11-06 从既有 Paper Atlas SQLite 数据库导出的前 20 条论文记录。它们是真实的历史记录,并不代表热门论文、最新成果或完整数据库。搜索、领域与分页均只针对这份样本。</p><h2>三种阅读方式</h2><p>Vignelli 以无衬线字体、网格和细分隔线组织内容;Editorial 以柔和的衬线阅读节奏呈现;Tufte 以低装饰和清楚的证据信息为主。可以用顶部的主题菜单比较。</p><h2>账户与共享后端</h2><p>身份认证通过已有 Foundry Core 完成。本站的项目回调尚需在后端注册后,才能完成 Google、Slack 与邮件验证登录。已有会话由服务端 Cookie 维持。</p></main>; }
|
||||
@@ -0,0 +1,39 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { COOKIE_NAME, allowedAuthMethod, cookie, maxAge, sameOrigin } from "@/lib/auth-policy.mjs";
|
||||
const appOrigin = process.env.APP_ORIGIN || "http://localhost:17322";
|
||||
async function handler(req: NextRequest, { params }: { params: Promise<{ path: string[] }> }) {
|
||||
const path = (await params).path.join("/");
|
||||
const headers = new Headers({ "Cache-Control": "no-store", "X-Content-Type-Options": "nosniff" });
|
||||
const json = (body: unknown, status: number) => NextResponse.json(body, { status, headers });
|
||||
if (!/^auth\/(me|login|register|verify-email|exchange|logout)$/.test(path)) return json({ error: "not_found" }, 404);
|
||||
if (!allowedAuthMethod(path, req.method)) return json({ error: "method_not_allowed" }, 405);
|
||||
if (req.method !== "GET" && (!sameOrigin(req.headers.get("origin") || "", appOrigin) || req.headers.get("sec-fetch-site") === "cross-site")) return json({ error: "invalid_origin" }, 403);
|
||||
const token = req.cookies.get(COOKIE_NAME)?.value;
|
||||
if ((path === "auth/me" || path === "auth/logout") && !token) return json({ error: "invalid_session" }, 401);
|
||||
const project = process.env.FOUNDRY_PROJECT;
|
||||
if (!project) return json({ error: "project_not_configured" }, 503);
|
||||
let body: unknown;
|
||||
if (req.method === "POST") {
|
||||
const reader = req.body?.getReader(); let count = 0; const parts: Uint8Array[] = [];
|
||||
if (reader) { try { for (;;) { const { value, done } = await reader.read(); if (done) break; count += value.byteLength; if (count > 16384) { await reader.cancel(); return json({ error: "body_too_large" }, 413); } parts.push(value); } } finally { reader.releaseLock(); } }
|
||||
try { body = count ? JSON.parse(Buffer.concat(parts).toString("utf8")) : {}; } catch { return json({ error: "invalid_json" }, 400); }
|
||||
}
|
||||
const outbound: Record<string,string> = { "Content-Type": "application/json" };
|
||||
if (token) outbound.Authorization = "Bearer " + token;
|
||||
if (path === "auth/exchange") for (const h of ["x-foundry-code", "x-foundry-verifier"]) { const v = req.headers.get(h); if (v && v.length <= 256) outbound[h] = v; }
|
||||
const secure = new URL(appOrigin).protocol === "https:";
|
||||
try {
|
||||
const origin = process.env.FOUNDRY_API_ORIGIN || "https://api.foundry.entroia.com";
|
||||
const up = await fetch(`${origin}/projects/${encodeURIComponent(project)}/${path}`, { method: req.method, headers: outbound, body: req.method === "POST" ? JSON.stringify(body) : undefined, signal: AbortSignal.timeout(15000), cache: "no-store", redirect: "error" });
|
||||
const retry = up.headers.get("retry-after"); if (retry) headers.set("Retry-After", retry);
|
||||
const data = up.status === 204 ? null : await up.json();
|
||||
if (data && typeof data === "object" && "access_token" in data) {
|
||||
if (up.ok && typeof data.access_token === "string" && maxAge(data.expires_in) !== null) headers.set("Set-Cookie", cookie(data.access_token, data.expires_in, secure));
|
||||
delete data.access_token; delete data.refresh_token;
|
||||
} else if (token && up.ok && maxAge(up.headers.get("X-Foundry-Session-Expires-In")) !== null) headers.set("Set-Cookie", cookie(token, up.headers.get("X-Foundry-Session-Expires-In"), secure));
|
||||
if (up.status === 401 || path === "auth/logout" && up.ok) headers.set("Set-Cookie", cookie("", 0, secure));
|
||||
if (up.status === 204) return new NextResponse(null, { status: 204, headers });
|
||||
return json(data, up.status);
|
||||
} catch { return json({ error: "upstream_unavailable" }, 502); }
|
||||
}
|
||||
export { handler as GET, handler as POST };
|
||||
@@ -0,0 +1,10 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
export async function GET(req: NextRequest, { params }: { params: Promise<{ provider: string }> }) {
|
||||
const { provider } = await params;
|
||||
const cs = req.nextUrl.searchParams.getAll("challenge");
|
||||
if (!["google", "slack"].includes(provider) || cs.length !== 1 || !/^[-_a-zA-Z0-9]{43}$/.test(cs[0])) return NextResponse.json({ error: "invalid_request" }, { status: 400 });
|
||||
const project = process.env.FOUNDRY_PROJECT;
|
||||
if (!project) return NextResponse.json({ error: "project_not_configured" }, { status: 503 });
|
||||
const origin = process.env.FOUNDRY_API_ORIGIN || "https://api.foundry.entroia.com";
|
||||
return NextResponse.redirect(`${origin}/projects/${encodeURIComponent(project)}/auth/${provider}?challenge=${cs[0]}`, { status: 302, headers: { "Cache-Control": "no-store" } });
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
"use client";
|
||||
import { useEffect, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { finishCallback, errorText } from "@/lib/auth-client";
|
||||
import { useSession } from "@/components/providers";
|
||||
export default function Callback() { const [error,setError]=useState("");const router=useRouter();const {reload}=useSession();useEffect(()=>{let alive=true;finishCallback().then(async()=>{await reload();if(alive)router.replace("/");}).catch(e=>{if(alive)setError(errorText(e));});return()=>{alive=false;};},[router,reload]);return <main id="main" className="page-shell empty-state"><h1>{error?"登录未完成":"正在完成登录…"}</h1>{error?<><p role="alert">{error}</p><Link className="text-link" href="/login">返回登录 →</Link></>:<p role="status">正在安全兑换账户会话。</p>}</main>; }
|
||||
@@ -0,0 +1,3 @@
|
||||
"use client";
|
||||
import { Button } from "@/components/ui/button";
|
||||
export default function ErrorPage({ reset }: { reset: () => void }) { return <main id="main" className="page-shell empty-state"><h1>论文暂时没有加载成功</h1><p>数据服务可能暂时不可用,请稍后重试。</p><Button onClick={reset}>重新加载</Button></main>; }
|
||||
@@ -0,0 +1,153 @@
|
||||
@import "tailwindcss";
|
||||
@theme inline {
|
||||
--color-background: var(--paper);
|
||||
--color-foreground: var(--ink);
|
||||
--color-muted: var(--surface);
|
||||
--color-muted-foreground: var(--secondary);
|
||||
--color-border: var(--line);
|
||||
}
|
||||
:root { --paper:#fafafa; --ink:#202125; --secondary:#71717a; --surface:#f0f0f1; --line:#dedee1; --accent:#b73729; --reading: "Helvetica Neue",Arial,"PingFang SC",sans-serif; --display:var(--reading); }
|
||||
:root[data-theme="editorial"] { --paper:#fbfaf7; --ink:#25231e; --secondary:#746e63; --surface:#f1efe9; --line:#dedad0; --accent:#7d3b2f; --reading:Georgia,"Songti SC",serif; --display:var(--reading); }
|
||||
:root[data-theme="tufte"] { --paper:#fffff8; --ink:#25251f; --secondary:#6b6b5d; --surface:#f5f4ea; --line:#d8d2c2; --accent:#48656a; --reading:Palatino,"Palatino Linotype",Georgia,"Songti SC",serif; --display:var(--reading); }
|
||||
* { box-sizing:border-box; }
|
||||
html { scroll-behavior:smooth; scroll-padding-top:100px; }
|
||||
body { margin:0; background:var(--paper); color:var(--ink); font-family:"Helvetica Neue",Arial,"PingFang SC",sans-serif; font-size:14px; -webkit-font-smoothing:antialiased; }
|
||||
::selection { background:#dededb; color:#171717; }
|
||||
a { color:inherit; text-decoration:none; }
|
||||
button,select,input { font:inherit; }
|
||||
button,select { cursor:pointer; }
|
||||
button:disabled { cursor:wait; }
|
||||
:focus-visible { outline:2px solid var(--accent); outline-offset:4px; }
|
||||
h1,h2,h3,p { margin:0; }
|
||||
h1,h2,h3 { text-wrap:balance; }
|
||||
p { text-wrap:pretty; }
|
||||
svg { flex-shrink:0; }
|
||||
.skip-link { position:fixed; top:-100px; left:16px; z-index:100; padding:12px; background:var(--ink); color:var(--paper); }
|
||||
.skip-link:focus { top:12px; }
|
||||
.site-header { border-bottom:1px solid var(--line); background:var(--paper); }
|
||||
.header-inner { max-width:1320px; padding:0 48px; height:80px; margin:auto; display:flex; align-items:center; gap:64px; }
|
||||
.brand { display:flex; align-items:center; gap:9px; }
|
||||
.brand>span { font-size:25px; font-weight:600; letter-spacing:-1px; }
|
||||
.brand-dot { color:var(--accent); }
|
||||
.brand small { font-size:10px; letter-spacing:1.2px; color:var(--secondary); padding-left:14px; margin-left:5px; border-left:1px solid var(--line); }
|
||||
.header-inner nav { display:flex; gap:28px; color:var(--secondary); }
|
||||
.header-inner nav a:first-child { color:var(--ink); }
|
||||
.header-actions { margin-left:auto; display:flex; align-items:center; gap:22px; }
|
||||
.theme-label select { border:0; background:transparent; color:var(--secondary); padding:8px 4px; font-size:12px; }
|
||||
.account-name { max-width:130px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
||||
.page-shell { max-width:1200px; margin:auto; padding:0 48px; }
|
||||
.index-intro { padding:50px 0 40px; }
|
||||
.section-kicker { display:flex; justify-content:space-between; gap:20px; font-size:10px; font-weight:500; letter-spacing:1.5px; color:var(--secondary); }
|
||||
.section-kicker>span { letter-spacing:.5px; }
|
||||
.index-intro h1 { margin:35px 0 26px; font-family:var(--display); font-size:clamp(42px,5vw,68px); font-weight:450; line-height:1.2; letter-spacing:-2px; }
|
||||
.muted-heading { color:#99999f; }
|
||||
.intro-bottom { display:flex; justify-content:space-between; align-items:flex-end; }
|
||||
.intro-bottom p { color:var(--secondary); font-size:14px; line-height:1.9; }
|
||||
.text-link { display:inline-flex; align-items:center; gap:12px; font-size:13px; padding-bottom:4px; border-bottom:1px solid var(--line); }
|
||||
.text-link:hover { color:var(--accent); border-color:var(--accent); }
|
||||
.sample-note { display:flex; align-items:center; gap:10px; border-top:1px solid var(--line); border-bottom:1px solid var(--line); padding:14px 0; font-size:11px; color:var(--secondary); line-height:1.7; }
|
||||
.sample-note p { flex:1; }
|
||||
.sample-note a { display:flex; align-items:center; gap:4px; white-space:nowrap; }
|
||||
.status-dot { width:5px; height:5px; background:var(--accent); border-radius:50%; flex-shrink:0; }
|
||||
.index-grid { display:grid; grid-template-columns:180px minmax(0,1fr); gap:64px; padding:46px 0 70px; }
|
||||
.index-aside h2 { font-size:12px; font-weight:500; margin-bottom:24px; }
|
||||
.topic { display:flex; justify-content:space-between; padding:9px 0; color:var(--secondary); font-size:12px; }
|
||||
.topic.active { color:var(--ink); font-weight:600; }
|
||||
.topic:hover { color:var(--accent); }
|
||||
.aside-note { margin-top:38px; padding-top:20px; border-top:1px solid var(--line); font-size:11px; line-height:1.9; color:var(--secondary); }
|
||||
.index-heading { display:flex; justify-content:space-between; align-items:center; margin-bottom:20px; gap:12px; }
|
||||
.index-heading h2 { font-size:17px; font-weight:500; }
|
||||
.index-heading h2>span { font-size:10px; color:var(--secondary); vertical-align:top; margin-left:9px; font-variant-numeric:tabular-nums; }
|
||||
.sort-form { display:flex; align-items:center; gap:6px; }
|
||||
.sort-form select { color:var(--secondary); border:0; background:transparent; font-size:11px; padding:6px; }
|
||||
.quiet-button { border:1px solid var(--line); background:transparent; padding:5px 8px; border-radius:4px; font-size:11px; color:var(--secondary); }
|
||||
.search-form { display:flex; align-items:center; gap:12px; border:1px solid var(--line); padding:11px 14px; border-radius:5px; color:var(--secondary); }
|
||||
.search-form input { border:0; outline:0; background:transparent; min-width:0; width:100%; color:var(--ink); font-size:12px; }
|
||||
.search-form:focus-within { outline:2px solid var(--accent); outline-offset:2px; }
|
||||
.search-form button { border:0; padding:4px; background:transparent; }
|
||||
.paper-list { list-style:none; margin:10px 0 0; padding:0; }
|
||||
.paper-row { display:grid; grid-template-columns:30px minmax(0,1fr); gap:14px; padding:30px 0; border-bottom:1px solid var(--line); }
|
||||
.row-number { padding-top:3px; color:#939399; font-size:10px; font-variant-numeric:tabular-nums; }
|
||||
.paper-meta { display:flex; flex-wrap:wrap; gap:16px; font-size:10px; color:var(--secondary); margin-bottom:10px; }
|
||||
.paper-meta span:first-child { color:var(--accent); }
|
||||
.paper-row h3 { font-family:var(--display); font-size:21px; line-height:1.38; font-weight:500; letter-spacing:-.4px; }
|
||||
.paper-row h3 a:hover { text-decoration:underline; text-decoration-thickness:1px; text-underline-offset:4px; }
|
||||
.paper-summary { font-family:var(--reading); margin-top:11px; color:var(--secondary); font-size:13px; line-height:1.8; display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden; }
|
||||
.row-bottom { display:flex; justify-content:space-between; gap:20px; margin-top:17px; align-items:center; font-size:10px; }
|
||||
.authors { color:var(--secondary); min-width:0; }
|
||||
.read-link { display:flex; gap:7px; align-items:center; white-space:nowrap; }
|
||||
.read-link:hover { color:var(--accent); }
|
||||
.filter-note { display:flex; justify-content:space-between; font-size:12px; padding-top:20px; color:var(--secondary); }
|
||||
.filter-note a { display:flex; gap:6px; align-items:center; }
|
||||
.pagination { display:flex; justify-content:space-between; padding-top:25px; font-size:12px; color:var(--secondary); }
|
||||
.pagination div { display:flex; gap:25px; }
|
||||
.pagination a { display:inline-flex; align-items:center; gap:8px; color:var(--ink); }
|
||||
.site-footer { display:flex; justify-content:space-between; gap:24px; margin:0 auto; padding:28px 48px; max-width:1200px; border-top:1px solid var(--line); font-size:11px; }
|
||||
.site-footer>span:last-child { color:var(--secondary); }
|
||||
.back-link { display:inline-flex; gap:8px; align-items:center; font-size:12px; color:var(--secondary); margin-top:32px; }
|
||||
.article-header { margin-top:44px; }
|
||||
.article-header h1 { font-family:var(--display); max-width:940px; font-size:clamp(30px,3.8vw,49px); line-height:1.2; letter-spacing:-1.5px; font-weight:450; margin:25px 0; }
|
||||
.article-authors { color:var(--secondary); font-size:14px; line-height:1.8; max-width:850px; }
|
||||
.article-toolbar { border-bottom:1px solid var(--line); padding:24px 0; display:flex; align-items:center; justify-content:space-between; gap:20px; }
|
||||
.article-toolbar>span { font-size:11px; color:var(--secondary); }
|
||||
.article-grid { display:grid; grid-template-columns:120px minmax(0,650px) 140px; gap:44px; padding:48px 0 80px; }
|
||||
.article-toc { display:flex; flex-direction:column; align-self:start; gap:16px; position:sticky; top:30px; font-size:11px; color:var(--secondary); }
|
||||
.article-toc>span { color:var(--ink); margin-bottom:5px; }
|
||||
.article-toc a:hover { color:var(--accent); }
|
||||
.article-body { min-width:0; }
|
||||
.article-body section+section { margin-top:54px; }
|
||||
.article-body h2 { font-size:24px; font-weight:450; font-family:var(--display); margin:15px 0 24px; }
|
||||
.article-body section>p:not(.source-caption):not(.section-description) { font:18px/1.85 var(--reading); margin:0 0 22px; overflow-wrap:anywhere; text-wrap:pretty; }
|
||||
.source-caption { color:var(--secondary); font-size:11px; line-height:1.8; margin-top:22px; }
|
||||
.paper-details { margin:0; }
|
||||
.paper-details>div { display:grid; grid-template-columns:82px minmax(0,1fr); gap:16px; padding:13px 0; border-bottom:1px solid var(--line); font-size:13px; line-height:1.8; }
|
||||
.paper-details dt { color:var(--secondary); font-size:11px; }
|
||||
.paper-details dd { margin:0; overflow-wrap:anywhere; }
|
||||
.paper-details a:hover { text-decoration:underline; }
|
||||
.reading-note { align-self:start; color:var(--secondary); }
|
||||
.tiny-rule { display:block; width:20px; height:2px; background:var(--accent); margin-bottom:18px; }
|
||||
.reading-note h3 { color:var(--ink); font-size:14px; line-height:1.7; font-weight:500; margin-bottom:12px; }
|
||||
.reading-note p { font-size:11px; line-height:1.9; }
|
||||
.section-description { font-size:13px; color:var(--secondary); margin-bottom:20px; }
|
||||
.source-row { display:flex; justify-content:space-between; align-items:center; border-top:1px solid var(--line); border-bottom:1px solid var(--line); padding:19px 0; }
|
||||
.source-row div { display:flex; flex-direction:column; gap:5px; }
|
||||
.source-row strong { font-size:15px; font-weight:500; }
|
||||
.source-row span { color:var(--secondary); font-size:11px; }
|
||||
.source-row:hover { color:var(--accent); }
|
||||
.empty-state { padding-top:100px; padding-bottom:140px; text-align:center; }
|
||||
.empty-state h1,.empty-state h3 { font-weight:450; font-size:28px; margin:16px 0; }
|
||||
.empty-state p { color:var(--secondary); margin:16px auto 25px; max-width:520px; line-height:1.8; }
|
||||
.auth-page { min-height:calc(100dvh - 160px); display:grid; place-items:center; padding:65px 24px 90px; }
|
||||
.auth-box { width:100%; max-width:390px; }
|
||||
.auth-box h1 { font-size:32px; letter-spacing:-1px; font-weight:450; margin-top:24px; }
|
||||
.auth-description { color:var(--secondary); font-size:13px; line-height:1.8; margin:14px 0 30px; }
|
||||
.social-row { display:grid; grid-template-columns:1fr 1fr; gap:12px; }
|
||||
.provider-mark { font-size:17px; font-weight:650; }
|
||||
.auth-divider { display:flex; align-items:center; gap:15px; font-size:11px; color:var(--secondary); margin:26px 0; }
|
||||
.auth-divider:before,.auth-divider:after { content:""; height:1px; background:var(--line); flex:1; }
|
||||
.auth-box form>label,.password-label label { display:block; font-size:12px; margin-bottom:8px; }
|
||||
.auth-box input { width:100%; padding:11px 12px; border:1px solid var(--line); border-radius:5px; color:var(--ink); background:transparent; font-size:14px; }
|
||||
.password-label { display:flex; justify-content:space-between; align-items:center; margin-top:20px; }
|
||||
.password-label small { color:var(--secondary); font-size:10px; margin-bottom:8px; }
|
||||
.password-field { position:relative; }
|
||||
.password-field input { padding-right:42px; }
|
||||
.password-field button { position:absolute; right:8px; top:6px; padding:7px; border:0; background:transparent; color:var(--secondary); }
|
||||
.form-error,.form-message { font-size:12px; line-height:1.8; margin-top:14px; }
|
||||
.form-error { color:#a33024; }
|
||||
.form-message { color:#38614b; }
|
||||
.auth-switch { text-align:center; font-size:12px; margin-top:24px; color:var(--secondary); }
|
||||
.auth-switch button { background:transparent; border:0; color:var(--ink); text-decoration:underline; text-underline-offset:4px; }
|
||||
.auth-trust { color:var(--secondary); font-size:11px; line-height:1.8; margin:30px 0 25px; }
|
||||
.about-page { max-width:800px; padding-top:75px; padding-bottom:90px; }
|
||||
.about-page h1 { font-size:44px; font-weight:450; line-height:1.3; margin:25px 0; }
|
||||
.about-page h2 { font-size:21px; font-weight:500; margin:40px 0 15px; }
|
||||
.about-page p { color:var(--secondary); font:17px/1.9 var(--reading); }
|
||||
:root[data-theme="editorial"] .paper-row h3 { font-size:23px; }
|
||||
:root[data-theme="tufte"] .article-body section>p:not(.source-caption):not(.section-description) { font-size:18px; line-height:1.8; }
|
||||
:root[data-theme="tufte"] .reading-note { font-family:var(--reading); }
|
||||
@media(min-width:1500px) { .page-shell,.site-footer { max-width:1280px; } .index-intro { padding-top:65px; } }
|
||||
@media(max-width:1000px) { .header-inner { padding:0 30px; gap:32px; } .page-shell { padding:0 30px; } .brand small { display:none; } .index-grid { grid-template-columns:140px minmax(0,1fr); gap:36px; } .article-grid { grid-template-columns:100px minmax(0,1fr); gap:30px; } .reading-note { display:none; } .site-footer { padding:25px 30px; } .about-page { padding-top:60px; padding-bottom:70px; } }
|
||||
@media(max-width:640px) { .header-inner { height:68px; padding:0 20px; gap:20px; } .brand>span { font-size:23px; } .header-inner nav { display:none; } .header-actions { gap:10px; } .theme-label select { max-width:90px; font-size:11px; } .account-name { display:none; } .page-shell { padding:0 20px; } .index-intro { padding:35px 0 28px; } .index-intro h1 { font-size:43px; margin:28px 0 24px; letter-spacing:-1.5px; } .section-kicker { font-size:9px; gap:12px; letter-spacing:1px; } .intro-bottom { align-items:flex-start; gap:18px; } .intro-bottom p { font-size:12px; } .intro-bottom a { font-size:11px; white-space:nowrap; margin-top:5px; } .sample-note { align-items:flex-start; font-size:10px; padding:12px 0; } .status-dot { margin-top:6px; } .sample-note a { font-size:10px; } .index-grid { display:block; padding-top:26px; padding-bottom:45px; } .index-aside { display:flex; gap:8px; flex-wrap:wrap; margin-bottom:28px; } .index-aside h2,.aside-note { display:none; } .topic { padding:7px 10px; border:1px solid var(--line); font-size:10px; } .topic span { display:none; } .topic.active { border-color:var(--ink); } .paper-row { gap:7px; grid-template-columns:22px minmax(0,1fr); padding:25px 0; } .paper-row h3 { font-size:19px; } .paper-meta { font-size:9px; gap:12px; } .paper-summary { font-size:12px; } .row-bottom { flex-wrap:wrap; gap:10px; } .authors { flex-basis:100%; } .site-footer { padding:24px 20px; font-size:10px; } .site-footer>span:last-child { display:none; } .article-header { margin-top:32px; } .article-header h1 { font-size:32px; letter-spacing:-.9px; overflow-wrap:anywhere; } .article-authors { font-size:12px; } .article-toolbar { align-items:flex-start; flex-direction:column; gap:14px; padding:20px 0; } .article-grid { display:block; padding:30px 0 60px; } .article-toc { display:none; } .article-body section>p:not(.source-caption):not(.section-description) { font-size:17px; line-height:1.8; } .paper-details>div { grid-template-columns:70px minmax(0,1fr); gap:12px; } .about-page { padding-top:45px; padding-bottom:60px; } .about-page h1 { font-size:36px; } .about-page p { font-size:16px; } .auth-page { padding-top:50px; } }
|
||||
@media(prefers-reduced-motion:reduce) { html { scroll-behavior:auto; } *,*:before,*:after { animation:none!important; transition:none!important; } }
|
||||
|
||||
.search-form select { border:0; background:transparent; color:var(--secondary); font-size:11px; max-width:100px; padding:3px 0; }
|
||||
@@ -0,0 +1,8 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Header } from "@/components/header";
|
||||
import { Providers } from "@/components/providers";
|
||||
import "./globals.css";
|
||||
export const metadata: Metadata = { title: { default: "Thesisly 研迹 — Research in context", template: "%s · Thesisly 研迹" }, description: "发现论文,阅读摘要,回到研究的原始出处。", robots: { index: false, follow: false } };
|
||||
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
return <html lang="zh-CN" data-theme="vignelli" suppressHydrationWarning><body><a className="skip-link" href="#main">跳到正文</a><Providers><Header />{children}<footer className="site-footer"><span>Thesisly 研迹. <span className="text-muted-foreground">读到研究的本来面目。</span></span><span>Research, in context.</span></footer></Providers></body></html>;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export default function Loading() { return <main id="main" className="page-shell py-24" aria-busy="true"><p role="status">正在读取论文索引…</p><div className="mt-10 h-32 border-y border-border bg-muted" /></main>; }
|
||||
@@ -0,0 +1,15 @@
|
||||
"use client";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useState, type FormEvent } from "react";
|
||||
import { Eye, EyeOff, ArrowRight, LoaderCircle } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { api, startOAuth, errorText } from "@/lib/auth-client";
|
||||
import { useSession } from "@/components/providers";
|
||||
export default function Login() {
|
||||
const [register,setRegister]=useState(false), [email,setEmail]=useState(""), [password,setPassword]=useState(""), [show,setShow]=useState(false), [busy,setBusy]=useState(false), [error,setError]=useState(""), [message,setMessage]=useState("");
|
||||
const router=useRouter(); const {reload}=useSession();
|
||||
async function submit(e:FormEvent) { e.preventDefault(); setBusy(true);setError("");setMessage("");try { await api(`auth/${register?"register":"login"}`,{method:"POST",body:JSON.stringify({email,password})}); if(register) setMessage("验证邮件已请求发送,请检查邮箱并点击验证链接。");else {await reload();router.replace("/");} }catch(e){setError(errorText(e));}finally{setBusy(false);} }
|
||||
async function oauth(p:"google"|"slack") {setBusy(true);setError("");try{await startOAuth(p);}catch(e){setError(errorText(e));setBusy(false);} }
|
||||
return <main id="main" className="auth-page"><div className="auth-box"><div className="section-kicker">YOUR RESEARCH, CONTINUED</div><h1>{register?"创建账户":"继续你的阅读。"}</h1><p className="auth-description">{register?"一个账户,连接你的研究与阅读。":"登录你的账户,回到感兴趣的研究。"}</p><div className="social-row"><Button variant="outline" disabled={busy} onClick={()=>void oauth("google")}>Google</Button><Button variant="outline" disabled={busy} onClick={()=>void oauth("slack")}>Slack</Button></div><div className="auth-divider">或使用邮箱</div><form onSubmit={submit}><label htmlFor="email">邮箱地址</label><input id="email" autoComplete="email" type="email" required value={email} onChange={e=>setEmail(e.target.value)} placeholder="you@university.edu" /><div className="password-label"><label htmlFor="password">密码</label>{register&&<small>12–72 个字符</small>}</div><div className="password-field"><input id="password" type={show?"text":"password"} autoComplete={register?"new-password":"current-password"} required minLength={register?12:1} maxLength={72} value={password} onChange={e=>setPassword(e.target.value)} aria-describedby={error?"auth-error":undefined} /><button type="button" aria-label={show?"隐藏密码":"显示密码"} onClick={()=>setShow(!show)}>{show?<EyeOff size={17}/>:<Eye size={17}/>}</button></div>{error&&<p id="auth-error" role="alert" className="form-error">{error}</p>}{message&&<p role="status" className="form-message">{message}</p>}<Button className="w-full mt-6" type="submit" disabled={busy}>{busy?<><LoaderCircle className="animate-spin"/>处理中…</>:<>{register?"创建账户":"登录"}<ArrowRight/></>}</Button></form><p className="auth-switch">{register?"已有账户?":"第一次来?"} <button onClick={()=>{setRegister(!register);setError("");setMessage("");}}>{register?"登录":"创建账户"}</button></p><p className="auth-trust">账户由 Foundry Core 管理。首次邮箱注册需要验证;Google 或 Slack 首次授权将按后端规则创建账户。</p><Link className="text-link" href="/">先浏览论文 →</Link></div></main>;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
import Link from "next/link";
|
||||
export default function NotFound() { return <main id="main" className="page-shell empty-state"><span className="section-kicker">404</span><h1>这篇论文不在当前索引里。</h1><p>如果正在使用样本模式,只有导出的 20 条论文可以打开。</p><Link className="text-link" href="/">返回论文索引 →</Link></main>; }
|
||||
@@ -0,0 +1,16 @@
|
||||
import Link from "next/link";
|
||||
import { ArrowRight, ArrowUpRight, Search, X } from "lucide-react";
|
||||
import { listPapers, normalizeFilters, paperDate, type Filters } from "@/lib/papers";
|
||||
import { Button } from "@/components/ui/button";
|
||||
export default async function Home({ searchParams }: { searchParams: Promise<Record<string,string | string[] | undefined>> }) {
|
||||
const f = normalizeFilters(await searchParams), data = await listPapers(f);
|
||||
const pages = Math.ceil(data.total / data.pageSize);
|
||||
function href(changes: Filters) { return "/?" + new URLSearchParams(Object.entries({ ...f, ...changes }).filter(([, v]) => typeof v === "string" && !!v) as [string, string][]).toString(); }
|
||||
return <main id="main" className="page-shell"><section className="index-intro"><div className="section-kicker">THE OPEN RESEARCH READER <span>01 — INDEX</span></div><h1>好研究,<br /><span className="muted-heading">值得被读到。</span></h1><div className="intro-bottom"><p>从一个问题,到一篇论文。<br />发现研究、阅读摘要,回到每一项工作的原始出处。</p><a href="#papers" className="text-link">浏览论文 <ArrowRight size={16} /></a></div></section>
|
||||
{data.mode === "sample" && <div className="sample-note" role="note"><span className="status-dot" /><p>真实数据库样本 · 当前展示 2026-11-06 导出的前 20 条记录,搜索与筛选仅限此样本。</p><Link href="/about">数据说明 <ArrowUpRight size={13} /></Link></div>}
|
||||
<section id="papers" className="index-grid"><aside className="index-aside"><h2>研究领域</h2><Link className={!f.topic ? "topic active" : "topic"} href={href({ topic: "", page: "1" })}>全部领域 <span>↗</span></Link>{data.topics.map(t => <Link key={t} className={f.topic === t ? "topic active" : "topic"} href={href({ topic: t, page: "1" })}>{t}</Link>)}<p className="aside-note">保留原始标题与摘要。<br />不把自动生成的解读当成论文结论。</p></aside><div className="paper-index"><div className="index-heading"><h2>{f.topic || "论文索引"}<span>{data.total.toString().padStart(2, "0")}</span></h2><form className="sort-form"><input type="hidden" name="q" value={f.q || ""} /><input type="hidden" name="topic" value={f.topic || ""} /><input type="hidden" name="searchIn" value={f.searchIn || "text"} /><label htmlFor="sort" className="sr-only">排序</label><select id="sort" name="sort" defaultValue={f.sort || "recent"}><option value="recent">收录顺序</option><option value="title">标题 A–Z</option></select><button type="submit" className="quiet-button">排序</button></form></div>
|
||||
<form className="search-form" role="search"><Search size={18} aria-hidden /><select name="searchIn" aria-label="搜索范围" defaultValue={f.searchIn || "text"}><option value="text">标题 / 摘要</option><option value="author">作者</option></select><label htmlFor="paper-search" className="sr-only">搜索论文</label><input id="paper-search" name="q" placeholder={f.searchIn === "author" ? "搜索作者姓名…" : "搜索标题或摘要…"} defaultValue={f.q || ""} maxLength={200} /><input type="hidden" name="topic" value={f.topic || ""} /><input type="hidden" name="sort" value={f.sort || "recent"} /><button type="submit" aria-label="搜索论文"><ArrowRight size={18} /></button></form>
|
||||
{(f.q || f.topic) && <div className="filter-note"><span>{f.q ? `搜索 “${f.q}”` : f.topic}</span><Link href="/">清除筛选 <X size={13} /></Link></div>}
|
||||
{data.items.length ? <ol className="paper-list">{data.items.map((p, i) => <li key={p.id}><article className="paper-row"><span className="row-number">{String((data.page - 1) * data.pageSize + i + 1).padStart(2, "0")}</span><div><div className="paper-meta"><span>{p.topics[0] || "Research"}</span><span>{paperDate(p.publishedAt)}</span></div><h3><Link href={`/papers/${p.id}`}>{p.title}</Link></h3><p className="paper-summary">{p.abstract || "来源未提供摘要。"}</p><div className="row-bottom"><span className="authors">{p.authors.slice(0, 3).join(", ")}{p.authors.length > 3 ? ` +${p.authors.length - 3}` : ""}</span><Link href={`/papers/${p.id}`} className="read-link">阅读简介 <ArrowUpRight size={15} /></Link></div></div></article></li>)}</ol> : <div className="empty-state"><h3>没有找到符合条件的论文</h3><p>{data.mode === "sample" ? "当前只有 20 条样本,试试更短的关键词,或清除领域筛选。" : "试试其他关键词,或清除筛选。"}</p><Button asChild variant="outline"><Link href="/">查看全部论文</Link></Button></div>}
|
||||
<nav className="pagination" aria-label="论文分页"><span>{data.total ? `${data.page} / ${Math.max(1,pages)}` : "0 条结果"}</span><div>{data.page > 1 && <Link href={href({ page: String(data.page - 1) })}>上一页</Link>}{data.page < pages && <Link href={href({ page: String(data.page + 1) })}>下一页 <ArrowRight size={14} /></Link>}</div></nav></div></section></main>;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { ArrowLeft, ArrowUpRight } from "lucide-react";
|
||||
import { getPaper, paperDate, safeExternalUrl } from "@/lib/papers";
|
||||
import { PaperActions } from "@/components/paper-actions";
|
||||
export async function generateMetadata({ params }: { params: Promise<{ id: string }> }) { const p = await getPaper((await params).id); return { title: p?.title || "论文未找到", description: p?.abstract?.slice(0, 150) }; }
|
||||
export default async function PaperPage({ params }: { params: Promise<{ id: string }> }) {
|
||||
const p = await getPaper((await params).id); if (!p) notFound();
|
||||
const citation = `${p.authors.join(", ")}. ${p.title}. ${paperDate(p.publishedAt)}. ${p.sources[0]?.url || ""}`;
|
||||
return <main id="main" className="page-shell paper-page"><Link href="/" className="back-link"><ArrowLeft size={15} />返回论文索引</Link><header className="article-header"><div className="section-kicker">PAPER / {p.id.padStart(4, "0")}<span>{p.topics.join(" · ")}</span></div><h1 lang="en">{p.title}</h1><p className="article-authors">{p.authors.join(" · ")}</p><div className="article-toolbar"><span>{paperDate(p.publishedAt)}{!process.env.PAPER_API_ORIGIN ? " · 数据库样本" : ""}</span><PaperActions citation={citation} /></div></header><div className="article-grid"><aside className="article-toc"><span>本文内容</span><a href="#abstract">摘要</a><a href="#details">论文信息</a><a href="#sources">原始出处</a></aside><article className="article-body"><section id="abstract"><div className="section-kicker">01 / ABSTRACT</div><h2>摘要</h2>{p.abstract ? p.abstract.split(/\n\s*\n/).map((s, i) => <p key={i} lang="en">{s.replace(/\n/g," ")}</p>) : <p>当前来源尚未提供摘要。可通过下方链接查看原文。</p>}<p className="source-caption">以上为数据库保存的原始摘要,未进行 AI 改写。</p></section><section id="details"><div className="section-kicker">02 / DETAILS</div><h2>论文信息</h2><dl className="paper-details"><div><dt>作者</dt><dd>{p.authors.join(", ")}</dd></div><div><dt>首次发表</dt><dd>{paperDate(p.publishedAt)}</dd></div><div><dt>领域</dt><dd>{p.topics.join(" / ") || "来源未标注"}</dd></div>{p.publications?.map((v,i)=><div key={i}><dt>会议 / 投稿</dt><dd>{v.venue} {v.year} · {v.track} · {({accepted:"已录用",rejected:"未录用",desk_rejected:"编辑拒稿",withdrawn:"已撤稿",under_review:"审稿中",unknown:"状态未知"} as Record<string,string>)[v.status] || v.status}</dd></div>)}{p.journal && <div><dt>发表信息</dt><dd>{p.journal}</dd></div>}{p.doi && <div><dt>DOI</dt><dd><a href={`https://doi.org/${encodeURIComponent(p.doi)}`} target="_blank" rel="noopener noreferrer">{p.doi} ↗</a></dd></div>}{p.comments && <div><dt>作者备注</dt><dd>{p.comments}</dd></div>}</dl></section><section id="sources"><div className="section-kicker">03 / SOURCES</div><h2>回到原始研究</h2><p className="section-description">查看论文的完整论证、方法与实验。</p>{p.sources.map(s => { const url = safeExternalUrl(s.url); return url ? <a className="source-row" href={url} key={url} target="_blank" rel="noopener noreferrer"><div><strong>{s.label}</strong><span>{s.externalId}</span></div><ArrowUpRight size={20} /></a> : null; })}{!p.sources.length && <p>当前记录没有可用的原始链接。</p>}<p className="source-caption">代码仓库、数据集和复现结果将在有来源证据时展示。</p></section></article><aside className="reading-note"><span className="tiny-rule" /><h3>先读摘要,<br />再回到原文。</h3><p>这是一篇研究的入口。方法的边界与结论的条件,都需要在原文中核对。</p></aside></div></main>;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
"use client";
|
||||
import Link from "next/link";
|
||||
import { useEffect, useState } from "react";
|
||||
import { ArrowUpRight, BookOpen, LogOut } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useSession } from "./providers";
|
||||
export function Header() {
|
||||
const [theme, setTheme] = useState("vignelli");
|
||||
const { user, loading, failed, reload, logout } = useSession();
|
||||
useEffect(() => { try { const t = localStorage.getItem("reading-theme"); if (t && ["vignelli", "tufte", "editorial"].includes(t)) { setTheme(t); document.documentElement.dataset.theme = t; } } catch {} }, []);
|
||||
function changeTheme(t: string) { setTheme(t); document.documentElement.dataset.theme = t; try { localStorage.setItem("reading-theme", t); } catch {} }
|
||||
return <header className="site-header"><div className="header-inner"><Link className="brand" href="/" aria-label="研究索引首页"><BookOpen strokeWidth={1.5} size={23} /><span>Thesisly<span className="brand-dot">.</span></span><small>研迹 / RESEARCH INDEX</small></Link><nav aria-label="主导航"><Link href="/">论文</Link><Link href="/about">关于</Link></nav><div className="header-actions"><label className="theme-label"><span className="sr-only">阅读主题</span><select aria-label="阅读主题" value={theme} onChange={e => changeTheme(e.target.value)}><option value="vignelli">Vignelli</option><option value="editorial">Editorial</option><option value="tufte">Tufte</option></select></label>{loading ? <span className="text-sm text-muted-foreground">检查登录…</span> : failed ? <Button variant="ghost" size="sm" onClick={() => void reload()}>重试账户</Button> : user ? <><span className="account-name">{user.display_name || user.username || user.email || "已登录"}</span><Button variant="ghost" size="icon" aria-label="退出登录" onClick={() => void logout()}><LogOut /></Button></> : <Button asChild size="sm"><Link href="/login">登录 <ArrowUpRight /></Link></Button>}</div></div></header>;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
"use client";
|
||||
import { Copy, Link as LinkIcon } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import { Button } from "@/components/ui/button";
|
||||
export function PaperActions({ citation }: { citation: string }) {
|
||||
async function copy(text: string, label: string) { try { await navigator.clipboard.writeText(text); toast.success(label); } catch { toast.error("复制失败,请手动选择文本复制。"); } }
|
||||
return <div className="flex flex-wrap gap-2"><Button variant="outline" size="sm" onClick={() => void copy(citation, "已复制引用信息")}><Copy />复制引用</Button><Button variant="ghost" size="sm" onClick={() => void copy(location.href, "已复制页面链接")}><LinkIcon />分享</Button></div>;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
"use client";
|
||||
import { createContext, useCallback, useContext, useEffect, useRef, useState } from "react";
|
||||
import { Toaster, toast } from "sonner";
|
||||
import { api } from "@/lib/auth-client";
|
||||
type User = { email?: string | null; display_name?: string | null; username?: string | null; id: string };
|
||||
const Session = createContext<{ user: User | null; loading: boolean; failed: boolean; reload: () => Promise<void>; logout: () => Promise<void> }>({ user: null, loading: true, failed: false, reload: async () => {}, logout: async () => {} });
|
||||
export function useSession() { return useContext(Session); }
|
||||
export function Providers({ children }: { children: React.ReactNode }) {
|
||||
const [user, setUser] = useState<User | null>(null), [loading, setLoading] = useState(true), [failed, setFailed] = useState(false);
|
||||
const request = useRef(0), active = useRef<AbortController | null>(null);
|
||||
const reload = useCallback(async () => {
|
||||
const sequence = ++request.current;
|
||||
active.current?.abort();
|
||||
const controller = new AbortController();
|
||||
active.current = controller;
|
||||
setFailed(false);
|
||||
try {
|
||||
const d = await api<User>("auth/me", { signal: controller.signal });
|
||||
if (!d || typeof d.id !== "string") throw new Error("invalid_api_response");
|
||||
if (sequence === request.current) setUser(d);
|
||||
} catch (e) {
|
||||
if (sequence !== request.current || controller.signal.aborted) return;
|
||||
if (e instanceof Error && e.message === "invalid_session") setUser(null);
|
||||
else setFailed(true);
|
||||
} finally { if (sequence === request.current) setLoading(false); }
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
void reload();
|
||||
const focus = () => void reload();
|
||||
const change = (event: StorageEvent) => { if (event.key === "thesisly-session-change") void reload(); };
|
||||
window.addEventListener("focus", focus); window.addEventListener("storage", change);
|
||||
return () => { active.current?.abort(); ++request.current; window.removeEventListener("focus", focus); window.removeEventListener("storage", change); };
|
||||
}, [reload]);
|
||||
async function logout() {
|
||||
try {
|
||||
await api("auth/logout", { method: "POST" });
|
||||
++request.current; active.current?.abort(); setUser(null); setFailed(false); setLoading(false);
|
||||
try { localStorage.setItem("thesisly-session-change", String(Date.now())); } catch {}
|
||||
toast.success("已退出登录");
|
||||
} catch { toast.error("退出失败,请重试"); }
|
||||
}
|
||||
return <Session.Provider value={{ user, loading, failed, reload, logout }}>{children}<Toaster position="bottom-right" /></Session.Provider>;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import * as React from "react";
|
||||
import { Slot } from "@radix-ui/react-slot";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const buttonVariants = cva("inline-flex items-center justify-center gap-2 whitespace-nowrap text-sm font-medium transition-colors focus-visible:outline-2 focus-visible:outline-offset-4 disabled:pointer-events-none disabled:opacity-50 [&_svg]:size-4", {
|
||||
variants: { variant: { default: "bg-foreground text-background hover:opacity-85", outline: "border border-border bg-background hover:bg-muted", ghost: "hover:bg-muted" }, size: { default: "h-10 px-4 py-2 rounded-md", sm: "h-9 px-3 rounded-md", icon: "size-10 rounded-md" } }, defaultVariants: { variant: "default", size: "default" },
|
||||
});
|
||||
function Button({ className, variant, size, asChild = false, ...props }: React.ComponentProps<"button"> & VariantProps<typeof buttonVariants> & { asChild?: boolean }) {
|
||||
const Comp = asChild ? Slot : "button";
|
||||
return <Comp data-slot="button" className={cn(buttonVariants({ variant, size, className }))} {...props} />;
|
||||
}
|
||||
export { Button, buttonVariants };
|
||||
@@ -0,0 +1,475 @@
|
||||
{
|
||||
"capturedAt": "2026-11-06T13:58:30.251508+00:00",
|
||||
"papers": [
|
||||
{
|
||||
"id": "1",
|
||||
"title": "Calculation of prompt diphoton production cross sections at Tevatron and LHC energies",
|
||||
"abstract": "A fully differential calculation in perturbative quantum chromodynamics is\npresented for the production of massive photon pairs at hadron colliders. All\nnext-to-leading order perturbative contributions from quark-antiquark,\ngluon-(anti)quark, and gluon-gluon subprocesses are included, as well as\nall-orders resummation of initial-state gluon radiation valid at\nnext-to-next-to-leading logarithmic accuracy. The region of phase space is\nspecified in which the calculation is most reliable. Good agreement is\ndemonstrated with data from the Fermilab Tevatron, and predictions are made for\nmore detailed tests with CDF and DO data. Predictions are shown for\ndistributions of diphoton pairs produced at the energy of the Large Hadron\nCollider (LHC). Distributions of the diphoton pairs from the decay of a Higgs\nboson are contrasted with those produced from QCD processes at the LHC, showing\nthat enhanced sensitivity to the signal can be obtained with judicious\nselection of events.",
|
||||
"authors": [
|
||||
"C. Balázs",
|
||||
"E. L. Berger",
|
||||
"P. M. Nadolsky",
|
||||
"C. -P. Yuan"
|
||||
],
|
||||
"publishedAt": "Mon, 2 Apr 2007 19:18:42 GMT",
|
||||
"topics": [
|
||||
"hep-ph"
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"label": "arXiv",
|
||||
"url": "https://arxiv.org/abs/0704.0001",
|
||||
"externalId": "0704.0001"
|
||||
}
|
||||
],
|
||||
"doi": "10.1103/PhysRevD.76.013009",
|
||||
"journal": "Phys.Rev.D76:013009,2007",
|
||||
"comments": "37 pages, 15 figures; published version"
|
||||
},
|
||||
{
|
||||
"id": "2",
|
||||
"title": "Sparsity-certifying Graph Decompositions",
|
||||
"abstract": "We describe a new algorithm, the $(k,\\ell)$-pebble game with colors, and use\nit obtain a characterization of the family of $(k,\\ell)$-sparse graphs and\nalgorithmic solutions to a family of problems concerning tree decompositions of\ngraphs. Special instances of sparse graphs appear in rigidity theory and have\nreceived increased attention in recent years. In particular, our colored\npebbles generalize and strengthen the previous results of Lee and Streinu and\ngive a new proof of the Tutte-Nash-Williams characterization of arboricity. We\nalso present a new decomposition that certifies sparsity based on the\n$(k,\\ell)$-pebble game with colors. Our work also exposes connections between\npebble game algorithms and previous sparse graph algorithms by Gabow, Gabow and\nWestermann and Hendrickson.",
|
||||
"authors": [
|
||||
"Ileana Streinu",
|
||||
"Louis Theran"
|
||||
],
|
||||
"publishedAt": "Sat, 31 Mar 2007 02:26:18 GMT",
|
||||
"topics": [
|
||||
"math.CO",
|
||||
"cs.CG"
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"label": "arXiv",
|
||||
"url": "https://arxiv.org/abs/0704.0002",
|
||||
"externalId": "0704.0002"
|
||||
}
|
||||
],
|
||||
"doi": null,
|
||||
"journal": null,
|
||||
"comments": "To appear in Graphs and Combinatorics"
|
||||
},
|
||||
{
|
||||
"id": "3",
|
||||
"title": "The evolution of the Earth-Moon system based on the dark matter field fluid model",
|
||||
"abstract": "The evolution of Earth-Moon system is described by the dark matter field\nfluid model proposed in the Meeting of Division of Particle and Field 2004,\nAmerican Physical Society. The current behavior of the Earth-Moon system agrees\nwith this model very well and the general pattern of the evolution of the\nMoon-Earth system described by this model agrees with geological and fossil\nevidence. The closest distance of the Moon to Earth was about 259000 km at 4.5\nbillion years ago, which is far beyond the Roche's limit. The result suggests\nthat the tidal friction may not be the primary cause for the evolution of the\nEarth-Moon system. The average dark matter field fluid constant derived from\nEarth-Moon system data is 4.39 x 10^(-22) s^(-1)m^(-1). This model predicts\nthat the Mars's rotation is also slowing with the angular acceleration rate\nabout -4.38 x 10^(-22) rad s^(-2).",
|
||||
"authors": [
|
||||
"Hongjun Pan"
|
||||
],
|
||||
"publishedAt": "Sun, 1 Apr 2007 20:46:54 GMT",
|
||||
"topics": [
|
||||
"physics.gen-ph"
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"label": "arXiv",
|
||||
"url": "https://arxiv.org/abs/0704.0003",
|
||||
"externalId": "0704.0003"
|
||||
}
|
||||
],
|
||||
"doi": null,
|
||||
"journal": null,
|
||||
"comments": "23 pages, 3 figures"
|
||||
},
|
||||
{
|
||||
"id": "4",
|
||||
"title": "A determinant of Stirling cycle numbers counts unlabeled acyclic single-source automata",
|
||||
"abstract": "We show that a determinant of Stirling cycle numbers counts unlabeled acyclic\nsingle-source automata. The proof involves a bijection from these automata to\ncertain marked lattice paths and a sign-reversing involution to evaluate the\ndeterminant.",
|
||||
"authors": [
|
||||
"David Callan"
|
||||
],
|
||||
"publishedAt": "Sat, 31 Mar 2007 03:16:14 GMT",
|
||||
"topics": [
|
||||
"math.CO"
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"label": "arXiv",
|
||||
"url": "https://arxiv.org/abs/0704.0004",
|
||||
"externalId": "0704.0004"
|
||||
}
|
||||
],
|
||||
"doi": null,
|
||||
"journal": null,
|
||||
"comments": "11 pages"
|
||||
},
|
||||
{
|
||||
"id": "5",
|
||||
"title": "From dyadic $\\Lambda_{\\alpha}$ to $\\Lambda_{\\alpha}$",
|
||||
"abstract": "In this paper we show how to compute the $\\Lambda_{\\alpha}$ norm, $\\alpha\\ge\n0$, using the dyadic grid. This result is a consequence of the description of\nthe Hardy spaces $H^p(R^N)$ in terms of dyadic and special atoms.",
|
||||
"authors": [
|
||||
"Wael Abu-Shammala",
|
||||
"Alberto Torchinsky"
|
||||
],
|
||||
"publishedAt": "Mon, 2 Apr 2007 18:09:58 GMT",
|
||||
"topics": [
|
||||
"math.CA",
|
||||
"math.FA"
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"label": "arXiv",
|
||||
"url": "https://arxiv.org/abs/0704.0005",
|
||||
"externalId": "0704.0005"
|
||||
}
|
||||
],
|
||||
"doi": null,
|
||||
"journal": "Illinois J. Math. 52 (2008) no.2, 681-689",
|
||||
"comments": null
|
||||
},
|
||||
{
|
||||
"id": "6",
|
||||
"title": "Bosonic characters of atomic Cooper pairs across resonance",
|
||||
"abstract": "We study the two-particle wave function of paired atoms in a Fermi gas with\ntunable interaction strengths controlled by Feshbach resonance. The Cooper pair\nwave function is examined for its bosonic characters, which is quantified by\nthe correction of Bose enhancement factor associated with the creation and\nannihilation composite particle operators. An example is given for a\nthree-dimensional uniform gas. Two definitions of Cooper pair wave function are\nexamined. One of which is chosen to reflect the off-diagonal long range order\n(ODLRO). Another one corresponds to a pair projection of a BCS state. On the\nside with negative scattering length, we found that paired atoms described by\nODLRO are more bosonic than the pair projected definition. It is also found\nthat at $(k_F a)^{-1} \\ge 1$, both definitions give similar results, where more\nthan 90% of the atoms occupy the corresponding molecular condensates.",
|
||||
"authors": [
|
||||
"Y. H. Pong",
|
||||
"C. K. Law"
|
||||
],
|
||||
"publishedAt": "Sat, 31 Mar 2007 04:24:59 GMT",
|
||||
"topics": [
|
||||
"cond-mat.mes-hall"
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"label": "arXiv",
|
||||
"url": "https://arxiv.org/abs/0704.0006",
|
||||
"externalId": "0704.0006"
|
||||
}
|
||||
],
|
||||
"doi": "10.1103/PhysRevA.75.043613",
|
||||
"journal": null,
|
||||
"comments": "6 pages, 4 figures, accepted by PRA"
|
||||
},
|
||||
{
|
||||
"id": "7",
|
||||
"title": "Polymer Quantum Mechanics and its Continuum Limit",
|
||||
"abstract": "A rather non-standard quantum representation of the canonical commutation\nrelations of quantum mechanics systems, known as the polymer representation has\ngained some attention in recent years, due to its possible relation with Planck\nscale physics. In particular, this approach has been followed in a symmetric\nsector of loop quantum gravity known as loop quantum cosmology. Here we explore\ndifferent aspects of the relation between the ordinary Schroedinger theory and\nthe polymer description. The paper has two parts. In the first one, we derive\nthe polymer quantum mechanics starting from the ordinary Schroedinger theory\nand show that the polymer description arises as an appropriate limit. In the\nsecond part we consider the continuum limit of this theory, namely, the reverse\nprocess in which one starts from the discrete theory and tries to recover back\nthe ordinary Schroedinger quantum mechanics. We consider several examples of\ninterest, including the harmonic oscillator, the free particle and a simple\ncosmological model.",
|
||||
"authors": [
|
||||
"Alejandro Corichi",
|
||||
"Tatjana Vukasinac",
|
||||
"Jose A. Zapata"
|
||||
],
|
||||
"publishedAt": "Sat, 31 Mar 2007 04:27:22 GMT",
|
||||
"topics": [
|
||||
"gr-qc"
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"label": "arXiv",
|
||||
"url": "https://arxiv.org/abs/0704.0007",
|
||||
"externalId": "0704.0007"
|
||||
}
|
||||
],
|
||||
"doi": "10.1103/PhysRevD.76.044016",
|
||||
"journal": "Phys.Rev.D76:044016,2007",
|
||||
"comments": "16 pages, no figures. Typos corrected to match published version"
|
||||
},
|
||||
{
|
||||
"id": "8",
|
||||
"title": "Numerical solution of shock and ramp compression for general material properties",
|
||||
"abstract": "A general formulation was developed to represent material models for\napplications in dynamic loading. Numerical methods were devised to calculate\nresponse to shock and ramp compression, and ramp decompression, generalizing\nprevious solutions for scalar equations of state. The numerical methods were\nfound to be flexible and robust, and matched analytic results to a high\naccuracy. The basic ramp and shock solution methods were coupled to solve for\ncomposite deformation paths, such as shock-induced impacts, and shock\ninteractions with a planar interface between different materials. These\ncalculations capture much of the physics of typical material dynamics\nexperiments, without requiring spatially-resolving simulations. Example\ncalculations were made of loading histories in metals, illustrating the effects\nof plastic work on the temperatures induced in quasi-isentropic and\nshock-release experiments, and the effect of a phase transition.",
|
||||
"authors": [
|
||||
"Damian C. Swift"
|
||||
],
|
||||
"publishedAt": "Sat, 31 Mar 2007 04:47:20 GMT",
|
||||
"topics": [
|
||||
"cond-mat.mtrl-sci"
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"label": "arXiv",
|
||||
"url": "https://arxiv.org/abs/0704.0008",
|
||||
"externalId": "0704.0008"
|
||||
}
|
||||
],
|
||||
"doi": "10.1063/1.2975338",
|
||||
"journal": "Journal of Applied Physics, vol 104, 073536 (2008)",
|
||||
"comments": "Minor corrections"
|
||||
},
|
||||
{
|
||||
"id": "9",
|
||||
"title": "The Spitzer c2d Survey of Large, Nearby, Insterstellar Clouds. IX. The Serpens YSO Population As Observed With IRAC and MIPS",
|
||||
"abstract": "We discuss the results from the combined IRAC and MIPS c2d Spitzer Legacy\nobservations of the Serpens star-forming region. In particular we present a set\nof criteria for isolating bona fide young stellar objects, YSO's, from the\nextensive background contamination by extra-galactic objects. We then discuss\nthe properties of the resulting high confidence set of YSO's. We find 235 such\nobjects in the 0.85 deg^2 field that was covered with both IRAC and MIPS. An\nadditional set of 51 lower confidence YSO's outside this area is identified\nfrom the MIPS data combined with 2MASS photometry. We describe two sets of\nresults, color-color diagrams to compare our observed source properties with\nthose of theoretical models for star/disk/envelope systems and our own modeling\nof the subset of our objects that appear to be star+disks. These objects\nexhibit a very wide range of disk properties, from many that can be fit with\nactively accreting disks to some with both passive disks and even possibly\ndebris disks. We find that the luminosity function of YSO's in Serpens extends\ndown to at least a few x .001 Lsun or lower for an assumed distance of 260 pc.\nThe lower limit may be set by our inability to distinguish YSO's from\nextra-galactic sources more than by the lack of YSO's at very low luminosities.\nA spatial clustering analysis shows that the nominally less-evolved YSO's are\nmore highly clustered than the later stages and that the background\nextra-galactic population can be fit by the same two-point correlation function\nas seen in other extra-galactic studies. We also present a table of matches\nbetween several previous infrared and X-ray studies of the Serpens YSO\npopulation and our Spitzer data set.",
|
||||
"authors": [
|
||||
"Paul Harvey",
|
||||
"Bruno Merin",
|
||||
"Tracy L. Huard",
|
||||
"Luisa M. Rebull",
|
||||
"Nicholas Chapman",
|
||||
"Neal J. Evans II",
|
||||
"Philip C. Myers"
|
||||
],
|
||||
"publishedAt": "Mon, 2 Apr 2007 19:41:34 GMT",
|
||||
"topics": [
|
||||
"astro-ph"
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"label": "arXiv",
|
||||
"url": "https://arxiv.org/abs/0704.0009",
|
||||
"externalId": "0704.0009"
|
||||
}
|
||||
],
|
||||
"doi": "10.1086/518646",
|
||||
"journal": "Astrophys.J.663:1149-1173,2007",
|
||||
"comments": null
|
||||
},
|
||||
{
|
||||
"id": "10",
|
||||
"title": "Partial cubes: structures, characterizations, and constructions",
|
||||
"abstract": "Partial cubes are isometric subgraphs of hypercubes. Structures on a graph\ndefined by means of semicubes, and Djokovi\\'{c}'s and Winkler's relations play\nan important role in the theory of partial cubes. These structures are employed\nin the paper to characterize bipartite graphs and partial cubes of arbitrary\ndimension. New characterizations are established and new proofs of some known\nresults are given.\n The operations of Cartesian product and pasting, and expansion and\ncontraction processes are utilized in the paper to construct new partial cubes\nfrom old ones. In particular, the isometric and lattice dimensions of finite\npartial cubes obtained by means of these operations are calculated.",
|
||||
"authors": [
|
||||
"Sergei Ovchinnikov"
|
||||
],
|
||||
"publishedAt": "Sat, 31 Mar 2007 05:10:16 GMT",
|
||||
"topics": [
|
||||
"math.CO"
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"label": "arXiv",
|
||||
"url": "https://arxiv.org/abs/0704.0010",
|
||||
"externalId": "0704.0010"
|
||||
}
|
||||
],
|
||||
"doi": null,
|
||||
"journal": null,
|
||||
"comments": "36 pages, 17 figures"
|
||||
},
|
||||
{
|
||||
"id": "11",
|
||||
"title": "Computing genus 2 Hilbert-Siegel modular forms over $\\Q(\\sqrt{5})$ via the Jacquet-Langlands correspondence",
|
||||
"abstract": "In this paper we present an algorithm for computing Hecke eigensystems of\nHilbert-Siegel cusp forms over real quadratic fields of narrow class number\none. We give some illustrative examples using the quadratic field\n$\\Q(\\sqrt{5})$. In those examples, we identify Hilbert-Siegel eigenforms that\nare possible lifts from Hilbert eigenforms.",
|
||||
"authors": [
|
||||
"Clifton Cunningham",
|
||||
"Lassina Dembele"
|
||||
],
|
||||
"publishedAt": "Sat, 31 Mar 2007 05:32:49 GMT",
|
||||
"topics": [
|
||||
"math.NT",
|
||||
"math.AG"
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"label": "arXiv",
|
||||
"url": "https://arxiv.org/abs/0704.0011",
|
||||
"externalId": "0704.0011"
|
||||
}
|
||||
],
|
||||
"doi": null,
|
||||
"journal": null,
|
||||
"comments": "14 pages; title changed; to appear in Experimental Mathematics"
|
||||
},
|
||||
{
|
||||
"id": "12",
|
||||
"title": "Distribution of integral Fourier Coefficients of a Modular Form of Half Integral Weight Modulo Primes",
|
||||
"abstract": "Recently, Bruinier and Ono classified cusp forms $f(z) := \\sum_{n=0}^{\\infty}\na_f(n)q ^n \\in S_{\\lambda+1/2}(\\Gamma_0(N),\\chi)\\cap \\mathbb{Z}[[q]]$ that does\nnot satisfy a certain distribution property for modulo odd primes $p$. In this\npaper, using Rankin-Cohen Bracket, we extend this result to modular forms of\nhalf integral weight for primes $p \\geq 5$. As applications of our main theorem\nwe derive distribution properties, for modulo primes $p\\geq5$, of traces of\nsingular moduli and Hurwitz class number. We also study an analogue of Newman's\nconjecture for overpartitions.",
|
||||
"authors": [
|
||||
"Dohoon Choi"
|
||||
],
|
||||
"publishedAt": "Sat, 31 Mar 2007 05:48:51 GMT",
|
||||
"topics": [
|
||||
"math.NT"
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"label": "arXiv",
|
||||
"url": "https://arxiv.org/abs/0704.0012",
|
||||
"externalId": "0704.0012"
|
||||
}
|
||||
],
|
||||
"doi": null,
|
||||
"journal": null,
|
||||
"comments": null
|
||||
},
|
||||
{
|
||||
"id": "13",
|
||||
"title": "$p$-adic Limit of Weakly Holomorphic Modular Forms of Half Integral Weight",
|
||||
"abstract": "Serre obtained the p-adic limit of the integral Fourier coefficient of\nmodular forms on $SL_2(\\mathbb{Z})$ for $p=2,3,5,7$. In this paper, we extend\nthe result of Serre to weakly holomorphic modular forms of half integral weight\non $\\Gamma_{0}(4N)$ for $N=1,2,4$. A proof is based on linear relations among\nFourier coefficients of modular forms of half integral weight. As applications\nwe obtain congruences of Borcherds exponents, congruences of quotient of\nEisentein series and congruences of values of $L$-functions at a certain point\nare also studied. Furthermore, the congruences of the Fourier coefficients of\nSiegel modular forms on Maass Space are obtained using Ikeda lifting.",
|
||||
"authors": [
|
||||
"Dohoon Choi",
|
||||
"YoungJu Choie"
|
||||
],
|
||||
"publishedAt": "Sat, 31 Mar 2007 06:21:49 GMT",
|
||||
"topics": [
|
||||
"math.NT"
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"label": "arXiv",
|
||||
"url": "https://arxiv.org/abs/0704.0013",
|
||||
"externalId": "0704.0013"
|
||||
}
|
||||
],
|
||||
"doi": null,
|
||||
"journal": null,
|
||||
"comments": null
|
||||
},
|
||||
{
|
||||
"id": "14",
|
||||
"title": "Iterated integral and the loop product",
|
||||
"abstract": "In this article we discuss a relation between the string topology and\ndifferential forms based on the theory of Chen's iterated integrals and the\ncyclic bar complex.",
|
||||
"authors": [
|
||||
"Koichi Fujii"
|
||||
],
|
||||
"publishedAt": "Sun, 1 Apr 2007 12:04:13 GMT",
|
||||
"topics": [
|
||||
"math.CA",
|
||||
"math.AT"
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"label": "arXiv",
|
||||
"url": "https://arxiv.org/abs/0704.0014",
|
||||
"externalId": "0704.0014"
|
||||
}
|
||||
],
|
||||
"doi": null,
|
||||
"journal": null,
|
||||
"comments": "18 pages, 1 figure"
|
||||
},
|
||||
{
|
||||
"id": "15",
|
||||
"title": "Fermionic superstring loop amplitudes in the pure spinor formalism",
|
||||
"abstract": "The pure spinor formulation of the ten-dimensional superstring leads to\nmanifestly supersymmetric loop amplitudes, expressed as integrals in pure\nspinor superspace. This paper explores different methods to evaluate these\nintegrals and then uses them to calculate the kinematic factors of the one-loop\nand two-loop massless four-point amplitudes involving two and four Ramond\nstates.",
|
||||
"authors": [
|
||||
"Christian Stahn"
|
||||
],
|
||||
"publishedAt": "Mon, 2 Apr 2007 18:10:09 GMT",
|
||||
"topics": [
|
||||
"hep-th"
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"label": "arXiv",
|
||||
"url": "https://arxiv.org/abs/0704.0015",
|
||||
"externalId": "0704.0015"
|
||||
}
|
||||
],
|
||||
"doi": "10.1088/1126-6708/2007/05/034",
|
||||
"journal": "JHEP 0705:034,2007",
|
||||
"comments": "22 pages; signs and coefficients adjusted for anticommuting\n superfields, section 4.3 changed accordingly, reference added"
|
||||
},
|
||||
{
|
||||
"id": "16",
|
||||
"title": "Lifetime of doubly charmed baryons",
|
||||
"abstract": "In this work, we evaluate the lifetimes of the doubly charmed baryons\n$\\Xi_{cc}^{+}$, $\\Xi_{cc}^{++}$ and $\\Omega_{cc}^{+}$. We carefully calculate\nthe non-spectator contributions at the quark level where the Cabibbo-suppressed\ndiagrams are also included. The hadronic matrix elements are evaluated in the\nsimple non-relativistic harmonic oscillator model. Our numerical results are\ngenerally consistent with that obtained by other authors who used the diquark\nmodel. However, all the theoretical predictions on the lifetimes are one order\nlarger than the upper limit set by the recent SELEX measurement. This\ndiscrepancy would be clarified by the future experiment, if more accurate\nexperiment still confirms the value of the SELEX collaboration, there must be\nsome unknown mechanism to be explored.",
|
||||
"authors": [
|
||||
"Chao-Hsi Chang",
|
||||
"Tong Li",
|
||||
"Xue-Qian Li",
|
||||
"Yu-Ming Wang"
|
||||
],
|
||||
"publishedAt": "Sat, 31 Mar 2007 07:04:26 GMT",
|
||||
"topics": [
|
||||
"hep-ph"
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"label": "arXiv",
|
||||
"url": "https://arxiv.org/abs/0704.0016",
|
||||
"externalId": "0704.0016"
|
||||
}
|
||||
],
|
||||
"doi": "10.1088/0253-6102/49/4/38",
|
||||
"journal": "Commun.Theor.Phys.49:993-1000,2008",
|
||||
"comments": "17 pages, 3 figures and 1 table"
|
||||
},
|
||||
{
|
||||
"id": "17",
|
||||
"title": "Spectroscopic Observations of the Intermediate Polar EX Hydrae in Quiescence",
|
||||
"abstract": "Results from spectroscopic observations of the Intermediate Polar (IP) EX Hya\nin quiescence during 1991 and 2001 are presented. Spin-modulated radial\nvelocities consistent with an outer disc origin were detected for the first\ntime in an IP. The spin pulsation was modulated with velocities near ~500-600\nkm/s. These velocities are consistent with those of material circulating at the\nouter edge of the accretion disc, suggesting corotation of the accretion\ncurtain with material near the Roche lobe radius. Furthermore, spin Doppler\ntomograms have revealed evidence of the accretion curtain emission extending\nfrom velocities of ~500 km/s to ~1000 km/s. These findings have confirmed the\ntheoretical model predictions of King & Wynn (1999), Belle et al. (2002) and\nNorton et al. (2004) for EX Hya, which predict large accretion curtains that\nextend to a distance close to the Roche lobe radius in this system. Evidence\nfor overflow stream of material falling onto the magnetosphere was observed,\nconfirming the result of Belle et al. (2005) that disc overflow in EX Hya is\npresent during quiescence as well as outburst. It appears that the hbeta and\nhgamma spin radial velocities originated from the rotation of the funnel at the\nouter disc edge, while those of halpha were produced due to the flow of\nmaterial along the field lines far from the white dwarf (narrow component) and\nclose to the white dwarf (broad-base component), in agreement with the\naccretion curtain model.",
|
||||
"authors": [
|
||||
"Nceba Mhlahlo",
|
||||
"David H. Buckley",
|
||||
"Vikram S. Dhillon",
|
||||
"Steven B. Potter",
|
||||
"Brian Warner",
|
||||
"Patric A. Woudt"
|
||||
],
|
||||
"publishedAt": "Sat, 31 Mar 2007 07:38:48 GMT",
|
||||
"topics": [
|
||||
"astro-ph"
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"label": "arXiv",
|
||||
"url": "https://arxiv.org/abs/0704.0017",
|
||||
"externalId": "0704.0017"
|
||||
}
|
||||
],
|
||||
"doi": "10.1111/j.1365-2966.2007.11762.x",
|
||||
"journal": "Mon.Not.Roy.Astron.Soc.378:211-220,2007",
|
||||
"comments": "10 pages, 11 figures (figures 3, 4, 7 and 8 at reduced resolution,\n originals available on request). Accepted for publication in Monthly Notices\n of the Royal Astronomical Society"
|
||||
},
|
||||
{
|
||||
"id": "18",
|
||||
"title": "In quest of a generalized Callias index theorem",
|
||||
"abstract": "We give a prescription for how to compute the Callias index, using as\nregulator an exponential function. We find agreement with old results in all\nodd dimensions. We show that the problem of computing the dimension of the\nmoduli space of self-dual strings can be formulated as an index problem in\neven-dimensional (loop-)space. We think that the regulator used in this Letter\ncan be applied to this index problem.",
|
||||
"authors": [
|
||||
"Andreas Gustavsson"
|
||||
],
|
||||
"publishedAt": "Mon, 2 Apr 2007 08:58:27 GMT",
|
||||
"topics": [
|
||||
"hep-th"
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"label": "arXiv",
|
||||
"url": "https://arxiv.org/abs/0704.0018",
|
||||
"externalId": "0704.0018"
|
||||
}
|
||||
],
|
||||
"doi": null,
|
||||
"journal": null,
|
||||
"comments": "20 pages, v2: an overall sign and typos corrected"
|
||||
},
|
||||
{
|
||||
"id": "19",
|
||||
"title": "Approximation for extinction probability of the contact process based on the Gr\\\"obner basis",
|
||||
"abstract": "In this note we give a new method for getting a series of approximations for\nthe extinction probability of the one-dimensional contact process by using the\nGr\\\"obner basis.",
|
||||
"authors": [
|
||||
"Norio Konno"
|
||||
],
|
||||
"publishedAt": "Sat, 31 Mar 2007 08:12:35 GMT",
|
||||
"topics": [
|
||||
"math.PR",
|
||||
"math.AG"
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"label": "arXiv",
|
||||
"url": "https://arxiv.org/abs/0704.0019",
|
||||
"externalId": "0704.0019"
|
||||
}
|
||||
],
|
||||
"doi": null,
|
||||
"journal": "RIMS Kokyuroku, No.1551, pp.57-62 (2007)",
|
||||
"comments": "6 pages, Journal-ref added"
|
||||
},
|
||||
{
|
||||
"id": "20",
|
||||
"title": "Measurement of the Hadronic Form Factor in D0 --> K- e+ nue Decays",
|
||||
"abstract": "The shape of the hadronic form factor f+(q2) in the decay D0 --> K- e+ nue\nhas been measured in a model independent analysis and compared with theoretical\ncalculations. We use 75 fb(-1) of data recorded by the BABAR detector at the\nPEPII electron-positron collider. The corresponding decay branching fraction,\nrelative to the decay D0 --> K- pi+, has also been measured to be RD = BR(D0\n--> K- e+ nue)/BR(D0 --> K- pi+) = 0.927 +/- 0.007 +/- 0.012. From these\nresults, and using the present world average value for BR(D0 --> K- pi+), the\nnormalization of the form factor at q2=0 is determined to be f+(0)=0.727 +/-\n0.007 +/- 0.005 +/- 0.007 where the uncertainties are statistical, systematic,\nand from external inputs, respectively.",
|
||||
"authors": [
|
||||
"The BABAR Collaboration",
|
||||
"B. Aubert"
|
||||
],
|
||||
"publishedAt": "Sat, 31 Mar 2007 09:49:10 GMT",
|
||||
"topics": [
|
||||
"hep-ex"
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"label": "arXiv",
|
||||
"url": "https://arxiv.org/abs/0704.0020",
|
||||
"externalId": "0704.0020"
|
||||
}
|
||||
],
|
||||
"doi": "10.1103/PhysRevD.76.052005",
|
||||
"journal": "Phys.Rev.D76:052005,2007",
|
||||
"comments": "21 pages, 13 postscript figures, submitted to Phys. Rev. D,\n contributed to 42nd Rencontres de Moriond: QCD and Hadronic Interactions"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
export async function api<T = unknown>(path: string, options: RequestInit = {}): Promise<T> {
|
||||
const res = await fetch("/api/foundry/" + path, { ...options, headers: { "Content-Type": "application/json", ...options.headers }, cache: "no-store" });
|
||||
let data;
|
||||
try { data = res.status === 204 ? null : await res.json(); }
|
||||
catch { if (options.signal?.aborted) throw options.signal.reason; throw new Error("invalid_api_response"); }
|
||||
if (!res.ok) throw new Error(typeof data?.error === "string" ? data.error : data?.error?.code || "request_failed");
|
||||
return data as T;
|
||||
}
|
||||
const key = "research_reader_verifier";
|
||||
const base64 = (b: Uint8Array) => btoa(String.fromCharCode(...b)).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"");
|
||||
export async function startOAuth(provider: "google" | "slack") {
|
||||
const verifier = base64(crypto.getRandomValues(new Uint8Array(32)));
|
||||
const challenge = base64(new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier))));
|
||||
sessionStorage.setItem(key, verifier);
|
||||
location.assign(`/api/oauth/${provider}?challenge=${challenge}`);
|
||||
}
|
||||
let callbackAttempt: Promise<void> | undefined;
|
||||
export function finishCallback(): Promise<void> {
|
||||
if (callbackAttempt) return callbackAttempt;
|
||||
callbackAttempt = (async () => {
|
||||
const hash = new URLSearchParams(location.hash.slice(1));
|
||||
history.replaceState(null, "", location.pathname);
|
||||
const failure = hash.get("error"), code = hash.get("code"), verify = hash.get("verify_email");
|
||||
const verifier = sessionStorage.getItem(key); sessionStorage.removeItem(key);
|
||||
if (failure) throw new Error(failure);
|
||||
if (verify) { await api("auth/verify-email", { method: "POST", body: JSON.stringify({ token: verify }) }); return; }
|
||||
if (!code || !verifier) throw new Error("invalid_verification");
|
||||
await api("auth/exchange", { method: "POST", headers: { "X-Foundry-Code": code, "X-Foundry-Verifier": verifier } });
|
||||
})();
|
||||
return callbackAttempt;
|
||||
}
|
||||
export function errorText(error: unknown) {
|
||||
const messages: Record<string,string> = { invalid_credentials: "邮箱或密码不正确,或邮箱尚未验证。", invalid_session: "登录已过期,请重新登录。", invalid_verification: "验证链接已失效,或当前标签页没有登录凭据。请重新登录。", rate_limited: "请求太频繁,请稍后再试。", project_not_configured: "此站点尚未配置 Foundry 项目,请先完成后端回调注册。", unknown_project: "此项目尚未在 Foundry 后端注册。", upstream_unavailable: "暂时无法连接账户服务,请稍后重试。", slack_not_configured: "Slack 登录尚未配置完成。", invalid_origin: "当前访问地址与注册的站点地址不一致,请使用 localhost:17322。" };
|
||||
return messages[error instanceof Error ? error.message : ""] || "操作未完成,请稍后重试。";
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
export const COOKIE_NAME = "research_reader_session";
|
||||
export const IDLE_SECONDS = 2592000;
|
||||
export function maxAge(value) {
|
||||
const n = typeof value === "number" ? value : typeof value === "string" && /^\d+$/.test(value) ? Number(value) : NaN;
|
||||
return Number.isSafeInteger(n) && n >= 0 ? Math.min(n, IDLE_SECONDS) : null;
|
||||
}
|
||||
export function cookie(token, age, secure) {
|
||||
return `${COOKIE_NAME}=${encodeURIComponent(token)}; HttpOnly; SameSite=Lax; Path=/; Max-Age=${maxAge(age) ?? 0}${secure ? "; Secure" : ""}`;
|
||||
}
|
||||
export function sameOrigin(origin, expected) { try { return new URL(origin).origin === new URL(expected).origin; } catch { return false; } }
|
||||
export function allowedAuthMethod(path, method) { return path === "auth/me" ? method === "GET" : /^auth\/(login|register|verify-email|exchange|logout)$/.test(path) && method === "POST"; }
|
||||
@@ -0,0 +1,51 @@
|
||||
import snapshot from "@/data/papers.json";
|
||||
|
||||
export type Paper = { id: string; title: string; abstract: string | null; authors: string[]; publishedAt: string | null; topics: string[]; sources: { label: string; url: string; externalId: string }[]; doi?: string | null; journal?: string | null; comments?: string | null; publications?: { venue: string; year: number; track: string; status: string; evidenceUrl?: string }[] };
|
||||
export type PaperPage = { items: Paper[]; total: number; page: number; pageSize: number; topics: string[]; mode: "sample" | "live"; capturedAt?: string };
|
||||
export type Filters = { q?: string; topic?: string; sort?: string; page?: string; searchIn?: string };
|
||||
export function normalizeFilters(input: Record<string, string | string[] | undefined>): Filters {
|
||||
const value = (key: string) => typeof input[key] === "string" ? (input[key] as string).slice(0, 200) : undefined;
|
||||
return { q: value("q"), topic: value("topic"), sort: value("sort"), page: value("page"), searchIn: value("searchIn") };
|
||||
}
|
||||
const size = 8;
|
||||
export function safeExternalUrl(input: string | null | undefined) {
|
||||
if (!input) return null;
|
||||
try { const u = new URL(input); return u.protocol === "https:" || u.protocol === "http:" ? u.href : null; } catch { return null; }
|
||||
}
|
||||
function isPaper(p: unknown): p is Paper {
|
||||
if (!p || typeof p !== "object") return false;
|
||||
const v = p as Paper;
|
||||
return typeof v.id === "string" && typeof v.title === "string" && (typeof v.abstract === "string" || v.abstract === null) && Array.isArray(v.authors) && v.authors.every(x => typeof x === "string") && Array.isArray(v.topics) && v.topics.every(x => typeof x === "string") && Array.isArray(v.sources) && v.sources.every(x => typeof x.label === "string" && typeof x.externalId === "string" && typeof x.url === "string") && (v.publishedAt === null || typeof v.publishedAt === "string");
|
||||
}
|
||||
async function remote(path: string) {
|
||||
const origin = process.env.PAPER_API_ORIGIN;
|
||||
if (!origin) throw new Error("paper_api_not_configured");
|
||||
const res = await fetch(new URL(path, origin.replace(/\/$/, "") + "/"), { cache: "no-store", signal: AbortSignal.timeout(12000), redirect: "error" });
|
||||
if (res.status === 404) return null;
|
||||
if (!res.ok) throw new Error("paper_api_unavailable");
|
||||
return res.json();
|
||||
}
|
||||
export async function listPapers(filters: Filters): Promise<PaperPage> {
|
||||
const page = Math.max(1, Math.min(10000, Number.parseInt(filters.page || "1", 10) || 1));
|
||||
const q = (filters.q || "").trim().slice(0, 200), topic = filters.topic || "", sort = filters.sort === "title" ? "title" : "recent";
|
||||
if (process.env.PAPER_API_ORIGIN) {
|
||||
const params = new URLSearchParams({ q, topic, sort, searchIn: filters.searchIn === "author" ? "author" : "text", page: String(page), pageSize: String(size) });
|
||||
const data = await remote("papers?" + params);
|
||||
if (!data || !Array.isArray(data.items) || !data.items.every(isPaper) || !Number.isSafeInteger(data.total) || data.total < 0 || !Array.isArray(data.topics) || !data.topics.every((x: unknown) => typeof x === "string")) throw new Error("invalid_paper_response");
|
||||
return { ...data, page, pageSize: size, mode: "live" };
|
||||
}
|
||||
const all: Paper[] = snapshot.papers;
|
||||
const topics = Array.from(new Set(all.flatMap(p => p.topics))).sort();
|
||||
const items = all.filter(p => (!topic || p.topics.includes(topic)) && (!q || (filters.searchIn === "author" ? p.authors : [p.title, p.abstract]).join(" ").toLocaleLowerCase().includes(q.toLocaleLowerCase())));
|
||||
items.sort((a, b) => sort === "title" ? a.title.localeCompare(b.title) : Number(b.id) - Number(a.id));
|
||||
return { items: items.slice((page - 1) * size, page * size), total: items.length, page, pageSize: size, topics, mode: "sample", capturedAt: snapshot.capturedAt };
|
||||
}
|
||||
export async function getPaper(id: string): Promise<Paper | null> {
|
||||
if (!/^[a-zA-Z0-9_-]{1,100}$/.test(id)) return null;
|
||||
if (process.env.PAPER_API_ORIGIN) { const data = await remote("papers/" + encodeURIComponent(id)); if (data === null) return null; if (!isPaper(data)) throw new Error("invalid_paper_response"); return data; }
|
||||
return snapshot.papers.find(p => p.id === id) || null;
|
||||
}
|
||||
export function paperDate(date: string | null) {
|
||||
const d = date ? new Date(date) : null;
|
||||
return d && !Number.isNaN(d.valueOf()) ? d.toLocaleDateString("en-US", { year: "numeric", month: "short", day: "numeric", timeZone: "UTC" }) : "日期未提供";
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import { clsx, type ClassValue } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); }
|
||||
Reference in New Issue
Block a user