# `PhoenixKitKnowledgeBase.Markdown`
[🔗](https://github.com/BeamLabEU/phoenix_kit_knowledge_base/blob/v0.1.0/lib/phoenix_kit_knowledge_base/markdown.ex#L1)

Everything this module knows how to read out of a note's markdown: YAML front
matter, tags, `[[wiki-links]]`, and the rendered HTML.

The markdown is the single source of truth. `title_key`, `frontmatter`, `tags`
and the whole link index are derived from it on save, which is what keeps
notes portable — the database holds nothing you could not recover by
re-reading the text.

## Code is not scanned

Tags and wiki-links are only read from *outside* code, so a `[[Foo]]` inside a
fenced block or a backtick span stays literal instead of quietly becoming a
link and a phantom entry in the vault's unresolved list. Fenced blocks
(```` ``` ````, `~~~`) and inline spans are handled; four-space indented code
blocks are not, because telling one apart from a wrapped list item needs a
real block parser.

# `wiki_link`

```elixir
@type wiki_link() :: %{key: String.t(), target: String.t(), label: String.t()}
```

One parsed `[[wiki-link]]`.

  * `:key`    — normalised target, what resolution matches on
  * `:target` — the target as written (without any `#heading` part)
  * `:label`  — what to display: the alias after `|`, or the target

# `excerpt`

```elixir
@spec excerpt(String.t() | nil, pos_integer()) :: String.t()
```

A short plain-text excerpt for note lists — front matter, markdown syntax and
wiki-link brackets removed.

Cosmetic only: it is never stored, and never used for search (that is the
`tsvector` column's job).

# `extract_tags`

```elixir
@spec extract_tags(map(), String.t()) :: [String.t()]
```

All tags for a note: front matter `tags:` merged with inline `#tags` in the
body, downcased, de-duplicated and sorted.

Front matter accepts either a list (`tags: [a, b]`) or a single string
(`tags: a, b`), which are the two spellings Obsidian itself accepts.

Tags are downcased so `#Project` and `#project` are one tag — they are a
filtering key here, not a display string.

# `normalize_title`

```elixir
@spec normalize_title(String.t() | nil) :: String.t() | nil
```

The canonical form of a note title — downcased with runs of whitespace
collapsed.

This is what the per-vault unique index constrains and what `[[wiki-links]]`
match on, so "My  Note", "my note" and " My Note " are all the same note.

# `parse_wiki_link`

```elixir
@spec parse_wiki_link(String.t()) :: wiki_link() | nil
```

Parses the inside of one `[[...]]` into a `t:wiki_link/0`, or `nil` if there
is no target left after trimming.

`[[Target#Heading|Alias]]` keeps `Alias` as the label and resolves against
`Target` — the heading anchor is dropped, since this module has no
heading-level addressing yet.

# `split_frontmatter`

```elixir
@spec split_frontmatter(String.t() | nil) :: {map(), String.t()}
```

Splits YAML front matter off the top of a note.

Returns `{properties, body}`. A note with no front matter returns `{%{}, note}`
unchanged — and so does one whose front matter is invalid YAML, deliberately:
a typo in a property block must not swallow the writing underneath it. The
broken block stays visible in the body, where the author can see and fix it.

# `to_html`

```elixir
@spec to_html(String.t() | nil, (wiki_link() -&gt; {:resolved | :unresolved, String.t()})) ::
  String.t()
```

Renders a note to sanitized HTML, with `[[wiki-links]]` turned into real
links.

> #### No caller in the UI, on purpose {: .info}
>
> There is one editing surface — Leaf — and it renders markdown itself, so
> nothing in the LiveView calls this today. It is kept because it is the
> server half of wiki-link rendering: when Leaf learns to display `[[…]]`,
> this is the function that tells it which links resolve, where they point,
> and which are dangling. Its tests pin that contract, which is the thing
> worth not losing.

`resolver` is called once per distinct link with a `t:wiki_link/0` and must
return `{:resolved, href}` or `{:unresolved, href}`. Unresolved links get a
muted, dotted-underline style so a vault's gaps are visible while reading —
the class survives sanitization, which allows `class` on any tag.

Front matter is stripped rather than rendered; the properties are shown by
the UI, not by the prose.

# `wiki_links`

```elixir
@spec wiki_links(String.t() | nil) :: [wiki_link()]
```

Every distinct `[[wiki-link]]` in a body, in order of first appearance.

De-duplicated by `:key`, because writing `[[Foo]]` five times in one note is
one link, not five — the same thing the `(source_note_uuid, target_key)`
unique index enforces in the database.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
