Compare commits

...

3 commits

Author SHA1 Message Date
ashastral
aab735bd39 add bookmarklet 2024-09-18 17:57:16 -07:00
ashastral
a31ab6b8ea add shared items, relative dates, tags; CSS fixes 2024-09-18 17:42:45 -07:00
ashastral
35a477bf07 add concurrently to rebuild TS during serve 2024-09-18 17:42:01 -07:00
6 changed files with 930 additions and 316 deletions

1
bookmarklet-uri.txt Normal file
View file

@ -0,0 +1 @@
javascript:void%20function(){let%20a=JSON.parse(document.getElementById(%22trpc-dehydrated-state%22).innerText),b=a.queries.find(a=%3Ea.queryKey[0].includes(%22singlePost%22)).state.data.post,c=document.createElement(%22cohost-post%22);if(c.setAttribute(%22avatarSrc%22,b.postingProject.avatarURL),c.setAttribute(%22avatarShape%22,b.postingProject.avatarShape),c.setAttribute(%22displayName%22,b.postingProject.displayName),c.setAttribute(%22handle%22,b.postingProject.handle),c.setAttribute(%22permalink%22,b.singlePostPageUrl),c.setAttribute(%22publishedAt%22,b.publishedAt),b.headline%26%26c.setAttribute(%22singlePostHeadline%22,b.headline),c.setAttribute(%22tags%22,b.tags.join(%22,%22)),b.shareTree%26%26b.shareTree.length){c.setAttribute(%22sharedItems%22,%22%22);let%20a=b.shareTree[b.shareTree.length-1];c.setAttribute(%22sharedAvatarSrc%22,a.postingProject.avatarURL),c.setAttribute(%22sharedAvatarShape%22,a.postingProject.avatarShape),c.setAttribute(%22sharedDisplayName%22,a.postingProject.displayName),c.setAttribute(%22sharedHandle%22,a.postingProject.handle);let%20d=[...b.shareTree,b];for(sharedPost%20of%20d){if(null!==sharedPost.transparentShareOfPostId)continue;let%20a=document.createElement(%22cohost-shared-item%22);a.setAttribute(%22avatarSrc%22,sharedPost.postingProject.avatarURL),a.setAttribute(%22avatarShape%22,sharedPost.postingProject.avatarShape),a.setAttribute(%22displayName%22,sharedPost.postingProject.displayName),a.setAttribute(%22handle%22,sharedPost.postingProject.handle),sharedPost.headline%26%26a.setAttribute(%22headline%22,sharedPost.headline),a.setAttribute(%22permalink%22,sharedPost.singlePostPageUrl),a.setAttribute(%22publishedAt%22,sharedPost.publishedAt),a.setAttribute(%22tags%22,sharedPost.tags.join(%22,%22));let%20b=document.getElementById(`post-${sharedPost.postId}`);b%26%26(a.innerHTML=b.parentElement.querySelector(%22[data-post-body]%22).innerHTML),c.appendChild(a)}}else{let%20a=document.getElementById(`post-${b.postId}`);a%26%26(c.innerHTML=document.getElementById(`post-${b.postId}`).parentElement.querySelector(%22[data-post-body]%22).innerHTML)}let%20d=document.createElement(%22div%22);d.id=%22cohost-wc-bookmarklet-output%22,d.style=%22position:%20fixed;%20bottom:%200.5rem;%20left:%200.5rem;%20display:%20flex;%20flex-direction:%20column;%20gap:%200.25rem;%20font-size:%200.875rem;%20z-index:%20100%22;let%20e=document.createElement(%22textarea%22);e.value=c.outerHTML,e.rows=5,e.cols=20,e.style=%22font-size:%20inherit;%20line-height:%201.4%22,d.append(e);let%20f=document.createElement(%22button%22);f.innerText=%22dismiss%22,f.onclick=function(){d.remove()},d.append(f),document.body.append(d)}();

68
bookmarklet.cjs Normal file
View file

