Skip to content

Commit 8c926ff

Browse files
committed
Add Papers section from Daniel's Lattes CV
Import peer-reviewed papers (journal, accepted and full conference papers since 2013) from the Lattes XML export shared on Google Drive. sync-papers.py downloads the zip from the LATTES_DRIVE_URL secret, keeps only bibliographic fields, cleans Lattes escaping, skips unchanged exports and preserves stored data on failure. It runs in the weekly content workflow, which now also commits papers.json. The Home page shows the papers between Team and Projects, with a year filter and pagination, and the header links to the new section. Lattes zip/xml files are git-ignored since they hold personal data.
1 parent 655d2b0 commit 8c926ff

10 files changed

Lines changed: 7839 additions & 2 deletions

File tree

‎.github/workflows/pages.yml‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,12 @@ jobs:
5555
env:
5656
APIFY_TOKEN: ${{ secrets.APIFY_TOKEN }}
5757
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
58+
LATTES_DRIVE_URL: ${{ secrets.LATTES_DRIVE_URL }}
5859
run: npm run sync:weekly
5960
- name: Commit only changed content
6061
if: github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && inputs.refresh_content)
6162
run: |
62-
git add web/data/publications.json web/data/projects.json web/data/people.json web/data/media
63+
git add web/data/publications.json web/data/projects.json web/data/people.json web/data/papers.json web/data/media
6364
if ! git diff --cached --quiet; then
6465
git config user.name 'github-actions[bot]'
6566
git config user.email '41898282+github-actions[bot]@users.noreply.github.com'

