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 curtidas

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 curtidas

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 curtida

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

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

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 curtida

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 curtidas

Perfect! Thank you, Simon

1 curtida

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

Usamos esse componente de tema há algum tempo, e hoje ele parou de funcionar.

Aqui está o código do componente (common, head_tag.html)

<!-- PRECISA DE RESPOSTA -->
<script type="text/discourse-plugin" version="0.8.18">
    if (I18n.translations.en) {
        I18n.translations.en.js.filters.needsreply = {title: "Precisa de Resposta", help: "Tópicos sem resposta"};
    }

    // PERSONALIZE ESSES VALORES
    // Defina a consulta de pesquisa
    let search_query = '?ascending=false&max_posts=1';
    // Exclua certas categorias da exibição do item de menu "Precisa de Resposta"
    let excludeList = ['pitches', 'weekly-recap', 'staff'];


    // Adicione a classe ativa ao botão "Precisa de Resposta"
    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'));
            }
        }),
    });

    // Remova o filtro max_posts e o filtro de tags
    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) { // não possui categoria
                needsreplyHref = tag ? '/latest/' + search_query + '&tags=' + tag : '/latest/' + search_query; // se houver uma tag, construa o href com a tag; caso contrário, retorne à visualização mais recente e à consulta de pesquisa
            }
            else if (excludeList.indexOf(category.slug) != -1) { // a categoria está na lista de exclusão, não faça nada
                return list; // retorne a lista sem criar o item de navegação personalizado
            }
            else if (!category.parentCategory) { // não é uma subcategoria
                needsreplyHref = tag ? '/c/' + category.slug + search_query + '&tags=' + tag : '/c/' + category.slug + search_query; // se houver uma tag, construa o href com tags e a categoria; caso contrário, retorne à consulta de pesquisa, etc.
            } else { // é uma subcategoria
                needsreplyHref = tag ? '/c/' + category.parentCategory.slug + '/' + category.slug + search_query + '&tags=' + tag :
                    '/c/' + category.parentCategory.slug + '/' + category.slug + search_query; // se houver uma tag, construa o href com tags, subcategoria e categoria; caso contrário, retorne à consulta de pesquisa, etc.
            }
            list.push(Discourse.ExternalNavItem.create({href: needsreplyHref, name: 'needsreply'}));
            return list;
        }
    });
</script>

Há um erro no console do JS:

E, investigando um pouco mais, encontrei esta postagem falando sobre o Discourse.NavItem estar obsoleto:

Alguma sugestão para reescrever isso sem a classe NavItem obsoleta?

2 curtidas

Consegui chegar quase lá analisando Custom Top Navigation Links.

<script type="text/discourse-plugin" version="0.8.18">
if (I18n.translations.en) {
    I18n.translations.en.js.filters.needsreply = {title: "Precisa de Resposta", help: "Tópicos sem resposta"};
}

// PERSONALIZE ESSES VALORES
// Defina a consulta de pesquisa
let search_query = '?max_posts=1';
// Exclua certas categorias de exibir o item de menu "Precisa de Resposta"
let excludeList = ['pitches', 'weekly-recap', 'staff'];

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

// Adiciona a classe ativa ao botão "Precisa de Resposta"
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 o filtro max_posts e o filtro de tags
api.modifyClass('controller:discovery/topics', {
    resetParams: function () {
        this.setProperties({max_posts: null});
        this.setProperties({tags: null});
        this._super();
    }
});


</script>

A única parte que ainda não consegui entender é como acessar a categoria atual neste script.

No código original, o Discourse.NavItem permite acessar a categoria atual. Com base nesse código, alteramos dinamicamente o comportamento do link (ocultá-lo ou alterar o href para visualizar apenas postagens nesta categoria).

Alguém pode recomendar uma solução para injetar a categoria atual no script acima, para que eu possa personalizar o comportamento do link dependendo da categoria que o usuário está visualizando?

1 curtida

Veja só no que eu acabei chegando… pode faltar um par de recursos. No geral, porém, o código ficou mais simples.

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

// Define a consulta de busca
let search_query = '?max_posts=1';
// Especifique os identificadores de categoria que não devem ter esse recurso
let filtered_categories = ["pitches", "weekly-recap", "staff"]


var needs_reply = api.addNavigationBarItem({
    name: "needsreply",
    displayName:"Precisa de Resposta",
    title: "Precisa de Resposta",
    customFilter: (category, args, router) => { return !category || !filtered_categories.includes(category.slug)}, // excluir categorias
    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 curtidas

Talvez você queira conferir este componente, Grayden: ele faz o mesmo que o seu, é mais rápido e possui opções de interface para remover categorias.

3 curtidas

O que precisa ser substituído para que funcione novamente?

Discourse.ExternalNavItem = Discourse.NavItem.extend(

Funcionou, obrigado!