building out frontend stuff... almost there
This commit is contained in:
parent
c39c9f9412
commit
fd2abcbb76
10 changed files with 470 additions and 70 deletions
24
build.sh
24
build.sh
|
@ -1,8 +1,28 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
client)
|
||||||
if [ ! -e ./src ]; then
|
if [ ! -e ./src ]; then
|
||||||
mkdir ./src
|
mkdir ./src
|
||||||
fi
|
fi
|
||||||
|
buildlog=$(mktemp)
|
||||||
npx tsc &&
|
npx tsc 2>&1 | nobs | sed -e 's/\.ts\(/\.ts:/g' -e 's/,[0-9]+\)://g' > ${buildlog}
|
||||||
|
if [ -s ${buildlog} ]; then
|
||||||
|
cat ${buildlog} | head
|
||||||
|
rm ${buildlog}
|
||||||
|
else
|
||||||
npx webpack --config webpack.config.js
|
npx webpack --config webpack.config.js
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
server)
|
||||||
|
go mod tidy
|
||||||
|
go build
|
||||||
|
;;
|
||||||
|
both)
|
||||||
|
$0 client
|
||||||
|
$0 server
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "USAGE: ${0} <client|server|both>"
|
||||||
|
;;
|
||||||
|
esac
|
|
@ -6,42 +6,42 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Datagram struct {
|
type Datagram struct {
|
||||||
Id string
|
Id string `json:"id"`
|
||||||
Uri string
|
Uri string `json:"uri"`
|
||||||
Protocol string
|
Protocol string `json:"protocol"`
|
||||||
Adapter string
|
Adapter string `json:"adapter"`
|
||||||
Type string
|
Type string `json:"type"`
|
||||||
Target *string
|
Target *string `json:"target,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Message struct {
|
type Message struct {
|
||||||
Datagram
|
Datagram
|
||||||
Author string
|
Author string `json:"author"`
|
||||||
Content string
|
Content string `json:"content"`
|
||||||
Attachments []Attachment
|
Attachments []Attachment `json:"attachments"`
|
||||||
ReplyTo *string
|
ReplyTo *string `json:"replyTo"`
|
||||||
Replies []string
|
Replies []string `json:"replies"`
|
||||||
ReplyCount int
|
ReplyCount int `json:"replyCount"`
|
||||||
Mentions []string
|
Mentions []string `json:"mentions"`
|
||||||
Created time.Time
|
Created time.Time `json:"created"`
|
||||||
Visibility string
|
Edited *time.Time `json:"edited,omitempty"`
|
||||||
Aux map[string]string
|
Visibility string `json:"visibility"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Author struct {
|
type Author struct {
|
||||||
Datagram
|
Datagram
|
||||||
Name string
|
Name string `json:"name"`
|
||||||
ProfileData interface{}
|
ProfileData interface{} `json:"profileData"`
|
||||||
ProfilePic string
|
ProfilePic string `json:"profilePic"`
|
||||||
Messages []Message
|
Messages []string `json:"messages,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Attachment struct {
|
type Attachment struct {
|
||||||
Src string
|
Src string `json:"src"`
|
||||||
ThumbSrc string
|
ThumbSrc string `json:"thumbSrc"`
|
||||||
Desc string
|
Desc string `json:"desc"`
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
Size uint64
|
Size uint64 `json:"size"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SocketData interface {
|
type SocketData interface {
|
||||||
|
|
209
ts/adapter-element.ts
Normal file
209
ts/adapter-element.ts
Normal file
|
@ -0,0 +1,209 @@
|
||||||
|
import util from "./util"
|
||||||
|
import { Message, Author } from "./message"
|
||||||
|
import { MessageThread } from "./thread"
|
||||||
|
|
||||||
|
var _ = util._
|
||||||
|
var $ = util.$
|
||||||
|
|
||||||
|
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 = "";
|
||||||
|
|
||||||
|
// 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 ?? "";
|
||||||
|
this.buildThreads();
|
||||||
|
this.attributeChangedCallback();
|
||||||
|
}
|
||||||
|
|
||||||
|
attributeChangedCallback() {
|
||||||
|
|
||||||
|
// set the viewing subject if it's changed
|
||||||
|
const viewing = this.getAttribute("data-viewing");
|
||||||
|
if (this._viewing != viewing && viewing != null) {
|
||||||
|
this._viewing = viewing;
|
||||||
|
// if the viewing subject changed (not to nothing), unset the view
|
||||||
|
// this will force it to refresh
|
||||||
|
if (this._viewing) {
|
||||||
|
this._view = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// initialize the view if it's changed
|
||||||
|
const view = this.getAttribute("data-view");
|
||||||
|
if (this._view != view) {
|
||||||
|
console.log("view changed! let's go")
|
||||||
|
this._view = view ?? "";
|
||||||
|
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
|
||||||
|
const latest = this.getAttribute("latest");
|
||||||
|
if (latest ?? "" != this._latest) {
|
||||||
|
this._latest = latest ?? "";
|
||||||
|
console.log("about to update the index view");
|
||||||
|
let datastore = _("datastore");
|
||||||
|
console.log(datastore);
|
||||||
|
const latestMsg = datastore.messages.get(this._latest);
|
||||||
|
if (latestMsg) {
|
||||||
|
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":
|
||||||
|
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 {
|
||||||
|
const latestAuthor = _("datastore")[this._name].profileCache.get(this._latest);
|
||||||
|
switch (this._view) {
|
||||||
|
case "index":
|
||||||
|
case "thread":
|
||||||
|
case "profile":
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 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() {
|
||||||
|
let profile_bar = $("profile_bar");
|
||||||
|
if (profile_bar) {
|
||||||
|
// clear any previous data
|
||||||
|
} else {
|
||||||
|
// insert the profileSidebar into the dom
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
populateIdxView() {
|
||||||
|
// skip dm list for now
|
||||||
|
// public/unified list
|
||||||
|
const pl = $("public_list");
|
||||||
|
if (pl) {
|
||||||
|
console.log(JSON.stringify(this._threads));
|
||||||
|
for (const t of this._threads) {
|
||||||
|
console.log(t.root.data.id);
|
||||||
|
pl.append(`<li><underbbs-thread-summary data-len="${t.messageCount}" data-adapter="${t.root.data.adapter}" data-msg="${t.root.data.id}" data-latest="${t.latest}" data-created="${t.created}" data-new=""></underbbs-thread-summary></li>`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateIdxView(latest: string, rootId: string) {
|
||||||
|
const existingThread = this.querySelector("underbbs-thread-summary[msg='${this._latest}']");
|
||||||
|
const thread = this._threads.find(t=>t.root.data.id == rootId);
|
||||||
|
if (existingThread && thread) {
|
||||||
|
existingThread.setAttribute("data-latest", `${thread.latest[Symbol.toPrimitive]("number")}`);
|
||||||
|
existingThread.setAttribute("data-len", `${thread.messageCount}`);
|
||||||
|
existingThread.setAttribute("data-new", "true");
|
||||||
|
} else {
|
||||||
|
// unified/public list for now
|
||||||
|
const pl = $("public_list");
|
||||||
|
if (pl && thread) {
|
||||||
|
pl.prepend(`<li><underbbs-thread-summary data-len="${thread.messageCount}" data-adapter="${thread.root.data.adapter}" data-msg="${thread.root.data.id}" data-latest="${thread.latest}" data-created="${thread.created}" data-new="true"></underbbs-thread-summary></li>`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
populateThreadView() {
|
||||||
|
}
|
||||||
|
|
||||||
|
populateProfileView() {
|
||||||
|
}
|
||||||
|
|
||||||
|
buildThreads() {
|
||||||
|
const datastore = _("datastore")[this._name];
|
||||||
|
// 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;
|
||||||
|
}, 0) + this._orphans.length < datastore.messages.keys().length)
|
||||||
|
}
|
||||||
|
|
||||||
|
placeMsg(k: string): string | null {
|
||||||
|
const msg = _("datastore")[this._name].messages.get(k);
|
||||||
|
if (msg.replyTo) {
|
||||||
|
for (let t of this._threads) {
|
||||||
|
// avoid processing nodes again on subsequent passes
|
||||||
|
if (t.findNode(t.root, msg.id)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let x = t.findNode(t.root, msg.replyTo);
|
||||||
|
if (x) {
|
||||||
|
t.addReply(msg.replyTo, msg);
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this._orphans.filter(o=>o.id == msg.id).length == 0) {
|
||||||
|
this._orphans.push(msg);
|
||||||
|
// TODO: request the parent's data
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
this._threads.push(new MessageThread(msg));
|
||||||
|
return k;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -4,6 +4,8 @@ import util from "./util"
|
||||||
import { TabBarElement } from "./tabbar-element"
|
import { TabBarElement } from "./tabbar-element"
|
||||||
import { MessageElement } from "./message-element"
|
import { MessageElement } from "./message-element"
|
||||||
import { SettingsElement } from "./settings-element"
|
import { SettingsElement } from "./settings-element"
|
||||||
|
import { AdapterElement } from "./adapter-element"
|
||||||
|
import { ThreadSummaryElement } from "./thread-summary-element"
|
||||||
|
|
||||||
var $ = util.$
|
var $ = util.$
|
||||||
var _ = util._
|
var _ = util._
|
||||||
|
@ -14,6 +16,8 @@ function main() {
|
||||||
customElements.define("underbbs-tabbar", TabBarElement);
|
customElements.define("underbbs-tabbar", TabBarElement);
|
||||||
customElements.define("underbbs-message", MessageElement);
|
customElements.define("underbbs-message", MessageElement);
|
||||||
customElements.define("underbbs-settings", SettingsElement);
|
customElements.define("underbbs-settings", SettingsElement);
|
||||||
|
customElements.define("underbbs-adapter", AdapterElement);
|
||||||
|
customElements.define("underbbs-thread-summary", ThreadSummaryElement);
|
||||||
|
|
||||||
tabbarInit(settings.adapters?.map((a:any)=>a.nickname) ?? []);
|
tabbarInit(settings.adapters?.map((a:any)=>a.nickname) ?? []);
|
||||||
|
|
||||||
|
|
|
@ -1,47 +1,35 @@
|
||||||
import {NDKEvent} from "@nostr-dev-kit/ndk"
|
|
||||||
import * as masto from "masto";
|
|
||||||
|
|
||||||
type APStatus = masto.mastodon.v1.Status;
|
|
||||||
|
|
||||||
export class Message {
|
export class Message {
|
||||||
public author: Author = new Author();
|
public id: string = "";
|
||||||
|
public uri: string = "";
|
||||||
public protocol: string = "";
|
public protocol: string = "";
|
||||||
|
public adapter: string = "";
|
||||||
|
public author: string = ""
|
||||||
public content: string = "";
|
public content: string = "";
|
||||||
public attachments: Attachment[] = [];
|
public attachments: Attachment[] = [];
|
||||||
public replyTo: Message | null = null;
|
public replyTo: string | null = null;
|
||||||
public replies: Message[] = [];
|
public replies: string[] = [];
|
||||||
public mentions: Author[] = [];
|
public mentions: string[] = [];
|
||||||
public created: Date = new Date();
|
public created: Date = new Date();
|
||||||
public edited: Date = new Date();
|
public edited: Date | null = null;
|
||||||
public visibility: string = "public";
|
public visibility: string = "public";
|
||||||
|
|
||||||
// this will contain additional data about what kind of message it is
|
|
||||||
public aux: any | null = null;
|
|
||||||
|
|
||||||
public static fromNostr(event: NDKEvent): Message {
|
|
||||||
let self = new Message();
|
|
||||||
// build out the message based on the contents of event
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static fromMasto(status: APStatus): Message {
|
|
||||||
let self = new Message();
|
|
||||||
// build out the message based on the contents of status
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Author {
|
export class Author {
|
||||||
public id: string = "";
|
public id: string = "";
|
||||||
|
public uri: string = "";
|
||||||
|
public protocol: string = "";
|
||||||
|
public adapter: string = "";
|
||||||
public name: string = "";
|
public name: string = "";
|
||||||
public profileData: string = "";
|
public profileData: any = {};
|
||||||
public messages: Message[] = [];
|
public profilePic: string = "";
|
||||||
|
public messages: string[] = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Attachment {
|
export class Attachment {
|
||||||
public file: Uint8Array = new Uint8Array();
|
public Src: string = "";
|
||||||
public altText: string = "";
|
public ThumbSrc: string = "";
|
||||||
public filename: string = "";
|
public Desc: string = "";
|
||||||
|
public CreatedAt: Date = new Date();
|
||||||
}
|
}
|
||||||
|
|
||||||
export default { Message, Attachment, Author }
|
export default { Message, Attachment, Author }
|
|
@ -31,7 +31,7 @@ export class SettingsElement extends HTMLElement {
|
||||||
return self;
|
return self;
|
||||||
}, "<ul id='settings_adapterlist'>");
|
}, "<ul id='settings_adapterlist'>");
|
||||||
html += "</ul>";
|
html += "</ul>";
|
||||||
html += "<button id='settings_save_btn'>save</button>";
|
html += "<button id='settings_connect_btn'>connect</button>";
|
||||||
self.innerHTML = html;
|
self.innerHTML = html;
|
||||||
|
|
||||||
let create = $("settings_adapter_create_btn");
|
let create = $("settings_adapter_create_btn");
|
||||||
|
|
|
@ -76,7 +76,7 @@ export class TabBarElement extends HTMLElement {
|
||||||
return ()=>{
|
return ()=>{
|
||||||
let x = $("mainarea_injectparent");
|
let x = $("mainarea_injectparent");
|
||||||
if (x) {
|
if (x) {
|
||||||
x.innerHTML = `<underbbs-adapter data-name="${adapter}"></underbbs-adapter>`;
|
x.innerHTML = `<underbbs-adapter id="adapter_${adapter}" data-name="${adapter}" data-view="index"></underbbs-adapter>`;
|
||||||
self.setAttribute("data-currentadapter", adapter);
|
self.setAttribute("data-currentadapter", adapter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
111
ts/thread-summary-element.ts
Normal file
111
ts/thread-summary-element.ts
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
import util from "./util"
|
||||||
|
import { Message, Author } from "./message"
|
||||||
|
var _ = util._
|
||||||
|
var $ = util.$
|
||||||
|
|
||||||
|
export class ThreadSummaryElement extends HTMLElement {
|
||||||
|
static observedAttributes = [ "data-len", "data-msg", "data-author", "data-latest", "data-created", "data-new" ];
|
||||||
|
|
||||||
|
private _len: number = 0;;
|
||||||
|
private _msg: Message | null = null;;
|
||||||
|
private _author: Author | null = null;
|
||||||
|
private _adapter: string = "";
|
||||||
|
private _created: Date = new Date();
|
||||||
|
private _latest: Date = new Date();
|
||||||
|
private _new: boolean = false;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
this.innerHTML = "<div class='thread_summary'><div class='thread_author'></div><div class='thread_text'></div><div class='thread_metadata'></div></div>"
|
||||||
|
|
||||||
|
// adapter shouldn't change, just set it here
|
||||||
|
this._adapter = this.getAttribute("data-adapter") ?? "";
|
||||||
|
this.attributeChangedCallback();
|
||||||
|
if (this._msg) {
|
||||||
|
this.addEventListener("click", this.viewThread(this), false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
attributeChangedCallback() {
|
||||||
|
const datastore = _("datastore")[this._adapter];
|
||||||
|
const msgId = this.getAttribute("data-msg");
|
||||||
|
if (msgId && datastore && ((this._msg && this._msg.id != msgId) || !this._msg)) {
|
||||||
|
this._msg = datastore.messages.get(msgId);
|
||||||
|
if (this._msg) {
|
||||||
|
const threadText = this.querySelector(".thread_text");
|
||||||
|
if (threadText) {
|
||||||
|
threadText.innerHTML = this._msg.content;
|
||||||
|
}
|
||||||
|
let author = datastore.profileCache.get(this._msg.author);
|
||||||
|
if (!author) {
|
||||||
|
// request it!
|
||||||
|
}
|
||||||
|
this._author = author || <Author>{ id: this._msg.author };
|
||||||
|
const threadAuthor = this.querySelector(".thread_author");
|
||||||
|
if (threadAuthor && this._author) {
|
||||||
|
threadAuthor.innerHTML = this._author.profilePic
|
||||||
|
? `<img src="${this._author.profilePic}" alt="${this._author.id}"/> <a id="thread_${this._adapter}_${this._msg.id}_${this._author.id}" href="#author?id=${this._author.id}>${this._author.id}</a>`
|
||||||
|
: `<a id="thread_${this._adapter}_${this._msg.id}_${this._author.id}" href="#author?id=${this._author.id}>${this._author.id}</a>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const authorId = this.getAttribute("data-author");
|
||||||
|
if (authorId) {
|
||||||
|
let author = datastore?.profileCache?.get(this._msg?.author);
|
||||||
|
if (author) {
|
||||||
|
this._author = author;
|
||||||
|
const threadAuthor = this.querySelector(".thread_author");
|
||||||
|
if (threadAuthor && this._author) {
|
||||||
|
threadAuthor.innerHTML = this._author.profilePic
|
||||||
|
? `<img src="${this._author.profilePic}" alt="${this._author.id}"/> <a id="thread_${this._adapter}_${this._msg.id}_${this._author.id}" href="#author?id=${this._author.id}>${this._author.id}</a>`
|
||||||
|
: `<a id="thread_${this._adapter}_${this._msg.id}_${this._author.id}" href="#author?id=${author.id}>${this._author.id}</a>` ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} }
|
||||||
|
|
||||||
|
const l = parseInt(this.getAttribute("data-len") ?? "0");
|
||||||
|
const latest = new Date(this.getAttribute("data-latest") ?? 0);
|
||||||
|
const created = new Date(this.getAttribute("data-created")?? 0);
|
||||||
|
const newness = this.getAttribute("data-new") ? true : false;
|
||||||
|
|
||||||
|
let metadataChanged = false;
|
||||||
|
|
||||||
|
if (l != this._len) {
|
||||||
|
metadataChanged = true;
|
||||||
|
this._len = l;
|
||||||
|
}
|
||||||
|
if (latest != this._latest) {
|
||||||
|
metadataChanged = true;
|
||||||
|
this._latest = latest;
|
||||||
|
}
|
||||||
|
if (created != this._created) {
|
||||||
|
metadataChanged = true;
|
||||||
|
this._created = created;
|
||||||
|
}
|
||||||
|
if (newness != this._new) {
|
||||||
|
metadataChanged = true;
|
||||||
|
this._new = newness;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (metadataChanged) {
|
||||||
|
const threadMeta = this.querySelector(".thread_metadata");
|
||||||
|
if (threadMeta) {
|
||||||
|
threadMeta.innerHTML = `<span>${this._new ? "!" : ""}[${this._len}] created: ${this._created}, updated: ${this._latest}</span>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
viewThread(self: ThreadSummaryElement) {
|
||||||
|
return () => {
|
||||||
|
const a = $(`adapter_${self._adapter}`);
|
||||||
|
if (a && self._msg) {
|
||||||
|
a.setAttribute("data-view", "thread");
|
||||||
|
a.setAttribute("data-viewing", self._msg.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
69
ts/thread.ts
Normal file
69
ts/thread.ts
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
import util from "./util"
|
||||||
|
import { Message } from "./message"
|
||||||
|
var _ = util._
|
||||||
|
var $ = util.$
|
||||||
|
|
||||||
|
export class MessageNode {
|
||||||
|
public parent: MessageNode | null = null;
|
||||||
|
public children: MessageNode[] = [];
|
||||||
|
public data: Message;
|
||||||
|
|
||||||
|
constructor(msg: Message, parent: MessageNode | null = null) {
|
||||||
|
this.data = msg;
|
||||||
|
this.parent = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
findRoot(): MessageNode {
|
||||||
|
let self: MessageNode | null = this;
|
||||||
|
|
||||||
|
while(self.parent) {
|
||||||
|
self = self.parent;
|
||||||
|
}
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MessageThread {
|
||||||
|
public root: MessageNode;
|
||||||
|
public messageCount: number;
|
||||||
|
public visibility: string;
|
||||||
|
public created: Date;
|
||||||
|
public latest: Date;
|
||||||
|
|
||||||
|
constructor(first: Message) {
|
||||||
|
this.root = new MessageNode(first);
|
||||||
|
this.messageCount = 1;
|
||||||
|
this.visibility = first.visibility;
|
||||||
|
this.created = first.created;
|
||||||
|
this.latest = first.edited ? first.edited : first.created;
|
||||||
|
}
|
||||||
|
|
||||||
|
addReply(parentID: string, reply: Message) {
|
||||||
|
let node = this.findNode(this.root, parentID);
|
||||||
|
if (node) {
|
||||||
|
node.children.push(new MessageNode(reply, node));
|
||||||
|
this.messageCount++;
|
||||||
|
const mtime = reply.edited ? reply.edited : reply.created;
|
||||||
|
if (this.latest < mtime) {
|
||||||
|
this.latest = mtime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
findNode(node: MessageNode, id: string): MessageNode | null {
|
||||||
|
if (node.data.id == id) {
|
||||||
|
return node;
|
||||||
|
} else {
|
||||||
|
for (let n of node.children) {
|
||||||
|
console.log("descending through children...")
|
||||||
|
const x = this.findNode(n, id);
|
||||||
|
if (x != null) {
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,8 +7,7 @@ var _ = util._
|
||||||
|
|
||||||
function connect() {
|
function connect() {
|
||||||
|
|
||||||
var datastore: AdapterState = {}
|
let datastore = <AdapterState>_("datastore", {});
|
||||||
datastore = _("datastore", datastore);
|
|
||||||
|
|
||||||
const wsProto = location.protocol == "https:" ? "wss" : "ws";
|
const wsProto = location.protocol == "https:" ? "wss" : "ws";
|
||||||
const _conn = new WebSocket(`${wsProto}://${location.host}/subscribe`, "underbbs");
|
const _conn = new WebSocket(`${wsProto}://${location.host}/subscribe`, "underbbs");
|
||||||
|
@ -17,12 +16,8 @@ function connect() {
|
||||||
console.log(JSON.stringify(e));
|
console.log(JSON.stringify(e));
|
||||||
});
|
});
|
||||||
_conn.addEventListener("message", (e: any) => {
|
_conn.addEventListener("message", (e: any) => {
|
||||||
|
|
||||||
// debugging
|
|
||||||
console.log(e)
|
|
||||||
|
|
||||||
// now we'll figure out what to do with it
|
|
||||||
const data = JSON.parse(e.data);
|
const data = JSON.parse(e.data);
|
||||||
|
console.log(data);
|
||||||
if (data.key) {
|
if (data.key) {
|
||||||
_("skey", data.key)
|
_("skey", data.key)
|
||||||
util.authorizedFetch("POST", "/api/adapters", JSON.stringify(_("settings").adapters))
|
util.authorizedFetch("POST", "/api/adapters", JSON.stringify(_("settings").adapters))
|
||||||
|
@ -43,6 +38,10 @@ function connect() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// if the adapter is active, inject the web components
|
// if the adapter is active, inject the web components
|
||||||
|
let adapter = $(`adapter_${data.adapter}`);
|
||||||
|
if (adapter) {
|
||||||
|
adapter.setAttribute("data-latest", data.id);
|
||||||
|
}
|
||||||
// FOR HOTFETCHED DATA:
|
// FOR HOTFETCHED DATA:
|
||||||
// before fetching, we can set properties on the DOM,
|
// before fetching, we can set properties on the DOM,
|
||||||
// so when those data return to us we know where to inject components!
|
// so when those data return to us we know where to inject components!
|
||||||
|
|
Loading…
Reference in a new issue