Reply reminder - Remind users to reply to new users topics with zero replies

First off, thank you @cpradio for the original script.

We’re using this on a number of our sites and it’s hugely helpful in keeping our community engaged (especially welcoming new users).

We modified it, adding the ability to filter out certain categories that we don’t want the needs reply feature to show up on. Here’s the gist of what we’re currently using: https://gist.github.com/alxpck/e092385c609b14459317b798dc55b07e

But I’m stuck when it comes to using this in conjunction with tagging and the tag filters. Is there a way to check for a tag filter and apply it within the script?

It seems like the tag name from the response object might be the thing I need to build the right url. Anyone have insight here?

5 Likes

Update: I got it working. You need to include the args.tagId in the url.

I’ve updated the gist to include the needed code for anyone who is curious or whom it would benefit.

It came down to searching the Discourse repo for Discourse.NavItem and then reading the code for the NavItem and realizing the only two arguments I had access to were category and args. So I logged those to the console and found the tagId.

4 Likes

Ok, I’m stuck again. It seems that when a tag’s initial filter view are involved, the link isn’t sending an XMLHttp Request.

I strongly suspect there’s a bug in my code (see the gist above) and since JavaScript is not my best language I’m probably missing something obvious.

To be honest, I’m not even sure I’m articulating this correctly. But here’s a screencast of what I’m seeing:

In most cases when you click on the “Posts That Need Love” link, it highlights that menu option and there’s activity in the XHR tab of the dev tools. But when you’re in a tag’s filter view that click on the custom nav link (PTNL) it correctly constructs the url by appending ?max_posts=5 but it doesn’t log any activity in the XHR tab (so I’m guessing doesn’t send an XHR). If you click on a different nav link (like latest) it sends the XHR and after that clicking on the custom nav link both constructs the url correctly and sends the XHR.

I can’t seem to debug why this is happening.

Anyone have a lead on why this might be happening?

Oh this is a bit tricky, I can see what is happening, basically you want clicking on the nav item to wipe out any existing query and now it is keeping it… can you publish a theme-component here? then we can help whip it into shape with a PR

1 Like

@sam That would be wonderful. Yes, please.

Took me a bit to wrap it up as a theme component, but here it is: https://github.com/alxpck/discourse_needsreply

Let me know how else I can help, and/or what else you need.

4 Likes

@sam Any luck on this front? Anything else I can do to help?

The problem I’m seeing is that the tags route doesn’t respect the max_posts filter, for example, this doesn’t work: https://meta.discourse.org/tags/pr-welcome?max_posts=1.

A workaround for this is to use tags as a filter in the URL. Something like this might be close to what you’re looking for. It needs a bit of testing before I’d use it on a live site.

<script type="text/discourse-plugin" version="0.8.18">
    if (I18n.translations.en) {
        I18n.translations.en.js.filters.unanswered = {title: "Unanswered", help: "Topics that have not be answered"};
    }

    api.modifyClass('component:navigation-item', {
        active: Ember.computed("contentFilterMode", "filterMode", function () {
            let contentFilterMode = this.get('content').get('filterMode');
            if (window.location.search && window.location.search.split('&')[0] === "?max_posts=1") {
                return contentFilterMode === "unanswered";
            } else {
                return this._super(contentFilterMode, this.get('filterMode'));
            }
        }),
    });

    api.modifyClass('controller:discovery/topics', {
        resetParams: function () {
            this.setProperties({max_posts: null});
            this.setProperties({tags: null});
            this._super();
        }
    });

    Discourse.ExternalNavItem = Discourse.NavItem.extend({
        href: function () {
            return this.get('href');
        }.property("href")
    });

    Discourse.NavItem.reopenClass({
        buildList: function (category, args) {
            let list = this._super(category, args),
                tag = args.tagId,
                unansweredHref;

            if (!category) {
                unansweredHref = tag ? '/latest/?max_posts=1&tags=' + tag : '/latest/?max_posts=1';
            }
            else if (!category.parentCategory) {
                unansweredHref = tag ? '/c/' + category.slug + '?max_posts=1&tags=' + tag : '/c/' + category.slug + '?max_posts=1';
            } else {
                unansweredHref = tag ? '/c/' + category.parentCategory.slug + '/' + category.slug + '?max_posts=1&tags=' + tag :
                    '/c/' + category.parentCategory.slug + '/' + category.slug + '?max_posts=1';
            }
            list.push(Discourse.ExternalNavItem.create({href: unansweredHref, name: 'unanswered'}));
            return list;
        }
    });
</script>
6 Likes

Thanks Simon!

I’ve been testing it this morning, and added back in a few of the features we need (like an exclude list). I also moved the query to a variable so we didn’t need to keep typing it. My code is below.

It doesn’t it solves the underlying problem for us.

One thing I noticed while testing is that the query doesn’t work on the base url (for us) because we have our site setup to only display categories on the home page.

