Compare commits

..

No commits in common. "main" and "0.4-indev-hotfix-1" have entirely different histories.

24 changed files with 50 additions and 243 deletions

3
.gitignore vendored
View file

@ -9,5 +9,4 @@ chatexamples.txt
server.cert server.cert
server.key server.key
www/nonfree/* www/nonfree/*
migration/* migration/*
www/hrt.zip

View file

@ -9,7 +9,7 @@ Canopy
<a href="https://git.ourfore.st/rainbownapkin/canopy/issues" target="_blank"><img src="https://git.ourfore.st/rainbownapkin/canopy/badges/issues/closed.svg"></a> <a href="https://git.ourfore.st/rainbownapkin/canopy/issues" target="_blank"><img src="https://git.ourfore.st/rainbownapkin/canopy/badges/issues/closed.svg"></a>
<a href="https://www.gnu.org/licenses/agpl-3.0.en.html" target="_blank"><img src="https://img.shields.io/badge/License-AGPL_v3-663366.svg"></a> <a href="https://www.gnu.org/licenses/agpl-3.0.en.html" target="_blank"><img src="https://img.shields.io/badge/License-AGPL_v3-663366.svg"></a>
0.1-Alpha (Panama Red) - Hotfix 2 0.4-INDEV Hotfix 1
========= =========
Canopy - /ˈkæ.nə.pi/: Canopy - /ˈkæ.nə.pi/:

View file

@ -1,7 +1,6 @@
{ {
"name": "canopy-of-alpha", "name": "canopy-of-indev",
"version": "0.1.2", "version": "0.4",
"canopyDisplayVersion": "0.1-Alpha (Panama Red) - Hotfix 2",
"license": "AGPL-3.0-only", "license": "AGPL-3.0-only",
"dependencies": { "dependencies": {
"@braintree/sanitize-url": "^7.1.1", "@braintree/sanitize-url": "^7.1.1",

View file

@ -212,12 +212,6 @@ class chatHandler{
* @param {chat} chat - Chat Object representing the message to broadcast to the given channel * @param {chat} chat - Chat Object representing the message to broadcast to the given channel
*/ */
relayChatObject(chan, chat){ relayChatObject(chan, chat){
//If we have an empty chat
if(chat.msg.length <= 0){
//Drop it
return;
}
//Send out chat //Send out chat
this.server.io.in(chan).emit("chatMessage", chat); this.server.io.in(chan).emit("chatMessage", chat);

View file

@ -148,15 +148,6 @@ class tokebot{
//Add the toking user to the tokers map //Add the toking user to the tokers map
this.tokers.set(commandObj.socket.user.user, commandObj.argumentArray[0].toLowerCase()); this.tokers.set(commandObj.socket.user.user, commandObj.argumentArray[0].toLowerCase());
if(this.tokeCounter <= 3){
//Drop the toke timer
clearTimeout(this.tokeTimer);
//Roll the toke counter back to 3
this.tokeCounter = 3;
//Re-start the toke timer
this.tokeTimer = setTimeout(this.countdown.bind(this), 1000);
}
//If the user is already in the toke //If the user is already in the toke
}else{ }else{
//Tell them to fuck off //Tell them to fuck off
@ -219,7 +210,7 @@ class tokebot{
//Decrement toke time //Decrement toke time
this.tokeCounter--; this.tokeCounter--;
//try again in another second //try again in another second
this.tokeTimer = setTimeout(this.countdown.bind(this), 1000); this.tokeTimer = setTimeout(this.countdown.bind(this), 1000)
} }
/** /**

View file

@ -16,7 +16,6 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.*/
//Config //Config
const config = require('../../config.json'); const config = require('../../config.json');
const package = require('../../package.json');
//Local Imports //Local Imports
const csrfUtils = require('../utils/csrfUtils'); const csrfUtils = require('../utils/csrfUtils');
@ -24,5 +23,5 @@ const csrfUtils = require('../utils/csrfUtils');
//register page functions //register page functions
module.exports.get = async function(req, res){ module.exports.get = async function(req, res){
//Render page //Render page
return res.render('about', {aboutText: config.aboutText, instance: config.instanceName, user: req.session.user, version: package.canopyDisplayVersion, csrfToken: csrfUtils.generateToken(req)}); return res.render('about', {aboutText: config.aboutText, instance: config.instanceName, user: req.session.user, csrfToken: csrfUtils.generateToken(req)});
} }

View file

@ -33,14 +33,17 @@ module.exports.post = async function(req, res){
const data = matchedData(req); const data = matchedData(req);
//make sure we're not bullshitting ourselves here. //make sure we're not bullshitting ourselves here.
if(user == null || user.user == null){ if(user == null){
return errorHandler(res, 'You must be logged in to delete your account!', 'unauthorized'); res.status(400);
return res.send('Invalid Session! Cannot delete account while logged out!');
} }
const userDB = await userModel.findOne({user: user.user}); const userDB = await userModel.findOne(user);
if(!userDB){ if(!userDB){
return errorHandler(res, 'User not found!', 'unauthorized'); res.status(400);
return res.send('Invalid User! Account must exist in order to delete!');
} }
await userDB.nuke(data.pass); await userDB.nuke(data.pass);

View file

@ -60,7 +60,7 @@ module.exports.post = async function(req, res){
//Look through DB and migration cache for existing email //Look through DB and migration cache for existing email
const existingDB = await userModel.findOne({email: new RegExp(`^${email}$`, 'i')}); const existingDB = await userModel.findOne({email: new RegExp(email, 'i')});
const needsMigration = userModel.migrationCache.emails.includes(email.toLowerCase()); const needsMigration = userModel.migrationCache.emails.includes(email.toLowerCase());
//If the email is in use //If the email is in use

View file

@ -90,7 +90,7 @@ module.exports.post = async function(req, res){
const {user, pass} = matchedData(req); const {user, pass} = matchedData(req);
//Look for the username in the migration DB //Look for the username in the migration DB
const migrationDB = await migrationModel.findOne({user: new RegExp(`^${user}$`, 'i')}); const migrationDB = await migrationModel.findOne({user});
//If we found a migration profile //If we found a migration profile
if(migrationDB != null){ if(migrationDB != null){

View file

@ -46,13 +46,7 @@ module.exports.post = async function(req, res){
const {field, change} = data; const {field, change} = data;
const {user} = req.session; const {user} = req.session;
//If the user is null const userDB = await userModel.findOne(user);
if(user == null || user.user == null){
//BEFORE YOU BREAK MY HEART!!!
return errorHandler(res, 'You must be logged in to preform this action!', 'unauthorized');
}
const userDB = await userModel.findOne({user: user.user});
const update = {}; const update = {};
@ -92,7 +86,8 @@ module.exports.post = async function(req, res){
res.status(200); res.status(200);
return res.send(update); return res.send(update);
}else{ }else{
return errorHandler(res, 'User not found!', 'unauthorized'); res.status(400);
return res.send({errors: [{msg:"User not found!"}]});
} }
}else{ }else{
res.status(400); res.status(400);

View file

@ -1,28 +0,0 @@
/*Canopy - The next generation of stoner streaming software
Copyright (C) 2024-2025 Rainbownapkin and the TTN Community
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.*/
//Config
const config = require('../../config.json');
const package = require('../../package.json');
//Local Imports
const csrfUtils = require('../utils/csrfUtils');
//register page functions
module.exports.get = async function(req, res){
//Render page
return res.render('hrt', {instance: config.instanceName, user: req.session.user, csrfToken: csrfUtils.generateToken(req)});
}

View file

@ -1,34 +0,0 @@
/*Canopy - The next generation of stoner streaming software
Copyright (C) 2024-2025 Rainbownapkin and the TTN Community
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.*/
//npm imports
const { Router } = require('express');
//local imports
const hrtController = require("../controllers/hrtController");
const presenceUtils = require("../utils/presenceUtils");
//globals
const router = Router();
//Use presence middleware
router.use(presenceUtils.presenceMiddleware);
//routing functions
router.get('/', hrtController.get);
module.exports = router;

View file

@ -217,22 +217,13 @@ migrationSchema.statics.ingestLegacyUser = async function(rawProfile){
return; return;
} }
//Pull rank, dropping over-ranked users down to current enum length
let rank = Math.min(Math.max(0, profileArray[3]), permissionModel.rankEnum.length - 1);
//If this user was a mod on the old site
if(rank == 2){
//Set them up as a mod here
rank = permissionModel.rankEnum.length - 2;
}
//Create migration profile object from scraped info //Create migration profile object from scraped info
const migrationProfile = new this({ const migrationProfile = new this({
user: profileArray[1], user: profileArray[1],
pass: profileArray[2], pass: profileArray[2],
//Clamp rank to 0 and the max setting allowed by the rank enum //Clamp rank to 0 and the max setting allowed by the rank enum
rank, rank: Math.min(Math.max(0, profileArray[3]), permissionModel.rankEnum.length - 1),
email: validator.normalizeEmail(profileArray[4]), email: validator.normalizeEmail(profileArray[4]),
date: profileArray[7], date: profileArray[7],
}) })
@ -323,7 +314,7 @@ migrationSchema.statics.buildMigrationCache = async function(){
migrationSchema.statics.consumeByUsername = async function(ip, migration){ migrationSchema.statics.consumeByUsername = async function(ip, migration){
//Pull migration doc by case-insensitive username //Pull migration doc by case-insensitive username
const migrationDB = await this.findOne({user: new RegExp(`^${migration.user}$`, 'i')}); const migrationDB = await this.findOne({user: new RegExp(migration.user, 'i')});
//If we have no migration document //If we have no migration document
if(migrationDB == null){ if(migrationDB == null){

View file

@ -256,13 +256,13 @@ userSchema.statics.register = async function(userObj, ip){
//Check password confirmation matches //Check password confirmation matches
if(pass == passConfirm){ if(pass == passConfirm){
//Setup user query //Setup user query
let userQuery = {user: new RegExp(`^${user}$`, 'i')}; let userQuery = {user: new RegExp(user, 'i')};
//If we have an email //If we have an email
if(email != null && email != ""){ if(email != null && email != ""){
userQuery = {$or: [ userQuery = {$or: [
userQuery, userQuery,
{email: new RegExp(`^${email}$`, 'i')} {email: new RegExp(email, 'i')}
]}; ]};
} }
@ -319,7 +319,7 @@ userSchema.statics.authenticate = async function(user, pass, failLine = "Bad Use
} }
//get the user if it exists //get the user if it exists
const userDB = await this.findOne({ user: new RegExp(`^${user}$`, 'i')}); const userDB = await this.findOne({ user: new RegExp(user, 'i')});
//if not scream and shout //if not scream and shout
if(!userDB){ if(!userDB){

View file

@ -55,7 +55,6 @@ const fileNotFoundController = require('./controllers/404Controller');
//Humie-Friendly //Humie-Friendly
const indexRouter = require('./routers/indexRouter'); const indexRouter = require('./routers/indexRouter');
const aboutRouter = require('./routers/aboutRouter'); const aboutRouter = require('./routers/aboutRouter');
const hrtRouter = require('./routers/hrtRouter');
const registerRouter = require('./routers/registerRouter'); const registerRouter = require('./routers/registerRouter');
const loginRouter = require('./routers/loginRouter'); const loginRouter = require('./routers/loginRouter');
const profileRouter = require('./routers/profileRouter'); const profileRouter = require('./routers/profileRouter');
@ -76,7 +75,6 @@ const apiRouter = require('./routers/apiRouter');
//Define Config variables //Define Config variables
const config = require('../config.json'); const config = require('../config.json');
const package = require('../package.json');
const port = config.port; const port = config.port;
const dbUrl = `mongodb://${config.db.user}:${config.db.pass}@${config.db.address}:${config.db.port}/${config.db.database}`; const dbUrl = `mongodb://${config.db.user}:${config.db.pass}@${config.db.address}:${config.db.port}/${config.db.database}`;
@ -180,7 +178,6 @@ app.use(sessionUtils.rememberMeMiddleware);
//Humie-Friendly //Humie-Friendly
app.use('/', indexRouter); app.use('/', indexRouter);
app.use('/about', aboutRouter); app.use('/about', aboutRouter);
app.use('/hrt', hrtRouter);
app.use('/register', registerRouter); app.use('/register', registerRouter);
app.use('/login', loginRouter); app.use('/login', loginRouter);
app.use('/profile', profileRouter); app.use('/profile', profileRouter);
@ -211,7 +208,7 @@ Might be better if she kicked off everything at once, and ran a while loop to ch
This runs once at server startup, and most startups will run fairly quickly so... Not worth it?*/ This runs once at server startup, and most startups will run fairly quickly so... Not worth it?*/
async function asyncKickStart(){ async function asyncKickStart(){
//Lettum fuckin' know wassup //Lettum fuckin' know wassup
console.log(`${config.instanceName}(Powered by Canopy ${package.canopyDisplayVersion}) is booting up!`); console.log(`${config.instanceName}(Powered by Canopy) is booting up!`);
//Run legacy migration //Run legacy migration
await migrationModel.ingestLegacyDump(); await migrationModel.ingestLegacyDump();

View file

@ -40,8 +40,6 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. %>
it was decided that the original cytube fork, fore.st, had been run past it's prime. In summer/fall 2024, work began on a it was decided that the original cytube fork, fore.st, had been run past it's prime. In summer/fall 2024, work began on a
replacement. The resulting software became <a href="https://git.ourfore.st/rainbownapkin/canopy">Canopy</a>, which was replacement. The resulting software became <a href="https://git.ourfore.st/rainbownapkin/canopy">Canopy</a>, which was
first used to run the ourfore.st instance in late 2025.</p> first used to run the ourfore.st instance in late 2025.</p>
<br>
<h2>Canopy Ver: <%= version %></h2>
</div> </div>
</div> </div>
</body> </body>

View file

@ -1,56 +0,0 @@
<%# Canopy - The next generation of stoner streaming software
Copyright (C) 2024-2025 Rainbownapkin and the TTN Community
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. %>
<!DOCTYPE html>
<html>
<head>
<%- include('partial/styles', {instance, user}); %>
<%- include('partial/csrfToken', {csrfToken}); %>
<link rel="stylesheet" type="text/css" href="css/about.css">
<title><%= instance %> - DIY HRT Archive</title>
</head>
<body>
<%- include('partial/navbar', {user}); %>
<div id="about-div">
<h1>Bowie's DIY HRT Archive</h1>
<div class="dynamic-container" id="about-text">
<br>
This page is an attempt at putting together everything I know about DIY HRT.
<br><br>
So far I have used Homebrew Sublingual Oil from Open Gate Labs with great results, and have received a small batch of raw estradoil from Dragon Ordnance.
<br><br>
I am currently in the process of figuring out brewing my own sublingual oil.
<br><br>
<a href="/hrt.zip"><h3>This zip file contains everything I know.</h3></a>
<br>
<span>You should probably use <a href="https://www.torproject.org/">TOR</a> or a <a href="https://mullvad.net">decent VPN</a> in either an <a href="https://tails.net">amnesiac OS</a> or <a href="https://qubes-os.org">dispoable VM.</a> Everything paid w/ either <a href="https://www.getmonero.org/">XMR</a> or <a href="https://mullvad.net/en/blog/sending-cash-use-our-new-address">cash by mail</a>.</span>
<br><br>
<span class="critical-danger-text">
This page is not intended to be a replacement for professional medical advice, merely an attempt at harm reduction for my friends.
It should be used at most as a starting point for research. Everyone's HRT experience, and really transition, are unique and individual journeys.
Take the time to do the best research you can, to make sure you're starting and continuing yours correctly.
</span>
<br><br>
Much love, and remember to take your meds!
<br><br>
&nbsp;&nbsp;&nbsp;-rainbownapkin &lt;3
<br>&nbsp;
</div>
</div>
</body>
<footer>
<%- include('partial/scripts', {user}); %>
</footer>
</html>

View file

@ -337,16 +337,13 @@ function onYouTubeIframeAPIReady(){
//Set embed api to true //Set embed api to true
client.ytEmbedAPILoaded = true; client.ytEmbedAPILoaded = true;
//If the player is ready and has a mediaHandler loaded //Get currently playing item
if(client.player != null && client.player.mediaHandler != null){ const nowPlaying = client.player.mediaHandler.nowPlaying;
//Get currently playing item
const nowPlaying = client.player.mediaHandler.nowPlaying;
//If we're playing a youtube video and the official embeds are enabled //If we're playing a youtube video and the official embeds are enabled
if(nowPlaying.type == 'yt' && localStorage.getItem('ytPlayerType') == "embed"){ if(nowPlaying.type == 'yt' && localStorage.getItem('ytPlayerType') == "embed"){
//Restart the video now that the embed api has loaded //Restart the video now that the embed api has loaded
client.player.start({media: nowPlaying}); client.player.start({media: nowPlaying});
}
} }
} }

View file

@ -140,11 +140,10 @@ class chatPostprocessor{
this.messageArray = []; this.messageArray = [];
//Unescape any sanatized char codes as we use .textContent for double-safety, and to prevent splitting of char codes //Unescape any sanatized char codes as we use .textContent for double-safety, and to prevent splitting of char codes
//Split string by word-boundries on words and non-word boundries around whitespace, //Split string by word-boundries on words and non-word boundries around whitespace, with negative lookaheads to exclude file seperators so we don't split link placeholders, and dashes so we dont split usernames and other things
//with negative lookaheads to exclude file seperators so we don't split link placeholders, dashes so we dont split usernames and other things, and accented characters to keep those from splitting boundries too
//Also split by any invisble whitespace as a crutch to handle mushed links/emotes //Also split by any invisble whitespace as a crutch to handle mushed links/emotes
//If we can one day figure out how to split non-repeating special chars instead of special chars with whitespace, that would be perf, unfortunately my brain hasn't rotted enough to understand regex like that just yet. //If we can one day figure out how to split non-repeating special chars instead of special chars with whitespace, that would be perf, unfortunately my brain hasn't rotted enough to understand regex like that just yet.
const splitString = utils.unescapeEntities(this.rawData.msg).split(/(?<!-)(?<!␜)(?=\w)\b|(?!-|[\u00C0-\u017F])(?<=\w)\b|(?=\s)\B|(?<=\s)\B|/g); const splitString = utils.unescapeEntities(this.rawData.msg).split(/(?<!-)(?<!␜)(?=\w)\b|(?!-)(?<=\w)\b|(?=\s)\B|(?<=\s)\B|/g);
//for each word in the splitstring //for each word in the splitstring
splitString.forEach((string) => { splitString.forEach((string) => {
@ -260,7 +259,7 @@ class chatPostprocessor{
link.textContent = wordObj.command; link.textContent = wordObj.command;
//Add chatbox functionality //Add chatbox functionality
link.addEventListener('click', () => {this.client.chatBox.transmit(wordObj.command)}); link.addEventListener('click', () => {this.client.chatBox.commandPreprocessor.preprocess(wordObj.command)});
//We don't have to worry about injecting this into whitespace since there shouldn't be any here. //We don't have to worry about injecting this into whitespace since there shouldn't be any here.
injectionArray.push(link); injectionArray.push(link);

View file

@ -269,8 +269,7 @@ class commandPreprocessor{
usernames:{ usernames:{
prefix: '', prefix: '',
postfix: '', postfix: '',
//cmds: injectPerms(Array.from(client.userList.colorMap.keys())) cmds: injectPerms(Array.from(client.userList.colorMap.keys()))
cmds: injectPerms(client.userList.getOnlineUserNames())
}, },
emotes:{ emotes:{
prefix:'[', prefix:'[',

View file

@ -907,10 +907,6 @@ class hlsLiveStreamHandler extends hlsBase{
return; return;
} }
//Resize chat box to video aspect, since this is the only event thats reliably called on ratio change
//Re-enforcing UX rules a little more often shouldnt cause too many issues anywho.
this.client.chatBox.resizeAspect();
//Calculate distance to end of stream //Calculate distance to end of stream
const difference = this.video.duration - this.video.currentTime; const difference = this.video.duration - this.video.currentTime;

View file

@ -24,7 +24,7 @@ class pmPanel extends panelObj{
* @param {channel} client - Parent client Management Object * @param {channel} client - Parent client Management Object
* @param {Document} panelDocument - Panel Document * @param {Document} panelDocument - Panel Document
*/ */
constructor(client, panelDocument, startSesh){ constructor(client, panelDocument){
super(client, "Private Messaging", "/panel/pm", panelDocument); super(client, "Private Messaging", "/panel/pm", panelDocument);
/** /**
@ -71,17 +71,7 @@ class pmPanel extends panelObj{
//Tell PMHandler to start tracking this panel //Tell PMHandler to start tracking this panel
this.client.pmHandler.panelList.set(this.uuid, null); this.client.pmHandler.panelList.set(this.uuid, null);
//Define network related listeners
this.defineListeners(); this.defineListeners();
//If a start sesh was provided
if(startSesh != null && startSesh != ""){
//Send message out to server
this.client.pmSocket.emit("pm", {
recipients: startSesh.split(" "),
msg: ""
});
}
} }
closer(){ closer(){
@ -136,6 +126,7 @@ class pmPanel extends panelObj{
this.seshSendButton.addEventListener("click", this.send.bind(this)); this.seshSendButton.addEventListener("click", this.send.bind(this));
this.seshBuffer.addEventListener('scroll', this.scrollHandler.bind(this)); this.seshBuffer.addEventListener('scroll', this.scrollHandler.bind(this));
this.ownerDoc.defaultView.addEventListener('resize', this.handleAutoScroll.bind(this)); this.ownerDoc.defaultView.addEventListener('resize', this.handleAutoScroll.bind(this));
} }
startSesh(event){ startSesh(event){
@ -189,12 +180,6 @@ class pmPanel extends panelObj{
* Render out current sesh array to sesh list UI * Render out current sesh array to sesh list UI
*/ */
renderSeshList(){ renderSeshList(){
//If we don't have a sesh list
if(this.seshList == null){
//Fuck off, you're not even done building the object yet.
return;
}
//Clear out the sesh list //Clear out the sesh list
this.seshList.innerHTML = ""; this.seshList.innerHTML = "";

View file

@ -32,12 +32,6 @@ class userList{
* Click Dragger object for handling userlist resizes * Click Dragger object for handling userlist resizes
*/ */
this.clickDragger = new canopyUXUtils.clickDragger("#chat-panel-users-drag-handle", "#chat-panel-users-div", true, this.client.chatBox.clickDragger); this.clickDragger = new canopyUXUtils.clickDragger("#chat-panel-users-drag-handle", "#chat-panel-users-div", true, this.client.chatBox.clickDragger);
/**
* List of currently connected users
*/
this.userList = [];
/** /**
* Userlist color array (Maps to css classes) * Userlist color array (Maps to css classes)
@ -52,7 +46,7 @@ class userList{
"userlist-color6"]; "userlist-color6"];
/** /**
* Map of connected and buffered usernames to assigned username color/flair * Map of usernames to assigned username color
*/ */
this.colorMap = new Map(); this.colorMap = new Map();
@ -64,7 +58,7 @@ class userList{
/** /**
* userlist div * userlist div
*/ */
this.userListDiv = document.querySelector("#chat-panel-users-list-div"); this.userList = document.querySelector("#chat-panel-users-list-div");
/** /**
* user count label * user count label
@ -109,28 +103,28 @@ class userList{
updateList(list){ updateList(list){
//Clear list and set user count //Clear list and set user count
this.userCount.textContent = list.length == 1 ? '1 User' : `${list.length} Users`; this.userCount.textContent = list.length == 1 ? '1 User' : `${list.length} Users`;
this.userListDiv.innerHTML = null; this.userList.innerHTML = null;
//Set userlist array to received list //create a new map
this.userList = list; var newMap = new Map();
//for each user //for each user
list.forEach((user) => { list.forEach((user) => {
//randomly pick a color //randomly pick a color
var color = this.userColors[Math.floor(Math.random()*this.userColors.length)] var color = this.userColors[Math.floor(Math.random()*this.userColors.length)]
//if this user wasn't in the previous colormap //if this user was in the previous colormap
if(this.colorMap.get(user.user) == null){ if(this.colorMap.get(user.user) != null){
//Store new randomly selected color //Override with previous color
this.colorMap.set(user.user, color);
}else{
//Use color from previous entry
color = this.colorMap.get(user.user); color = this.colorMap.get(user.user);
} }
newMap.set(user.user, color);
this.renderUser(user, color); this.renderUser(user, color);
}); });
this.colorMap = newMap;
//Make sure we're not cutting the ux off //Make sure we're not cutting the ux off
this.clickDragger.fixCutoff(); this.clickDragger.fixCutoff();
} }
@ -174,13 +168,12 @@ class userList{
userSpan.addEventListener('click', renderContextMenu.bind(this)); userSpan.addEventListener('click', renderContextMenu.bind(this));
userSpan.addEventListener('contextmenu', renderContextMenu.bind(this)); userSpan.addEventListener('contextmenu', renderContextMenu.bind(this));
this.userListDiv.appendChild(userSpan); this.userList.appendChild(userSpan);
function renderContextMenu(event){ function renderContextMenu(event){
//Setup menu map //Setup menu map
let menuMap = new Map([ let menuMap = new Map([
["Profile", ()=>{this.client.cPanel.setActivePanel(new panelObj(this.client, user.user, `/panel/profile?user=${user.user}`))}], ["Profile", ()=>{this.client.cPanel.setActivePanel(new panelObj(this.client, `${user.user}`, `/panel/profile?user=${user.user}`))}],
["PM", ()=>{this.client.cPanel.setActivePanel(new pmPanel(client, undefined, user.user))}],
["Mention", ()=>{this.client.chatBox.catChat(`${user.user} `)}], ["Mention", ()=>{this.client.chatBox.catChat(`${user.user} `)}],
["Toke With", ()=>{this.client.chatBox.tokeWith(user.user)}], ["Toke With", ()=>{this.client.chatBox.tokeWith(user.user)}],
]); ]);
@ -217,14 +210,4 @@ class userList{
} }
} }
//returns list of strings containing all currently online users
getOnlineUserNames(){
var names = [];
for(let user of this.userList){
names.push(user.user);
}
return names;
}
} }

View file

@ -198,7 +198,7 @@ class rankList{
imgNode.src = user.img; imgNode.src = user.img;
//If the listed user rank is equal or higher than the signed-in user //If the listed user rank is equal or higher than the signed-in user
if(curUser != null && rankEnum.indexOf(user.rank) >= rankEnum.indexOf(curUser.rank)){ if(rankEnum.indexOf(user.rank) >= rankEnum.indexOf(curUser.rank)){
var rankContent = user.rank; var rankContent = user.rank;
}else{ }else{
//Create rank select //Create rank select