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 лайков

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 лайка

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 лайк

@sam That would be wonderful. Yes, please.

Took me a bit to wrap it up as a theme component, but here it is: GitHub - akimboinc/discourse_needsreply: Discourse theme component to add a custom menu item for topics that need more replies. Used for The Marketing Seminar.

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

4 лайка

@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 лайков

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 лайк

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 лайков

Perfect! Thank you, Simon

1 лайк

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

Мы используем этот компонент темы уже какое-то время, но сегодня он перестал работать.

Вот код компонента (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>

В консоли JS появилась ошибка:

Поглубже изучив проблему, я нашёл пост, где говорится о том, что Discourse.NavItem устарел:

Есть ли какие-то предложения по переписыванию этого кода без использования устаревшего класса NavItem?

2 лайка

Мне удалось продвинуться довольно далеко, изучив Custom Top Navigation Links.

<script type="text/discourse-plugin" version="0.8.18">
if (I18n.translations.en) {
    I18n.translations.en.js.filters.needsreply = {title: "Требуется ответ", help: "Неответленные темы"};
}

// НАСТРОЙТЕ ЭТИ ЗНАЧЕНИЯ
// Установите поисковый запрос
let search_query = '?max_posts=1';
// Исключите определенные категории из отображения пункта меню «Требуется ответ»
let excludeList = ['pitches', 'weekly-recap', 'staff'];

api.addNavigationBarItem({
name: "needsreply",
displayName:"Требуется ответ",
title: "Требуется ответ",
href: "/latest" + search_query
});

// Добавить активный класс кнопке «Требуется ответ»
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'));
        }
    }),
});


// Удалить фильтр max_posts и фильтр тегов
api.modifyClass('controller:discovery/topics', {
    resetParams: function () {
        this.setProperties({max_posts: null});
        this.setProperties({tags: null});
        this._super();
    }
});


</script>

Единственная часть, в которой я пока не разобрался, — это как получить доступ к текущей категории в этом скрипте.

В исходном коде Discourse.NavItem позволяет получить доступ к текущей категории. Исходя из этого кода, мы динамически изменяем поведение ссылки (скрываем её или меняем href так, чтобы она отображала только сообщения в этой категории).

Может ли кто-нибудь предложить решение для внедрения текущей категории в скрипт выше, чтобы я мог настраивать поведение ссылки в зависимости от категории, которую просматривает пользователь?

1 лайк

Вот что у меня получилось.. Возможно, не хватает пары функций. В целом, однако, код стал проще.

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

// Установить поисковый запрос
let search_query = '?max_posts=1';
// Укажите слага категорий, для которых эта функция не должна применяться
let filtered_categories = ["pitches", "weekly-recap", "staff"]


var needs_reply = api.addNavigationBarItem({
    name: "needsreply",
    displayName:"Нужен ответ",
    title: "Нужен ответ",
    customFilter: (category, args, router) => { return !category || !filtered_categories.includes(category.slug)}, // исключить категории
    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 лайка

Возможно, стоит посмотреть на этот компонент, Grayden. Он выполняет те же функции, что и ваш, но работает быстрее и имеет опции интерфейса для удаления категорий.

3 лайка

Что нужно заменить, чтобы это снова заработало?

Discourse.ExternalNavItem = Discourse.NavItem.extend(

Сработало, спасибо!