Which means that when we try to append the tag filter to the base url it doesn’t return any results.

https://domainname/?max_posts=5&tags=prompt-1

vs. https://meta.discourse.org/?max_posts=5&tag=pr-welcome

Is there a way around this without either a) changing our homepage display or b) hardcoding the category?

  • (hardcoding the category actually does work for us because we only care about tags in one category, I’m just trying to avoid it as a best practice)

Is there a reason the tags route doesn’t respect the max_posts filter?

Thanks again, I really appreciate the help.

<!-- POSTS THAT NEED LOVE -->
<script type="text/discourse-plugin" version="0.8.18">
    if (I18n.translations.en) {
        I18n.translations.en.js.filters.needsreply = {title: "Posts That Need Love", help: "Topics that have not be answered"};
    }

    // CUSTOMIZE THESE VALUES
    // Set the search query
    let search_query = '?ascending=true&order=posts&max_posts=5';
    // Exclude certain categories from showing needs_reply
    let excludeList = ['process', 'resources', 'prompts', 'staff', 'unpublished-prompts'];


    // Add active class to Needs Reply button
    api.modifyClass('component:navigation-item', {
        active: Ember.computed("contentFilterMode", "filterMode", function () {
            let contentFilterMode = this.get('content').get('filterMode');
            if (window.location.search && window.location.search.split('&')[0] === search_query.split('&')[0]) {
                return contentFilterMode === "needsreply";
            } else {
                return this._super(contentFilterMode, this.get('filterMode'));
            }
        }),
    });

    // Remove max_posts filter and tags filter
    api.modifyClass('controller:discovery/topics', {
        resetParams: function () {
            this.setProperties({max_posts: null});
            this.setProperties({tags: null});
            this._super();
        }
    });

    Discourse.ExternalNavItem = Discourse.NavItem.extend({
        href: function () {
            return this.get('href');
        }.property("href")
    });

    Discourse.NavItem.reopenClass({
        buildList: function (category, args) {
            let list = this._super(category, args),
                tag = args.tagId,
                needsreplyHref;

            if (!category) { // does not have a category
                if (args.tagId) { // has a tag
                    needsreplyHref = '/c/student-work' + search_query + '&tags=' + tag; // build the href with the tag.
                } else {
                    return list; // return the list without creating the custom nav item because the query doesn't work on the base url of a Discourse install
                }
            }
            else if (excludeList.indexOf(category.slug) != -1) { // the category is in the exclude list, do nothing
                return list;
            }
            else if (!category.parentCategory) { // is not a sub-category
                needsreplyHref = tag ? '/c/' + category.slug + search_query + '&tags=' + tag : '/c/' + category.slug + search_query; // if there's a tag build the href with tags and the category, else fallback to the search query et al
            } else { // is a sub-category
                needsreplyHref = tag ? '/c/' + category.parentCategory.slug + '/' + category.slug + search_query + '&tags=' + tag :
                    '/c/' + category.parentCategory.slug + '/' + category.slug + search_query; // if there's a tag build the href with tags, sub-cateogry, and category, else fallback to search query et al
            }
            list.push(Discourse.ExternalNavItem.create({href: needsreplyHref, name: 'needsreply'}));
            return list;
        }
    });
</script>

edit: fixed the code to clear and add the active class based on the search query variable

1 Like

Right, I’ve edited the code to fix that. This:

unansweredHref = tag ? '?max_posts=1&tags=' + tag : '/?max_posts=1';

needed to be changed to:

unansweredHref = tag ? '/latest/?max_posts=1&tags=' + tag : '/latest/?max_posts=1';
5 Likes

Perfect! Thank you, Simon

1 Like

So a question here - first thanks @simon for this - it was recently requested by one of our members so I added it and it looks great! Only thing I am having a problem understanding, is when I click the Unanswered the nav CSS doesn’t adjust - it stays on Latest

Any help would be appreciated

We’ve been using this theme component for some time, and today it broke.

Here’s the component code (common, head_tag.html)

