Something went wrong. Try again.
A custom element for kibun.social statuses. Built atop https://tangled.org/strings/did:plc:2sqok7oqqrhtmmmb5sulkrw2/3m6cyhxo2pw22
Something went wrong. Try again.
5.1 kB · 173 lines · Created 11mo ago
JavaScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174const urls = { identityResolveMiniDoc: (username) => `https://slingshot.microcosm.blue/xrpc/com.bad-example.identity.resolveMiniDoc?identifier=${username}`, repoGetRecord: (userDidDoc) => `${userDidDoc.pds}/xrpc/com.atproto.repo.getRecord?repo=${userDidDoc.did}&collection=app.bsky.actor.profile&rkey=self`, repoListRecords: (userDidDoc) => `${userDidDoc.pds}/xrpc/com.atproto.repo.listRecords?repo=${userDidDoc.did}&collection=social.kibun.status&limit=1`,}
const defaultStyles = `:host { display: block;}#container { border: 1px #7dd3fc solid; box-shadow: 4px 4px 0 #7dd3fc; padding: 20px; max-width: 400px; background-color: #FFFFFF; font-family: 'Inter', 'San Francisco', 'Lucida Grande', Arial, sans-serif; font-size: 14px; position: relative;}#header { display: flex; gap: 10px; align-items: center; flex-wrap: wrap;}#displayname { color: black; font-weight: bold; text-decoration: none;}#handle { color: #666666; font-size: .8em; text-decoration: none;}#datetime { color: #666666; font-size: .8em;}#datetime:before { content: """; margin-right: 10px;}#status { margin-top: 10px;}#link { position: absolute; bottom: 5px; right: 5px; font-size: .6em; color: #666666;}`;
class KibunStatus extends HTMLElement { constructor() { super(); this.attachShadow({ mode: 'open' }); }
connectedCallback() { this.render(); }
async render() { const username = this.getAttribute('username'); const hideKibun = this.hasAttribute('hide-kibun'); const noStyles = this.hasAttribute('no-styles');
const details = await this._retrieveStatus(username).catch(err => this._dispatchError(err)); if (!details) { return; }
const { displayName, emoji, statusText, timeAgoText } = details
this.shadowRoot.innerHTML = ` ${noStyles ? '' : `<style>${defaultStyles}</style>`} <div id="container"> <div id="header"> <a id="displayname" href="https://www.kibun.social/users/${username}" target="_blank" rel="external">${displayName}</a> <span id="emoji">${emoji}</span> <a id="handle" href="https://www.kibun.social/users/${username}" target="_blank" rel="external">@${username}</a> <span id="datetime">${timeAgoText}</span> </div> <div id="status">${statusText}</div> ${hideKibun ? '' : '<a id="link" href="https://www.kibun.social/" target="_blank" rel="external">kibun.social</a>'} </div> `; }
_dispatchError(error) { this.dispatchEvent(new CustomEvent('error', { detail: { error, message: 'Unable to retrieve Kibun status information'}, bubbles: true, composed: true, })); console.error(error); }
async _retrieveStatus(username) { if (!username || !/\./.test(username)) { throw new Error('Please include at least a Kibun username: eg. <kibun-status username="kibun.social">'); }
let userDidDoc; try { userDidDoc = await fetch(urls.identityResolveMiniDoc(username)).then(res => res.json()); } catch(error) { throw new Error('Unable to retrieve ATProto user data from Slingshot', error); } let userInfoData; try { userInfoData = await fetch(urls.repoGetRecord(userDidDoc)).then(res => res.json()); } catch(error) { throw new Error('Unable to retrieve user profile data from their PDS', error); }
let statuses; try { statuses = await fetch(urls.repoListRecords(userDidDoc)).then(res => res.json()); } catch (error) { throw new Error('Unable to retrieve kibun records from user PDS', error); }
if (statuses.records.length === 0) { throw new Error(`'${username}' doesn't seem to use Kibun!`); }
const status = statuses.records[0]; return { displayName: userInfoData.value.displayName, emoji: status.value.emoji, statusText: status.value.text, timeAgoText: this._timeAgo(status.value.createdAt), } }
_timeAgo (dateString) { const date = Date.parse(dateString); const curDate = new Date(date); const now = Date.now(); const yest = new Date(Date.parse(dateString)); const today = new Date(date); yest.setDate(today - 1); const diff = (now - date) / 1000; // difference in seconds if (diff < 5) { return "just now"; } else if (diff < 60) { return `${diff} seconds ago`; } else if (diff < 60*60) { const min = Math.floor(diff / 60); return `${min} minute${min > 1 ? 's' : ''} ago`; } else if (diff < 60*60*24) { const hr = Math.floor(diff / (60*60)); return `${hr} hour${hr > 1 ? 's' : ''} ago`; } else if (date.getDate() === yest.getDate() && date.getMonth() === yest.getMonth() && date.getYear() === yest.getYear()) { return "yesterday"; } return `${curDate.toLocaleDateString(undefined, { weekday: 'short', year: 'numeric', month: 'short', day: 'numeric' }).toLowerCase()}`; }}
customElements.define('kibun-status', KibunStatus);