class BushelAuthor extends HTMLElement { constructor() { super(); this.attachShadow({ mode: 'open' }); } static get observedAttributes() { return [ 'name', 'given-name', 'family-name', 'nickname', 'email', 'website', 'github', 'mastodon', 'twitter', 'photo', 'bio', 'org', 'job-title', 'display-mode', 'link-profile' ]; } connectedCallback() { this.render(); } attributeChangedCallback() { this.render(); } get displayMode() { return this.getAttribute('display-mode') || 'inline'; } get shouldLinkProfile() { return this.hasAttribute('link-profile'); } buildHCard() { const name = this.getAttribute('name'); const givenName = this.getAttribute('given-name'); const familyName = this.getAttribute('family-name'); const nickname = this.getAttribute('nickname'); const email = this.getAttribute('email'); const website = this.getAttribute('website'); const github = this.getAttribute('github'); const mastodon = this.getAttribute('mastodon'); const twitter = this.getAttribute('twitter'); const photo = this.getAttribute('photo'); const bio = this.getAttribute('bio'); const org = this.getAttribute('org'); const jobTitle = this.getAttribute('job-title'); const displayName = name || `${givenName || ''} ${familyName || ''}`.trim() || nickname; const hcard = document.createElement('div'); hcard.className = 'h-card bushel-author-card'; // Photo if (photo) { const img = document.createElement('img'); img.className = 'u-photo author-photo'; img.src = photo; img.alt = `Photo of ${displayName}`; img.loading = 'lazy'; hcard.appendChild(img); } const infoContainer = document.createElement('div'); infoContainer.className = 'author-info'; // Name (with optional link) const nameElement = document.createElement(this.shouldLinkProfile && website ? 'a' : 'span'); nameElement.className = 'p-name author-name'; nameElement.textContent = displayName; if (this.shouldLinkProfile && website) { nameElement.href = website; nameElement.className += ' u-url'; } // Hidden structured name parts if (givenName) { const givenSpan = document.createElement('span'); givenSpan.className = 'p-given-name visually-hidden'; givenSpan.textContent = givenName; nameElement.appendChild(givenSpan); } if (familyName) { const familySpan = document.createElement('span'); familySpan.className = 'p-family-name visually-hidden'; familySpan.textContent = familyName; nameElement.appendChild(familySpan); } if (nickname && nickname !== displayName) { const nickSpan = document.createElement('span'); nickSpan.className = 'p-nickname visually-hidden'; nickSpan.textContent = nickname; nameElement.appendChild(nickSpan); } infoContainer.appendChild(nameElement); // Job title and organization if (jobTitle || org) { const titleElement = document.createElement('div'); titleElement.className = 'author-title'; if (jobTitle) { const jobSpan = document.createElement('span'); jobSpan.className = 'p-job-title'; jobSpan.textContent = jobTitle; titleElement.appendChild(jobSpan); if (org) { titleElement.appendChild(document.createTextNode(' at ')); } } if (org) { const orgSpan = document.createElement('span'); orgSpan.className = 'p-org'; orgSpan.textContent = org; titleElement.appendChild(orgSpan); } infoContainer.appendChild(titleElement); } // Bio/note if (bio) { const bioElement = document.createElement('div'); bioElement.className = 'p-note author-bio'; bioElement.textContent = bio; infoContainer.appendChild(bioElement); } // Social links const socialContainer = document.createElement('div'); socialContainer.className = 'author-social'; let hasSocial = false; if (email) { const emailLink = document.createElement('a'); emailLink.className = 'u-email social-link'; emailLink.href = `mailto:${email}`; emailLink.textContent = 'Email'; emailLink.setAttribute('aria-label', `Email ${displayName}`); socialContainer.appendChild(emailLink); hasSocial = true; } if (github) { const githubLink = document.createElement('a'); githubLink.className = 'u-url social-link'; githubLink.href = `https://github.com/${github}`; githubLink.textContent = 'GitHub'; githubLink.setAttribute('aria-label', `${displayName} on GitHub`); githubLink.rel = 'me'; socialContainer.appendChild(githubLink); hasSocial = true; } if (mastodon) { const mastodonLink = document.createElement('a'); mastodonLink.className = 'u-url social-link'; mastodonLink.href = mastodon; mastodonLink.textContent = 'Mastodon'; mastodonLink.setAttribute('aria-label', `${displayName} on Mastodon`); mastodonLink.rel = 'me'; socialContainer.appendChild(mastodonLink); hasSocial = true; } if (twitter) { const twitterLink = document.createElement('a'); twitterLink.className = 'u-url social-link'; twitterLink.href = `https://twitter.com/${twitter}`; twitterLink.textContent = 'Twitter'; twitterLink.setAttribute('aria-label', `${displayName} on Twitter`); twitterLink.rel = 'me'; socialContainer.appendChild(twitterLink); hasSocial = true; } if (website && !this.shouldLinkProfile) { const websiteLink = document.createElement('a'); websiteLink.className = 'u-url social-link'; websiteLink.href = website; websiteLink.textContent = 'Website'; websiteLink.setAttribute('aria-label', `${displayName}'s website`); socialContainer.appendChild(websiteLink); hasSocial = true; } if (hasSocial) { infoContainer.appendChild(socialContainer); } hcard.appendChild(infoContainer); return hcard; } render() { const styles = ` `; this.shadowRoot.innerHTML = styles + this.buildHCard().outerHTML; } } customElements.define('bushel-author', BushelAuthor);