|
246 | 246 | return s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); |
247 | 247 | } |
248 | 248 |
|
| 249 | + async function renderSinglePost(dirName) { |
| 250 | + fileContent.innerHTML = '<div class="small">Loading single post...</div>'; |
| 251 | + fileTitle.textContent = dirName; |
| 252 | + fileMeta.textContent = ''; |
| 253 | + try { |
| 254 | + const postsPath = 'posts'; |
| 255 | + const baseUrl = `https://api.github.com/repos/${owner}/${repo}/contents`; |
| 256 | + const dirUrl = `${baseUrl}/${encodeURIComponent(postsPath)}/${encodeURIComponent(dirName)}`; |
| 257 | + const dirContents = await apiFetch(dirUrl); |
| 258 | + const readmeFile = dirContents.find(f => /^readme(\.(md|markdown))?$/i.test(f.name)); |
| 259 | + if (!readmeFile) { |
| 260 | + fileContent.innerHTML = '<div class="small">No README found for this post.</div>'; |
| 261 | + return; |
| 262 | + } |
| 263 | + const fileData = await apiFetch(readmeFile.url); |
| 264 | + const decoded = decodeBase64ToUtf8(fileData.content || ''); |
| 265 | + const html = marked.parse(decoded || ''); |
| 266 | + fileContent.innerHTML = html; |
| 267 | + rewriteRelativeImageSrcs(fileContent, `${postsPath}/${dirName}`); |
| 268 | + fileContent.querySelectorAll('pre code').forEach(block => hljs.highlightElement(block)); |
| 269 | + setStatus(''); |
| 270 | + } catch (err) { |
| 271 | + fileContent.innerHTML = `<div class="small" style="color:#ffb4b4">Failed to load post: ${err.message}</div>`; |
| 272 | + } |
| 273 | + } |
| 274 | + |
249 | 275 | async function loadPosts(owner, repo) { |
250 | 276 | if (!owner || !repo) { |
251 | 277 | setStatus('Owner and repo required', true); |
|
366 | 392 | header.style.alignItems = 'center'; |
367 | 393 | header.style.marginBottom = '6px'; |
368 | 394 |
|
369 | | - const titleDiv = document.createElement('div'); |
| 395 | + const titleDiv = document.createElement('a'); |
| 396 | + titleDiv.href = '#'; |
| 397 | + titleDiv.className = 'link'; |
370 | 398 | titleDiv.style.fontWeight = '700'; |
| 399 | + titleDiv.style.textDecoration = 'none'; |
| 400 | + titleDiv.style.cursor = 'pointer'; |
371 | 401 | titleDiv.textContent = dir.name; |
| 402 | + titleDiv.addEventListener('click', (e) => { |
| 403 | + e.preventDefault(); |
| 404 | + renderSinglePost(dir.name); |
| 405 | + }); |
372 | 406 |
|
373 | 407 | const badge = document.createElement('div'); |
374 | 408 | badge.className = 'small'; |
|
0 commit comments