@ -0,0 +1,68 @@
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("avatarShape", postData.postingProject.avatarShape);
postElement.setAttribute("displayName", postData.postingProject.displayName);
postElement.setAttribute("handle", postData.postingProject.handle);
postElement.setAttribute("permalink", postData.singlePostPageUrl);
postElement.setAttribute("publishedAt", postData.publishedAt);
if (postData.headline) {
postElement.setAttribute("singlePostHeadline", postData.headline);
}
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("sharedAvatarShape", sharedFrom.postingProject.avatarShape);
postElement.setAttribute("sharedDisplayName", sharedFrom.postingProject.displayName);
postElement.setAttribute("sharedHandle", sharedFrom.postingProject.handle);
let shareTree = [...postData.shareTree, postData];
for (sharedPost of shareTree) {
if (sharedPost.transparentShareOfPostId !== null) {
continue;
}
let sharedElement = document.createElement("cohost-shared-item");
sharedElement.setAttribute("avatarSrc", sharedPost.postingProject.avatarURL);
sharedElement.setAttribute("avatarShape", sharedPost.postingProject.avatarShape);
sharedElement.setAttribute("displayName", sharedPost.postingProject.displayName);
sharedElement.setAttribute("handle", sharedPost.postingProject.handle);
if (sharedPost.headline) {
sharedElement.setAttribute("headline", sharedPost.headline);
}
sharedElement.setAttribute("permalink", sharedPost.singlePostPageUrl);
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;
}
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 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);

View file

@ -1,42 +1,40 @@
/* atkinson-hyperlegible-regular - latin_latin-ext */
/* Atkinson Hyperlegible */
@font-face {
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-display: swap;
font-family: "Atkinson Hyperlegible";
font-style: normal;
font-weight: 400;
src: url("../fonts/atkinson-hyperlegible-v11-latin_latin-ext-regular.woff2")
format("woff2"); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
format("woff2");
}
/* atkinson-hyperlegible-italic - latin_latin-ext */
@font-face {
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-display: swap;
font-family: "Atkinson Hyperlegible";
font-style: italic;
font-weight: 400;
src: url("../fonts/atkinson-hyperlegible-v11-latin_latin-ext-italic.woff2")
format("woff2"); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
format("woff2");
}
/* atkinson-hyperlegible-700 - latin_latin-ext */
@font-face {
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-display: swap;
font-family: "Atkinson Hyperlegible";
font-style: normal;
font-weight: 700;
src: url("../fonts/atkinson-hyperlegible-v11-latin_latin-ext-700.woff2")
format("woff2"); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
format("woff2");
}
/* atkinson-hyperlegible-700italic - latin_latin-ext */
@font-face {
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-display: swap;
font-family: "Atkinson Hyperlegible";
font-style: italic;
font-weight: 700;
src: url("./fonts/atkinson-hyperlegible-v11-latin_latin-ext-700italic.woff2")
format("woff2"); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
format("woff2");
}
/* Keyframes */
@keyframes bounce {
0%,
100% {
@ -111,35 +109,37 @@
}
}
cohost-wc * {
/* Cohost post content styling */
cohost-post * {
box-sizing: border-box;
border-width: 0;
border-style: solid;
border-color: #ded9d3;
}
cohost-wc a {
cohost-post a {
color: inherit;
text-decoration: inherit;
}
cohost-wc hr {
cohost-post hr {
height: 0;
color: inherit;
border-top-width: 1px;
}
cohost-wc blockquote,
cohost-wc dl,
cohost-wc dd,
cohost-wc h1,
cohost-wc h2,
cohost-wc h3,
cohost-wc h4,
cohost-wc h5,
cohost-wc h6,
cohost-wc hr,
cohost-wc figure,
cohost-wc p,
cohost-wc pre {
cohost-post blockquote,
cohost-post dl,
cohost-post dd,
cohost-post h1,
cohost-post h2,
cohost-post h3,
cohost-post h4,
cohost-post h5,
cohost-post h6,
cohost-post hr,
cohost-post figure,
cohost-post p,
cohost-post pre {
margin: 0;
}
.prose {
@ -163,7 +163,9 @@ cohost-wc pre {
font-size: 1em;
line-height: 1.75;
}
.prose :where(h3):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
cohost-post
.prose
:where(h3):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
color: var(--tw-prose-headings);
font-weight: 600;
font-size: 1.25em;
@ -171,15 +173,142 @@ cohost-wc pre {
margin-bottom: 0.6em;
line-height: 1.6;
}
.prose
:where(.prose > :last-child):not(
:where([class~="not-prose"], [class~="not-prose"] *)
) {
margin-bottom: 0;
}
.prose
cohost-post
.prose
:where(.prose > :first-child):not(
:where([class~="not-prose"], [class~="not-prose"] *)
) {
margin-top: 0;
}
cohost-post
.prose
:where(p):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
margin-top: 1.25em;
margin-bottom: 1.25em;
}
cohost-post
.prose
:where(hr):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
border-color: var(--tw-prose-hr);
border-top-width: 1px;
margin-top: 1em;
margin-bottom: 1em;
}
cohost-post
.prose
:where(a):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
text-decoration: underline;
font-weight: 500;
}
cohost-post
.prose
:where(ul):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
list-style-type: disc;
}
cohost-post
.prose
:where(ul > li):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
padding-inline-start: 0.375em;
}
cohost-post
.prose
:where(li):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
margin-top: 0.5em;
margin-bottom: 0.5em;
}
cohost-post
.prose
:where(.prose > :last-child):not(
:where([class~="not-prose"], [class~="not-prose"] *)
) {
margin-bottom: 0;
}
cohost-post
.prose
:where(.prose > :first-child):not(
:where([class~="not-prose"], [class~="not-prose"] *)
) {
margin-top: 0;
}
cohost-post .co-embed {
background-color: light-dark(rgb(255 241 223), rgb(25 25 25));
}
cohost-post .co-ui-text {
color: light-dark(rgb(25 25 25), rgb(255 249 242));
}
/* Hide interactive UI */
cohost-post button.co-link-button {
display: none;
}
/* Tailwind properties */
cohost-post .text-right {
text-align: right;
}
cohost-post .p-3 {
padding: 0.75rem;
}
cohost-post .mt-0 {
margin-top: 0px;
}
cohost-post .my-4 {
margin-top: 1rem;
margin-bottom: 1rem;
}
cohost-post .overflow-hidden {
overflow: hidden;
}
cohost-post .break-words {
overflow-wrap: break-word;
}
cohost-post .px-3 {
padding-left: 0.75rem;
padding-right: 0.75rem;
}
/* TODO: light */
@media (prefers-color-scheme: dark) {
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-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-text: var(--color-notWhite);
--color-bg-text: var(--color-notWhite);
--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: 255 208 172;
--color-secondary-300: 255 191 131;
--color-secondary-400: 255 171 92;
--color-secondary-600: 188 109 40;
--color-secondary-700: 147 74 21;
--color-secondary: var(--color-mango);
--color-accent: var(--color-mango);
--color-background: var(--color-notBlack);
--color-sidebar-bg: var(--color-notBlack);
--color-sidebar-text: var(--color-notWhite);
--color-sidebar-accent: var(--color-mango);
--color-compose-button: var(--color-foreground);
--color-compose-button-400: var(--color-foreground-400);
--color-compose-button-600: var(--color-foreground-600);
--emoji-scale: 1.25em;
}
}

