ghost messages and some fixes
This commit is contained in:
parent
8e98535407
commit
c479d9a683
11 changed files with 222 additions and 29 deletions
|
@ -12,6 +12,7 @@ import {SnowFlake} from "./snowflake.js";
|
|||
import {
|
||||
channeljson,
|
||||
embedjson,
|
||||
filejson,
|
||||
messageCreateJson,
|
||||
messagejson,
|
||||
readyjson,
|
||||
|
@ -24,6 +25,7 @@ import {User} from "./user.js";
|
|||
import {I18n} from "./i18n.js";
|
||||
import {mobile} from "./utils/utils.js";
|
||||
import {webhookMenu} from "./webhooks.js";
|
||||
import {File} from "./file.js";
|
||||
|
||||
declare global {
|
||||
interface NotificationOptions {
|
||||
|
@ -926,6 +928,11 @@ class Channel extends SnowFlake {
|
|||
messages.append(html);
|
||||
}
|
||||
async getHTML(addstate = true) {
|
||||
const ghostMessages = document.getElementById("ghostMessages") as HTMLElement;
|
||||
ghostMessages.innerHTML = "";
|
||||
for (const thing of this.fakeMessages) {
|
||||
ghostMessages.append(thing[1]);
|
||||
}
|
||||
if (addstate) {
|
||||
history.pushState([this.guild_id, this.id], "", "/channels/" + this.guild_id + "/" + this.id);
|
||||
}
|
||||
|
@ -1438,6 +1445,91 @@ class Channel extends SnowFlake {
|
|||
return "default";
|
||||
}
|
||||
}
|
||||
fakeMessages: [Message, HTMLElement][] = [];
|
||||
fakeMessageMap = new Map<string, [Message, HTMLElement]>();
|
||||
destroyFakeMessage(id: string) {
|
||||
const message = this.fakeMessageMap.get(id);
|
||||
if (!message) return;
|
||||
this.fakeMessages = this.fakeMessages.filter((_) => _[0] !== message[0]);
|
||||
message[1].remove();
|
||||
for (const {url} of message[0].attachments) {
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
this.fakeMessageMap.delete(id);
|
||||
}
|
||||
|
||||
makeFakeMessage(content: string, files: filejson[] = []) {
|
||||
const m = new Message(
|
||||
{
|
||||
author: this.localuser.user.tojson(),
|
||||
channel_id: this.id,
|
||||
guild_id: this.guild.id,
|
||||
id: "fake" + Math.random(),
|
||||
content,
|
||||
timestamp: new Date() + "",
|
||||
edited_timestamp: null,
|
||||
mentions: [],
|
||||
mention_roles: [],
|
||||
mention_everyone: false,
|
||||
attachments: files,
|
||||
tts: false,
|
||||
embeds: [],
|
||||
reactions: [],
|
||||
nonce: Math.random() + "",
|
||||
type: 0,
|
||||
pinned: false,
|
||||
},
|
||||
this,
|
||||
true,
|
||||
);
|
||||
const ghostMessages = document.getElementById("ghostMessages");
|
||||
if (!ghostMessages) throw Error("oops");
|
||||
const html = m.buildhtml(this.lastmessage, true);
|
||||
html.classList.add("messagediv", "loadingMessage");
|
||||
console.log(html);
|
||||
ghostMessages.append(html);
|
||||
this.fakeMessages.push([m, html]);
|
||||
let loadingP = document.createElement("span");
|
||||
|
||||
const buttons = document.createElement("div");
|
||||
buttons.classList.add("flexltr");
|
||||
|
||||
const retryB = document.createElement("button");
|
||||
retryB.textContent = I18n.message.retry();
|
||||
|
||||
const dont = document.createElement("button");
|
||||
dont.textContent = I18n.message.delete();
|
||||
dont.onclick = (_) => html.remove();
|
||||
dont.style.marginLeft = "4px";
|
||||
buttons.append(retryB, dont);
|
||||
return {
|
||||
gotid: (id: string) => {
|
||||
this.fakeMessageMap.set(id, [m, html]);
|
||||
const m2 = this.messages.get(id);
|
||||
if (m2 && m2.div) {
|
||||
this.destroyFakeMessage(id);
|
||||
}
|
||||
},
|
||||
progress: (total: number, sofar: number) => {
|
||||
if (total < 20000 || sofar === total) {
|
||||
loadingP.remove();
|
||||
return;
|
||||
}
|
||||
html.append(loadingP);
|
||||
loadingP.textContent = File.filesizehuman(sofar) + " / " + File.filesizehuman(total);
|
||||
},
|
||||
failed: (retry: () => void) => {
|
||||
loadingP.remove();
|
||||
html.append(buttons);
|
||||
retryB.onclick = () => {
|
||||
retry();
|
||||
html.classList.remove("erroredMessage");
|
||||
buttons.remove();
|
||||
};
|
||||
html.classList.add("erroredMessage");
|
||||
},
|
||||
};
|
||||
}
|
||||
async sendMessage(
|
||||
content: string,
|
||||
{
|
||||
|
@ -1453,6 +1545,36 @@ class Channel extends SnowFlake {
|
|||
message_id: replyingto.id,
|
||||
};
|
||||
}
|
||||
|
||||
let prom: Promise<void>;
|
||||
let res: XMLHttpRequest;
|
||||
let funcs: {
|
||||
gotid: (id: string) => void;
|
||||
progress: (total: number, sofar: number) => void;
|
||||
failed: (restart: () => void) => void;
|
||||
};
|
||||
const progress = (e: ProgressEvent<EventTarget>) => {
|
||||
funcs.progress(e.total, e.loaded);
|
||||
};
|
||||
const promiseHandler = (resolve: () => void) => {
|
||||
res.onload = () => {
|
||||
resolve();
|
||||
console.log(res.response);
|
||||
funcs.gotid(res.response.id);
|
||||
};
|
||||
};
|
||||
const fail = () => {
|
||||
funcs.failed(() => {
|
||||
res.open("POST", this.info.api + "/channels/" + this.id + "/messages");
|
||||
res.setRequestHeader("Authorization", this.headers.Authorization);
|
||||
if (ctype) {
|
||||
res.setRequestHeader("Content-type", ctype);
|
||||
}
|
||||
res.send(rbody);
|
||||
});
|
||||
};
|
||||
let rbody: string | FormData;
|
||||
let ctype: string | undefined;
|
||||
if (attachments.length === 0) {
|
||||
const body = {
|
||||
content,
|
||||
|
@ -1462,11 +1584,23 @@ class Channel extends SnowFlake {
|
|||
if (replyjson) {
|
||||
body.message_reference = replyjson;
|
||||
}
|
||||
return await fetch(this.info.api + "/channels/" + this.id + "/messages", {
|
||||
res = new XMLHttpRequest();
|
||||
res.responseType = "json";
|
||||
res.upload.onprogress = progress;
|
||||
res.onerror = fail;
|
||||
prom = new Promise<void>(promiseHandler);
|
||||
res.open("POST", this.info.api + "/channels/" + this.id + "/messages");
|
||||
res.setRequestHeader("Content-type", (ctype = this.headers["Content-type"]));
|
||||
res.setRequestHeader("Authorization", this.headers.Authorization);
|
||||
funcs = this.makeFakeMessage(content);
|
||||
res.send((rbody = JSON.stringify(body)));
|
||||
/*
|
||||
res = fetch(this.info.api + "/channels/" + this.id + "/messages", {
|
||||
method: "POST",
|
||||
headers: this.headers,
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
*/
|
||||
} else {
|
||||
const formData = new FormData();
|
||||
const body = {
|
||||
|
@ -1481,12 +1615,36 @@ class Channel extends SnowFlake {
|
|||
for (const i in attachments) {
|
||||
formData.append("files[" + i + "]", attachments[i]);
|
||||
}
|
||||
return await fetch(this.info.api + "/channels/" + this.id + "/messages", {
|
||||
|
||||
res = new XMLHttpRequest();
|
||||
res.responseType = "json";
|
||||
res.upload.onprogress = progress;
|
||||
res.onerror = fail;
|
||||
prom = new Promise<void>(promiseHandler);
|
||||
res.open("POST", this.info.api + "/channels/" + this.id + "/messages", true);
|
||||
|
||||
res.setRequestHeader("Authorization", this.headers.Authorization);
|
||||
funcs = this.makeFakeMessage(
|
||||
content,
|
||||
attachments.map((_) => ({
|
||||
id: "string",
|
||||
filename: "",
|
||||
content_type: _.type,
|
||||
size: _.size,
|
||||
url: URL.createObjectURL(_),
|
||||
})),
|
||||
);
|
||||
res.send((rbody = formData));
|
||||
/*
|
||||
res = fetch(this.info.api + "/channels/" + this.id + "/messages", {
|
||||
method: "POST",
|
||||
body: formData,
|
||||
headers: {Authorization: this.headers.Authorization},
|
||||
});
|
||||
*/
|
||||
}
|
||||
|
||||
return prom;
|
||||
}
|
||||
unreads() {
|
||||
if (!this.hasunreads) {
|
||||
|
@ -1502,7 +1660,12 @@ class Channel extends SnowFlake {
|
|||
}
|
||||
}
|
||||
}
|
||||
messageCreate(messagep: messageCreateJson): void {
|
||||
async messageCreate(messagep: messageCreateJson): Promise<void> {
|
||||
if (this.localuser.channelfocus !== this) {
|
||||
if (this.fakeMessageMap.has(this.id)) {
|
||||
this.destroyFakeMessage(this.id);
|
||||
}
|
||||
}
|
||||
if (!this.hasPermission("VIEW_CHANNEL")) {
|
||||
return;
|
||||
}
|
||||
|
@ -1528,9 +1691,9 @@ class Channel extends SnowFlake {
|
|||
this.guild.unreads();
|
||||
if (this === this.localuser.channelfocus) {
|
||||
if (!this.infinitefocus) {
|
||||
this.tryfocusinfinate();
|
||||
await this.tryfocusinfinate();
|
||||
}
|
||||
this.infinite.addedBottom();
|
||||
await this.infinite.addedBottom();
|
||||
}
|
||||
if (messagez.author === this.localuser.user) {
|
||||
return;
|
||||
|
|
|
@ -429,6 +429,11 @@ class Group extends Channel {
|
|||
return div;
|
||||
}
|
||||
async getHTML(addstate = true) {
|
||||
const ghostMessages = document.getElementById("ghostMessages") as HTMLElement;
|
||||
ghostMessages.innerHTML = "";
|
||||
for (const thing of this.fakeMessages) {
|
||||
ghostMessages.append(thing[1]);
|
||||
}
|
||||
const id = ++Channel.genid;
|
||||
if (this.localuser.channelfocus) {
|
||||
this.localuser.channelfocus.infinite.delete();
|
||||
|
@ -462,7 +467,15 @@ class Group extends Channel {
|
|||
}
|
||||
this.buildmessages();
|
||||
}
|
||||
messageCreate(messagep: {d: messagejson}) {
|
||||
async messageCreate(messagep: {d: messagejson}) {
|
||||
if (this.localuser.channelfocus !== this) {
|
||||
if (this.fakeMessageMap.has(this.id)) {
|
||||
this.destroyFakeMessage(this.id);
|
||||
}
|
||||
}
|
||||
if (this.fakeMessageMap.has(messagep.d.id)) {
|
||||
this.destroyFakeMessage(messagep.d.id);
|
||||
}
|
||||
this.mentions++;
|
||||
const messagez = new Message(messagep.d, this);
|
||||
|
||||
|
@ -501,9 +514,9 @@ class Group extends Channel {
|
|||
}
|
||||
if (this === this.localuser.channelfocus) {
|
||||
if (!this.infinitefocus) {
|
||||
this.tryfocusinfinate();
|
||||
await this.tryfocusinfinate();
|
||||
}
|
||||
this.infinite.addedBottom();
|
||||
await this.infinite.addedBottom();
|
||||
}
|
||||
this.unreads();
|
||||
if (messagez.author === this.localuser.user) {
|
||||
|
|
|
@ -88,6 +88,7 @@
|
|||
<div style="position: relative">
|
||||
<div id="searchOptions" class="flexttb searchOptions"></div>
|
||||
</div>
|
||||
<div id="ghostMessages"></div>
|
||||
<div id="pasteimage" class="flexltr"></div>
|
||||
<div id="replybox" class="hideReplyBox"></div>
|
||||
<div id="typediv">
|
||||
|
|
|
@ -188,11 +188,11 @@ import {I18n} from "./i18n.js";
|
|||
(document.getElementById("settings") as HTMLImageElement).onclick = userSettings;
|
||||
const memberListToggle = document.getElementById("memberlisttoggle") as HTMLInputElement;
|
||||
memberListToggle.checked = !localStorage.getItem("memberNotChecked");
|
||||
memberListToggle.onclick = () => {
|
||||
memberListToggle.onchange = () => {
|
||||
if (!memberListToggle.checked) {
|
||||
localStorage.setItem("memberNotChecked", "true");
|
||||
} else {
|
||||
localStorage.delete("memberNotChecked");
|
||||
localStorage.removeItem("memberNotChecked");
|
||||
}
|
||||
};
|
||||
if (mobile) {
|
||||
|
|
|
@ -130,7 +130,13 @@ class InfiniteScroller {
|
|||
async addedBottom(): Promise<void> {
|
||||
await this.updatestuff();
|
||||
const func = this.snapBottom();
|
||||
await this.watchForChange();
|
||||
if (this.changePromise) {
|
||||
while (this.changePromise) {
|
||||
await new Promise((res) => setTimeout(res, 30));
|
||||
}
|
||||
} else {
|
||||
await this.watchForChange();
|
||||
}
|
||||
func();
|
||||
}
|
||||
|
||||
|
@ -245,6 +251,7 @@ class InfiniteScroller {
|
|||
}
|
||||
|
||||
this.changePromise = new Promise<boolean>(async (res) => {
|
||||
//debugger;
|
||||
try {
|
||||
if (!this.div) {
|
||||
res(false);
|
||||
|
|
|
@ -334,7 +334,7 @@ type messagejson = {
|
|||
member?: memberjson;
|
||||
content: string;
|
||||
timestamp: string;
|
||||
edited_timestamp: string;
|
||||
edited_timestamp: string | null;
|
||||
tts: boolean;
|
||||
mention_everyone: boolean;
|
||||
mentions: []; //need examples to fix
|
||||
|
@ -349,7 +349,7 @@ type messagejson = {
|
|||
nonce: string;
|
||||
pinned: boolean;
|
||||
type: number;
|
||||
webhook: webhookInfo;
|
||||
webhook?: webhookInfo;
|
||||
};
|
||||
type filejson = {
|
||||
id: string;
|
||||
|
|
|
@ -156,7 +156,6 @@ function makePlayBox(mor: string | media, player: MediaPlayer, ctime = 0) {
|
|||
};
|
||||
function followUpdates(cur: mediaEvents) {
|
||||
if (audio && cur.type !== "playing") {
|
||||
console.log(cur);
|
||||
}
|
||||
if (cur.type == "audio" && audio) {
|
||||
if (cur.t == "start") {
|
||||
|
@ -220,7 +219,6 @@ function makePlayBox(mor: string | media, player: MediaPlayer, ctime = 0) {
|
|||
regenTime(+bar.value * 1000);
|
||||
};
|
||||
async function regenTime(curTime: number = 0) {
|
||||
console.log(med.length);
|
||||
const len = await med.length;
|
||||
bar.disabled = false;
|
||||
bar.max = "" + len / 1000;
|
||||
|
@ -229,7 +227,6 @@ function makePlayBox(mor: string | media, player: MediaPlayer, ctime = 0) {
|
|||
}
|
||||
regenTime();
|
||||
title.textContent = thing.title;
|
||||
console.log(thing);
|
||||
});
|
||||
return div;
|
||||
}
|
||||
|
@ -274,7 +271,6 @@ class MediaPlayer {
|
|||
if (!document.contains(elm)) {
|
||||
clearInterval(int);
|
||||
this.listeners = this.listeners.filter((_) => _[0] !== updates);
|
||||
console.log("cleared data");
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
@ -352,7 +348,6 @@ class MediaPlayer {
|
|||
if (size !== 0) {
|
||||
cbuff = (await read.read()).value;
|
||||
index = 0;
|
||||
console.log("got more buffer", index, arri, size);
|
||||
}
|
||||
}
|
||||
return arr;
|
||||
|
@ -372,7 +367,6 @@ class MediaPlayer {
|
|||
const Identify = String.fromCharCode(await next(), await next(), await next());
|
||||
const sizeArr = await get8BitArray(3);
|
||||
const size = (sizeArr[0] << 16) + (sizeArr[1] << 8) + sizeArr[2];
|
||||
console.log(sizeLeft, size, index);
|
||||
if (Identify === String.fromCharCode(0, 0, 0)) {
|
||||
break;
|
||||
}
|
||||
|
@ -389,7 +383,6 @@ class MediaPlayer {
|
|||
} else {
|
||||
mappy.set(Identify, await get8BitArray(size));
|
||||
}
|
||||
console.warn(sizeLeft);
|
||||
}
|
||||
const pic = mappy.get("PIC");
|
||||
if (pic) {
|
||||
|
@ -400,7 +393,6 @@ class MediaPlayer {
|
|||
}
|
||||
const description = new TextDecoder().decode(new Uint8Array(desc));
|
||||
i++;
|
||||
console.warn(pic, i);
|
||||
const blob = new Blob([pic.slice(i, pic.length).buffer], {type: "image/jpeg"});
|
||||
const urlmaker = window.URL || window.webkitURL;
|
||||
const url = urlmaker.createObjectURL(blob);
|
||||
|
@ -467,7 +459,6 @@ class MediaPlayer {
|
|||
continue;
|
||||
}
|
||||
|
||||
console.log(sizeLeft, size, index);
|
||||
if (Identify === String.fromCharCode(0, 0, 0, 0)) {
|
||||
break;
|
||||
}
|
||||
|
@ -484,7 +475,6 @@ class MediaPlayer {
|
|||
} else {
|
||||
mappy.set(Identify, await get8BitArray(size));
|
||||
}
|
||||
console.warn(sizeLeft);
|
||||
}
|
||||
const pic = mappy.get("APIC");
|
||||
if (pic) {
|
||||
|
@ -530,14 +520,12 @@ class MediaPlayer {
|
|||
}
|
||||
const TYER = mappy.get("TYER");
|
||||
if (TYER) {
|
||||
console.log(decodeText(TYER));
|
||||
output.year = +decodeText(TYER);
|
||||
}
|
||||
const TLEN = mappy.get("TLEN");
|
||||
if (TLEN) {
|
||||
output.length = +decodeText(TLEN);
|
||||
}
|
||||
console.log(mappy);
|
||||
}
|
||||
} //TODO implement more metadata types
|
||||
} catch (e) {
|
||||
|
@ -550,12 +538,10 @@ class MediaPlayer {
|
|||
const audio = document.createElement("audio");
|
||||
audio.src = url;
|
||||
audio.onloadeddata = (_) => {
|
||||
console.log("Loaded!", audio.duration * 1000);
|
||||
output.length = audio.duration * 1000;
|
||||
res(audio.duration * 1000);
|
||||
};
|
||||
audio.load();
|
||||
console.log(audio);
|
||||
});
|
||||
}
|
||||
if (!output.title) {
|
||||
|
|
|
@ -310,6 +310,9 @@ class Message extends SnowFlake {
|
|||
}
|
||||
return build;
|
||||
}
|
||||
getUnixTime(): number {
|
||||
return new Date(this.timestamp).getTime();
|
||||
}
|
||||
async edit(content: string) {
|
||||
return await fetch(this.info.api + "/channels/" + this.channel.id + "/messages/" + this.id, {
|
||||
method: "PATCH",
|
||||
|
@ -920,6 +923,9 @@ class Message extends SnowFlake {
|
|||
}
|
||||
}
|
||||
buildhtml(premessage?: Message | undefined, dupe = false): HTMLElement {
|
||||
if (this.channel.fakeMessageMap.has(this.id)) {
|
||||
this.channel.destroyFakeMessage(this.id);
|
||||
}
|
||||
if (dupe) {
|
||||
return this.generateMessage(premessage, false, document.createElement("div")) as HTMLElement;
|
||||
}
|
||||
|
|
|
@ -123,6 +123,9 @@ async function getfile(event: FetchEvent): Promise<Response> {
|
|||
|
||||
self.addEventListener("fetch", (e) => {
|
||||
const event = e as FetchEvent;
|
||||
if (event.request.method === "POST") {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
event.respondWith(getfile(event));
|
||||
} catch (e) {
|
||||
|
|
|
@ -1087,6 +1087,9 @@ span.instanceStatus {
|
|||
overflow-y: auto;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
#ghostMessages {
|
||||
transform: translate(0px, -22px);
|
||||
}
|
||||
#pasteimage:empty {
|
||||
height: 0;
|
||||
padding: 0;
|
||||
|
@ -1218,7 +1221,17 @@ span.instanceStatus {
|
|||
.dot:nth-child(3) {
|
||||
animation-delay: 0.66s;
|
||||
}
|
||||
|
||||
.loadingMessage {
|
||||
span {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
.erroredMessage {
|
||||
span {
|
||||
opacity: 1;
|
||||
color: var(--red);
|
||||
}
|
||||
}
|
||||
/* Message */
|
||||
.messagediv,
|
||||
.titlespace {
|
||||
|
|
|
@ -393,7 +393,8 @@
|
|||
"edit": "Edit message",
|
||||
"edited": "(edited)",
|
||||
"deleted": "Deleted message",
|
||||
"attached": "Sent an attachment"
|
||||
"attached": "Sent an attachment",
|
||||
"retry": "Resend errored message"
|
||||
},
|
||||
"instanceStats": {
|
||||
"name": "Instance stats: $1",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue