TryGhost / Ghost

Independent technology for modern publishing, memberships, subscriptions and newsletters.
https://ghost.org
MIT License
47.46k stars 10.35k forks source link

Feature: Internal Tags - Tags for Structure & Workflow #6165

Closed ErisDS closed 8 years ago

ErisDS commented 8 years ago

We'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:

  1. There will not be a page at /tag/:slug/ for this tag
  2. 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 havelinktag, and I create a#linktag, the slug for the first would belinkand for the secondhash-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:

  1. Showing posts which match by tag as well as title in the autocomplete search box
  2. 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:

When the labs flag is checked:

Other:

Cleanup:

kevinansfield commented 8 years ago

Do we also want to show the "system" labels in the posts list as a quick identifier? Using github's issue list as an example it's easy to see at a glance which posts have which status/tag:

screen shot 2015-12-03 at 11 50 50
ErisDS commented 8 years ago

I really like that idea :+1:

ErisDS commented 8 years ago

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 is public, but tags also support a value of internal 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:

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.

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 a visibility of public. They would also both have a new visibility 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 of all.

kenofthenorth commented 8 years ago

Hidden tags should also be removed from meta tags like 'twitter:data2' Thanks!

WaqasIbrahim commented 8 years ago

Hi, I am using internal tags in my theme development but i am having a small issue. I wanted to use internal tags for post types and use them as class names. Currently as far as i know internal tags are fetched along with the hashtag. I am using a workaround with javascript to remove the hashtags so they can be used as class names. Is there a way to fetch them without the hashtag? Thanks

ErisDS commented 8 years ago

@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 the slug version of the name for a classname. The sluggified name is all lowercase and hyphenated and therefore suitable for class names.