Include all needed files in Dockerfile

Signed-off-by: Marcus Noble <github@marcusnoble.co.uk>
This commit is contained in:
2025-08-22 18:13:18 +01:00
parent 537eaa7236
commit 9a6d7cf4a5
5 changed files with 2 additions and 6 deletions

165
src/index.html Normal file

File diff suppressed because one or more lines are too long

193
src/script.js Normal file
View File

@@ -0,0 +1,193 @@
function imageSrc(value) {
if (value) {
this.setAttribute('src', value);
} else {
this.setAttribute('src', "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==");
}
}
function asHTML(value) {
this.innerHTML = value;
}
function shouldShow(value) {
if (value == false || value == "") {
this.style.display = 'none';
} else {
this.style.display = '';
}
}
function setWidth(value) {
this.style.width = `${value}px`;
}
(() => {
var timeout;
let {bskyPost, config} = bindIt({
config: {
corsProxy: "https://corsproxy.io/?url=",
width: 600,
windowDecoration: true,
showInteractions: true
},
bskyPost: {
url: 'https://bsky.app/profile/averagemarcus.bsky.social/post/3lujs2efm5s2f',
handle: '',
displayName: '',
avatar: '',
text: '',
createdAt: '',
likes: 0,
reposts: 0,
replies: 0,
externalLink: {
exists: false,
title: "",
description: "",
image: "",
domain: ""
},
images: {
exists: false,
image1: "",
image2: "",
image3: "",
image4: "",
}
}
});
function isValidHttpUrl(string) {
let url;
try {
url = new URL(string);
} catch (_) {
return false;
}
return url.protocol === "http:" || url.protocol === "https:";
}
function formatDate(d) {
const months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
var d = new Date(d);
let month = months[d.getMonth()];
let ampm = "AM";
let hour = d.getHours();
if (hour > 12) {
ampm = "PM";
hour = hour - 12;
} else if (hour == 12) {
ampm = "PM";
}
return `${month} ${d.getDate()}, ${d.getFullYear()} at ${hour}:${d.getMinutes()} ${ampm}`;
}
async function loadPost() {
bskyPost.url = document.getElementById('post-url').value
if (!isValidHttpUrl(bskyPost.url)) {
return;
}
let urlParts = bskyPost.url.split('/');
let rKey = urlParts[6];
let handle = urlParts[4];
bskyPost.handle = `@${handle}`;
let profile = await (await fetch(`https://public.api.bsky.app/xrpc/app.bsky.actor.getProfile?actor=${handle}`)).json();
let did = profile.did;
bskyPost.displayName = profile.displayName;
bskyPost.avatar = config.corsProxy + profile.avatar;
let record = await (await fetch(`https://public.api.bsky.app/xrpc/com.atproto.repo.getRecord?repo=${did}&collection=app.bsky.feed.post&rkey=${rKey}`)).json();
console.log(record);
let parts = [];
let body = unescape(encodeURIComponent(record.value.text));
let offset = 0;
if (record.value.facets) {
for (const facet of record.value.facets) {
parts.push(body.substring(0, facet.index.byteStart - offset));
body = body.substring(facet.index.byteStart - offset, body.length)
offset = facet.index.byteStart;
parts.push(body.substring(0, facet.index.byteEnd - offset));
body = body.substring(facet.index.byteEnd - offset, body.length)
offset = facet.index.byteEnd;
}
}
parts.push(body);
let postBody = "";
for (let index = 0; index < parts.length; index++) {
postBody += parts[index];
if (index == parts.length-1) {
} else if (index % 2 == 0) {
postBody += '<span class="link">'
} else {
postBody += '</span>'
}
}
bskyPost.text = decodeURIComponent(escape(postBody)).replaceAll("\n", "<br>");
bskyPost.createdAt = formatDate(record.value.createdAt);
let uri = record.uri;
let postLikes = await (await fetch(`https://public.api.bsky.app/xrpc/app.bsky.feed.getLikes?uri=${uri}&limit=100`)).json();
bskyPost.likes = postLikes.likes.length;
let postReposts = await (await fetch(`https://public.api.bsky.app/xrpc/app.bsky.feed.getRepostedBy?uri=${uri}&limit=100`)).json();
bskyPost.reposts = postReposts.repostedBy.length;
let thread = await (await fetch(`https://public.api.bsky.app/xrpc/app.bsky.feed.getPostThread?uri=${uri}&parentHeight=1&depth=1000`)).json();
bskyPost.replies = thread.thread.replies.length
bskyPost.externalLink.exists = false;
bskyPost.images.exists = false;
bskyPost.images.image1 = "";
bskyPost.images.image2 = "";
bskyPost.images.image3 = "";
bskyPost.images.image4 = "";
if (record.value.embed && record.value.embed["$type"] == "app.bsky.embed.images") {
bskyPost.images.exists = true;
if (record.value.embed.images.length > 0) bskyPost.images.image1 = `${config.corsProxy}https://cdn.bsky.app/img/feed_thumbnail/plain/${did}/${record.value.embed.images[0].image.ref.$link}@jpeg`;
if (record.value.embed.images.length > 1) bskyPost.images.image2 = `${config.corsProxy}https://cdn.bsky.app/img/feed_thumbnail/plain/${did}/${record.value.embed.images[1].image.ref.$link}@jpeg`;
if (record.value.embed.images.length > 2) bskyPost.images.image3 = `${config.corsProxy}https://cdn.bsky.app/img/feed_thumbnail/plain/${did}/${record.value.embed.images[2].image.ref.$link}@jpeg`;
if (record.value.embed.images.length > 3) bskyPost.images.image4 = `${config.corsProxy}https://cdn.bsky.app/img/feed_thumbnail/plain/${did}/${record.value.embed.images[3].image.ref.$link}@jpeg`;
} else if (record.value.embed && record.value.embed["$type"] == "app.bsky.embed.external") {
bskyPost.externalLink.exists = true;
bskyPost.externalLink.title = record.value.embed.external.title;
bskyPost.externalLink.description = record.value.embed.external.description;
bskyPost.externalLink.domain = record.value.embed.external.uri.replaceAll("https://", "").split("/")[0];
bskyPost.externalLink.image = `${config.corsProxy}https://cdn.bsky.app/img/feed_thumbnail/plain/${did}/${record.value.embed.external.thumb.ref["$link"]}@jpeg`;
}
}
function updateImage() {
if (timeout) {
window.cancelAnimationFrame(timeout);
}
timeout = window.requestAnimationFrame(function() {
domtoimage.toPng(document.getElementById('post'), { cacheBust: false })
.then(function (dataUrl) {
document.getElementById('result-image').src = dataUrl;
});
});
}
[...document.querySelectorAll('#post img'), ...document.querySelectorAll('#input-form input')].forEach(el => el.addEventListener("load", updateImage));
document.getElementById('submit').addEventListener('click', loadPost);
document.getElementById('post-url').addEventListener('change', loadPost);
document.getElementById('post-url').addEventListener('keyup', loadPost);
window.requestAnimationFrame(loadPost);
document.getElementById('download').addEventListener('click', function() {
let link = document.createElement('a');
link.download = 'post.png';
link.href = document.getElementById('result-image').src;
link.click();
});
})();

