redoing frontend stuff, we have a basic profile fetch working
This commit is contained in:
parent
305366dc9e
commit
64d00083b7
10 changed files with 135 additions and 50 deletions
|
@ -137,7 +137,7 @@ func getBodyJson(res *http.Response) []byte {
|
||||||
l := res.ContentLength
|
l := res.ContentLength
|
||||||
// 4k is a reasonable max size if we get unknown length right?
|
// 4k is a reasonable max size if we get unknown length right?
|
||||||
if l < 0 {
|
if l < 0 {
|
||||||
l = 4096
|
l = 4096
|
||||||
}
|
}
|
||||||
jsonData := make([]byte, l)
|
jsonData := make([]byte, l)
|
||||||
res.Body.Read(jsonData)
|
res.Body.Read(jsonData)
|
||||||
|
|
|
@ -36,13 +36,14 @@ func (self *HonkAdapter) Name() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *HonkAdapter) Init(settings Settings, data *chan SocketData) error {
|
func (self *HonkAdapter) Init(settings Settings, data *chan SocketData) error {
|
||||||
|
self.data = data
|
||||||
// separate name and server in handle
|
// separate name and server in handle
|
||||||
parts := strings.Split(*settings.Handle, "@")
|
parts := strings.Split(*settings.Handle, "@")
|
||||||
self.username = parts[1]
|
self.username = parts[1]
|
||||||
self.server = "https://" + parts[2]
|
self.server = "https://" + parts[2]
|
||||||
self.nickname = settings.Nickname
|
self.nickname = settings.Nickname
|
||||||
|
|
||||||
if settings.Password == nil {
|
if settings.Password == nil || *settings.Password == "" {
|
||||||
// we're anonymous!
|
// we're anonymous!
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -71,10 +72,11 @@ func (self *HonkAdapter) Init(settings Settings, data *chan SocketData) error {
|
||||||
|
|
||||||
func (self *HonkAdapter) Subscribe(string) []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
|
// 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 {
|
func (self *HonkAdapter) Fetch(etype string, ids []string) error {
|
||||||
// honk API is limited, we fall back to the anonymous adapter for fetch ops
|
// honk API is limited, we fall back to the anonymous adapter for fetch ops
|
||||||
aaa := anonAPAdapter{}
|
aaa := anonAPAdapter{}
|
||||||
aaa.Init(self.data, self.server, "honk", self.nickname)
|
aaa.Init(self.data, self.server, "honk", self.nickname)
|
||||||
return aaa.Fetch(etype, ids)
|
return aaa.Fetch(etype, ids)
|
||||||
|
|
9
frontend/dist/index.html
vendored
9
frontend/dist/index.html
vendored
|
@ -2,7 +2,7 @@
|
||||||
<html lang="en-US">
|
<html lang="en-US">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<title>underBBS</title>
|
<title>underBBS test</title>
|
||||||
<meta name="viewport" content="width=device-width" />
|
<meta name="viewport" content="width=device-width" />
|
||||||
<link rel="shortcut icon" href="./favicon.png"/>
|
<link rel="shortcut icon" href="./favicon.png"/>
|
||||||
<link href="./style.css" rel="stylesheet" />
|
<link href="./style.css" rel="stylesheet" />
|
||||||
|
@ -13,9 +13,10 @@
|
||||||
</div>
|
</div>
|
||||||
</noscript>
|
</noscript>
|
||||||
<div id="err_wrapper" style='display:none'><button id="err_close" onclick="closeErr()">x</button><div id="err_div"></div></div>
|
<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">
|
<main>
|
||||||
</nav>
|
<details open><summary>settings</summary><div id="settings_parent"></div></details>
|
||||||
<main id="mainarea_injectparent">
|
<details open><summary>profile</summary><div id="profile_parent"></div></details>
|
||||||
|
<details open><summary>honks</summary><div id="honks_parent"></div></details>
|
||||||
</main>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
<script src="./main.js" type="application/javascript"></script>
|
<script src="./main.js" type="application/javascript"></script>
|
||||||
|
|
|
@ -3,6 +3,7 @@ export class BoostTileElement extends HTMLElement {
|
||||||
static observedAttributes = [ "data-boostid", "data-msgid", "data-author", "data-booster" ];
|
static observedAttributes = [ "data-boostid", "data-msgid", "data-author", "data-booster" ];
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
super();
|
||||||
this.innerHTML = "<div class='boost_booster'></div><div class='boost_author'></div><div class='boost_content'></div>";
|
this.innerHTML = "<div class='boost_booster'></div><div class='boost_author'></div><div class='boost_content'></div>";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,9 +7,11 @@ 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"
|
||||||
|
import { ProfileElement } from "./profile-element"
|
||||||
|
import {DatagramSocket} from "./websocket"
|
||||||
|
|
||||||
function main() {
|
function main() {
|
||||||
const saveData = localStorage.getItem("settings");
|
const saveData = localStorage.getItem("underbbs_settings");
|
||||||
Settings._instance = saveData ? <Settings>JSON.parse(saveData) : new Settings();
|
Settings._instance = saveData ? <Settings>JSON.parse(saveData) : new Settings();
|
||||||
|
|
||||||
customElements.define("underbbs-tabbar", TabBarElement);
|
customElements.define("underbbs-tabbar", TabBarElement);
|
||||||
|
@ -17,39 +19,17 @@ function main() {
|
||||||
customElements.define("underbbs-settings", SettingsElement);
|
customElements.define("underbbs-settings", SettingsElement);
|
||||||
customElements.define("underbbs-adapter", AdapterElement);
|
customElements.define("underbbs-adapter", AdapterElement);
|
||||||
customElements.define("underbbs-thread-summary", ThreadSummaryElement);
|
customElements.define("underbbs-thread-summary", ThreadSummaryElement);
|
||||||
|
customElements.define("underbbs-profile", ProfileElement);
|
||||||
|
|
||||||
util._("closeErr", util.closeErr);
|
util._("closeErr", util.closeErr);
|
||||||
|
let settingsParent = util.$("settings_parent");
|
||||||
tabbarInit(Settings._instance.adapters?.map(a=>a.nickname) ?? []);
|
if (settingsParent) {
|
||||||
|
settingsParent.innerHTML = `<underbbs-settings data-adapters='${Settings._instance.adapters.map(a=>a.nickname).join(",")}'></underbbs-settings>`
|
||||||
registerServiceWorker();
|
|
||||||
}
|
|
||||||
|
|
||||||
function tabbarInit(adapters: string[]) {
|
|
||||||
const nav = util.$("tabbar_injectparent");
|
|
||||||
if (nav) {
|
|
||||||
nav.innerHTML = `<underbbs-tabbar data-adapters="" data-currentadapter=""></underbbs-tabbar>`;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
let profileParent = util.$("profile_parent");
|
||||||
async function registerServiceWorker() {
|
if (profileParent) {
|
||||||
if ("serviceWorker" in navigator) {
|
profileParent.innerHTML = "<underbbs-profile data-adapter='honk' data-target='https://cafe.nilfm.cc/u/nilix'></underbbs-profile>"
|
||||||
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!")});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
82
frontend/ts/profile-element.ts
Normal file
82
frontend/ts/profile-element.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -58,7 +58,7 @@ export class SettingsElement extends HTMLElement {
|
||||||
return ()=>{
|
return ()=>{
|
||||||
// dropdown for protocol
|
// dropdown for protocol
|
||||||
let html = "<select id='settings_newadapter_protocolselect'>";
|
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>`;
|
self += `<option value='${p}'>${p}</option>`;
|
||||||
return self;
|
return self;
|
||||||
}, "");
|
}, "");
|
||||||
|
@ -114,6 +114,11 @@ export class SettingsElement extends HTMLElement {
|
||||||
const apiKey = (util.$("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;
|
||||||
|
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;
|
const settings = Settings._instance;
|
||||||
if (settings) {
|
if (settings) {
|
||||||
|
@ -122,7 +127,7 @@ export class SettingsElement extends HTMLElement {
|
||||||
}
|
}
|
||||||
settings.adapters.push(adapterdata);
|
settings.adapters.push(adapterdata);
|
||||||
self._adapters.push(adapterdata.nickname);
|
self._adapters.push(adapterdata.nickname);
|
||||||
localStorage.setItem("settings", JSON.stringify(settings));
|
localStorage.setItem("underbbs_settings", JSON.stringify(settings));
|
||||||
|
|
||||||
self.showSettings(self)();
|
self.showSettings(self)();
|
||||||
}
|
}
|
||||||
|
@ -146,6 +151,10 @@ export class SettingsElement extends HTMLElement {
|
||||||
html += " <label>server<input id='settings_newadapter_masto_server'/></label>";
|
html += " <label>server<input id='settings_newadapter_masto_server'/></label>";
|
||||||
html += " <label>API key<input id='settings_newadapter_masto_apikey'/></label>";
|
html += " <label>API key<input id='settings_newadapter_masto_apikey'/></label>";
|
||||||
break;
|
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");
|
const div = util.$("settings_newadapter_protocoloptions");
|
||||||
|
|
|
@ -10,6 +10,10 @@ export class AdapterConfig {
|
||||||
// nostr
|
// nostr
|
||||||
public privkey: string | null = null;
|
public privkey: string | null = null;
|
||||||
public relays: string[] | null = null;
|
public relays: string[] | null = null;
|
||||||
|
|
||||||
|
// honk
|
||||||
|
public handle: string | null = null;
|
||||||
|
public password: string | null = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Settings {
|
export class Settings {
|
||||||
|
|
|
@ -22,10 +22,12 @@ export class DatagramSocket {
|
||||||
util.authorizedFetch("POST", "/api/adapters", JSON.stringify(Settings._instance.adapters))
|
util.authorizedFetch("POST", "/api/adapters", JSON.stringify(Settings._instance.adapters))
|
||||||
.then(r=> {
|
.then(r=> {
|
||||||
if (r.ok) {
|
if (r.ok) {
|
||||||
const tabbar = document.querySelector("underbbs-tabbar");
|
// iterate through any components which might want to fetch data
|
||||||
if (tabbar) {
|
const profiles = document.querySelectorAll("underbbs-profile");
|
||||||
tabbar.setAttribute("data-adapters", Settings._instance.adapters.map(a=>a.nickname).join(","));
|
profiles.forEach(p=>{
|
||||||
}
|
const target = p.getAttribute("data-target");
|
||||||
|
p.setAttribute("data-target", target ?? "");
|
||||||
|
});
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(e => {
|
.catch(e => {
|
||||||
|
@ -36,7 +38,8 @@ export class DatagramSocket {
|
||||||
if (!store) {
|
if (!store) {
|
||||||
AdapterState._instance.data.set(data.adapter, new AdapterData(data.protocol));
|
AdapterState._instance.data.set(data.adapter, new AdapterData(data.protocol));
|
||||||
store = AdapterState._instance.data.get(data.adapter);
|
store = AdapterState._instance.data.get(data.adapter);
|
||||||
} else {
|
}
|
||||||
|
if (store) {
|
||||||
// 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":
|
||||||
|
@ -49,11 +52,12 @@ export class DatagramSocket {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// if the adapter is active signal it that there's new data
|
console.log(store);
|
||||||
let adapter = util.$(`adapter_${data.adapter}`);
|
// now search for any components with this adapter and target
|
||||||
if (adapter) {
|
let targets = document.querySelectorAll(`*[data-adapter="${data.adapter}"][data-target="${data.id}"]`);
|
||||||
adapter.setAttribute("data-latest", data.id);
|
targets.forEach(t=>{
|
||||||
}
|
t.setAttribute("data-latest", data.id);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -80,6 +80,8 @@ func apiConfigureAdapters(next http.Handler, subscribers map[*Subscriber][]adapt
|
||||||
a = &adapter.MastoAdapter{}
|
a = &adapter.MastoAdapter{}
|
||||||
case "misskey":
|
case "misskey":
|
||||||
a = &adapter.MisskeyAdapter{}
|
a = &adapter.MisskeyAdapter{}
|
||||||
|
case "honk":
|
||||||
|
a = &adapter.HonkAdapter{}
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue