All files / markdown / plugins / wikilinks.ts

78.57% Branches 11/14
100.00% Functions 3/3
83.33% Lines 20/24
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
 
 
x20
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
x20
x3
 
 
x5
 
 
x5
x5
x5
x5
x5
x5
x5
x5
x5
x5
x5
x5
x5
x5
x3
x3




































I


I




I










// Imports
import type { MarkdownIt } from "../types.ts"
import { slugify as slug } from "@std/text/unstable-slugify"

/** Wiki links options. */
export type WikilinksOptions = {
  /** Indicate which possible slugs are possible for a given reference. */
  slugify?: (name: string) => string[]
  /** Indicate how to resolve a link to a URL. */
  resolve?: (link: string) => string
  /** List of existing permalinks (any link not present in the list will possess the `new` class). */
  existing?: string[]
}

/**
 * Add support for wiki links.
 *
 * Use the `existing` option to provide a list of existing permalinks.
 * Any link not present in the list will possess the `new` class.
 *
 * Use the `slugify` option to indicate which possible slugs are possible for a given reference.
 * The first slug present in the list of existing permalinks is used, defaulting to the first generated slug.
 *
 * Use the `resolve` option to indicate how to resolve a link to a URL.
 *
 * All links generated by this plugin will have the `wikilink` class.
 *
 * ```md
 * [[foo]]
 * ```
 * ```html
 * <a class="wikilink" href="/pages/foo">foo</a>
 * ```
 */
export default function wikilinks(engine: MarkdownIt, { slugify = (name) => [slug(name)], resolve = (link) => `/pages/${link}`, existing }: WikilinksOptions = {}): void {
  engine.inline.ruler.before("link", "wikilinks", (state, silent) => {
    if (state.src.slice(state.pos, state.pos + 2) !== "[[")
      return false
    const match = /^\[\[([^\]|\n]+)(?:\|([^\]\n]+))?\]\]/.exec(state.src.slice(state.pos, state.posMax))
    if (!match)
      return false
    if (!silent) {
      const name = match[1].trim()
      const label = match[2]?.trim() || name
      const slugs = slugify(name)
      const target = slugs.find((slugged) => existing?.includes(slugged)) ?? slugs[0] ?? name
      const created = Array.isArray(existing) && (!existing.includes(target))
      const open = state.push("link_open", "a", 1)
      open.attrSet("class", `wikilink${created ? " new" : ""}`)
      open.attrSet("href", resolve(target))
      state.push("text", "", 0).content = label
      state.push("link_close", "a", -1)
    }
    state.pos += match[0].length
    return true
  })
}