Changed out spliced ID's for dataset in admin panel. Removed auto-generated documentation from build step.

This commit is contained in:
rainbow napkin 2025-09-07 09:43:45 -04:00
parent cbd2136ca6
commit 3ab6c6c715
151 changed files with 31 additions and 142267 deletions

View file

@ -20,8 +20,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. %>
<% Object.keys(permList).forEach((key)=>{ %>
<% if(key != "channelOverrides"){ %>
<span class="admin-list-field-container">
<label id="admin-perm-list-label-<%- key %>" class="admin-list-label admin-perm-list" for="admin-perm-list-rank-select-<%- key %>"><%- key %>: </label>
<select id="admin-perm-list-rank-select-<%- key %>" name="admin-perm-list-rank-select-<%- key %>" class="admin-list-select admin-perm-list-rank-select">
<label class="admin-list-label admin-perm-list" for="admin-perm-list-rank-select-<%- key %>"><%- key %>: </label>
<select name="admin-perm-list-rank-select-<%- key %>" data-key="<%- key %>" class="admin-list-select admin-perm-list-rank-select">
<%rankEnum.slice().reverse().forEach((rank)=>{ %>
<option <%if(permList[key] == rank){%> selected <%}%> value="<%- rank %>"><%- rank %></option>
<% }); %>
@ -33,8 +33,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. %>
<% Object.keys(permList.channelOverrides).forEach((key)=>{ %>
<% if(key != "channelOverrides"){ %>
<span class="admin-list-field-container">
<label id="admin-chan-perm-list-label-<%- key %>" class="admin-list-label admin-chan-perm-list" for="admin-chan-perm-list-rank-select-<%- key %>"><%- key %>: </label>
<select id="admin-chan-perm-list-rank-select-<%- key %>" name="admin-chan-perm-list-rank-select-<%- key %>" class="admin-list-select admin-chan-perm-list-rank-select">
<label class="admin-list-label admin-chan-perm-list" for="admin-chan-perm-list-rank-select-<%- key %>"><%- key %>: </label>
<select name="admin-chan-perm-list-rank-select-<%- key %>" data-key="<%- key %>" class="admin-list-select admin-chan-perm-list-rank-select">
<%rankEnum.slice().reverse().forEach((rank)=>{ %>
<option <%if(permList.channelOverrides[key] == rank){%> selected <%}%> value="<%- rank %>"><%- rank %></option>
<% }); %>

View file

@ -41,23 +41,23 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. %>
</td>
</tr>
<% userList.forEach((curUser) => { %>
<tr id="admin-user-list-entry-<%- curUser.user %>" class="admin-list-entry">
<td id="admin-user-list-entry-img-<%- curUser.user %>" class="admin-list-entry-item">
<tr id="admin-user-list-entry-<%- curUser.user %>" class="admin-list-entry" data-name="<%- curUser.user %>" >
<td class="admin-list-entry-item">
<a href="/profile/<%- curUser.user %>" class="admin-list-entry-item">
<img id="admin-user-list-entry-img-<%- curUser.user %>" class="admin-list-entry-item" src="<%- curUser.img %>">
<img class="admin-list-entry-item" src="<%- curUser.img %>">
</a>
</td>
<td id="admin-user-list-entry-id-<%- curUser.user %>" class="admin-list-entry-item not-first-col">
<td class="admin-list-entry-item not-first-col">
<a href="/profile/<%- curUser.user %>" class="admin-list-entry-item">
<%- curUser.id %>
</a>
</td>
<td id="admin-user-list-entry-name-<%- curUser.user %>" class="admin-list-entry-item not-first-col">
<a href="/profile/<%- curUser.user %>" class="admin-list-entry-item admin-user-list-name" id="admin-user-list-name-<%- curUser.user %>">
<td class="admin-list-entry-item not-first-col">
<a href="/profile/<%- curUser.user %>" class="admin-list-entry-item admin-user-list-name">
<%- curUser.user %>
</a>
</td>
<td id="admin-user-list-entry-rank-<%- curUser.user %>" class="admin-list-entry-item not-first-col">
<td class="admin-list-entry-item not-first-col">
<% if(rankEnum.indexOf(curUser.rank) < rankEnum.indexOf(user.rank)){%>
<select id="admin-user-list-rank-select-<%- curUser.user %>" class="admin-user-list-rank-select">
<%rankEnum.slice().reverse().forEach((rank)=>{ %>
@ -68,15 +68,15 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. %>
<%- curUser.rank %>
<% } %>
</td>
<td id="admin-user-list-entry-email-<%- curUser.user %>" class="admin-list-entry-item not-first-col">
<td class="admin-list-entry-item not-first-col">
<%- curUser.email ? curUser.email : "N/A" %>
</td>
<td id="admin-user-list-entry-date-<%- curUser.user %>" class="admin-list-entry-item not-first-col">
<td class="admin-list-entry-item not-first-col">
<%- curUser.date.toUTCString() %>
</td>
<td id="admin-user-list-entry-action-<%- curUser.user %>" class="admin-list-entry-item not-first-col">
<td class="admin-list-entry-item not-first-col">
<%# It's either this or add whitespce >:( %>
<i class="bi-radioactive admin-user-list-icon admin-user-list-nuke-icon" id="admin-user-list-nuke-icon-<%- curUser.user %>" title="Nuke Account: <%- curUser.user %>"></i><i class="bi-fire admin-user-list-icon admin-user-list-ban-icon" id="admin-user-list-ban-icon-<%- curUser.user %>" title="Ban User: <%- curUser.user %>"></i><i class="bi-arrow-clockwise admin-user-list-icon admin-user-list-pw-reset-icon" id="admin-user-list-pw-reset-icon-<%- curUser.user %>" title="Generate Password Reset Link for <%- curUser.user %>"></i>
<i class="bi-radioactive admin-user-list-icon admin-user-list-nuke-icon" title="Nuke Account: <%- curUser.user %>"></i><i class="bi-fire admin-user-list-icon admin-user-list-ban-icon" title="Ban User: <%- curUser.user %>"></i><i class="bi-arrow-clockwise admin-user-list-icon admin-user-list-pw-reset-icon" title="Generate Password Reset Link for <%- curUser.user %>"></i>
</td>
</tr>
<% }); %>

View file

@ -1,884 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Class: addURLPopup</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Class: addURLPopup</h1>
<section>
<header>
<h2><span class="attribs"><span class="type-signature"></span></span>addURLPopup<span class="signature">(event, playlist, location, client, doc)</span><span class="type-signature"></span></h2>
<div class="class-description">Class representing pop-up dialogue which adds media to a given playlist</div>
</header>
<article>
<div class="container-overview">
<h2>Constructor</h2>
<h4 class="name" id="addURLPopup"><span class="type-signature"></span>new addURLPopup<span class="signature">(event, playlist, location, client, doc)</span><span class="type-signature"></span></h4>
<div class="description">
Instantiates a new Add URL Pop-up
</div>
<h5>Parameters:</h5>
<table class="params">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th class="last">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="name"><code>event</code></td>
<td class="type">
<span class="param-type">Event</span>
</td>
<td class="description last">Event passed down from Event Listener</td>
</tr>
<tr>
<td class="name"><code>playlist</code></td>
<td class="type">
<span class="param-type">String</span>
</td>
<td class="description last">Playlist name</td>
</tr>
<tr>
<td class="name"><code>location</code></td>
<td class="type">
<span class="param-type">String</span>
</td>
<td class="description last">Location of playlist, either Channel or User</td>
</tr>
<tr>
<td class="name"><code>client</code></td>
<td class="type">
<span class="param-type"><a href="channel.html">channel</a></span>
</td>
<td class="description last">Parent Client Management Object</td>
</tr>
<tr>
<td class="name"><code>doc</code></td>
<td class="type">
<span class="param-type">Document</span>
</td>
<td class="description last">Current owner documnet of the panel, so we know where to drop our pop-up</td>
</tr>
</tbody>
</table>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_playlistManager.js.html">panels/queuePanel/playlistManager.js</a>, <a href="panels_queuePanel_playlistManager.js.html#line708">line 708</a>
</li></ul></dd>
</dl>
</div>
<h3 class="subsection-title">Members</h3>
<h4 class="name" id="client"><span class="type-signature"></span>client<span class="type-signature"></span></h4>
<div class="description">
Parent Client Management Object
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_playlistManager.js.html">panels/queuePanel/playlistManager.js</a>, <a href="panels_queuePanel_playlistManager.js.html#line721">line 721</a>
</li></ul></dd>
</dl>
<h4 class="name" id="location"><span class="type-signature"></span>location<span class="type-signature"></span></h4>
<div class="description">
Location of playlist, either Channel or User
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_playlistManager.js.html">panels/queuePanel/playlistManager.js</a>, <a href="panels_queuePanel_playlistManager.js.html#line731">line 731</a>
</li></ul></dd>
</dl>
<h4 class="name" id="playlist"><span class="type-signature"></span>playlist<span class="type-signature"></span></h4>
<div class="description">
Playlist Name
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_playlistManager.js.html">panels/queuePanel/playlistManager.js</a>, <a href="panels_queuePanel_playlistManager.js.html#line726">line 726</a>
</li></ul></dd>
</dl>
<h4 class="name" id="popup"><span class="type-signature"></span>popup<span class="type-signature"></span></h4>
<div class="description">
canopyUXUtils.popup() object
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_playlistManager.js.html">panels/queuePanel/playlistManager.js</a>, <a href="panels_queuePanel_playlistManager.js.html#line738">line 738</a>
</li></ul></dd>
</dl>
<h3 class="subsection-title">Methods</h3>
<h4 class="name" id="addToPlaylist"><span class="type-signature"></span>addToPlaylist<span class="signature">(event)</span><span class="type-signature"></span></h4>
<div class="description">
Handles sending request to add to a playlist to the server
</div>
<h5>Parameters:</h5>
<table class="params">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th class="last">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="name"><code>event</code></td>
<td class="type">
<span class="param-type">Event</span>
</td>
<td class="description last">Event passed down from Event Listener</td>
</tr>
</tbody>
</table>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_playlistManager.js.html">panels/queuePanel/playlistManager.js</a>, <a href="panels_queuePanel_playlistManager.js.html#line764">line 764</a>
</li></ul></dd>
</dl>
<h4 class="name" id="asyncConstructor"><span class="type-signature"></span>asyncConstructor<span class="signature">()</span><span class="type-signature"></span></h4>
<div class="description">
Continuation of object construction, called after child popup object construction
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_playlistManager.js.html">panels/queuePanel/playlistManager.js</a>, <a href="panels_queuePanel_playlistManager.js.html#line744">line 744</a>
</li></ul></dd>
</dl>
<h4 class="name" id="setupInput"><span class="type-signature"></span>setupInput<span class="signature">()</span><span class="type-signature"></span></h4>
<div class="description">
Defines input-related Event Handlers
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_playlistManager.js.html">panels/queuePanel/playlistManager.js</a>, <a href="panels_queuePanel_playlistManager.js.html#line754">line 754</a>
</li></ul></dd>
</dl>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="addURLPopup.html">addURLPopup</a></li><li><a href="cPanel.html">cPanel</a></li><li><a href="channel.html">channel</a></li><li><a href="chatBox.html">chatBox</a></li><li><a href="chatPostprocessor.html">chatPostprocessor</a></li><li><a href="clearPopup.html">clearPopup</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="defaultTitlesPopup.html">defaultTitlesPopup</a></li><li><a href="emotePanel.html">emotePanel</a></li><li><a href="hlsBase.html">hlsBase</a></li><li><a href="hlsLiveStreamHandler.html">hlsLiveStreamHandler</a></li><li><a href="mediaHandler.html">mediaHandler</a></li><li><a href="newPlaylistPopup.html">newPlaylistPopup</a></li><li><a href="nullHandler.html">nullHandler</a></li><li><a href="panelObj.html">panelObj</a></li><li><a href="player.html">player</a></li><li><a href="playlistManager.html">playlistManager</a></li><li><a href="poppedPanel.html">poppedPanel</a></li><li><a href="queuePanel.html">queuePanel</a></li><li><a href="rawFileBase.html">rawFileBase</a></li><li><a href="rawFileHandler.html">rawFileHandler</a></li><li><a href="renamePopup.html">renamePopup</a></li><li><a href="reschedulePopup.html">reschedulePopup</a></li><li><a href="schedulePopup.html">schedulePopup</a></li><li><a href="settingsPanel.html">settingsPanel</a></li><li><a href="userList.html">userList</a></li><li><a href="youtubeEmbedHandler.html">youtubeEmbedHandler</a></li></ul><h3>Global</h3><ul><li><a href="global.html#onYouTubeIframeAPIReady">onYouTubeIframeAPIReady</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Sat Sep 06 2025 19:07:57 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,385 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: channel.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: channel.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>/*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 &lt;https://www.gnu.org/licenses/>.*/
/**
* Class containing base code for the Canopy channel client.
*/
class channel{
/**
* Instantiates a new channel object
*/
constructor(){
//Establish connetion to the server via socket.io
this.connect();
//Define socket listeners
this.defineListeners();
/**
* Returns true once the ytEmbed API has loaded in from google (eww)
*/
this.ytEmbedAPILoaded = false;
/**
* Current connected channels name
*/
this.channelName = window.location.pathname.split('/c/')[1].split('/')[0];
/**
* Child Video Player object
*/
this.player = new player(this);
/**
* Child Chat Box Object
*/
this.chatBox = new chatBox(this);
/**
* Child User List Object
*/
this.userList = new userList(this);
/**
* Child Canopy Panel Object
*/
this.cPanel = new cPanel(this);
//Set defaults for any unset settings and run any required process steps for the current config
this.setDefaults(false, true);
//Freak out any weirdos who take a peek in the dev console for shits n gigs
console.log("👁️👄👁️ 𝓊𝓃𝒿𝓊𝓇.");
}
/**
* Handles initial client connection
*/
connect(){
this.socket = io({
extraHeaders: {
//Include CSRF token
'x-csrf-token': utils.ajax.getCSRFToken()
}
});
}
/**
* Defines network-related listeners
*/
defineListeners(){
this.socket.on("connect", () => {
document.title = `${this.channelName} - Connected`
});
this.socket.on("kick", async (data) => {
if(data.reason == "Invalid CSRF Token!"){
//Reload the CSRF token
await utils.ajax.reloadCSRFToken();
//Retry the connection
this.connect();
}else{
new canopyUXUtils.popup(`You have been ${data.type} from the channel for the following reason:&lt;br>${data.reason}`);
}
});
this.socket.on("clientMetadata", this.handleClientInfo.bind(this));
this.socket.on("error", utils.ux.displayResponseError);
this.socket.on("queue", (data) => {
this.queue = new Map(data.queue);
});
this.socket.on("lock", (data) => {
this.queueLock = data.locked;
});
}
/**
* Handles initial client-metadata ingestion from server upon connection
* @param {Object} data - Data glob from server
*/
handleClientInfo(data){
//Ingest user data
this.user = data.user;
//Re-hydrate permission maps
this.user.permMap.site = new Map(data.user.permMap.site);
this.user.permMap.chan = new Map(data.user.permMap.chan);
//Tell the chatbox to handle client info
//should it have its own event listener instead? Guess it's a stylistic choice :P
this.chatBox.handleClientInfo(data);
//Store queue for use by the queue panel
this.queue = new Map(data.queue);
//Store queue lock status
this.queueLock = data.queueLock;
//For each chat held in the chat buffer
for(let chat of data.chatBuffer){
//Display the chat
this.chatBox.displayChat(chat);
}
}
/**
* Processes and applies default config on any unset settings
* @param {Boolean} force - Whether or not to forcefully reset already set settings
* @param {Boolean} processConfig - Whether or not to run the Process Config function once complete
*/
setDefaults(force = false, processConfig = false){
//Iterate through default config
for(let [key, value] of channel.defaultConfig){
//If the setting is unset or function was called forcefully
if(force || localStorage.getItem(key) == null){
//Set item from default map
localStorage.setItem(key, value);
}
//If we're running process steps for the config
if(processConfig){
//Process the current config value
this.processConfig(key, localStorage.getItem(key));
}
}
}
/**
* Run once every config change to ensure settings are properly set
* @param {String} key - Setting to change
* @param {*} value - Value to set setting to
*/
processConfig(key, value){
//Unfortunately we can't scope constants to switch-cases so this is the best we got if we wanna re-use the name
let nowPlaying;
//Switch/case by config key
switch(key){
case 'ytPlayerType':
const embedScript = document.querySelector(".yt-embed-api");
//If the user is running the embedded player and we don't have en embed script loaded
if(value == 'embed' &amp;&amp; embedScript == null){
//Find our footer
const footer = document.querySelector('footer');
//Create new script tag
const ytEmbedAPI = document.createElement('script');
//Link the iframe api from youtube
ytEmbedAPI.src = "https://www.youtube.com/iframe_api";
//set the iframe api script id
ytEmbedAPI.classList.add('yt-embed-api');
//Append the script tag to the top of the footer to give everything else access
footer.prepend(ytEmbedAPI);
//If we're not using the embed player but the script is loaded
}else if(embedScript != null){
//Pull all scripts since the main one might have pulled others
const scripts = document.querySelectorAll('script');
//Iterate through all script tags on the page
for(let script of scripts){
//If the script came from youtube
if(script.src.match(/youtube\.com|youtu\.be/)){
//Rip it out
script.remove();
}
}
}
//If the player or mediaHandler isn't loaded
if(this.player == null || this.player.mediaHandler == null){
//We're fuggin done here
return;
}
//Get current video
nowPlaying = this.player.mediaHandler.nowPlaying;
//If we're playing a youtube video
if(nowPlaying != null &amp;&amp; nowPlaying.type == 'yt'){
//Restart the video
this.player.hardReload();
}
//Stop while we're ahead
return;
case 'IACDN':
//If the player or mediaHandler isn't loaded
if(this.player == null || this.player.mediaHandler == null){
//We're fuggin done here
return;
}
//Get current video
nowPlaying = this.player.mediaHandler.nowPlaying;
//If we're playing a video from Internet Archive
if(nowPlaying != null &amp;&amp; nowPlaying.type == 'ia'){
//Hard reload the media, forcing media handler re-creation
this.player.hardReload();
}
return;
case 'syncTolerance':
//If the player isn't loaded
if(this.player == null){
//We're fuckin' done here
return;
}
//Set syncronization tolerance
this.player.syncTolerance = value;
return;
case 'liveSyncTolerance':
//If the player isn't loaded
if(this.player == null){
//We're fuckin' done here
return;
}
//Set syncronization tolerance
this.player.streamSyncTolerance = value;
return;
case 'syncDelta':
//If the player isn't loaded
if(this.player == null){
//We're fuckin' done here
return;
}
//Set syncronization delta
this.player.syncDelta = value;
return;
case 'chatWidthMin':
//If the chat isn't loaded
if(this.chatBox == null){
//We're fuckin' done here
return;
}
//Set Chat Box Width minimum while Locked to Aspect-Ratio
this.chatBox.chatWidthMinimum = value / 100;
return;
case 'userlistHidden':
//If the userlist class isn't loaded in yet
if(this.userList == null){
//We're fuckin' done here
return;
}
//Pass value down to UI toggle, making sure to allow for string conversion
this.userList.toggleUI(!(value == true || value == "true"));
return;
case 'cinemaMode':
//If the userlist class isn't loaded in yet
if(this.player == null){
//We're fuckin' done here
return;
}
//Pass value down to UI toggle, making sure to allow for string conversion
this.player.toggleCinemaMode(value == true || value == "true");
return;
}
}
/**
* Default channel config
*/
static defaultConfig = new Map([
["ytPlayerType","raw"],
["IACDN",""],
["syncTolerance",0.4],
["liveSyncTolerance", 2],
["syncDelta", 6],
["chatWidthMin", 20],
["userlistHidden", false],
["cinemaMode", false]
]);
}
/**
* Youtube iframe-embed API entry point
*/
function onYouTubeIframeAPIReady(){
//Set embed api to true
client.ytEmbedAPILoaded = true;
//Get currently playing item
const nowPlaying = client.player.mediaHandler.nowPlaying;
//If we're playing a youtube video and the official embeds are enabled
if(nowPlaying.type == 'yt' &amp;&amp; localStorage.getItem('ytPlayerType') == "embed"){
//Restart the video now that the embed api has loaded
client.player.start({media: nowPlaying});
}
}
const client = new channel();</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="addURLPopup.html">addURLPopup</a></li><li><a href="cPanel.html">cPanel</a></li><li><a href="channel.html">channel</a></li><li><a href="chatBox.html">chatBox</a></li><li><a href="chatPostprocessor.html">chatPostprocessor</a></li><li><a href="clearPopup.html">clearPopup</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="defaultTitlesPopup.html">defaultTitlesPopup</a></li><li><a href="emotePanel.html">emotePanel</a></li><li><a href="hlsBase.html">hlsBase</a></li><li><a href="hlsLiveStreamHandler.html">hlsLiveStreamHandler</a></li><li><a href="mediaHandler.html">mediaHandler</a></li><li><a href="newPlaylistPopup.html">newPlaylistPopup</a></li><li><a href="nullHandler.html">nullHandler</a></li><li><a href="panelObj.html">panelObj</a></li><li><a href="player.html">player</a></li><li><a href="playlistManager.html">playlistManager</a></li><li><a href="poppedPanel.html">poppedPanel</a></li><li><a href="queuePanel.html">queuePanel</a></li><li><a href="rawFileBase.html">rawFileBase</a></li><li><a href="rawFileHandler.html">rawFileHandler</a></li><li><a href="renamePopup.html">renamePopup</a></li><li><a href="reschedulePopup.html">reschedulePopup</a></li><li><a href="schedulePopup.html">schedulePopup</a></li><li><a href="settingsPanel.html">settingsPanel</a></li><li><a href="userList.html">userList</a></li><li><a href="youtubeEmbedHandler.html">youtubeEmbedHandler</a></li></ul><h3>Global</h3><ul><li><a href="global.html#onYouTubeIframeAPIReady">onYouTubeIframeAPIReady</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Sat Sep 06 2025 19:07:57 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

View file

@ -1,676 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: chat.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: chat.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>/*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 &lt;https://www.gnu.org/licenses/>.*/
/**
* Class which represents Canopy Chat Box UI
*/
class chatBox{
/**
* Instantiates a new Chat Box object
* @param {channel} client - Parent client Management Object
*/
constructor(client){
/**
* Parent Client Management Object
*/
this.client = client
/**
* Whether or not chat-size should be locked to current media aspect ratio
*/
this.aspectLock = true;
/**
* Whether or not the chat box should auto-scroll on new chat
*/
this.autoScroll = true;
/**
* Chat-Width Minimum while sized to media Aspect-Ratio
*/
this.chatWidthMinimum = localStorage.getItem('chatWidthMin') / 100;
/**
* Chat Buffer Scroll Top on last scroll
*/
this.lastPos = 0;
/**
* Height of Chat Buffer on last scroll
*/
this.lastHeight = 0;
/**
* Width of Chat Buffer on last scroll
*/
this.lastWidth = 0;
/**
* Click-Dragger Object for handling dynamic chat/video split re-sizing
*/
this.clickDragger = new canopyUXUtils.clickDragger("#chat-panel-drag-handle", "#chat-panel-div");
/**
* Command Pre-Processor Object
*/
this.commandPreprocessor = new commandPreprocessor(client);
/**
* Chat Post-Processor Object
*/
this.chatPostprocessor = new chatPostprocessor(client);
//Element Nodes
/**
* Chat Panel Container Div
*/
this.chatPanel = document.querySelector("#chat-panel-div");
/**
* High Level Selector
*/
this.highSelect = document.querySelector("#chat-panel-high-level-select");
/**
* Flair Selector
*/
this.flairSelect = document.querySelector("#chat-panel-flair-select");
/**
* Chat Buffer Div
*/
this.chatBuffer = document.querySelector("#chat-panel-buffer-div");
/**
* Chat Prompt
*/
this.chatPrompt = document.querySelector("#chat-panel-prompt");
/**
* Auto-Complete Placeholder
*/
this.autocompletePlaceholder = document.querySelector("#chat-panel-prompt-autocomplete-filler");
/**
* Auto-Complete Display
*/
this.autocompleteDisplay = document.querySelector("#chat-panel-prompt-autocomplete-display");
/**
* Settings Panel Icon
*/
this.settingsIcon = document.querySelector("#chat-panel-settings-icon");
/**
* Admin Panel Icon
*/
this.adminIcon = document.querySelector("#chat-panel-admin-icon");
/**
* Emote Icon
*/
this.emoteIcon = document.querySelector("#chat-panel-emote-icon");
/**
* Send Chat/Command Button
*/
this.sendButton = document.querySelector("#chat-panel-send-button");
/**
* Aspect Lock Icon
* Seems weird to stick this in here, but the split is dictated by chat width :P
*/
this.aspectLockIcon = document.querySelector("#media-panel-aspect-lock-icon");
/**
* Hide Chat Icon
*/
this.hideChatIcon = document.querySelector("#chat-panel-div-hide");
/**
* Show Chat Icon
*/
this.showChatIcon = document.querySelector("#media-panel-show-chat-icon");
//Setup functions
this.setupInput();
this.defineListeners();
this.sizeToAspect();
}
/**
* Defines input-related event listeners
*/
setupInput(){
//Chat bar
this.chatPrompt.addEventListener("keydown", this.send.bind(this));
this.chatPrompt.addEventListener("keydown", this.tabComplete.bind(this));
this.chatPrompt.addEventListener("input", this.displayAutocomplete.bind(this));
this.autocompleteDisplay.addEventListener("click", this.tabComplete.bind(this));
this.sendButton.addEventListener("click", this.send.bind(this));
this.settingsIcon.addEventListener("click", ()=>{this.client.cPanel.setActivePanel(new settingsPanel(client))});
this.adminIcon.addEventListener("click", ()=>{this.client.cPanel.setActivePanel(new queuePanel(client))});
this.emoteIcon.addEventListener("click", ()=>{this.client.cPanel.setActivePanel(new emotePanel(client))});
//Header icons
this.aspectLockIcon.addEventListener("click", this.lockAspect.bind(this));
this.showChatIcon.addEventListener("click", ()=>{this.toggleUI()});
this.hideChatIcon.addEventListener("click", ()=>{this.toggleUI()});
this.highSelect.addEventListener("change", this.setHighLevel.bind(this));
this.flairSelect.addEventListener("change", this.setFlair.bind(this));
//Clickdragger/Resize
this.clickDragger.handle.addEventListener("mousedown", this.unlockAspect.bind(this));
this.clickDragger.handle.addEventListener("clickdrag", this.handleAutoScroll.bind(this));
window.addEventListener("resize", this.resizeAspect.bind(this));
//chatbuffer
this.chatBuffer.addEventListener('scroll', this.scrollHandler.bind(this));
}
/**
* Defines network-related event listners
*/
defineListeners(){
this.client.socket.on("chatMessage", this.displayChat.bind(this));
this.client.socket.on("clearChat", this.clearChat.bind(this));
}
/**
* Clears chat on command from server
* @param {Object} data - Data from server
*/
clearChat(data){
//If we where passed a user to check
if(data.user != null){
var clearedChats = document.querySelectorAll(`.chat-entry-${data.user}`);
}else{
var clearedChats = document.querySelectorAll('.chat-entry');
}
//For each chat found
clearedChats.forEach((chat) => {
//fuckin' nukem!
chat.remove();
});
}
/**
* Receives, Post-Processes, and Displays chat messages from server
* @param {Object} data De-hydrated chat object from server
*/
displayChat(data){
//Create chat-entry span
var chatEntry = document.createElement('span');
chatEntry.classList.add("chat-panel-buffer","chat-entry",`chat-entry-${data.user}`);
//Create high-level label
var highLevel = document.createElement('p');
highLevel.classList.add("chat-panel-buffer","chat-entry-high-level","high-level");
highLevel.textContent = utils.unescapeEntities(`${data.highLevel}`);
chatEntry.appendChild(highLevel);
//If we're not using classic flair
if(data.flair != "classic"){
//Use flair
var flair = `flair-${data.flair}`;
//Otherwise
}else{
//Pull user's assigned color from the color map
var flair = this.client.userList.colorMap.get(data.user);
}
//Create username label
var userLabel = document.createElement('p');
userLabel.classList.add("chat-panel-buffer", "chat-entry-username", );
//Create color span
var flairSpan = document.createElement('span');
flairSpan.classList.add("chat-entry-flair-span", flair);
flairSpan.innerHTML = data.user;
//Inject flair span into user label before the colon
userLabel.innerHTML = `${flairSpan.outerHTML}: `;
//Append user label
chatEntry.appendChild(userLabel);
//Create chat body
var chatBody = document.createElement('p');
chatBody.classList.add("chat-panel-buffer","chat-entry-body");
chatEntry.appendChild(chatBody);
//Append the post-processed chat-body to the chat buffer
this.chatBuffer.appendChild(this.chatPostprocessor.postprocess(chatEntry, data));
//Set size to aspect on launch
this.resizeAspect();
}
/**
* Concatenate Text into Chat Prompt
* @param {String} text - Text to Concatenate
*/
catChat(text){
this.chatPrompt.value += text;
this.displayAutocomplete();
}
/**
* Calls a toke command out with a specified user
* @param {String} user - User to toke with
*/
tokeWith(user){
this.commandPreprocessor.preprocess(user == this.client.user.user ? "!toke up fuckers" : `!toke up ${user}`);
}
/**
* Pre-processes and sends text from chat prompt to server
* @param {Event} event - Event passed down from Event Handler
*/
send(event){
if((!event || !event.key || event.key == "Enter") &amp;&amp; this.chatPrompt.value){
this.commandPreprocessor.preprocess(this.chatPrompt.value);
//Clear our prompt and autocomplete nodes
this.chatPrompt.value = "";
this.autocompletePlaceholder.innerHTML = '';
this.autocompleteDisplay.innerHTML = '';
}
}
/**
* Displays auto-complete text against current prompt input
* @param {Event} event - Event passed down from Event Handler
*/
displayAutocomplete(event){
//Find current match
const match = this.checkAutocomplete();
//Set placeholder to space out the autocomplete display
//Use text content because it's unescaped, and while this only effects local users, it'll keep someone from noticing and whinging about it
this.autocompletePlaceholder.textContent = this.chatPrompt.value;
//Set the autocomplete display
this.autocompleteDisplay.textContent = match.match.replace(match.word, '');
}
/**
* Called upon tab-complete
* @param {Event} event - Event passed down from Event Handler
*/
tabComplete(event){
//If we hit tab or this isn't a keyboard event
if(event.key == "Tab" || event.key == null){
//Prevent default action
event.preventDefault();
//return focus to the chat prompt
this.chatPrompt.focus();
//Grab autocompletion match
const match = this.checkAutocomplete();
//If we have a match
if(match.match != ''){
//Autocomplete the current word
this.chatPrompt.value += match.match.replace(match.word, '');
//Clear out the autocomplete display
this.autocompleteDisplay.innerHTML = '';
}
}
}
/**
* Checks string input against auto-complete dictionary to generate the best guess as to what the user is typing
* @param {String} input - Current input from Chat Prompt
* @returns {Object} Object containing word we where handed and the match we found
*/
checkAutocomplete(input = this.chatPrompt.value){
//Rebuild this fucker every time because it really doesn't take that much compute power and emotes/used tokes change
//Worst case we could store it persistantly and update as needed but I think that might be much
const dictionary = this.commandPreprocessor.buildAutocompleteDictionary();
//Split our input by whitespace
const splitInput = input.split(/\s/g);
//Get the current word we're working on
const word = splitInput[splitInput.length - 1];
let matches = [];
//Run through dictionary sets
for(let set of Object.keys(dictionary)){
//Go through the current definitions of the current dictionary set
//I went with a for loop instead of a filter beacuse I wanted to pull the processed definition with pre/postfix
//and also directly push it into a shared array :P
for(let cmd of dictionary[set].cmds){
//Append the proper prefix/postfix to the current command
const definition = (`${dictionary[set].prefix}${cmd[0]}${dictionary[set].postfix}`);
//if definition starts with the current word and the command is enabled
if((word == '' ? false : definition.indexOf(word) == 0) &amp;&amp; cmd[1]){
//Add definition to match list
matches.push(definition);
}
}
}
//If we found jack shit
if(matches.length == 0){
//Return jack shit
return {
match: '',
word
};
//If we got something
}else{
//return our top match
return {
match: matches[0],
word
};
}
}
/**
* Handles initial client meta-data dump from server upon connection
* @param {Object} data - Data dump from server
*/
handleClientInfo(data){
this.updateFlairSelect(data.flairList, data.user.flair);
this.updateHighSelect(data.user.highLevel);
}
/**
* Sets user high-level
* @param {Event} event - Event passed down from Event Handler
*/
setHighLevel(event){
const highLevel = event.target.value;
this.client.socket.emit("setHighLevel", {highLevel});
}
/**
* Sets user flair
* @param {Event} event - Event passed down from Event Handler
*/
setFlair(event){
const flair = event.target.value;
this.client.socket.emit("setFlair", {flair});
}
/**
* Handles High-Level updates from the server
* @param {Number} highLevel - High Level to Set
*/
updateHighSelect(highLevel){
this.highSelect.value = highLevel;
}
/**
* Handles flair updates from the server
* @param {Array} fliarList - List of flairs to put into flair select
* @param {String} fliar - Flair to set
*/
updateFlairSelect(flairList, flair){
//clear current flair select
this.flairSelect.innerHTML = "";
//For each flair in flairlist
flairList.forEach((flair) => {
//Create an option
var flairOption = document.createElement('option');
//Set the name and innerHTML
flairOption.value = flair.name;
flairOption.textContent = utils.unescapeEntities(flair.displayName);
//Append it to the select
this.flairSelect.appendChild(flairOption);
});
//Set the selected flair in the UI
this.flairSelect.value = flair;
//Re-style the UI, do this in two seperate steps in-case we're running for the first time and have nothing to replace.
this.flairSelect.className = this.flairSelect.className.replace(/flair-\S*/, "");
this.flairSelect.classList.add(`flair-${flair}`);
}
/**
* Locks chat-size to aspect ratio of media
* @param {Event} event - Event passed down from Event Handler
*/
lockAspect(event){
//prevent the user from breaking shit :P
if(this.chatPanel.style.display != "none"){
this.aspectLock = true;
this.aspectLockIcon.style.display = "none";
this.sizeToAspect();
}
}
/**
* Un-locks chat-size to aspect ratio of media
* @param {Event} event - Event passed down from Event Handler
*/
unlockAspect(event){
//Disable aspect lock
this.aspectLock = false;
//Show aspect lock icon
this.aspectLockIcon.style.display = "inline";
}
/**
* Re-sizes chat back to aspect ratio on window re-size when chat box is aspect locked
* Also prevents horizontal scroll-bars from chat/window resizing
* @param {Event} event - Event passed down from Event Handler
*/
resizeAspect(event){
const playerHidden = this.client.player.playerDiv.style.display == "none";
//If the aspect is locked and the player is hidden
if(this.aspectLock &amp;&amp; !playerHidden){
this.sizeToAspect();
//Otherwise
}else{
//Fix the clickDragger on userlist
this.client.userList.clickDragger.fixCutoff();
}
//Autoscroll chat in-case we fucked it up
this.handleAutoScroll();
}
/**
* Re-sizes chat box relative to media aspect ratio
*/
sizeToAspect(){
if(this.chatPanel.style.display != "none"){
var targetVidWidth = this.client.player.getRatio() * this.chatPanel.getBoundingClientRect().height;
const targetChatWidth = window.innerWidth - targetVidWidth;
//This should be changeable in settings later on, for now it defaults to 20%
const limit = window.innerWidth * this.chatWidthMinimum;
//Set width to target or 20vw depending on whether or not we've hit the width limit
this.chatPanel.style.flexBasis = targetChatWidth > limit ? `${targetChatWidth}px` : `${this.chatWidthMinimum * 100}vw`;
//Fix busted layout
var pageBreak = document.body.scrollWidth - document.body.getBoundingClientRect().width;
this.chatPanel.style.flexBasis = `${this.chatPanel.getBoundingClientRect().width + pageBreak}px`;
//This sometimes gets called before userList ahs been initiated :p
if(this.client.userList != null){
this.client.userList.clickDragger.fixCutoff();
}
}
}
/**
* Toggles Chat Box UX
* @param {Boolean} show - Whether or not to show Chat Box UX
*/
toggleUI(show = !this.chatPanel.checkVisibility()){
if(show){
this.chatPanel.style.display = "flex";
this.showChatIcon.style.display = "none";
this.client.player.hideVideoIcon.style.display = "flex";
this.client.userList.clickDragger.fixCutoff();
}else{
this.chatPanel.style.display = "none";
this.showChatIcon.style.display = "flex";
this.client.player.hideVideoIcon.style.display = "none";
}
}
/**
* Handles Video Toggling
* @param {Boolean} show - Whether or not the video is currently being hidden
*/
handleVideoToggle(show){
//If we're enabling the video
if(show){
//Show hide chat icon
this.hideChatIcon.style.display = "flex";
//Re-enable the click dragger
this.clickDragger.enabled = true;
//Lock the chat to aspect ratio of the video, to make sure the chat width isn't breaking shit
this.lockAspect();
//If we're disabling the video
}else{
//Hide hide hide hide hide hide chat icon
this.hideChatIcon.style.display = "none";
//Need to clear the width from the split, or else it doesn't display properly
this.chatPanel.style.flexBasis = "100%";
//Disable the click dragger
this.clickDragger.enabled = false;
}
}
/**
* Handles scrolling within the chat buffer
* @param {Event} event - Event passed down from Event Handler
*/
scrollHandler(event){
//If we're just starting out
if(this.lastPos == 0){
//Set last pos for the first time
this.lastPos = this.chatBuffer.scrollTop;
}
//Calculate scroll delta
const deltaY = this.chatBuffer.scrollTop - this.lastPos;
//Grab visible bounding rect so we don't have to do it again (can't use offset because someone might zoom in :P)
const bufferRect = this.chatBuffer.getBoundingClientRect();
const bufferHeight = Math.round(bufferRect.height);
const bufferWidth = Math.round(bufferRect.width);
if(this.lastHeight == 0){
this.lastHeight = bufferHeight;
}
if(this.lastWidth == 0){
this.lastWidth = bufferWidth;
}
//If we're scrolling up
if(deltaY &lt; 0){
//If we have room to scroll, and we didn't resize
if(this.chatBuffer.scrollHeight > bufferHeight &amp;&amp; (this.lastWidth == bufferWidth &amp;&amp; this.lastHeight == bufferHeight)){
//Disable auto scrolling
this.autoScroll = false;
}else{
this.handleAutoScroll();
}
//Otherwise if the difference between the chat buffers scroll height and offset height is equal to the scroll top
//(Because it is scrolled all the way down)
}else if((this.chatBuffer.scrollHeight - bufferHeight) == this.chatBuffer.scrollTop){
this.autoScroll = true;
}
//Set last post/size for next the run
this.lastPos = this.chatBuffer.scrollTop;
this.lastHeight = bufferHeight;
this.lastWidth = bufferWidth;
}
/**
* Auto-scrolls chat buffer when new chats are entered.
*/
handleAutoScroll(){
//If autoscroll is enabled
if(this.autoScroll){
//Set chatBuffer scrollTop to the difference between scrollHeight and buffer height (scroll to the bottom)
this.chatBuffer.scrollTop = this.chatBuffer.scrollHeight - Math.round(this.chatBuffer.getBoundingClientRect().height);
}
}
}</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="addURLPopup.html">addURLPopup</a></li><li><a href="cPanel.html">cPanel</a></li><li><a href="channel.html">channel</a></li><li><a href="chatBox.html">chatBox</a></li><li><a href="chatPostprocessor.html">chatPostprocessor</a></li><li><a href="clearPopup.html">clearPopup</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="defaultTitlesPopup.html">defaultTitlesPopup</a></li><li><a href="emotePanel.html">emotePanel</a></li><li><a href="hlsBase.html">hlsBase</a></li><li><a href="hlsLiveStreamHandler.html">hlsLiveStreamHandler</a></li><li><a href="mediaHandler.html">mediaHandler</a></li><li><a href="newPlaylistPopup.html">newPlaylistPopup</a></li><li><a href="nullHandler.html">nullHandler</a></li><li><a href="panelObj.html">panelObj</a></li><li><a href="player.html">player</a></li><li><a href="playlistManager.html">playlistManager</a></li><li><a href="poppedPanel.html">poppedPanel</a></li><li><a href="queuePanel.html">queuePanel</a></li><li><a href="rawFileBase.html">rawFileBase</a></li><li><a href="rawFileHandler.html">rawFileHandler</a></li><li><a href="renamePopup.html">renamePopup</a></li><li><a href="reschedulePopup.html">reschedulePopup</a></li><li><a href="schedulePopup.html">schedulePopup</a></li><li><a href="settingsPanel.html">settingsPanel</a></li><li><a href="userList.html">userList</a></li><li><a href="youtubeEmbedHandler.html">youtubeEmbedHandler</a></li></ul><h3>Global</h3><ul><li><a href="global.html#onYouTubeIframeAPIReady">onYouTubeIframeAPIReady</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Sat Sep 06 2025 19:07:57 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,680 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: chatPostprocessor.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: chatPostprocessor.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>/*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 &lt;https://www.gnu.org/licenses/>.*/
/**
* Class contianing client-side message post-processing code
*/
class chatPostprocessor{
/**
* Instantiates a new Chat Post-Processor object
* @param {channel} client - Parent client Management Object
*/
constructor(client){
/**
* Parent Client Management Object
*/
this.client = client;
}
/**
* Post-Processes a single message from the server and returns a presntable DOM Node
* @param {Node} chatEntry - Chat entry generated by initial chatBox method
* @param {Object} rawData - Raw data from server
* @returns {Node} Post-Processed Chat Entry
*/
postprocess(chatEntry, rawData){
//Create empty array to hold filter spans
this.filterSpans = [];
//Set raw message data
this.rawData = rawData;
//Set current chat nodes
this.chatEntry = chatEntry;
this.chatBody = this.chatEntry.querySelector(".chat-entry-body");
//Split the chat message into an array of objects representing each word/chunk
this.splitMessage();
//Process Qoutes
this.processQoute();
//Re-Hydrate and Inject links and embedded media into un-processed placeholders
this.processLinks();
//Inject clickable command examples
this.processCommandExamples();
//Inject clickable channel names
this.processChannelNames();
//Inject clickable usernames
this.processUsernames();
//Detect inline spoilers
this.processSpoilers();
//Detect inline strikethrough
this.processStrikethrough();
//Detect inline bold text
this.processBold();
//Detect inline italics
this.processItalics();
//Inject whitespace into long ass-words
this.addWhitespace();
//Handle non-standard chat types
this.handleChatType();
//Inject the pre-processed chat hyper-text into the chatEntry node
this.injectBody();
//Return the pre-processed node
return this.chatEntry;
}
/**
* Splits message into an array of Word Objects for further processing
*/
splitMessage(){
//Create an empty array to hold the body
this.messageArray = [];
//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, with negative lookaheads to exclude file seperators so we don't split link placeholders, and dashes so we dont split usernames and other things
//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.
const splitString = utils.unescapeEntities(this.rawData.msg).split(/(?&lt;!-)(?&lt;!␜)(?=\w)\b|(?!-)(?&lt;=\w)\b|(?=\s)\B|(?&lt;=\s)\B|/g);
//for each word in the splitstring
splitString.forEach((string) => {
//create a word object
const wordObj = {
string: string,
filterClasses: [],
type: "word"
}
//Add it to our body array
this.messageArray.push(wordObj);
});
}
/**
* Injects word objects into chat-entry as proper DOM Nodes
*/
injectBody(){
//Create an empty array to hold the objects to inject
const injectionArray = [];
//For each word object
this.messageArray.forEach((wordObj) => {
if(wordObj.type == 'word'){
//Create span node
const span = document.createElement('span');
//Set span filter classes
span.classList.add(...wordObj.filterClasses);
//Set span text
span.textContent = wordObj.string;
//Inject node into array
injectionArray.push(span);
}else if(wordObj.type == 'link'){
//Create a link node from our link
const link = document.createElement('a');
link.classList.add('chat-link', ...wordObj.filterClasses);
link.href = wordObj.link;
link.target = "_blank";
//Use textContent to be safe since links can't be escaped serverside
link.textContent = wordObj.link;
//Append node to chatBody
combineNode(wordObj, link);
}else if(wordObj.type == 'deadLink'){
//Create a text span node from our link
const badLink = document.createElement('a');
badLink.classList.add('chat-dead-link', 'danger-link', ...wordObj.filterClasses);
badLink.href = wordObj.link;
badLink.target = "_blank";
//Use textContent to be safe since links can't be escaped serverside
badLink.textContent = wordObj.link;
//Append node to chatBody
combineNode(wordObj, badLink);
}else if(wordObj.type == 'malformedLink'){
//Create a text span node from our link
const malformedLink = document.createElement('span');
malformedLink.classList.add('chat-malformed-link', ...wordObj.filterClasses);
//Use textContent to be safe since links can't be escaped (this is why we don't just add it using injectString)
//arguably we could sanatize malformed links serverside since they're never actually used as links
malformedLink.textContent = wordObj.link;
//Append node to chatBody
combineNode(wordObj, malformedLink);
}else if(wordObj.type == 'image'){
//Create an img node from our link
const img = document.createElement('img');
img.classList.add('chat-img', ...wordObj.filterClasses);
img.src = wordObj.link;
//Look for an emote by link since emotes are tx'd as bare links
const emote = this.client.chatBox.commandPreprocessor.getEmoteByLink(wordObj.link);
//If this is a known emote
if(emote != null){
//Set the hover text to the emote's name
img.title = `[${emote.name}]`;
}
//Append node to chatBody
combineNode(wordObj, img);
}else if(wordObj.type == 'video'){
//Create a video node from our link
const vid = document.createElement('video');
vid.classList.add('chat-video', ...wordObj.filterClasses);
vid.src = wordObj.link;
vid.controls = false;
vid.autoplay = true;
vid.loop = true;
vid.muted = true;
//Look for an emote by link since emotes are tx'd as bare links
const emote = this.client.chatBox.commandPreprocessor.getEmoteByLink(wordObj.link);
//If this is a known emote
if(emote != null){
//Set the hover text to the emote's name
vid.title = `[${emote.name}]`;
}
combineNode(wordObj, vid);
}else if(wordObj.type == 'command'){
//Create link node
const link = document.createElement('a');
//Set class
link.classList.add('chat-link', ...wordObj.filterClasses);
//Set href and inner text
link.href = "javascript:";
link.textContent = wordObj.command;
//Add chatbox functionality
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.
injectionArray.push(link);
}else if(wordObj.type == "username"){
//Create link node
const link = document.createElement('a');
//set class
link.classList.add(wordObj.color, ...wordObj.filterClasses);
//Set href and inner text
link.href = "javascript:";
link.textContent = wordObj.string;
//add chatbox functionality
link.addEventListener('click', () => {this.client.chatBox.chatPrompt.value += `${wordObj.string} `});
//We don't have to worry about injecting this into whitespace since there shouldn't be any here.
injectionArray.push(link);
}else if(wordObj.type == "channel"){
//Create link node
const link = document.createElement('a');
//set class
link.classList.add('chat-link', ...wordObj.filterClasses);
//Set href and inner text
link.href = `/c/${wordObj.chan}`;
link.target = "_blank"
link.textContent = wordObj.string;
//We don't have to worry about injecting this into whitespace since there shouldn't be any here.
injectionArray.push(link);
}else{
console.warn("Unknown chat postprocessor word type:");
console.warn(wordObj);
}
});
//For each item found in the injection array
for(let itemIndex in injectionArray){
const item = injectionArray[itemIndex];
//Currently this doesnt support multiple overlapping span-type filters
//not a huge deal since we only have once (spoiler)
//All others can be applied per-node without any usability side effects
const curFilter = this.filterSpans.filter(filterFilters)[0];
let appendBody = this.chatBody;
//If we have a filter span
if(curFilter != null){
//If we're beggining the array
if(itemIndex == curFilter.index[0]){
//Create the span
appendBody = document.createElement('span');
//Label it for what it is
appendBody.classList.add(curFilter.class);
//Add it to the chat body
this.chatBody.appendChild(appendBody);
//Otherwise
}else{
//Use the existing span
appendBody = (this.chatBody.children[this.chatBody.children.length - 1]);
}
}
//Append the node to our chat body
appendBody.appendChild(item);
function filterFilters(filter){
//If the index is within the filter span
return filter.index[0] &lt;= itemIndex &amp;&amp; filter.index[1] >= itemIndex;
}
}
//Like string.replace except it actually injects the node so we can keep things like event handlers
function combineNode(wordObj, node, placeholder = '␜'){
//Split string by the placeholder so we can keep surrounding whitespace
const splitWord = wordObj.string.split(placeholder, 2);
//Create combined node
const combinedSpan = document.createElement('span');
//Add the first part of the text
combinedSpan.textContent = splitWord[0];
//Add in the requestd node
combinedSpan.appendChild(node);
//Finish it off with the last bit of text
combinedSpan.insertAdjacentText('beforeend', splitWord[1]);
//Add to injection array as three nested items to keep arrays lined up
injectionArray.push(combinedSpan);
}
}
/**
* Processes qouted text in chat
*/
processQoute(){
//If the message starts off with '>'
if(this.messageArray[0].string[0] == '>'){
this.chatBody.classList.add("qoute");
}
}
/**
* Processes clickable command examples in chat
*/
processCommandExamples(){
//for each word object in the body
this.messageArray.forEach((wordObj, wordIndex) => {
//if the word object hasn't been pre-processed elsewhere
if(wordObj.type == "word"){
//Get last char of current word
const lastChar = wordObj.string[wordObj.string.length - 1];
//if the last char is !
if(lastChar == '!' || lastChar == '/'){
//get next word
const nextWord = this.messageArray[wordIndex + 1];
//if we have another word
if(nextWord != null){
const command = lastChar + nextWord.string;
//Take out the command marker
this.messageArray[wordIndex].string = wordObj.string.slice(0,-1);
const commandObj = {
type: "command",
string: nextWord.string,
filterClasses: [],
command: command
}
this.messageArray[wordIndex + 1] = commandObj;
}
}
}
});
}
/**
* Processes clickable channel names in chat
*/
processChannelNames(){
//for each word object in the body
this.messageArray.forEach((wordObj, wordIndex) => {
//if the word object hasn't been pre-processed elsewhere
if(wordObj.type == "word"){
//Get last char of current word with slashes pounds
const lastChar = wordObj.string[wordObj.string.length - 1];
const secondLastChar = wordObj.string[wordObj.string.length - 2];
//if the last char is # and the second to last char isn't &amp; or # (avoid spoilers)
if(lastChar == '#' &amp;&amp; secondLastChar != '#'){
//get next word
const nextWord = this.messageArray[wordIndex + 1];
//if we have another word
if(nextWord != null){
//Take out the chan marker
this.messageArray[wordIndex].string = wordObj.string.slice(0,-1);
const commandObj = {
type: "channel",
string: lastChar + nextWord.string,
filterClasses: [],
chan: nextWord.string
}
this.messageArray[wordIndex + 1] = commandObj;
}
}
}
});
}
/**
* Processes clickable username callouts in chat
*/
processUsernames(){
//for each word object in the body
this.messageArray.forEach((wordObj, wordIndex) => {
//if the word object hasn't been pre-processed elsewhere
if(wordObj.type == "word"){
//Check for user and get their color
const color = this.client.userList.colorMap.get(wordObj.string);
//If the current word is the username of a connected user
if(color != null){
//Mark it as so
this.messageArray[wordIndex].type = "username";
//Store their color
this.messageArray[wordIndex].color = color;
}
}
});
}
/**
* Injects invisible whitespace in long-ass words to prevent fucking up the chat buffer size
*/
addWhitespace(){
//for each word object in the body
this.messageArray.forEach((wordObj, wordIndex) => {
//if the word object hasn't been pre-processed elsewhere
if(wordObj.type == "word"){
//Create an empty array to hold our word
var wordArray = [];
//For each character in the string of the current word object
this.messageArray[wordIndex].string.split("").forEach((char, charIndex) => {
//push the current character to the wordArray
wordArray.push(char);
//After eight characters
if(charIndex > 8){
//Push an invisible line-break character between every character
wordArray.push("");
}
});
//Join the wordArray into a single string, and use it to set the current wordObject's string
this.messageArray[wordIndex].string = wordArray.join("");
}
});
}
/**
* Searches for text in-between a specific delimiter and runs a given callback against it
*
* Internal command used by several text filters to prevent code re-writes
* @param {String} delimiter - delimiter to search string by
* @param {Function} cb - Callback function to run against found strings
* @returns {Array} - list of found instances of filter
*/
processFilter(delimiter, cb){
//Create empty array to hold spoilers (keep this seperate at first for internal function use)
const foundFilters = [];
//Spoiler detection stage
//For each word object in the message array
main: for(let wordIndex = 0; wordIndex &lt; this.messageArray.length; wordIndex++){
//Get the current word object
const wordObj = this.messageArray[wordIndex];
//If its a regular word and contains '##'
if(wordObj.type == 'word' &amp;&amp; wordObj.string.match(utils.escapeRegex(delimiter))){
//Crawl through detected spoilers
for(let spoiler of foundFilters){
//If the current word object is part of a detected spoiler
if(wordIndex == spoiler[0] || wordIndex == spoiler[1]){
//ignore it and continue on to the next word object
continue main;
}
}
//Crawl throw word objects after the current one
for(let endIndex = (wordIndex + 1); endIndex &lt; this.messageArray.length; endIndex++){
//Get the current end object
const endObj = this.messageArray[endIndex];
//If its a regular word and contains '##'
if(endObj.type == 'word' &amp;&amp; endObj.string.match(utils.escapeRegex(delimiter))){
//Setup the found filter array
const foundFilter = [wordIndex, endIndex];
//Scrape out delimiters
wordObj.string = wordObj.string.replaceAll(delimiter,'');
endObj.string = endObj.string.replaceAll(delimiter,'');
//Add it to the list of detected filters
foundFilters.push(foundFilter);
//Run the filter callback
cb(foundFilter)
//Break the nested end-detection loop
break;
}
}
}
}
return foundFilters;
}
/**
* Processes in-line spoilers
*/
processSpoilers(){
//Process spoilers using '##' delimiter
this.processFilter('##', (foundSpoiler)=>{
//For each found spoiler add it to the list of found filter spans
this.filterSpans.push({class: "spoiler", index: [foundSpoiler[0] + 1, foundSpoiler[1] - 1], delimiters: [foundSpoiler[0], foundSpoiler[1]]});
});
}
/**
* Processes in-line Strike-through
*/
processStrikethrough(){
//Process strikethrough's using '~~' delimiter
this.processFilter('~~', (foundStrikethrough)=>{
for(let wordIndex = foundStrikethrough[0]; wordIndex &lt; foundStrikethrough[1]; wordIndex++){
this.messageArray[wordIndex].filterClasses.push("strikethrough");
}
})
}
/**
* Processes in-line Bold/Strong text
*/
processBold(){
//Process strong text using '*' delimiter
this.processFilter('**', (foundStrikethrough)=>{
for(let wordIndex = foundStrikethrough[0]; wordIndex &lt; foundStrikethrough[1]; wordIndex++){
this.messageArray[wordIndex].filterClasses.push("bold");
}
})
}
/**
* Processes in-line Italics
*/
processItalics(){
//Process italics using '__' delimiter
this.processFilter('*', (foundStrikethrough)=>{
for(let wordIndex = foundStrikethrough[0]; wordIndex &lt; foundStrikethrough[1]; wordIndex++){
this.messageArray[wordIndex].filterClasses.push("italics");
}
})
}
/**
* Processes clickable links and embedded media
*/
processLinks(){
//If we don't have links
if(this.rawData.links == null){
//Don't bother
return;
}
//For every link received in this message
this.rawData.links.forEach((link, linkIndex) => {
//For every word obj in the message array
this.messageArray.forEach((wordObj, wordIndex) => {
//Check current wordobj for link (placeholder may contain whitespace with it)
if(wordObj.string.match(`␜${linkIndex}`)){
//Set current word object in the body array to the new link object
this.messageArray[wordIndex] = {
//Don't want to use a numbered placeholder to make this easier during body injection
//but we also don't want to clobber any surrounding whitespace
string: wordObj.string.replace(`␜${linkIndex}`, '␜'),
link: link.link,
type: link.type,
filterClasses: []
}
}
})
});
}
/**
* Marks chat nodes in-case of non-standard chat types
*/
handleChatType(){
if(this.rawData.type == "whisper"){
//add whisper class
this.chatBody.classList.add('whisper');
}else if(this.rawData.type == "announcement"){
//Squash the high-level
this.chatEntry.querySelector('.high-level').remove();
//Get the username and make it into an announcement title (little hacky but this *IS* postprocessing)
const userNode = this.chatEntry.querySelector('.chat-entry-username');
userNode.textContent = `${userNode.textContent.slice(0,-2)} Announcement`;
//Add/remove relevant classes
userNode.classList.remove('chat-entry-username');
userNode.classList.add('announcement-title');
this.chatBody.classList.add('announcement-body');
this.chatEntry.classList.add('announcement');
}else if(this.rawData.type == "toke"){
//Squash the high-level
this.chatEntry.querySelector('.high-level').remove();
//remove the username
this.chatEntry.querySelector('.chat-entry-username').remove();
//Add toke/tokewhisper class
this.chatBody.classList.add("toke");
}else if(this.rawData.type == "tokewhisper"){
//Squash the high-level
this.chatEntry.querySelector('.high-level').remove();
//remove the username
this.chatEntry.querySelector('.chat-entry-username').remove();
//Add toke/tokewhisper class
this.chatBody.classList.add("tokewhisper","serverwhisper");
}else if(this.rawData.type == "spoiler"){
//Set whole-body spoiler
this.chatBody.classList.add("spoiler");
}else if(this.rawData.type == "strikethrough"){
//Set whole-body spoiler
this.chatBody.classList.add("strikethrough");
}else if(this.rawData.type == "bold"){
//Set whole-body spoiler
this.chatBody.classList.add("bold");
}else if(this.rawData.type == "italics"){
//Set whole-body spoiler
this.chatBody.classList.add("italics");
}
}
}</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="addURLPopup.html">addURLPopup</a></li><li><a href="cPanel.html">cPanel</a></li><li><a href="channel.html">channel</a></li><li><a href="chatBox.html">chatBox</a></li><li><a href="chatPostprocessor.html">chatPostprocessor</a></li><li><a href="clearPopup.html">clearPopup</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="defaultTitlesPopup.html">defaultTitlesPopup</a></li><li><a href="emotePanel.html">emotePanel</a></li><li><a href="hlsBase.html">hlsBase</a></li><li><a href="hlsLiveStreamHandler.html">hlsLiveStreamHandler</a></li><li><a href="mediaHandler.html">mediaHandler</a></li><li><a href="newPlaylistPopup.html">newPlaylistPopup</a></li><li><a href="nullHandler.html">nullHandler</a></li><li><a href="panelObj.html">panelObj</a></li><li><a href="player.html">player</a></li><li><a href="playlistManager.html">playlistManager</a></li><li><a href="poppedPanel.html">poppedPanel</a></li><li><a href="queuePanel.html">queuePanel</a></li><li><a href="rawFileBase.html">rawFileBase</a></li><li><a href="rawFileHandler.html">rawFileHandler</a></li><li><a href="renamePopup.html">renamePopup</a></li><li><a href="reschedulePopup.html">reschedulePopup</a></li><li><a href="schedulePopup.html">schedulePopup</a></li><li><a href="settingsPanel.html">settingsPanel</a></li><li><a href="userList.html">userList</a></li><li><a href="youtubeEmbedHandler.html">youtubeEmbedHandler</a></li></ul><h3>Global</h3><ul><li><a href="global.html#onYouTubeIframeAPIReady">onYouTubeIframeAPIReady</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Sat Sep 06 2025 19:07:57 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

View file

@ -1,799 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Class: clearPopup</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Class: clearPopup</h1>
<section>
<header>
<h2><span class="attribs"><span class="type-signature"></span></span>clearPopup<span class="signature">(event, client, cb, doc)</span><span class="type-signature"></span></h2>
<div class="class-description">Class represneting pop-up dialogue for clearing queue between a range of two dates</div>
</header>
<article>
<div class="container-overview">
<h2>Constructor</h2>
<h4 class="name" id="clearPopup"><span class="type-signature"></span>new clearPopup<span class="signature">(event, client, cb, doc)</span><span class="type-signature"></span></h4>
<div class="description">
Instantiates a new queue Clear Popup
</div>
<h5>Parameters:</h5>
<table class="params">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th class="last">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="name"><code>event</code></td>
<td class="type">
<span class="param-type">Event</span>
</td>
<td class="description last">Event passed down from Event Listener</td>
</tr>
<tr>
<td class="name"><code>client</code></td>
<td class="type">
<span class="param-type"><a href="channel.html">channel</a></span>
</td>
<td class="description last">Parent Client Management Object</td>
</tr>
<tr>
<td class="name"><code>cb</code></td>
<td class="type">
<span class="param-type">function</span>
</td>
<td class="description last">Callback function, passed upon pop-up creation</td>
</tr>
<tr>
<td class="name"><code>doc</code></td>
<td class="type">
<span class="param-type">Document</span>
</td>
<td class="description last">Current owner documnet of the panel, so we know where to drop our pop-up</td>
</tr>
</tbody>
</table>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_queuePanel.js.html">panels/queuePanel/queuePanel.js</a>, <a href="panels_queuePanel_queuePanel.js.html#line1554">line 1554</a>
</li></ul></dd>
</dl>
</div>
<h3 class="subsection-title">Members</h3>
<h4 class="name" id="cb"><span class="type-signature"></span>cb<span class="type-signature"></span></h4>
<div class="description">
Callback function, passed upon pop-up creation
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_queuePanel.js.html">panels/queuePanel/queuePanel.js</a>, <a href="panels_queuePanel_queuePanel.js.html#line1571">line 1571</a>
</li></ul></dd>
</dl>
<h4 class="name" id="client"><span class="type-signature"></span>client<span class="type-signature"></span></h4>
<div class="description">
Parent Client Management Object
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_queuePanel.js.html">panels/queuePanel/queuePanel.js</a>, <a href="panels_queuePanel_queuePanel.js.html#line1566">line 1566</a>
</li></ul></dd>
</dl>
<h4 class="name" id="popup"><span class="type-signature"></span>popup<span class="type-signature"></span></h4>
<div class="description">
canopyUXUtils.popup() object
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_queuePanel.js.html">panels/queuePanel/queuePanel.js</a>, <a href="panels_queuePanel_queuePanel.js.html#line1578">line 1578</a>
</li></ul></dd>
</dl>
<h3 class="subsection-title">Methods</h3>
<h4 class="name" id="asyncConstructor"><span class="type-signature"></span>asyncConstructor<span class="signature">()</span><span class="type-signature"></span></h4>
<div class="description">
Continuation of object construction, called after child popup object construction
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_queuePanel.js.html">panels/queuePanel/queuePanel.js</a>, <a href="panels_queuePanel_queuePanel.js.html#line1584">line 1584</a>
</li></ul></dd>
</dl>
<h4 class="name" id="clear"><span class="type-signature"></span>clear<span class="signature">(event)</span><span class="type-signature"></span></h4>
<div class="description">
Handles sending request to clear playlist between two dates to the server
</div>
<h5>Parameters:</h5>
<table class="params">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th class="last">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="name"><code>event</code></td>
<td class="type">
<span class="param-type">Event</span>
</td>
<td class="description last">Event passed down from Event Listener</td>
</tr>
</tbody>
</table>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_queuePanel.js.html">panels/queuePanel/queuePanel.js</a>, <a href="panels_queuePanel_queuePanel.js.html#line1624">line 1624</a>
</li></ul></dd>
</dl>
<h4 class="name" id="setupInput"><span class="type-signature"></span>setupInput<span class="signature">()</span><span class="type-signature"></span></h4>
<div class="description">
Defines input-related Event Handlers
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_queuePanel.js.html">panels/queuePanel/queuePanel.js</a>, <a href="panels_queuePanel_queuePanel.js.html#line1614">line 1614</a>
</li></ul></dd>
</dl>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="addURLPopup.html">addURLPopup</a></li><li><a href="cPanel.html">cPanel</a></li><li><a href="channel.html">channel</a></li><li><a href="chatBox.html">chatBox</a></li><li><a href="chatPostprocessor.html">chatPostprocessor</a></li><li><a href="clearPopup.html">clearPopup</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="defaultTitlesPopup.html">defaultTitlesPopup</a></li><li><a href="emotePanel.html">emotePanel</a></li><li><a href="hlsBase.html">hlsBase</a></li><li><a href="hlsLiveStreamHandler.html">hlsLiveStreamHandler</a></li><li><a href="mediaHandler.html">mediaHandler</a></li><li><a href="newPlaylistPopup.html">newPlaylistPopup</a></li><li><a href="nullHandler.html">nullHandler</a></li><li><a href="panelObj.html">panelObj</a></li><li><a href="player.html">player</a></li><li><a href="playlistManager.html">playlistManager</a></li><li><a href="poppedPanel.html">poppedPanel</a></li><li><a href="queuePanel.html">queuePanel</a></li><li><a href="rawFileBase.html">rawFileBase</a></li><li><a href="rawFileHandler.html">rawFileHandler</a></li><li><a href="renamePopup.html">renamePopup</a></li><li><a href="reschedulePopup.html">reschedulePopup</a></li><li><a href="schedulePopup.html">schedulePopup</a></li><li><a href="settingsPanel.html">settingsPanel</a></li><li><a href="userList.html">userList</a></li><li><a href="youtubeEmbedHandler.html">youtubeEmbedHandler</a></li></ul><h3>Global</h3><ul><li><a href="global.html#onYouTubeIframeAPIReady">onYouTubeIframeAPIReady</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Sat Sep 06 2025 19:07:57 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

File diff suppressed because it is too large Load diff

View file

@ -1,380 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: commandPreprocessor.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: commandPreprocessor.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>/*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 &lt;https://www.gnu.org/licenses/>.*/
/**
* Class containing chat and command pre-processing logic
*/
class commandPreprocessor{
/**
* Instantiates a new commandPreprocessor object
* @param {channel} client - Parent client Management Object
*/
constructor(client){
/**
* Parent Client Management object
*/
this.client = client;
/**
* Child Command Processor object
*/
this.commandProcessor = new commandProcessor(client);
/**
* Set of arrays containing site-wide, channel-wide, and user-specific emotes
*/
this.emotes = {
site: [],
chan: [],
personal: []
}
//define listeners
this.defineListeners();
}
/**
* Defines Network-Related Listeners
*/
defineListeners(){
//When we receive site-wide emote list
this.client.socket.on("siteEmotes", this.setSiteEmotes.bind(this));
this.client.socket.on("chanEmotes", this.setChanEmotes.bind(this));
this.client.socket.on("personalEmotes", this.setPersonalEmotes.bind(this));
this.client.socket.on("usedTokes", this.setUsedTokes.bind(this));
}
/**
* Pre-Processes a single chat/command before sending it off to the server
* @param {String} command - Chat/Command to pre-process
*/
preprocess(command){
//Set command and sendFlag
this.command = command;
this.sendFlag = true;
//Attempt to process as local command
this.processLocalCommand();
//If we made it through the local command processor
if(this.sendFlag){
//Set the message to the command
this.message = command;
//Process message emotes into links
this.processEmotes();
//Process unmarked links into marked links
this.processLinks();
//Send command off to server
this.sendRemoteCommand();
}
}
/**
* Processes local commands, starting with '/'
*/
processLocalCommand(){
//Create an empty array to hold the command
this.commandArray = [];
//Split string by words
this.commandArray = this.command.split(/\b/g);//Split by word-borders
this.argumentArray = this.command.match(/\b\w+\b/g);//Match by words surrounded by borders
//If this is a local command
if(this.commandArray[0] == '/'){
//If the command exists
if(this.argumentArray != null &amp;&amp; this.commandProcessor[this.argumentArray[0].toLowerCase()] != null){
//Don't send it to the server
this.sendFlag = false;
//Call the command with the argument array
this.commandProcessor[this.argumentArray[0].toLowerCase()](this.argumentArray, this.commandArray);
}
}
}
/**
* Processes emotes refrences in loaded message into links to be further processed by processLinks()
*/
processEmotes(){
//inject invisible whitespace in-between emotes to prevent from mushing links together
this.message = this.message.replaceAll('][','][');
//For each list of emotes
Object.keys(this.emotes).forEach((key) => {
//For each emote in the current list
this.emotes[key].forEach((emote) => {
//Inject emote links into the message, pad with invisible whitespace to keep link from getting mushed
this.message = this.message.replaceAll(`[${emote.name}]`, `${emote.link}`);
});
});
}
/**
* Processes links into numbered file seperators, putting links into a dedicated array.
*/
processLinks(){
//Strip out file seperators in-case the user is being a smart-ass
this.message = this.message.replaceAll('␜','');
//Split message by links
var splitMessage = this.message.split(/(https?:\/\/[^\s]+)/g);
//Create an empty array to hold links
this.links = [];
splitMessage.forEach((chunk, chunkIndex) => {
//For each chunk that is a link
if(chunk.match(/(https?:\/\/[^\s]+)/g)){
//I looked online for obscure characters that no one would use to prevent people from chatting embed placeholders
//Then I found this fucker, turns out it's literally made for the job lmao (even if it was originally intended for paper/magnetic tape)
//Replace link with indexed placeholder
splitMessage[chunkIndex] = `␜${this.links.length}`
//push current chunk as link
this.links.push(chunk);
}
});
//Join the message back together
this.message = splitMessage.join('');
}
/**
* Transmits message/command off to server
*/
sendRemoteCommand(){
this.client.socket.emit("chatMessage",{msg: this.message, links: this.links});
}
/**
* Sets site emotes
* @param {Object} data - Emote data from server
*/
setSiteEmotes(data){
this.emotes.site = data;
}
/**
* Sets channel emotes
* @param {Object} data - Emote data from server
*/
setChanEmotes(data){
this.emotes.chan = data;
}
/**
* Sets personal emotes
* @param {Object} data - Emote data from server
*/
setPersonalEmotes(data){
this.emotes.personal = data;
}
/**
* Sets used tokes
* @param {Object} data - Used toke data from server
*/
setUsedTokes(data){
this.usedTokes = data.tokes;
}
/**
* Fetches emote by link
* @param {String} link - Link to fetch emote with
* @returns {Object} found emote
*/
getEmoteByLink(link){
//Create an empty variable to hold the found emote
var foundEmote = null;
//For each list of emotes
Object.keys(this.emotes).forEach((key) => {
//For each emote in the current list
this.emotes[key].forEach((emote) => {
//if we found a match
if(emote.link == link){
//return the match
foundEmote = emote;
}
});
});
return foundEmote;
}
/**
* Generates flat list of emote names
* @returns {Array} List of strings containing emote names
*/
getEmoteNames(){
//Create an empty array to hold names
let names = [];
//For every set of emotes
for(let set of Object.keys(this.emotes)){
//for every emote in the current set of emotes
for(let emote of this.emotes[set]){
//push the name of the emote to the name list
names.push(emote.name);
}
}
//return our list of names
return names;
}
/**
* Generates auto-complete dictionary from pre-written commands, emotes, and used tokes from servers for use with autocomplete
* @returns {Object} Generated Dictionary object
*/
buildAutocompleteDictionary(){
let dictionary = {
tokes: {
prefix: '!',
postfix: '',
cmds: [
['toke', true]
].concat(injectPerms(this.usedTokes))
},
//Make sure to add spaces at the end for commands that take arguments
//Not necissary but definitely nice to have
serverCMD: {
prefix: '!',
postfix: '',
cmds: [
["whisper ", true],
["announce ", client.user.permMap.chan.get('announce')],
["serverannounce ", client.user.permMap.site.get('announce')],
["clear ", client.user.permMap.chan.get('clearChat')],
["kick ", client.user.permMap.chan.get('kickUser')],
]
},
localCMD:{
prefix: '/',
postfix: '',
cmds: [
["high ", true]
]
},
usernames:{
prefix: '',
postfix: '',
cmds: injectPerms(Array.from(client.userList.colorMap.keys()))
},
emotes:{
prefix:'[',
postfix:']',
cmds: injectPerms(this.getEmoteNames())
}
};
//return our dictionary object
return dictionary;
function injectPerms(cmds, perm = true){
//Create empty array to hold cmds
let cmdSet = [];
//For each cmd
for(let cmd of cmds){
//Add the cmd with its perm to the cmdset
cmdSet.push([cmd, perm]);
}
//return the cmd set
return cmdSet;
}
}
}
/**
* Class which contains logic for client-side commands
*/
class commandProcessor{
/**
* Instantiates a new Command Processor object
* @param {channel} client - Parent client mgmt object
*/
constructor(client){
/**
* Parent Client Management object
*/
this.client = client
}
/**
* Method handling /high client command
* @param {Array} argumentArray - Array of arguments passed down from Command Pre-Processor
*/
high(argumentArray){
//If we have an argument
if(argumentArray[1]){
//Use it to set our high level
//Technically this is less of a local command than it would be if it where telling the select to do this
//but TTN used to treat this as a local command so fuck it
this.client.socket.emit("setHighLevel", {highLevel: argumentArray[1]});
}
}
}</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="addURLPopup.html">addURLPopup</a></li><li><a href="cPanel.html">cPanel</a></li><li><a href="channel.html">channel</a></li><li><a href="chatBox.html">chatBox</a></li><li><a href="chatPostprocessor.html">chatPostprocessor</a></li><li><a href="clearPopup.html">clearPopup</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="defaultTitlesPopup.html">defaultTitlesPopup</a></li><li><a href="emotePanel.html">emotePanel</a></li><li><a href="hlsBase.html">hlsBase</a></li><li><a href="hlsLiveStreamHandler.html">hlsLiveStreamHandler</a></li><li><a href="mediaHandler.html">mediaHandler</a></li><li><a href="newPlaylistPopup.html">newPlaylistPopup</a></li><li><a href="nullHandler.html">nullHandler</a></li><li><a href="panelObj.html">panelObj</a></li><li><a href="player.html">player</a></li><li><a href="playlistManager.html">playlistManager</a></li><li><a href="poppedPanel.html">poppedPanel</a></li><li><a href="queuePanel.html">queuePanel</a></li><li><a href="rawFileBase.html">rawFileBase</a></li><li><a href="rawFileHandler.html">rawFileHandler</a></li><li><a href="renamePopup.html">renamePopup</a></li><li><a href="reschedulePopup.html">reschedulePopup</a></li><li><a href="schedulePopup.html">schedulePopup</a></li><li><a href="settingsPanel.html">settingsPanel</a></li><li><a href="userList.html">userList</a></li><li><a href="youtubeEmbedHandler.html">youtubeEmbedHandler</a></li></ul><h3>Global</h3><ul><li><a href="global.html#onYouTubeIframeAPIReady">onYouTubeIframeAPIReady</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Sat Sep 06 2025 19:07:57 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

View file

@ -1,430 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Class: commandProcessor</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Class: commandProcessor</h1>
<section>
<header>
<h2><span class="attribs"><span class="type-signature"></span></span>commandProcessor<span class="signature">(client)</span><span class="type-signature"></span></h2>
<div class="class-description">Class which contains logic for client-side commands</div>
</header>
<article>
<div class="container-overview">
<h2>Constructor</h2>
<h4 class="name" id="commandProcessor"><span class="type-signature"></span>new commandProcessor<span class="signature">(client)</span><span class="type-signature"></span></h4>
<div class="description">
Instantiates a new Command Processor object
</div>
<h5>Parameters:</h5>
<table class="params">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th class="last">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="name"><code>client</code></td>
<td class="type">
<span class="param-type"><a href="channel.html">channel</a></span>
</td>
<td class="description last">Parent client mgmt object</td>
</tr>
</tbody>
</table>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="commandPreprocessor.js.html">commandPreprocessor.js</a>, <a href="commandPreprocessor.js.html#line305">line 305</a>
</li></ul></dd>
</dl>
</div>
<h3 class="subsection-title">Members</h3>
<h4 class="name" id="client"><span class="type-signature"></span>client<span class="type-signature"></span></h4>
<div class="description">
Parent Client Management object
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="commandPreprocessor.js.html">commandPreprocessor.js</a>, <a href="commandPreprocessor.js.html#line314">line 314</a>
</li></ul></dd>
</dl>
<h3 class="subsection-title">Methods</h3>
<h4 class="name" id="high"><span class="type-signature"></span>high<span class="signature">(argumentArray)</span><span class="type-signature"></span></h4>
<div class="description">
Method handling /high client command
</div>
<h5>Parameters:</h5>
<table class="params">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th class="last">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="name"><code>argumentArray</code></td>
<td class="type">
<span class="param-type">Array</span>
</td>
<td class="description last">Array of arguments passed down from Command Pre-Processor</td>
</tr>
</tbody>
</table>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="commandPreprocessor.js.html">commandPreprocessor.js</a>, <a href="commandPreprocessor.js.html#line321">line 321</a>
</li></ul></dd>
</dl>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="addURLPopup.html">addURLPopup</a></li><li><a href="cPanel.html">cPanel</a></li><li><a href="channel.html">channel</a></li><li><a href="chatBox.html">chatBox</a></li><li><a href="chatPostprocessor.html">chatPostprocessor</a></li><li><a href="clearPopup.html">clearPopup</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="defaultTitlesPopup.html">defaultTitlesPopup</a></li><li><a href="emotePanel.html">emotePanel</a></li><li><a href="hlsBase.html">hlsBase</a></li><li><a href="hlsLiveStreamHandler.html">hlsLiveStreamHandler</a></li><li><a href="mediaHandler.html">mediaHandler</a></li><li><a href="newPlaylistPopup.html">newPlaylistPopup</a></li><li><a href="nullHandler.html">nullHandler</a></li><li><a href="panelObj.html">panelObj</a></li><li><a href="player.html">player</a></li><li><a href="playlistManager.html">playlistManager</a></li><li><a href="poppedPanel.html">poppedPanel</a></li><li><a href="queuePanel.html">queuePanel</a></li><li><a href="rawFileBase.html">rawFileBase</a></li><li><a href="rawFileHandler.html">rawFileHandler</a></li><li><a href="renamePopup.html">renamePopup</a></li><li><a href="reschedulePopup.html">reschedulePopup</a></li><li><a href="schedulePopup.html">schedulePopup</a></li><li><a href="settingsPanel.html">settingsPanel</a></li><li><a href="userList.html">userList</a></li><li><a href="youtubeEmbedHandler.html">youtubeEmbedHandler</a></li></ul><h3>Global</h3><ul><li><a href="global.html#onYouTubeIframeAPIReady">onYouTubeIframeAPIReady</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Sat Sep 06 2025 19:07:57 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

View file

@ -1,524 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: cpanel.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: cpanel.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>/*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 &lt;https://www.gnu.org/licenses/>.*/
/**
* Class containing code for managing the Canopy Panel UX
*/
class cPanel{
/**
* Instantiates a new Canopy Panel Management object
* @param {channel} client - Parent client Management Object
*/
constructor(client){
/**
* Parent Client Management object
*/
this.client = client;
/**
* Active Panel Object
*/
this.activePanel = null;
/**
* Pinned Panel Object
*/
this.pinnedPanel = null;
/**
* Popped Panel Objects
*/
this.poppedPanels = [];
/**
* Click-Dragger object for re-sizable active panel
*/
this.activePanelDragger = new canopyUXUtils.clickDragger("#cpanel-active-drag-handle", "#cpanel-active-div", false, null, false);
/**
* Click-Dragger object for re-sizable pinned panel
*/
this.pinnedPanelDragger = new canopyUXUtils.clickDragger("#cpanel-pinned-drag-handle", "#cpanel-pinned-div", false, this.client.chatBox.clickDragger);
//Element Nodes
//Active Panel
/**
* Active Panel Container
*/
this.activePanelDiv = document.querySelector("#cpanel-active-div");
/**
* Active Panel Title
*/
this.activePanelTitle = document.querySelector("#cpanel-active-title");
/**
* Active Title Document Div
*/
this.activePanelDoc = document.querySelector("#cpanel-active-doc");
/**
* Active Panel Pin Icon
*/
this.activePanelPinIcon = document.querySelector("#cpanel-active-pin-icon");
/**
* Active Panel Pop-Out Icon
*/
this.activePanelPopoutIcon = document.querySelector("#cpanel-active-popout-icon");
/**
* Active Panel Close Icon
*/
this.activePanelCloseIcon = document.querySelector("#cpanel-active-close-icon");
//Pinned Panel
/**
* Pinned Panel Contianer
*/
this.pinnedPanelDiv = document.querySelector("#cpanel-pinned-div");
/**
* Pinned Panel Title
*/
this.pinnedPanelTitle = document.querySelector("#cpanel-pinned-title");
/**
* Pinned Panel Document Div
*/
this.pinnedPanelDoc = document.querySelector("#cpanel-pinned-doc");
/**
* Pinned Panel Un-Pin Icon
*/
this.pinnedPanelUnpinIcon = document.querySelector("#cpanel-pinned-unpin-icon");
/**
* Pinned Panel Pop-Out Icon
*/
this.pinnedPanelPopoutIcon = document.querySelector("#cpanel-pinned-popout-icon");
/**
* Pinned Panel Close Icon
*/
this.pinnedPanelCloseIcon = document.querySelector("#cpanel-pinned-close-icon");
this.setupInput();
}
/**
* Defines input-related event listeners
*/
setupInput(){
this.activePanelCloseIcon.addEventListener("click", this.hideActivePanel.bind(this));
this.activePanelPinIcon.addEventListener("click", this.pinPanel.bind(this));
this.activePanelPopoutIcon.addEventListener("click", this.popActivePanel.bind(this));
this.pinnedPanelCloseIcon.addEventListener("click", this.hidePinnedPanel.bind(this));
this.pinnedPanelUnpinIcon.addEventListener("click", this.unpinPanel.bind(this));
this.pinnedPanelPopoutIcon.addEventListener("click", this.popPinnedPanel.bind(this));
}
/**
* Sets Active Panel
* @param {panelObj} panel - Panel Object to set as active
* @param {String} panelBody - innerHTML of Panel, pulls from panelObj.getPage() if empty
*/
async setActivePanel(panel, panelBody){
//Set active panel
this.activePanel = panel;
//Grab panel hypertext content and load it into div
this.activePanelDoc.innerHTML = (panelBody == null || panelBody == "") ? await this.activePanel.getPage() : panelBody;
//Display panel
this.activePanelDiv.style.display = "flex";
this.activePanelTitle.textContent = this.activePanel.name;
//Call panel initialization function
this.activePanel.panelDocument = this.activePanelDoc;
this.activePanel.docSwitch();
}
/**
* Hides active panel
* @param {Event} event - Event passed down from Input Handler
* @param {Boolean} keepAlive - Prevents closing panel if true
*/
hideActivePanel(event, keepAlive = false){
if(!keepAlive){
this.activePanel.closer();
}
//Hide the panel
this.activePanelDiv.style.display = "none";
//Clear out the panel
this.activePanelDoc.innerHTML = '';
//Set active panel to null
this.activePanel = null;
}
/**
* Pins active panel
*/
pinPanel(){
this.setPinnedPanel(this.activePanel, this.activePanelDoc.innerHTML);
this.hideActivePanel(null, true);
}
/**
* Pop's out active panel
*/
popActivePanel(){
this.popPanel(this.activePanel, this.activePanelDoc.innerHTML);
this.hideActivePanel(null, true);
}
/**
* Sets pinned panel
* @param {panelObj} panel - Panel Object to apply to panel
* @param {String} panelBody - Raw HTML to inject into panel body, defaults to panel page if null
*/
async setPinnedPanel(panel, panelBody){
//Set pinned panel
this.pinnedPanel = panel;
//Set Title
this.pinnedPanelTitle.textContent = this.pinnedPanel.name;
//Grab panel hypertext content and load it into div
this.pinnedPanelDoc.innerHTML = (panelBody == null || panelBody == "") ? await this.pinnedPanel.getPage() : panelBody;
//Display panel
this.pinnedPanelDiv.style.display = "flex";
//Call panel initialization function
this.pinnedPanel.panelDocument = this.pinnedPanelDoc;
this.pinnedPanel.docSwitch();
//Resize to window/content
this.pinnedPanelDragger.fixCutoff();
}
/**
* Hides pinned panel
* @param {Event} event - Passed down input event
* @param {Boolean} keepAlive - Prevents panel.closer() from running if true
*/
hidePinnedPanel(event, keepAlive = false){
this.pinnedPanelDiv.style.display = "none";
if(!keepAlive){
this.pinnedPanel.closer();
}
this.pinnedPanel = null;
}
/**
* Sets pinned panel to active
*/
unpinPanel(){
this.setActivePanel(this.pinnedPanel, this.pinnedPanelDoc.innerHTML);
this.hidePinnedPanel(null, true);
}
/**
* Pops pinned panel
*/
popPinnedPanel(){
this.popPanel(this.pinnedPanel, this.pinnedPanelDoc.innerHTML);
this.hidePinnedPanel(null, true);
}
/**
* Pops a new pop-out panel
* @param {panelObj} panel - panelObj to apply to the panel
* @param {String} panelBody - Raw HTML to inject into panel body, injects panel default if left to null
*/
popPanel(panel, panelBody){
var newPanel = new poppedPanel(panel, panelBody, this)
this.poppedPanels.push(newPanel);
}
}
/**
* Template Class for other Classes for Objects which represent a single Canopy Panel
*/
class panelObj{
/**
* Instantiates a new Panel Object
* @param {channel} client - Parent client Management Object
* @param {String} name - Panel Name
* @param {String} pageURL - Panel Default Page URL
* @param {Document} panelDocument - Panel Document
*/
constructor(client, name = "Placeholder Panel", pageURL = "/panel/placeholder", panelDocument = window.document){
/**
* Panel Name
*/
this.name = name;
/**
* Panel Default Page URL
*/
this.pageURL = pageURL;
/**
* Panel Document
*/
this.panelDocument = panelDocument;
/**
* Current root document panel doc lives within
*/
this.ownerDoc = this.panelDocument.ownerDocument == null ? this.panelDocument : this.panelDocument.ownerDocument;
/**
* Parent Client Management object
*/
this.client = client;
}
/**
* Fetches panel page from the server
* @returns {String} Raw panel doc HTML
*/
async getPage(){
var response = await fetch(this.pageURL,{
method: "GET",
});
return await response.text();
}
/**
* Handles Document/Panel Changes
*/
docSwitch(){
//Set owner doc
this.ownerDoc = this.panelDocument.ownerDocument == null ? this.panelDocument : this.panelDocument.ownerDocument;
}
/**
* Called upon panel close/exit
*/
closer(){
}
}
/**
* Class which represents a single instance of a popped-out panel
*/
class poppedPanel{
/**
* Instantiates a new Popped Panel Object
* @param {panelObj} panel - Panel Object to apply to Popped Panel
* @param {String} panelBody - Raw HTML to inject into panel body, defaults to panel page if null
* @param {cPanel} cPanel - Parent Canopy Panel Management Object
*/
constructor(panel, panelBody, cPanel){
/**
* Panel Object to apply to Popped Panel
*/
this.panel = panel;
/**
* Raw HTML to inject into panel body, defaults to panel page if null
*/
this.panelBody = panelBody;
/**
* Browser Window taken up by the Popped Panel
*/
this.window = null;
/**
* Popped Panel Container Div
*/
this.pinnedPanelDiv = null;
/**
* Popped Panel Title
*/
this.pinnedPanelTitle = null;
/**
* Popped Panel Document Div
*/
this.pinnedPanelDoc = null;
/**
* Popped Panel Close Icon
*/
this.pinnedPanelCloseIcon = null;
/**
* Parent Canopy Panel Management Object
*/
this.cPanel = cPanel;
/**
* Disables this.panel.closer() calls from this.closer()
*/
this.keepAlive = false;
//Continue constructor asynchrnously
this.asyncConstructor();
}
/**
* Continuation of constructor method for asynchronous function calls
*/
async asyncConstructor(){
//Set panel body properly
this.panelBody = (this.panelBody == null || this.panelBody == "") ? await this.panel.getPage() : this.panelBody;
//Pop the panel
this.popContainer();
}
/**
* Pops/Opens container window upon start
*/
popContainer(){
//Set Window Object
this.window = window.open("/panel/popoutContainer","",`menubar=no,height=850,width=600`);
this.window.addEventListener("load", this.fillContainer.bind(this));
}
/**
* Fills container window with Popped Panel container elements
*/
fillContainer(){
//Set Element Nodes
this.panelDiv = this.window.document.querySelector("#cpanel-div");
this.panelTitle = this.window.document.querySelector("#cpanel-title");
this.panelDoc = this.window.document.querySelector("#cpanel-doc");
this.panelPopinIcon = this.window.document.querySelector("#cpanel-popin-icon");
this.panelPinIcon = this.window.document.querySelector("#cpanel-pin-icon");
//Set Window Title
this.window.document.title = this.window.document.title.replace("NULL_POPOUT", `${this.panel.name} (${client.channelName})`);
//Set Panel Content
this.panelTitle.innerText = this.panel.name;
this.panelDoc.innerHTML = this.panelBody;
//Set panel object document and call the related function
this.panel.panelDocument = this.window.document;
this.panel.docSwitch();
this.setupInput();
}
/**
* Defines default input-related popped-panel Event Listeners
*/
setupInput(){
this.panelPopinIcon.addEventListener("click", this.unpop.bind(this));
this.panelPinIcon.addEventListener("click", this.pin.bind(this));
this.window.addEventListener("unload", this.closer.bind(this));
}
/**
* Called upon close/exit of panel
*/
closer(){
if(!this.keepAlive){
this.panel.closer();
}
this.cPanel.poppedPanels.splice(this.cPanel.poppedPanels.indexOf(this),1);
}
/**
* Un-pops panel into active-panel slot
*/
unpop(){
//Set active panel
this.cPanel.setActivePanel(this.panel, this.panelDoc.innerHTML);
this.keepAlive = true;
//Close the popped window
this.window.close();
}
/**
* Pins panel next to chat
*/
pin(){
this.cPanel.setPinnedPanel(this.panel, this.panelDoc.innerHTML);
this.keepAlive = true;
this.window.close();
}
}</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="addURLPopup.html">addURLPopup</a></li><li><a href="cPanel.html">cPanel</a></li><li><a href="channel.html">channel</a></li><li><a href="chatBox.html">chatBox</a></li><li><a href="chatPostprocessor.html">chatPostprocessor</a></li><li><a href="clearPopup.html">clearPopup</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="defaultTitlesPopup.html">defaultTitlesPopup</a></li><li><a href="emotePanel.html">emotePanel</a></li><li><a href="hlsBase.html">hlsBase</a></li><li><a href="hlsLiveStreamHandler.html">hlsLiveStreamHandler</a></li><li><a href="mediaHandler.html">mediaHandler</a></li><li><a href="newPlaylistPopup.html">newPlaylistPopup</a></li><li><a href="nullHandler.html">nullHandler</a></li><li><a href="panelObj.html">panelObj</a></li><li><a href="player.html">player</a></li><li><a href="playlistManager.html">playlistManager</a></li><li><a href="poppedPanel.html">poppedPanel</a></li><li><a href="queuePanel.html">queuePanel</a></li><li><a href="rawFileBase.html">rawFileBase</a></li><li><a href="rawFileHandler.html">rawFileHandler</a></li><li><a href="renamePopup.html">renamePopup</a></li><li><a href="reschedulePopup.html">reschedulePopup</a></li><li><a href="schedulePopup.html">schedulePopup</a></li><li><a href="settingsPanel.html">settingsPanel</a></li><li><a href="userList.html">userList</a></li><li><a href="youtubeEmbedHandler.html">youtubeEmbedHandler</a></li></ul><h3>Global</h3><ul><li><a href="global.html#onYouTubeIframeAPIReady">onYouTubeIframeAPIReady</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Sat Sep 06 2025 19:07:57 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

View file

@ -1,969 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Class: defaultTitlesPopup</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Class: defaultTitlesPopup</h1>
<section>
<header>
<h2><span class="attribs"><span class="type-signature"></span></span>defaultTitlesPopup<span class="signature">(event, playlist, titles, location, client, doc)</span><span class="type-signature"></span></h2>
<div class="class-description">Class Representing popup dialogue for changing playlists defualt titles</div>
</header>
<article>
<div class="container-overview">
<h2>Constructor</h2>
<h4 class="name" id="defaultTitlesPopup"><span class="type-signature"></span>new defaultTitlesPopup<span class="signature">(event, playlist, titles, location, client, doc)</span><span class="type-signature"></span></h4>
<div class="description">
Instantiates a new Default Titles Popup
</div>
<h5>Parameters:</h5>
<table class="params">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th class="last">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="name"><code>event</code></td>
<td class="type">
<span class="param-type">Event</span>
</td>
<td class="description last">Event passed down from Event Listener</td>
</tr>
<tr>
<td class="name"><code>playlist</code></td>
<td class="type">
<span class="param-type">String</span>
</td>
<td class="description last">Playlist name</td>
</tr>
<tr>
<td class="name"><code>titles</code></td>
<td class="type">
<span class="param-type">String</span>
</td>
<td class="description last">List of titles, denoted by newlines</td>
</tr>
<tr>
<td class="name"><code>location</code></td>
<td class="type">
<span class="param-type">String</span>
</td>
<td class="description last">Location of playlist, either Channel or User</td>
</tr>
<tr>
<td class="name"><code>client</code></td>
<td class="type">
<span class="param-type"><a href="channel.html">channel</a></span>
</td>
<td class="description last">Parent Client Management Object</td>
</tr>
<tr>
<td class="name"><code>doc</code></td>
<td class="type">
<span class="param-type">Document</span>
</td>
<td class="description last">Current owner documnet of the panel, so we know where to drop our pop-up</td>
</tr>
</tbody>
</table>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_playlistManager.js.html">panels/queuePanel/playlistManager.js</a>, <a href="panels_queuePanel_playlistManager.js.html#line784">line 784</a>
</li></ul></dd>
</dl>
</div>
<h3 class="subsection-title">Members</h3>
<h4 class="name" id="client"><span class="type-signature"></span>client<span class="type-signature"></span></h4>
<div class="description">
Parent Client Management Object
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_playlistManager.js.html">panels/queuePanel/playlistManager.js</a>, <a href="panels_queuePanel_playlistManager.js.html#line798">line 798</a>
</li></ul></dd>
</dl>
<h4 class="name" id="location"><span class="type-signature"></span>location<span class="type-signature"></span></h4>
<div class="description">
Location of playlist, either Channel or User
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_playlistManager.js.html">panels/queuePanel/playlistManager.js</a>, <a href="panels_queuePanel_playlistManager.js.html#line808">line 808</a>
</li></ul></dd>
</dl>
<h4 class="name" id="playlist"><span class="type-signature"></span>playlist<span class="type-signature"></span></h4>
<div class="description">
Playlist Name
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_playlistManager.js.html">panels/queuePanel/playlistManager.js</a>, <a href="panels_queuePanel_playlistManager.js.html#line803">line 803</a>
</li></ul></dd>
</dl>
<h4 class="name" id="popup"><span class="type-signature"></span>popup<span class="type-signature"></span></h4>
<div class="description">
canopyUXUtils.popup() object
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_playlistManager.js.html">panels/queuePanel/playlistManager.js</a>, <a href="panels_queuePanel_playlistManager.js.html#line820">line 820</a>
</li></ul></dd>
</dl>
<h4 class="name" id="titles"><span class="type-signature"></span>titles<span class="type-signature"></span></h4>
<div class="description">
Array of titles to set
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_playlistManager.js.html">panels/queuePanel/playlistManager.js</a>, <a href="panels_queuePanel_playlistManager.js.html#line813">line 813</a>
</li></ul></dd>
</dl>
<h3 class="subsection-title">Methods</h3>
<h4 class="name" id="asyncConstructor"><span class="type-signature"></span>asyncConstructor<span class="signature">()</span><span class="type-signature"></span></h4>
<div class="description">
Continuation of object construction, called after child popup object construction
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_playlistManager.js.html">panels/queuePanel/playlistManager.js</a>, <a href="panels_queuePanel_playlistManager.js.html#line826">line 826</a>
</li></ul></dd>
</dl>
<h4 class="name" id="changeDefaultTitles"><span class="type-signature"></span>changeDefaultTitles<span class="signature">(event)</span><span class="type-signature"></span></h4>
<div class="description">
Handles sending request to change default titles of playlist to the server
</div>
<h5>Parameters:</h5>
<table class="params">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th class="last">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="name"><code>event</code></td>
<td class="type">
<span class="param-type">Event</span>
</td>
<td class="description last">Event passed down from Event Listener</td>
</tr>
</tbody>
</table>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_playlistManager.js.html">panels/queuePanel/playlistManager.js</a>, <a href="panels_queuePanel_playlistManager.js.html#line847">line 847</a>
</li></ul></dd>
</dl>
<h4 class="name" id="setupInput"><span class="type-signature"></span>setupInput<span class="signature">()</span><span class="type-signature"></span></h4>
<div class="description">
Defines input-related Event Handlers
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_playlistManager.js.html">panels/queuePanel/playlistManager.js</a>, <a href="panels_queuePanel_playlistManager.js.html#line837">line 837</a>
</li></ul></dd>
</dl>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="addURLPopup.html">addURLPopup</a></li><li><a href="cPanel.html">cPanel</a></li><li><a href="channel.html">channel</a></li><li><a href="chatBox.html">chatBox</a></li><li><a href="chatPostprocessor.html">chatPostprocessor</a></li><li><a href="clearPopup.html">clearPopup</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="defaultTitlesPopup.html">defaultTitlesPopup</a></li><li><a href="emotePanel.html">emotePanel</a></li><li><a href="hlsBase.html">hlsBase</a></li><li><a href="hlsLiveStreamHandler.html">hlsLiveStreamHandler</a></li><li><a href="mediaHandler.html">mediaHandler</a></li><li><a href="newPlaylistPopup.html">newPlaylistPopup</a></li><li><a href="nullHandler.html">nullHandler</a></li><li><a href="panelObj.html">panelObj</a></li><li><a href="player.html">player</a></li><li><a href="playlistManager.html">playlistManager</a></li><li><a href="poppedPanel.html">poppedPanel</a></li><li><a href="queuePanel.html">queuePanel</a></li><li><a href="rawFileBase.html">rawFileBase</a></li><li><a href="rawFileHandler.html">rawFileHandler</a></li><li><a href="renamePopup.html">renamePopup</a></li><li><a href="reschedulePopup.html">reschedulePopup</a></li><li><a href="schedulePopup.html">schedulePopup</a></li><li><a href="settingsPanel.html">settingsPanel</a></li><li><a href="userList.html">userList</a></li><li><a href="youtubeEmbedHandler.html">youtubeEmbedHandler</a></li></ul><h3>Global</h3><ul><li><a href="global.html#onYouTubeIframeAPIReady">onYouTubeIframeAPIReady</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Sat Sep 06 2025 19:07:57 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

Before

Width:  |  Height:  |  Size: 116 KiB

File diff suppressed because it is too large Load diff

Before

Width:  |  Height:  |  Size: 118 KiB

File diff suppressed because it is too large Load diff

Before

Width:  |  Height:  |  Size: 120 KiB

File diff suppressed because it is too large Load diff

Before

Width:  |  Height:  |  Size: 114 KiB

File diff suppressed because it is too large Load diff

Before

Width:  |  Height:  |  Size: 120 KiB

File diff suppressed because it is too large Load diff

Before

Width:  |  Height:  |  Size: 117 KiB

View file

@ -1,217 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Global</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Global</h1>
<section>
<header>
<h2></h2>
</header>
<article>
<div class="container-overview">
<dl class="details">
</dl>
</div>
<h3 class="subsection-title">Methods</h3>
<h4 class="name" id="onYouTubeIframeAPIReady"><span class="type-signature"></span>onYouTubeIframeAPIReady<span class="signature">()</span><span class="type-signature"></span></h4>
<div class="description">
Youtube iframe-embed API entry point
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="channel.js.html">channel.js</a>, <a href="channel.js.html#line321">line 321</a>
</li></ul></dd>
</dl>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="addURLPopup.html">addURLPopup</a></li><li><a href="cPanel.html">cPanel</a></li><li><a href="channel.html">channel</a></li><li><a href="chatBox.html">chatBox</a></li><li><a href="chatPostprocessor.html">chatPostprocessor</a></li><li><a href="clearPopup.html">clearPopup</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="defaultTitlesPopup.html">defaultTitlesPopup</a></li><li><a href="emotePanel.html">emotePanel</a></li><li><a href="hlsBase.html">hlsBase</a></li><li><a href="hlsLiveStreamHandler.html">hlsLiveStreamHandler</a></li><li><a href="mediaHandler.html">mediaHandler</a></li><li><a href="newPlaylistPopup.html">newPlaylistPopup</a></li><li><a href="nullHandler.html">nullHandler</a></li><li><a href="panelObj.html">panelObj</a></li><li><a href="player.html">player</a></li><li><a href="playlistManager.html">playlistManager</a></li><li><a href="poppedPanel.html">poppedPanel</a></li><li><a href="queuePanel.html">queuePanel</a></li><li><a href="rawFileBase.html">rawFileBase</a></li><li><a href="rawFileHandler.html">rawFileHandler</a></li><li><a href="renamePopup.html">renamePopup</a></li><li><a href="reschedulePopup.html">reschedulePopup</a></li><li><a href="schedulePopup.html">schedulePopup</a></li><li><a href="settingsPanel.html">settingsPanel</a></li><li><a href="userList.html">userList</a></li><li><a href="youtubeEmbedHandler.html">youtubeEmbedHandler</a></li></ul><h3>Global</h3><ul><li><a href="global.html#onYouTubeIframeAPIReady">onYouTubeIframeAPIReady</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Sat Sep 06 2025 19:07:57 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,96 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Home</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Home</h1>
<h3> </h3>
<section>
<article><h1>Canopy - 0.3-INDEV</h1>
<p>Canopy - /ˈkæ.nə.pi/:</p>
<ul>
<li>The upper layer of foliage and branches of a forest, containing the majority of animal life.</li>
</ul>
<p>Canopy is a community chat &amp; synced video embedding web application, intended to replace fore.st as the server software for ourfore.st.
This new codebase intends to solve the following issues with the current CyTube based software:</p>
<ul>
<li>Unmaintained upstream codebase.</li>
<li>Different goals.</li>
<li>Different coding styles.</li>
<li>Obsolete Technology and Dependencies.</li>
<li>General Clunk</li>
<li>Less Unique Community Identity</li>
</ul>
<p>Canopy intends to be a simple node/express.js app. It leverages yt-dlp and the internet archive REST api for metadata gathering. Persistant storage is handled by mongodb, as it's document based nature inherintly works well for cleanly storing large config documents for user/channel settings, and the low use of inter-collection references within the canopy software. All hardcore security functions like server-side input sanatization, session handling, CSRF mitigation, and password hashing are handled by industry-standard open source libraries such as validator/express-validator, express-sessions, csrf-sync, and bcrypt, however it IS hobbiest software, and it should be treated as such.</p>
<p>The Canopy codebase does not, nor will it ever contain:</p>
<ul>
<li>Advertisements (targetted or otherwise)</li>
<li>Proprietary Code</li>
<li>Cryptocurrency/Blockchain integration</li>
<li>'Analytics/Telemtry' spyware</li>
<li>The use of video sources which require proprietary 'Digital <s>Rights Management</s> Ristricitons Malware' such as Widevine.</li>
</ul>
<p>Thirdparty media providers may or may not contain all of the above atrocities :P (though browser-side DRM extensions will never be required), always use an ad-blocker!</p>
<p>Our current goal is to create a cleaner, more modern, purpose-built codebase that has feature-parity with the current version of fore.st, while writing improvements where possible. Once this is accomplished, and ourfore.st has been migrated, work will continue to re-create features from TTN, while also building completely new ones as well.</p>
<h2>License</h2>
<p>Canopy is written by the community, and provided under the GNU Affero General Public License v3 in order to prevent Canopy from being used in proprietary software or shitcoin scams.</p></article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="addURLPopup.html">addURLPopup</a></li><li><a href="cPanel.html">cPanel</a></li><li><a href="channel.html">channel</a></li><li><a href="chatBox.html">chatBox</a></li><li><a href="chatPostprocessor.html">chatPostprocessor</a></li><li><a href="clearPopup.html">clearPopup</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="defaultTitlesPopup.html">defaultTitlesPopup</a></li><li><a href="emotePanel.html">emotePanel</a></li><li><a href="hlsBase.html">hlsBase</a></li><li><a href="hlsLiveStreamHandler.html">hlsLiveStreamHandler</a></li><li><a href="mediaHandler.html">mediaHandler</a></li><li><a href="newPlaylistPopup.html">newPlaylistPopup</a></li><li><a href="nullHandler.html">nullHandler</a></li><li><a href="panelObj.html">panelObj</a></li><li><a href="player.html">player</a></li><li><a href="playlistManager.html">playlistManager</a></li><li><a href="poppedPanel.html">poppedPanel</a></li><li><a href="queuePanel.html">queuePanel</a></li><li><a href="rawFileBase.html">rawFileBase</a></li><li><a href="rawFileHandler.html">rawFileHandler</a></li><li><a href="renamePopup.html">renamePopup</a></li><li><a href="reschedulePopup.html">reschedulePopup</a></li><li><a href="schedulePopup.html">schedulePopup</a></li><li><a href="settingsPanel.html">settingsPanel</a></li><li><a href="userList.html">userList</a></li><li><a href="youtubeEmbedHandler.html">youtubeEmbedHandler</a></li></ul><h3>Global</h3><ul><li><a href="global.html#onYouTubeIframeAPIReady">onYouTubeIframeAPIReady</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Sat Sep 06 2025 19:07:57 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

File diff suppressed because it is too large Load diff

View file

@ -1,844 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: mediaHandler.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: mediaHandler.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>/*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 &lt;https://www.gnu.org/licenses/>.*/
/*
* Base class for all Canopy Media Handlers
*
* This is little more than a interface class
*/
class mediaHandler{
/**
* Instantiates a new Media Handler object
* @param {channel} client - Parent Client Management Object
* @param {player} player - Parent Canopy Player Object
* @param {Object} media - De-hydrated media object from server
* @param {String} type - Media Handler Source Type
*/
constructor(client, player, media, type){
/**
* Parent Client Management Object
*/
this.client = client;
/**
* Parent Canopy Player Object
*/
this.player = player;
/**
* Media Handler Source Type
*/
this.type = type
/*
* Denotes wether a seek call was made by the syncing function
*/
this.selfAct = false;
/*
* Contains the last received time stamp
*/
this.lastTimestamp = 0;
//Ingest media object from server
this.startMedia(media);
}
/**
* Ingests media nd starts playback
* @param {Object} media - Media object from server
*/
startMedia(media){
//If we properly ingested the media
if(this.ingestMedia(media)){
//Build the video player
this.buildPlayer();
//Call the start function
this.start();
}
}
/**
* Builds video player element
*/
buildPlayer(){
//Reset player lock
this.lock = false;
}
/**
* Destroys video player element
*/
destroyPlayer(){
//Null out video property
this.video = null;
}
/**
* Ingests media object from server
* @param {Object} media - Media object from the server
* @returns {Boolean} True upon success
*/
ingestMedia(media){
//Set now playing
this.nowPlaying = media;
//return true to signify success
return true;
}
/**
* Starts video playback
*/
start(){
this.setVideoTitle(this.nowPlaying.title);
}
/**
* Syncronizes timestamp based on timestamp received from server
* @param {Number} timestamp - Current video timestamp in seconds
*/
sync(timestamp = this.lastTimestamp){
//Skip sync calls that won't seek so we don't pointlessly throw selfAct
if(timestamp != this.video.currentTime){
//Set self act flag
this.selfAct = true;
}
}
/**
* Reloads media player
*/
reload(){
//Get current timestamp
const timestamp = this.video.currentTime;
//Throw self act flag to make sure we don't un-sync the player
this.selfAct = true;
}
/**
* Handles media end
*/
end(){
//Null out current media
this.nowPlaying = null;
//Throw self act to prevent unlock on video end
this.selfAct = true;
//Destroy the player
this.destroyPlayer();
}
/**
* Plays video
*/
play(){
}
/**
* Pauses video
*/
pause(){
}
/**
* Toggles player control lockout
* @param {Boolean} lock - Whether or not to lock-out user control of video
*/
setPlayerLock(lock){
//set lock property
this.lock = lock;
}
/**
* Calculates Aspect Ratio of media
* @returns {Number} Media Aspect Ratio as Floating Point number
*/
getRatio(){
return 4/3;
}
/**
* Gets current timestamp from video
* @returns {Number} Media Timestamp in seconds
*/
getTimestamp(){
return 0;
}
/**
* Sets player title
* @param {String} title - Title to set
*/
setVideoTitle(title){
this.player.title.textContent = `Currently Playing: ${title}`;
}
/**
* Called once all video metadata has properly been fetched
* @param {Event} event - Event passed down by event handler
*/
onMetadataLoad(event){
//Resize aspect (if locked), since the video doesn't properly report it's resolution until it's been loaded
this.client.chatBox.resizeAspect();
}
/**
* Called on media pause
* @param {Event} event - Event passed down by event handler
*/
onPause(event){
//If the video was paused out-side of code
if(!this.selfAct){
this.player.unlockSync();
}
this.selfAct = false;
}
/**
* Called on media volume change
* @param {Event} event - Event passed down by event handler
*/
onVolumeChange(event){
}
/**
* Called on media seek
* @param {Event} event - Event passed down by event handler
*/
onSeek(event){
//If the video was seeked out-side of code
if(!this.selfAct){
this.player.unlockSync();
}
//reset self act flag
this.selfAct = false;
}
/**
* Called on media buffer
* @param {Event} event - Event passed down by event handler
*/
onBuffer(){
this.selfAct = true;
}
}
/**
* Class containing basic building blocks for anything that touches a &lt;video> tag
* @extends mediaHandler
*/
class rawFileBase extends mediaHandler{
/**
* Instantiates a new rawFileBase object
* @param {channel} client - Parent Client Management Object
* @param {player} player - Parent Canopy Player Object
* @param {Object} media - De-hydrated media object from server
* @param {String} type - Media Handler Source Type
*/
constructor(client, player, media, type){
super(client, player, media, type);
}
/**
* Defines input-related event listeners
*/
defineListeners(){
//Resize to aspect on metadata load
this.video.addEventListener('loadedmetadata', this.onMetadataLoad.bind(this));
this.video.addEventListener('volumechange', this.onVolumeChange.bind(this));
}
buildPlayer(){
//Create player
this.video = document.createElement('video');
//Enable controls
this.video.controls = true;
//Append it to page
this.player.videoContainer.appendChild(this.video);
//Run derived method
super.buildPlayer();
}
destroyPlayer(){
//Stops playback
this.video.pause();
//Remove player from page
this.video.remove();
//Run derived method
super.destroyPlayer();
}
reload(){
//Call derived method
super.reload();
//Load video from source
this.video.load();
//Set it back to the proper time
this.video.currentTime = this.lastTimestamp;
//Play the video
this.video.play();
}
setPlayerLock(lock){
//toggle controls
this.video.controls = !lock;
//Only toggle mute if we're locking, or if we're unlocking after being locked
//If this is ran twice without locking we don't want to surprise unmute on the user
if(lock || this.lock){
//toggle mute
this.video.muted = lock;
}
//toggle looping
this.video.loop = lock;
//Run derived method
super.setPlayerLock(lock);
}
getRatio(){
return this.video.videoWidth / this.video.videoHeight;
}
onVolumeChange(event){
//Pull volume from video
this.player.volume = this.video.volume;
}
}
/**
* Off air static 'player'
* @extends rawFileBase
*/
class nullHandler extends rawFileBase{
/**
* Instantiates a new Null Handler object
* @param {channel} client - Parent Client Management Object
* @param {player} player - Parent Canopy Player Object
*/
constructor(client, player){
//Call derived constructor
super(client, player, {}, null);
this.defineListeners();
}
defineListeners(){
//Run derived method
super.defineListeners();
//Disable right clicking
this.video.addEventListener('contextmenu', (e)=>{e.preventDefault()});
}
start(){
//call derived start function
super.start();
//Lock the player
this.setPlayerLock(true);
//Set the static placeholder
this.video.src = '/video/static.webm';
//play the placeholder video
this.video.play();
}
setVideoTitle(title){
this.player.title.textContent = `Channel Off Air`;
}
}
/**
* Basic building blocks needed for proper time-synchronized raw-file playback
* @extends rawFileBase
*/
class rawFileHandler extends rawFileBase{
/**
* Instantiates a new Null Handler object
* @param {channel} client - Parent Client Management Object
* @param {player} player - Parent Canopy Player Object
* @param {Object} media - De-hydrated media object from server
*/
constructor(client, player, media){
//Call derived constructor
super(client, player, media, 'raw');
//Define listeners
this.defineListeners();
}
defineListeners(){
//Run derived method
super.defineListeners();
this.video.addEventListener('pause', this.onPause.bind(this));
this.video.addEventListener('seeked', this.onSeek.bind(this));
this.video.addEventListener('waiting', this.onBuffer.bind(this));
}
start(){
//Call derived start
super.start();
//Set video
this.video.src = this.nowPlaying.rawLink;
//Set video volume
this.video.volume = this.player.volume;
//Unlock player
this.setPlayerLock(false);
//play video
this.video.play();
}
play(){
this.video.play();
}
pause(){
this.video.pause();
}
sync(timestamp = this.lastTimestamp){
//Call derived sync
super.sync(timestamp);
//Skip sync calls that won't seek so we don't pointlessly throw selfAct
if(timestamp != this.video.currentTime){
//Set current video time based on timestamp received from server
this.video.currentTime = timestamp;
}
}
getTimestamp(){
//Return current timestamp
return this.video.currentTime;
}
}
/**
* Handles Youtube playback via the official YT embed (gross)
* @extends mediaHandler
*/
class youtubeEmbedHandler extends mediaHandler{
/**
* Instantiates a new Youtube Embed Handler object
* @param {channel} client - Parent Client Management Object
* @param {player} player - Parent Canopy Player Object
* @param {Object} media - De-hydrated media object from server
*/
constructor(client, player, media){
//Call derived constructor
super(client, player, media, 'ytEmbed');
//Set flag to notify functions when the player is actually ready
this.ready = false;
//Create property to hold video iframe for easy access
this.iframe = null;
}
//custom start media function since we want the youtube player to call the start function once it's ready
startMedia(media){
//If we properly ingested the media
if(this.ingestMedia(media)){
//Build the video player
this.buildPlayer();
}
}
buildPlayer(){
//If the embed API hasn't loaded
if(!this.client.ytEmbedAPILoaded){
//Complain and stop
return console.warn("youtubeEmbedHandler.buildPlayer() Called before YT Iframe API Loaded, waiting on refresh to rebuild...");
}
//Create temp div for yt api to replace
const tempDiv = document.createElement('div');
//Name the div
tempDiv.id = "youtube-embed-player"
//Append it to the video container
this.player.videoContainer.appendChild(tempDiv);
//Create a new youtube player using the official YT iframe-embed api
this.video = new YT.Player('youtube-embed-player', {
//Inject video id
videoId: this.nowPlaying.id,
events: {
'onReady': this.start.bind(this),
'onStateChange': this.onStateChange.bind(this)
}
});
//Call derived function
super.buildPlayer();
}
start(){
//Call derived start function
super.start();
//Set volume based on player volume
this.video.setVolume(this.player.volume * 100);
//Kick the video off
this.video.playVideo();
//Pull iframe
this.iframe = this.video.getIframe()
//Throw the ready flag
this.ready = true;
}
destroyPlayer(){
//If we've had enough time to create a player frame
if(this.ready){
//Pull volume from player before destroying since google didn't give us a volume change event like a bunch of dicks
this.player.volume = (this.video.getVolume() / 100);
//Use the embed api's built in destroy function
this.video.destroy();
}
//Check the f̶r̶i̶d̶g̶e video container for leftovers
const leftovers = this.player.videoContainer.querySelector("#youtube-embed-player");
//If we have any leftovers
if(leftovers != null){
//Nukem like last nights chicken
leftovers.remove();
}
//Call derived destroy function
super.destroyPlayer();
}
sync(timestamp = this.lastTimestamp){
//If we're not ready
if(!this.ready){
//Kick off a timer to wait it out and try again l8r
setTimeout(this.sync.bind(this), 100);
//If it failed, tell randy to fuck off
return;
}
//Seek to timestamp, allow buffering
this.video.seekTo(timestamp, true);
}
reload(){
//if we're ready
if(this.ready){
//re-load the video by id
this.video.loadVideoById(this.nowPlaying.id);
}
}
play(){
//If we're ready
if(this.ready){
//play the video
this.video.playVideo();
}
}
pause(){
//If we're ready
if(this.ready){
//pause the video
this.video.pauseVideo();
}
}
getRatio(){
//TODO: Implement a type-specific metadata property object in the media class to hold type-sepecifc meta-data
//Alternatively we could fill in resolution information from the raw link
//However keeping embedded functionality dependant on raw-links seems like bad practice
}
getTimestamp(){
//If we're ready
if(this.ready){
//Return the timestamp
return this.video.getCurrentTime();
}
//If we fall through, simply report that the video hasn't gone anywhere yet
return 0;
}
setVideoTitle(){
//Clear out the player title so that youtube's baked in title can do it's thing.
//This will be replaced once we complete the full player control and remove the defualt youtube UI
this.player.title.textContent = "";
}
/**
* Generic handler for state changes since google is a dick
*/
onStateChange(event){
switch(event.data){
//video unstarted
case -1:
return;
//video ended
case 0:
return;
//video playing
case 1:
return;
//video paused
case 2:
super.onPause(event);
return;
//video buffering
case 3:
//There is no good way to tell slow connections apart from user seeking
//This will be easier to implement once we get custom player controls up
//super.onSeek(event);
return;
//video queued
case 5:
return;
//bad status code
default:
return;
}
}
setPlayerLock(lock){
super.setPlayerLock(lock);
if(this.ready){
this.iframe.style.pointerEvents = (lock ? "none" : "");
}
}
}
/**
* Base HLS Media handler for handling all HLS related media
* @extends rawFileBase
*/
class hlsBase extends rawFileBase{
/**
* Instantiates a new HLS Base object
* @param {channel} client - Parent Client Management Object
* @param {player} player - Parent Canopy Player Object
* @param {Object} media - De-hydrated media object from server
* @param {String} type - Media Handler Source Type
*/
constructor(client, player, media, type){
//Call derived constructor
super(client, player, media, type);
}
buildPlayer(){
//Call derived buildPlayer function
super.buildPlayer();
//Instantiate HLS object
this.hls = new Hls();
//Load HLS Stream
this.hls.loadSource(this.nowPlaying.url);
//Attatch hls object to video element
this.hls.attachMedia(this.video);
//Bind onMetadataLoad to MANIFEST_PARSED
this.hls.on(Hls.Events.MANIFEST_PARSED, this.onMetadataLoad.bind(this));
}
end(){
//Stop hls.js from loading any more of the stream
this.hls.stopLoad();
//Call derived method
super.end();
}
onMetadataLoad(){
//Call derived method
super.onMetadataLoad();
}
start(){
//Call derived method
super.start();
//Start the video
this.video.play();
}
}
/**
* HLS Livestream Handler
* @extends hlsBase
*/
class hlsLiveStreamHandler extends hlsBase{
/**
* Instantiates a new HLS Live Stream Handler object
* @param {channel} client - Parent Client Management Object
* @param {player} player - Parent Canopy Player Object
* @param {Object} media - De-hydrated media object from server
*/
constructor(client, player, media){
//Call derived constructor
super(client, player, media, "livehls");
//Create variable to determine if we need to resync after next seek
this.reSync = false;
this.video.addEventListener('pause', this.onPause.bind(this));
this.video.addEventListener('seeked', this.onSeek.bind(this));
this.video.addEventListener('waiting', this.onBuffer.bind(this));
}
sync(){
//Kick the video back on if it was paused
this.video.play();
//Pull video duration
const duration = this.video.duration;
//Ignore bad timestamps
if(duration > 0){
//Seek to the end to sync up w/ the livestream
this.video.currentTime = duration;
}
}
setVideoTitle(title){
//Add title as text content for security :P
this.player.title.textContent = `: ${title}`;
//Create glow span
const glowSpan = document.createElement('span');
//Fill glow span content
glowSpan.textContent = "🔴LIVE";
//Set glowspan class
glowSpan.classList.add('critical-danger-text');
//Inject glowspan into title in a way that allows it to be easily replaced
this.player.title.prepend(glowSpan);
}
onBuffer(event){
//Call derived function
super.onBuffer(event);
//If we're supposed to be synced by the end of buffering
if(this.player.syncLock){
//Throw flag to manually sync since this works entirely differently from literally every other fucking media source
this.reSync = true;
}
}
onSeek(event){
//Call derived method
super.onSeek(event);
//If we stopped playing the video
if(this.video == null){
//Don't worry about it
return;
}
//Calculate distance to end of stream
const difference = this.video.duration - this.video.currentTime;
//If we where buffering under sync lock
if(this.reSync){
//Set reSync to false
this.reSync = false;
//If the difference is bigger than streamSyncTolerance
if(difference > this.player.streamSyncTolerance){
//Sync manually since we have no timestamp, and therefore the player won't do it for us
this.sync();
}
}
}
}</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="addURLPopup.html">addURLPopup</a></li><li><a href="cPanel.html">cPanel</a></li><li><a href="channel.html">channel</a></li><li><a href="chatBox.html">chatBox</a></li><li><a href="chatPostprocessor.html">chatPostprocessor</a></li><li><a href="clearPopup.html">clearPopup</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="defaultTitlesPopup.html">defaultTitlesPopup</a></li><li><a href="emotePanel.html">emotePanel</a></li><li><a href="hlsBase.html">hlsBase</a></li><li><a href="hlsLiveStreamHandler.html">hlsLiveStreamHandler</a></li><li><a href="mediaHandler.html">mediaHandler</a></li><li><a href="newPlaylistPopup.html">newPlaylistPopup</a></li><li><a href="nullHandler.html">nullHandler</a></li><li><a href="panelObj.html">panelObj</a></li><li><a href="player.html">player</a></li><li><a href="playlistManager.html">playlistManager</a></li><li><a href="poppedPanel.html">poppedPanel</a></li><li><a href="queuePanel.html">queuePanel</a></li><li><a href="rawFileBase.html">rawFileBase</a></li><li><a href="rawFileHandler.html">rawFileHandler</a></li><li><a href="renamePopup.html">renamePopup</a></li><li><a href="reschedulePopup.html">reschedulePopup</a></li><li><a href="schedulePopup.html">schedulePopup</a></li><li><a href="settingsPanel.html">settingsPanel</a></li><li><a href="userList.html">userList</a></li><li><a href="youtubeEmbedHandler.html">youtubeEmbedHandler</a></li></ul><h3>Global</h3><ul><li><a href="global.html#onYouTubeIframeAPIReady">onYouTubeIframeAPIReady</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Sat Sep 06 2025 19:07:57 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

View file

@ -1,714 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Class: newPlaylistPopup</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Class: newPlaylistPopup</h1>
<section>
<header>
<h2><span class="attribs"><span class="type-signature"></span></span>newPlaylistPopup<span class="signature">(event, client, doc)</span><span class="type-signature"></span></h2>
<div class="class-description">Class representing pop-up dialogue for creating a new playlist</div>
</header>
<article>
<div class="container-overview">
<h2>Constructor</h2>
<h4 class="name" id="newPlaylistPopup"><span class="type-signature"></span>new newPlaylistPopup<span class="signature">(event, client, doc)</span><span class="type-signature"></span></h4>
<div class="description">
Instantiates a New Playlist Popup
</div>
<h5>Parameters:</h5>
<table class="params">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th class="last">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="name"><code>event</code></td>
<td class="type">
<span class="param-type">Event</span>
</td>
<td class="description last">Event passed down from Event Listener</td>
</tr>
<tr>
<td class="name"><code>client</code></td>
<td class="type">
<span class="param-type"><a href="channel.html">channel</a></span>
</td>
<td class="description last">Parent Client Management Object</td>
</tr>
<tr>
<td class="name"><code>doc</code></td>
<td class="type">
<span class="param-type">Document</span>
</td>
<td class="description last">Current owner documnet of the panel, so we know where to drop our pop-up</td>
</tr>
</tbody>
</table>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_playlistManager.js.html">panels/queuePanel/playlistManager.js</a>, <a href="panels_queuePanel_playlistManager.js.html#line643">line 643</a>
</li></ul></dd>
</dl>
</div>
<h3 class="subsection-title">Members</h3>
<h4 class="name" id="client"><span class="type-signature"></span>client<span class="type-signature"></span></h4>
<div class="description">
Parent Client Management Object
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_playlistManager.js.html">panels/queuePanel/playlistManager.js</a>, <a href="panels_queuePanel_playlistManager.js.html#line654">line 654</a>
</li></ul></dd>
</dl>
<h4 class="name" id="popup"><span class="type-signature"></span>popup<span class="type-signature"></span></h4>
<div class="description">
canopyUXUtils.popup() object
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_playlistManager.js.html">panels/queuePanel/playlistManager.js</a>, <a href="panels_queuePanel_playlistManager.js.html#line661">line 661</a>
</li></ul></dd>
</dl>
<h3 class="subsection-title">Methods</h3>
<h4 class="name" id="asyncConstructor"><span class="type-signature"></span>asyncConstructor<span class="signature">()</span><span class="type-signature"></span></h4>
<div class="description">
Continuation of object construction, called after child popup object construction
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_playlistManager.js.html">panels/queuePanel/playlistManager.js</a>, <a href="panels_queuePanel_playlistManager.js.html#line667">line 667</a>
</li></ul></dd>
</dl>
<h4 class="name" id="createPlaylist"><span class="type-signature"></span>createPlaylist<span class="signature">(event)</span><span class="type-signature"></span></h4>
<div class="description">
Sends request to create a playlist off to the server
</div>
<h5>Parameters:</h5>
<table class="params">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th class="last">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="name"><code>event</code></td>
<td class="type">
<span class="param-type">Event</span>
</td>
<td class="description last">Event passed down from Event Listener</td>
</tr>
</tbody>
</table>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_playlistManager.js.html">panels/queuePanel/playlistManager.js</a>, <a href="panels_queuePanel_playlistManager.js.html#line689">line 689</a>
</li></ul></dd>
</dl>
<h4 class="name" id="setupInput"><span class="type-signature"></span>setupInput<span class="signature">()</span><span class="type-signature"></span></h4>
<div class="description">
Defines input-related Event Handlers
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_playlistManager.js.html">panels/queuePanel/playlistManager.js</a>, <a href="panels_queuePanel_playlistManager.js.html#line679">line 679</a>
</li></ul></dd>
</dl>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="addURLPopup.html">addURLPopup</a></li><li><a href="cPanel.html">cPanel</a></li><li><a href="channel.html">channel</a></li><li><a href="chatBox.html">chatBox</a></li><li><a href="chatPostprocessor.html">chatPostprocessor</a></li><li><a href="clearPopup.html">clearPopup</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="defaultTitlesPopup.html">defaultTitlesPopup</a></li><li><a href="emotePanel.html">emotePanel</a></li><li><a href="hlsBase.html">hlsBase</a></li><li><a href="hlsLiveStreamHandler.html">hlsLiveStreamHandler</a></li><li><a href="mediaHandler.html">mediaHandler</a></li><li><a href="newPlaylistPopup.html">newPlaylistPopup</a></li><li><a href="nullHandler.html">nullHandler</a></li><li><a href="panelObj.html">panelObj</a></li><li><a href="player.html">player</a></li><li><a href="playlistManager.html">playlistManager</a></li><li><a href="poppedPanel.html">poppedPanel</a></li><li><a href="queuePanel.html">queuePanel</a></li><li><a href="rawFileBase.html">rawFileBase</a></li><li><a href="rawFileHandler.html">rawFileHandler</a></li><li><a href="renamePopup.html">renamePopup</a></li><li><a href="reschedulePopup.html">reschedulePopup</a></li><li><a href="schedulePopup.html">schedulePopup</a></li><li><a href="settingsPanel.html">settingsPanel</a></li><li><a href="userList.html">userList</a></li><li><a href="youtubeEmbedHandler.html">youtubeEmbedHandler</a></li></ul><h3>Global</h3><ul><li><a href="global.html#onYouTubeIframeAPIReady">onYouTubeIframeAPIReady</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Sat Sep 06 2025 19:07:57 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

File diff suppressed because it is too large Load diff

View file

@ -1,918 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Class: panelObj</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Class: panelObj</h1>
<section>
<header>
<h2><span class="attribs"><span class="type-signature"></span></span>panelObj<span class="signature">(client, name, pageURL, panelDocument)</span><span class="type-signature"></span></h2>
<div class="class-description">Template Class for other Classes for Objects which represent a single Canopy Panel</div>
</header>
<article>
<div class="container-overview">
<h2>Constructor</h2>
<h4 class="name" id="panelObj"><span class="type-signature"></span>new panelObj<span class="signature">(client, name, pageURL, panelDocument)</span><span class="type-signature"></span></h4>
<div class="description">
Instantiates a new Panel Object
</div>
<h5>Parameters:</h5>
<table class="params">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Default</th>
<th class="last">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="name"><code>client</code></td>
<td class="type">
<span class="param-type"><a href="channel.html">channel</a></span>
</td>
<td class="default">
</td>
<td class="description last">Parent client Management Object</td>
</tr>
<tr>
<td class="name"><code>name</code></td>
<td class="type">
<span class="param-type">String</span>
</td>
<td class="default">
Placeholder Panel
</td>
<td class="description last">Panel Name</td>
</tr>
<tr>
<td class="name"><code>pageURL</code></td>
<td class="type">
<span class="param-type">String</span>
</td>
<td class="default">
/panel/placeholder
</td>
<td class="description last">Panel Default Page URL</td>
</tr>
<tr>
<td class="name"><code>panelDocument</code></td>
<td class="type">
<span class="param-type">Document</span>
</td>
<td class="default">
</td>
<td class="description last">Panel Document</td>
</tr>
</tbody>
</table>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="cpanel.js.html">cpanel.js</a>, <a href="cpanel.js.html#line263">line 263</a>
</li></ul></dd>
</dl>
</div>
<h3 class="subsection-title">Members</h3>
<h4 class="name" id="client"><span class="type-signature"></span>client<span class="type-signature"></span></h4>
<div class="description">
Parent Client Management object
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="cpanel.js.html">cpanel.js</a>, <a href="cpanel.js.html#line295">line 295</a>
</li></ul></dd>
</dl>
<h4 class="name" id="name"><span class="type-signature"></span>name<span class="type-signature"></span></h4>
<div class="description">
Panel Name
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="cpanel.js.html">cpanel.js</a>, <a href="cpanel.js.html#line275">line 275</a>
</li></ul></dd>
</dl>
<h4 class="name" id="ownerDoc"><span class="type-signature"></span>ownerDoc<span class="type-signature"></span></h4>
<div class="description">
Current root document panel doc lives within
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="cpanel.js.html">cpanel.js</a>, <a href="cpanel.js.html#line290">line 290</a>
</li></ul></dd>
</dl>
<h4 class="name" id="pageURL"><span class="type-signature"></span>pageURL<span class="type-signature"></span></h4>
<div class="description">
Panel Default Page URL
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="cpanel.js.html">cpanel.js</a>, <a href="cpanel.js.html#line280">line 280</a>
</li></ul></dd>
</dl>
<h4 class="name" id="panelDocument"><span class="type-signature"></span>panelDocument<span class="type-signature"></span></h4>
<div class="description">
Panel Document
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="cpanel.js.html">cpanel.js</a>, <a href="cpanel.js.html#line285">line 285</a>
</li></ul></dd>
</dl>
<h3 class="subsection-title">Methods</h3>
<h4 class="name" id="closer"><span class="type-signature"></span>closer<span class="signature">()</span><span class="type-signature"></span></h4>
<div class="description">
Called upon panel close/exit
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>