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

@@ -13,12 +13,11 @@ type PeerID = string;
class Post {
post_timestamp: Date;
post_id: string;
reply_to_id: string|null;
author: string;
author_id: string;
text: string;
image_data: ArrayBuffer | null;
importedFrom: "twitter" | null;
importSource: any;
@@ -29,7 +28,8 @@ class Post {
post_timestamp: Date,
imageData: ArrayBuffer | null = null,
importedFrom: "twitter" | null = null,
importSource: any = null) {
importSource: any = null,
reply_to_id:string|null = null) {
this.post_timestamp = post_timestamp;
this.post_id = generateID();
@@ -41,6 +41,7 @@ class Post {
this.importedFrom = importedFrom;
this.importSource = importSource;
this.reply_to_id = reply_to_id;
}
}
@@ -118,6 +119,7 @@ export class App {
peername: string = '';
userID: string = '';
peerID: string = '';
replyToID: string|null = null;
following: Set<string> = new Set();
posts: StoragePost[] = [];
isHeadless: boolean = false;
@@ -342,23 +344,16 @@ export class App {
}
return true;
// return posts;
// return postIDs;
});
this.peerManager.registerRPC('sendPostForUser', async (sendingPeerID: string, userID: string, post: 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++
@@ -372,14 +367,13 @@ 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.")
@@ -729,7 +723,7 @@ export class App {
}
}
async createNewPost(userID: string, postText: string, mediaData?: ArrayBuffer, mimeType?: "image/png" | "image/gif" | "image/jpg" | "image/jpeg" | "video/mp4") {
async createPost(userID: string, postText: string, mediaData?: ArrayBuffer, mimeType?: "image/svg+xml" | "image/png" | "image/gif" | "image/jpg" | "image/jpeg" | "video/mp4", replyToID:string|null = null) {
if ((typeof postText !== "string") || postText.length === 0) {
console.log.apply(null, log("Not posting an empty string..."));
return;
@@ -737,14 +731,14 @@ export class App {
if (mediaData &&
(mimeType === 'image/jpg' || mimeType === 'image/jpeg' || mimeType === 'image/png') &&
(mediaData as ArrayBuffer).byteLength > 500 * 1024) {
(mediaData as ArrayBuffer).byteLength > 256 * 1024) {
let compressedImage = await this.compressImage(mediaData as ArrayBuffer, mimeType, 0.9);
if (compressedImage) {
mediaData = compressedImage as ArrayBuffer;
}
}
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);
@@ -990,8 +984,7 @@ 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();
});
@@ -999,11 +992,13 @@ export class App {
filePicker?.addEventListener('change', async (event: any) => {
for (let file of filePicker.files as any) {
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');
@@ -1040,25 +1035,28 @@ export class App {
// clearPostsButton.addEventListener('click', () => { clearData(userID); posts = []; this.render() });
let postButton = document.getElementById("button_post") as HTMLButtonElement;
let cancelPostButton = document.getElementById("button_cancel_post") as HTMLElement;
let postText = document.getElementById("textarea_post") as HTMLTextAreaElement;
if (!(postButton && postText)) {
let postButton = document.getElementById("button_post") as HTMLButtonElement;
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 as any);
await this.createPost(this.userID, 'image...', buffer, file.type as any);
});
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", () => {
@@ -1072,6 +1070,31 @@ export class App {
}
// Change this all to a template so we're not toggling state in this crazy way!
enterCompose(replyToID:string|null=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") as HTMLTextAreaElement;
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.
@@ -1486,6 +1509,12 @@ export class App {
this.render();
}
renderComposeReplyArea(replyToID:string) {
let composeReplyArea = document.getElementById('compose-reply-area') as HTMLElement;
composeReplyArea.innerText = replyToID;
composeReplyArea.classList.add("show");
}
renderPost(post: Post, first: boolean) {
if (!(post.hasOwnProperty("text"))) {
throw new Error("Post is malformed!");
@@ -1505,6 +1534,16 @@ export class App {
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;
@@ -1530,11 +1569,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;
@@ -1545,33 +1590,33 @@ export class App {
// containerDiv.querySelector('#editButton')?.appendChild(editButton);
}
containerDiv.querySelector('#shareButton')?.appendChild(shareButton);
containerDiv.querySelector('#replyButton')?.appendChild(replyButton);
if (!("image_data" in post && post.image_data)) {
// containerDiv.appendChild(timestampDiv);
return containerDiv;
// return null;
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 as ArrayBuffer]);
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 as ArrayBuffer]);
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;
}