Skip to content

Threaded post support for NodeBB

Let's Build It
146 5 118.7k 1
  • wrote on last edited by DownPW
    #90

    @phenomlab

    It looks like I got something working by adding this code

    li.pt-4.self-post:not(.self-post .topic-owner-post).threaded {
      margin-left: 0rem;
    }
    
    li.pt-4.self-post.threaded {
      margin-left: 0rem;
    }
    
    li.necro-post.text-muted.timeline-event.d-flex.gap-2.pt-4.threaded {    
      background: transparent !important;
      margin-bottom: 10px !important;
    }
    
  • wrote on last edited by
    #91

    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 😉

  • 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 😉

    wrote on last edited by
    #92

    @DownPW thanks. Let me review

    @crazycells good point. 1200 is a bit too generic I think and perhaps we need to only execute on a more forgiving resolution.

  • 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 😉

    wrote on last edited by
    #93

    @DownPW Having reviewed this, it looks like layout shift is causing this - let me have a look at the most efficient way to counter this.

  • 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 😉

    wrote on last edited by
    #94

    @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

  • wrote on last edited by DownPW
    #95

    @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

    wrote on last edited by
    #96

    @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.

  • wrote on last edited by
    #97

    @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;
    }
    
    wrote on last edited by
    #98

    @DownPW no, it’s a box shadow

  • wrote on last edited by DownPW
    #99

    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

  • wrote on last edited by DownPW

    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

    wrote on last edited by

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

    Good job.

  • wrote on last edited by DownPW

    I forgot the screen 🙂

    image.png

  • I forgot the screen 🙂

    image.png

    wrote on last edited by

    @DownPW looks great.

  • wrote on last edited by DownPW

    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;
    }
    
    wrote on last edited by

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

  • wrote on last edited by DownPW

    @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

    wrote on last edited by phenomlab

    @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

    wrote on last edited by phenomlab

    @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

  • wrote on last edited by

    it’s cool too ^^


Related Topics
  • Extended functions for Q&A NodeBB plugin

    Let's Build It q&a panels cards
    3
    4
    5 Votes
    3 Posts
    3k Views
    DownPW
    Very good like always
  • Page control arrows for PWA

    Solved Customisation nodebb
    27
    25 Votes
    27 Posts
    15k Views
    phenomlab
    @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.
  • Setup OGProxy for use in NodeBB

    Moved Let's Build It ogproxy opengraph npm node
    110
    2
    21 Votes
    110 Posts
    57k Views
    phenomlab
    @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
  • Smart Widgets

    Solved Configure nodebb
    9
    3 Votes
    9 Posts
    3k Views
    phenomlab
    @Panda said in Smart Widgets: So why is that, or conversely why would the function to expose username ever be required, as it seems app.user is already an available global object? It is, yes, but not if you are using it outside of a widget. The function I wrote is also historical and comes from the 2.x train
  • restarting nodebb on boot

    Unsolved Configure nodebb
    3
    1 Votes
    3 Posts
    2k Views
    phenomlab
    @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.
  • optional "snow falling" theme

    Solved Customisation nodebb
    6
    6 Votes
    6 Posts
    2k Views
    phenomlab
    @Sala ok, but that’s still the same loop which is going to have an impact on the CPU.
  • Forum Icons NodeBB

    Solved Customisation icons forum nodebb
    13
    0 Votes
    13 Posts
    5k Views
    phenomlab
    @cagatay That matches what I see [image: 4f0f858d-9812-42b1-9f61-ffb13d31dccd-image.png]
  • NodeBB vs Discourse

    Chitchat nodebb discourse board
    25
    6 Votes
    25 Posts
    14k Views
    RiekMedia
    @phenomlab said in NodeBB vs Discourse: Hetzner eh ? I use them also. In fact, Sudonix is hosted in Nuremberg yes i’m also at hetzner, i have been a customer there for years with a reseller account for domains. My VPS that I host there are also in Nuremberg