‎web/.gitignore‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,7 @@ data/*.tmp
99
data/sync*.log
1010
scripts/__pycache__/
1111
.venv/
12+
13+
# Lattes exports contain personal data (CPF, RG, address); never commit them.
14+
*.zip
15+
*.xml

‎web/app/globals.css‎

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1504,3 +1504,120 @@ footer {
15041504
grid-template-columns: 1fr;
15051505
}
15061506
}
1507+
.papers-section {
1508+
padding: 48px 40px 56px;
1509+
border-top: 1px solid var(--line);
1510+
scroll-margin-top: 24px;
1511+
}
1512+
.papers-year {
1513+
font: inherit;
1514+
font-size: 12px;
1515+
color: var(--ink);
1516+
background: #fff;
1517+
border: 1px solid var(--line);
1518+
border-radius: 3px;
1519+
padding: 8px 10px;
1520+
}
1521+
.papers-list {
1522+
list-style: none;
1523+
margin: 0;
1524+
padding: 0;
1525+
border-top: 1px solid var(--line);
1526+
}
1527+
.paper-entry {
1528+
display: grid;
1529+
grid-template-columns: 64px minmax(0, 1fr);
1530+
gap: 12px;
1531+
padding: 20px 0;
1532+
border-bottom: 1px solid var(--line);
1533+
}
1534+
.paper-year {
1535+
font-size: 12px;
1536+
color: var(--muted);
1537+
padding-top: 3px;
1538+
}
1539+
.paper-entry h3 {
1540+
font-size: 16px;
1541+
font-weight: 500;
1542+
line-height: 1.45;
1543+
margin: 0;
1544+
overflow-wrap: anywhere;
1545+
}
1546+
.paper-entry h3 a {
1547+
color: var(--ink);
1548+
}
1549+
.paper-entry h3 a:hover {
1550+
color: var(--blue);
1551+
}
1552+
.paper-entry h3 svg {
1553+
vertical-align: -2px;
1554+
}
1555+
.paper-authors,
1556+
.paper-venue {
1557+
font-size: 12px;
1558+
color: var(--muted);
1559+
line-height: 1.7;
1560+
margin: 6px 0 0;
1561+
}
1562+
.paper-venue {
1563+
font-style: italic;
1564+
}
1565+
.paper-kind {
1566+
display: inline-block;
1567+
font-style: normal;
1568+
font-size: 10px;
1569+
color: var(--blue);
1570+
background: #edf4f9;
1571+
border-radius: 999px;
1572+
padding: 1px 8px;
1573+
margin-right: 8px;
1574+
}
1575+
.papers-pagination {
1576+
display: flex;
1577+
flex-wrap: wrap;
1578+
align-items: center;
1579+
justify-content: center;
1580+
gap: 6px;
1581+
margin-top: 23px;
1582+
}
1583+
.papers-page {
1584+
display: inline-flex;
1585+
align-items: center;
1586+
gap: 6px;
1587+
color: var(--muted);
1588+
}
1589+
.papers-pagination button {
1590+
display: inline-flex;
1591+
align-items: center;
1592+
justify-content: center;
1593+
min-width: 34px;
1594+
height: 34px;
1595+
font: inherit;
1596+
font-size: 12px;
1597+
color: var(--blue);
1598+
background: #f5f8fb;
1599+
border: 1px solid var(--line);
1600+
border-radius: 3px;
1601+
cursor: pointer;
1602+
}
1603+
.papers-pagination button:hover:not(:disabled) {
1604+
background: #edf4f9;
1605+
}
1606+
.papers-pagination button.active {
1607+
color: #fff;
1608+
background: var(--ink);
1609+
border-color: var(--ink);
1610+
}
1611+
.papers-pagination button:disabled {
1612+
opacity: 0.4;
1613+
cursor: default;
1614+
}
1615+
@media (max-width: 700px) {
1616+
.papers-section {
1617+
padding: 34px 22px;
1618+
}
1619+
.paper-entry {
1620+
grid-template-columns: 1fr;
1621+
gap: 4px;
1622+
}
1623+
}

‎web/app/page.js‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,18 @@ import LinkedinPosts from './linkedin-posts';
55
import SiteHeader from './site-header';
66
import People from './people';
77
import HomeCarousel from './home-carousel';
8+
import Papers from './papers';
89
import { assetPath } from '../lib/site-path';
910
import { readPeople } from '../lib/people-store';
11+
import { readPapers } from '../lib/papers-store';
1012

1113
export default async function Home() {
12-
const [github, news, people] = await Promise.all([getGithub(), getNews(), readPeople()]);
14+
const [github, news, people, papers] = await Promise.all([
15+
getGithub(),
16+
getNews(),
17+
readPeople(),
18+
readPapers(),
19+
]);
1320
const activity = {
1421
items: Object.values(github.details || {}).flatMap((detail) => detail.updates || []),
1522
fetchedAt: github.fetchedAt,
@@ -72,6 +79,7 @@ export default async function Home() {
7279
<LinkedinPosts news={news} />
7380
</section>
7481
<People people={people} />
82+
<Papers papers={papers} />
7583
<section id="projetos" className="projects-section">
7684
<div className="section-heading">
7785
<div>

‎web/app/papers.js‎

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
'use client';
2+
import { useRef, useState } from 'react';
3+
import { ArrowLeft, ArrowRight, ArrowUpRight } from 'lucide-react';
4+
const PAGE_SIZE = 10;
5+
const kindLabel = { journal: 'Journal', accepted: 'Accepted', conference: 'Conference' };
6+
// First, last and the pages around the current one.
7+
const pageWindow = (page, pages) =>
8+
[...new Set([0, page - 1, page, page + 1, pages - 1])]
9+
.filter((i) => i >= 0 && i < pages)
10+
.sort((a, b) => a - b);
11+
function Authors({ authors }) {
12+
const names = authors.slice(0, 10).map((author) => author.name);
13+
return names.join('; ') + (authors.length > 10 ? '; et al.' : '');
14+
}
15+
export default function Papers({ papers }) {
16+
const section = useRef(null);
17+
const [year, setYear] = useState('');
18+
const [page, setPage] = useState(0);
19+
const years = [...new Set(papers.papers.map((paper) => paper.year))];
20+
const filtered = year
21+
? papers.papers.filter((paper) => paper.year === Number(year))
22+
: papers.papers;
23+
const pages = Math.ceil(filtered.length / PAGE_SIZE);
24+
function go(next) {
25+
setPage(next);
26+
section.current?.scrollIntoView({ block: 'start' });
27+
}
28+
return (
29+
<section id="artigos" className="papers-section" ref={section}>
30+
<div className="section-heading">
31+
<div>
32+
<div className="kicker">PAPERS / JOURNALS / CONFERENCES</div>
33+
<h2>Papers</h2>
34+
</div>
35+
<select
36+
className="papers-year"
37+
value={year}
38+
onChange={(event) => {
39+
setYear(event.target.value);
40+
setPage(0);
41+
}}
42+
aria-label="Filter papers by year"
43+
>
44+
<option value="">All years</option>
45+
{years.map((value) => (
46+
<option key={value} value={value}>
47+
{value}
48+
</option>
49+
))}
50+
</select>
51+
</div>
52+
<p className="section-description">
53+
Peer-reviewed papers since 2013 · {filtered.length} papers
54+
{papers.lattesUpdatedAt &&
55+
` · from Daniel de Oliveira’s Lattes CV, updated on ${new Date(
56+
`${papers.lattesUpdatedAt}T12:00:00Z`,
57+
).toLocaleDateString('en-US')}`}
58+
</p>
59+
<ol className="papers-list">
60+
{filtered.slice(page * PAGE_SIZE, (page + 1) * PAGE_SIZE).map((paper) => (
61+
<li key={`${paper.kind}-${paper.year}-${paper.title}`} className="paper-entry">
62+
<span className="paper-year">{paper.year}</span>
63+
<div>
64+
<h3>
65+
{paper.url ? (
66+
<a href={paper.url} target="_blank" rel="noreferrer">
67+
{paper.title} <ArrowUpRight size={14} />
68+
</a>
69+
) : (
70+
paper.title
71+
)}
72+
</h3>
73+
<p className="paper-authors">
74+
<Authors authors={paper.authors} />
75+
</p>
76+
<p className="paper-venue">
77+
<span className="paper-kind">{kindLabel[paper.kind]}</span>
78+
{paper.venue}
79+
</p>
80+
</div>
81+
</li>
82+
))}
83+
</ol>
84+
{!filtered.length && <p className="no-activity">No papers available.</p>}
85+
{pages > 1 && (
86+
<nav className="papers-pagination" aria-label="Papers pages">
87+
<button onClick={() => go(page - 1)} disabled={page === 0} aria-label="Previous page">
88+
<ArrowLeft size={15} />
89+
</button>
90+
{pageWindow(page, pages).map((i, index, list) => (
91+
<span key={i} className="papers-page">
92+
{index > 0 && i - list[index - 1] > 1 && <span aria-hidden="true">…</span>}
93+
<button
94+
onClick={() => go(i)}
95+
className={i === page ? 'active' : ''}
96+
aria-current={i === page ? 'page' : undefined}
97+
>
98+
{i + 1}
99+
</button>
100+
</span>
101+
))}
102+
<button onClick={() => go(page + 1)} disabled={page === pages - 1} aria-label="Next page">
103+
<ArrowRight size={15} />
104+
</button>
105+
</nav>
106+
)}
107+
</section>
108+
);
109+
}

‎web/app/site-header.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export default function SiteHeader() {
1111
</a>
1212
<nav aria-label="Main navigation">
1313
<a href={homePath('#novidades')}>Publications</a>
14+
<a href={homePath('#artigos')}>Papers</a>
1415
<a href={homePath('#projetos')}>Projects</a>
1516
<a href={homePath('#pessoas')}>Team</a>
1617
</nav>

0 commit comments

Comments
 (0)