underbbs/frontend/ts/timeline-element.ts

113 lines
No EOL
4.5 KiB
TypeScript

import { Author, Message } from "./message"
import util from "./util"
import { Subscriber } from "./subscriber"
import { Fetcher } from "./fetcher"
import { AdapterState } from "./adapter"
export class TimelineElement extends HTMLElement {
static observedAttributes = [ "data-latest", "data-adapter", "data-target" ];
private _timeline: string | null = null;
private _adapter: string = "";
private _interactable: boolean = false;
private _replyWith: string | null = null;
private _inspectWith: string | null = null;
private _mode: string = "subscribe";
private _messages: Message[] = [];
private _subscriber: Subscriber | null = null;
private _byAuthorFetcher: Fetcher | null = null;
constructor() {
super();
this.innerHTML = `<ul class="messages_list"></ul>`;
}
connectedCallback() {
this._timeline = this.getAttribute("data-target");
this._adapter = this.getAttribute("data-adapter") ?? "";
const gateway = this.getAttribute("data-gateway") ?? "";
this._subscriber = new Subscriber(gateway, this._adapter ?? "", this.getAttribute("id") ?? null);
this._byAuthorFetcher = new Fetcher(gateway, this._adapter, "byAuthor");
this._interactable = this.getAttribute("data-interactable") != null;
this._replyWith = this.getAttribute("data-replywith");
this._inspectWith = this.getAttribute("data-inspectwith");
this._mode = this.getAttribute("data-mode") ?? "subscribe";
}
attributeChangedCallback(attr: string, prev: string, next: string) {
switch (attr) {
case "data-target":
if (!next) {
return
}
this._timeline = next;
this.innerHTML = `<ul class="messages_list"></ul>`;
this._messages = [];
switch (this._mode) {
case "byAuthor":
if (this._byAuthorFetcher) {
this._byAuthorFetcher.queue(next, 100);
}
break;
case "subscribe":
if (this._subscriber) {
this._subscriber.subscribe(next);
}
break;
}
break;
case "data-latest":
let datastore = AdapterState._instance.data.get(this._adapter);
if (!datastore) {
console.log("no data yet, wait for some to come in maybe...");
return;
}
let msg = datastore.messages.get(next);
if (msg) {
console.log(msg);
const existingIdx = this._messages.findIndex(m=>(m.renoteId ?? m.id) == (msg.renoteId ?? msg.id) && ((m.edited ?? m.created) <= (msg.edited ?? msg.created)));
// first we update the backing data store
if (existingIdx >= 0) {
this._messages[existingIdx] = msg;
} else if (!this._messages.some(m=>(m.renoteId ?? m.id) == (msg.renoteId ?? msg.id))) {
this._messages.push(msg);
}
const ul = this.children[0];
if (ul) {
// first pass through the dom, try to update a message if it's there
for (let i = 0; i < ul.childElementCount; i++){
const id = ul.children[i]?.children[0]?.getAttribute("data-target");
const ogMsg = this._messages.find(m=>(m.renoteId ?? m.id) == id);
if (ogMsg && existingIdx >= 0) {
ul.children[i]?.children[0]?.setAttribute("data-latest", id ?? "");
return;
}
}
// if we made it this far, let's create a node
const e = document.createElement("li");
e.innerHTML = `<underbbs-message data-adapter="${this._adapter}" data-target="${next}" ${this._interactable ? "data-interactable" : ""} ${this._replyWith ? "data-replywith='" + this._replyWith + "'" : ""} ${this._inspectWith ? "data-inspectwith='" + this._inspectWith + "'": ""}></underbbs-message>`
// second pass, try to place it in reverse-chronological order
for (let i = 0; i < ul.childElementCount; i++){
const id = ul.children[i]?.children[0]?.getAttribute("data-target");
const ogMsg = this._messages.find(m=>(m.renoteId ?? m.id) == id);
if (ogMsg && (ogMsg.renoteTime ?? ogMsg.created) <= (msg.renoteTime ?? msg.created)) {
ul.insertBefore(e, ul.children[i])
e.children[0].setAttribute("data-latest", next);
return;
}
}
// final pass, we must be the earliest child (or maybe the first one to be rendered)
ul.append(e);
e.children[0].setAttribute("data-latest", next);
}
}
}
}
}