127
package-lock.json generated
View file

@ -13,6 +13,7 @@
"devDependencies": {
"@web/dev-server": "^0.4.6",
"@webcomponents/webcomponentsjs": "^2.8.0",
"concurrently": "^9.0.1",
"eslint": "^9.10.0",
"lit-analyzer": "^2.0.3",
"prettier": "^3.3.3",
@ -1266,6 +1267,46 @@
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"dev": true
},
"node_modules/concurrently": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.0.1.tgz",
"integrity": "sha512-wYKvCd/f54sTXJMSfV6Ln/B8UrfLBKOYa+lzc6CHay3Qek+LorVSBdMVfyewFhRbH0Rbabsk4D+3PL/VjQ5gzg==",
"dev": true,
"dependencies": {
"chalk": "^4.1.2",
"lodash": "^4.17.21",
"rxjs": "^7.8.1",
"shell-quote": "^1.8.1",
"supports-color": "^8.1.1",
"tree-kill": "^1.2.2",
"yargs": "^17.7.2"
},
"bin": {
"conc": "dist/bin/concurrently.js",
"concurrently": "dist/bin/concurrently.js"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/open-cli-tools/concurrently?sponsor=1"
}
},
"node_modules/concurrently/node_modules/supports-color": {
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
"integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
"dev": true,
"dependencies": {
"has-flag": "^4.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/supports-color?sponsor=1"
}
},
"node_modules/content-disposition": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
@ -3159,6 +3200,15 @@
"queue-microtask": "^1.2.2"
}
},
"node_modules/rxjs": {
"version": "7.8.1",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
"integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
"dev": true,
"dependencies": {
"tslib": "^2.1.0"
}
},
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@ -3206,6 +3256,15 @@
"node": ">=8"
}
},
"node_modules/shell-quote": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz",
"integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==",
"dev": true,
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/signal-exit": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
@ -3353,12 +3412,27 @@
"node": ">=18"
}
},
"node_modules/tree-kill": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
"integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==",
"dev": true,
"bin": {
"tree-kill": "cli.js"
}
},
"node_modules/ts-simple-type": {
"version": "2.0.0-next.0",
"resolved": "https://registry.npmjs.org/ts-simple-type/-/ts-simple-type-2.0.0-next.0.tgz",
"integrity": "sha512-A+hLX83gS+yH6DtzNAhzZbPfU+D9D8lHlTSd7GeoMRBjOt3GRylDqLTYbdmjA4biWvq2xSfpqfIDj2l0OA/BVg==",
"dev": true
},
"node_modules/tslib": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz",
"integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==",
"dev": true
},
"node_modules/tsscmp": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz",
@ -4604,6 +4678,32 @@
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"dev": true
},
"concurrently": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.0.1.tgz",
"integrity": "sha512-wYKvCd/f54sTXJMSfV6Ln/B8UrfLBKOYa+lzc6CHay3Qek+LorVSBdMVfyewFhRbH0Rbabsk4D+3PL/VjQ5gzg==",
"dev": true,
"requires": {
"chalk": "^4.1.2",
"lodash": "^4.17.21",
"rxjs": "^7.8.1",
"shell-quote": "^1.8.1",
"supports-color": "^8.1.1",
"tree-kill": "^1.2.2",
"yargs": "^17.7.2"
},
"dependencies": {
"supports-color": {
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
"integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
"dev": true,
"requires": {
"has-flag": "^4.0.0"
}
}
}
},
"content-disposition": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
@ -5997,6 +6097,15 @@
"queue-microtask": "^1.2.2"
}
},
"rxjs": {
"version": "7.8.1",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
"integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
"dev": true,
"requires": {
"tslib": "^2.1.0"
}
},
"safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@ -6024,6 +6133,12 @@
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"dev": true
},
"shell-quote": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz",
"integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==",
"dev": true
},
"signal-exit": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
@ -6131,12 +6246,24 @@
"punycode": "^2.3.1"
}
},
"tree-kill": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
"integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==",
"dev": true
},
"ts-simple-type": {
"version": "2.0.0-next.0",
"resolved": "https://registry.npmjs.org/ts-simple-type/-/ts-simple-type-2.0.0-next.0.tgz",
"integrity": "sha512-A+hLX83gS+yH6DtzNAhzZbPfU+D9D8lHlTSd7GeoMRBjOt3GRylDqLTYbdmjA4biWvq2xSfpqfIDj2l0OA/BVg==",
"dev": true
},
"tslib": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz",
"integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==",
"dev": true
},
"tsscmp": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz",

