Compare commits

..

4 commits

Author SHA1 Message Date
ashastral
cd4b3a9410 I don't wanna break this up into smaller commits 2024-09-20 19:51:12 -07:00
ashastral
6ee7597805 update HTMLElementTagNameMap 2024-09-18 21:33:12 -07:00
ashastral
662c461c94 even more CSS 2024-09-18 21:33:04 -07:00
ashastral
ec6b10c1f2 support year in relative date format 2024-09-18 18:06:27 -07:00
5 changed files with 2232 additions and 211 deletions

View file

@ -1,12 +1,48 @@
(async () => {
const COHOST_CDN_BASE = "https://staging.cohostcdn.org/";
const COHOST_STATIC_BASE = "https://cohost.org/static/";
const CORS_PROXY_BASE = "https://cohost-web-component.meow.garden/";
const OUTPUT_DIV_ID = "cohost-wc-bookmarklet-output";
const MEDIA_FLAG_DIV_ID = "cohost-wc-bookmarklet-media-flag";
const AUDIO_PLAYER_FLAG_DIV_ID = "cohost-wc-audio-player-flag";
async function maybeConvertToDataUri(imageUrl, shouldConvert) {
if (!shouldConvert) { return imageUrl; }
let proxyUrl;
if (imageUrl.startsWith(COHOST_CDN_BASE)) {
proxyUrl = imageUrl.replace(COHOST_CDN_BASE, CORS_PROXY_BASE + "cohostcdn-cors-proxy/");
if (imageUrl.includes("/avatar/")) {
proxyUrl += "?dpr=2&width=32&height=32&fit=cover&auto=webp";
}
} else if (imageUrl.startsWith(COHOST_STATIC_BASE)) {
proxyUrl = imageUrl.replace(COHOST_STATIC_BASE, CORS_PROXY_BASE + "cohost-static-cors-proxy/");
} else {
return imageUrl;
}
let response = await fetch(proxyUrl);
let blob = await response.blob();
return new Promise((resolve) => {
let reader = new FileReader();
reader.onload = () => resolve(reader.result);
reader.readAsDataURL(blob);
});
}
async function assembleWebComponent(dataUris) {
let trpcState = JSON.parse(document.getElementById("trpc-dehydrated-state").innerText);
let postData = trpcState.queries.find((q) => q.queryKey[0].includes("singlePost")).state.data.post;
let postElement = document.createElement("cohost-post");
postElement.setAttribute("avatarSrc", postData.postingProject.avatarURL);
postElement.setAttribute("avatarSrc", await maybeConvertToDataUri(postData.postingProject.avatarURL, dataUris));
postElement.setAttribute("avatarShape", postData.postingProject.avatarShape);
postElement.setAttribute("comments", postData.numComments);
postElement.setAttribute("displayName", postData.postingProject.displayName);
postElement.setAttribute("handle", postData.postingProject.handle);
postElement.setAttribute("permalink", postData.singlePostPageUrl);
if (postData.publishedAt) {
postElement.setAttribute("publishedAt", postData.publishedAt);
}
if (postData.numSharedComments) {
postElement.setAttribute("sharedComments", postData.numSharedComments);
}
if (postData.headline) {
postElement.setAttribute("singlePostHeadline", postData.headline);
}
@ -15,7 +51,7 @@ postElement.setAttribute("tags", postData.tags.join(","));
if (postData.shareTree && postData.shareTree.length) {
postElement.setAttribute("sharedItems", "");
let sharedFrom = postData.shareTree[postData.shareTree.length - 1];
postElement.setAttribute("sharedAvatarSrc", sharedFrom.postingProject.avatarURL);
postElement.setAttribute("sharedAvatarSrc", await maybeConvertToDataUri(sharedFrom.postingProject.avatarURL, dataUris));
postElement.setAttribute("sharedAvatarShape", sharedFrom.postingProject.avatarShape);
postElement.setAttribute("sharedDisplayName", sharedFrom.postingProject.displayName);
postElement.setAttribute("sharedHandle", sharedFrom.postingProject.handle);
@ -26,7 +62,7 @@ if (postData.shareTree && postData.shareTree.length) {
continue;
}
let sharedElement = document.createElement("cohost-shared-item");
sharedElement.setAttribute("avatarSrc", sharedPost.postingProject.avatarURL);
sharedElement.setAttribute("avatarSrc", await maybeConvertToDataUri(sharedPost.postingProject.avatarURL, dataUris));
sharedElement.setAttribute("avatarShape", sharedPost.postingProject.avatarShape);
sharedElement.setAttribute("displayName", sharedPost.postingProject.displayName);
sharedElement.setAttribute("handle", sharedPost.postingProject.handle);
@ -34,35 +70,83 @@ if (postData.shareTree && postData.shareTree.length) {
sharedElement.setAttribute("headline", sharedPost.headline);
}
sharedElement.setAttribute("permalink", sharedPost.singlePostPageUrl);
if (sharedPost.publishedAt) {
sharedElement.setAttribute("publishedAt", sharedPost.publishedAt);
}
sharedElement.setAttribute("tags", sharedPost.tags.join(","));
let sharedIdElement = document.getElementById(`post-${sharedPost.postId}`);
if (sharedIdElement) {
sharedElement.innerHTML = sharedIdElement.parentElement.querySelector("[data-post-body]").innerHTML;
let sharedPostBody = sharedIdElement.parentElement.querySelector("[data-post-body]");
if (sharedPostBody) {
sharedElement.innerHTML = sharedPostBody.innerHTML;
}
}
postElement.appendChild(sharedElement);
}
} else {
let postIdElement = document.getElementById(`post-${postData.postId}`);
if (postIdElement) {
postElement.innerHTML = document.getElementById(`post-${postData.postId}`).parentElement.querySelector("[data-post-body]").innerHTML;
let postBody = document.getElementById(`post-${postData.postId}`).parentElement.querySelector("[data-post-body]");
if (postBody) {
postElement.innerHTML = postBody.innerHTML;
}
}
let outputContainer = document.createElement("div");
outputContainer.id = "cohost-wc-bookmarklet-output"
outputContainer.style = "position: fixed; bottom: 0.5rem; left: 0.5rem; display: flex; flex-direction: column; gap: 0.25rem; font-size: 0.875rem; z-index: 100";
let outputTextarea = document.createElement("textarea");
outputTextarea.value = postElement.outerHTML;
outputTextarea.rows = 5;
outputTextarea.cols = 20;
outputTextarea.style = "font-size: inherit; line-height: 1.4";
outputContainer.append(outputTextarea);
let outputDismissButton = document.createElement("button");
outputDismissButton.innerText = "dismiss";
outputDismissButton.onclick = function() { outputContainer.remove(); }
outputContainer.append(outputDismissButton);
document.body.append(outputContainer);
}
let mediaFlag = false;
let audioPlayerFlag = false;
for (const mediaElement of postElement.querySelectorAll('img, audio')) {
if (mediaElement.tagName == 'AUDIO') { audioPlayerFlag = true; }
let src = mediaElement.getAttribute("src");
if (dataUris && src.startsWith(COHOST_STATIC_BASE)) {
mediaElement.setAttribute('src', await maybeConvertToDataUri(src, true));
} else if (src.startsWith("https://cohost.org/") || src.startsWith(COHOST_CDN_BASE)) {
mediaFlag = true;
}
}
return {
postElement,
mediaFlag,
audioPlayerFlag,
};
}
async function renderOutput(webComponent, dataUris) {
let template = document.createElement('template');
template.innerHTML = `<div id="${OUTPUT_DIV_ID}" class="fixed bottom-0 p-2 flex flex-col items-center gap-2 font-sm bg-background co-themed-box border rounded-lg">
<textarea rows="5" cols="35" style="font-size: inherit; line-height: 1.4"></textarea>
<div id="${MEDIA_FLAG_DIV_ID}" class="co-info-box co-info text-sm mx-auto w-full rounded-lg p-3">
Post <b>still</b> contains media hosted on cohost.<br />Consider rehosting any images or audio files on your website.
</div>
<div id="${AUDIO_PLAYER_FLAG_DIV_ID}" class="co-info-box co-info text-sm mx-auto w-full rounded-lg p-3">
cohost's audio player doesn't function correctly (yet).
</div>
<div class="flex flex-row gap-1">
<button id="cohost-wc-data-uris-button" class="rounded-lg bg-cherry py-2 px-4 text-sm font-bold text-notWhite hover:bg-cherry-600 active:bg-cherry-700">Convert avatars and custom emoji to data URIs</button>
<button id="cohost-wc-dismiss-button" class="rounded-lg bg-cherry py-2 px-4 text-sm font-bold text-notWhite hover:bg-cherry-600 active:bg-cherry-700">dismiss</button>
</div>
</div>`;
template.content.querySelector('textarea').value = webComponent.postElement.outerHTML;
if (!webComponent.mediaFlag) {
template.content.querySelector(`#${MEDIA_FLAG_DIV_ID}`).remove();
} else if (!dataUris) {
template.content.querySelector(`#${MEDIA_FLAG_DIV_ID} b`).remove();
}
if (!webComponent.audioPlayerFlag) {
template.content.querySelector(`#${AUDIO_PLAYER_FLAG_DIV_ID}`).remove();
}
template.content.querySelector("#cohost-wc-data-uris-button").onclick = async () => {
let webComponent = await assembleWebComponent(true);
renderOutput(webComponent, true);
}
template.content.querySelector("#cohost-wc-dismiss-button").onclick = function() {
document.querySelector(`#${OUTPUT_DIV_ID}`).remove();
}
let existingOutput = document.querySelector(`#${OUTPUT_DIV_ID}`);
if (existingOutput) {
existingOutput.replaceWith(template.content);
} else {
document.body.append(template.content);
}
}
let webComponent = await assembleWebComponent(false);
renderOutput(webComponent, false);
})();

