All files / plugins / directives.ts

100.00% Branches 2/2
100.00% Lines 29/29
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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
x1
 
 
x4
x4
x4
 
 
x1
 
 
 
x4
x4
x4
x5
x5
x4
 
x1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
x4
x4
x5
x5
x6
x7
x8
x12
x15
x15
x13
x8
x7
x6
x6
x5
x5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 













































































































// Imports
import type { Plugin } from "../renderer.ts"
import type { Arg, Nullable, Optional } from "@libs/typing/types"
import remarkDirective from "remark-directive"
import { visit } from "unist-util-visit"
export { h } from "hastscript"
import type { Data, ElementContent, Properties } from "hast"

/**
 * Add support for custom directives.
 *
 * {@link https://github.com/remarkjs/remark-directive?tab=readme-ov-file#examples | See remark-directive for more information}.
 */
export default {
  remark(processor) {
    return processor.use(remarkDirective)
  },
} as Plugin

/**
 * Create a new custom directive.
 *
 * @example
 * ```ts
 * import { Renderer } from "../renderer.ts"
 * import directives, { directive, h } from "./directives.ts"
 *
 * const foo = directive((node) => {
 *   node.data ??= {}
 *   node.data.hName = "div"
 *   node.data.hProperties = h(node.data.hName, { bar: "qux" }).properties
 * })
 *
 * const markdown = new Renderer({ plugins: [directives, foo] })
 * await markdown.render(`
 * :::foo
 * baz
 * :::
 * `.trim())
 * ```
 */
export function directive(callback: (node: AugmentedNode) => void): Plugin {
  return {
    remark(processor) {
      return processor.use(function () {
        return function (tree: Arg<typeof visit>) {
          visit(tree, (node) => {
            if (!/(?:container|leaf|text)Directive/.test(node.type)) {
              return
            }
            callback(node as AugmentedNode)
          })
        }
      })
    },
  } as Plugin
}

/**
 * Node.
 *
 * Augmented from {@link https://github.com/syntax-tree/mdast-util-directive/blob/main/index.d.ts | mdast-util-directive}.
 */
interface AugmentedNode extends Arg<typeof visit> {
  /**
   * Directive name.
   */
  name: string
  /**
   * Directive attributes.
   */
  attributes?: Nullable<Record<string, Optional<Nullable<string>>>>

  /**
   * Info from the ecosystem.
   */
  data?: AugmentedData

  /**
   * Children of the directive.
   */
  children: AugmentedNode[]
}

/**
 * Info associated with hast nodes by the ecosystem.
 *
 * Augmented from {@link  https://github.com/syntax-tree/mdast-util-to-hast/blob/main/index.d.ts | mdast-util-to-hast}.
 */
interface AugmentedData extends Data {
  /**
   * Signal that a node should result in something with these children.
   *
   * When this is defined, when a parent is created, these children will be used.
   */
  hChildren?: ElementContent[]

  /**
   * Signal that a node should result in a particular element, instead of its default behavior.
   *
   * When this is defined, an element with the given tag name is created.
   * For example, when setting `hName` to `'b'`, a `<b>` element is created.
   */
  hName?: string

  /**
   * Signal that a node should result in an element with these properties.
   *
   * When this is defined, when an element is created, these properties will be used.
   */
  hProperties?: Properties
}