redoing frontend stuff, we have a basic profile fetch working

This commit is contained in:
Iris Lightshard 2024-12-01 14:08:02 -07:00
parent 305366dc9e
commit 64d00083b7
Signed by: Iris Lightshard
GPG key ID: 688407174966CAF3
10 changed files with 135 additions and 50 deletions

View file

@ -36,13 +36,14 @@ func (self *HonkAdapter) Name() string {
}
func (self *HonkAdapter) Init(settings Settings, data *chan SocketData) error {
self.data = data
// separate name and server in handle
parts := strings.Split(*settings.Handle, "@")
self.username = parts[1]
self.server = "https://" + parts[2]
self.nickname = settings.Nickname
if settings.Password == nil {
if settings.Password == nil || *settings.Password == "" {
// we're anonymous!
return nil
}
@ -71,6 +72,7 @@ func (self *HonkAdapter) Init(settings Settings, data *chan SocketData) error {
func (self *HonkAdapter) Subscribe(string) []error {
// similar to the misskey adapter, we will poll for new honks and send them on the channel as they come in
return nil
}
func (self *HonkAdapter) Fetch(etype string, ids []string) error {

View file

@ -2,7 +2,7 @@
<html lang="en-US">
<head>
<meta charset="UTF-8" />
<title>underBBS</title>
<title>underBBS test</title>
<meta name="viewport" content="width=device-width" />
<link rel="shortcut icon" href="./favicon.png"/>
<link href="./style.css" rel="stylesheet" />
@ -13,9 +13,10 @@
</div>
</noscript>
<div id="err_wrapper" style='display:none'><button id="err_close" onclick="closeErr()">x</button><div id="err_div"></div></div>
<nav id="tabbar_injectparent">
</nav>
<main id="mainarea_injectparent">
<main>
<details open><summary>settings</summary><div id="settings_parent"></div></details>
<details open><summary>profile</summary><div id="profile_parent"></div></details>
<details open><summary>honks</summary><div id="honks_parent"></div></details>
</main>
</body>
<script src="./main.js" type="application/javascript"></script>

View file

@ -3,6 +3,7 @@ export class BoostTileElement extends HTMLElement {
static observedAttributes = [ "data-boostid", "data-msgid", "data-author", "data-booster" ];
constructor() {
super();
this.innerHTML = "<div class='boost_booster'></div><div class='boost_author'></div><div class='boost_content'></div>";
}

View file

@ -7,9 +7,11 @@ import { MessageElement } from "./message-element"
import { SettingsElement } from "./settings-element"
import { AdapterElement } from "./adapter-element"
import { ThreadSummaryElement } from "./thread-summary-element"
import { ProfileElement } from "./profile-element"
import {DatagramSocket} from "./websocket"
function main() {
const saveData = localStorage.getItem("settings");
const saveData = localStorage.getItem("underbbs_settings");
Settings._instance = saveData ? <Settings>JSON.parse(saveData) : new Settings();
customElements.define("underbbs-tabbar", TabBarElement);
@ -17,39 +19,17 @@ function main() {
customElements.define("underbbs-settings", SettingsElement);
customElements.define("underbbs-adapter", AdapterElement);
customElements.define("underbbs-thread-summary", ThreadSummaryElement);
customElements.define("underbbs-profile", ProfileElement);
util._("closeErr", util.closeErr);
tabbarInit(Settings._instance.adapters?.map(a=>a.nickname) ?? []);
registerServiceWorker();
let settingsParent = util.$("settings_parent");
if (settingsParent) {
settingsParent.innerHTML = `<underbbs-settings data-adapters='${Settings._instance.adapters.map(a=>a.nickname).join(",")}'></underbbs-settings>`
}
function tabbarInit(adapters: string[]) {
const nav = util.$("tabbar_injectparent");
if (nav) {
nav.innerHTML = `<underbbs-tabbar data-adapters="" data-currentadapter=""></underbbs-tabbar>`;
}
}
async function registerServiceWorker() {
if ("serviceWorker" in navigator) {
try {
const registration = await navigator.serviceWorker.register("/serviceWorker.js", {
scope: "/",
});
if (registration.installing) {
console.log("Service worker installing");
} else if (registration.waiting) {
console.log("Service worker installed");
} else if (registration.active) {
console.log("Service worker active");
}
} catch (error) {
console.error(`Registration failed with ${error}`);
}
const registration = await navigator.serviceWorker.ready;
(registration as any).sync.register("testdata").then((r:any)=>{console.log("but i will see this!")});
let profileParent = util.$("profile_parent");
if (profileParent) {
profileParent.innerHTML = "<underbbs-profile data-adapter='honk' data-target='https://cafe.nilfm.cc/u/nilix'></underbbs-profile>"
}
}

View file

@ -0,0 +1,82 @@
import { Author } from "./message"
import util from "./util"
import { BatchTimer } from "./batch-timer"
import { AdapterState } from "./adapter"
export class ProfileElement extends HTMLElement {
static observedAttributes = [ "data-latest", "data-adapter", "data-target" ]
private _id: string | null = null;
private _adapter: string = "";
private _author: Author | null = null;
private _authorTimer: BatchTimer | null = null;
constructor() {
super();
this.innerHTML = "<div class='author_pfp'></div><div class='author_id'></div><div class='author_name'></div><div class='author_description'></div>"
}
connectedCallback() {
this._id = this.getAttribute("data-target");
this._adapter = this.getAttribute("data-adapter") ?? "";
const gateway = this.getAttribute("data-gateway") ?? "";
this._authorTimer = new BatchTimer((ids: string[])=>{
let url = `${gateway}/api/adapters/${this._adapter}/fetch?entity_type=author`;
for (let id of ids) {
url += `&entity_id=${id}`;
}
util.authorizedFetch("GET", url, null)
});
}
attributeChangedCallback(attr: string, prev: string, next: string) {
switch (attr) {
case "data-target":
if (!next) {
return
}
this._id = next;
if (this._authorTimer) {
this._authorTimer.queue(next, 100);
}
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;
}
this._author = datastore.profileCache.get(next) ?? null;
console.log(this._author);
if (this._author == null) {
return;
}
if (this._author.id == next) {
let pfp = this.querySelector(".author_pfp");
let handle = this.querySelector(".author_id");
let prefName = this.querySelector(".author_name");
let bio = this.querySelector(".author_description");
if (pfp) {
pfp.innerHTML = `<img src="${this._author.profilePic}">`
}
if (handle) {
handle.innerHTML = `<a href="${this._author.uri}">${this._author.id}</a>`;
}
if (prefName) {
prefName.innerHTML = this._author.name;
}
if (bio) {
bio.innerHTML = this._author.profileData;
}
}
break;
}
}
}

View file

@ -58,7 +58,7 @@ export class SettingsElement extends HTMLElement {
return ()=>{
// dropdown for protocol
let html = "<select id='settings_newadapter_protocolselect'>";
html += [ "nostr", "mastodon", "misskey" ].reduce((self, p)=>{
html += [ "nostr", "mastodon", "misskey", "honk" ].reduce((self, p)=>{
self += `<option value='${p}'>${p}</option>`;
return self;
}, "");
@ -114,6 +114,11 @@ export class SettingsElement extends HTMLElement {
const apiKey = (util.$("settings_newadapter_masto_apikey") as HTMLInputElement)?.value ?? "";
adapterdata = { nickname: nickname, protocol: proto.options[proto.selectedIndex].value, server: server, apiKey: apiKey };
break;
case "honk":
const handle = (util.$("settings_newadapter_honk_handle") as HTMLInputElement)?.value ?? "";
const password = (util.$("settings_newadapter_honk_password") as HTMLInputElement)?.value ?? "";
adapterdata = { nickname: nickname, protocol: proto.options[proto.selectedIndex].value, handle: handle, password: password };
break;
}
const settings = Settings._instance;
if (settings) {
@ -122,7 +127,7 @@ export class SettingsElement extends HTMLElement {
}
settings.adapters.push(adapterdata);
self._adapters.push(adapterdata.nickname);
localStorage.setItem("settings", JSON.stringify(settings));
localStorage.setItem("underbbs_settings", JSON.stringify(settings));
self.showSettings(self)();
}
@ -146,6 +151,10 @@ export class SettingsElement extends HTMLElement {
html += " <label>server<input id='settings_newadapter_masto_server'/></label>";
html += " <label>API key<input id='settings_newadapter_masto_apikey'/></label>";
break;
case "honk":
html += " <label>nickname<input id='settings_newadapter_nickname'/></label>";
html += " <label>handle<input id='settings_newadapter_honk_handle'/></label>";
html += " <label>password<input id='settings_newadapter_honk_password'/></label>";
}
const div = util.$("settings_newadapter_protocoloptions");

View file

@ -10,6 +10,10 @@ export class AdapterConfig {
// nostr
public privkey: string | null = null;
public relays: string[] | null = null;
// honk
public handle: string | null = null;
public password: string | null = null;
}
export class Settings {

View file

@ -22,10 +22,12 @@ export class DatagramSocket {
util.authorizedFetch("POST", "/api/adapters", JSON.stringify(Settings._instance.adapters))
.then(r=> {
if (r.ok) {
const tabbar = document.querySelector("underbbs-tabbar");
if (tabbar) {
tabbar.setAttribute("data-adapters", Settings._instance.adapters.map(a=>a.nickname).join(","));
}
// iterate through any components which might want to fetch data
const profiles = document.querySelectorAll("underbbs-profile");
profiles.forEach(p=>{
const target = p.getAttribute("data-target");
p.setAttribute("data-target", target ?? "");
});
}
})
.catch(e => {
@ -36,7 +38,8 @@ export class DatagramSocket {
if (!store) {
AdapterState._instance.data.set(data.adapter, new AdapterData(data.protocol));
store = AdapterState._instance.data.get(data.adapter);
} else {
}
if (store) {
// typeswitch on the incoming data type and fill the memory
switch (data.type) {
case "message":
@ -49,11 +52,12 @@ export class DatagramSocket {
break;
}
}
// if the adapter is active signal it that there's new data
let adapter = util.$(`adapter_${data.adapter}`);
if (adapter) {
adapter.setAttribute("data-latest", data.id);
}
console.log(store);
// now search for any components with this adapter and target
let targets = document.querySelectorAll(`*[data-adapter="${data.adapter}"][data-target="${data.id}"]`);
targets.forEach(t=>{
t.setAttribute("data-latest", data.id);
});
}
}

View file

@ -80,6 +80,8 @@ func apiConfigureAdapters(next http.Handler, subscribers map[*Subscriber][]adapt
a = &adapter.MastoAdapter{}
case "misskey":
a = &adapter.MisskeyAdapter{}
case "honk":
a = &adapter.HonkAdapter{}
default:
break