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);