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
    // 设置搜索查询
    let search_query = '?ascending=false&max_posts=1';
    // 从“需要回复”菜单项中排除某些分类
    let excludeList = ['pitches', 'weekly-recap', 'staff'];


    // 为“需要回复”按钮添加 active 类
    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'));
            }
        }),
    });

    // 移除 max_posts 过滤器和标签过滤器
    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) { // 没有分类
                needsreplyHref = tag ? '/latest/' + search_query + '&tags=' + tag : '/latest/' + search_query; // 如果有标签,则构建包含标签的 href,否则回退到最新视图和搜索查询
            }
            else if (excludeList.indexOf(category.slug) != -1) { // 分类在排除列表中,不执行任何操作
                return list; // 返回列表,不创建自定义导航项
            }
            else if (!category.parentCategory) { // 不是子分类
                needsreplyHref = tag ? '/c/' + category.slug + search_query + '&tags=' + tag : '/c/' + category.slug + search_query; // 如果有标签,则构建包含标签和分类的 href,否则回退到搜索查询等
            } else { // 是子分类
                needsreplyHref = tag ? '/c/' + category.parentCategory.slug + '/' + category.slug + search_query + '&tags=' + tag :
                    '/c/' + category.parentCategory.slug + '/' + category.slug + search_query; // 如果有标签,则构建包含标签、子分类和分类的 href,否则回退到搜索查询等
            }
            list.push(Discourse.ExternalNavItem.create({href: needsreplyHref, name: 'needsreply'}));
            return list;
        }
    });
</script>

JS 控制台中出现了一个错误:

进一步调查后,我发现了一篇关于 Discourse.NavItem 已被弃用的帖子:

对于在不使用已弃用的 NavItem 类的情况下重写此代码,有什么建议吗?

2 个赞

通过查看 https://meta.discourse.org/t/custom-top-navigation-links/87225,我基本上已经完成了大部分工作。

<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 和 tags 过滤器
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';
// 指定不应包含此功能的分类 slug
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,你可能想看看这个组件,它的功能和你的一样,但速度更快,并且提供 UI 选项来移除分类

3 个赞

需要替换什么才能让它再次工作?

Discourse.ExternalNavItem = Discourse.NavItem.extend(

它起作用了,谢谢