building out frontend stuff... almost there
This commit is contained in:
parent
c39c9f9412
commit
fd2abcbb76
10 changed files with 470 additions and 70 deletions
30
build.sh
30
build.sh
|
@ -1,8 +1,28 @@
|
|||
#!/bin/sh
|
||||
|
||||
if [ ! -e ./src ]; then
|
||||
case "$1" in
|
||||
client)
|
||||
if [ ! -e ./src ]; then
|
||||
mkdir ./src
|
||||
fi
|
||||
|
||||
npx tsc &&
|
||||
npx webpack --config webpack.config.js
|
||||
fi
|
||||
buildlog=$(mktemp)
|
||||
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
|
||||
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 {
|
||||
Id string
|
||||
Uri string
|
||||
Protocol string
|
||||
Adapter string
|
||||
Type string
|
||||
Target *string
|
||||
Id string `json:"id"`
|
||||
Uri string `json:"uri"`
|
||||
Protocol string `json:"protocol"`
|
||||
Adapter string `json:"adapter"`
|
||||
Type string `json:"type"`
|
||||
Target *string `json:"target,omitempty"`
|
||||
}
|
||||
|
||||
type Message struct {
|
||||
Datagram
|
||||
Author string
|
||||
Content string
|
||||
Attachments []Attachment
|
||||
ReplyTo *string
|
||||
Replies []string
|
||||
ReplyCount int
|
||||
Mentions []string
|
||||
Created time.Time
|
||||
Visibility string
|
||||
Aux map[string]string
|
||||
Author string `json:"author"`
|
||||
Content string `json:"content"`
|
||||
Attachments []Attachment `json:"attachments"`
|
||||
ReplyTo *string `json:"replyTo"`
|
||||
Replies []string `json:"replies"`
|
||||
ReplyCount int `json:"replyCount"`
|
||||
Mentions []string `json:"mentions"`
|
||||
Created time.Time `json:"created"`
|
||||
Edited *time.Time `json:"edited,omitempty"`
|
||||
Visibility string `json:"visibility"`
|
||||
}
|
||||
|
||||
type Author struct {
|
||||
Datagram
|
||||
Name string
|
||||
ProfileData interface{}
|
||||
ProfilePic string
|
||||
Messages []Message
|
||||
Name string `json:"name"`
|
||||
ProfileData interface{} `json:"profileData"`
|
||||
ProfilePic string `json:"profilePic"`
|
||||
Messages []string `json:"messages,omitempty"`
|
||||
}
|
||||
|
||||
type Attachment struct {
|
||||
Src string
|
||||
ThumbSrc string
|
||||
Desc string
|
||||
CreatedAt time.Time
|
||||
Size uint64
|
||||
Src string `json:"src"`
|
||||
ThumbSrc string `json:"thumbSrc"`
|
||||
Desc string `json:"desc"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
Size uint64 `json:"size"`
|
||||
}
|
||||
|
||||
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 { MessageElement } from "./message-element"
|
||||
import { SettingsElement } from "./settings-element"
|
||||
import { AdapterElement } from "./adapter-element"
|
||||
import { ThreadSummaryElement } from "./thread-summary-element"
|
||||
|
||||
var $ = util.$
|
||||
var _ = util._
|
||||
|
@ -14,6 +16,8 @@ function main() {
|
|||
customElements.define("underbbs-tabbar", TabBarElement);
|
||||
customElements.define("underbbs-message", MessageElement);
|
||||
customElements.define("underbbs-settings", SettingsElement);
|
||||
customElements.define("underbbs-adapter", AdapterElement);
|
||||
customElements.define("underbbs-thread-summary", ThreadSummaryElement);
|
||||
|
||||
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 {
|
||||
public author: Author = new Author();
|
||||
public id: string = "";
|
||||
public uri: string = "";
|
||||
public protocol: string = "";
|
||||
public adapter: string = "";
|
||||
public author: string = ""
|
||||
public content: string = "";
|
||||
public attachments: Attachment[] = [];
|
||||
public replyTo: Message | null = null;
|
||||
public replies: Message[] = [];
|
||||
public mentions: Author[] = [];
|
||||
public replyTo: string | null = null;
|
||||
public replies: string[] = [];
|
||||
public mentions: string[] = [];
|
||||
public created: Date = new Date();
|
||||
public edited: Date = new Date();
|
||||
public edited: Date | null = null;
|
||||
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 {
|
||||
public id: string = "";
|
||||
public uri: string = "";
|
||||
public protocol: string = "";
|
||||
public adapter: string = "";
|
||||
public name: string = "";
|
||||
public profileData: string = "";
|
||||
public messages: Message[] = [];
|
||||
public profileData: any = {};
|
||||
public profilePic: string = "";
|
||||
public messages: string[] = [];
|
||||
}
|
||||
|
||||
export class Attachment {
|
||||
public file: Uint8Array = new Uint8Array();
|
||||
public altText: string = "";
|
||||
public filename: string = "";
|
||||
public Src: string = "";
|
||||
public ThumbSrc: string = "";
|
||||
public Desc: string = "";
|
||||
public CreatedAt: Date = new Date();
|
||||
}
|
||||
|
||||
export default { Message, Attachment, Author }
|
|
@ -31,7 +31,7 @@ export class SettingsElement extends HTMLElement {
|
|||
return self;
|
||||
}, "<ul id='settings_adapterlist'>");
|
||||
html += "</ul>";
|
||||
html += "<button id='settings_save_btn'>save</button>";
|
||||
html += "<button id='settings_connect_btn'>connect</button>";
|
||||
self.innerHTML = html;
|
||||
|
||||
let create = $("settings_adapter_create_btn");
|
||||
|
|
|
@ -76,7 +76,7 @@ export class TabBarElement extends HTMLElement {
|
|||
return ()=>{
|
||||
let x = $("mainarea_injectparent");
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
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() {
|
||||
|
||||
var datastore: AdapterState = {}
|
||||
datastore = _("datastore", datastore);
|
||||
let datastore = <AdapterState>_("datastore", {});
|
||||
|
||||
const wsProto = location.protocol == "https:" ? "wss" : "ws";
|
||||
const _conn = new WebSocket(`${wsProto}://${location.host}/subscribe`, "underbbs");
|
||||
|
@ -17,12 +16,8 @@ function connect() {
|
|||
console.log(JSON.stringify(e));
|
||||
});
|
||||
_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);
|
||||
console.log(data);
|
||||
if (data.key) {
|
||||
_("skey", data.key)
|
||||
util.authorizedFetch("POST", "/api/adapters", JSON.stringify(_("settings").adapters))
|
||||
|
@ -43,6 +38,10 @@ function connect() {
|
|||
break;
|
||||
}
|
||||
// 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:
|
||||
// before fetching, we can set properties on the DOM,
|
||||
// so when those data return to us we know where to inject components!
|
||||
|
|
Loading…
Reference in a new issue