This post that you're reading isn't a post at all. It's not even an
<iframe>
pointing to cohost! It's a
made to replicate the look of a post on cohost. It requires no 3rd party dependencies - just a JS and CSS
file.
Save these two files to your web server:
(You'll also want to add Atkinson Hyperlegible as a webfont via another CSS file.)
Next, put these tags in your webpage's <head>
element:
(Replace /assets/
with whatever web-facing directory you saved the files to! And don't forget
the webfont CSS file too!!)
Now your page has access to two custom elements, <cohost-post>
and
<cohost-shared-item>
. Keep reading to see how you can create these elements!
I made this bookmarklet that you can add to your browser's Bookmarks tab to quickly generate the custom element for a post:
javascript:void%20function(){(async()=%3E{async%20function%20a(a,b){if(!b)return%20a;let%20c;if(a.startsWith(d))c=a.replace(d,%22https://cohost-web-component.meow.garden/cohostcdn-cors-proxy/%22),a.includes(%22/avatar/%22)%26%26(c+=%22%3Fdpr=2%26width=32%26height=32%26fit=cover%26auto=webp%22);else%20if(a.startsWith(e))c=a.replace(e,%22https://cohost-web-component.meow.garden/cohost-static-cors-proxy/%22);else%20return%20a;let%20f=await%20fetch(c),g=await%20f.blob();return%20new%20Promise(a=%3E{let%20b=new%20FileReader;b.onload=()=%3Ea(b.result),b.readAsDataURL(g)})}async%20function%20b(b){let%20c=JSON.parse(document.getElementById(%22trpc-dehydrated-state%22).innerText),f=c.queries.find(a=%3Ea.queryKey[0].includes(%22singlePost%22)).state.data.post,g=document.createElement(%22cohost-post%22);if(g.setAttribute(%22avatarSrc%22,await%20a(f.postingProject.avatarURL,b)),g.setAttribute(%22avatarShape%22,f.postingProject.avatarShape),g.setAttribute(%22comments%22,f.numComments),g.setAttribute(%22displayName%22,f.postingProject.displayName),g.setAttribute(%22handle%22,f.postingProject.handle),g.setAttribute(%22originalUrl%22,f.singlePostPageUrl),f.publishedAt%26%26g.setAttribute(%22publishedAt%22,f.publishedAt),f.numSharedComments%26%26g.setAttribute(%22sharedComments%22,f.numSharedComments),f.headline%26%26g.setAttribute(%22standaloneHeadline%22,f.headline),g.setAttribute(%22tags%22,f.tags.join(%22,%22)),f.shareTree%26%26f.shareTree.length){g.setAttribute(%22sharedItems%22,%22%22);let%20c=f.shareTree[f.shareTree.length-1];g.setAttribute(%22sharedAvatarSrc%22,await%20a(c.postingProject.avatarURL,b)),g.setAttribute(%22sharedAvatarShape%22,c.postingProject.avatarShape),g.setAttribute(%22sharedDisplayName%22,c.postingProject.displayName),g.setAttribute(%22sharedHandle%22,c.postingProject.handle);let%20d=[...f.shareTree,f],e=!0;for(sharedPost%20of%20d){if(null!==sharedPost.transparentShareOfPostId)continue;let%20c=document.createElement(%22cohost-shared-item%22);c.setAttribute(%22avatarSrc%22,await%20a(sharedPost.postingProject.avatarURL,b)),c.setAttribute(%22avatarShape%22,sharedPost.postingProject.avatarShape),c.setAttribute(%22displayName%22,sharedPost.postingProject.displayName),c.setAttribute(%22handle%22,sharedPost.postingProject.handle),sharedPost.headline%26%26c.setAttribute(%22headline%22,sharedPost.headline),c.setAttribute(%22originalUrl%22,sharedPost.singlePostPageUrl),sharedPost.publishedAt%26%26c.setAttribute(%22publishedAt%22,sharedPost.publishedAt),c.setAttribute(%22tags%22,sharedPost.tags.join(%22,%22));let%20d=document.getElementById(`post-${sharedPost.postId}`);if(d){let%20a=d.parentElement.querySelector(%22[data-post-body]%22);if(a){let%20b=document.createElement(%22noscript%22);b.innerText=`${e%3F%22Posted%22:%22Shared%22}%20by%20${sharedPost.postingProject.displayName||%22%22}%20(%40${sharedPost.postingProject.handle}):%20${sharedPost.headline||%22%22}`,c.appendChild(b),c.append(...a.cloneNode(!0).childNodes)}}g.appendChild(c),e=!1}}else{let%20a=document.getElementById(`post-${f.postId}`);if(a){let%20a=document.getElementById(`post-${f.postId}`).parentElement.querySelector(%22[data-post-body]%22);if(a){let%20b=document.createElement(%22noscript%22);if(f.shareTree%26%26f.shareTree.length){let%20a=f.shareTree[f.shareTree.length-1];b.innerText=`Post%20by%20${a.postingProject.displayName||%22%22}%20(%40${a.postingProject.handle}),%20shared%20by%20${f.postingProject.displayName||%22%22}%20(%40${f.postingProject.handle}):%20${f.headline||%22%22}`}else%20b.innerText=`Post%20by%20${f.postingProject.displayName||%22%22}%20(%40${f.postingProject.handle}):%20${f.headline||%22%22}`;g.appendChild(b),g.append(...a.cloneNode(!0).childNodes)}}}let%20h=!1,i=!1;for(const%20c%20of%20g.querySelectorAll(%22img,%20audio%22)){%22AUDIO%22==c.tagName%26%26(i=!0);let%20f=c.getAttribute(%22src%22);b%26%26f.startsWith(e)%3Fc.setAttribute(%22src%22,await%20a(f,!0)):(f.startsWith(%22https://cohost.org/%22)||f.startsWith(d))%26%26(h=!0)}return{postElement:g,mediaFlag:h,audioPlayerFlag:i}}async%20function%20c(a,d){let%20e=document.createElement(%22template%22);e.innerHTML=`%3Cdiv%20id=%22${g}%22%20class=%22fixed%20bottom-0%20p-2%20flex%20flex-col%20items-center%20gap-2%20font-sm%20bg-background%20co-themed-box%20border%20rounded-lg%22%3E %20%20%20%20%20%20%3Ctextarea%20rows=%225%22%20cols=%2235%22%20style=%22font-size:%20inherit;%20line-height:%201.4%22%3E%3C/textarea%3E %20%20%20%20%20%20%3Cdiv%20id=%22${h}%22%20class=%22co-info-box%20co-info%20text-sm%20mx-auto%20w-full%20rounded-lg%20p-3%22%3E %20%20%20%20%20%20%20%20Post%20%3Cb%3Estill%3C/b%3E%20contains%20media%20hosted%20on%20cohost.%3Cbr%20/%3EConsider%20rehosting%20any%20images%20or%20audio%20files%20on%20your%20website. %20%20%20%20%20%20%3C/div%3E %20%20%20%20%20%20%3Cdiv%20id=%22${i}%22%20class=%22co-info-box%20co-info%20text-sm%20mx-auto%20w-full%20rounded-lg%20p-3%22%3E %20%20%20%20%20%20%20%20cohost's%20audio%20player%20doesn't%20function%20correctly%20(yet). %20%20%20%20%20%20%3C/div%3E %20%20%20%20%20%20%3Cdiv%20class=%22flex%20flex-row%20gap-1%22%3E %20%20%20%20%20%20%20%20%3Cbutton%20id=%22${j}%22%20class=%22rounded-lg%20bg-cherry%20py-2%20px-4%20text-sm%20font-bold%20text-notWhite%20hover:bg-cherry-600%20active:bg-cherry-700%22%3EConvert%20avatars%20and%20custom%20emoji%20to%20data%20URIs%3C/button%3E %20%20%20%20%20%20%20%20%3Cbutton%20id=%22${k}%22%20class=%22rounded-lg%20bg-cherry%20py-2%20px-4%20text-sm%20font-bold%20text-notWhite%20hover:bg-cherry-600%20active:bg-cherry-700%22%3Edismiss%3C/button%3E %20%20%20%20%20%20%3C/div%3E %20%20%20%20%3C/div%3E`,e.content.querySelector(%22textarea%22).value=a.postElement.outerHTML,a.mediaFlag%3F!d%26%26e.content.querySelector(`%23${h}%20b`).remove():e.content.getElementById(h).remove(),a.audioPlayerFlag||e.content.getElementById(i).remove(),e.content.getElementById(j).onclick=async()=%3E{document.getElementById(j).style=%22cursor:%20progress%22;let%20a=await%20b(!0);c(a,!0)},e.content.getElementById(k).onclick=function(){document.getElementById(g).remove()};let%20f=document.getElementById(g);f%3Ff.replaceWith(e.content):document.body.append(e.content)}const%20d=%22https://staging.cohostcdn.org/%22,e=%22https://cohost.org/static/%22,f=%22cohost-wc-%22,g=f+%22bookmarklet-output%22,h=f+%22bookmarklet-media-flag%22,i=f+%22audio-player-flag%22,j=f+%22data-uris-button%22,k=f+%22dismiss-button%22;let%20l=await%20b(!1);c(l,!1)})()}();
Once you've added it to your bookmarks tab, you can click it while on any single post page on cohost (like this one) to generate the custom element for that post. Give it a shot! And act quickly, before cohost shuts down for good
After generating the custom element, you can click the button labeled... Convert avatars and custom emoji to data URIs ...to durably encode these small images in the HTML itself. This is strongly recommended for future-proofing. It won't convert other images, though, so you may still need to rehost some pictures on your website.
Clicking the button will load the avatars & emoji through this website as a proxy due to CORS restrictions. I'm telling you this in case you consider it to be a privacy concern, although rest assured I don't care whose avatars are going through the proxy as long as you're not abusing the service.
You shouldn't need to know these details if you're using the bookmarklet, but here they are for reference:
<cohost-post>
The outermost container of a post. Should either contain the HTML for a standalone post or
<cohost-shared-item>
elements for a shared post.
All attributes are optional.
<cohost-shared-item>
s)
<cohost-shared-item>
s
<cohost-shared-item>
A shared post inside of a <cohost-post>
. These should always be direct children of the
<cohost-post>
, with no other elements in between.
Attributes are the mostly same as for
<cohost-post>
, with these exceptions:
Q: why do I need a CSS file alongside the JS file? can't web components style themselves?
A: web components can style themselves, but not their children. the CSS file provides styles for the
post contents; in fact, every single rule in it is scoped to the
cohost-post
element.
Q: did you just copy cohost's entire stylesheet?
A: no, I painstakingly hand-selected styles until everything looked right. let me know if I missed something!
Think of this web component as being static for now. It has no interactivity, unless it's displaying an interactive CSS crime.
Known bugs (I'll fix these eventually):
Other limitations (unlikely to change):