Source file admonition_extension.ml

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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
(** Admonition extension for odoc.

    Provides styled callout blocks for documentation:
    - [@admonition] or [@admonition.note] - Informational notes
    - [@admonition.warning] - Warning messages
    - [@admonition.tip] - Helpful tips
    - [@admonition.important] - Important information
*)

open Odoc_extension_api

let prefix = "admonition"

(** CSS styles for admonitions - registered as a support file *)
let admonition_css = {|
/* Admonition extension styles */

/* Reset the parent ul to align with content flow */
.at-tags:has(li[class^="admonition"]) {
  margin-left: 0;
}

/* Style the outer container */
.at-tags li[class^="admonition"] {
  padding: 0.875em 1em;
  margin: 1em 0;
  border-left: 3px solid;
  border-radius: 2px;
  text-indent: 0;
  padding-left: 1em;
  list-style: none;
}

/* Remove duplicate styling from inner paragraphs */
.admonition-note, .admonition-warning, .admonition-tip, .admonition-important {
  padding: 0;
  margin: 0;
  border: none;
  background: transparent;
}

/* Container colors - light mode */
.at-tags li[class^="admonition"][class*="note"],
.at-tags li[class="admonition"] {
  border-color: #1976D2;
  background: #F5F9FD;
  color: #1565C0;
}
.at-tags li[class*="warning"] {
  border-color: #EF6C00;
  background: #FFF8F0;
  color: #E65100;
}
.at-tags li[class*="tip"] {
  border-color: #2E7D32;
  background: #F5FAF5;
  color: #1B5E20;
}
.at-tags li[class*="important"] {
  border-color: #C62828;
  background: #FDF5F5;
  color: #B71C1C;
}

.admonition-label {
  font-weight: 600;
  margin-bottom: 0.25em;
  display: block;
  letter-spacing: 0.01em;
}

/* Dark mode support */
@media (prefers-color-scheme: dark) {
  .at-tags li[class^="admonition"][class*="note"],
  .at-tags li[class="admonition"] {
    border-color: #64B5F6;
    background: rgba(33, 150, 243, 0.08);
    color: #90CAF9;
  }
  .at-tags li[class*="warning"] {
    border-color: #FFB74D;
    background: rgba(255, 152, 0, 0.08);
    color: #FFCC80;
  }
  .at-tags li[class*="tip"] {
    border-color: #81C784;
    background: rgba(76, 175, 80, 0.08);
    color: #A5D6A7;
  }
  .at-tags li[class*="important"] {
    border-color: #E57373;
    background: rgba(244, 67, 54, 0.08);
    color: #EF9A9A;
  }
}
|}

(** Map tag variants to admonition types *)
type admonition_type = Note | Warning | Tip | Important

let parse_type = function
  | "admonition" | "admonition.note" -> Note
  | "admonition.warning" -> Warning
  | "admonition.tip" -> Tip
  | "admonition.important" -> Important
  | tag -> raise (Unsupported_tag tag)

let type_to_class = function
  | Note -> "admonition-note"
  | Warning -> "admonition-warning"
  | Tip -> "admonition-tip"
  | Important -> "admonition-important"

let type_to_label = function
  | Note -> "Note"
  | Warning -> "Warning"
  | Tip -> "Tip"
  | Important -> "Important"

(** Convert comment content to Block.t, preserving references and formatting *)
let content_to_blocks content =
  Odoc_document.Comment.nestable_block_element_list content

(** Document phase - wrap content in styled admonition block *)
let to_document ~tag content =
  let admon_type = parse_type tag in
  let class_name = type_to_class admon_type in
  let label = type_to_label admon_type in

  let label_inline = Inline.[{ attr = [ "admonition-label" ]; desc = Text label }] in
  let content_blocks = content_to_blocks content in

  let blocks =
    Block.[{ attr = [ class_name ]; desc = Paragraph label_inline }] @
    (List.map (fun b -> Block.{ b with attr = class_name :: b.attr }) content_blocks)
  in

  {
    content = blocks;
    overrides = [];
    resources = [
      (* Reference the external CSS file instead of inline *)
      Css_url "extensions/admonition.css";
    ];
    assets = [];
  }

(* Register extension and its support files when plugin is loaded *)
let () =
  (* Register the extension *)
  Registry.register (module struct
    let prefix = prefix
    let to_document = to_document
  end);
  (* Register CSS as a support file *)
  Registry.register_support_file ~prefix {
    filename = "extensions/admonition.css";
    content = admonition_css;
  }