Working.
This commit is contained in:
96
src/App.ts
96
src/App.ts
@@ -1,7 +1,7 @@
|
||||
import { generateID } from "IDUtils";
|
||||
import { PeerManager, PeerEventTypes } from "PeerManager";
|
||||
import { Sync } from "Sync";
|
||||
import { openDatabase, getData, addData, addDataArray, clearData, deleteData, mergeDataArray, getAllData, checkPostIds, getAllIds, getPostsByIds, getPostForUser, getRepliesForPost, buildReplyCountMap } from "db";
|
||||
import { openDatabase, getData, addData, addDataArray, clearData, deleteData, mergeDataArray, getAllData, checkPostIds, getAllIds, getPostsByIds, getPostForUser, getPostById, getRepliesForPost, buildReplyCountMap } from "db";
|
||||
import { arrayBufferToBase64, compressString, decompressBuffer, base64ToArrayBuffer } from "dataUtils";
|
||||
import { log, logID, renderLog, setLogVisibility } from "log"
|
||||
|
||||
@@ -14,6 +14,7 @@ class Post {
|
||||
post_timestamp: Date;
|
||||
post_id: string;
|
||||
reply_to_id: string|null;
|
||||
root_id: string|null;
|
||||
author: string;
|
||||
author_id: string;
|
||||
text: string;
|
||||
@@ -29,7 +30,8 @@ class Post {
|
||||
imageData: ArrayBuffer | null = null,
|
||||
importedFrom: "twitter" | null = null,
|
||||
importSource: any = null,
|
||||
reply_to_id:string|null = null) {
|
||||
reply_to_id: string|null = null,
|
||||
root_id: string|null = null) {
|
||||
|
||||
this.post_timestamp = post_timestamp;
|
||||
this.post_id = generateID();
|
||||
@@ -42,6 +44,7 @@ class Post {
|
||||
this.importedFrom = importedFrom;
|
||||
this.importSource = importSource;
|
||||
this.reply_to_id = reply_to_id;
|
||||
this.root_id = root_id;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,6 +123,7 @@ export class App {
|
||||
userID: string = '';
|
||||
peerID: string = '';
|
||||
replyToID: string|null = null;
|
||||
replyRootID: string|null = null;
|
||||
following: Set<string> = new Set();
|
||||
posts: StoragePost[] = [];
|
||||
isHeadless: boolean = false;
|
||||
@@ -737,7 +741,7 @@ export class App {
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
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, replyRootID: string|null = null) {
|
||||
if ((typeof postText !== "string") || postText.length === 0) {
|
||||
console.log.apply(null, log("Not posting an empty string..."));
|
||||
return;
|
||||
@@ -752,7 +756,7 @@ export class App {
|
||||
}
|
||||
}
|
||||
|
||||
let post = new Post(this.username, userID, postText, new Date(), mediaData, null, null, replyToID);
|
||||
let post = new Post(this.username, userID, postText, new Date(), mediaData, null, null, replyToID, replyRootID);
|
||||
// this.posts.push(post);
|
||||
// localStorage.setItem(key, JSON.stringify(posts));
|
||||
addData(userID, post);
|
||||
@@ -763,27 +767,23 @@ export class App {
|
||||
this.render();
|
||||
}
|
||||
|
||||
getPeerID() {
|
||||
let id = localStorage.getItem("peer_id");
|
||||
|
||||
if (!id) {
|
||||
console.log.apply(null, log(`Didn't find a peer ID, generating one`));;
|
||||
id = generateID();
|
||||
localStorage.setItem("peer_id", id);
|
||||
}
|
||||
getPeerID(): string {
|
||||
const existing = localStorage.getItem("peer_id");
|
||||
if (existing) return existing;
|
||||
|
||||
console.log.apply(null, log(`Didn't find a peer ID, generating one`));
|
||||
const id = generateID();
|
||||
localStorage.setItem("peer_id", id);
|
||||
return id;
|
||||
}
|
||||
|
||||
getUserID() {
|
||||
let id = localStorage.getItem("dandelion_id");
|
||||
|
||||
if (!id) {
|
||||
console.log.apply(null, log(`Didn't find a user ID, generating one`));;
|
||||
id = generateID();
|
||||
localStorage.setItem("dandelion_id", id);
|
||||
}
|
||||
getUserID(): string {
|
||||
const existing = localStorage.getItem("dandelion_id");
|
||||
if (existing) return existing;
|
||||
|
||||
console.log.apply(null, log(`Didn't find a user ID, generating one`));
|
||||
const id = generateID();
|
||||
localStorage.setItem("dandelion_id", id);
|
||||
return id;
|
||||
}
|
||||
|
||||
@@ -1016,7 +1016,7 @@ export class App {
|
||||
filePicker?.addEventListener('change', async (event: any) => {
|
||||
for (let file of filePicker.files as any) {
|
||||
let buffer = await file.arrayBuffer();
|
||||
await this.createPost(this.userID, 'image...', buffer, file.type, this.replyToID);
|
||||
await this.createPost(this.userID, 'image...', buffer, file.type, this.replyToID, this.replyRootID);
|
||||
}
|
||||
|
||||
// Reset so that if they pick the same image again, we still get the change event.
|
||||
@@ -1075,11 +1075,11 @@ export class App {
|
||||
const dataTransfer = e.clipboardData
|
||||
const file = dataTransfer!.files[0];
|
||||
let buffer = await file.arrayBuffer();
|
||||
await this.createPost(this.userID, 'image...', buffer, file.type as any, this.replyToID);
|
||||
await this.createPost(this.userID, 'image...', buffer, file.type as any, this.replyToID, this.replyRootID);
|
||||
});
|
||||
|
||||
const submitPost = () => {
|
||||
this.createPost(userID, postText.value, undefined, undefined, this.replyToID);
|
||||
this.createPost(userID, postText.value, undefined, undefined, this.replyToID, this.replyRootID);
|
||||
this.exitCompose();
|
||||
};
|
||||
|
||||
@@ -1105,8 +1105,9 @@ export class App {
|
||||
|
||||
|
||||
// Change this all to a template so we're not toggling state in this crazy way!
|
||||
enterCompose(replyToID:string|null=null) {
|
||||
enterCompose(replyToID: string|null = null, replyRootID: string|null = null) {
|
||||
this.replyToID = replyToID;
|
||||
this.replyRootID = replyRootID;
|
||||
|
||||
if (replyToID) {
|
||||
this.renderComposeReplyArea(replyToID);
|
||||
@@ -1121,6 +1122,7 @@ export class App {
|
||||
|
||||
exitCompose() {
|
||||
this.replyToID = null;
|
||||
this.replyRootID = null;
|
||||
let postText = document.getElementById("textarea_post") as HTMLTextAreaElement;
|
||||
postText.value = "";
|
||||
document.getElementById('compose')!.style.display = 'none';
|
||||
@@ -1532,6 +1534,15 @@ export class App {
|
||||
contentDiv.appendChild(fragment);
|
||||
|
||||
if (isPostView && this.posts.length > 0) {
|
||||
const currentPost = this.posts[0].data;
|
||||
if (currentPost.root_id) {
|
||||
const rootRecord = await getPostById(currentPost.root_id);
|
||||
if (rootRecord) {
|
||||
const rootEl = this.renderPost(rootRecord.data, true, replyCountMap.recursive.get(rootRecord.data.post_id) ?? 0);
|
||||
contentDiv.insertBefore(rootEl, contentDiv.firstChild);
|
||||
}
|
||||
}
|
||||
|
||||
const renderReplies = async (postID: string, depth: number) => {
|
||||
if (depth > 2) return;
|
||||
const replies = await getRepliesForPost(postID);
|
||||
@@ -1552,7 +1563,7 @@ export class App {
|
||||
await renderReplies(reply.data.post_id, depth + 1);
|
||||
}
|
||||
};
|
||||
await renderReplies(this.posts[0].data.post_id, 0);
|
||||
await renderReplies(this.posts[0].data.post_id, currentPost.root_id ? 1 : 0);
|
||||
}
|
||||
|
||||
let renderTime = this.timerDelta();
|
||||
@@ -1575,10 +1586,31 @@ export class App {
|
||||
this.render();
|
||||
}
|
||||
|
||||
renderComposeReplyArea(replyToID:string) {
|
||||
let composeReplyArea = document.getElementById('compose-reply-area') as HTMLElement;
|
||||
composeReplyArea.innerText = replyToID;
|
||||
composeReplyArea.classList.add("show");
|
||||
async renderComposeReplyArea(replyToID: string) {
|
||||
const composeReplyArea = document.getElementById('compose-reply-area') as HTMLElement;
|
||||
composeReplyArea.innerHTML = '';
|
||||
|
||||
const record = await getPostById(replyToID);
|
||||
if (!record) return;
|
||||
const postData = record.data;
|
||||
|
||||
const previewDiv = document.createElement('div');
|
||||
previewDiv.className = 'compose-reply-preview';
|
||||
|
||||
const textDiv = document.createElement('div');
|
||||
textDiv.className = 'compose-reply-preview-text';
|
||||
textDiv.innerHTML = this.markedAvailable ? marked.parse(postData.text) : postData.text;
|
||||
previewDiv.appendChild(textDiv);
|
||||
|
||||
if (postData.image_data) {
|
||||
const img = document.createElement('img');
|
||||
img.className = 'compose-reply-preview-image';
|
||||
const blob = new Blob([postData.image_data as ArrayBuffer]);
|
||||
img.src = URL.createObjectURL(blob);
|
||||
previewDiv.appendChild(img);
|
||||
}
|
||||
|
||||
composeReplyArea.appendChild(previewDiv);
|
||||
}
|
||||
|
||||
renderPost(post: Post, first: boolean, replyCount: number = 0) {
|
||||
@@ -1603,10 +1635,8 @@ export class App {
|
||||
let replyButton = document.createElement('button'); replyButton.innerText = replyCount > 0 ? `reply (${replyCount})` : '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)
|
||||
const rootID = post.root_id ?? post.post_id;
|
||||
this.enterCompose(post.post_id, rootID);
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { openDatabase, getData, addData, addDataArray, clearData, deleteData, me
|
||||
import { log, logID } from "log";
|
||||
|
||||
|
||||
async function bytesToBase64DataUrl(bytes: Uint8Array, type = "application/octet-stream") {
|
||||
async function bytesToBase64DataUrl(bytes: Uint8Array<ArrayBuffer>, type = "application/octet-stream") {
|
||||
return await new Promise((resolve, reject) => {
|
||||
const reader = Object.assign(new FileReader(), {
|
||||
onload: () => resolve(reader.result),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export async function bytesToBase64DataUrl(bytes: Uint8Array, type = "application/octet-stream") {
|
||||
export async function bytesToBase64DataUrl(bytes: Uint8Array<ArrayBuffer>, type = "application/octet-stream") {
|
||||
return await new Promise((resolve, reject) => {
|
||||
const reader = Object.assign(new FileReader(), {
|
||||
onload: () => resolve(reader.result),
|
||||
|
||||
18
src/db.ts
18
src/db.ts
@@ -505,6 +505,24 @@ export async function buildReplyCountMap(): Promise<{ direct: Map<string, number
|
||||
return { direct, recursive };
|
||||
}
|
||||
|
||||
export async function getPostById(postID: string): Promise<any | undefined> {
|
||||
const knownUsers = [...(await indexedDB.databases())]
|
||||
.map(db => db.name?.replace('user_', '')).filter(Boolean) as string[];
|
||||
for (const userID of knownUsers) {
|
||||
try {
|
||||
const { store } = await getDBTransactionStore(userID);
|
||||
const index = store.index("postIDIndex");
|
||||
const result: any = await new Promise((resolve, reject) => {
|
||||
const req = index.get(postID);
|
||||
req.onsuccess = () => resolve(req.result);
|
||||
req.onerror = () => reject(req.error);
|
||||
});
|
||||
if (result) return result;
|
||||
} catch (_) {}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export async function getRepliesForPost(postID: string): Promise<any[]> {
|
||||
const knownUsers = [...(await indexedDB.databases())]
|
||||
.map(db => db.name?.replace('user_', '')).filter(Boolean) as string[];
|
||||
|
||||
@@ -230,7 +230,7 @@ window.addEventListener('scroll', () => {
|
||||
// }
|
||||
// }
|
||||
|
||||
async function bytesToBase64DataUrl(bytes: Uint8Array, type = "application/octet-stream") {
|
||||
async function bytesToBase64DataUrl(bytes: Uint8Array<ArrayBuffer>, type = "application/octet-stream") {
|
||||
return await new Promise((resolve, reject) => {
|
||||
const reader = Object.assign(new FileReader(), {
|
||||
onload: () => resolve(reader.result),
|
||||
@@ -918,7 +918,7 @@ class App {
|
||||
}_${String(d.getSeconds()).padStart(2, '0')}`;
|
||||
|
||||
|
||||
this.downloadBinary(compressedData, `ddln_${this.username}_export_${timestamp}.json.gz`);
|
||||
this.downloadBinary(compressedData.buffer, `ddln_${this.username}_export_${timestamp}.json.gz`);
|
||||
}
|
||||
|
||||
async importTweetArchive(userID: string, tweetArchive: any[]) {
|
||||
|
||||
Reference in New Issue
Block a user