Better navigation for (structured) tags

Hi all,

I’m trying to ensure tags get more structured visibility in navigation, especially for those of us who rely on Parent > Children. We have just over 40 tags, so structured navigation for tags (which, soapbox alert, are woefully underutilized by so many forums, and which Discourse excels!) is pretty vital.

Since it’s silly to add multiple menus on top of Discourse native nav, my pitch (I think?) isn’t anything too dramatic. I ugly-mocked this up as an idea …

If something like this exists, I can’t find it. @Johani’s component comes the closest, but unfortunately isn’t structured.

Anyway, would love any feedback, even if it’s “This doesn’t make sense” or “It took me 10 seconds to locate this on GitHub, ya newb.”

Thanks!

2 Likes

with the amount of parent > child tags some sites can have, they wont all fit in there.

for example: look at meta’s tags

https://meta.discourse.org/tags

2 Likes

Sympathetic to that—I’d argue [tip-toeing] meta’s example is not the best way to use tags—not that there’s any obvious best practice around tags. But even as just an option for those of us who have a controlled vocabulary around tags, and rely heavily on tags (much more so than categories) this might be useful. My example was purely just that … I suspect others may have a better idea!

2 Likes

Why not just use the tags page? You could highlight the link to that page in various ways. The page itself supports structuring by groups by default.

Then you could add custom styles, either for the tags:

Or style the layout of the entire page to get a more specific presentation.

2 Likes

Yep, love the tags page—and the flexibility of the tags page. It’s great. But the tags page is not ubiquitous, whereas a menu would provide dynamic, omnipresent navigation, as it does for categories.

I suspect, like me, there’s some of us who rely far more heavily on tags than we do categories. That might seem counterintuitive, but it makes sense in certain contexts. Even the car example used in the Tags post would benefit from something like this. If it’s for car enthusiasts, for example, they might like hopping between tags (“Ford vs Ferrari”), rather than categories.

Then again, maybe you guys aren’t ready for this yet. But your kids are gonna love it. :wink:

1 Like

I’d be happy if you find a more dynamic solution! But I guess it would involve a serious amount of coding if you want sth that you don’t have to maintain manually. Until then I’d just make the link to the tags page more ubiquitous :wink:

Screenshot from 2021-10-22 21-57-26

The other aspect that makes tags much more user-friendly in my experience are proper tag banners. I did a simple component as add-on to the tag-banners component that enables you to add descriptions to tags: GitHub - nolosb/discourse-tag-banners-descriptions: A Discourse theme component. Extends the 'Discourse Tag Banners' component, adding descriptions to tags in banners.

3 Likes

A slightly different direction: I’ve made tag rendering on topics and in topic lists show hierarchy breadcrumb-style: "parent > child’ instead of “child, parent”.

My solution was somewhat site-specific and rather hacky, but the end result is pleasing.

The biggest obstacle to what I did, and what you want to do, is that discourse doesn’t preload the tag groups so they’re not available when you need them without making an API request. I think it should, in the same way that the category structure is preloaded.

3 Likes

The issue here is a space limitation and not a technical limitation.

as noted here

Imagine your experience as a user; if you open a menu on a website, it looks like this.

You’d be overwhelmed, to say the least. Especially since that menu doesn’t have a search function to help you narrow down the results.

So, let’s try to find the middle ground between what you want to do and what users want to experience. How do we do that? We show a limited number of tag groups and indicate that there are more to look at. So, something like this:

So, how do you do that?

Here’s the code:

<script type="text/discourse-plugin" version="0.8">
const MAX_TAGS_TO_SHOW = 20;
const Category = require("discourse/models/category").default;
const siteSettings = api.container.lookup("site-settings:main");
const tagStyle = siteSettings.tag_style;

const getNumberOfTags = (tags, categoryTagsGroups) => {
  let count = 0;
  count = tags.length;
  for (const categoryTagsGroup of categoryTagsGroups) {
    count = count + categoryTagsGroup.tags.length;
  }
  return count;
};

