Skip to content

v3 & Harmony diary / thoughts / code snippets

Announcements
  • Hi All,

    I thought that seeing as I’ve been quiet for a while that I’d start a new thread that will detail the journey Sudonix is taking to reach BS5, v3 of NodeBB, and the adoption of the Harmony theme. I’ve been playing with the “Nord” theme, and am pleased to report that the CSS needed since the move from standard CSS to LESS / SCSS has been remarkably reduced. Presently, I’m at 324 lines - a huge difference to the 4,479 (yes, that’s right…) currently in use here.

    And no doubt you’re curious as to what v3.beta1 of NodeBB looks like, combined with Harmony, and the theme I’m working on?

    Here’s what the standard iteration looks like via https://community.nodebb.org

    8afb6691-68ef-4c34-8de8-088808181b75-image.png

    And here’s what the Nord theme on https://sudonix.dev looks like

    WARNING: Extreme sexiness ahead 🙂

    6f377bf7-f1c8-4908-a18c-cdbf473d2d3e-image.png

    cfbb2f07-1549-41fe-a937-3ccffdead9c4-image.png

    3c2cc439-bfbb-49f7-82b5-488740c93d5b-image.png

    The Swatch script is also functional

    fd3020e9-ec4f-4df8-9d79-9ca8262fe005-image.png

    Interestingly, this differs heavily from v2 (mostly because the jump from BS3 to BS5 is significant). Currently, it looks like this in v2

    // ------------------------------------------
    // Swatch Applet
    // ------------------------------------------
    
    
    
    
    $(document).ready(function() {
    	function generateRandomString(length) {
    
    		var text = "";
    		var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
    
    		for (var i = 0; i < length; i++) {
    			text += possible.charAt(Math.floor(Math.random() * possible.length));
    		}
    
    		return text;
    	}
    	var string = generateRandomString(10);
    	$("#random_string").text(string);
    	var whichTheme = localStorage.getItem("theme");
    	var activeTheme = localStorage.getItem("activeTheme");
    	// If no theme is detected (for example, a new visitor), then set this to default
    	if (!whichTheme) {
    		// dark-mode media query matched or not
    		let matched = window.matchMedia('(prefers-color-scheme: dark)').matches;
    		//var override = getUrlParameter('override');
    
    		if (matched) {
    			// Offer the mifnight theme by default
    			whichTheme = "midnight";
    			activeTheme = "/assets/customcss/midnight.css?version=" + string;
    			//$("link[rel=stylesheet]").attr('href' , thishref + "?version=" + string + "");
    		} else {
    			// Leave the default theme intact
    			whichTheme = "default";
    			activeTheme = "/assets/customcss/daylight.css?version=" + string;
    		}
    		// See if override has been enabled
    		if (whichTheme === 'default') {
    			// Sudonix is overriding operating system settings and will force dark scheme
    			activeTheme = "/assets/customcss/daylight.css?version=" + string;
    		}
    		if (whichTheme === 'daylight') {
    			// $('[component="post"]').addClass("background");
    			$('li.self-post .content:not(.isSolved [component="post/content"]').addClass("response");
    		} else {
    			// Nothing to do :)
    		}
    	}
    
    	/*$(".forum-logo").attr("src","/assets/uploads/system/sl_" + whichTheme + ".webp?version=" + string + ""); */
    	var panel = $('<li id="switcher" class="dropdown"> \
    <a title="" data-original-title="Swatch" class="navigation-link dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> \
    <i id="ticon" class="fa fa-light fa-swatchbook" data-content="" aria-hidden="true"></i> \
    <span class="visible-xs-inline">Swatch</span> \
    <i class="fa fa-caret-down" aria-hidden="true"></i> \
    </a> \
    <ul id="theme" class="dropdown-menu"> \
    <li><a id="default" href="#" rel="/assets/customcss/daylight.css">Default</a></li> \
    <li><a id="anthracite" href="#" rel="/assets/customcss/anthracite.css">Anthracite</a></li> \
    <li><a id="darkly" href="#" rel="/assets/customcss/darkly.css">Darkly</a></li> \
    <li><a id="daylight" href="#" rel="/assets/customcss/daylight.css">Daylight</a></li> \
    <li><a id="facetube" href="#" rel="/assets/customcss/facetube.css">FaceTube</a></li> \
    <li><a id="greybird" href="#" rel="/assets/customcss/greybird.css">Greybird</a></li> \
    <li><a id="midnight" href="#" rel="/assets/customcss/midnight.css">Midnight</a></li> \
    <li><a id="nord" href="#" rel="/assets/customcss/nord.css">Nord</a></li> \
    <li><a id="slate" href="#" rel="/assets/customcss/slate.css">Slate</a></li> \
    <li><a id="superhero" href="#" rel="/assets/customcss/superhero.css">Superhero</a></li> \
    <li><a id="tempest" href="#" rel="/assets/customcss/tempest.css">Tempest</a></li> \
    <li><a id="twitter" href="#" rel="/assets/customcss/twitter.css">Twitter</a></li> \
    </ul> \
    </li> \
    </div> \
    ');
    
    
    	if (whichTheme) {
    		$.get(activeTheme, function(css) {
    			$('<style type="text/css"></style>')
    				.html(css)
    				.appendTo("head");
    		});
    	} else {}
    
    	$('#main-nav').append(panel);
    
    	if (utils.findBootstrapEnvironment() === 'xs') {
    		$('#main-nav').append(panel);
    	}
    
    	$(document).ready(function() {
    
    		$("body").on("click change", "#theme li a", function() {
    			var thishref = $(this).attr('rel') + '?version=' + string;
    			$.get(thishref, function(css) {
    				$('<style type="text/css"></style>')
    					.html(css)
    					.appendTo("head");
    			});
    			console.log("Applying swatch " + thishref);
    			//location.reload();
    			var selected = $(this).attr("id");
    			var theTheme = $(this).attr("rel");
    			if (selected === 'default') {
    				localStorage.setItem("theme", selected);
    				localStorage.setItem("activeTheme", "/assets/customcss/daylight.css?version=" + string);
    				//location.reload();
    			} else {
    				localStorage.setItem("theme", selected);
    				localStorage.setItem("activeTheme", theTheme);
    			}
    			return false;
    		});
    	});
    });
    

    Now in v3, it looks VERY different

    var mobiledropdown = $('<li class="nav-item  dropend" title="Swatch"> \
    	<a class="nav-link nav-btn navigation-link px-3 py-2 dropdown-toggle" href="#" role="button" data-bs-toggle="collapse" data-bs-target="#theme" onclick="event.stopPropagation();"> \
    		<span class="d-inline-flex justify-content-between align-items-center w-100"> \
    			<span class="text-nowrap truncate-open"> \
    				<i class="fa fa-fw fa-painbrush" data-content="" aria-hidden="true"></i>
    				<span class="nav-text visible-open px-2 fw-semibold">More</span>
    			</span>
    			<span component="navigation/count" class="visible-open badge rounded-1 bg-primary hidden"></span>
    		</span>
    	</a>
    	<div class="ps-3">
    		<ul id="theme" class="collapse list-unstyled ps-3"> \
    			<li><a id="default" class="dropdown-item rounded-1" href="#" rel="/assets/customcss/daylight.css">Default</a></li> \
    			<li><a id="anthracite" class="dropdown-item rounded-1" href="#" rel="/assets/customcss/anthracite.css">Anthracite</a></li> \
    			<li><a id="darkly" class="dropdown-item rounded-1" href="#" rel="/assets/customcss/darkly.css">Darkly</a></li> \
    			<li><a id="daylight" class="dropdown-item rounded-1" href="#" rel="/assets/customcss/daylight.css">Daylight</a></li> \
    			<li><a id="facetube" class="dropdown-item rounded-1" href="#" rel="/assets/customcss/facetube.css">FaceTube</a></li> \
    			<li><a id="greybird" class="dropdown-item rounded-1" href="#" rel="/assets/customcss/greybird.css">Greybird</a></li> \
    			<li><a id="midnight" class="dropdown-item rounded-1" href="#" rel="/assets/customcss/midnight.css">Midnight</a></li> \
    			<li><a id="nord" class="dropdown-item rounded-1" href="#" rel="/assets/customcss/nord.css">Nord</a></li> \
    			<li><a id="slate" class="dropdown-item rounded-1" href="#" rel="/assets/customcss/slate.css">Slate</a></li> \
    			<li><a id="superhero" class="dropdown-item rounded-1" href="#" rel="/assets/customcss/superhero.css">Superhero</a></li> \
    			<li><a id="tempest" class="dropdown-item rounded-1" href="#" rel="/assets/customcss/tempest.css">Tempest</a></li> \
    			<li><a id="twitter" class="dropdown-item rounded-1" href="#" rel="/assets/customcss/twitter.css">Twitter</a></li> \
    		</ul>
    	</div>
    	
    </li>
    ');
    
    var desktopnavbar = $('<li id="switcher" class="nav-item mx-2 dropend" title="Swatch"> \
    	<a class="nav-link nav-btn navigation-link d-flex gap-2 justify-content-between align-items-center dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> \
    					<span class="d-flex gap-2 align-items-center text-nowrap truncate-open"> \
    						<span class="position-relative"> \
    							<i class="fa fa-fw fa-paintbrush" data-content="" aria-hidden="true"></i> \
    							<span component="navigation/count" class="visible-closed position-absolute top-0 start-100 translate-middle badge rounded-1 bg-primary hidden"></span> \
    						</span> \
    						<span class="nav-text small visible-open fw-semibold text-truncate">Swatch</span> \
    					</span> \
    					<span component="navigation/count" class="visible-open badge rounded-1 bg-primary hidden"></span> \
    	</a> \
    	<ul id="theme" class="dropdown-menu overflow-auto" data-popper-placement="right-end"> \
    	<li><a id="default" class="dropdown-item rounded-1" href="#" rel="/assets/customcss/daylight.css">Default</a></li> \
    	<li><a id="anthracite" class="dropdown-item rounded-1" href="#" rel="/assets/customcss/anthracite.css">Anthracite</a></li> \
    	<li><a id="darkly" class="dropdown-item rounded-1" href="#" rel="/assets/customcss/darkly.css">Darkly</a></li> \
    	<li><a id="daylight" class="dropdown-item rounded-1" href="#" rel="/assets/customcss/daylight.css">Daylight</a></li> \
    	<li><a id="facetube" class="dropdown-item rounded-1" href="#" rel="/assets/customcss/facetube.css">FaceTube</a></li> \
    	<li><a id="greybird" class="dropdown-item rounded-1" href="#" rel="/assets/customcss/greybird.css">Greybird</a></li> \
    	<li><a id="midnight" class="dropdown-item rounded-1" href="#" rel="/assets/customcss/midnight.css">Midnight</a></li> \
    	<li><a id="nord" class="dropdown-item rounded-1" href="#" rel="/assets/customcss/nord.css">Nord</a></li> \
    	<li><a id="slate" class="dropdown-item rounded-1" href="#" rel="/assets/customcss/slate.css">Slate</a></li> \
    	<li><a id="superhero" class="dropdown-item rounded-1" href="#" rel="/assets/customcss/superhero.css">Superhero</a></li> \
    	<li><a id="tempest" class="dropdown-item rounded-1" href="#" rel="/assets/customcss/tempest.css">Tempest</a></li> \
    	<li><a id="twitter" class="dropdown-item rounded-1" href="#" rel="/assets/customcss/twitter.css">Twitter</a></li> \
    	</ul> \
    	</li> \
    	</div> \
    	');
    

    I’ve yet to decide how many of the v2 swatches will make their way into v3 on Sudonix -likely candidates will be

    • Anthracite
    • Darkly
    • Facetube
    • Midnight
    • Nord
    • Slate
    • Superhero
    • Tempest
    • Twitter

    Also, the “conversational / messenger view” is destined to be scrapped. It looked good at the time, and I know a number of you adopted it for your own forums, but it’s not really “today’s standard” - and, a LOT of effort to code for very little cosmetic return.

    Stay tuned for more updates 🙂

  • Let me know if you have any specific requests around v3 theming 🙂

  • Can’t wait to try this 🙂

  • The colour palette used in the Nord theme (see first post here) is taken from this excellent resource

    https://nordui.netlify.app/

  • @phenomlab

    How to use it ?
    PM if you want.

  • @DownPW You just use the hex codes it provides. Nothing more to it than that.

  • @phenomlab

    On ACP/JS ?

    Nothing on ACP/general/navigation ?

  • @DownPW sorry, I thought you were referring to the Nord theme URL I posted. The Swatch code isn’t ready for use yet.

  • @phenomlab Ok bro. No problem 🙂

  • I’ve made significant progress with the first Swatch template. However, it’s a little way off being completely ready as there are a number of js functions that need to be completed (or in some cases, rewritten) first before the alpha phase of testing can start.

    In addition, there’s the rapid succession of changes and updates to the bootstrap core in NodeBB v3, plus updates to the harmony theme itself, It’s hard to determine the impact this will have on the Swatch code itself. I’ve already found an issue in the tooltip function - as the Swatch code runs after the harmony code, it means that the same function for the tooltip has to be run again.

    This then causes an error to be generated in the console at random intervals. I know why this is - essentially, it’s because destroy as a JS function was removed in BS 4.1 and replaced with dispose - see the below thread raised in the NodeBB forum.

    https://community.nodebb.org/topic/16914/3-0-0-bug-report-thread/56?_=1675107774184

    The advice here is to start NodeBB using Grunt so that the JS remains uncompressed - which should make the destroy function easier to track. None of this is the “end of the world” - it’s merely cosmetic, but could cause issues further down the line, so it’s best to isolate this issue now and remediate - unless there’s a better way to activate the tooltip function again without it throwing the same error.

    Another option here is to use the navigation menu in the ACP which means that the tooltip function should fire as the menu items are already in the DOM. However, taking this route comes with a number of drawbacks in the sense that it isn’t as flexible as adding arbitrary code.

    This is something I’ll need to investigate further but taking this route places additional burden on those using the Swatch code which obviously negates the user experience.

  • More styling work completed today. Started looking at an decent alternative to the scroll top navigator featured on this site. I designed this a while ago and it’s designed to mimic the post scroller in Persona. With a little bit of modification, it would fit harmony very well in my view.

    Currently the code I wrote for this is lying dormant on a VM I created for v3 development but since abandoned when the announcement around the harmony theme was announced. Its going to be hard to walk away from what constitutes around 100 hours of new CSS and js but this was designed for Persona, and my thoughts are given all the fanfare with Harmony, the previous staple theme will fade into obscurity - perhaps no longer developed, meaning I’d have to both fork the last version and maintain it going forward.

    Honestly, I just don’t have time to commit to that, so the decision to turn my back on Persona development has been made, and it’s all harmony from now on. Sadly, this does mean the death knell for the conversation style CSS I developed. I won’t be including this in harmony as it’s difficult to support given it needs to add various new CSS classes into the DOM as posts are being loaded by Ajax. Clearly, this isn’t exactly optimum, and in fact, will slow down the rendering process if poorly coded.

    Not sure if it’s my imagination, but v3 seems “oh so slightly” slower than v2. Literally nothing in it, but noticeable to me so clearly, there’s some room for tuning here which I think will be at the nginx level.

    Stay tuned.

  • @phenomlab said in v3 / Harmony diary:

    perhaps no longer developed

    well, although I understand your choice with Harmony, I believe Persona has its charm and will continue to be developed. you do not think so?

  • @phenomlab it seems i gonne use v2 end of this year. because i see that v2 very fast than v3 i mean page speed, user test etc.

  • @crazycells said in v3 / Harmony diary:

    I believe Persona has its charm and will continue to be developed. you do not think so?

    It’s a feeling I have, and in my experience, something I’ve seen many times. Persona was the default theme shipped with v2, and has been the mainstay of that version for a long time. However, things change, and by NodeBB’s own admission, it needed an update to bring it in line with modern layouts and mobile design. As you know, Harmony is not a fork, but a completely new design that also requires v3 for it to work at all.

    To me, this is the final whistle for Persona. Harmony will supersede it in every way, and eventually, development will stall for the former product with all the focus being on Harmony itself. My view is that Persona will become a community fork to stop it from disappearing completely, but in the same vein, I think most (like me) will jump ship and move to Harmony.

  • @cagatay said in v3 / Harmony diary:

    it seems i gonne use v2 end of this year. because i see that v2 very fast than v3 i mean page speed, user test etc

    Yes, I’m glad I’m not the only one to witness this. I am going to raise this with the NodeBB team

    EDIT - raised in the v3 Bug thread
    https://community.nodebb.org/topic/16914/3-0-0-bug-report-thread/76?_=1675420990079

  • Ok, spun up the dev VM I started working on

    9097e069-c077-40e4-a949-0b4aeacecca2-image.png

    And here’s a video of the reworked pagination / scroller utility I wrote that fires on pages

    https://sudonix.com/assets/uploads/files/screen-capture.webm

    This was originally designed to work in tandem with the Persona scroller / navigation, but would be simple to convert to Harmony and BS5.

  • Today’s playground 🙂

    c67e0b6c-b534-4306-8e58-dbe77e30c6a0-image.png

    Here’s a video… still needs a bit more work, but… 🙂 Notice the newer scroll and progress bar I was talking about earlier…

    https://sudonix.com/assets/uploads/files/footer-added.webm

  • @phenomlab

    Oh It’s very good. I love it 😊

  • @phenomlab said in v3 / Harmony diary:

    Today’s playground 🙂

    c67e0b6c-b534-4306-8e58-dbe77e30c6a0-image.png

    Here’s a video… still needs a bit more work, but… 🙂 Notice the newer scroll and progress bar I was talking about earlier…

    https://sudonix.com/assets/uploads/files/footer-added.webm

    Hello Mark,

    I just tested the functioning of the scroll bar and I saw this bug:

    https://i.imgur.com/VQw5zw5.mp4

    It should be moved to the left so as not to encroach on the collapse button of the custom footer navbar.

    Then, when we play with the collapse button of the custom footer navbar we should:

    • When the custom footer navbar is deactivated: it sticks to the bottom right while not encroaching on the right sidebar. All this taking into account the collapse of the right sidebar (not obvious, I don’t know if I’m clear 🙂 )

    • When the custom footer navbar is activated: it moves just to the left of the floatright block or can be above the floatright block?

    The solution may be less difficult to code would be to make a vertical scrollbar inside the right sidebar like in topics. There might be less to manage

    Keep the good work my friend 😉


Related Topics
  • Opening links in nodebb widget

    Solved Configure
    6
    4 Votes
    6 Posts
    297 Views

    A more efficient way of including this would be to not over complicate it and leverage a standard iframe (providing the CSP headers of the remote site permit this) like below

    <iframe src="https://www.classmarker.com/online-test/start/?quiz=gag66aea7922f0a5" width="700" height="800"></iframe>

    This works first time every time on your site as intended.

  • CSS code customization for the link preview plugin

    Solved Customisation
    4
    3 Votes
    4 Posts
    598 Views

    @crazycells said in CSS code customization for the link preview plugin:

    does OGProxy show the pdf previews as well?

    Not yet, but it could with a bit of additional code.

  • Page control arrows for PWA

    Solved Customisation
    27
    25 Votes
    27 Posts
    1k Views

    @crazycells it is, yes - I think I’ll leave it as there is no specific PWA CSS classes I know of. Well, you could use something like the below, but this means multiple CSS files for different operating systems.

    /** * Determine the mobile operating system. * This function returns one of 'iOS', 'Android', 'Windows Phone', or 'unknown'. * * @returns {String} */ function getMobileOperatingSystem() { var userAgent = navigator.userAgent || navigator.vendor || window.opera; // Windows Phone must come first because its UA also contains "Android" if (/windows phone/i.test(userAgent)) { return "Windows Phone"; } if (/android/i.test(userAgent)) { return "Android"; } if (/iPad|iPhone|iPod/.test(userAgent) && !window.MSStream) { return "iOS"; } return "unknown"; // return “Android” - one should either handle the unknown or fallback to a specific platform, let’s say Android }

    Once you’re in that rabbit hole, it’s impossible to get out of it.

  • Bug in Nodebb route when clicking title

    Moved Configure
    3
    2 Votes
    3 Posts
    223 Views

    Ah silly me, thanks for finding that!

  • restarting nodebb on boot

    Unsolved Configure
    3
    1 Votes
    3 Posts
    298 Views

    @eeeee said in restarting nodebb on boot:

    can I just run nodebb under nodemon for auto restarts?

    It’s a better method. Nodemon just looks for file system changes and would effectively die if the server was rebooted meaning you’d have to start it again anyway. Systemd is the defacto standard which is how the operating system interacts in terms of services, scheduled tasks etc.

  • Iframely (Nodebb)

    Solved Configure
    40
    4 Votes
    40 Posts
    3k Views

    @DownPW This is now resolved. The issue was an incorrect URL specified in the Nodebb plugin. I’ve corrected this, and now it works as intended.

  • Easier to read code and pre blocks

    Announcements
    1
    1 Votes
    1 Posts
    288 Views
    No one has replied
  • Platform development diary

    Announcements
    9
    1 Votes
    9 Posts
    764 Views

    It’s been a while since I checked in here. Plenty going on - mostly around rectifying small pockets of resistance between light and dark modes, plus the addition of new features such as an enhanced reputation system and the ability to create polls. Plus, there are several changes going on under the hood which are completely transparent to users or the operation of the platform.

    However, some changes mean that the platform does need to be restarted for code changes to stick and function correctly. I tend to do this during non busy periods, but sometimes, it’s unfortunately inevitable. The good news is that in most cases, a full restart takes only 20 seconds.

    More to come