PhoenixKit.Modules.Publishing.Hashtags (PhoenixKitPublishing v0.4.5)

Copy Markdown View Source

Body hashtags ARE the tag system (boss call, 2026-07-28): writers type #elixir inline and the post's tags derive from the text on save — there is no separate tags field. The public side is unchanged (tag archives, feeds, chips); only the source of truth moved into the prose.

What counts as a hashtag

# followed immediately by a letter, at the start of a line or after whitespace/(. That rule excludes, by construction:

  • Markdown headings (# Title — the space after # breaks the match);
  • URL fragments (…/page#section — no preceding whitespace);
  • code (fenced blocks and inline backticks are stripped before scanning, mirroring the Renderer's component-scan mask);
  • Markdown links/images ([jump](#section) is an anchor, and a tag inside link text would nest links).

Letters are Unicode (#uudised and #новости tag fine); digits, underscore and hyphen may follow. Dedup is case-insensitive keeping the first spelling; capped at 20 per document (the old input's cap).

Summary

Functions

All hashtags in a document, first-spelling-wins, order-preserving.

The tag set for a whole version: the union of hashtags across every language's content (per-language bodies may tag differently; the version carries one combined list, same storage as before).

Rewrites every hashtag into a Markdown link ([#tag](url_fun.(tag))), leaving code regions and existing Markdown links untouched — the renderer runs this before the Markdown pass so the links pick up the prose link styling.

Tags already used in a group, for the editor's # autocomplete: the distinct tags across the group's posts, ranked prefix-match first, then by how often they're used, then alphabetically.

Functions

extract(content)

All hashtags in a document, first-spelling-wins, order-preserving.

extract_all(contents)

The tag set for a whole version: the union of hashtags across every language's content (per-language bodies may tag differently; the version carries one combined list, same storage as before).

linkify(content, url_fun)

Rewrites every hashtag into a Markdown link ([#tag](url_fun.(tag))), leaving code regions and existing Markdown links untouched — the renderer runs this before the Markdown pass so the links pick up the prose link styling.

suggest(group_slug, query, opts \\ [])

Tags already used in a group, for the editor's # autocomplete: the distinct tags across the group's posts, ranked prefix-match first, then by how often they're used, then alphabetically.

Reads the listing cache (which already carries each post's tags), so typing a # costs no database round trip. Returns [%{tag: "elixir", count: 12}], capped by :limit.