mirror of
https://github.com/litruv/lit.ruv.wtf.git
synced 2026-07-24 02:36:02 +10:00
added masonry
This commit is contained in:
163
website/main.js
163
website/main.js
@@ -1 +1,162 @@
|
||||
function formatDate(t){return new Date(t).toLocaleDateString("en-US",{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit"})}function getRandomRotation(){return 3*Math.random()-1.5}function createPost(t){const e=document.createElement("div");e.className="post",e.style.transform=`rotate(${getRandomRotation()}deg)`;const o=document.createElement("a");o.href=t.url,o.target="_blank",o.style.textDecoration="none";const n=t.embed.length>0?t.embed.map((t=>`\n <a href="${t.url}" target="_blank">\n <img class="image-placeholder" src="${t.url}" alt="${t.alt||"Image"}" style="width: 100%;" />\n </a>\n `)).join(""):"",a=t.text.replace(/\n/g,"<br>");return e.innerHTML=`\n <div class="post-header">\n <div class="avatar">\n <img src="${t.avatar}" alt="${t.author}" style="width: 100%; height: 100%; object-fit: cover;" />\n </div>\n <div>\n <div style="font-weight: bold;">${t.author} (@${t.handle})</div>\n <div style="font-size: 0.875rem; color: #666;">\n ${formatDate(t.createdAt)}\n </div>\n </div>\n </div>\n <div class="post-content">${a}</div>\n ${n}\n <div class="post-actions">\n <button class="action-btn like">♥ ${t.likes}</button>\n <button class="action-btn repost">⟲ ${t.reposts}</button>\n </div>\n `,o.href=t.url,o.appendChild(e),o}function filterOriginalPosts(t){return t.filter((t=>{const e="app.bsky.feed.defs#reasonRepost"===t?.reason?.$type,o=t?.post?.record?.reply;return!e&&!o}))}async function fetchPosts(){try{const t=await fetch("https://public.api.bsky.app/xrpc/app.bsky.feed.getAuthorFeed?actor=lit.mates.dev&limit=20&filter=posts_no_replies");if(!t.ok)throw new Error("Failed to fetch posts");const e=await t.json(),o=filterOriginalPosts(e.feed).map((t=>({author:t.post.author.displayName,handle:t.post.author.handle,avatar:t.post.author.avatar,createdAt:t.post.record.createdAt,text:t.post.record.text,likes:t.post.likeCount||0,reposts:t.post.repostCount||0,url:`https://bsky.app/profile/${t.post.author.handle}/post/${t.post.uri.split("/").pop()}`,embed:t.post.embed?.images?.map((t=>({url:t.thumb,alt:t.alt||""})))||[]}))),n=document.getElementById("posts");n.innerHTML="",o.forEach((t=>{n.appendChild(createPost(t))}))}catch(t){console.error("Error fetching posts:",t)}}fetchPosts();
|
||||
function formatDate(t) {
|
||||
return new Date(t).toLocaleDateString("en-US", {
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit"
|
||||
});
|
||||
}
|
||||
|
||||
function getRandomRotation() {
|
||||
return 3 * Math.random() - 1.5;
|
||||
}
|
||||
|
||||
function createPost(t) {
|
||||
const post = document.createElement("div");
|
||||
post.className = "post";
|
||||
post.style.transform = `rotate(${getRandomRotation()}deg)`;
|
||||
|
||||
const link = document.createElement("a");
|
||||
link.href = t.url;
|
||||
link.target = "_blank";
|
||||
link.style.textDecoration = "none";
|
||||
|
||||
const embedHtml = t.embed.length > 0
|
||||
? t.embed.map(img =>
|
||||
`
|
||||
<a href="${img.url}" target="_blank">
|
||||
<img class="image-placeholder" src="${img.url}" alt="${img.alt || "Image"}" style="width: 100%;" />
|
||||
</a>
|
||||
`
|
||||
).join("")
|
||||
: "";
|
||||
|
||||
const textHtml = t.text.replace(/\n/g, "<br>");
|
||||
|
||||
post.innerHTML = `
|
||||
<div class="post-header">
|
||||
<div class="avatar">
|
||||
<img src="${t.avatar}" alt="${t.author}" style="width: 100%; height: 100%; object-fit: cover;" />
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-weight: bold;">${t.author} (@${t.handle})</div>
|
||||
<div style="font-size: 0.875rem; color: #666;">
|
||||
${formatDate(t.createdAt)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="post-content">${textHtml}</div>
|
||||
${embedHtml}
|
||||
<div class="post-actions">
|
||||
<button class="action-btn like">♥ ${t.likes}</button>
|
||||
<button class="action-btn repost">⟲ ${t.reposts}</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
link.href = t.url;
|
||||
link.appendChild(post);
|
||||
return link;
|
||||
}
|
||||
|
||||
function filterOriginalPosts(feed) {
|
||||
return feed.filter(item => {
|
||||
const isRepost = item?.reason?.$type === "app.bsky.feed.defs#reasonRepost";
|
||||
const isReply = item?.post?.record?.reply;
|
||||
return !isRepost && !isReply;
|
||||
});
|
||||
}
|
||||
|
||||
let postElementsCache = null;
|
||||
|
||||
function createColumns(num) {
|
||||
const postsContainer = document.getElementById("posts");
|
||||
postsContainer.innerHTML = "";
|
||||
const columns = [];
|
||||
for (let i = 0; i < num; i++) {
|
||||
const col = document.createElement("div");
|
||||
col.className = "masonry-col";
|
||||
postsContainer.appendChild(col);
|
||||
columns.push(col);
|
||||
}
|
||||
return columns;
|
||||
}
|
||||
|
||||
function getNumColumns() {
|
||||
if (window.innerWidth >= 1200) return 3;
|
||||
if (window.innerWidth >= 800) return 2;
|
||||
return 1;
|
||||
}
|
||||
|
||||
function getShortestColumn(columns) {
|
||||
let minHeight = columns[0].offsetHeight;
|
||||
let minCol = columns[0];
|
||||
for (const col of columns) {
|
||||
if (col.offsetHeight < minHeight) {
|
||||
minHeight = col.offsetHeight;
|
||||
minCol = col;
|
||||
}
|
||||
}
|
||||
return minCol;
|
||||
}
|
||||
|
||||
function layoutPosts(postElements) {
|
||||
const numCols = getNumColumns();
|
||||
const columns = createColumns(numCols);
|
||||
postElements.forEach(postEl => {
|
||||
const col = getShortestColumn(columns);
|
||||
col.appendChild(postEl);
|
||||
});
|
||||
}
|
||||
|
||||
async function fetchPosts() {
|
||||
try {
|
||||
const res = await fetch(
|
||||
"https://public.api.bsky.app/xrpc/app.bsky.feed.getAuthorFeed?actor=lit.mates.dev&limit=20&filter=posts_no_replies"
|
||||
);
|
||||
if (!res.ok) throw new Error("Failed to fetch posts");
|
||||
const data = await res.json();
|
||||
|
||||
const posts = filterOriginalPosts(data.feed).map(item => ({
|
||||
author: item.post.author.displayName,
|
||||
handle: item.post.author.handle,
|
||||
avatar: item.post.author.avatar,
|
||||
createdAt: item.post.record.createdAt,
|
||||
text: item.post.record.text,
|
||||
likes: item.post.likeCount || 0,
|
||||
reposts: item.post.repostCount || 0,
|
||||
url: `https://bsky.app/profile/${item.post.author.handle}/post/${item.post.uri.split("/").pop()}`,
|
||||
embed: item.post.embed?.images?.map(img => ({
|
||||
url: img.thumb,
|
||||
alt: img.alt || ""
|
||||
})) || []
|
||||
}));
|
||||
|
||||
// Only create post elements once
|
||||
postElementsCache = posts.map(post => createPost(post));
|
||||
// Preload images
|
||||
await Promise.all(postElementsCache.map(el => {
|
||||
const imgs = el.querySelectorAll("img");
|
||||
if (!imgs.length) return Promise.resolve();
|
||||
return Promise.all(Array.from(imgs).map(img => {
|
||||
if (img.complete) return Promise.resolve();
|
||||
return new Promise(res => {
|
||||
img.onload = img.onerror = res;
|
||||
});
|
||||
}));
|
||||
}));
|
||||
|
||||
layoutPosts(postElementsCache);
|
||||
|
||||
} catch (err) {
|
||||
console.error("Error fetching posts:", err);
|
||||
}
|
||||
}
|
||||
|
||||
// Only re-layout on resize, do not re-fetch or re-create posts
|
||||
window.addEventListener("resize", () => {
|
||||
if (postElementsCache) {
|
||||
layoutPosts(postElementsCache);
|
||||
}
|
||||
});
|
||||
|
||||
fetchPosts();
|
||||
Reference in New Issue
Block a user