use static classes for Settings and AdapterState
This commit is contained in:
parent
fead16168a
commit
2976d438d9
17 changed files with 262 additions and 140 deletions
10
README.md
10
README.md
|
@ -10,11 +10,17 @@ each distinct `adapter` connection/configuration is represented in the frontend
|
||||||
|
|
||||||
adapters receive commands via a quartzgun web API and send data back on their shared websocket connection
|
adapters receive commands via a quartzgun web API and send data back on their shared websocket connection
|
||||||
|
|
||||||
## building
|
## building and running
|
||||||
|
|
||||||
requirements are
|
requirements are
|
||||||
|
|
||||||
- go 1.22
|
- go 1.22
|
||||||
- any recent nodejs that can do `typescript` and `webpack` 5
|
- any recent nodejs that can do `typescript` and `webpack` 5
|
||||||
|
|
||||||
run `./build.sh` from the project root. you can supply 'front' or 'server' as an argument to build only one or the other; by default it builds both
|
from the project root:
|
||||||
|
|
||||||
|
1. `./build.sh front`
|
||||||
|
2. `./build.sh server`
|
||||||
|
3. `./underbbs`
|
||||||
|
|
||||||
|
visit `http://localhost:9090/app`
|
|
@ -110,10 +110,10 @@ func (self *MastoAdapter) mastoUpdateToMessage(status madon.Status) *Message {
|
||||||
Id: fmt.Sprintf("%d", status.ID),
|
Id: fmt.Sprintf("%d", status.ID),
|
||||||
Uri: status.URI,
|
Uri: status.URI,
|
||||||
Type: "message",
|
Type: "message",
|
||||||
|
Created: status.CreatedAt.UnixMilli(),
|
||||||
},
|
},
|
||||||
Content: status.Content,
|
Content: status.Content,
|
||||||
Author: status.Account.Acct,
|
Author: status.Account.Acct,
|
||||||
Created: status.CreatedAt,
|
|
||||||
Visibility: status.Visibility,
|
Visibility: status.Visibility,
|
||||||
}
|
}
|
||||||
if status.InReplyToID != nil {
|
if status.InReplyToID != nil {
|
||||||
|
|
|
@ -205,10 +205,9 @@ func (self *MisskeyAdapter) toMessage(n mkm.Note, bustCache bool) *Message {
|
||||||
Protocol: "misskey",
|
Protocol: "misskey",
|
||||||
Adapter: self.nickname,
|
Adapter: self.nickname,
|
||||||
Type: "message",
|
Type: "message",
|
||||||
|
Created: n.CreatedAt.UnixMilli(),
|
||||||
},
|
},
|
||||||
|
|
||||||
Created: n.CreatedAt,
|
|
||||||
|
|
||||||
Author: authorId,
|
Author: authorId,
|
||||||
Content: n.Text,
|
Content: n.Text,
|
||||||
Attachments: []Attachment{},
|
Attachments: []Attachment{},
|
||||||
|
@ -224,7 +223,7 @@ func (self *MisskeyAdapter) toMessage(n mkm.Note, bustCache bool) *Message {
|
||||||
ThumbSrc: f.ThumbnailURL,
|
ThumbSrc: f.ThumbnailURL,
|
||||||
Size: f.Size,
|
Size: f.Size,
|
||||||
Desc: f.Comment,
|
Desc: f.Comment,
|
||||||
CreatedAt: f.CreatedAt,
|
Created: f.CreatedAt.UnixMilli(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return &msg
|
return &msg
|
||||||
|
@ -242,6 +241,12 @@ func (self *MisskeyAdapter) toAuthor(usr mkm.User) *Author {
|
||||||
authorId = fmt.Sprintf("@%s", usr.Username)
|
authorId = fmt.Sprintf("@%s", usr.Username)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var updated *int64 = nil
|
||||||
|
if usr.UpdatedAt != nil {
|
||||||
|
updatedTmp := usr.UpdatedAt.UnixMilli()
|
||||||
|
updated = &updatedTmp
|
||||||
|
}
|
||||||
|
|
||||||
author := Author{
|
author := Author{
|
||||||
Datagram: Datagram{
|
Datagram: Datagram{
|
||||||
Id: authorId,
|
Id: authorId,
|
||||||
|
@ -249,6 +254,8 @@ func (self *MisskeyAdapter) toAuthor(usr mkm.User) *Author {
|
||||||
Protocol: "misskey",
|
Protocol: "misskey",
|
||||||
Adapter: self.nickname,
|
Adapter: self.nickname,
|
||||||
Type: "author",
|
Type: "author",
|
||||||
|
Created: usr.CreatedAt.UnixMilli(),
|
||||||
|
Updated: updated,
|
||||||
},
|
},
|
||||||
Name: usr.Name,
|
Name: usr.Name,
|
||||||
ProfilePic: usr.AvatarURL,
|
ProfilePic: usr.AvatarURL,
|
||||||
|
@ -303,7 +310,6 @@ func (self *MisskeyAdapter) Fetch(etype, id string) error {
|
||||||
case "author":
|
case "author":
|
||||||
user := ""
|
user := ""
|
||||||
host := ""
|
host := ""
|
||||||
fmt.Printf("fetch author: %s\n", id)
|
|
||||||
idParts := strings.Split(id, "@")
|
idParts := strings.Split(id, "@")
|
||||||
user = idParts[1]
|
user = idParts[1]
|
||||||
if len(idParts) == 3 {
|
if len(idParts) == 3 {
|
||||||
|
@ -315,12 +321,6 @@ func (self *MisskeyAdapter) Fetch(etype, id string) error {
|
||||||
hostPtr = &host
|
hostPtr = &host
|
||||||
}
|
}
|
||||||
|
|
||||||
if hostPtr == nil {
|
|
||||||
fmt.Printf("looking up user: @%s\n", user)
|
|
||||||
} else {
|
|
||||||
fmt.Printf("looking up remote user: @%s@%s\n", user, host)
|
|
||||||
}
|
|
||||||
|
|
||||||
// fmt.Printf("attempting user resolution: @%s@%s\n", user, host)
|
// fmt.Printf("attempting user resolution: @%s@%s\n", user, host)
|
||||||
data, err := self.mk.Users().Show(users.ShowRequest{
|
data, err := self.mk.Users().Show(users.ShowRequest{
|
||||||
Username: &user,
|
Username: &user,
|
||||||
|
|
|
@ -106,7 +106,7 @@ func (self *NostrAdapter) nostrEventToMsg(evt *nostr.Event) (Message, error) {
|
||||||
case nostr.KindTextNote:
|
case nostr.KindTextNote:
|
||||||
m.Id = evt.ID
|
m.Id = evt.ID
|
||||||
m.Author = evt.PubKey
|
m.Author = evt.PubKey
|
||||||
m.Created = evt.CreatedAt.Time()
|
m.Created = evt.CreatedAt.Time().UnixMilli()
|
||||||
m.Content = evt.Content
|
m.Content = evt.Content
|
||||||
return m, nil
|
return m, nil
|
||||||
default:
|
default:
|
||||||
|
|
3
build.sh
3
build.sh
|
@ -23,7 +23,6 @@ case "$1" in
|
||||||
go build
|
go build
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
$0 client
|
echo "usage: ${0} <front|server>"
|
||||||
$0 server
|
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import util from "./util"
|
import util from "./util"
|
||||||
|
|
||||||
import { Message, Author } from "./message"
|
import { Message, Author } from "./message"
|
||||||
import { MessageThread } from "./thread"
|
import { MessageThread } from "./thread"
|
||||||
|
import { AdapterState } from "./adapter"
|
||||||
var _ = util._
|
|
||||||
var $ = util.$
|
|
||||||
|
|
||||||
export class AdapterElement extends HTMLElement {
|
export class AdapterElement extends HTMLElement {
|
||||||
static observedAttributes = [ "data-latest", "data-view", "data-viewing" ]
|
static observedAttributes = [ "data-latest", "data-view", "data-viewing" ]
|
||||||
|
@ -30,14 +29,17 @@ export class AdapterElement extends HTMLElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
attributeChangedCallback() {
|
attributeChangedCallback() {
|
||||||
|
console.log(`${this._name}.attributeChangedCallback: start`);
|
||||||
// set the viewing subject if it's changed
|
// set the viewing subject if it's changed
|
||||||
const viewing = this.getAttribute("data-viewing");
|
const viewing = this.getAttribute("data-viewing");
|
||||||
if (this._viewing != viewing && viewing != null) {
|
if (this._viewing != viewing && viewing != null) {
|
||||||
|
console.log(`${this._name}.attributeChangedCallback: resetting viewing subject`);
|
||||||
this._viewing = viewing;
|
this._viewing = viewing;
|
||||||
// if the viewing subject changed (not to nothing), unset the view
|
// if the viewing subject changed (not to nothing), unset the view
|
||||||
// this will force it to refresh
|
// this will force it to refresh
|
||||||
if (this._viewing) {
|
if (this._viewing) {
|
||||||
|
|
||||||
|
console.log(`${this._name}.attributeChangedCallback: forcing view update`);
|
||||||
this._view = "";
|
this._view = "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,6 +48,8 @@ export class AdapterElement extends HTMLElement {
|
||||||
const view = this.getAttribute("data-view");
|
const view = this.getAttribute("data-view");
|
||||||
if (this._view != view ?? "index") {
|
if (this._view != view ?? "index") {
|
||||||
this._view = view ?? "index";
|
this._view = view ?? "index";
|
||||||
|
|
||||||
|
console.log(`${this._name}.attributeChangedCallback: setting view: ${this._view}`);
|
||||||
switch (this._view) {
|
switch (this._view) {
|
||||||
case "index":
|
case "index":
|
||||||
this.setIdxView();
|
this.setIdxView();
|
||||||
|
@ -63,18 +67,25 @@ export class AdapterElement extends HTMLElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
// if latest changed, check if it's a message
|
// if latest changed, check if it's a message
|
||||||
const latest = this.getAttribute("latest");
|
const latest = this.getAttribute("data-latest");
|
||||||
|
console.log(`${this._name}.attributeChangedCallback: checking latest(${latest}) vs _latest${this._latest}`);
|
||||||
if (latest ?? "" != this._latest) {
|
if (latest ?? "" != this._latest) {
|
||||||
|
console.log("latest changed")
|
||||||
this._latest = latest ?? "";
|
this._latest = latest ?? "";
|
||||||
let datastore = _("datastore");
|
let datastore = AdapterState._instance.data.get(this._name);
|
||||||
console.log(datastore);
|
if (!datastore) {
|
||||||
|
util.errMsg(this._name + " has no datastore!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
const latestMsg = datastore.messages.get(this._latest);
|
const latestMsg = datastore.messages.get(this._latest);
|
||||||
if (latestMsg) {
|
if (latestMsg) {
|
||||||
|
console.log('latest was a message; place it');
|
||||||
const rootId = this.placeMsg(this._latest);
|
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 is null, this is an orphan and we don't need to actually do any updates yet
|
||||||
if (rootId) {
|
if (rootId) {
|
||||||
switch (this._view) {
|
switch (this._view) {
|
||||||
case "index":
|
case "index":
|
||||||
|
console.log(`message was placed in thread ${rootId}, update view`)
|
||||||
this.updateIdxView(this._latest, rootId);
|
this.updateIdxView(this._latest, rootId);
|
||||||
break;
|
break;
|
||||||
case "thread":
|
case "thread":
|
||||||
|
@ -85,14 +96,25 @@ export class AdapterElement extends HTMLElement {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const latestAuthor = _("datastore")[this._name].profileCache.get(this._latest);
|
const latestAuthor = datastore.profileCache.get(this._latest);
|
||||||
|
if (latestAuthor) {
|
||||||
switch (this._view) {
|
switch (this._view) {
|
||||||
case "index":
|
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 "thread":
|
||||||
case "profile":
|
case "profile":
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// so, try to insert it into the threads
|
// so, try to insert it into the threads
|
||||||
// then, switch on view
|
// then, switch on view
|
||||||
// if index, iterate through the topics and find the one to indicate new activity,
|
// if index, iterate through the topics and find the one to indicate new activity,
|
||||||
|
@ -113,7 +135,7 @@ export class AdapterElement extends HTMLElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
setProfileView() {
|
setProfileView() {
|
||||||
let profile_bar = $("profile_bar");
|
let profile_bar = util.$("profile_bar");
|
||||||
if (profile_bar) {
|
if (profile_bar) {
|
||||||
// clear any previous data
|
// clear any previous data
|
||||||
} else {
|
} else {
|
||||||
|
@ -124,28 +146,44 @@ export class AdapterElement extends HTMLElement {
|
||||||
populateIdxView() {
|
populateIdxView() {
|
||||||
// skip dm list for now
|
// skip dm list for now
|
||||||
// public/unified list
|
// public/unified list
|
||||||
const pl = $("public_list");
|
const pl = util.$("public_list");
|
||||||
if (pl) {
|
if (pl) {
|
||||||
let html = "";
|
let html = "";
|
||||||
for (const t of this._threads) {
|
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}"></underbbs-thread-summary></li>`;
|
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>`;
|
||||||
}
|
}
|
||||||
pl.innerHTML = html;
|
pl.innerHTML = html;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateIdxView(latest: string, rootId: string) {
|
updateIdxView(latest: string, rootId: string) {
|
||||||
const existingThread = this.querySelector("underbbs-thread-summary[msg='${this._latest}']");
|
const existingThread = this.querySelector(`underbbs-thread-summary[data-msg="${rootId}"]`);
|
||||||
const thread = this._threads.find(t=>t.root.data.id == rootId);
|
const thread = this._threads.find(t=>t.root.data.id == rootId);
|
||||||
if (existingThread && thread) {
|
if (existingThread && thread) {
|
||||||
existingThread.setAttribute("data-latest", `${thread.latest[Symbol.toPrimitive]("number")}`);
|
console.log(`updating thread: ${thread.root.data.id} // ${thread.messageCount} NEW`)
|
||||||
|
existingThread.setAttribute("data-latest", `${thread.latest}`);
|
||||||
existingThread.setAttribute("data-len", `${thread.messageCount}`);
|
existingThread.setAttribute("data-len", `${thread.messageCount}`);
|
||||||
existingThread.setAttribute("data-new", "true");
|
existingThread.setAttribute("data-new", "true");
|
||||||
} else {
|
} else {
|
||||||
// unified/public list for now
|
// unified/public list for now
|
||||||
const pl = $("public_list");
|
const pl = util.$("public_list");
|
||||||
if (pl && thread) {
|
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>`);
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -157,7 +195,11 @@ export class AdapterElement extends HTMLElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
buildThreads() {
|
buildThreads() {
|
||||||
const datastore = _("datastore")[this._name];
|
const datastore = AdapterState._instance.data.get(this._name);
|
||||||
|
if (!datastore) {
|
||||||
|
util.errMsg(this._name + " has no datastore!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
// make multiple passes over the store until every message is either
|
// 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
|
// placed in a thread, or orphaned and waiting for its parent to be returned
|
||||||
do{
|
do{
|
||||||
|
@ -166,14 +208,23 @@ export class AdapterElement extends HTMLElement {
|
||||||
}
|
}
|
||||||
} while (this._threads.reduce((sum: number, thread: MessageThread)=>{
|
} while (this._threads.reduce((sum: number, thread: MessageThread)=>{
|
||||||
return sum + thread.messageCount;
|
return sum + thread.messageCount;
|
||||||
}, 0) + this._orphans.length < datastore.messages.keys().length);
|
}, 0) + this._orphans.length < datastore.messages.size);
|
||||||
}
|
}
|
||||||
|
|
||||||
placeMsg(k: string): string | null {
|
placeMsg(k: string): string | null {
|
||||||
const msg = _("datastore")[this._name].messages.get(k);
|
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;
|
||||||
|
}
|
||||||
for (let t of this._threads) {
|
for (let t of this._threads) {
|
||||||
// avoid processing nodes again on subsequent passes
|
// avoid processing nodes again on subsequent passes
|
||||||
if (t.findNode(t.root, msg.id)) {
|
if (!msg || t.findNode(t.root, msg.id)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (msg.replyTo) {
|
if (msg.replyTo) {
|
||||||
|
@ -196,6 +247,14 @@ export class AdapterElement extends HTMLElement {
|
||||||
// if it doesn't have a parent, we can make a new thread with it
|
// if it doesn't have a parent, we can make a new thread with it
|
||||||
if (!msg.replyTo) {
|
if (!msg.replyTo) {
|
||||||
this._threads.push(new MessageThread(msg));
|
this._threads.push(new MessageThread(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 msg.id;
|
return msg.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -206,7 +265,7 @@ export class AdapterElement extends HTMLElement {
|
||||||
|
|
||||||
if (this.placeMsg(orphanedParent.id)) {
|
if (this.placeMsg(orphanedParent.id)) {
|
||||||
this._orphans.splice(this._orphans.indexOf(orphanedParent), 1);
|
this._orphans.splice(this._orphans.indexOf(orphanedParent), 1);
|
||||||
return this.placeMsg(msg);
|
return this.placeMsg(k);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,8 @@ export class AdapterData {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AdapterState {
|
export class AdapterState {
|
||||||
[nickname: string]: AdapterData;
|
public data: Map<string, AdapterData> = new Map<string, AdapterData>();
|
||||||
|
|
||||||
|
static _instance: AdapterState = new AdapterState();
|
||||||
}
|
}
|
|
@ -1,17 +1,15 @@
|
||||||
|
import util from "./util"
|
||||||
import {AdapterState, AdapterData} from "./adapter";
|
import {AdapterState, AdapterData} from "./adapter";
|
||||||
import {Message, Attachment, Author} from "./message"
|
import {Message, Attachment, Author} from "./message"
|
||||||
import util from "./util"
|
import {Settings} from "./settings"
|
||||||
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 { AdapterElement } from "./adapter-element"
|
||||||
import { ThreadSummaryElement } from "./thread-summary-element"
|
import { ThreadSummaryElement } from "./thread-summary-element"
|
||||||
|
|
||||||
var $ = util.$
|
|
||||||
var _ = util._
|
|
||||||
|
|
||||||
function main() {
|
function main() {
|
||||||
const settings = _("settings", JSON.parse(localStorage.getItem("settings") ?? "{}"));
|
Settings._instance = <Settings>JSON.parse(localStorage.getItem("settings") ?? "{}");
|
||||||
|
|
||||||
customElements.define("underbbs-tabbar", TabBarElement);
|
customElements.define("underbbs-tabbar", TabBarElement);
|
||||||
customElements.define("underbbs-message", MessageElement);
|
customElements.define("underbbs-message", MessageElement);
|
||||||
|
@ -19,15 +17,17 @@ function main() {
|
||||||
customElements.define("underbbs-adapter", AdapterElement);
|
customElements.define("underbbs-adapter", AdapterElement);
|
||||||
customElements.define("underbbs-thread-summary", ThreadSummaryElement);
|
customElements.define("underbbs-thread-summary", ThreadSummaryElement);
|
||||||
|
|
||||||
tabbarInit(settings.adapters?.map((a:any)=>a.nickname) ?? []);
|
util._("closeErr", util.closeErr);
|
||||||
|
|
||||||
|
tabbarInit(Settings._instance.adapters?.map(a=>a.nickname) ?? []);
|
||||||
|
|
||||||
registerServiceWorker();
|
registerServiceWorker();
|
||||||
}
|
}
|
||||||
|
|
||||||
function tabbarInit(adapters: string[]) {
|
function tabbarInit(adapters: string[]) {
|
||||||
const nav = $("tabbar_injectparent");
|
const nav = util.$("tabbar_injectparent");
|
||||||
if (nav) {
|
if (nav) {
|
||||||
nav.innerHTML = `<underbbs-tabbar data-adapters="${adapters.join(",")}" data-currentadapter=""></underbbs-tabbar>`;
|
nav.innerHTML = `<underbbs-tabbar data-adapters="" data-currentadapter=""></underbbs-tabbar>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,8 +9,8 @@ export class Message {
|
||||||
public replyTo: string | null = null;
|
public replyTo: string | null = null;
|
||||||
public replies: string[] = [];
|
public replies: string[] = [];
|
||||||
public mentions: string[] = [];
|
public mentions: string[] = [];
|
||||||
public created: Date = new Date();
|
public created: number = 0;
|
||||||
public edited: Date | null = null;
|
public edited: number | null = null;
|
||||||
public visibility: string = "public";
|
public visibility: string = "public";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import util from "./util"
|
import util from "./util"
|
||||||
import websocket from "./websocket"
|
import {DatagramSocket} from "./websocket"
|
||||||
|
import {Settings} from "./settings"
|
||||||
|
|
||||||
var $ = util.$
|
|
||||||
var _ = util._
|
|
||||||
|
|
||||||
export class SettingsElement extends HTMLElement {
|
export class SettingsElement extends HTMLElement {
|
||||||
static observedAttributes = [ "data-adapters" ]
|
static observedAttributes = [ "data-adapters" ]
|
||||||
|
@ -18,7 +17,12 @@ export class SettingsElement extends HTMLElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
attributeChangedCallback() {
|
attributeChangedCallback() {
|
||||||
|
const adapters = this.getAttribute("data-adapters");
|
||||||
|
if (adapters) {
|
||||||
this._adapters = this.getAttribute("data-adapters")?.split(",") ?? [];
|
this._adapters = this.getAttribute("data-adapters")?.split(",") ?? [];
|
||||||
|
} else {
|
||||||
|
this._adapters = [];
|
||||||
|
}
|
||||||
this.showSettings(this)();
|
this.showSettings(this)();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,19 +38,19 @@ export class SettingsElement extends HTMLElement {
|
||||||
html += "<button id='settings_connect_btn'>connect</button>";
|
html += "<button id='settings_connect_btn'>connect</button>";
|
||||||
self.innerHTML = html;
|
self.innerHTML = html;
|
||||||
|
|
||||||
let create = $("settings_adapter_create_btn");
|
let create = util.$("settings_adapter_create_btn");
|
||||||
if (create) {
|
if (create) {
|
||||||
create.addEventListener("click", self.showCreateAdapter(self), false);
|
create.addEventListener("click", self.showCreateAdapter(self), false);
|
||||||
}
|
}
|
||||||
for (let a of this._adapters) {
|
for (let a of this._adapters) {
|
||||||
let edit = $(`settings_adapter_edit_${a}`);
|
let edit = util.$(`settings_adapter_edit_${a}`);
|
||||||
if (edit) {
|
if (edit) {
|
||||||
edit.addEventListener("click", self.showEditAdapterFunc(a, self), false);
|
edit.addEventListener("click", self.showEditAdapterFunc(a, self), false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let connect = $("settings_connect_btn");
|
let connect = util.$("settings_connect_btn");
|
||||||
if (connect) {
|
if (connect) {
|
||||||
connect.addEventListener("click", websocket.connect, false);
|
connect.addEventListener("click", DatagramSocket.connect, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -73,17 +77,17 @@ export class SettingsElement extends HTMLElement {
|
||||||
|
|
||||||
self.innerHTML = html;
|
self.innerHTML = html;
|
||||||
|
|
||||||
let protocolSelect = $("settings_newadapter_protocolselect");
|
let protocolSelect = util.$("settings_newadapter_protocolselect");
|
||||||
if (protocolSelect) {
|
if (protocolSelect) {
|
||||||
protocolSelect.addEventListener("change", self.fillAdapterProtocolOptions, false);
|
protocolSelect.addEventListener("change", self.fillAdapterProtocolOptions, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
let save = $("settings_adapter_create_save_btn");
|
let save = util.$("settings_adapter_create_save_btn");
|
||||||
if (save) {
|
if (save) {
|
||||||
save.addEventListener("click", self.saveAdapter(self), false);
|
save.addEventListener("click", self.saveAdapter(self), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
let back = $("settings_adapter_create_back_btn");
|
let back = util.$("settings_adapter_create_back_btn");
|
||||||
if (back) {
|
if (back) {
|
||||||
back.addEventListener("click", self.showSettings(self), false);
|
back.addEventListener("click", self.showSettings(self), false);
|
||||||
}
|
}
|
||||||
|
@ -94,25 +98,25 @@ export class SettingsElement extends HTMLElement {
|
||||||
return ()=>{
|
return ()=>{
|
||||||
let adapterdata: any = {};
|
let adapterdata: any = {};
|
||||||
// get selected adapter protocol
|
// get selected adapter protocol
|
||||||
const proto = $("settings_newadapter_protocolselect") as HTMLSelectElement;
|
const proto = util.$("settings_newadapter_protocolselect") as HTMLSelectElement;
|
||||||
|
|
||||||
const nickname = ($("settings_newadapter_nickname") as HTMLInputElement)?.value ?? "" ;
|
const nickname = (util.$("settings_newadapter_nickname") as HTMLInputElement)?.value ?? "" ;
|
||||||
|
|
||||||
// switch protocol
|
// switch protocol
|
||||||
switch (proto.options[proto.selectedIndex].value) {
|
switch (proto.options[proto.selectedIndex].value) {
|
||||||
case "nostr":
|
case "nostr":
|
||||||
const privkey = ($("settings_newadapter_nostr_privkey") as HTMLInputElement)?.value ?? "";
|
const privkey = (util.$("settings_newadapter_nostr_privkey") as HTMLInputElement)?.value ?? "";
|
||||||
const relays = ($("settings_newadapter_nostr_default_relays") as HTMLInputElement)?.value ?? "";
|
const relays = (util.$("settings_newadapter_nostr_default_relays") as HTMLInputElement)?.value ?? "";
|
||||||
adapterdata = { nickname: nickname, protocol: "nostr", privkey: privkey, relays: relays.split(",").map(r=>r.trim()) };
|
adapterdata = { nickname: nickname, protocol: "nostr", privkey: privkey, relays: relays.split(",").map(r=>r.trim()) };
|
||||||
break;
|
break;
|
||||||
case "mastodon":
|
case "mastodon":
|
||||||
case "misskey":
|
case "misskey":
|
||||||
const server = ($("settings_newadapter_masto_server") as HTMLInputElement)?.value ?? "";
|
const server = (util.$("settings_newadapter_masto_server") as HTMLInputElement)?.value ?? "";
|
||||||
const apiKey = ($("settings_newadapter_masto_apikey") as HTMLInputElement)?.value ?? "";
|
const apiKey = (util.$("settings_newadapter_masto_apikey") as HTMLInputElement)?.value ?? "";
|
||||||
adapterdata = { nickname: nickname, protocol: proto.options[proto.selectedIndex].value, server: server, apiKey: apiKey };
|
adapterdata = { nickname: nickname, protocol: proto.options[proto.selectedIndex].value, server: server, apiKey: apiKey };
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
const settings = _("settings");
|
const settings = Settings._instance;
|
||||||
if (settings) {
|
if (settings) {
|
||||||
if (!settings.adapters) {
|
if (!settings.adapters) {
|
||||||
settings.adapters = [];
|
settings.adapters = [];
|
||||||
|
@ -121,13 +125,13 @@ export class SettingsElement extends HTMLElement {
|
||||||
self._adapters.push(adapterdata.nickname);
|
self._adapters.push(adapterdata.nickname);
|
||||||
localStorage.setItem("settings", JSON.stringify(settings));
|
localStorage.setItem("settings", JSON.stringify(settings));
|
||||||
|
|
||||||
self.setAttribute("adapters", self._adapters.join(","));
|
self.showSettings(self);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fillAdapterProtocolOptions() {
|
fillAdapterProtocolOptions() {
|
||||||
const proto = $("settings_newadapter_protocolselect") as HTMLSelectElement;
|
const proto = util.$("settings_newadapter_protocolselect") as HTMLSelectElement;
|
||||||
|
|
||||||
let html = "";
|
let html = "";
|
||||||
|
|
||||||
|
@ -145,7 +149,7 @@ export class SettingsElement extends HTMLElement {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const div = $("settings_newadapter_protocoloptions");
|
const div = util.$("settings_newadapter_protocoloptions");
|
||||||
if (div) {
|
if (div) {
|
||||||
div.innerHTML = html;
|
div.innerHTML = html;
|
||||||
}
|
}
|
||||||
|
|
19
frontend/ts/settings.ts
Normal file
19
frontend/ts/settings.ts
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
export class AdapterConfig {
|
||||||
|
// common
|
||||||
|
public nickname: string = "";
|
||||||
|
public protocol: string = "";
|
||||||
|
|
||||||
|
// masto/misskey
|
||||||
|
public server: string | null = null;
|
||||||
|
public apiKey: string | null = null;
|
||||||
|
|
||||||
|
// nostr
|
||||||
|
public privkey: string | null = null;
|
||||||
|
public relays: string[] | null = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Settings {
|
||||||
|
public adapters: AdapterConfig[] = [];
|
||||||
|
|
||||||
|
static _instance: Settings = new Settings();
|
||||||
|
}
|
|
@ -1,6 +1,5 @@
|
||||||
import util from "./util"
|
import util from "./util"
|
||||||
var _ = util._
|
import {Settings} from "./settings"
|
||||||
var $ = util.$
|
|
||||||
|
|
||||||
export class TabBarElement extends HTMLElement {
|
export class TabBarElement extends HTMLElement {
|
||||||
static observedAttributes = [ "data-adapters", "data-currentadapter" ]
|
static observedAttributes = [ "data-adapters", "data-currentadapter" ]
|
||||||
|
@ -24,7 +23,7 @@ export class TabBarElement extends HTMLElement {
|
||||||
|
|
||||||
attributeChangedCallback() {
|
attributeChangedCallback() {
|
||||||
let html = "<ul><li><a id='tabbar_settings' href='#settings'>settings</a></li>";
|
let html = "<ul><li><a id='tabbar_settings' href='#settings'>settings</a></li>";
|
||||||
if (this.getAttribute("data-adapters") == "") {
|
if (!this.getAttribute("data-adapters")) {
|
||||||
this._adapters = [];
|
this._adapters = [];
|
||||||
} else {
|
} else {
|
||||||
this._adapters = this.getAttribute("data-adapters")?.split(",") ?? [];
|
this._adapters = this.getAttribute("data-adapters")?.split(",") ?? [];
|
||||||
|
@ -43,7 +42,7 @@ export class TabBarElement extends HTMLElement {
|
||||||
|
|
||||||
this.innerHTML = html;
|
this.innerHTML = html;
|
||||||
// now we can query the child elements and add click handlers to them
|
// now we can query the child elements and add click handlers to them
|
||||||
var s = $("tabbar_settings");
|
var s = util.$("tabbar_settings");
|
||||||
if (s) {
|
if (s) {
|
||||||
s.addEventListener("click", this.showSettings(this), false);
|
s.addEventListener("click", this.showSettings(this), false);
|
||||||
if (!this._currentAdapter) {
|
if (!this._currentAdapter) {
|
||||||
|
@ -51,7 +50,7 @@ export class TabBarElement extends HTMLElement {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (let i of this._adapters) {
|
for (let i of this._adapters) {
|
||||||
var a = $(`tabbar_${i}`);
|
var a = util.$(`tabbar_${i}`);
|
||||||
if (a) {
|
if (a) {
|
||||||
a.addEventListener("click", this.showAdapterFunc(this, i), false);
|
a.addEventListener("click", this.showAdapterFunc(this, i), false);
|
||||||
if (this._currentAdapter == i) {
|
if (this._currentAdapter == i) {
|
||||||
|
@ -64,9 +63,9 @@ export class TabBarElement extends HTMLElement {
|
||||||
|
|
||||||
showSettings(self: TabBarElement): ()=>void {
|
showSettings(self: TabBarElement): ()=>void {
|
||||||
return () => {
|
return () => {
|
||||||
let x = $("mainarea_injectparent");
|
let x = util.$("mainarea_injectparent");
|
||||||
if (x) {
|
if (x) {
|
||||||
x.innerHTML = `<underbbs-settings data-adapters=${self._adapters?.join(",") ?? []}></underbbs-settings>`;
|
x.innerHTML = `<underbbs-settings data-adapters=${Settings._instance.adapters.map(a=>a.nickname).join(",") ?? []}></underbbs-settings>`;
|
||||||
self.setAttribute("data-currentadapter", "");
|
self.setAttribute("data-currentadapter", "");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -74,7 +73,7 @@ export class TabBarElement extends HTMLElement {
|
||||||
|
|
||||||
showAdapterFunc(self: TabBarElement, adapter: string): ()=>void {
|
showAdapterFunc(self: TabBarElement, adapter: string): ()=>void {
|
||||||
return ()=>{
|
return ()=>{
|
||||||
let x = $("mainarea_injectparent");
|
let x = util.$("mainarea_injectparent");
|
||||||
if (x) {
|
if (x) {
|
||||||
x.innerHTML = `<underbbs-adapter id="adapter_${adapter}" data-name="${adapter}"></underbbs-adapter>`;
|
x.innerHTML = `<underbbs-adapter id="adapter_${adapter}" data-name="${adapter}"></underbbs-adapter>`;
|
||||||
self.setAttribute("data-currentadapter", adapter);
|
self.setAttribute("data-currentadapter", adapter);
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import util from "./util"
|
import util from "./util"
|
||||||
import { Message, Author } from "./message"
|
import { Message, Author } from "./message"
|
||||||
var _ = util._
|
import { AdapterState } from "./adapter"
|
||||||
var $ = util.$
|
|
||||||
|
|
||||||
export class ThreadSummaryElement extends HTMLElement {
|
export class ThreadSummaryElement extends HTMLElement {
|
||||||
static observedAttributes = [ "data-len", "data-author", "data-latest", "data-new" ];
|
static observedAttributes = [ "data-len", "data-author", "data-latest", "data-new" ];
|
||||||
|
@ -10,8 +9,8 @@ export class ThreadSummaryElement extends HTMLElement {
|
||||||
private _msg: Message | null = null;;
|
private _msg: Message | null = null;;
|
||||||
private _author: Author | null = null;
|
private _author: Author | null = null;
|
||||||
private _adapter: string = "";
|
private _adapter: string = "";
|
||||||
private _created: Date = new Date();
|
private _created: number = 0;
|
||||||
private _latest: Date = new Date();
|
private _latest: number = 0;
|
||||||
private _new: boolean = false;
|
private _new: boolean = false;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -30,10 +29,10 @@ export class ThreadSummaryElement extends HTMLElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
attributeChangedCallback() {
|
attributeChangedCallback() {
|
||||||
const datastore = _("datastore")[this._adapter];
|
const datastore = AdapterState._instance.data.get(this._adapter);
|
||||||
const msgId = this.getAttribute("data-msg");
|
const msgId = this.getAttribute("data-msg");
|
||||||
if (msgId && datastore && !this._msg) {
|
if (msgId && datastore && !this._msg) {
|
||||||
this._msg = datastore.messages.get(msgId);
|
this._msg = datastore.messages.get(msgId) || null;
|
||||||
if (this._msg) {
|
if (this._msg) {
|
||||||
const threadText = this.querySelector(".thread_text");
|
const threadText = this.querySelector(".thread_text");
|
||||||
if (threadText) {
|
if (threadText) {
|
||||||
|
@ -59,14 +58,14 @@ export class ThreadSummaryElement extends HTMLElement {
|
||||||
// update author if it's passed in the attribute
|
// update author if it's passed in the attribute
|
||||||
const authorId = this.getAttribute("data-author");
|
const authorId = this.getAttribute("data-author");
|
||||||
if (authorId) {
|
if (authorId) {
|
||||||
let author = datastore?.profileCache?.get(this._msg?.author);
|
let author = datastore?.profileCache?.get(this._msg?.author || "");
|
||||||
if (author) {
|
if (author) {
|
||||||
this._author = author;
|
this._author = author;
|
||||||
const threadAuthor = this.querySelector(".thread_author");
|
const threadAuthor = this.querySelector(".thread_author");
|
||||||
if (threadAuthor && this._author && this._msg) {
|
if (threadAuthor && this._author && this._msg) {
|
||||||
threadAuthor.innerHTML = this._author.profilePic
|
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>`
|
? `<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>` ;
|
: `<a id="thread_${this._adapter}_${this._msg.id}_${this._author.id}" href="#author?id=${author.id}">${this._author.id}</a>` ;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -82,14 +81,14 @@ export class ThreadSummaryElement extends HTMLElement {
|
||||||
metadataChanged = true;
|
metadataChanged = true;
|
||||||
this._len = l;
|
this._len = l;
|
||||||
}
|
}
|
||||||
if (created && new Date(created) != this._created) {
|
if (created && parseInt(created) != this._created) {
|
||||||
metadataChanged = true;
|
metadataChanged = true;
|
||||||
this._created = new Date(created);
|
this._created = parseInt(created);
|
||||||
this._latest = this._created;
|
this._latest = this._created;
|
||||||
}
|
}
|
||||||
if (latest && new Date(latest) != this._latest) {
|
if (latest && parseInt(latest) != this._latest) {
|
||||||
metadataChanged = true;
|
metadataChanged = true;
|
||||||
this._latest = new Date(latest);
|
this._latest = parseInt(latest);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newness != this._new) {
|
if (newness != this._new) {
|
||||||
|
@ -106,7 +105,7 @@ export class ThreadSummaryElement extends HTMLElement {
|
||||||
}
|
}
|
||||||
viewThread(self: ThreadSummaryElement) {
|
viewThread(self: ThreadSummaryElement) {
|
||||||
return () => {
|
return () => {
|
||||||
const a = $(`adapter_${self._adapter}`);
|
const a = util.$(`adapter_${self._adapter}`);
|
||||||
if (a && self._msg) {
|
if (a && self._msg) {
|
||||||
a.setAttribute("data-view", "thread");
|
a.setAttribute("data-view", "thread");
|
||||||
a.setAttribute("data-viewing", self._msg.id);
|
a.setAttribute("data-viewing", self._msg.id);
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
import util from "./util"
|
import util from "./util"
|
||||||
import { Message } from "./message"
|
import { Message } from "./message"
|
||||||
var _ = util._
|
|
||||||
var $ = util.$
|
|
||||||
|
|
||||||
export class MessageNode {
|
export class MessageNode {
|
||||||
public parent: MessageNode | null = null;
|
public parent: MessageNode | null = null;
|
||||||
|
@ -27,8 +25,8 @@ export class MessageThread {
|
||||||
public root: MessageNode;
|
public root: MessageNode;
|
||||||
public messageCount: number;
|
public messageCount: number;
|
||||||
public visibility: string;
|
public visibility: string;
|
||||||
public created: Date;
|
public created: number;
|
||||||
public latest: Date;
|
public latest: number;
|
||||||
|
|
||||||
constructor(first: Message) {
|
constructor(first: Message) {
|
||||||
this.root = new MessageNode(first);
|
this.root = new MessageNode(first);
|
||||||
|
@ -44,7 +42,7 @@ export class MessageThread {
|
||||||
node.children.push(new MessageNode(reply, node));
|
node.children.push(new MessageNode(reply, node));
|
||||||
this.messageCount++;
|
this.messageCount++;
|
||||||
const mtime = reply.edited ? reply.edited : reply.created;
|
const mtime = reply.edited ? reply.edited : reply.created;
|
||||||
if (this.latest.getTime() < mtime.getTime()) {
|
if (this.latest < mtime) {
|
||||||
this.latest = mtime;
|
this.latest = mtime;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
@ -58,7 +56,7 @@ export class MessageThread {
|
||||||
} else {
|
} else {
|
||||||
for (let n of node.children) {
|
for (let n of node.children) {
|
||||||
const x = this.findNode(n, id);
|
const x = this.findNode(n, id);
|
||||||
if (x != null) {
|
if (x) {
|
||||||
return x;
|
return x;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { DatagramSocket } from './websocket'
|
||||||
|
|
||||||
function _(key: string, value: any | null | undefined = undefined): any | null {
|
function _(key: string, value: any | null | undefined = undefined): any | null {
|
||||||
const x = <any>window;
|
const x = <any>window;
|
||||||
|
@ -11,9 +12,25 @@ function $(id: string): HTMLElement | null {
|
||||||
return document.getElementById(id);
|
return document.getElementById(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function errMsg(msg: string): void {
|
||||||
|
const div = $("err_div");
|
||||||
|
const w = $("err_wrapper");
|
||||||
|
if (div && w) {
|
||||||
|
div.innerText = msg;
|
||||||
|
w.style.display = "block";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeErr(): void {
|
||||||
|
const w = $("err_wrapper");
|
||||||
|
if (w) {
|
||||||
|
w.style.display = "none";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function authorizedFetch(method: string, uri: string, body: any): Promise<Response> {
|
async function authorizedFetch(method: string, uri: string, body: any): Promise<Response> {
|
||||||
const headers = new Headers()
|
const headers = new Headers()
|
||||||
headers.set('Authorization', 'Bearer ' + _("skey"))
|
headers.set('Authorization', 'Bearer ' + DatagramSocket.skey)
|
||||||
return await fetch(uri, {
|
return await fetch(uri, {
|
||||||
method: method,
|
method: method,
|
||||||
headers: headers,
|
headers: headers,
|
||||||
|
@ -21,4 +38,4 @@ async function authorizedFetch(method: string, uri: string, body: any): Promise<
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export default { _, $, authorizedFetch }
|
export default { _, $, authorizedFetch, errMsg, closeErr }
|
|
@ -1,54 +1,75 @@
|
||||||
import util from "./util"
|
import util from "./util"
|
||||||
import {AdapterState, AdapterData} from "./adapter";
|
import {AdapterState, AdapterData} from "./adapter";
|
||||||
import {Message, Attachment, Author} from "./message"
|
import {Message, Attachment, Author} from "./message"
|
||||||
|
import {Settings} from "./settings"
|
||||||
|
|
||||||
var $ = util.$
|
|
||||||
var _ = util._
|
|
||||||
|
|
||||||
function connect() {
|
export class DatagramSocket {
|
||||||
|
public static skey: string | null = null;
|
||||||
|
public static conn: WebSocket | null;
|
||||||
|
|
||||||
let datastore = <AdapterState>_("datastore", {});
|
|
||||||
|
|
||||||
const wsProto = location.protocol == "https:" ? "wss" : "ws";
|
private static onOpen(e: Event) {
|
||||||
const _conn = new WebSocket(`${wsProto}://${location.host}/subscribe`, "underbbs");
|
|
||||||
_conn.addEventListener("open", (e: any) => {
|
|
||||||
console.log("websocket connection opened");
|
console.log("websocket connection opened");
|
||||||
console.log(JSON.stringify(e));
|
console.log(JSON.stringify(e));
|
||||||
});
|
}
|
||||||
_conn.addEventListener("message", (e: any) => {
|
|
||||||
|
private static onMsg(e: MessageEvent) {
|
||||||
const data = JSON.parse(e.data);
|
const data = JSON.parse(e.data);
|
||||||
console.log(data);
|
console.log(data);
|
||||||
if (data.key) {
|
if (data.key) {
|
||||||
_("skey", data.key)
|
DatagramSocket.skey = data.key;
|
||||||
util.authorizedFetch("POST", "/api/adapters", JSON.stringify(_("settings").adapters))
|
util.authorizedFetch("POST", "/api/adapters", JSON.stringify(Settings._instance.adapters))
|
||||||
} else {
|
.then(r=> {
|
||||||
if (!datastore[data.adapter]) {
|
if (r.ok) {
|
||||||
datastore[data.adapter] = new AdapterData(data.protocol);
|
const tabbar = document.querySelector("underbbs-tabbar");
|
||||||
|
if (tabbar) {
|
||||||
|
tabbar.setAttribute("data-adapters", Settings._instance.adapters.map(a=>a.nickname).join(","));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
util.errMsg(e.message);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
let store = AdapterState._instance.data.get(data.adapter);
|
||||||
|
if (!store) {
|
||||||
|
AdapterState._instance.data.set(data.adapter, new AdapterData(data.protocol));
|
||||||
|
store = AdapterState._instance.data.get(data.adapter);
|
||||||
|
} else {
|
||||||
// typeswitch on the incoming data type and fill the memory
|
// typeswitch on the incoming data type and fill the memory
|
||||||
switch (data.type) {
|
switch (data.type) {
|
||||||
case "message":
|
case "message":
|
||||||
datastore[data.adapter].messages.set(data.id, <Message>data);
|
store.messages.set(data.id, <Message>data);
|
||||||
break;
|
break;
|
||||||
case "author":
|
case "author":
|
||||||
datastore[data.adapter].profileCache.set(data.id, <Author>data);
|
store.profileCache.set(data.id, <Author>data);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// if the adapter is active signal it that there's new data
|
// if the adapter is active signal it that there's new data
|
||||||
let adapter = $(`adapter_${data.adapter}`);
|
let adapter = util.$(`adapter_${data.adapter}`);
|
||||||
if (adapter) {
|
if (adapter) {
|
||||||
adapter.setAttribute("data-latest", data.id);
|
adapter.setAttribute("data-latest", data.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
static connect(): void {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const wsProto = location.protocol == "https:" ? "wss" : "ws";
|
||||||
|
const _conn = new WebSocket(`${wsProto}://${location.host}/subscribe`, "underbbs");
|
||||||
|
_conn.addEventListener("open", DatagramSocket.onOpen);
|
||||||
|
_conn.addEventListener("message", DatagramSocket.onMsg);
|
||||||
_conn.addEventListener("error", (e: any) => {
|
_conn.addEventListener("error", (e: any) => {
|
||||||
console.log("websocket connection error");
|
console.log("websocket connection error");
|
||||||
console.log(JSON.stringify(e));
|
console.log(JSON.stringify(e));
|
||||||
});
|
});
|
||||||
_("websocket", _conn);
|
DatagramSocket.conn = _conn;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default { connect }
|
|
|
@ -2,7 +2,6 @@ package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Datagram struct {
|
type Datagram struct {
|
||||||
|
@ -12,6 +11,8 @@ type Datagram struct {
|
||||||
Adapter string `json:"adapter"`
|
Adapter string `json:"adapter"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Target *string `json:"target,omitempty"`
|
Target *string `json:"target,omitempty"`
|
||||||
|
Created int64 `json:"created"`
|
||||||
|
Updated *int64 `json:"updated,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Message struct {
|
type Message struct {
|
||||||
|
@ -23,8 +24,6 @@ type Message struct {
|
||||||
Replies []string `json:"replies"`
|
Replies []string `json:"replies"`
|
||||||
ReplyCount int `json:"replyCount"`
|
ReplyCount int `json:"replyCount"`
|
||||||
Mentions []string `json:"mentions"`
|
Mentions []string `json:"mentions"`
|
||||||
Created time.Time `json:"created"`
|
|
||||||
Edited *time.Time `json:"edited,omitempty"`
|
|
||||||
Visibility string `json:"visibility"`
|
Visibility string `json:"visibility"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,7 +39,7 @@ type Attachment struct {
|
||||||
Src string `json:"src"`
|
Src string `json:"src"`
|
||||||
ThumbSrc string `json:"thumbSrc"`
|
ThumbSrc string `json:"thumbSrc"`
|
||||||
Desc string `json:"desc"`
|
Desc string `json:"desc"`
|
||||||
CreatedAt time.Time `json:"createdAt"`
|
Created int64 `json:"created"`
|
||||||
Size uint64 `json:"size"`
|
Size uint64 `json:"size"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue