Include all needed files in Dockerfile
Signed-off-by: Marcus Noble <github@marcusnoble.co.uk>
This commit is contained in:
165
src/index.html
Normal file
165
src/index.html
Normal file
File diff suppressed because one or more lines are too long
193
src/script.js
Normal file
193
src/script.js
Normal 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
267
src/style.css
Normal 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;
|
||||
}
|
Reference in New Issue
Block a user