Skip to content

Conversation

@halostatue
Copy link
Contributor

@halostatue halostatue commented Dec 11, 2025

This change makes tags with spaces or special characters play nicely with the Web. By default, tag display values will be converted to slugs using Slugify when building permalinks.

The tag extension configuration has been extended to have a :tags option with a map of display values to options (the only supported option is :slug):

config :tableau, Tableau.TagExtension,
  tags: %{
    "C++" => [slug: "c-plus-plus"],
  }

This will be used instead of automatic slug conversion. Automatic slug conversion can be configured in the main Tableau configuration with the :slug keyword option, which is passed directly to Slugify.

Other extension writers may use Tableau.Extension.Common.slugify/2.

Resolves: #160

@halostatue halostatue changed the title feat: Slugify tag names feat: slugify tag names Dec 11, 2025
@mhanberg
Copy link
Collaborator

Mostly looks good.

Since the permalink is basically the real identifier of a tag, what happens if you have both the in memoriam and in-memoriam tags? are those considered the same?

I think my intuition says that currently this would treat them as different, but the one that gets processed second will overwrite the first in the file system.

@halostatue
Copy link
Contributor Author

I think you're correct, but can do some verification this evening.

This could be made certain by actually making the permalink the key in the t:tags/0 map, but I feel like that could result in a breaking change for anyone upgrading. I'd need to look at it, but I think %{permalink :: String.t() => %{tag: tag(), pages: [Tableau.PostExtension.post()]} and ensuring that the .pages is what's hoisted into the assigns for the loop…but I'm not sure if that's possible. Would need to look.

@halostatue
Copy link
Contributor Author

Your intuition is correct. The current solution is problematic because it shows two separate tags, but they both render to the same filename overwriting each other, so only one of the two actually shows a linked page.

I have a fix that I'm adding as a second commit that resolves this by having a two-stage map to prevent an external API change. Instead of mapping tag => posts, I map slug => %{tag: tag, posts: posts} and then map that into tag => posts.

There's a display oddity where the display value (tag.tag or tag.title) will vary based on which variant (in memoriam or in-memoriam) is processed last, but they resolve to the same effective tag. That is, if I have two pages:

  • Manuel Orozco (tagged in memoriam)
  • Calvin Franz (tagged in-memoriam)

The display value will depend on the file sort order. If Orozco is processed first, then the tag on the tag page will be displayed as in-memoriam; otherwise it will be in memoriam. On Orozco's page, the tag will be displayed as in memoriam and on Franz's page, it will be displayed as in-memoriam. That's an unavoidable wart that I think is easily ignored for safer web (and case-preserving filesystems like APFS) behaviour.

I'm using a clean commit but am happy to squash it down if you prefer.

@mhanberg
Copy link
Collaborator

Does it make more sense to URI encode the tag names for the permalink rather than turning them into slugs? that way you can make them whatever you want without worrying that they'll merge or overwrite another.

@halostatue
Copy link
Contributor Author

I think that the slug approach is more natural given that this is for the web, and spaces in URLs are poorly handled in general—even when URI-encoded.

The disconnect comes from what you want to see and what you want to Just Work regardless of your hosting solution.

@mhanberg
Copy link
Collaborator

Okay, i've done some research, and I think this is a reasonable approach.

One thing I'd like to add and I think is doable in this PR, is to make it so you can hard code a tags slug.

I believe this is best done in the TagExtension's config, something like

config :tableau, Tableau.TagExtension,
  tags: %{
    "C++" => [slug: "c-plus-plus"],
  }

If a tag is not configured, it just does the default behavior.

@mhanberg mhanberg changed the title feat: slugify tag names feat(tags): slugify tag names Dec 17, 2025
@halostatue
Copy link
Contributor Author

halostatue commented Dec 18, 2025

Sure. I'll see about doing that a bit later.

One question: should we "lint" the configured tags to ensure there's no spaces or do we make it buyer-beware?

@mhanberg
Copy link
Collaborator

Buyer beware for now. Seems like what the other projects do.

This change makes tags with spaces or special characters play nicely
with the Web. By default, tag display values will be converted to
`slugs` using [Slugify][slugify] when building permalinks.

The tag extension configuration has been extended to have a `:tags`
option with a map of display values to options (the only supported
option is `:slug`):

```elixir
config :tableau, Tableau.TagExtension,
  tags: %{
    "C++" => [slug: "c-plus-plus"],
  }
```

This will be used instead of automatic slug conversion. Automatic slug
conversion can be configured in the main Tableau configuration with the
`:slug` keyword option, which is passed directly to [Slugify][slugify].

Other extension writers may use `Tableau.Extension.Common.slugify/2`.

[slugify]: https://hexdocs.pm/slugify/Slug.html

Resolves: elixir-tools#160
@mhanberg mhanberg merged commit 00defac into elixir-tools:main Dec 18, 2025
8 checks passed
@halostatue halostatue deleted the slugify-tag-names branch December 24, 2025 03:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

TagExtension support for tags with special characters in them (spaces, '/', etc.)

2 participants