2024-07-05 01:27:29 +00:00
|
|
|
import util from "./util"
|
2024-07-16 19:43:35 +00:00
|
|
|
|
2024-07-05 01:27:29 +00:00
|
|
|
import { Message, Author } from "./message"
|
|
|
|
import { MessageThread } from "./thread"
|
2024-07-16 19:43:35 +00:00
|
|
|
import { AdapterState } from "./adapter"
|
2024-08-03 16:52:33 +00:00
|
|
|
import { BatchTimer } from "./batch-timer"
|
2024-07-05 01:27:29 +00:00
|
|
|
|
|
|
|
export class AdapterElement extends HTMLElement {
|
|
|
|
static observedAttributes = [ "data-latest", "data-view", "data-viewing" ]
|
|
|
|
|
|
|
|
private _latest: string = "" ;
|
|
|
|
private _view: string = "";
|
|
|
|
private _name: string = ""
|
|
|
|
private _viewing: string = "";
|
|
|
|
|
2024-08-03 16:52:33 +00:00
|
|
|
private _convoyBatchTimer = new BatchTimer((ids: string[])=>{
|
|
|
|
let url = `/api/adapters/${this._name}/fetch?entity_type=convoy`;
|
|
|
|
for (let id of ids) {
|
|
|
|
url += `&entity_id=${id}`;
|
|
|
|
}
|
|
|
|
util.authorizedFetch("GET", url, null)
|
|
|
|
});
|
|
|
|
|
2024-07-05 01:27:29 +00:00
|
|
|
// TODO: use visibility of the thread to organize into DMs and public threads
|
|
|
|
private _threads: MessageThread[] = [];
|
|
|
|
private _orphans: Message[] = [];
|
|
|
|
|
|
|
|
constructor() {
|
|
|
|
super();
|
|
|
|
}
|
|
|
|
|
|
|
|
connectedCallback() {
|
|
|
|
const name = this.getAttribute("data-name");
|
|
|
|
this._name = name ?? "";
|
2024-07-05 05:23:56 +00:00
|
|
|
this._view = "";
|
2024-07-05 01:27:29 +00:00
|
|
|
this.buildThreads();
|
2024-07-05 05:23:56 +00:00
|
|
|
this.setAttribute("data-view", "index");
|
2024-07-05 01:27:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
attributeChangedCallback() {
|
|
|
|
// set the viewing subject if it's changed
|
|
|
|
const viewing = this.getAttribute("data-viewing");
|
|
|
|
if (this._viewing != viewing && viewing != null) {
|
2024-07-16 19:43:35 +00:00
|
|
|
console.log(`${this._name}.attributeChangedCallback: resetting viewing subject`);
|
2024-07-05 01:27:29 +00:00
|
|
|
this._viewing = viewing;
|
|
|
|
// if the viewing subject changed (not to nothing), unset the view
|
|
|
|
// this will force it to refresh
|
|
|
|
if (this._viewing) {
|
2024-07-16 19:43:35 +00:00
|
|
|
|
|
|
|
console.log(`${this._name}.attributeChangedCallback: forcing view update`);
|
2024-07-05 01:27:29 +00:00
|
|
|
this._view = "";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// initialize the view if it's changed
|
|
|
|
const view = this.getAttribute("data-view");
|
2024-07-05 05:23:56 +00:00
|
|
|
if (this._view != view ?? "index") {
|
|
|
|
this._view = view ?? "index";
|
2024-07-16 19:43:35 +00:00
|
|
|
|
|
|
|
console.log(`${this._name}.attributeChangedCallback: setting view: ${this._view}`);
|
2024-07-05 01:27:29 +00:00
|
|
|
switch (this._view) {
|
|
|
|
case "index":
|
|
|
|
this.setIdxView();
|
|
|
|
this.populateIdxView();
|
|
|
|
break;
|
|
|
|
case "thread":
|
|
|
|
this.setThreadView();
|
|
|
|
this.populateThreadView();
|
|
|
|
break;
|
|
|
|
case "profile":
|
|
|
|
this.setProfileView();
|
|
|
|
this.populateProfileView();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// if latest changed, check if it's a message
|
2024-07-16 19:43:35 +00:00
|
|
|
const latest = this.getAttribute("data-latest");
|
2024-07-05 01:27:29 +00:00
|
|
|
if (latest ?? "" != this._latest) {
|
|
|
|
this._latest = latest ?? "";
|
2024-07-16 19:43:35 +00:00
|
|
|
let datastore = AdapterState._instance.data.get(this._name);
|
|
|
|
if (!datastore) {
|
2024-08-03 16:52:33 +00:00
|
|
|
//util.errMsg(this._name + " has no datastore!");
|
2024-07-16 19:43:35 +00:00
|
|
|
return;
|
|
|
|
}
|
2024-07-05 01:27:29 +00:00
|
|
|
const latestMsg = datastore.messages.get(this._latest);
|
|
|
|
if (latestMsg) {
|
2024-07-16 19:43:35 +00:00
|
|
|
console.log('latest was a message; place it');
|
2024-07-05 01:27:29 +00:00
|
|
|
const rootId = this.placeMsg(this._latest);
|
|
|
|
// if rootId is null, this is an orphan and we don't need to actually do any updates yet
|
|
|
|
if (rootId) {
|
|
|
|
switch (this._view) {
|
|
|
|
case "index":
|
2024-07-16 19:43:35 +00:00
|
|
|
console.log(`message was placed in thread ${rootId}, update view`)
|
2024-07-05 01:27:29 +00:00
|
|
|
this.updateIdxView(this._latest, rootId);
|
|
|
|
break;
|
|
|
|
case "thread":
|
|
|
|
// if the the message is part of this thread, update it
|
|
|
|
case "profile":
|
|
|
|
// if the message is from this user, show it in their profile
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
2024-07-16 19:43:35 +00:00
|
|
|
const latestAuthor = datastore.profileCache.get(this._latest);
|
|
|
|
if (latestAuthor) {
|
|
|
|
switch (this._view) {
|
|
|
|
case "index":
|
|
|
|
console.log (`author was updated: ${this._latest}, update their threads`)
|
|
|
|
const threadsByThisAuthor = this._threads.filter(t=>t.root.data.author == this._latest);
|
|
|
|
for (let t of threadsByThisAuthor) {
|
|
|
|
let tse = this.querySelector(`underbbs-thread-summary[data-msg='${t.root.data.id}']`)
|
|
|
|
if (tse) {
|
|
|
|
console.log(`author has a thread in the dom, update it: ${t.root.data.id}`)
|
|
|
|
tse.setAttribute("data-author", this._latest);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
case "thread":
|
|
|
|
case "profile":
|
|
|
|
break;
|
|
|
|
}
|
2024-07-05 01:27:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
// so, try to insert it into the threads
|
|
|
|
// then, switch on view
|
|
|
|
// if index, iterate through the topics and find the one to indicate new activity,
|
|
|
|
// if thread, if any relatives are in this thread, insert message appropriately
|
|
|
|
// if profile, if latest is this profile, update it
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
setIdxView() {
|
|
|
|
this.innerHTML = "<ul id='dm_list'></ul><ul id='public_list'></ul>"
|
|
|
|
}
|
|
|
|
|
|
|
|
setThreadView() {
|
|
|
|
let html = `<a href="#${this._name}">← return to index</a>`;
|
|
|
|
html += "<ul id='msg_list'></ul>";
|
|
|
|
this.innerHTML = html;
|
|
|
|
}
|
|
|
|
|
|
|
|
setProfileView() {
|
2024-07-16 19:43:35 +00:00
|
|
|
let profile_bar = util.$("profile_bar");
|
2024-07-05 01:27:29 +00:00
|
|
|
if (profile_bar) {
|
|
|
|
// clear any previous data
|
|
|
|
} else {
|
|
|
|
// insert the profileSidebar into the dom
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
populateIdxView() {
|
|
|
|
// skip dm list for now
|
|
|
|
// public/unified list
|
2024-07-16 19:43:35 +00:00
|
|
|
const pl = util.$("public_list");
|
2024-07-05 01:27:29 +00:00
|
|
|
if (pl) {
|
2024-07-05 05:23:56 +00:00
|
|
|
let html = "";
|
2024-07-16 19:43:35 +00:00
|
|
|
for (const t of this._threads.sort((a: MessageThread, b: MessageThread) => b.latest - a.latest)) {
|
|
|
|
html +=`<li><underbbs-thread-summary data-len="${t.messageCount}" data-adapter="${t.root.data.adapter}" data-msg="${t.root.data.id}" data-created="${t.created}" data-latest="${t.latest}"></underbbs-thread-summary></li>`;
|
2024-07-05 01:27:29 +00:00
|
|
|
}
|
2024-07-05 05:23:56 +00:00
|
|
|
pl.innerHTML = html;
|
2024-07-05 01:27:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
updateIdxView(latest: string, rootId: string) {
|
2024-08-03 16:52:33 +00:00
|
|
|
const existingThread = document.querySelector(`underbbs-thread-summary[data-msg='${rootId}']`);
|
2024-07-05 01:27:29 +00:00
|
|
|
const thread = this._threads.find(t=>t.root.data.id == rootId);
|
2024-08-03 16:52:33 +00:00
|
|
|
console.log(`looking for thread ${rootId}`)
|
|
|
|
console.log(`- DOM object: ${existingThread}`);
|
|
|
|
console.log(`- in memory: ${thread}`);
|
2024-07-05 01:27:29 +00:00
|
|
|
if (existingThread && thread) {
|
2024-07-16 19:43:35 +00:00
|
|
|
console.log(`updating thread: ${thread.root.data.id} // ${thread.messageCount} NEW`)
|
|
|
|
existingThread.setAttribute("data-latest", `${thread.latest}`);
|
2024-07-05 01:27:29 +00:00
|
|
|
existingThread.setAttribute("data-len", `${thread.messageCount}`);
|
|
|
|
existingThread.setAttribute("data-new", "true");
|
|
|
|
} else {
|
|
|
|
// unified/public list for now
|
2024-07-16 19:43:35 +00:00
|
|
|
const pl = util.$("public_list");
|
2024-07-05 01:27:29 +00:00
|
|
|
if (pl && thread) {
|
2024-07-16 19:43:35 +00:00
|
|
|
const li = document.createElement("li");
|
|
|
|
li.innerHTML = `<underbbs-thread-summary data-len="1" data-adapter="${thread.root.data.adapter}" data-msg="${thread.root.data.id}" data-latest="${thread.latest}" data-created="${thread.created}"></underbbs-thread-summary>`;
|
|
|
|
let nextThread: Element | null = null;
|
|
|
|
for (let i = 0; i < pl.children.length; i++) {
|
|
|
|
const c = pl.children.item(i);
|
|
|
|
const latest = c?.children.item(0)?.getAttribute("data-latest")
|
|
|
|
if (latest && parseInt(latest) < thread.latest) {
|
|
|
|
nextThread = c;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (nextThread) {
|
|
|
|
nextThread.insertAdjacentElement('beforebegin', li)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
pl.append(li);
|
2024-07-05 01:27:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
populateThreadView() {
|
|
|
|
}
|
|
|
|
|
|
|
|
populateProfileView() {
|
|
|
|
}
|
|
|
|
|
|
|
|
buildThreads() {
|
2024-07-16 19:43:35 +00:00
|
|
|
const datastore = AdapterState._instance.data.get(this._name);
|
|
|
|
if (!datastore) {
|
|
|
|
util.errMsg(this._name + " has no datastore!");
|
|
|
|
return;
|
|
|
|
}
|
2024-07-05 01:27:29 +00:00
|
|
|
// make multiple passes over the store until every message is either
|
|
|
|
// placed in a thread, or orphaned and waiting for its parent to be returned
|
|
|
|
do{
|
|
|
|
for (let k of datastore.messages.keys()) {
|
|
|
|
this.placeMsg(k);
|
|
|
|
}
|
|
|
|
} while (this._threads.reduce((sum: number, thread: MessageThread)=>{
|
|
|
|
return sum + thread.messageCount;
|
2024-07-16 19:43:35 +00:00
|
|
|
}, 0) + this._orphans.length < datastore.messages.size);
|
2024-07-05 01:27:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
placeMsg(k: string): string | null {
|
2024-07-16 19:43:35 +00:00
|
|
|
const datastore = AdapterState._instance.data.get(this._name);
|
|
|
|
if (!datastore) {
|
|
|
|
util.errMsg(this._name + " has no datastore!");
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
const msg = datastore.messages.get(k);
|
|
|
|
if (!msg) {
|
|
|
|
util.errMsg(`message [${this._name}:${k}] doesn't exist`);
|
|
|
|
return null;
|
|
|
|
}
|
2024-07-05 05:23:56 +00:00
|
|
|
for (let t of this._threads) {
|
|
|
|
// avoid processing nodes again on subsequent passes
|
2024-07-16 19:43:35 +00:00
|
|
|
if (!msg || t.findNode(t.root, msg.id)) {
|
2024-07-05 05:23:56 +00:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
if (msg.replyTo) {
|
|
|
|
let x = t.addReply(msg.replyTo, msg);
|
2024-07-05 01:27:29 +00:00
|
|
|
if (x) {
|
|
|
|
// after adding, we try to adopt some orphans
|
|
|
|
const orphanChildren = this._orphans.filter(m=>m.replyTo == k);
|
|
|
|
for (let o of orphanChildren) {
|
|
|
|
let adopted = this.placeMsg(o.id);
|
|
|
|
if (adopted) {
|
|
|
|
this._orphans.splice(this._orphans.indexOf(o), 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return t.root.data.id;
|
2024-07-05 05:23:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// if we made it this far, this message doesn't go in any existing thread
|
|
|
|
|
|
|
|
// if it doesn't have a parent, we can make a new thread with it
|
|
|
|
if (!msg.replyTo) {
|
2024-07-05 01:27:29 +00:00
|
|
|
this._threads.push(new MessageThread(msg));
|
2024-07-16 19:43:35 +00:00
|
|
|
// after adding, we try to adopt some orphans
|
|
|
|
const orphanChildren = this._orphans.filter(m=>m.replyTo == k);
|
|
|
|
for (let o of orphanChildren) {
|
|
|
|
let adopted = this.placeMsg(o.id);
|
|
|
|
if (adopted) {
|
|
|
|
this._orphans.splice(this._orphans.indexOf(o), 1);
|
|
|
|
}
|
|
|
|
}
|
2024-07-05 05:23:56 +00:00
|
|
|
return msg.id;
|
|
|
|
}
|
|
|
|
|
2024-07-06 18:43:25 +00:00
|
|
|
// then, we should check if its parent is an orphan
|
|
|
|
const orphanedParent = this._orphans.find(o=>o.id == msg.replyTo);
|
|
|
|
if (orphanedParent) {
|
|
|
|
// then, try to place them both
|
|
|
|
|
|
|
|
if (this.placeMsg(orphanedParent.id)) {
|
|
|
|
this._orphans.splice(this._orphans.indexOf(orphanedParent), 1);
|
2024-07-16 19:43:35 +00:00
|
|
|
return this.placeMsg(k);
|
2024-07-06 18:43:25 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-05 05:23:56 +00:00
|
|
|
// otherwise we can orphan it and try to fill it in later
|
|
|
|
if (this._orphans.filter(o=>o.id == msg.id).length == 0) {
|
|
|
|
this._orphans.push(msg);
|
2024-07-07 03:13:18 +00:00
|
|
|
if (msg.replyTo) {
|
2024-08-03 16:52:33 +00:00
|
|
|
this._convoyBatchTimer.queue(k, 2000);
|
2024-07-07 03:13:18 +00:00
|
|
|
}
|
|
|
|
|
2024-07-05 01:27:29 +00:00
|
|
|
}
|
2024-07-05 05:23:56 +00:00
|
|
|
return null;
|
2024-07-05 01:27:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|