From 49684b32a11bb3a6317a90ed385b64d12e0d5188 Mon Sep 17 00:00:00 2001 From: rainbow napkin Date: Sun, 17 May 2026 18:48:11 -0400 Subject: [PATCH 1/4] Added portrait orientation mode to channel UX. --- www/js/channel/chat.js | 86 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 74 insertions(+), 12 deletions(-) diff --git a/www/js/channel/chat.js b/www/js/channel/chat.js index 5311fb5..2793cc5 100644 --- a/www/js/channel/chat.js +++ b/www/js/channel/chat.js @@ -38,6 +38,11 @@ class chatBox{ */ this.autoScroll = true; + /** + * Whether or not the screen is currently in portrait mode + */ + this.portrait = false; + /** * Chat-Width Minimum while sized to media Aspect-Ratio */ @@ -74,6 +79,11 @@ class chatBox{ this.chatPostprocessor = new chatPostprocessor(client); //Element Nodes + /** + * Channel Div + */ + this.channelDiv = document.querySelector("#channel-flexbox"); + /** * Chat Panel Container Div */ @@ -473,8 +483,14 @@ class chatBox{ resizeAspect(event){ const playerHidden = this.client.player.playerDiv.style.display == "none"; - //If the aspect is locked and the player is hidden - if(this.aspectLock && !playerHidden){ + //If window is taller than wide and not in portrait mode, or vice-versa + if(this.portrait != (window.innerWidth <= window.innerHeight)){ + //Toggle portrait mode + this.togglePortrait(); + } + + //If the aspect is locked/the window is portrait and the player isn't hidden + if((this.aspectLock || this.portrait) && !playerHidden){ this.sizeToAspect(); //Otherwise }else{ @@ -490,24 +506,70 @@ class chatBox{ * Re-sizes chat box relative to media aspect ratio */ sizeToAspect(){ + //If the chat panel is visible 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; + //If our window width is more than or equal to window height (not portrait mode) + if(!this.portrait){ + //Get target video width by multiplying media ratio by window height + var targetVidWidth = this.client.player.getRatio() * this.chatPanel.getBoundingClientRect().height; + //Get target chat width my subtracting target media width from total window width + 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`; + //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`; + }else{ + //Calculate target video height from media aspect ratio and window width + var targetVidHeight = window.innerWidth / this.client.player.getRatio(); + //Calculate target chat height from the difference between the channel div height and the target video height + var targetChatHeight = this.channelDiv.getBoundingClientRect().height - targetVidHeight; + + //Set div heights accordingly + this.client.player.playerDiv.style.height = `${targetVidHeight}px`; + this.chatPanel.style.height = `${targetChatHeight}px`; + } - //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(); } + } + } + + togglePortrait(){ + //If our window width is more than or equal to window height (not portrait mode) + if(window.innerWidth >= window.innerHeight){ + //Disable portrait CSS modifiers + this.channelDiv.style.flexDirection = "row"; + this.clickDragger.enabled = true; + this.chatPanel.style.width = ""; + this.client.player.playerDiv.style.height = ""; + this.chatPanel.style.height = ""; + + //Disable portrait behavior modifiers + this.portrait = false; + + //resize player in-case of empty flex basis + this.resizeAspect(); + }else{ + //Modify CSS for portrait displays + this.channelDiv.style.flexDirection = "column"; + this.clickDragger.enabled = false; + this.chatPanel.style.width = "100%"; + this.chatPanel.style.flexBasis = ""; + + //Enable portrait behavior modifiers + this.portrait = true; + + //resize player to correct height + this.resizeAspect(); } - } + } /** * Toggles Chat Box UX From 9f52d13bded59e812c416c0dfaf98b2b85dafc0e Mon Sep 17 00:00:00 2001 From: rainbow napkin Date: Sun, 17 May 2026 18:56:59 -0400 Subject: [PATCH 2/4] Corrected invisible whitespace on chromium-based browsers for line-breaks in long words. --- www/js/channel/chatPostprocessor.js | 4 ++-- www/js/channel/commandPreprocessor.js | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/www/js/channel/chatPostprocessor.js b/www/js/channel/chatPostprocessor.js index a1b7e43..8dd9c31 100644 --- a/www/js/channel/chatPostprocessor.js +++ b/www/js/channel/chatPostprocessor.js @@ -144,7 +144,7 @@ class chatPostprocessor{ //with negative lookaheads to exclude file seperators so we don't split link placeholders, dashes so we dont split usernames and other things, and accented characters to keep those from splitting boundries too //Also split by any invisble whitespace as a crutch to handle mushed links/emotes //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(/(? { @@ -474,7 +474,7 @@ class chatPostprocessor{ //After eight characters if(charIndex > 8){ //Push an invisible line-break character between every character - wordArray.push("ㅤ"); + wordArray.push("​"); } }); diff --git a/www/js/channel/commandPreprocessor.js b/www/js/channel/commandPreprocessor.js index 1dbc218..efed2ab 100644 --- a/www/js/channel/commandPreprocessor.js +++ b/www/js/channel/commandPreprocessor.js @@ -116,14 +116,14 @@ class commandPreprocessor{ */ processEmotes(){ //inject invisible whitespace in-between emotes to prevent from mushing links together - this.message = this.message.replaceAll('][',']ㅤ['); + 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}ㅤ`); + this.message = this.message.replaceAll(`[${emote.name}]`, `​${emote.link}​`); }); }); } @@ -135,13 +135,13 @@ class commandPreprocessor{ //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); + 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)){ + 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 From 2a20a6de01b6ecb9b4196cdfbda885e3c6af783f Mon Sep 17 00:00:00 2001 From: rainbow napkin Date: Sun, 17 May 2026 18:58:51 -0400 Subject: [PATCH 3/4] Fixed failed chat sanatization causing unexpected server-side exception because I did a gross hack which I would've been forced to make cleaner had I used typescript over js XP --- src/app/chatPreprocessor.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/chatPreprocessor.js b/src/app/chatPreprocessor.js index a4331ab..dc3d4bd 100644 --- a/src/app/chatPreprocessor.js +++ b/src/app/chatPreprocessor.js @@ -57,7 +57,7 @@ class chatPreprocessor{ //If we don't pass sanatization/validation turn this car around if(!this.sanatizeCommand(commandObj)){ - return; + return false; } //split the command From 73aab6f20e4d598b3e257d17c456e333c01283de Mon Sep 17 00:00:00 2001 From: rainbow napkin Date: Sun, 17 May 2026 19:11:27 -0400 Subject: [PATCH 4/4] Updated Version Number --- README.md | 2 +- package.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b333dfb..a61d209 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Canopy -0.1-Alpha (Panama Red) - Hotfix 2 +0.1-Alpha (Panama Red) - Hotfix 3 ========= Canopy - /ˈkæ.nə.pi/: diff --git a/package.json b/package.json index ee1cb25..c66dc18 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "canopy-of-alpha", - "version": "0.1.2", - "canopyDisplayVersion": "0.1-Alpha (Panama Red) - Hotfix 2", + "version": "0.1.3", + "canopyDisplayVersion": "0.1-Alpha (Panama Red) - Hotfix 3", "license": "AGPL-3.0-only", "dependencies": { "@braintree/sanitize-url": "^7.1.1",