working on replies

This commit is contained in:
2026-04-15 06:05:46 +00:00
parent 0d392c90cc
commit c1de283fb6
15 changed files with 436 additions and 211 deletions

View File

@@ -5,7 +5,7 @@ import { openDatabase, getData, addData, deleteData, getAllData, getPostForUser
import { arrayBufferToBase64, compressString } from "dataUtils";
import { log, logID, renderLog, setLogVisibility } from "log";
class Post {
constructor(author, author_id, text, post_timestamp, imageData = null, importedFrom = null, importSource = null) {
constructor(author, author_id, text, post_timestamp, imageData = null, importedFrom = null, importSource = null, reply_to_id = null) {
this.post_timestamp = post_timestamp;
this.post_id = generateID();
this.author = author;
@@ -14,6 +14,7 @@ class Post {
this.image_data = imageData;
this.importedFrom = importedFrom;
this.importSource = importSource;
this.reply_to_id = reply_to_id;
}
}
class StatusBar {
@@ -68,6 +69,7 @@ export class App {
this.peername = '';
this.userID = '';
this.peerID = '';
this.replyToID = null;
this.following = new Set();
this.posts = [];
this.isHeadless = false;
@@ -252,20 +254,14 @@ export class App {
await this.peerManager?.rpc.sendPostForUser(requestingPeerID, this.peerID, userID, post);
}
return true;
// return posts;
// return postIDs;
});
this.peerManager.registerRPC('sendPostForUser', async (sendingPeerID, userID, post) => {
console.log.apply(null, log(`[app] sendPostForUser got post[${logID(post.post_id)}] from peer[${logID(sendingPeerID)}] for user[${logID(userID)}] author[${post.author}] text[${post.text}]`));
// if (post.text === "image...") {
// debugger;
// }
let peerData = this.statusBar.getPeerData(sendingPeerID);
if (peerData) {
this.statusBar.updatePeerMessage(sendingPeerID, `⬇️${logID(userID)} ${peerData.havePostCount}/${peerData.neededPostCount}}`);
}
await this.sync.writePostForUser(userID, post);
// if (userID === this.userID) {
if (peerData) {
peerData.havePostCount++;
this.statusBar.updatePeerMessage(sendingPeerID, `⬇️${logID(userID)} ${peerData.havePostCount}/${peerData.neededPostCount}}`);
@@ -275,13 +271,12 @@ export class App {
}
this.renderTimer = setTimeout(() => { this.render(); }, 1000);
return true;
// }
});
this.statusBar.setMessageHTML("Connecting to ddln network...");
this.statusBar.setMessageHTML("Connecting to ddln...");
await this.peerManager.connect();
console.log.apply(null, log("*************** after peerManager.connect"));
;
this.statusBar.setMessageHTML("Connected to ddln network...");
this.statusBar.setMessageHTML("Connected to ddln.");
if (this.isBootstrapPeer) {
return;
}
@@ -530,20 +525,20 @@ export class App {
return null;
}
}
async createNewPost(userID, postText, mediaData, mimeType) {
async createPost(userID, postText, mediaData, mimeType, replyToID = null) {
if ((typeof postText !== "string") || postText.length === 0) {
console.log.apply(null, log("Not posting an empty string..."));
return;
}
if (mediaData &&
(mimeType === 'image/jpg' || mimeType === 'image/jpeg' || mimeType === 'image/png') &&
mediaData.byteLength > 500 * 1024) {
mediaData.byteLength > 256 * 1024) {
let compressedImage = await this.compressImage(mediaData, mimeType, 0.9);
if (compressedImage) {
mediaData = compressedImage;
}
}
let post = new Post(this.username, userID, postText, new Date(), mediaData);
let post = new Post(this.username, userID, postText, new Date(), mediaData, null, null, replyToID);
// this.posts.push(post);
// localStorage.setItem(key, JSON.stringify(posts));
addData(userID, post);
@@ -736,17 +731,17 @@ export class App {
});
let composeButton = this.div('compose-button');
composeButton.addEventListener('click', e => {
document.getElementById('compose').style.display = 'block';
document.getElementById('textarea_post')?.focus();
this.enterCompose();
});
let filePicker = document.getElementById('file-input');
filePicker?.addEventListener('change', async (event) => {
for (let file of filePicker.files) {
let buffer = await file.arrayBuffer();
await this.createNewPost(this.userID, 'image...', buffer, file.type);
await this.createPost(this.userID, 'image...', buffer, file.type);
}
// Reset so that if they pick the same image again, we still get the change event.
filePicker.value = '';
this.exitCompose();
});
let filePickerLabel = document.getElementById('file-input-label');
filePickerLabel?.addEventListener('click', () => {
@@ -773,21 +768,24 @@ export class App {
// this.render();
// });
// clearPostsButton.addEventListener('click', () => { clearData(userID); posts = []; this.render() });
let postButton = document.getElementById("button_post");
let cancelPostButton = document.getElementById("button_cancel_post");
let postText = document.getElementById("textarea_post");
if (!(postButton && postText)) {
let postButton = document.getElementById("button_post");
if (!(cancelPostButton && postButton && postText)) {
throw new Error();
}
cancelPostButton.addEventListener('click', (e) => {
this.exitCompose();
});
postText.addEventListener('paste', async (e) => {
const dataTransfer = e.clipboardData;
const file = dataTransfer.files[0];
let buffer = await file.arrayBuffer();
await this.createNewPost(this.userID, 'image...', buffer, file.type);
await this.createPost(this.userID, 'image...', buffer, file.type);
});
postButton.addEventListener("click", () => {
this.createNewPost(userID, postText.value);
postText.value = "";
document.getElementById('compose').style.display = 'none';
this.createPost(userID, postText.value, undefined, undefined, this.replyToID);
this.exitCompose();
});
// updateApp.addEventListener("click", () => {
// registration?.active?.postMessage({ type: "update_app" });
@@ -796,6 +794,26 @@ export class App {
// this.showInfo()
// });
}
// Change this all to a template so we're not toggling state in this crazy way!
enterCompose(replyToID = null) {
if (replyToID) {
this.renderComposeReplyArea(replyToID);
document.getElementById("compose-reply-area").style.display = "block";
}
replyToID = replyToID;
document.getElementById('compose').style.display = 'block';
document.getElementById('textarea_post')?.focus();
document.getElementById('compose-dimmer')?.classList.add("compose-dimmer-dimmed");
document.body.classList.add("no-scroll");
}
exitCompose() {
let postText = document.getElementById("textarea_post");
postText.value = "";
document.getElementById('compose').style.display = 'none';
document.getElementById('compose-dimmer')?.classList.remove("compose-dimmer-dimmed");
document.getElementById("compose-reply-area").style.display = "none";
document.body.classList.remove("no-scroll");
}
async getPostsForFeed() {
// get N posts from each user and sort them by date.
// This isn't really going to work very well.
@@ -1090,6 +1108,11 @@ export class App {
deleteData(userID, postID);
this.render();
}
renderComposeReplyArea(replyToID) {
let composeReplyArea = document.getElementById('compose-reply-area');
composeReplyArea.innerText = replyToID;
composeReplyArea.classList.add("show");
}
renderPost(post, first) {
if (!(post.hasOwnProperty("text"))) {
throw new Error("Post is malformed!");
@@ -1106,6 +1129,14 @@ export class App {
let shareUrl = `${document.location.origin}/user/${post.author_id}/post/${post.post_id}`;
await navigator.clipboard.writeText(shareUrl);
};
let replyButton = document.createElement('button');
replyButton.innerText = 'reply';
replyButton.onclick = async () => {
console.log(`replying to post ${post.post_id}`);
this.enterCompose(post.post_id);
// let shareUrl = `${document.location.origin}/user/${post.author_id}/post/${post.post_id}`;
// await navigator.clipboard.writeText(shareUrl)
};
let ownPost = post.author_id === this.userID;
let markdown = post.text;
if (this.markedAvailable) {
@@ -1124,11 +1155,17 @@ export class App {
<span class='header' title='${timestamp}'><img class="logo" src="/static/favicon.ico"><a class="username" href="${userURL}">@${post.author}</a> -
<span style="color:rgb(128,128,128)">${post.post_timestamp.toLocaleDateString()}</span>
</span>
${ownPost ? `<span id="deleteButton"></span>` : ''}
${ownPost ? `<span id="editButton"></span>` : ''}
<span id="shareButton"></span>
</div>
<div>${markdown}</div>
<div id="image"></div>
<span id="replyButton"></span>
${ownPost ? `<span id="editButton"></span>` : ''}
<span id="shareButton"></span>
${ownPost ? `<span id="deleteButton"></span>` : ''}
</div>`;
containerDiv.innerHTML = postTemplate;
if (ownPost) {
@@ -1136,24 +1173,23 @@ export class App {
// containerDiv.querySelector('#editButton')?.appendChild(editButton);
}
containerDiv.querySelector('#shareButton')?.appendChild(shareButton);
if (!("image_data" in post && post.image_data)) {
// containerDiv.appendChild(timestampDiv);
return containerDiv;
// return null;
containerDiv.querySelector('#replyButton')?.appendChild(replyButton);
let hasImage = ("image_data" in post && post.image_data);
if (hasImage) {
let image = document.createElement("img");
image.title = `${(post.image_data.byteLength / 1024 / 1024).toFixed(2)}MBytes`;
// const blob = new Blob([post.image_data as ArrayBuffer], { type: 'image/png' });
const blob = new Blob([post.image_data]);
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";
// image.onclick = () => { App.maximizeElement(image) };
containerDiv.querySelector('#image')?.appendChild(image);
}
let image = document.createElement("img");
image.title = `${(post.image_data.byteLength / 1024 / 1024).toFixed(2)}MBytes`;
// const blob = new Blob([post.image_data as ArrayBuffer], { type: 'image/png' });
const blob = new Blob([post.image_data]);
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";
// image.onclick = () => { App.maximizeElement(image) };
containerDiv.appendChild(image);
// containerDiv.appendChild(timestampDiv);
return containerDiv;
}