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
 
 
x1
x1
x1
 
 
x1
 
 
 
x1
x1
x1
x2
x2
x1
 
x1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
x1
x1
x2
x2
x3
x4
x5
x9
x12
x12
x10
x5
x4
x3
x3
x2
x2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 













































































































// 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
}