267
src/style.css Normal file
View File

@@ -0,0 +1,267 @@
body {
min-height: 100vh;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.form {
display: flex;
}
.form * {
margin: 2px;
}
.post-wrapper {
overflow: hidden;
position: relative;
}
#post {
background: transparent;
position: absolute;
left: -9999;
top: -9999;
width: 600px;
margin-left: -10px;
}
.link {
color: rgb(16, 131, 254);
}
.border {
background: #fff;
border-radius: 22px;
border-color: rgba(180, 180, 178, 0.56);
border-style: double;
border-width: 2px;
}
.border .windowDecoration {
height: 34px;
display: grid;
grid-template-columns: 1fr 2fr 1fr;
justify-content: space-around;
}
.border .windowDecoration .buttons {
display: flex;
gap: 6px;
padding: 7px;
}
.border .windowDecoration .buttons .roundButton {
border-radius: 9999px;
border-width: 1px;
border-style: solid;
background-color: rgba(38, 34, 23, 0.9);
opacity: 0.9;
width: 10px;
height: 10px;
}
.border .windowDecoration .title {
text-align: center;
font-size: 16px;
letter-spacing: 0px;
color: rgb(66, 87, 108);
line-height: 20px;
font-family: InterVariable, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
font-variant: no-contextual;
}
.post {
width: 600px;
padding: 8px;
}
.post .header {
width: 100%;
display: flex;
gap: 12px;
flex-direction: row;
padding-bottom: 12px;
}
.post .header .avatar {
width: 42px;
height: 42px;
border-radius: 21px;
}
.post .header .displayName {
font-size: 16.875px;
letter-spacing: 0px;
color: rgb(11, 15, 20);
flex-shrink: 1;
font-weight: 600;
line-height: 22px;
font-family: InterVariable, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
font-variant: no-contextual;
}
.post .header .handle {
font-size: 15px;
letter-spacing: 0px;
color: rgb(66, 87, 108);
line-height: 20px;
font-family: InterVariable, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
font-variant: no-contextual;
}
.post .body {
width: 100%;
font-size: 18.75px;
letter-spacing: 0px;
color: rgb(11, 15, 20);
line-height: 24px;
flex: 1 1 0%;
font-family: InterVariable, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
font-variant: no-contextual;
}
.post .embeds {
display: flex;
}
.post .images {
gap: 6px;
display: flex;
border-radius: 12px;
overflow: hidden;
flex-direction: row;
align-content: flex-start;
align-items: stretch;
flex-wrap: wrap;
}
.post .images > div {
aspect-ratio: 1 / 1;
flex: 1 0 48%;
}
.post .images img {
vertical-align: middle;
width: 100%;
height: 100%;
object-fit: cover;
}
.post .images > div:nth-child(2) {
grid-column: 2;
}
.post .images > div:nth-child(3) {
grid-row: 2;
grid-column: 1;
}
.post .images > div:nth-child(4) {
grid-row: 2;
grid-column: 2;
}
.post .externalLink {
flex-direction: column;
border-radius: 12px;
overflow: hidden;
width: 100%;
border-width: 1px;
border-style: solid;
margin-top: 8px;
border-color: rgb(212, 219, 226);
}
.post .externalLink .linkMeta {
gap: 3px;
padding-bottom: 4px;
padding-left: 12px;
padding-right: 12px;
padding-top: 8px;
border-top-style: solid;
border-top-width: 1px;
border-color: rgb(212, 219, 226);
}
.post .externalLink .linkTitle {
font-size: 15px;
letter-spacing: 0px;
color: rgb(11, 15, 20);
font-weight: 600;
line-height: 20px;
font-family: InterVariable, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
font-variant: no-contextual;
}
.post .externalLink .linkDescription {
padding-top: 6px;
padding-bottom: 6px;
font-size: 13.125px;
letter-spacing: 0px;
color: rgb(11, 15, 20);
line-height: 17px;
font-family: InterVariable, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
font-variant: no-contextual;
}
.post .externalLink .linkDomain {
padding-bottom: 8px;
padding-top: 6px;
border-top-style: solid;
border-top-width: 1px;
border-color: rgb(212, 219, 226);
font-size: 11.25px;
letter-spacing: 0px;
color: rgb(66, 87, 108);
line-height: 15px;
font-family: InterVariable, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
font-variant: no-contextual;
}
.post .footer {
width: 100%;
padding-top: 12px;
display: flex;
justify-content: space-between;
border-color: rgb(212, 219, 226);
border-top-style: solid;
border-top-width: 1px;
margin-top: 12px;
padding-left: 4px;
padding-right: 8px;
padding-top: 12px;
padding-bottom: 12px;
}
.post .footer .createdAt {
font-size: 15px;
letter-spacing: 0px;
color: rgb(119, 129, 140);
line-height: 15px;
font-family: InterVariable, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
font-variant: no-contextual;
}
.post .footer .interactions {
display: flex;
flex-direction: row;
align-items: center;
gap: 16px;
}
.post .footer .replies,
.post .footer .reposts,
.post .footer .likes {
font-size: 15px;
letter-spacing: 0px;
color: rgb(119, 129, 140);
stroke: rgb(119, 129, 140);
line-height: 15px;
font-family: InterVariable, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
font-variant: no-contextual;
}
.post .footer .replies>svg,
.post .footer .reposts>svg,
.post .footer .likes>svg {
height: 20px;
margin-bottom: -5px;
}
.post .footer .replies>span,
.post .footer .reposts>span,
.post .footer .likes>span {
font-size: 15px;
letter-spacing: 0px;
font-weight: 600;
line-height: 15px;
font-family: InterVariable, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
font-variant: no-contextual;
}
#result-image {
display: block;
margin: 20px auto;
}
#download {
display: block;
margin: 10px auto 30px;
}