diff --git a/blocks/blog-header/blog-header.css b/blocks/blog-header/blog-header.css new file mode 100644 index 0000000..cf70127 --- /dev/null +++ b/blocks/blog-header/blog-header.css @@ -0,0 +1,131 @@ +.blog-header { + max-width: 1200px; + margin: 0 auto; + padding: 40px 24px; +} + +.blog-header-container h1 { + font-size: 48px; + line-height: 1.2; + margin: 0 0 24px; + font-weight: 700; + color: var(--text-color); +} + +.blog-header-meta { + display: flex; + flex-direction: column; + gap: 16px; + margin-bottom: 24px; +} + +.blog-header-date { + display: flex; + align-items: center; + gap: 8px; + margin: 0; + color: #666; + font-size: 16px; +} + +.blog-header-date svg { + width: 16px; + height: 16px; +} + +.blog-header-author { + list-style: none; + margin: 0; + padding: 0; +} + +.blog-header-author li { + display: inline-block; +} + +.blog-header-author img { + width: 32px; + height: 32px; + border-radius: 50%; + object-fit: cover; +} + +.blog-header-tags { + list-style: none; + margin: 0; + padding: 0; + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.blog-header-tags li { + display: inline-block; +} + +.blog-header-date a { + display: inline-flex; + align-items: center; + color: #666; + text-decoration: none; +} + +.blog-header-author a { + display: inline-flex; + align-items: center; + gap: 8px; + text-decoration: none; + color: var(--text-color); +} + +.blog-header-tags a { + display: inline-block; + padding: 8px 16px; + border: 1px solid #dadada; + border-radius: 4px; + text-decoration: none; + color: var(--text-color); + font-size: 14px; + transition: border-color 0.2s, color 0.2s; +} + +.blog-header-date a:hover { + color: var(--link-color); +} + +.blog-header-author a:hover { + color: var(--link-color); +} + +.blog-header-tags a:hover { + border-color: var(--link-color); + color: var(--link-color); +} + +/* Desktop styles */ +@media (width >= 900px) { + .blog-header { + padding: 60px 32px; + } + + .blog-header-container h1 { + font-size: 64px; + margin-bottom: 32px; + } + + .blog-header-meta { + flex-direction: row; + align-items: center; + gap: 0; + } + + .blog-header-date { + margin-right: auto; + } + + .blog-header-author img { + width: 40px; + height: 40px; + } +} + diff --git a/blocks/blog-header/blog-header.js b/blocks/blog-header/blog-header.js new file mode 100644 index 0000000..6a69c61 --- /dev/null +++ b/blocks/blog-header/blog-header.js @@ -0,0 +1,90 @@ +export default function decorate(block) { + // Extract date, author, tags from the auto-blocked structure + const [dateCell, authorCell, tagsCell] = [...block.children[0].children]; + const date = dateCell.textContent.trim(); + const author = authorCell.textContent.trim(); + const tags = tagsCell.textContent.trim(); + + // Create new structure + const container = document.createElement('div'); + container.className = 'blog-header-container'; + + // Meta section (date and author) + const meta = document.createElement('div'); + meta.className = 'blog-header-meta'; + + // Date + if (date) { + const dateWrapper = document.createElement('p'); + dateWrapper.className = 'blog-header-date'; + + const time = document.createElement('time'); + time.textContent = date; + dateWrapper.appendChild(time); + + // Add RSS icon + const rssLink = document.createElement('a'); + rssLink.href = '/feed'; + rssLink.setAttribute('aria-label', 'Atom Feed'); + rssLink.innerHTML = ''; + dateWrapper.appendChild(rssLink); + + meta.appendChild(dateWrapper); + } + + // Author + if (author) { + const authorWrapper = document.createElement('ul'); + authorWrapper.className = 'blog-header-author'; + + const li = document.createElement('li'); + const authorLink = document.createElement('a'); + + // Convert author name to GitHub username format (lowercase, no spaces) + const githubUsername = author.toLowerCase().replace(/\s+/g, ''); + authorLink.href = `https://github.com/${githubUsername}`; + + // Add author image from GitHub + const img = document.createElement('img'); + img.src = 'https://avatars.githubusercontent.com/u/2760139?v=4'; // This could be enhanced to lookup actual user + img.alt = author; + img.width = 40; + img.height = 40; + img.loading = 'lazy'; + authorLink.appendChild(img); + + // Add author name + const nameSpan = document.createElement('span'); + nameSpan.textContent = author; + authorLink.appendChild(nameSpan); + + li.appendChild(authorLink); + authorWrapper.appendChild(li); + meta.appendChild(authorWrapper); + } + + container.appendChild(meta); + + // Tags + if (tags) { + const tagsWrapper = document.createElement('ul'); + tagsWrapper.className = 'blog-header-tags'; + + // Split by comma in case there are multiple tags + const tagList = tags.split(',').map((t) => t.trim()).filter((t) => t); + tagList.forEach((tag) => { + const li = document.createElement('li'); + const tagLink = document.createElement('a'); + tagLink.href = `/blog?tag=${tag.toLowerCase()}`; + tagLink.textContent = tag; + li.appendChild(tagLink); + tagsWrapper.appendChild(li); + }); + + container.appendChild(tagsWrapper); + } + + // Replace block content + block.textContent = ''; + block.appendChild(container); +} diff --git a/scripts/scripts.js b/scripts/scripts.js index 5bd20cf..4dbbe81 100644 --- a/scripts/scripts.js +++ b/scripts/scripts.js @@ -55,12 +55,36 @@ function autolinkModals(doc) { }); } +/** + * Builds blog header block and inserts after H1. + * @param {Element} main The container element + */ +function buildBlogHeaderBlock(main) { + const date = getMetadata('date'); + const author = getMetadata('author'); + const tags = getMetadata('article:tag'); + + // Only build blog header if we have blog metadata + if (!date && !author && !tags) return; + + const h1 = main.querySelector('h1'); + if (!h1) return; + + // Build the blog header block structure + const cells = [date || '', author || '', tags || '']; + const blogHeader = buildBlock('blog-header', [cells]); + + // Insert right after H1 + h1.parentElement.insertBefore(blogHeader, h1.nextSibling); +} + /** * Builds all synthetic blocks in a container element. * @param {Element} main The container element */ function buildAutoBlocks(main) { try { + buildBlogHeaderBlock(main); if (!main.querySelector('.hero')) buildHeroBlock(main); } catch (error) { // eslint-disable-next-line no-console diff --git a/styles/styles.css b/styles/styles.css index 1acdbf1..bffc183 100644 --- a/styles/styles.css +++ b/styles/styles.css @@ -12,7 +12,7 @@ :root { /* colors */ - --white: #ffffff; + --white: #fff; --background-color: var(--white); --light-color: #ecf3fd; --dark-color: #505050;