Skip to content

Threaded post support for NodeBB

Let's Build It
146 5 104.1k 1
  • Better for me now @phenomlab .

    I have just seen this bug (see the video below)

    EXPLAIN:

    I have Threading mode ON, Space bettween timeline event and topic is good. I refresh the page with CTRL + F5.

    The last post is displayed highlighted then the div moves to its original place (too bad, it’s long) with the desired margin and then the beetween timeline event and topic space is no longer the same. To find the correct space that we had before refreshing the page, I have to deactivate Thread mode and reactivate it

    • It’s a shame to wait until the last message is highlighted for the margin to take effect.
    • It’s also a shame to deactivate/reactivate the mode to find the correct margin.

    VIDEO FOR BETTER EXPLAIN :

    blink2.gif


    Other things, I deactivate threadeChat function in function.js because don’t work for now now 😉

    @DownPW Not layout shift in fact, but a lack of CSS being added during the ajaxify.end - in other words, this class is not being added on page load

    li.necro-post.text-muted.timeline-event.d-flex.gap-2.pt-4.threaded {    
        background: transparent !important;
        margin-bottom: 10px !important;
    }
    

    I believe this is because this component type is being added after the page loads, so in fact, it is not in the DOM when the page loads initially, so it is skipped. I have the same issue on Sudonix, but work around it using the below additional CSS

    .page-topic .topic .posts.timeline [component="topic/event"].timeline-event, .page-topic .topic .posts.timeline [component="topic/necro-post"].timeline-event {
        margin-bottom: 10px;
    }
    

    I’ve added this to the CSS on your site, which “fixes” (albeit dirty) the problem

  • @phenomlab said in Threading support for NodeBB:

    @DownPW Not layout shift in fact, but a lack of CSS being added during the ajaxify.end - in other words, this class is not being added on page load

    li.necro-post.text-muted.timeline-event.d-flex.gap-2.pt-4.threaded {    
        background: transparent !important;
        margin-bottom: 10px !important;
    }
    

    I believe this is because this component type is being added after the page loads, so in fact, it is not in the DOM when the page loads initially, so it is skipped. I have the same issue on Sudonix, but work around it using the below additional CSS

    .page-topic .topic .posts.timeline [component="topic/event"].timeline-event, .page-topic .topic .posts.timeline [component="topic/necro-post"].timeline-event {
        margin-bottom: 10px;
    }
    

    I’ve added this to the CSS on your site, which “fixes” (albeit dirty) the problem

    Don’t see this directive on ACP/CSS. I add it myself seems that work.

    Currently, the last message is highlighted (in blue on the gif) and then the reply block shifts to the left (using the margin)

    Is there a possibility that the block will move to the margin directly rather than waiting for the blue indicator to disappear?

    It’s a shame to wait until the blue border-left disappears and then the block shifts to the left.
    It would have to shift directly without waiting

    edit :

    thats seems to work here, no delay :

    d91cde78-0c61-45a7-9f44-245db18db2d8-image.png

  • @phenomlab said in Threading support for NodeBB:

    @DownPW Not layout shift in fact, but a lack of CSS being added during the ajaxify.end - in other words, this class is not being added on page load

    li.necro-post.text-muted.timeline-event.d-flex.gap-2.pt-4.threaded {    
        background: transparent !important;
        margin-bottom: 10px !important;
    }
    

    I believe this is because this component type is being added after the page loads, so in fact, it is not in the DOM when the page loads initially, so it is skipped. I have the same issue on Sudonix, but work around it using the below additional CSS

    .page-topic .topic .posts.timeline [component="topic/event"].timeline-event, .page-topic .topic .posts.timeline [component="topic/necro-post"].timeline-event {
        margin-bottom: 10px;
    }
    

    I’ve added this to the CSS on your site, which “fixes” (albeit dirty) the problem

    Don’t see this directive on ACP/CSS. I add it myself seems that work.

    Currently, the last message is highlighted (in blue on the gif) and then the reply block shifts to the left (using the margin)

    Is there a possibility that the block will move to the margin directly rather than waiting for the blue indicator to disappear?

    It’s a shame to wait until the blue border-left disappears and then the block shifts to the left.
    It would have to shift directly without waiting

    edit :

    thats seems to work here, no delay :

    d91cde78-0c61-45a7-9f44-245db18db2d8-image.png

    @DownPW yes, because I don’t use a border, but a box shadow to avoid the layout shift. If you add the highlight class I any of the posts here you can capture the CSS.

  • @phenomlab

    seem you use a border non ?

    .page-topic .topic .posts.timeline .timeline-event.highlight, .page-topic .topic .posts.timeline > [component="post/placeholder"].highlight, .page-topic .topic .posts.timeline > [component=post].highlight {
        border: 2px solid var(--bs-post-unread) !important;
    }
    
  • @phenomlab

    seem you use a border non ?

    .page-topic .topic .posts.timeline .timeline-event.highlight, .page-topic .topic .posts.timeline > [component="post/placeholder"].highlight, .page-topic .topic .posts.timeline > [component=post].highlight {
        border: 2px solid var(--bs-post-unread) !important;
    }
    

    @DownPW no, it’s a box shadow

  • odd i see this code in dev console, search better 🙂

    I see too li.pt-4.self-post.highlight.threaded I don’t have this

    EDIT :

    yes resolved by this code :

    .page-topic .topic .posts.timeline .timeline-event.highlight, .page-topic .topic .posts.timeline > [component="post/placeholder"].highlight, .page-topic .topic .posts.timeline > [component=post].highlight.threaded {
        border: 2px solid var(--bs-post-unread) !important;
        border-radius: var(--bs-border-radius);
    }
    
    li.pt-4.self-post.highlight.threaded {
        margin-left: 0rem !important;
      }
    

    – RESULT :

    blink2.gif

  • Here I am again Mark @phenomlab 😉

    I am contributing to this code to add a tooltip to the button.

    The position can be changed according to your wishes.
    For my part, I prefer to put it at the bottom because if we put it at the top it can be annoying.

    Tell me what you think about it ?

    function threaded() {
        $(document).ready(function () {
            // Check if the screen width is 1200px or more
            if ($(window).width() >= 1200) {
                // Check if the dropdown already exists
                if ($('#enableThreading').length === 0) {
                    var threadView = $('<div class="threads-wrapper"><i class="fa fa-fw fa-bars left"></i><form class="form"><div class="form-check form-switch sticky-tools-bar"> \
                        <input class="form-check-input" id="enableThreading" type="checkbox" data-field="enableThreading"> \
                        <label class=" d-none d-md-inline fw-semibold" for="enableThreading"><i class="fa fa-fw fa-bars-staggered right"></i></label> \
                    </div></form></div>');
                    $('.topic .sticky-tools ul [component="topic/browsing-users"]:last-of-type').append(threadView);
                    // Check if there's a stored state for the checkbox and update it
                    var storedState = localStorage.getItem('enableThreadingState');
                    if (storedState === 'true') {
                        $('#enableThreading').prop('checked', true);
                    }
                }
                
                // Add a tooltip to the button
                $('#enableThreading').tooltip({
                    title: 'Thread View On/Off', // Replace with your tooltip text
                    placement: 'Bottom', // Adjust the placement as needed
                    trigger: 'hover', // Show tooltip on hover
                });
                
                // Toggle the class 'threaded' on or off when the checkbox changes state
                $('#enableThreading').on('change', function () {
                    var isChecked = $(this).is(':checked');
                    if (isChecked) {
                        console.log('Thread view is active.');
                        $('ul[component="topic"]').addClass('threaded');
                        $('.posts-container').addClass('threaded');
                        $('ul[component="topic"]').addClass('threaded');
                        $('.post-container').addClass('threaded');
                        $('.timeline-event').addClass('threaded');
                        $('[component="post/footer"]').addClass('threaded');
                        $('[component="post"]').each(function () {
                            // Add the 'threaded' class to matching elements
                            if ($(this).hasClass('pt-4') || $(this).hasClass('self-post')) {
                                $(this).addClass('threaded');
                                $('.topic .sticky-tools').addClass('threaded');
                            }
                        });
                    } else {
                        console.log('Thread view is inactive.');
                        $('[component="post"]').removeClass('threaded');
                        $('ul[component="topic"]').removeClass('threaded');
                        $('.posts-container').removeClass('threaded');
                        $('ul[component="topic"]').removeClass('threaded');
                        $('.post-container').removeClass('threaded');
                        $('.timeline-event').removeClass('threaded');
                        $('[component="post/footer"]').removeClass('threaded');
                        $('.topic .sticky-tools').removeClass('threaded');
                    }
                    // Store the checkbox state in localStorage
                    localStorage.setItem('enableThreadingState', isChecked);
                });
                // Check for changes in the checkbox state when the page loads
                $('#enableThreading').trigger('change');
            }
        });
    }
    
    $(window).on('action:ajaxify.end', function (data) {
        threaded();
    });
    
    $(window).on('action:posts.edited', function (data) {
        threaded();
    });
    
    $(window).on('action:posts.loaded', function (data) {
        threaded();
    });
    

    CYA my friend

  • Here I am again Mark @phenomlab 😉

    I am contributing to this code to add a tooltip to the button.

    The position can be changed according to your wishes.
    For my part, I prefer to put it at the bottom because if we put it at the top it can be annoying.

    Tell me what you think about it ?

    function threaded() {
        $(document).ready(function () {
            // Check if the screen width is 1200px or more
            if ($(window).width() >= 1200) {
                // Check if the dropdown already exists
                if ($('#enableThreading').length === 0) {
                    var threadView = $('<div class="threads-wrapper"><i class="fa fa-fw fa-bars left"></i><form class="form"><div class="form-check form-switch sticky-tools-bar"> \
                        <input class="form-check-input" id="enableThreading" type="checkbox" data-field="enableThreading"> \
                        <label class=" d-none d-md-inline fw-semibold" for="enableThreading"><i class="fa fa-fw fa-bars-staggered right"></i></label> \
                    </div></form></div>');
                    $('.topic .sticky-tools ul [component="topic/browsing-users"]:last-of-type').append(threadView);
                    // Check if there's a stored state for the checkbox and update it
                    var storedState = localStorage.getItem('enableThreadingState');
                    if (storedState === 'true') {
                        $('#enableThreading').prop('checked', true);
                    }
                }
                
                // Add a tooltip to the button
                $('#enableThreading').tooltip({
                    title: 'Thread View On/Off', // Replace with your tooltip text
                    placement: 'Bottom', // Adjust the placement as needed
                    trigger: 'hover', // Show tooltip on hover
                });
                
                // Toggle the class 'threaded' on or off when the checkbox changes state
                $('#enableThreading').on('change', function () {
                    var isChecked = $(this).is(':checked');
                    if (isChecked) {
                        console.log('Thread view is active.');
                        $('ul[component="topic"]').addClass('threaded');
                        $('.posts-container').addClass('threaded');
                        $('ul[component="topic"]').addClass('threaded');
                        $('.post-container').addClass('threaded');
                        $('.timeline-event').addClass('threaded');
                        $('[component="post/footer"]').addClass('threaded');
                        $('[component="post"]').each(function () {
                            // Add the 'threaded' class to matching elements
                            if ($(this).hasClass('pt-4') || $(this).hasClass('self-post')) {
                                $(this).addClass('threaded');
                                $('.topic .sticky-tools').addClass('threaded');
                            }
                        });
                    } else {
                        console.log('Thread view is inactive.');
                        $('[component="post"]').removeClass('threaded');
                        $('ul[component="topic"]').removeClass('threaded');
                        $('.posts-container').removeClass('threaded');
                        $('ul[component="topic"]').removeClass('threaded');
                        $('.post-container').removeClass('threaded');
                        $('.timeline-event').removeClass('threaded');
                        $('[component="post/footer"]').removeClass('threaded');
                        $('.topic .sticky-tools').removeClass('threaded');
                    }
                    // Store the checkbox state in localStorage
                    localStorage.setItem('enableThreadingState', isChecked);
                });
                // Check for changes in the checkbox state when the page loads
                $('#enableThreading').trigger('change');
            }
        });
    }
    
    $(window).on('action:ajaxify.end', function (data) {
        threaded();
    });
    
    $(window).on('action:posts.edited', function (data) {
        threaded();
    });
    
    $(window).on('action:posts.loaded', function (data) {
        threaded();
    });
    

    CYA my friend

    @DownPW looks good to me 😀 I started to write this a few days ago, but got sidetracked and never finished it.

    Good job.

  • I forgot the screen 🙂

    image.png

  • I forgot the screen 🙂

    image.png

    @DownPW looks great.

  • And just modified this for better display (centered)

    .threads-wrapper {
        display: flex;
        position: relative;
        /* top: -2px; */
        top: 1px;
    }
    
  • And just modified this for better display (centered)

    .threads-wrapper {
        display: flex;
        position: relative;
        /* top: -2px; */
        top: 1px;
    }
    

    @DownPW yes, it was styled for this site because it uses heavily modified css.

  • @phenomlab

    I note, however, that here on Sudonix, our own posts and those of other users are not shifted (to the left). Only those of the author of the topics are.

    In the code you provide on Github, the author’s posts and those of others are shifted (to the left), only our own posts are not.

    I do not know if it’s normal.

    It actually seems logical to me that only our own posts are not shifted to just better identify our own posts

  • @phenomlab

    I note, however, that here on Sudonix, our own posts and those of other users are not shifted (to the left). Only those of the author of the topics are.

    In the code you provide on Github, the author’s posts and those of others are shifted (to the left), only our own posts are not.

    I do not know if it’s normal.

    It actually seems logical to me that only our own posts are not shifted to just better identify our own posts

    @DownPW Correct. The design in DEV and the code on Git reflects the points you raised, which is why it was developed from scratch in DEV and was not a copy of PROD. It’s a matter of personal taste 🙂 You can fairly easily change the cosmetic behavior to suit your needs - it’s not set in stone.

  • Here I am again Mark @phenomlab 😉

    I am contributing to this code to add a tooltip to the button.

    The position can be changed according to your wishes.
    For my part, I prefer to put it at the bottom because if we put it at the top it can be annoying.

    Tell me what you think about it ?

    function threaded() {
        $(document).ready(function () {
            // Check if the screen width is 1200px or more
            if ($(window).width() >= 1200) {
                // Check if the dropdown already exists
                if ($('#enableThreading').length === 0) {
                    var threadView = $('<div class="threads-wrapper"><i class="fa fa-fw fa-bars left"></i><form class="form"><div class="form-check form-switch sticky-tools-bar"> \
                        <input class="form-check-input" id="enableThreading" type="checkbox" data-field="enableThreading"> \
                        <label class=" d-none d-md-inline fw-semibold" for="enableThreading"><i class="fa fa-fw fa-bars-staggered right"></i></label> \
                    </div></form></div>');
                    $('.topic .sticky-tools ul [component="topic/browsing-users"]:last-of-type').append(threadView);
                    // Check if there's a stored state for the checkbox and update it
                    var storedState = localStorage.getItem('enableThreadingState');
                    if (storedState === 'true') {
                        $('#enableThreading').prop('checked', true);
                    }
                }
                
                // Add a tooltip to the button
                $('#enableThreading').tooltip({
                    title: 'Thread View On/Off', // Replace with your tooltip text
                    placement: 'Bottom', // Adjust the placement as needed
                    trigger: 'hover', // Show tooltip on hover
                });
                
                // Toggle the class 'threaded' on or off when the checkbox changes state
                $('#enableThreading').on('change', function () {
                    var isChecked = $(this).is(':checked');
                    if (isChecked) {
                        console.log('Thread view is active.');
                        $('ul[component="topic"]').addClass('threaded');
                        $('.posts-container').addClass('threaded');
                        $('ul[component="topic"]').addClass('threaded');
                        $('.post-container').addClass('threaded');
                        $('.timeline-event').addClass('threaded');
                        $('[component="post/footer"]').addClass('threaded');
                        $('[component="post"]').each(function () {
                            // Add the 'threaded' class to matching elements
                            if ($(this).hasClass('pt-4') || $(this).hasClass('self-post')) {
                                $(this).addClass('threaded');
                                $('.topic .sticky-tools').addClass('threaded');
                            }
                        });
                    } else {
                        console.log('Thread view is inactive.');
                        $('[component="post"]').removeClass('threaded');
                        $('ul[component="topic"]').removeClass('threaded');
                        $('.posts-container').removeClass('threaded');
                        $('ul[component="topic"]').removeClass('threaded');
                        $('.post-container').removeClass('threaded');
                        $('.timeline-event').removeClass('threaded');
                        $('[component="post/footer"]').removeClass('threaded');
                        $('.topic .sticky-tools').removeClass('threaded');
                    }
                    // Store the checkbox state in localStorage
                    localStorage.setItem('enableThreadingState', isChecked);
                });
                // Check for changes in the checkbox state when the page loads
                $('#enableThreading').trigger('change');
            }
        });
    }
    
    $(window).on('action:ajaxify.end', function (data) {
        threaded();
    });
    
    $(window).on('action:posts.edited', function (data) {
        threaded();
    });
    
    $(window).on('action:posts.loaded', function (data) {
        threaded();
    });
    

    CYA my friend

    @DownPW Maybe go one better perhaps, and toggle the on/off state depending on the switch selection (I’m doing that here)

    https://github.com/phenomlab/nodebb-harmony-threading/blob/main/functions%2Cjs

    Enabled

    9a9261ae-7730-4212-a650-04bf1d6807ba-image.png

    Disabled

    ce2cb828-32a9-4c95-905a-4b8f84a24bd0-image.png

  • it’s cool too ^^

  • it’s cool too ^^

  • this code does not work for me. No button

    EDIT:

    The code on github is OK, not the last share above

  • this code does not work for me. No button

    EDIT:

    The code on github is OK, not the last share above

    @DownPW said in Threading support for NodeBB:

    The code on github is OK, not the last share above

    Sorry - changed that to the Git link

  • Just don’t forget to comment out lines for browsing-users plugin

    I’m got screwed 😉

    Good work my friend


