import { getData, addData, addDataArray } from "./db.js"; // let posts:any; // let keyBase = "dandelion_posts_v1_" // let key:string = ""; // interface PostTimestamp { // year: number, // month: number, // day: number, // hour: number, // minute: number, // second: number, // } class Post { constructor(author, author_id, text, post_timestamp, imageData = null, importedFrom = null, importSource = null) { this.post_timestamp = post_timestamp; this.post_id = generateID(); this.author = author; this.author_id = author_id; this.text = text; this.image_data = imageData; this.importedFrom = importedFrom; this.importSource = importSource; } } window.addEventListener('scroll', () => { // Total height of the document const totalPageHeight = document.body.scrollHeight; // Current scroll position const scrollPoint = window.scrollY + window.innerHeight; // Check if scrolled to bottom if (scrollPoint >= totalPageHeight) { console.log('Scrolled to the bottom!'); console.log(scrollPoint, totalPageHeight); // You can perform your action here } }); function initMarkdown() { const renderer = new marked.Renderer(); renderer.link = (href, title, text) => { return `${text}`; }; marked.setOptions({ renderer: renderer }); } function waitMs(durationMs) { return new Promise(resolve => setTimeout(resolve, durationMs)); } function uuidv4() { return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, (c) => (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)); } let logLines = []; let logLength = 10; function log(message) { logLines.push(`${new Date().toLocaleTimeString()}: ${message}`); if (logLines.length > 10) { logLines = logLines.slice(logLines.length - logLength); } let log = document.getElementById("log"); if (!log) { throw new Error(); } log.innerText = logLines.join("\n"); } function arrayBufferToBase64(buffer) { return new Promise((resolve, reject) => { const blob = new Blob([buffer], { type: 'application/octet-stream' }); const reader = new FileReader(); reader.onloadend = () => { const dataUrl = reader.result; if (!dataUrl) { resolve(null); return; } const base64 = dataUrl.split(',')[1]; resolve(base64); }; reader.onerror = (error) => { reject(error); }; reader.readAsDataURL(blob); }); } async function createTestData() { let postsTestData = await (await fetch("./postsTestData.json")).json(); return postsTestData; } let time = 0; function timerStart() { time = performance.now(); } function timerDelta() { return performance.now() - time; } async function getFixedTweetText(entry) { let fullText = entry.tweet.full_text; let linkMarkdown = ""; for (const url of entry.tweet.entities.urls) { linkMarkdown = `[${url.display_url}](${url.expanded_url})`; fullText = fullText.replace(url.url, linkMarkdown); } return fullText; } async function createTestData2(userID) { log("Importing tweet archive"); let postsTestData = []; let response = await fetch("./tweets.js"); let tweetsText = await response.text(); tweetsText = tweetsText.replace("window.YTD.tweets.part0", "window.tweetData"); new Function(tweetsText)(); // let tweets = JSON.parse(tweetJSON); let count = 0; for (let entry of window.tweetData) { // if (entry.tweet.hasOwnProperty("in_reply_to_screen_name") || entry.tweet.retweeted || entry.tweet.full_text.startsWith("RT")) { // continue; // } let mediaURL = entry.tweet?.entities?.media?.[0]?.media_url; let isImage = false; if (mediaURL) { isImage = mediaURL.includes('jpg'); } let imageData = null; if (isImage) { try { let response = await fetch(mediaURL); await waitMs(100); if (response.status === 200) { imageData = await response.arrayBuffer(); } console.log(imageData); } catch (e) { console.log(e); } } let timeStamp = new Date(entry.tweet.created_at); let tweetText = await getFixedTweetText(entry); let newPost = new Post('bobbydigitales', userID, tweetText, timeStamp, imageData, 'twitter', entry); postsTestData.push(newPost); count++; if (count % 100 === 0) { log(`Imported ${count} posts...`); // render(postsTestData); } // if (count == 100-1) { // break; // } } return postsTestData; } async function createTestData3(userID) { let posts = await (await (fetch('./posts.json'))).json(); return posts; } async function registerServiceWorker() { if (!("serviceWorker" in navigator)) { return; } let registrations = await navigator.serviceWorker.getRegistrations(); if (registrations.length > 0) { console.log("Service worker already registered."); return registrations[0]; } navigator.serviceWorker .register("/sw.js") .then((registration) => { console.log("Service Worker registered with scope:", registration.scope); return registration; }) .catch((error) => { console.error("Service Worker registration failed:", error); }); } function addPost(userID, posts, postText) { if ((typeof postText !== "string") || postText.length === 0) { log("Not posting an empty string..."); return; } let post = new Post(`bobbydigitales`, userID, postText, new Date()); posts.push(post); // localStorage.setItem(key, JSON.stringify(posts)); addData(userID, post); render(posts); } function generateID() { if (self.crypto.hasOwnProperty("randomUUID")) { return self.crypto.randomUUID(); } return uuidv4(); } function getUserID() { let id = localStorage.getItem("dandelion_id"); if (!id) { id = generateID(); localStorage.setItem("dandelion_id", id); } return id; } function connectWebsocket(userID) { let websocket = new WebSocket(`ws://${window.location.hostname}:${window.location.port}/ws`); websocket.onopen = function (evt) { log("Websocket: CONNECTED"); websocket.send(`{"messageType":"connect", "id": "${userID}"}`); let websocketPingInterval = window.setInterval(() => { websocket.send(`{"messageType":"ping", "id": "${userID}"}`); }, 5000); }; websocket.onclose = function (evt) { log("Websocket: DISCONNECTED"); }; websocket.onmessage = function (evt) { log('Websocket: RESPONSE: ' + evt.data); }; websocket.onerror = function (evt) { log('Websocket: ERROR: ' + evt); }; return websocket; } function setFont(fontName, fontSize) { let content = document.getElementById('content'); if (!content) { return; } content.style.fontFamily = fontName; content.style.fontSize = fontSize; let textArea = document.getElementById('textarea_post'); if (!textArea) { return; } textArea.style.fontFamily = fontName; textArea.style.fontSize = fontSize; } function initOffline() { // Event listener for going offline window.addEventListener('offline', () => { log("offline"); }); // Event listener for going online window.addEventListener('online', () => { log("online"); }); log(`Online status: ${navigator.onLine ? "online" : "offline"}`); } function initButtons(userID, posts) { let font1Button = document.getElementById("button_font1"); let font2Button = document.getElementById("button_font2"); font1Button.addEventListener('click', () => { setFont('Bookerly', '16px'); }); font2Button.addEventListener('click', () => { setFont('Virgil', '24px'); }); let postButton = document.getElementById("button_post"); let postText = document.getElementById("textarea_post"); if (!(postButton && postText)) { throw new Error(); } postButton.addEventListener("click", () => { addPost(userID, posts, postText.value); postText.value = ""; }); } async function loadPosts(userID) { timerStart(); let posts = await getData(userID, new Date(2022, 8), new Date()); if (posts.length > 0) { log(`Loaded ${posts.length} posts in ${timerDelta().toFixed(2)}ms`); return posts; } posts = await createTestData2(userID); log("Adding test data..."); addDataArray(userID, posts); return await getData(userID, new Date(2022, 8), new Date()); } async function main() { let posts = []; let time = 0; ``; let delta = 0; let urlParams = (new URL(window.location.href)).searchParams; if (urlParams.get("sw") === "true") { let registration = await registerServiceWorker(); } let userID = getUserID(); log(`Your user ID is: ${userID}`); if (navigator.storage && navigator.storage.persist && !navigator.storage.persisted) { const isPersisted = await navigator.storage.persist(); log(`Persisted storage granted: ${isPersisted}`); } log(`Persisted: ${(await navigator?.storage?.persisted())?.toString()}`); initMarkdown(); // let main = await fetch("/main.js"); // let code = await main.text(); // console.log(code); // registration.active.postMessage({type:"updateMain", code:code}); posts = await loadPosts(userID); let websocket = connectWebsocket(userID); initOffline(); initButtons(userID, posts); // debugger; timerStart(); render(posts); let renderTime = timerDelta(); log(`render took: ${renderTime.toFixed(2)}ms`); log(`memory used: ${(performance.memory.usedJSHeapSize / 1024 / 1024).toFixed(2)}Mb`); } function render(posts) { const fragment = document.createDocumentFragment(); let contentDiv = document.getElementById("content"); if (!contentDiv) { throw new Error(); } contentDiv.innerHTML = ""; let count = 0; for (let i = posts.length - 1; i >= 0; i--) { let postData = posts[i]; let post = renderPost(postData); if (post) { fragment.appendChild(post); count++; } if (count > 100) { break; } } if (!contentDiv) { throw new Error("Couldn't get content div!"); } contentDiv.appendChild(fragment); } function renderPost(post) { if (!(post.hasOwnProperty("text"))) { throw new Error("Post is malformed!"); } let containerDiv = document.createElement("div"); let textDiv = document.createElement("div"); let timestampDiv = document.createElement("div"); let hr = document.createElement("hr"); textDiv.innerHTML = marked.parse(post.text); // textDiv.innerHTML = DOMPurify.sanitize(marked.parse(post.text)); timestampDiv.innerText = `${post.post_timestamp.toDateString()}`; timestampDiv.title = `${post.post_timestamp.toLocaleTimeString()} ยท ${post.post_timestamp.toDateString()}`; containerDiv.appendChild(hr); containerDiv.appendChild(textDiv); if (!("image_data" in post && post.image_data)) { containerDiv.appendChild(timestampDiv); return containerDiv; // return null; } let image = document.createElement("img"); const blob = new Blob([post.image_data], { type: 'image/jpg' }); const url = URL.createObjectURL(blob); image.onload = () => { URL.revokeObjectURL(url); }; image.src = url; // image.src = image.src = "data:image/png;base64," + post.image; image.className = "postImage"; containerDiv.appendChild(image); containerDiv.appendChild(timestampDiv); return containerDiv; } window.addEventListener("load", main); //# sourceMappingURL=main.js.map