View file

@ -13,7 +13,7 @@
"lint:eslint": "eslint 'src/**/*.ts'",
"lint:lit-analyzer": "lit-analyzer",
"format": "prettier \"**/*.{cjs,html,js,json,md,ts}\" --ignore-path ./.eslintignore --write",
"serve": "wds --watch",
"serve": "concurrently \"tsc --watch\" \"wds --watch\"",
"serve:prod": "MODE=prod npm run serve"
},
"dependencies": {
@ -22,6 +22,7 @@
"devDependencies": {
"@web/dev-server": "^0.4.6",
"@webcomponents/webcomponentsjs": "^2.8.0",
"concurrently": "^9.0.1",
"eslint": "^9.10.0",
"lit-analyzer": "^2.0.3",
"prettier": "^3.3.3",

View file

@ -1,10 +1,44 @@
import { LitElement, html, css } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import { customElement, property } from "lit/decorators.js";
import { when } from "lit/directives/when.js";
import { ifDefined } from "lit/directives/if-defined.js";
@customElement("cohost-wc")
export class CohostWebComponent extends LitElement {
static override styles = css`
function formatRelativeTime(date: Date) {
const millisecondsPerSecond = 1000;
const secondsPerMinute = 60;
const minutesPerHour = 60;
const hoursPerDay = 24;
const daysPerMonth = 30; // JS date handling moment
const intervals = {
month:
millisecondsPerSecond *
secondsPerMinute *
minutesPerHour *
hoursPerDay *
daysPerMonth,
day:
millisecondsPerSecond * secondsPerMinute * minutesPerHour * hoursPerDay,
hour: millisecondsPerSecond * secondsPerMinute * minutesPerHour,
minute: millisecondsPerSecond * secondsPerMinute,
second: millisecondsPerSecond,
} as const;
const relativeDateFormat = new Intl.RelativeTimeFormat("en", {
style: "short",
});
const diff = date.getTime() - new Date().getTime();
for (const interval in intervals) {
const _interval = interval as keyof typeof intervals;
if (intervals[_interval] <= Math.abs(diff)) {
return relativeDateFormat.format(
Math.trunc(diff / intervals[_interval]),
_interval,
);
}
}
return relativeDateFormat.format(diff / 1000, "second");
}
const COMMON_CSS = css`
:host {
color-scheme: light dark;
font-family: "Atkinson Hyperlegible", ui-sans-serif, system-ui, sans-serif,
@ -41,48 +75,9 @@ export class CohostWebComponent extends LitElement {
pre {
margin: 0;
}
.co-post-box {
width: 100%;
overflow-x: auto;
border-radius: 0.5rem;
background-color: light-dark(white, black);
box-shadow:
0px 4px 5px #00000024,
0px 1px 10px #0000001f,
0px 2px 4px #0003;
}
@media (min-width: 1024px) {
.co-post-box {
max-width: 65ch;
}
}
@media (prefers-color-scheme: dark) {
.co-post-box {
border-width: 1px;
border-color: rgb(74 72 71);
}
}
.co-thread-header {
display: flex;
width: 100%;
flex-direction: row;
align-items: center;
justify-content: space-between;
gap: 0.5rem;
border-top-left-radius: 0.5rem;
border-top-right-radius: 0.5rem;
padding: 0.75rem;
background-color: light-dark(rgb(255 249 242), rgb(25 25 25));
}
.co-thread-header-1 {
line-height: 1;
gap: 0.5rem;
align-items: center;
flex-wrap: wrap;
flex-direction: row;
flex: 1 1 0%;
min-width: 0px;
display: flex;
.post-anchor {
position: relative;
top: -5rem;
}
.co-avatar-container {
width: 2rem;
@ -145,7 +140,8 @@ export class CohostWebComponent extends LitElement {
.co-project-handle:hover {
text-decoration: underline;
}
.co-thread-header time {
.co-thread-header time,
.co-post-header time {
display: block;
flex: none;
font-size: 0.75rem;
@ -153,24 +149,25 @@ export class CohostWebComponent extends LitElement {
font-variant-numeric: tabular-nums;
color: rgb(130 127 124);
}
.co-thread-header time a:hover {
.co-thread-header time a:hover,
.co-post-header time a:hover {
text-decoration: underline;
}
.co-hairline {
border-color: light-dark(rgb(191 186 181), rgb(74 72 71));
}
.post-title-container {
.post-headline-container {
display: flex;
flex-direction: row;
width: 100%;
padding: 0.75rem;
}
.post-title-a {
.post-headline-a {
overflow-wrap: break-word;
align-self: center;
flex-grow: 1;
}
.post-title-a:hover {
.post-headline-a:hover {
text-decoration: underline;
}
.prose {
@ -194,8 +191,7 @@ export class CohostWebComponent extends LitElement {
font-size: 1em;
line-height: 1.75;
}
.prose
:where(h3):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
.prose :where(h3):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
color: var(--tw-prose-headings);
font-weight: 600;
font-size: 1.25em;
@ -222,13 +218,80 @@ export class CohostWebComponent extends LitElement {
isolation: isolate;
position: relative;
}
.post-body-section {
padding-left: 0.75rem;
padding-right: 0.75rem;
.co-tags-container {
width: 100%;
max-width: 100%;
padding: 0.75rem;
}
.co-tags {
position: relative;
width: 100%;
overflow-y: hidden;
overflow-wrap: break-word;
overflow: hidden;
margin-top: 1rem;
margin-bottom: 1rem;
line-height: 1;
color: light-dark(rgb(104 102 100), rgb(191 186 181));
}
.co-tags a {
display: inline-block;
margin-right: 0.5rem;
font-size: 0.875rem;
line-height: 1.25rem;
}
.co-tags a:hover {
text-decoration: underline;
}
`;
@customElement("cohost-post")
export class CohostPost extends LitElement {
static override styles = css`
${COMMON_CSS}
.co-post-box {
width: 100%;
overflow-x: auto;
border-radius: 0.5rem;
background-color: light-dark(white, black);
box-shadow:
0px 4px 5px #00000024,
0px 1px 10px #0000001f,
0px 2px 4px #0003;
}
@media (min-width: 1024px) {
.co-post-box {
max-width: 65ch;
}
}
@media (prefers-color-scheme: dark) {
.co-post-box {
border-width: 1px;
border-color: rgb(74 72 71);
}
}
.co-thread-header {
display: flex;
width: 100%;
flex-direction: row;
align-items: center;
justify-content: space-between;
gap: 0.5rem;
border-top-left-radius: 0.5rem;
border-top-right-radius: 0.5rem;
padding: 0.75rem;
background-color: light-dark(rgb(255 249 242), rgb(25 25 25));
}
.co-thread-header-1 {
line-height: 1;
gap: 0.5rem;
align-items: center;
flex-wrap: wrap;
flex-direction: row;
flex: 1 1 0%;
min-width: 0px;
display: flex;
}
.header-share-icon {
width: 1.25rem;
height: 1.25rem;
}
.co-thread-footer {
width: 100%;
@ -281,41 +344,50 @@ export class CohostWebComponent extends LitElement {
displayName = "Display Name";
@property()
username = "username";
handle = "handle";
@property()
iso8601Timestamp = "2022-06-28T18:03:00Z";
publishedAt: string | undefined;
@property()
permalink = "https://cohost.org/";
permalink: string | undefined;
@property({ type: Boolean })
sharedItems: boolean = false;
@property()
postId: string | undefined = undefined;
postId: string | undefined;
@property()
postTitle: string | undefined = undefined;
singlePostHeadline: string | undefined;
@property()
sharedAvatarSrc: string | undefined = undefined;
sharedAvatarSrc: string | undefined;
@property()
sharedDisplayName: string | undefined = undefined;
sharedAvatarShape: string | undefined;
@property()
sharedUsername: string | undefined = undefined;
sharedDisplayName: string | undefined;
@property()
tags: string | undefined = undefined;
sharedHandle: string | undefined;
@property()
tags: string | undefined;
@property({ type: Number })
comments = 0;
@state()
protected _displayTimestamp = new Date(
this.iso8601Timestamp,
).toLocaleString();
override render() {
let displayTimestamp = undefined;
let relativeTimestamp = undefined;
if (this.publishedAt) {
let date = new Date(this.publishedAt);
displayTimestamp = date.toLocaleString();
relativeTimestamp = formatRelativeTime(date);
}
return html`
<article
data-theme="both"
@ -324,6 +396,9 @@ export class CohostWebComponent extends LitElement {
>
<header class="co-thread-header">
<div class="co-thread-header-1">
${when(
this.avatarSrc,
() => html`
<div class="co-avatar-container mask">
<img
src="${this.avatarSrc}"
@ -331,91 +406,176 @@ export class CohostWebComponent extends LitElement {
alt="${this.displayName}"
/>
</div>
`,
)}
${when(
this.displayName,
() => html`
<a
rel="author"
href="https://cohost.org/${this.username}"
href="https://cohost.org/${this.handle}"
class="co-project-display-name"
title="${this.displayName}"
data-url-popovers=""
style="user-select: none;"
data-longpress-event="true"
>${this.displayName}</a
><a
href="https://cohost.org/${this.username}"
>
`,
)}
${when(
this.handle,
() =>
html` <a
href="https://cohost.org/${this.handle}"
class="co-project-handle"
data-url-popovers=""
style="user-select: none;"
data-longpress-event="true"
>@${this.username}</a
><time
datetime="${this.iso8601Timestamp}"
class="block flex-none text-xs tabular-nums text-gray-500"
title="${this._displayTimestamp}"
><a href="${this.permalink}" class="hover:underline"
>${this._displayTimestamp}</a
>@${this.handle}</a
>`,
)}
${when(
this.publishedAt,
() => html`
<time
datetime="${ifDefined(this.publishedAt)}"
title="${ifDefined(displayTimestamp)}"
><a
href="${ifDefined(this.permalink)}"
class="hover:underline"
>${relativeTimestamp}</a
></time
>
</div>
<!--<button
id="headlessui-menu-button"
type="button"
aria-haspopup="menu"
aria-expanded="false"
data-headlessui-state=""
>
`,
)}
${when(
this.sharedAvatarSrc ||
this.sharedAvatarShape ||
this.sharedDisplayName ||
this.sharedHandle,
() => html`
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
fill="currentColor"
aria-hidden="true"
class="co-action-button h-6 w-6 transition-transform ui-open:rotate-90"
class="co-project-display-name header-share-icon"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M6.75 12a.75.75 0 11-1.5 0 .75.75 0 011.5 0zM12.75 12a.75.75 0 11-1.5 0 .75.75 0 011.5 0zM18.75 12a.75.75 0 11-1.5 0 .75.75 0 011.5 0z"
fill-rule="evenodd"
d="M4.755 10.059a7.5 7.5 0 0112.548-3.364l1.903 1.903h-3.183a.75.75 0 100 1.5h4.992a.75.75 0 00.75-.75V4.356a.75.75 0 00-1.5 0v3.18l-1.9-1.9A9 9 0 003.306 9.67a.75.75 0 101.45.388zm15.408 3.352a.75.75 0 00-.919.53 7.5 7.5 0 01-12.548 3.364l-1.902-1.903h3.183a.75.75 0 000-1.5H2.984a.75.75 0 00-.75.75v4.992a.75.75 0 001.5 0v-3.18l1.9 1.9a9 9 0 0015.059-4.035.75.75 0 00-.53-.918z"
clip-rule="evenodd"
></path>
</svg>
</button>-->
`,
)}
${when(
this.sharedAvatarSrc,
() => html`
<div class="co-avatar-container mask">
<img
src="${ifDefined(this.sharedAvatarSrc)}"
class="co-avatar mask mask-${this.sharedAvatarShape}"
alt="${ifDefined(this.sharedDisplayName)}"
/>
</div>
`,
)}
${when(
this.sharedDisplayName,
() => html`
<a
href="https://cohost.org/${this.sharedHandle}"
class="co-project-display-name"
title="${ifDefined(this.sharedDisplayName)}"
data-url-popovers=""
style="user-select: none;"
data-longpress-event="true"
>${this.sharedDisplayName}</a
>
`,
)}
${when(
this.sharedHandle,
() =>
html` <a
href="https://cohost.org/${this.sharedHandle}"
class="co-project-handle"
data-url-popovers=""
style="user-select: none;"
data-longpress-event="true"
>@${this.sharedHandle}</a
>`,
)}
</div>
</header>
<hr class="co-hairline" />
<div>
${when(
this.postId,
() => html`
<div id="post-${this.postId}" class="relative -top-20"></div>
<div id="post-${this.postId}" class="post-anchor"></div>
`,
)}
${when(
this.sharedItems,
() => html`<slot></slot>`,
() => html`
<div class="post-body-container">
${when(
this.postTitle,
this.singlePostHeadline,
() => html`
<div class="post-title-container flex w-full flex-row p-3">
<a href="${this.permalink}" class="post-title-a prose"
><h3>${this.postTitle}</h3></a
<div
class="post-headline-container flex w-full flex-row p-3"
>
<a
href="${ifDefined(this.permalink)}"
class="post-headline-a prose"
><h3>${this.singlePostHeadline}</h3></a
>
</div>
`,
)}
<div
class="post-body relative overflow-hidden supports-[overflow:clip]:overflow-clip isolate co-contain-paint"
class="post-body"
data-post-body="true"
data-testid="post-body"
>
<div class="post-body-section prose">
<slot></slot>
</div>
${when(
this.tags && this.tags.length,
() => html`
<div class="co-tags-container">
<div
class="co-tags relative w-full overflow-y-hidden break-words leading-none "
>
<div>
${(this.tags || "")
.split(",")
.map(
(tag) => html`
<a href="https://cohost.org/tagged/${tag}"
>#${tag}</a
>
`,
)}
</div>
</div>
</div>
`,
)}
</div>
`,
)}
</div>
<hr class="co-hairline" />
<footer class="co-thread-footer">
<div class="co-thread-footer-1">
<div class="comments">
<a
href="${this.permalink}#comments"
href="${ifDefined(this.permalink)}#comments"
class="text-sm hover:underline"
>${this.comments} comment${this.comments == 1 ? "" : "s"}</a
>
@ -458,8 +618,136 @@ export class CohostWebComponent extends LitElement {
}
}
declare global {
interface HTMLElementTagNameMap {
"cohost-wc": CohostWebComponent;
@customElement("cohost-shared-item")
export class CohostSharedItem extends LitElement {
static override styles = css`
${COMMON_CSS}
.co-post-header {
display: flex;
flex-direction: flex-row;
flex-wrap: wrap;
align-items: center;
gap: 0.5rem;
padding-left: 0.75rem;
padding-right: 0.75rem;
padding-top: 0.5rem;
padding-bottom: 0.5rem;
background-color: light-dark(rgb(255 249 242), rgb(25 25 25));
}
`;
@property()
avatarSrc =
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAACuUlEQVRYR9WXPYwpURTHz3QbDdFJdqOxEWJXT7EVsgoRCRK6jSgUZCVb2ZJ2V7ONoBIfpVLUVBoiohAKQie7jejue+e+3HljPnzMWzbvVmbm3vv/nf85Z8bler0e0ev1cHV1BZcc2+0W1us1cNPplOAPk8kEWq32IgxfX18wmUwAA+eWyyXRaDT0xiUgmDhqbTabPwAGgwGED87lhFhjtVr9BUDvzwkht7cE4FwQSoHJAnw3xD5XFQG+C+JQSvcC/CvEIXHc/yCAWohjxI8GOBXiWPGTAI6FOEX8ZIBDEKeKqwJQglAjrhpADIHXar8jR3WB0ueRRY3P1X7E/l8AYc4vngK5grtYEe4TUgOxUwODwQDsdjtfc/1+H+7v7/lrOYHFYgE3Nzf8nM/PT74jdDodf79UKsHT05OkninA29sbeX5+Bo7jYD6fw/X1NbTbbXC73UAIoYuUorPZbFCtVilouVyGVqsFhUIBgsEgJBIJ8Pv9wCDFAfHvgVQqRfL5vIQOgXCR0WhU7HPcHIFxoIORSASGw6EEOBwOg8fjkbhAHajX6wQniAcCjEYjsFqt0Ol0wOFw0ClCp4RrXl9f6WU2m5W4Fo/HIRaLgcvl2pFRfA9gCpLJJFQqFcB/zGgpRoYiDw8POxsJ64ClkKlg6rrdLni9Xj69QgIKYDabyXg85u+zDWu1Gjw+PtKzAgIVi0U6p9FoSNxiKcAiFkNYLBYIhUKQTqcl5w4K8HsxYYuY+MfHB0Sj0Z0FrCaEnSEmwaJ8f3/nHcJrLMSXlxfZOpIAoIicOLMeuwVToTSEAFhXt7e3sjXBzh0UwOfzkWazCYFAAO7u7iRWYXXncjlqPaYC3cKeZm7JtS5rSXG6FA8ms9kMnE6nJDDscWwtYV4xMlbR4pcXm4dOikcmk6FuSI5mP344/enj+S+rBamTP+UGdwAAAABJRU5ErkJggg==";
@property()
avatarShape = "squircle";
@property()
displayName = "Display Name";
@property()
handle = "handle";
@property()
publishedAt: string | undefined;
@property()
permalink: string | undefined;
@property({ type: Boolean })
sharedItems: boolean = false;
@property()
postId: string | undefined;
@property()
headline: string | undefined;
@property()
tags: string | undefined;
@property({ type: Number })
comments = 0;
override render() {
let displayTimestamp = undefined;
let relativeTimestamp = undefined;
if (this.publishedAt) {
let date = new Date(this.publishedAt);
displayTimestamp = date.toLocaleString();
relativeTimestamp = formatRelativeTime(date);
}
return html`
${when(
this.postId,
() => html` <div id="post-${this.postId}" class="post-anchor"></div> `,
)}
<div class="co-post-header">
<div class="co-avatar-container mask">
<img
src="${this.avatarSrc}"
class="co-avatar mask mask-${this.avatarShape}"
alt="${this.displayName}"
/>
</div>
<a
rel="author"
href="https://cohost.org/${this.handle}"
class="co-project-display-name"
title="${this.displayName}"
data-url-popovers=""
style="user-select: none;"
data-longpress-event="true"
>${this.displayName}</a
><a
rel="author"
href="https://cohost.org/${this.handle}"
class="co-project-handle"
data-url-popovers=""
style="user-select: none;"
data-longpress-event="true"
>@${this.handle}</a
>${when(
this.publishedAt,
() =>
html`<time
datetime="${ifDefined(this.publishedAt)}"
title="${ifDefined(displayTimestamp)}"
><a href="${ifDefined(this.permalink)}"
>${relativeTimestamp}</a
></time
>`,
)}
</div>
<div class="post-body-container">
${when(
this.headline,
() => html`
<div class="post-headline-container flex w-full flex-row p-3">
<a
href="${ifDefined(this.permalink)}"
class="post-headline-a prose"
><h3>${this.headline}</h3></a
>
</div>
`,
)}
<div class="post-body" data-post-body="true" data-testid="post-body">
<div class="post-body-section">
<slot></slot>
</div>
</div>
</div>
<hr class="co-hairline" />
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"cohost-post": CohostPost;
}
}