PageSourceSearch

https://roberttlange.com/js/about.js

js roberttlange.com collected 2026-10-03 22:59:01 UTC 8,002 bytes, 179 lines download raw bytes

1document.addEventListener('DOMContentLoaded', () => {
2    const repoGrid = document.getElementById('repo-grid');
3    const pinnedSource = '/data/repos.json';
4    const publicationsList = document.getElementById('pub-list');
5    const publicationsUpdated = document.querySelector('.pub-updated');
6    const publicationsSource = '/data/publications.json';
7
8    function setRepoMessage(message) {
9        if (!repoGrid) return;
10        repoGrid.innerHTML = `<div class="repo-card is-loading">${message}</div>`;
11    }
12
13    function formatCount(value) {
14        const count = Number(value);
15        return Number.isFinite(count) ? count.toLocaleString('en-US') : '0';
16    }
17
18    function renderRepos(repos) {
19        if (!repoGrid) return;
20        if (!Array.isArray(repos) || repos.length === 0) {
21            setRepoMessage('No pinned repositories found.');
22            return;
23        }
24
25        repoGrid.innerHTML = repos.map((repo) => {
26            const url = escapeHtml(safeLink(repo.url));
27            const name = escapeHtml(repo.name);
28            const description = escapeHtml(repo.description) || 'No description provided.';
29            const language = escapeHtml(repo.language) || 'N/A';
30
31            return `
32                <article class="repo-card">
33                    <h3 class="repo-title"><a href="${url}" target="_blank" rel="noopener noreferrer">${name}</a></h3>
34                    <p class="repo-desc">${description}</p>
35                    <div class="repo-meta">
36                        <span>Stars: ${formatCount(repo.stars)}</span>
37                        <span>Forks: ${formatCount(repo.forks)}</span>
38                        <span>${language}</span>
39                    </div>
40                </article>
41            `;
42        }).join('');
43    }
44
45    async function loadPinnedRepos() {
46        try {
47            const response = await fetch(pinnedSource, { cache: 'no-store' });
48            if (!response.ok) {
49                throw new Error(`Pinned repo fetch error: ${response.status}`);
50            }
51            const payload = await response.json();
52            renderRepos(payload && payload.items);
53        } catch (error) {
54            console.error('Failed to load pinned repositories:', error);
55            setRepoMessage('Unable to load pinned repositories right now.');
56        }
57    }
58
59    if (repoGrid) {
60        loadPinnedRepos();
61    }
62
63    function escapeHtml(value) {
64        return String(value || '')
65            .replace(/&/g, '&amp;')
66            .replace(/</g, '&lt;')
67            .replace(/>/g, '&gt;')
68            .replace(/"/g, '&quot;')
69            .replace(/'/g, '&#39;');
70    }
71
72    function safeLink(url) {
73        if (!url) return '';
74        try {
75            const parsed = new URL(url, window.location.href);
76            if (parsed.protocol === 'http:' || parsed.protocol === 'https:') {
77                return parsed.href;
78            }
79        } catch (error) {
80            return '';
81        }
82        return '';
83    }
84
85    function formatUpdatedDate(value) {
86        if (!value) return '';
87        const parts = value.split('-').map((part) => Number(part));
88        if (parts.length === 3 && parts.every((part) => !Number.isNaN(part))) {
89            const parsed = new Date(Date.UTC(parts[0], parts[1] - 1, parts[2]));
90            return new Intl.DateTimeFormat('en-US', {
91                year: 'numeric',
92                month: 'short',
93                day: 'numeric',
94                timeZone: 'UTC',
95            }).format(parsed);
96        }
97        const parsed = new Date(value);
98        if (Number.isNaN(parsed.getTime())) return value;
99        return parsed.toLocaleDateString('en-US', {
100            year: 'numeric',
101            month: 'short',
102            day: 'numeric',
103        });
104    }
105
106    function renderPubTags(item) {
107        const tags = [];
108        if (item.paper) {
109            tags.push(`<a href="${escapeHtml(item.paper)}" target="_blank" rel="noopener noreferrer"><span class="tag-paper"><img src="/arxiv-logo.png" alt="arXiv" class="arxiv-logo"/> Paper</span></a>`);
110        }
111        if (item.blog) {
112            tags.push(`<a href="${escapeHtml(item.blog)}" target="_blank" rel="noopener noreferrer"><span class="tag-link"><svg viewBox="0 0 16 16" width="12" height="12" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M6.5 4.5h-2a3 3 0 0 0 0 6h2"/><path d="M9.5 4.5h2a3 3 0 0 1 0 6h-2"/><path d="M5.5 8h5"/></svg> Blog</span></a>`);
113        }
114        if (item.code) {
115            tags.push(`<a href="${escapeHtml(item.code)}" target="_blank" rel="noopener noreferrer"><span class="tag-code"><svg viewBox="0 0 16 16" width="12" height="12" fill="currentColor"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"/></svg>
115 Code</span></a>`);
116        }
117        if (item.tweet) {
118            tags.push(`<a href="${escapeHtml(item.tweet)}" target="_blank" rel="noopener noreferrer"><span class="tag-tweet"><svg viewBox="0 0 16 16" width="12" height="12"><text x="3" y="12" font-family="serif" font-weight="bold" font-size="12" fill="currentColor">𝕏</text></svg> Tweet</span></a>`);
119        }
120        if (tags.length === 0) return '';
121        return `<div class="pub-tags">${tags.join('')}</div>`;
122    }
123
124    function renderPublications(items) {
125        if (!publicationsList) return;
126        if (!Array.isArray(items) || items.length === 0) return;
127
128        publicationsList.innerHTML = items.map((item) => {
129            const title = escapeHtml(item.title);
130            const authors = escapeHtml(item.authors);
131            const venue = escapeHtml(item.venue);
132            const year = escapeHtml(item.year || '--');
133            const link = escapeHtml(safeLink(item.link));
134            const titleMarkup = link
135                ? `<a href="${link}" target="_blank" rel="noopener noreferrer">${title}</a>`
136                : title;
137            const authorsMarkup = authors ? `<p class="pub-authors">${authors}</p>` : '';
138            const venueHtml = venue ? `<p class="pub-venue">${venue}</p>` : '';
139            const tagsHtml = renderPubTags(item);
140            const bottomMarkup = (venueHtml || tagsHtml)
141                ? `<div class="pub-bottom">${venueHtml}${tagsHtml}</div>`
142                : '';
143
144            return `
145                <li class="pub-item">
146                    <span class="pub-year">${year}</span>
147                    <div class="pub-info">
148                        <p class="pub-title">${titleMarkup}</p>
149                        ${authorsMarkup}
150                        ${bottomMarkup}
151                    </div>
152                </li>
153            `;
154        }).join('');
155    }
156
157    async function loadPublications() {
158        if (!publicationsList) return;
159        try {
160            const response = await fetch(publicationsSource, { cache: 'no-store' });
161            if (!response.ok) {
162                throw new Error(`Publications fetch error: ${response.status}`);
163            }
164            const payload = await response.json();
165            if (payload && payload.items) {
166                renderPublications(payload.items);
167            }
168            if (publicationsUpdated && payload && payload.updated) {
169                publicationsUpdated.textContent = `Updated ${formatUpdatedDate(payload.updated)}.`;
170            }
171        } catch (error) {
172            console.error('Failed to load publications:', error);
173        }
174    }
175
176    if (publicationsList) {
177        loadPublications();
178    }
179});

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.