<!-- NEEDS REPLY -->
<script type="text/discourse-plugin" version="0.8.18">
    if (I18n.translations.en) {
        I18n.translations.en.js.filters.needsreply = {title: "Needs Reply", help: "Unanswered Topics"};
    }

    // CUSTOMIZE THESE VALUES
    // Set the search query
    let search_query = '?ascending=false&max_posts=1';
    // Exclude certain categories from showing the needs reply menu item
    let excludeList = ['pitches', 'weekly-recap', 'staff'];


    // Add active class to Needs Reply button
    api.modifyClass('component:navigation-item', {
        active: Ember.computed("contentFilterMode", "filterMode", function () {
            let contentFilterMode = this.get('content').get('filterMode');
            if (window.location.search && window.location.search.split('&')[0] === search_query.split('&')[0]) {
                return contentFilterMode === "needsreply";
            } else {
                return this._super(contentFilterMode, this.get('filterMode'));
            }
        }),
    });

    // Remove max_posts filter and tags filter
    api.modifyClass('controller:discovery/topics', {
        resetParams: function () {
            this.setProperties({max_posts: null});
            this.setProperties({tags: null});
            this._super();
        }
    });

    Discourse.ExternalNavItem = Discourse.NavItem.extend({
        href: function () {
            return this.get('href');
        }.property("href")
    });

    Discourse.NavItem.reopenClass({
        buildList: function (category, args) {
            let list = this._super(category, args),
                tag = args.tagId,
                needsreplyHref;

            if (!category) { // does not have a category
                needsreplyHref = tag ? '/latest/' + search_query + '&tags=' + tag : '/latest/' + search_query; // if there's a tag build the href with the tag, else fall back to the latest view and the search query
            }
            else if (excludeList.indexOf(category.slug) != -1) { // the category is in the exclude list, do nothing
                return list; // return the list without creating the custom nav item
            }
            else if (!category.parentCategory) { // is not a sub-category
                needsreplyHref = tag ? '/c/' + category.slug + search_query + '&tags=' + tag : '/c/' + category.slug + search_query; // if there's a tag build the href with tags and the category, else fallback to the search query et al
            } else { // is a sub-category
                needsreplyHref = tag ? '/c/' + category.parentCategory.slug + '/' + category.slug + search_query + '&tags=' + tag :
                    '/c/' + category.parentCategory.slug + '/' + category.slug + search_query; // if there's a tag build the href with tags, sub-cateogry, and category, else fallback to search query et al
            }
            list.push(Discourse.ExternalNavItem.create({href: needsreplyHref, name: 'needsreply'}));
            return list;
        }
    });
</script>

There’s an error in the JS console:

And digging into a bit more I found this post talking about Discourse.NavItem being deprecated:

Any suggestions for rewriting this without the deprecated NavItem class?

2 Likes

Was able to get most of the way there looking at Custom top navigation links.

<script type="text/discourse-plugin" version="0.8.18">
if (I18n.translations.en) {
    I18n.translations.en.js.filters.needsreply = {title: "Needs Reply", help: "Unanswered Topics"};
}

// CUSTOMIZE THESE VALUES
// Set the search query
let search_query = '?max_posts=1';
// Exclude certain categories from showing the needs reply menu item
let excludeList = ['pitches', 'weekly-recap', 'staff'];

api.addNavigationBarItem({
name: "needsreply",
displayName:"Needs Reply",
title: "Needs Reply",
href: "/latest" + search_query
});

// Add active class to Needs Reply button
api.modifyClass('component:navigation-item', {
    active: Ember.computed("contentFilterMode", "filterMode", function () {
        let contentFilterMode = this.get('content').get('filterMode');
        console.log(window.location.search.split('&')[0], search_query.split('&')[0])
        if (window.location.search && window.location.search.split('&')[0] === search_query.split('&')[0]) {
            return contentFilterMode === "needsreply";
        } else {
            return this._super(contentFilterMode, this.get('filterMode'));
        }
    }),
});


// Remove max_posts filter and tags filter
api.modifyClass('controller:discovery/topics', {
    resetParams: function () {
        this.setProperties({max_posts: null});
        this.setProperties({tags: null});
        this._super();
    }
});


</script>

The only part I haven’t been able to figure out is how to access the current category in this script.

In the original code, Discourse.NavItem allows you to access the current category. Based on this code, we dynamically alter the behavior of the link (hide it, or change the href to only look at posts in this category).

Can anyone recommend a solution to inject the current category into the script above so I can customize the behavior of the link depending on what category the user is viewing?

1 Like

Here’s what I ended up with… there might be a couple missing features. Overall though, the code is simpler.

<script type="text/discourse-plugin" version="0.8.18">

// Set the search query
let search_query = '?max_posts=1';
// Specify any category slugs that shouldn't have this feature
let filtered_categories = ["pitches", "weekly-recap", "staff"]


var needs_reply = api.addNavigationBarItem({
    name: "needsreply",
    displayName:"Needs Reply",
    title: "Needs Reply",
    customFilter: (category, args, router) => { return !category || !filtered_categories.includes(category.slug)}, // exclude categories
    customHref: (category, args, router) => {  return search_query },
    init: (navItem, category) => { if (category) { navItem.set("category", category)  } },
    forceActive: (category, args, router) => {return router.currentURL.includes(search_query);}
});

</script>
4 Likes

You might want to check out this component Grayden, it does the same as yours and is faster, has UI options to remove categories

https://github.com/tshenry/discourse-unanswered-filter

3 Likes

what needs to be replaced to make it work again?

Discourse.ExternalNavItem = Discourse.NavItem.extend(

it worked, thanks