@@ -0,0 +1,3 @@
|
||||
FROM nginx:latest
|
||||
WORKDIR /usr/share/nginx/html
|
||||
ADD index.html ./
|
||||
|
4
Makefile
4
Makefile
@@ -1,6 +1,6 @@
|
||||
.DEFAULT_GOAL := default
|
||||
|
||||
IMAGE ?= rg.fr-par.scw.cloud/averagemarcus-private/bsky-screenshot:latest
|
||||
IMAGE ?= rg.fr-par.scw.cloud/averagemarcus/bsky-screenshot:latest
|
||||
|
||||
.PHONY: test # Run all tests, linting and format checks
|
||||
test: lint check-format run-tests
|
||||
@@ -67,7 +67,7 @@ ci:
|
||||
|
||||
.PHONY: release # Release the latest version of the application
|
||||
release:
|
||||
@echo "⚠️ 'release' unimplemented"
|
||||
@kubectl --namespace bsky-screenshot set image deployment bsky-screenshot web=docker.cluster.fun/averagemarcus/bsky-screenshot:$(SHA)
|
||||
|
||||
.PHONY: help # Show this list of commands
|
||||
help:
|
||||
|
158
index.html
Normal file
158
index.html
Normal file
@@ -0,0 +1,158 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset='utf-8'>
|
||||
<meta http-equiv='X-UA-Compatible' content='IE=edge'>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Bsky Screenshot</title>
|
||||
|
||||
<meta property="og:title" content="Bsky Screenshot">
|
||||
<meta property="og:site_name" content="Bsky Screenshot">
|
||||
<meta property="og:url" content="https://bsky-screenshot.cluster.fun">
|
||||
<meta property="og:description" content="Web app to generate screenshots of Bluesky posts">
|
||||
<meta property="og:type" content="website">
|
||||
|
||||
<script tpye="application/javascript" src="https://cdn.githubraw.com/AverageMarcus/BindIt.js/refs/heads/master/BindIt.js"></script>
|
||||
<script tpye="application/javascript" src="https://cdn.githubraw.com/tsayen/dom-to-image/refs/heads/master/dist/dom-to-image.min.js"></script>
|
||||
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,300italic,700,700italic">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/5.0.0/normalize.css">
|
||||
<link rel="stylesheet" href="https://githubraw.com/AverageMarcus/milligram/master/dist/milligram.min.css">
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1 class="heading-fancy">
|
||||
Bsky Screenshot
|
||||
</h1>
|
||||
|
||||
<blockquote>
|
||||
Web app to generate screenshots of Bluesky posts
|
||||
</blockquote>
|
||||
|
||||
<div class="row" id="input-form">
|
||||
<div class="column">
|
||||
<div class="form">
|
||||
<input type="text" id="post-url" bind-it-to="bskyPost.url" bind-it-with="value" />
|
||||
<button id="submit">Fetch</button>
|
||||
</div>
|
||||
<!-- Config is still WiP -->
|
||||
<!--
|
||||
<details>
|
||||
<summary>Advanced Config</summary>
|
||||
<div>
|
||||
<label>CORS Proxy: <input type="text" bind-it-to="config.corsProxy" bind-it-with="value" bind-it-two-way /></label>
|
||||
<label>Width: <input type="number" bind-it-to="config.width" bind-it-with="value" bind-it-two-way /></label>
|
||||
<label>Window Decoration: <input type="checkbox" bind-it-to="config.windowDecoration" bind-it-with="checked" bind-it-two-way /></label>
|
||||
</div>
|
||||
</details>
|
||||
-->
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="column post-wrapper">
|
||||
|
||||
<div id="post" bind-it-to="config.width" bind-it-with="setWidth">
|
||||
<div class="post border" bind-it-to="config.width" bind-it-with="setWidth">
|
||||
<div class="windowDecoration" bind-it-to="config.windowDecoration" bind-it-with="shouldShow">
|
||||
<div class="buttons">
|
||||
<div class="roundButton" style="background-color: #ff544d; border-color: #dd3a33"></div>
|
||||
<div class="roundButton" style="background-color: #feb429; border-color: #da9211"></div>
|
||||
<div class="roundButton" style="background-color: #d9d8d7; border-color: #b7b5b5"></div>
|
||||
</div>
|
||||
<div class="title" bind-it-to="bskyPost.handle"></div>
|
||||
<div class="spacer"></div>
|
||||
</div>
|
||||
<div class="header">
|
||||
<img id="avatar" class="avatar" bind-it-to="bskyPost.avatar" bind-it-with="imageSrc" />
|
||||
<div>
|
||||
<div class="displayName" bind-it-to="bskyPost.displayName"></div>
|
||||
<div class="handle" bind-it-to="bskyPost.handle"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="body" bind-it-to="bskyPost.text" bind-it-with="asHTML"></div>
|
||||
|
||||
<div class="embeds">
|
||||
<div class="images" bind-it-to="bskyPost.images.exists" bind-it-with="shouldShow">
|
||||
<div bind-it-to="bskyPost.images.image1" bind-it-with="shouldShow">
|
||||
<img bind-it-to="bskyPost.images.image1" bind-it-with="imageSrc" />
|
||||
</div>
|
||||
<div bind-it-to="bskyPost.images.image2" bind-it-with="shouldShow">
|
||||
<img bind-it-to="bskyPost.images.image2" bind-it-with="imageSrc" />
|
||||
</div>
|
||||
<div bind-it-to="bskyPost.images.image3" bind-it-with="shouldShow">
|
||||
<img bind-it-to="bskyPost.images.image3" bind-it-with="imageSrc" />
|
||||
</div>
|
||||
<div bind-it-to="bskyPost.images.image4" bind-it-with="shouldShow">
|
||||
<img bind-it-to="bskyPost.images.image4" bind-it-with="imageSrc" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="externalLink" bind-it-to="bskyPost.externalLink.exists" bind-it-with="shouldShow">
|
||||
<img id="externalLinkImage" bind-it-to="bskyPost.externalLink.image" bind-it-with="imageSrc" />
|
||||
<div class="linkMeta">
|
||||
<div class="linkTitle" bind-it-to="bskyPost.externalLink.title"></div>
|
||||
<div class="linkDescription" bind-it-to="bskyPost.externalLink.description"></div>
|
||||
<div class="linkDomain">
|
||||
<svg fill="none" viewBox="0 0 24 24" width="12" height="12" style="transition-property: color, background-color, border-color, text-decoration-color, fill, stroke; transition-timing-function: cubic-bezier(0.17, 0.73, 0.14, 1); transition-duration: 100ms; color: rgb(140, 158, 178);"><path fill="hsl(211, 20%, 62.4%)" fill-rule="evenodd" clip-rule="evenodd" d="M4.4 9.493C4.14 10.28 4 11.124 4 12a8 8 0 1 0 10.899-7.459l-.953 3.81a1 1 0 0 1-.726.727l-3.444.866-.772 1.533a1 1 0 0 1-1.493.35L4.4 9.493Zm.883-1.84L7.756 9.51l.44-.874a1 1 0 0 1 .649-.52l3.306-.832.807-3.227a7.993 7.993 0 0 0-7.676 3.597ZM2 12C2 6.477 6.477 2 12 2s10 4.477 10 10-4.477 10-10 10S2 17.523 2 12Zm8.43.162a1 1 0 0 1 .77-.29l1.89.121a1 1 0 0 1 .494.168l2.869 1.928a1 1 0 0 1 .336 1.277l-.973 1.946a1 1 0 0 1-.894.553h-2.92a1 1 0 0 1-.831-.445L9.225 14.5a1 1 0 0 1 .126-1.262l1.08-1.076Zm.915 1.913.177-.177 1.171.074 1.914 1.286-.303.607h-1.766l-1.194-1.79Z"></path></svg>
|
||||
<span bind-it-to="bskyPost.externalLink.domain"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<div class="interactions" bind-it-to="config.interactions" bind-it-with="shouldShow">
|
||||
<span class="replies">
|
||||
<svg fill="none" width="18" viewBox="0 0 24 24" height="18" style="color: rgb(111, 134, 159); pointer-events: none;"><path fill="hsl(211, 20%, 53%)" fill-rule="evenodd" clip-rule="evenodd" d="M2.002 6a3 3 0 0 1 3-3h14a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H12.28l-4.762 2.858A1 1 0 0 1 6.002 21v-2h-1a3 3 0 0 1-3-3V6Zm3-1a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h2a1 1 0 0 1 1 1v1.234l3.486-2.092a1 1 0 0 1 .514-.142h7a1 1 0 0 0 1-1V6a1 1 0 0 0-1-1h-14Z"></path></svg>
|
||||
<span bind-it-to="bskyPost.replies"></span> replies
|
||||
</span>
|
||||
<span class="reposts">
|
||||
<svg fill="none" width="18" viewBox="0 0 24 24" height="18" style="color: rgb(111, 134, 159); pointer-events: none;"><path fill="hsl(211, 20%, 53%)" fill-rule="evenodd" clip-rule="evenodd" d="M17.957 2.293a1 1 0 1 0-1.414 1.414L17.836 5H6a3 3 0 0 0-3 3v3a1 1 0 1 0 2 0V8a1 1 0 0 1 1-1h11.836l-1.293 1.293a1 1 0 0 0 1.414 1.414l2.47-2.47a1.75 1.75 0 0 0 0-2.474l-2.47-2.47ZM20 12a1 1 0 0 1 1 1v3a3 3 0 0 1-3 3H6.164l1.293 1.293a1 1 0 1 1-1.414 1.414l-2.47-2.47a1.75 1.75 0 0 1 0-2.474l2.47-2.47a1 1 0 0 1 1.414 1.414L6.164 17H18a1 1 0 0 0 1-1v-3a1 1 0 0 1 1-1Z"></path></svg>
|
||||
<span bind-it-to="bskyPost.reposts"></span> reposts
|
||||
</span>
|
||||
<span class="likes">
|
||||
<svg fill="none" width="18" viewBox="0 0 24 24" height="18" style="color: rgb(111, 134, 159); pointer-events: none;"><path fill="hsl(211, 20%, 53%)" fill-rule="evenodd" clip-rule="evenodd" d="M16.734 5.091c-1.238-.276-2.708.047-4.022 1.38a1 1 0 0 1-1.424 0C9.974 5.137 8.504 4.814 7.266 5.09c-1.263.282-2.379 1.206-2.92 2.556C3.33 10.18 4.252 14.84 12 19.348c7.747-4.508 8.67-9.168 7.654-11.7-.541-1.351-1.657-2.275-2.92-2.557Zm4.777 1.812c1.604 4-.494 9.69-9.022 14.47a1 1 0 0 1-.978 0C2.983 16.592.885 10.902 2.49 6.902c.779-1.942 2.414-3.334 4.342-3.764 1.697-.378 3.552.003 5.169 1.286 1.617-1.283 3.472-1.664 5.17-1.286 1.927.43 3.562 1.822 4.34 3.764Z"></path></svg>
|
||||
<span bind-it-to="bskyPost.likes"></span> likes
|
||||
</span>
|
||||
</div>
|
||||
<div class="createdAt" bind-it-to="bskyPost.createdAt"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="column">
|
||||
<img src="" id="result-image" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="column">
|
||||
<button id="download">Download Image</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
Source code available on <a href="https://github.com/AverageMarcus/bsky-screenshot" target="_blank" rel="noopener noreferrer">GitHub</a>, <a href="https://gitlab.com/AverageMarcus/bsky-screenshot" target="_blank" rel="noopener noreferrer">GitLab</a>, <a href="https://bitbucket.org/AverageMarcus/bsky-screenshot/" target="_blank" rel="noopener noreferrer">Bitbucket</a> & <a href="https://git.cluster.fun/AverageMarcus/bsky-screenshot" target="_blank" rel="noopener noreferrer">my own Gitea server</a>.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="column column-60 column-offset-20">
|
||||
<footer>
|
||||
Made with
|
||||
<svg height="20" class="fill-primary" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 449.3 449.3" xmlns:xlink="http://www.w3.org/1999/xlink"><title>love</title><g><path d="M0 162.7c1.5-7.7 2.7-15.4 4.5-23A125.5 125.5 0 0132 88a136.3 136.3 0 0162.7-40.6c8.3-2.9 17.7-3.2 26.6-3.7a134 134 0 0155.6 6.6c14.9 5.7 30 11 41 23.6 17-20 36.4-36.3 60-46.4 12-5.2 25.7-6.9 38.7-9.4a79.4 79.4 0 0140.3 3.2 96.4 96.4 0 0143.2 26 209.8 209.8 0 0137.8 55.4 133.2 133.2 0 0111 65.7c-3.2 42.2-21 79-41.5 114.8a431.2 431.2 0 01-47.6 64.3c-19.6 23-39.7 45.7-59.6 68.5-3.7 4.3-7.2 9-11.7 12.4-7.3 5.4-15.9 4.9-23.8 1.5-21.9-9.2-43.8-18.5-65.3-28.5a520.1 520.1 0 01-98-58.7c-28.2-21.5-55.5-44.3-74-75.3a183.8 183.8 0 01-25-61.4c-1-6.2-1.6-12.6-2.4-18.8v-24.5zM138 281l2.5.1c0 2.1.3 4.3 0 6.3l-9 55.5c-.3 1.6 0 4.2 1 5 5.8 4.1 11.8 7.8 17.9 11.8l11-59.7 2.5.3c3.7 21.6-6.3 42-7.6 63.5l19.4 9.6c.5-18 2.2-35 6.9-51.6 1.3 4 1.5 8 1.2 12-.7 11.2-1 22.5-2.6 33.7-1 7.3 1.9 10.6 8 13.3 27 11.7 54.1 23.5 81 35.7 5.5 2.5 8.8 1.5 12.6-2.9 12.5-14.4 25.7-28.1 38-42.7 17.2-20.2 34.5-40.5 50.5-61.7 29.5-39.2 52.7-81.7 61-131 3.6-21.7 2-43-5.6-63.5a176 176 0 00-33.9-54.5 84.8 84.8 0 00-38-26 91.2 91.2 0 00-44.2-2.5c-13 2-25.6 5.1-37.2 12.3a208.6 208.6 0 00-45.9 37c-5.7 6.3-7.8 6.4-15 1.4-5.7-4-11-8.8-17.2-11.9-15.1-7.5-30.7-14.4-48-14.9-13.7-.4-28-2-41.2 1-36 8.4-62.7 30-80.3 62.8a111 111 0 00-11.7 56.6c.3 10 1.4 20 2.2 29.9 7-18.9 11-38.3 19.7-56.3.6 3 .6 6 0 8.7l-14.5 53.2c-.7 2.6-2 5.7-1.2 8 2.7 8.4 6.2 16.6 9.4 24.8 0-20 13.7-63.9 21.8-66.8 0 .6.3 1.1.2 1.6a11936 11936 0 01-17 64c-.3 1.4-1.6 2.6-2.9 4.5l9.7 17a573 573 0 0120-67.3l2.9.7c-1 5-1.5 10-2.8 14.9-4.7 17.2-9.6 34.3-14.2 51.5-.7 2.5-1.5 6-.3 7.8 3.6 5.6 8 10.7 12.4 16.1 1.8-8.6 3.1-16.6 5.2-24.4 3.1-11.5 6.6-22.9 10.2-34.3.8-2.6 2.7-5 4-7.4l2 .8c-.1 1.6-.1 3.4-.5 5l-12.2 47.4c-4.6 17.9-4.3 18.9 7.8 29.4 2.2 1.8 4.5 3.5 7 5.3 3.7-31 12.5-64.7 18.4-68-.3 3.5-.3 6.6-.9 9.6l-12.7 58.8c-.3 1.2-.7 3-.1 3.5 4.7 4.4 9.6 8.6 14.5 12.9 3.5-21.4 7.6-41.7 13.6-61.6 3.4 8.7.6 17-.8 25.2-1.9 11.6-4.5 23.1-6.5 34.7-.4 2.3-.4 6 1 7 4.8 4.2 10.3 7.4 16 11.3 3.9-21.5 5.5-42.6 12.6-62.5z"/><path d="M323.2 180.5a24 24 0 01-24.5-19.7c-2-9.7.7-20.2 15-27 16.6-7.8 38.3 2.3 41.6 19.5 1.2 6.6-1.6 12.1-6 16.3a35.1 35.1 0 01-26 11z"/><path d="M138.9 167.1a31 31 0 0125 12.7 22 22 0 01-13.6 34.9 29.9 29.9 0 01-31-9.9c-6-7-7.6-15.3-4.1-24.1 3.7-9.5 12-12 21-13.5.8-.2 1.8 0 2.7 0z"/><path d="M233.2 202c-18.5-.4-33.7-13.6-34-29.7 0-4.2.4-8 5.4-8.7 4.5-.7 6.6 2.3 8 6 2.3 6.6 5.5 12.4 12.4 15.4 7.5 3.2 14 1.5 20.4-3.3 6.2-4.7 6.4-11 4.7-17.5-1.4-5.2.4-8.4 4.7-10.2 4.3-1.7 9 1.3 10.9 6.2 7.2 19.8-5.7 34.6-22.8 40.2-3 1-6.5 1-9.7 1.6z"/><path d="M201.2 384.7c-.9-2-2.7-4.2-2.5-6.2 1.2-14 2.8-28.1 4.4-42.2 0-.9.7-1.7 1-2.6l2.6 1.1-3.1 49.1-2.4.8z"/><path d="M222 387.4c-4.8-5.7-5-15-1-37.8 3.1 3 4 30.5 1 37.8z"/><path d="M240.9 361.7l-1.4 20.2c-4.8-3.3-4.6-11.9-.2-20.4l1.6.2z"/><path d="M257.4 380.4c-4.1-5.2-3.7-9.7 1-14.7l-1 14.7z"/></g></svg>
|
||||
by <a href="https://marcusnoble.co.uk" class="fancy-link">Marcus Noble</a>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script tpye="application/javascript" src="script.js"></script>
|
||||
</body>
|
||||
</html>
|
192
script.js
Normal file
192
script.js
Normal file
@@ -0,0 +1,192 @@
|
||||
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`;
|
||||
}
|
||||
|
||||
(() => {
|
||||
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`;
|
||||
}
|
||||
|
||||
updateImage();
|
||||
}
|
||||
|
||||
function updateImage() {
|
||||
window.requestAnimationFrame(function() {
|
||||
domtoimage.toPng(document.getElementById('post'), { cacheBust: false })
|
||||
.then(function (dataUrl) {
|
||||
document.getElementById('result-image').src = dataUrl;
|
||||
});
|
||||
});
|
||||
}
|
||||
[...document.querySelectorAll('img')].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();
|
||||
});
|
||||
|
||||
})();
|
263
style.css
Normal file
263
style.css
Normal file
@@ -0,0 +1,263 @@
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
#download, #result-image {
|
||||
display: block;
|
||||
margin: 30px auto;
|
||||
}
|
Reference in New Issue
Block a user