Compare commits
1 commit
Author | SHA1 | Date | |
---|---|---|---|
09c7eb8318 |
9 changed files with 38 additions and 167 deletions
|
@ -1,7 +1,6 @@
|
||||||
# underBBS
|
# underBBS
|
||||||
|
|
||||||
underBBS is a platform-agnostic messaging and social media client (feat consultation and motivational support from
|
underBBS is a platform-agnostic messaging and social media client
|
||||||
miggymofongo!!!)
|
|
||||||
|
|
||||||
## design
|
## design
|
||||||
|
|
||||||
|
|
|
@ -216,6 +216,7 @@ func (self *MisskeyAdapter) toMessage(n mkm.Note, bustCache bool) *Message {
|
||||||
ReplyTo: n.ReplyID,
|
ReplyTo: n.ReplyID,
|
||||||
ReplyCount: int(n.RepliesCount),
|
ReplyCount: int(n.RepliesCount),
|
||||||
Replies: []string{},
|
Replies: []string{},
|
||||||
|
RenoteId: (*string)(n.RenoteID),
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, f := range n.Files {
|
for _, f := range n.Files {
|
||||||
|
|
|
@ -24,6 +24,7 @@ export class AdapterElement extends HTMLElement {
|
||||||
// TODO: use visibility of the thread to organize into DMs and public threads
|
// TODO: use visibility of the thread to organize into DMs and public threads
|
||||||
private _threads: MessageThread[] = [];
|
private _threads: MessageThread[] = [];
|
||||||
private _orphans: Message[] = [];
|
private _orphans: Message[] = [];
|
||||||
|
private _boosts: Message[] = [];
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
@ -102,6 +103,7 @@ export class AdapterElement extends HTMLElement {
|
||||||
tse.setAttribute("data-author", this._latest);
|
tse.setAttribute("data-author", this._latest);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// also update any boosts by this author
|
||||||
case "thread":
|
case "thread":
|
||||||
case "profile":
|
case "profile":
|
||||||
break;
|
break;
|
||||||
|
@ -113,7 +115,7 @@ export class AdapterElement extends HTMLElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
setIdxView() {
|
setIdxView() {
|
||||||
this.innerHTML = "<ul id='dm_list'></ul><ul id='public_list'></ul>"
|
this.innerHTML = "<ul id='boost_carousel'></ul><ul id='dm_list'></ul><ul id='public_list'></ul>"
|
||||||
}
|
}
|
||||||
|
|
||||||
setThreadView() {
|
setThreadView() {
|
||||||
|
@ -132,6 +134,8 @@ export class AdapterElement extends HTMLElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
populateIdxView() {
|
populateIdxView() {
|
||||||
|
// populate boost carousel
|
||||||
|
|
||||||
// skip dm list for now
|
// skip dm list for now
|
||||||
// public/unified list
|
// public/unified list
|
||||||
const pl = util.$("public_list");
|
const pl = util.$("public_list");
|
||||||
|
@ -149,11 +153,12 @@ export class AdapterElement extends HTMLElement {
|
||||||
const existingThread = document.querySelector(threadSelector);
|
const existingThread = document.querySelector(threadSelector);
|
||||||
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) {
|
||||||
debugger;
|
|
||||||
existingThread.setAttribute("data-latest", `${thread.latest}`);
|
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 {
|
||||||
|
// if latest is a boost, put it in the carousel
|
||||||
|
|
||||||
// unified/public list for now
|
// unified/public list for now
|
||||||
const pl = util.$("public_list");
|
const pl = util.$("public_list");
|
||||||
if (pl && thread) {
|
if (pl && thread) {
|
||||||
|
@ -190,14 +195,14 @@ export class AdapterElement extends HTMLElement {
|
||||||
return;
|
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, the boost carousel, or orphaned and waiting for its parent to be returned
|
||||||
do{
|
do{
|
||||||
for (let k of datastore.messages.keys()) {
|
for (let k of datastore.messages.keys()) {
|
||||||
this.placeMsg(k);
|
this.placeMsg(k);
|
||||||
}
|
}
|
||||||
} 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.size);
|
}, 0) + this._boosts.length + this._orphans.length < datastore.messages.size);
|
||||||
}
|
}
|
||||||
|
|
||||||
placeMsg(k: string): string | null {
|
placeMsg(k: string): string | null {
|
||||||
|
@ -211,11 +216,20 @@ export class AdapterElement extends HTMLElement {
|
||||||
util.errMsg(`message [${this._name}:${k}] doesn't exist`);
|
util.errMsg(`message [${this._name}:${k}] doesn't exist`);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
if (msg.renoteId) {
|
||||||
|
// fetch the referent thread and put the boost in the carousel
|
||||||
|
this._convoyBatchTimer.queue(msg.renoteId, 2000);
|
||||||
|
if (!this._boosts.some(m=>m.id == msg.id)) {
|
||||||
|
this._boosts.push(msg);
|
||||||
|
}
|
||||||
|
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 (!msg || t.findNode(t.root, msg.id)) {
|
if (!msg || t.findNode(t.root, msg.id)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (msg.replyTo) {
|
if (msg.replyTo) {
|
||||||
let x = t.addReply(msg.replyTo, msg);
|
let x = t.addReply(msg.replyTo, msg);
|
||||||
if (x) {
|
if (x) {
|
||||||
|
|
14
frontend/ts/boost-tile-element.ts
Normal file
14
frontend/ts/boost-tile-element.ts
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
export class BoostTileElement extends HTMLElement {
|
||||||
|
|
||||||
|
static observedAttributes = [ "data-boostid", "data-msgid", "data-author", "data-booster" ];
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.innerHTML = "<div class='boost_booster'></div><div class='boost_author'></div><div class='boost_content'></div>";
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
}
|
||||||
|
|
||||||
|
attributeChangedCallback(attr: string, prev: string, next: string) {
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,6 +12,7 @@ export class Message {
|
||||||
public created: number = 0;
|
public created: number = 0;
|
||||||
public edited: number | null = null;
|
public edited: number | null = null;
|
||||||
public visibility: string = "public";
|
public visibility: string = "public";
|
||||||
|
public renoteId: string | null = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Author {
|
export class Author {
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
export class ProfileView extends HTMLElement {
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
const prof_view = document.createElement("span");
|
|
||||||
|
|
||||||
prof_view.setAttribute("class", "wrapper")
|
|
||||||
|
|
||||||
connectedCallback() {
|
|
||||||
this.
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,144 +0,0 @@
|
||||||
/* WEPA this is miggymofongo's contribution to underBBS! I'm practicing web components
|
|
||||||
in this bishhh
|
|
||||||
|
|
||||||
this template contains the html code of the side bar
|
|
||||||
*/
|
|
||||||
const template = document.createElement("template")
|
|
||||||
|
|
||||||
template.innerHTML = `
|
|
||||||
<style>
|
|
||||||
|
|
||||||
/* side profile view menu */
|
|
||||||
.sidenav {
|
|
||||||
height: 50%;
|
|
||||||
width: 0;
|
|
||||||
position: fixed; /* Stay in place */
|
|
||||||
z-index: 1;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
background-color: #888;
|
|
||||||
overflow-x: hidden; /* Disable horizontal scroll */
|
|
||||||
padding-top: 60px; /* Place content 60px from the top */
|
|
||||||
transition: width 0.5s ease; /* smooth transition */
|
|
||||||
}
|
|
||||||
|
|
||||||
/*nav menu links */
|
|
||||||
.sidenav a {
|
|
||||||
padding: 8px 8px 8px 32px;
|
|
||||||
text-decoration: none;
|
|
||||||
font-size: 25px;
|
|
||||||
color: #818181;
|
|
||||||
display: block;
|
|
||||||
transition: 0.3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* change their color when you hover the mouse */
|
|
||||||
.sidenav a:hover {
|
|
||||||
color: #f1f1f1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* style the close button (puts in the top right corner) */
|
|
||||||
.sidenav .closeBtn {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 25px;
|
|
||||||
font-size: 36px;
|
|
||||||
margin-left: 50px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Style page content - use this if you want to push the page content to the right when you open the side navigation */
|
|
||||||
#main {
|
|
||||||
transition: margin-left .5s ease;
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* change the style of the sidenav on smaller screens where height is less than 450px,
|
|
||||||
(less padding and a smaller font size) */
|
|
||||||
|
|
||||||
@media screen and (max-height: 450px) {
|
|
||||||
.sidenav {padding-top: 15px;}
|
|
||||||
.sidenav a {font-size: 18px;}
|
|
||||||
}
|
|
||||||
/* the div block has an id and class so that its targeted by the styles
|
|
||||||
and the component underneath */
|
|
||||||
</style>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div id="mySidenav" class="sidenav">
|
|
||||||
<div id="profile">
|
|
||||||
|
|
||||||
<img src="Kaido.png" alt="profile pic" style="width:100px;height100px"/>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<button href="" class="closeBtn">×</button>
|
|
||||||
<a href=""><button>Message</button></a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button id="openBtn">open profile view</button>
|
|
||||||
|
|
||||||
`
|
|
||||||
|
|
||||||
/* setting up a web component will extend the set of available HTML elements for you to use in your
|
|
||||||
project and keep your codebase organized and modular. Set up a web component in a separate file
|
|
||||||
and link it directly in your html document head.
|
|
||||||
|
|
||||||
creating a custom web component is just like creating a class. after setting up
|
|
||||||
the constructor function, you need to create some methods that tell the browser more about it.
|
|
||||||
you can add whatever methods you want, but they at least need some of the following:
|
|
||||||
|
|
||||||
1) connectedCallback() => call this method when the element is added to the document,
|
|
||||||
2) disconnectedCallback() => call this method when the element is removed from the document
|
|
||||||
3) static get observedAttributes() => this returns an array of attribute names to track for changes
|
|
||||||
4) attributeChangedCallback(name, oldValue, newValue) => this one is called when one of the attributes
|
|
||||||
5) adoptedCallback() => called when the element is moved to a new document
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class ProfileSideBar extends HTMLElement {
|
|
||||||
//
|
|
||||||
constructor() { // the constructor function initiates the new side bar
|
|
||||||
super()
|
|
||||||
this.appendChild(template.content.cloneNode(true))
|
|
||||||
this.sidenav = this.querySelector("#mySidenav");
|
|
||||||
this.openBtn = this.querySelector("#openBtn");
|
|
||||||
this.closeBtn = this.querySelector(".closeBtn");
|
|
||||||
this.mainContent = document.getElementById("main");
|
|
||||||
|
|
||||||
}
|
|
||||||
/* this method adds event listeners to the openBtn and closeBtn classes that
|
|
||||||
call the openNav closeNav function to open and close the sidebar*/
|
|
||||||
connectedCallback() {
|
|
||||||
|
|
||||||
this.openBtn.addEventListener("click", this.openNav.bind(this));
|
|
||||||
this.closeBtn.addEventListener("click", this.closeNav.bind(this));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
disconnectedCallback() {
|
|
||||||
this.openBtn.removeEventListener("click", this.openNav.bind(this));
|
|
||||||
this.closeBtn.removeEventListener("click", this.closeNav.bind(this));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/* these two methods will open and close the profile side menu*/
|
|
||||||
openNav() {
|
|
||||||
this.sidenav.style.width = "450px"; // changes sidenav width from 0px to 450px
|
|
||||||
if (this.mainContent) {
|
|
||||||
this.mainContent.style.marginLeft = "250px";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
closeNav() {
|
|
||||||
this.sidenav.style.width = "0"; //changes sidenav's width back to 0px
|
|
||||||
if (this.mainContent) {
|
|
||||||
this.mainContent.style.marginLeft = "0px"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/*lastly, we need to define the element tag itself and attach it to the webcomponent */
|
|
||||||
customElements.define("profile-side-bar", ProfileSideBar)
|
|
|
@ -58,13 +58,12 @@ export class DatagramSocket {
|
||||||
}
|
}
|
||||||
|
|
||||||
static connect(): void {
|
static connect(): void {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const wsProto = location.protocol == "https:" ? "wss" : "ws";
|
const wsProto = location.protocol == "https:" ? "wss" : "ws";
|
||||||
const _conn = new WebSocket(`${wsProto}://${location.host}/subscribe`, "underbbs");
|
const _conn = new WebSocket(`${wsProto}://${location.host}/subscribe`, "underbbs");
|
||||||
|
|
||||||
_conn.addEventListener("open", DatagramSocket.onOpen);
|
_conn.addEventListener("open", DatagramSocket.onOpen);
|
||||||
_conn.addEventListener("message", DatagramSocket.onMsg);
|
_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));
|
||||||
|
|
|
@ -25,6 +25,7 @@ type Message struct {
|
||||||
ReplyCount int `json:"replyCount"`
|
ReplyCount int `json:"replyCount"`
|
||||||
Mentions []string `json:"mentions"`
|
Mentions []string `json:"mentions"`
|
||||||
Visibility string `json:"visibility"`
|
Visibility string `json:"visibility"`
|
||||||
|
RenoteId *string `json:"renoteId,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Author struct {
|
type Author struct {
|
||||||
|
|
Loading…
Reference in a new issue