View file

@ -142,7 +142,129 @@ cohost-post p,
cohost-post pre {
margin: 0;
}
.prose {
cohost-post img,
cohost-post video {
max-width: 100%;
height: auto;
}
cohost-post img,
cohost-post svg,
cohost-post video,
cohost-post canvas,
cohost-post audio,
cohost-post iframe,
cohost-post embed,
cohost-post object {
display: block;
}
cohost-post button,
cohost-post input,
cohost-post optgroup,
cohost-post select,
cohost-post textarea {
font-family: inherit;
font-feature-settings: inherit;
font-variation-settings: inherit;
font-size: 100%;
font-weight: inherit;
line-height: inherit;
letter-spacing: inherit;
color: inherit;
margin: 0;
padding: 0;
}
cohost-post button,
cohost-post select {
text-transform: none;
}
cohost-post button,
cohost-post [role="button"] {
cursor: pointer;
}
cohost-post button,
cohost-post input:where([type="button"]),
cohost-post input:where([type="reset"]),
cohost-post input:where([type="submit"]) {
-webkit-appearance: button;
background-color: transparent;
background-image: none;
}
cohost-post
.prose
:where(code):not(
:where([class~="not-prose"], [class~="not-prose"] *)
)::after {
content: "`";
}
cohost-post
.prose
:where(code):not(
:where([class~="not-prose"], [class~="not-prose"] *)
)::before {
content: "`";
}
cohost-post
.prose
:where(code):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
color: var(--tw-prose-code);
font-weight: 600;
font-size: 0.875em;
}
cohost-post code,
cohost-post kbd,
cohost-post samp,
cohost-post pre {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
"Liberation Mono", "Courier New", monospace;
font-feature-settings: normal;
font-variation-settings: normal;
font-size: 1em;
}
cohost-post
.prose
:where(pre):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
color: var(--tw-prose-pre-code);
background-color: var(--tw-prose-pre-bg);
overflow-x: auto;
font-weight: 400;
font-size: 0.875em;
line-height: 1.7142857;
margin-top: 1.7142857em;
margin-bottom: 1.7142857em;
border-radius: 0.375rem;
padding-top: 0.8571429em;
padding-inline-end: 1.1428571em;
padding-bottom: 0.8571429em;
padding-inline-start: 1.1428571em;
}
cohost-post
.prose
:where(pre code):not(
:where([class~="not-prose"], [class~="not-prose"] *)
)::after {
content: none;
}
cohost-post
.prose
:where(pre code):not(
:where([class~="not-prose"], [class~="not-prose"] *)
)::before {
content: none;
}
cohost-post
.prose
:where(pre code):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
background-color: transparent;
border-width: 0;
border-radius: 0;
padding: 0;
font-weight: inherit;
color: inherit;
font-size: inherit;
font-family: inherit;
line-height: inherit;
}
cohost-post .prose {
color: light-dark(#191919, #fff9f2);
--tw-prose-body: light-dark(#374151, #fff9f2);
--tw-prose-headings: light-dark(#111827, #fff9f2);
@ -236,6 +358,32 @@ cohost-post .co-embed {
cohost-post .co-ui-text {
color: light-dark(rgb(25 25 25), rgb(255 249 242));
}
cohost-post .mask {
-webkit-mask-size: contain;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat;
-webkit-mask-position: center;
mask-position: center;
}
cohost-post .mask-squircle {
-webkit-mask-image: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNMTAwIDBDMjAgMCAwIDIwIDAgMTAwczIwIDEwMCAxMDAgMTAwIDEwMC0yMCAxMDAtMTAwUzE4MCAwIDEwMCAweiIvPjwvc3ZnPg==);
mask-image: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNMTAwIDBDMjAgMCAwIDIwIDAgMTAwczIwIDEwMCAxMDAgMTAwIDEwMC0yMCAxMDAtMTAwUzE4MCAwIDEwMCAweiIvPjwvc3ZnPg==);
}
cohost-post .mask-roundrect {
border-radius: 12.5%;
}
cohost-post .mask-circle {
border-radius: 9999px;
}
cohost-post .co-embedded-ask {
border-color: rgb(229 107 111);
background-color: light-dark(rgb(255 249 242), rgb(25 25 25));
color: light-dark(rgb(25 25 25), rgb(255 249 242));
}
cohost-post .co-attribution {
color: light-dark(rgb(130 127 124), rgb(160 156 152));
}
/* Hide interactive UI */
cohost-post button.co-link-button {
@ -244,31 +392,241 @@ cohost-post button.co-link-button {
/* Tailwind properties */
cohost-post .text-right {
text-align: right;
cohost-post .accent-mango {
accent-color: #ffab5c;
}
cohost-post .p-3 {
padding: 0.75rem;
cohost-post .align-middle {
vertical-align: middle;
}
cohost-post .aspect-square {
aspect-ratio: 1 / 1;
}
cohost-post .bg-cherry {
background-color: rgb(131 37 79);
}
cohost-post .bg-notBlack {
background-color: rgb(25 25 25);
}
cohost-post .border {
border-width: 1px;
}
cohost-post .break-words {
overflow-wrap: break-word;
}
cohost-post .col-start-1 {
grid-column-start: 1;
}
cohost-post .col-start-2 {
grid-column-start: 2;
}
cohost-post .content-start {
align-content: flex-start;
}
cohost-post .cursor-pointer {
cursor: pointer;
}
cohost-post .flex {
display: flex;
}
cohost-post .flex-1 {
flex: 1 1 0%;
}
cohost-post .flex-col {
flex-direction: column;
}
cohost-post .flex-grow {
flex-grow: 1;
}
cohost-post .flex-initial {
flex: 0 1 auto;
}
cohost-post .flex-nowrap {
flex-wrap: nowrap;
}
cohost-post .flex-row {
flex-direction: row;
}
cohost-post .flex-shrink-0 {
flex-shrink: 0;
}
cohost-post .font-bold {
font-weight: 700;
}
cohost-post .gap-x-3 {
-moz-column-gap: 0.75rem;
column-gap: 0.75rem;
}
cohost-post .gap-y-2 {
row-gap: 0.5rem;
}
cohost-post .grid {
display: grid;
}
cohost-post .grid-rows-\[2rem_1fr\] {
grid-template-rows: 2rem 1fr;
}
cohost-post .grid-cols-\[2rem_1fr\] {
grid-template-columns: 2rem 1fr;
}
cohost-post .h-6 {
height: 1.5rem;
}
cohost-post .h-8 {
height: 2rem;
}
cohost-post .h-9 {
height: 2.25rem;
}
cohost-post .h-full {
height: 100%;
}
cohost-post .hover\:underline:hover {
text-decoration: underline;
}
cohost-post .inline-block {
display: inline-block;
}
cohost-post .items-center {
align-items: center;
}
cohost-post .justify-between {
justify-content: space-between;
}
cohost-post .leading-8 {
line-height: 2rem;
}
cohost-post .m-0 {
margin: 0;
}
cohost-post .m-3 {
margin: 0.75rem;
}
cohost-post .m-auto {
margin: auto;
}
cohost-post .mt-0 {
margin-top: 0px;
}
cohost-post .mx-1 {
margin-left: 0.25rem;
margin-right: 0.25rem;
}
cohost-post .my-4 {
margin-top: 1rem;
margin-bottom: 1rem;
}
cohost-post .object-contain {
-o-object-fit: contain;
object-fit: contain;
}
cohost-post .object-cover {
-o-object-fit: cover;
object-fit: cover;
}
cohost-post .overflow-hidden {
overflow: hidden;
}
cohost-post .break-words {
overflow-wrap: break-word;
cohost-post .p-2 {
padding: 0.5rem;
}
cohost-post .p-3 {
padding: 0.75rem;
}
cohost-post .px-3 {
padding-left: 0.75rem;
padding-right: 0.75rem;
}
cohost-post .relative {
position: relative;
}
cohost-post .rounded-lg {
border-radius: 0.5rem;
}
cohost-post .row-start-1 {
grid-row-start: 1;
}
cohost-post .sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
cohost-post .tabular-nums {
--tw-numeric-spacing: tabular-nums;
font-variant-numeric: var(--tw-ordinal) var(--tw-slashed-zero)
var(--tw-numeric-figure) var(--tw-numeric-spacing)
var(--tw-numeric-fraction);
}
cohost-post .text-right {
text-align: right;
}
cohost-post .text-xs {
font-size: 0.75rem;
line-height: 1rem;
}
cohost-post .w-6 {
width: 1.5rem;
}
cohost-post .w-8 {
width: 2rem;
}
cohost-post .w-9 {
width: 2.25rem;
}
cohost-post .w-\[76px\] {
width: 76px;
}
cohost-post .w-full {
width: 100%;
}
/* CSS variables */
cohost-post {
--color-notWhite: 255 249 242;
--color-notBlack: 25 25 25;
--color-cherry: 131 37 79;
--color-strawberry: 229 107 111;
--color-mango: 255 171 92;
--color-longan: 255 216 168;
--color-text: var(--color-notWhite);
--color-bg-text: var(--color-notBlack);
--color-foreground-100: 253 206 224;
--color-foreground-200: 238 173 199;
--color-foreground-300: 211 116 155;
--color-foreground-400: 174 68 115;
--color-foreground-500: 131 37 79;
--color-foreground-600: 103 26 61;
--color-foreground-700: 81 17 46;
--color-foreground-800: 59 9 32;
--color-foreground: var(--color-cherry);
--color-secondary-200: 244 187 187;
--color-secondary-300: 238 153 155;
--color-secondary-400: 229 107 111;
--color-secondary-600: 164 42 47;
--color-secondary-700: 123 27 31;
--color-secondary: var(--color-strawberry);
--color-tertiary: var(--color-longan);
--color-tertiary-200: 255 229 196;
--color-tertiary-300: 255 216 168;
--color-tertiary-400: 255 202 122;
--color-tertiary-500: 183 133 61;
--color-tertiary-600: 183 133 61;
--color-tertiary-700: 132 94 38;
--color-accent: var(--color-mango);
--color-background: var(--color-notWhite);
--color-sidebar-bg: var(--color-notWhite);
--color-sidebar-text: var(--color-notBlack);
--color-sidebar-accent: var(--color-cherry);
--color-compose-button: var(--color-foreground);
--color-compose-button-400: var(--color-foreground-400);
--color-compose-button-600: var(--color-foreground-600);
}
/* TODO: light */
@media (prefers-color-scheme: dark) {
cohost-post {
--color-notWhite: 255 249 242;
@ -310,5 +668,7 @@ cohost-post .px-3 {
--color-compose-button-400: var(--color-foreground-400);
--color-compose-button-600: var(--color-foreground-600);
--emoji-scale: 1.25em;
--tw-border-opacity: 1;
--tw-bg-opacity: 1;
}
}

1820
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -14,18 +14,27 @@
"lint:lit-analyzer": "lit-analyzer",
"format": "prettier \"**/*.{cjs,html,js,json,md,ts}\" --ignore-path ./.eslintignore --write",
"serve": "concurrently \"tsc --watch\" \"wds --watch\"",
"serve:prod": "MODE=prod npm run serve"
"serve:prod": "MODE=prod npm run serve",
"rollup": "rollup -c",
"publish:dev": "rsync -avuz --delete build/ web@dev.xtil.net:/home/web/static/cohost-web-component.meow.garden/",
"publish:prod": "rsync -avuz --delete build/ web@xtil.net:/home/web/static/cohost-web-component.meow.garden/"
},
"dependencies": {
"lit": "^3.2.0"
},
"devDependencies": {
"@rollup/plugin-node-resolve": "^15.2.3",
"@rollup/plugin-terser": "^0.4.4",
"@web/dev-server": "^0.4.6",
"@web/rollup-plugin-copy": "^0.5.1",
"@web/rollup-plugin-html": "^2.3.0",
"@webcomponents/webcomponentsjs": "^2.8.0",
"concurrently": "^9.0.1",
"eslint": "^9.10.0",
"lit-analyzer": "^2.0.3",
"prettier": "^3.3.3",
"rollup": "^4.22.0",
"rollup-plugin-summary": "^2.0.1",
"typescript": "^5.6.2"
},
"customElements": "custom-elements.json"

View file

@ -8,8 +8,15 @@ function formatRelativeTime(date: Date) {
const secondsPerMinute = 60;
const minutesPerHour = 60;
const hoursPerDay = 24;
const daysPerMonth = 30; // JS date handling moment
const daysPerMonth = 365.2425 / 12; // not how it works!
const daysPerYear = 365.2425; // still not how it works!!
const intervals = {
year:
millisecondsPerSecond *
secondsPerMinute *
minutesPerHour *
hoursPerDay *
daysPerYear,
month:
millisecondsPerSecond *
secondsPerMinute *
@ -379,6 +386,9 @@ export class CohostPost extends LitElement {
@property({ type: Number })
comments = 0;
@property({ type: Number })
sharedComments = 0;
override render() {
let displayTimestamp = undefined;
let relativeTimestamp = undefined;
@ -577,8 +587,13 @@ export class CohostPost extends LitElement {
<a
href="${ifDefined(this.permalink)}#comments"
class="text-sm hover:underline"
>${this.comments} comment${this.comments == 1 ? "" : "s"}</a
>
${this.comments}
comment${this.comments == 1 ? "" : "s"}${when(
this.sharedComments,
() => html` + ${this.sharedComments} on shared posts`,
)}
</a>
</div>
<div class="controls">
<svg
@ -749,5 +764,6 @@ export class CohostSharedItem extends LitElement {
declare global {
interface HTMLElementTagNameMap {
"cohost-post": CohostPost;
"cohost-shared-item": CohostSharedItem;
}
}