fetch("/tags.json")
  .then(response => response.json())
  .then(data => {
    try {
      const tags = data.tags;
      const hasCategoryTagGroups = data.extras?.categories;

      if (hasCategoryTagGroups) {
        const categoryTagsGroups = data.extras.categories;
        let moreCount = getNumberOfTags(tags, categoryTagsGroups);
        let visibleCount = 0;

        const content = [];
        for (const categoryTagsGroup of categoryTagsGroups) {
          const category = Category.findById(categoryTagsGroup.id);
          const name = category.name;
          const childTags = categoryTagsGroup.tags;
          const childTagNodes = [];

          childTags.forEach((tag, index) => {
            if (visibleCount <= MAX_TAGS_TO_SHOW) {
              childTagNodes.push(
                api.h(
                  "li.tag-link-item",
                  api.h(
                    `a.discourse-tag.tag-link.${tagStyle}`,
                    { href: `/tag/${tag.text}` },
                    tag.text
                  )
                )
              );
              moreCount--;
              visibleCount++;
            }
          });

          if (childTagNodes.length) {
            content.push([
              api.h("li.heading", api.h("span", name)),
              childTagNodes
            ]);
          }
        }

        api.decorateWidget("menu-links:after", helper => {
          if (helper.attrs.name !== "general-links") return;
          return api.h("div.clearfix", [
            api.h("ul.tag-links", [
              api.h("a.categories-link", { href: "/tags" }, [
                "Tags ",
                moreCount ? `(${moreCount} more)...` : ""
              ]),
              content
            ]),
            api.h("hr")
          ]);
        });
      }
    } catch (error) {
      console.error("There's an issue in the hamburger tags theme component");
      console.error(error);
    }
  })
  .catch(console.error);

</script>

This goes in the header tab of your theme. You can change

const MAX_TAGS_TO_SHOW = 20;

at the top to the number of tags you want to show.

Then all you need to add is a bit of CSS to style the links. Here’s something basic to get you started.

.tag-links {
  .heading {
    padding: 0.25em 0.5em;
  }
  .tag-link-item {
    background-color: transparent;
    display: inline-flex;
    align-items: center;
    padding: 0.25em 0.5em;
    width: 50%;
    box-sizing: border-box;
    .tag-link {
      display: inline-flex;
      width: 100%;
      &:hover {
        color: var(--primary);
      }
    }
  }
}

Note that the javascript above will respect the tag style set in your site settings. Also, if your site relies very heavily on tags, then you probably don’t need to have the categories visible in that menu. Hiding them would help reduce user confusion.

Also, if you add an expanded tags section, then this link becomes redundant.

default tags link in hamburger menu

So, let’s hide them with something like this.

.panel-body {
  .category-links,
  .categories-separator,
  .widget-link[href="/tags"] {
    display: none;
  }
}

Finally, as noted here:

This data is not passed to the client by default unless you visit the /tags, so the code above adds an additional request on the homepage (runs only once per visit). Discourse tries to keep things very simple, so, unless the data is necessary, it won’t load it by default.

I don’t see this being added to Discourse core any time soon. So an extra request is pretty much your only choice here unless you want to write a plugin that runs on the server.

7 Likes

Thanks, Joe, this is fantastic!

A couple quick things … I saw that your original response pulled in by tag group (instead of child tags). Any reason for that? I couldn’t get the child tags to surface, but did have luck with your initial response using the tag group.

The only issue is that the tags aren’t visible to logged out users. I’m assuming this is API related? Any way around that?

Otherwise, this is a god-send—I can certainly see others using this code. Thank you so much!

2 Likes

I created a theme component that should make this a little bit easier. You pick between

  1. nested tag groups
  2. nested allowed category tags
  3. flat top tags

More details here

1 Like

I nominate Joe as Patron Saint of Discourse.

1 Like

hahaha, i was busy creating one as well for my training course.

darn.

thanks anyway

2 Likes