Related Topics
  • What’s going on with NodeBB?

    Performance nodebb script die
    20
    8 Votes
    20 Posts
    4k Views
    @cagatay The most reliable way to upgrade Node.js on Ubuntu depends on how you originally installed it. Method 1: Using NVM (Recommended) If you already use Node Version Manager (NVM), upgrading is simple. NVM allows you to keep both versions and switch between them if needed. Install Node 22: nvm install 22 Switch to Node 22: nvm use 22 Set it as your default: nvm alias default 22 Verify the change: node -v Method 2: Using NodeSource (PPA) If you installed Node.js via apt using the NodeSource repository, you need to update the repository script to point to the new version. Remove the old NodeSource list (optional but cleaner): sudo rm /etc/apt/sources.list.d/nodesource.list Download and run the NodeSource setup script for Node 22: curl -fsSL [https://deb.nodesource.com/setup_22.x](https://deb.nodesource.com/setup_22.x) | sudo -E bash - Install/Upgrade Node.js: sudo apt-get install -y nodejs Verify the installation: node -v Method 3: Using the ‘n’ Package If you have npm installed, you can use the n interactive manager. Clear the npm cache: sudo npm cache clean -f Install the ‘n’ helper: sudo npm install -g n Install Node 22: sudo n 22 Update your shell: hash -r Troubleshooting Permission Denied: If you see permission errors using Method 2 or 3, ensure you are using sudo. Path Issues: If node -v still shows version 20 after upgrading via NVM, restart your terminal or run source ~/.bashrc. Conflicts: Avoid mixing these methods. If you switch from apt to nvm, it is best to sudo apt remove nodejs first to avoid path conflicts.
  • 3 Votes
    2 Posts
    2k Views
    Very great
  • 15 Votes
    51 Posts
    24k Views
    Oh yes, that’s what’s super cool, I learn something every day. Afterwards I start from so low in JS
  • 21 Votes
    110 Posts
    50k Views
    @crazycells said in Setup OGProxy for use in NodeBB: are they cached for each user separately? No. It’s a shared cache @crazycells said in Setup OGProxy for use in NodeBB: additionally, this is also handling youtube videos etc, right? No. This is handled by nodebb-plugin-ns-embed
  • adding some console.log to Nodebb

    Solved Customisation nodebb
    4
    1 Votes
    4 Posts
    2k Views
    @eeeee if you’re using the console, you could try node app.js > app.log 2>&1 This would redirect stdout to a file named app.log and redirect stderr to stdout. I’m not sure about standard logging under NodeBB, but there is an error log located at logs/error.log. Failing that, you could always stop the NodeBB service then use ./nodebb dev from the console which would then provide debug output.
  • ineffecient use of space on mobile

    Solved Customisation nodebb
    10
    2
    7 Votes
    10 Posts
    4k Views
    @phenomlab Thanks
  • NodeBB 1.19.3

    Solved Performance nodebb 1.19.3 performance
    33
    4 Votes
    33 Posts
    15k Views
    @phenomlab I find the problem Mark The error message indicated this path : http://localhost:4567/assets/plugins/nodebb-plugin-emoji/emoji/styles.css?v=6983dobg16u I change the path url on config.json [image: 1645128773854-47bacc80-f141-41e4-a261-3f8d650cc6f6-image.png] And all it’s good Weird, I didn’t have to change that path before 1.19.3 But this does not prevent the problem from a clean install with Emoji Plugin EDIT: After test, that resolv the problem installation for 1.18.x but not for 1.19.x (I have other error message when I run ./nodebb Setup For resume: NodeJS 16_x with 1.18.x is ok
  • Display tweets in widget [NodeBB]

    Solved Customisation
    29
    4 Votes
    29 Posts
    12k Views
    @phenomlab brilliant, many thanks Mark