Feature: Internal Tags - Tags for Structure & Workflow
See original GitHub issueWe’ve made pretty huge progress in terms of all the new tag features that were outlined in Tags 101, as well as massively overhauling all of the UIs related to tags.
One feature we’ve not looked at adding yet, is adding two levels of ‘Visibility’ so that we can have a concept of hidden tags. This feature has been referred to as ‘private’ tags, however I think it may be a good idea to not use the word ‘private’ as everywhere else private data explicitly refers to data that you must be authenticated to see (draft posts vs published) and this idea is not related to auth. I’m using ‘hidden’ as that’s the name of the column in the database, but ‘system’ or ‘internal’ might work too.
Problem
At the moment, every tag you add to a post is the same. They all get listed on a post when it is published, and also get their own tag page / archive that lists out all of the posts. This makes perfect sense for tags which are meant to be a front-facing taxonomy, but limits the system so that tags cannot be used for workflow or structure. The idea of hidden tags is to make tags also useful for workflow and structure use cases:
Example 1: I want to tag a post with needs-review
once I’ve finished writing it so my editor can pick it up.
At the moment you can do this, but you have to remember to remove the tag before publishing, and there is also not a good way to view all the posts marked with need-review
in the admin panel.
Example 2: I want to implement link-posts which just link out to another site using a tag to mark my link
posts so that my theme can handle them differently.
I can tag all these posts as link
and handle this in my theme, however I also get link
output in the tags list and all of these posts appear on /tag/link/
which isn’t ideal.
Implementation
To solve this, tags being set to ‘hashtag’ will have two major impacts:
- There will not be a page at
/tag/:slug/
for this tag - This tag will not be output by the
{{tags}}
helper
The tag will still be returned by the API like any other tag, and available to theme, meaning it will still be possible to use {{#has tag="#hashtagname"}}
to check if the tag is set on a post.
One minor potential confusion point will be that any theme which has opted to cycle through tags manually rather than use the {{tags}}
helper will show hidden tags. These themes will need updating with a {{^unless hashtag}}
condition inside their tag loops.
Creating hashtags
When adding tags in the editor, there’s only a way to specify the name, rather than the type of tag. To combat this and make adding hidden tags quick & easy, it would be great if this could be controlled using a simple prefix. E.g. #needs-review
. Any tag that has a #
at the start of the name should automatically be marked as a hashtag. To resolve naming clashes in slugs between hidden and non-hidden tags with similar names, I think it would make sense to prefix the slugs for any hidden tags auto-created with a #
with hash-``. So if I already have
linktag, and I create a
#linktag, the slug for the first would be
linkand for the second
hash-link`, etc.
Filtering in the admin UI
To really complete the power of this new feature, we need to be able to filter posts by tags in the admin UI. There are two aspects to this which I can think of:
- Showing posts which match by tag as well as title in the autocomplete search box
- Being able to get from a tag to a filtered content screen list of the posts that match that tag
I think there are a couple of potential investigations / spikes to do here around what can be done with the autocomplete search & content screen - can we list posts by tag in the autocomplete results? Can we make it so that pressing ‘enter’ on a term rather than clicking an item in the dropdown results in a filtered content screen list? How does it feel to have this result in a filter rather than a direct go-to-resource action as it does now?
Tasks
Prep:
- Add labs flag for internal tags
- Rework the server-side labs utility to be synchronous (so that
{{tags}}
doesn’t become async) - Bonus: get config override working for client-side labs again
- Update DB to have visibility property
When the labs flag is checked:
Add new checkbox to tag management screen to mark/unmark a tag as hashtag- Filter the result from
{{tags}}
to not include hashtags - Return a 404 from
/tags/:slug/
for hashtags - Create tags starting with
#
as a hashtag in the tag input component - Display hashtags in the content list
Other:
- Spike on the autocomplete & content list filter to see what works easily and if there are any dependencies or other requirements here.
Cleanup:
- Remove labs flag
- Add proper tests for 404 tag, and
tags.read()
with hash parameter
Issue Analytics
- State:
- Created 8 years ago
- Comments:6 (4 by maintainers)
Top GitHub Comments
@WaqasIbrahim Tag names have never been suitable for use as class names, as they can include any character except
,
that includes spaces, tabs, etc. Instead, you should use theslug
version of the name for a classname. The sluggified name is all lowercase and hyphenated and therefore suitable for class names.Since the original issue for this feature was written, we’ve done most of the implementation. However, the spec has changed a little bit.
Originally, we were referring to these tags as “hidden tags” because they are hidden from the blog & the boolean field used to mark them in the DB was called
hidden
. Then they became “hashtags” because they are prefixed with#
and look like social media style#hashtags
, however over time that name for the feature has evolved into the slightly clearer “internal tags”.We are no longer using a simple boolean field in the database to mark these tags as “hidden”, but instead we now have a generic field called
visibility
which is a string and appears on tags, posts and users. It has validation at the model layer which restricts the value so that it operates as an enum. Across the board the default value ispublic
, but tags also support a value ofinternal
which is used to indicate an internal tag.From an API perspective, this gives us lots of flexibility for adding various levels of visibility to the main models. However, from a theming perspective, it means that it’s no longer easy to check the boolean flag of whether a particular tag is an internal tag or not. So, this part of the spec is no longer valid:
Handlebars doesn’t let us compare strings, so within a foreach loop, it’s now not possible to filter out the internal tags - you have to use
{{tags}}
. This brought me to a new, and I think better idea.With the new global concept of
visibility
on all of our core resources, it makes sense to honour this property in the{{foreach}}
helper. We made a decision back at the beginning of the theme API to design our own loop helper rather than use the built-in handlebars one… so let’s make use of that to correctly reflect the business logic of a Ghost theme.The
{{#foreach}}
helper &{{tags}}
helper will both be updated to, by default, only output items which have avisibility
ofpublic
. They would also both have a newvisibility
attribute added to them, that allows a developer to override the default behaviour.By default
{{#foreach "tags"}}
would behave the same as{{#foreach "tags" visibility="public"}}
By default{{tags}}
would behave the same as{{tags visibility="public"}}
If you wanted to override the behaviour and output all tags, the helpers could be overridden with
{{#foreach "tags" visibility="public, internal"}}
or{{tags visibility="public, internal"}}
. We could also consider supporting a value ofall
.