@dr-ishaan/remake-blocks 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,628 +0,0 @@
1
- /**
2
- * remark plugin for @dr-ishaan/remake-blocks
3
- *
4
- * A remark plugin that transforms callout directives inside blockquotes
5
- * into styled callout components, and optionally enhances regular blockquotes.
6
- *
7
- * 29 first-class callout types — each directive maps 1:1 to its own
8
- * unique visual identity. No alias resolution.
9
- */
10
- import { visit } from "unist-util-visit";
11
- // ---------------------------------------------------------------------------
12
- // 29 Built-in callout configurations
13
- // ---------------------------------------------------------------------------
14
- const BUILTIN_CALLOUTS = [
15
- // ── GFM Primaries (5) ─────────────────────────────────────────────────
16
- {
17
- type: "note",
18
- icon: `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg>`,
19
- className: "callout-note",
20
- defaultTitle: "Note",
21
- color: "#0969da",
22
- backgroundColor: "#ddf4ff",
23
- iconColor: "#0969da",
24
- },
25
- {
26
- type: "tip",
27
- icon: `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5"/><path d="M9 18h6"/><path d="M10 22h4"/></svg>`,
28
- className: "callout-tip",
29
- defaultTitle: "Tip",
30
- color: "#1a7f37",
31
- backgroundColor: "#dafbe1",
32
- iconColor: "#1a7f37",
33
- },
34
- {
35
- type: "important",
36
- icon: `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>`,
37
- className: "callout-important",
38
- defaultTitle: "Important",
39
- color: "#8250df",
40
- backgroundColor: "#fbefff",
41
- iconColor: "#8250df",
42
- },
43
- {
44
- type: "warning",
45
- icon: `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>`,
46
- className: "callout-warning",
47
- defaultTitle: "Warning",
48
- color: "#9a6700",
49
- backgroundColor: "#fff8c5",
50
- iconColor: "#9a6700",
51
- },
52
- {
53
- type: "caution",
54
- icon: `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/></svg>`,
55
- className: "callout-caution",
56
- defaultTitle: "Caution",
57
- color: "#cf222e",
58
- backgroundColor: "#ffebe9",
59
- iconColor: "#cf222e",
60
- },
61
- // ── Obsidian Primaries (10) ────────────────────────────────────────────
62
- {
63
- type: "abstract",
64
- icon: `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8Z"/><path d="M14 2v6h6"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><line x1="10" y1="9" x2="8" y2="9"/></svg>`,
65
- className: "callout-abstract",
66
- defaultTitle: "Abstract",
67
- color: "#0891b2",
68
- backgroundColor: "#ecfeff",
69
- iconColor: "#0891b2",
70
- },
71
- {
72
- type: "info",
73
- icon: `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/></svg>`,
74
- className: "callout-info",
75
- defaultTitle: "Info",
76
- color: "#57606a",
77
- backgroundColor: "#f6f8fa",
78
- iconColor: "#57606a",
79
- },
80
- {
81
- type: "success",
82
- icon: `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><path d="M22 4 12 14.01l-3-3"/></svg>`,
83
- className: "callout-success",
84
- defaultTitle: "Success",
85
- color: "#1a8840",
86
- backgroundColor: "#caf7ca",
87
- iconColor: "#1a8840",
88
- },
89
- {
90
- type: "question",
91
- icon: `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><path d="M12 17h.01"/></svg>`,
92
- className: "callout-question",
93
- defaultTitle: "Question",
94
- color: "#bf6c06",
95
- backgroundColor: "#fff1e5",
96
- iconColor: "#bf6c06",
97
- },
98
- {
99
- type: "failure",
100
- icon: `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/></svg>`,
101
- className: "callout-failure",
102
- defaultTitle: "Failure",
103
- color: "#b33a3a",
104
- backgroundColor: "#ffe2e2",
105
- iconColor: "#b33a3a",
106
- },
107
- {
108
- type: "danger",
109
- icon: `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg>`,
110
- className: "callout-danger",
111
- defaultTitle: "Danger",
112
- color: "#cf222e",
113
- backgroundColor: "#ffebe9",
114
- iconColor: "#cf222e",
115
- },
116
- {
117
- type: "quote",
118
- icon: `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 21c3 0 7-1 7-8V5c0-1.25-.756-2.017-2-2H4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2 1 0 1 0 1 1v1c0 1-1 2-2 2s-1 .008-1 1.031V21z"/><path d="M15 21c3 0 7-1 7-8V5c0-1.25-.757-2.017-2-2h-4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2h.75c0 2.25.25 4-2.75 4v3z"/></svg>`,
119
- className: "callout-quote",
120
- defaultTitle: "Quote",
121
- color: "#656d76",
122
- backgroundColor: "#f6f8fa",
123
- iconColor: "#656d76",
124
- },
125
- {
126
- type: "bug",
127
- icon: `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m8 2 1.88 1.88"/><path d="M14.12 3.88 16 2"/><path d="M9 7.13v-1a3.003 3.003 0 1 1 6 0v1"/><path d="M12 20c-3.3 0-6-2.7-6-6v-3a4 4 0 0 1 4-4h4a4 4 0 0 1 4 4v3c0 3.3-2.7 6-6 6"/><path d="M12 20v-9"/><path d="M6.53 9C4.6 8.8 3 7.1 3 5"/><path d="M6 13H2"/><path d="M3 21c0-2.1 1.7-3.9 3.8-4"/><path d="M20.97 5c0 2.1-1.6 3.8-3.5 4"/><path d="M22 13h-4"/><path d="M17.2 17c2.1.1 3.8 1.9 3.8 4"/></svg>`,
128
- className: "callout-bug",
129
- defaultTitle: "Bug",
130
- color: "#c93257",
131
- backgroundColor: "#ffedf2",
132
- iconColor: "#c93257",
133
- },
134
- {
135
- type: "example",
136
- icon: `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><line x1="10" y1="9" x2="8" y2="9"/></svg>`,
137
- className: "callout-example",
138
- defaultTitle: "Example",
139
- color: "#8250df",
140
- backgroundColor: "#fbefff",
141
- iconColor: "#8250df",
142
- },
143
- {
144
- type: "todo",
145
- icon: `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="18" x="3" y="3" rx="2"/><path d="m9 12 2 2 4-4"/></svg>`,
146
- className: "callout-todo",
147
- defaultTitle: "Todo",
148
- color: "#2274a5",
149
- backgroundColor: "#e5f2fc",
150
- iconColor: "#2274a5",
151
- },
152
- // ── Promoted Aliases — first-class types (14) ──────────────────────────
153
- {
154
- type: "summary",
155
- icon: `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8Z"/><path d="M14 2v6h6"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg>`,
156
- className: "callout-summary",
157
- defaultTitle: "Summary",
158
- color: "#0e7490",
159
- backgroundColor: "#f0fdfa",
160
- iconColor: "#0e7490",
161
- },
162
- {
163
- type: "tldr",
164
- icon: `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg>`,
165
- className: "callout-tldr",
166
- defaultTitle: "TL;DR",
167
- color: "#06b6d4",
168
- backgroundColor: "#ecfeff",
169
- iconColor: "#06b6d4",
170
- },
171
- {
172
- type: "hint",
173
- icon: `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 18v3c0 .6.4 1 1 1h4v-3h3v-3h2l1.4-1.4a6.5 6.5 0 1 0-4-4Z"/><circle cx="16.5" cy="7.5" r=".5" fill="currentColor"/></svg>`,
174
- className: "callout-hint",
175
- defaultTitle: "Hint",
176
- color: "#3d8b37",
177
- backgroundColor: "#e8f5e9",
178
- iconColor: "#3d8b37",
179
- },
180
- {
181
- type: "check",
182
- icon: `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="18" x="3" y="3" rx="2"/><path d="m9 12 2 2 4-4"/></svg>`,
183
- className: "callout-check",
184
- defaultTitle: "Check",
185
- color: "#0d9488",
186
- backgroundColor: "#f0fdfa",
187
- iconColor: "#0d9488",
188
- },
189
- {
190
- type: "done",
191
- icon: `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><path d="M22 4 12 14.01l-3-3"/></svg>`,
192
- className: "callout-done",
193
- defaultTitle: "Done",
194
- color: "#15803d",
195
- backgroundColor: "#dcfce7",
196
- iconColor: "#15803d",
197
- },
198
- {
199
- type: "help",
200
- icon: `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><path d="M12 17h.01"/></svg>`,
201
- className: "callout-help",
202
- defaultTitle: "Help",
203
- color: "#c26506",
204
- backgroundColor: "#fff7ed",
205
- iconColor: "#c26506",
206
- },
207
- {
208
- type: "faq",
209
- icon: `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/><path d="M8 10h.01"/><path d="M12 10h.01"/><path d="M16 10h.01"/></svg>`,
210
- className: "callout-faq",
211
- defaultTitle: "FAQ",
212
- color: "#b45309",
213
- backgroundColor: "#fffbeb",
214
- iconColor: "#b45309",
215
- },
216
- {
217
- type: "attention",
218
- icon: `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>`,
219
- className: "callout-attention",
220
- defaultTitle: "Attention",
221
- color: "#ca8a04",
222
- backgroundColor: "#fefce8",
223
- iconColor: "#ca8a04",
224
- },
225
- {
226
- type: "fail",
227
- icon: `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="7.86 2 16.14 2 22 7.86 22 16.14 16.14 22 7.86 22 2 16.14 2 7.86 7.86 2"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/></svg>`,
228
- className: "callout-fail",
229
- defaultTitle: "Fail",
230
- color: "#be123c",
231
- backgroundColor: "#fff1f2",
232
- iconColor: "#be123c",
233
- },
234
- {
235
- type: "missing",
236
- icon: `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/><line x1="8" y1="11" x2="14" y2="11"/></svg>`,
237
- className: "callout-missing",
238
- defaultTitle: "Missing",
239
- color: "#9f3a3a",
240
- backgroundColor: "#fef2f2",
241
- iconColor: "#9f3a3a",
242
- },
243
- {
244
- type: "error",
245
- icon: `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="7.86 2 16.14 2 22 7.86 22 16.14 16.14 22 7.86 22 2 16.14 2 7.86 7.86 2"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>`,
246
- className: "callout-error",
247
- defaultTitle: "Error",
248
- color: "#dc2626",
249
- backgroundColor: "#fef2f2",
250
- iconColor: "#dc2626",
251
- },
252
- {
253
- type: "cite",
254
- icon: `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 9a3 3 0 0 1 0 6v2a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-2a3 3 0 0 1 0-6V7a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2Z"/><path d="M13 5v2"/><path d="M13 17v2"/><path d="M13 11v2"/></svg>`,
255
- className: "callout-cite",
256
- defaultTitle: "Cite",
257
- color: "#5b6abf",
258
- backgroundColor: "#eef2ff",
259
- iconColor: "#5b6abf",
260
- },
261
- ];
262
- // ---------------------------------------------------------------------------
263
- // Default plugin options
264
- // ---------------------------------------------------------------------------
265
- const DEFAULT_OPTIONS = {
266
- calloutContainerTag: "div",
267
- calloutClass: "callout",
268
- calloutTitleClass: "callout-title",
269
- calloutBodyClass: "callout-body",
270
- enhanceBlockquotes: true,
271
- blockquoteClass: "blockquote-enhanced",
272
- dataCalloutType: true,
273
- customCallouts: [],
274
- calloutPattern: undefined,
275
- };
276
- // ---------------------------------------------------------------------------
277
- // Helper: Build the callout configuration map
278
- // ---------------------------------------------------------------------------
279
- function buildCalloutConfigMap(options) {
280
- const map = new Map();
281
- for (const config of BUILTIN_CALLOUTS) {
282
- map.set(config.type.toLowerCase(), config);
283
- }
284
- if (options.customCallouts) {
285
- for (const config of options.customCallouts) {
286
- map.set(config.type.toLowerCase(), config);
287
- }
288
- }
289
- return map;
290
- }
291
- // ---------------------------------------------------------------------------
292
- // Helper: Build the dynamic regex matching all directives
293
- // ---------------------------------------------------------------------------
294
- function buildCalloutPattern(configMap) {
295
- const allDirectives = Array.from(configMap.keys())
296
- .map((t) => t.toUpperCase())
297
- .sort((a, b) => b.length - a.length);
298
- return new RegExp(`^\\[!(${allDirectives.join("|")})\\]([+-]?)(?:[^\\S\\n]+(.+))?`, "i");
299
- }
300
- // ---------------------------------------------------------------------------
301
- // Helper: Parse the first paragraph of a blockquote to detect a callout
302
- // ---------------------------------------------------------------------------
303
- function parseCalloutDirective(blockquote, calloutPattern, configMap) {
304
- const firstChild = blockquote.children[0];
305
- if (!firstChild || firstChild.type !== "paragraph")
306
- return null;
307
- const firstText = extractTextContent(firstChild);
308
- if (!firstText)
309
- return null;
310
- const match = firstText.match(calloutPattern);
311
- if (!match)
312
- return null;
313
- const type = match[1].toLowerCase();
314
- if (!configMap.has(type))
315
- return null;
316
- // match[2] is the collapsible marker: "+", "-", or ""
317
- const foldMarker = match[2] || "";
318
- const collapsible = foldMarker === "+" || foldMarker === "-";
319
- const collapsibleOpen = foldMarker === "+";
320
- // The remainder is everything after the full regex match
321
- const remainder = firstText.slice(match[0].length);
322
- let customTitle;
323
- let remainingContent;
324
- if (match[3]) {
325
- customTitle = match[3].trim() || undefined;
326
- if (remainder.includes("\n")) {
327
- const newlineIdx = remainder.indexOf("\n");
328
- remainingContent = remainder.slice(newlineIdx + 1).trim() || undefined;
329
- }
330
- }
331
- else {
332
- const hasNewlineAfterDirective = /^\s*\n/.test(remainder);
333
- if (hasNewlineAfterDirective) {
334
- const afterNewline = remainder.replace(/^\s*\n/, "");
335
- remainingContent = afterNewline.trim() || undefined;
336
- }
337
- }
338
- return { type, customTitle, remainingContent, collapsible, collapsibleOpen };
339
- }
340
- // ---------------------------------------------------------------------------
341
- // Helper: Extract plain text from an inline node
342
- // ---------------------------------------------------------------------------
343
- function extractTextContent(node) {
344
- let text = "";
345
- for (const child of node.children) {
346
- if (child.type === "text") {
347
- text += child.value;
348
- }
349
- else if ("children" in child && Array.isArray(child.children)) {
350
- text += extractTextContent(child);
351
- }
352
- }
353
- return text;
354
- }
355
- // ---------------------------------------------------------------------------
356
- // Helper: Create an HTML node
357
- // ---------------------------------------------------------------------------
358
- function html(value) {
359
- return { type: "html", value };
360
- }
361
- // ---------------------------------------------------------------------------
362
- // Helper: Build the callout HTML
363
- // ---------------------------------------------------------------------------
364
- function buildCalloutHtml(parsed, blockquote, calloutPattern, configMap, options) {
365
- const config = configMap.get(parsed.type);
366
- const title = parsed.customTitle || config.defaultTitle;
367
- const dataAttr = options.dataCalloutType ? ` data-callout-type="${parsed.type}"` : "";
368
- // Build body HTML from blockquote children, properly handling the first
369
- // paragraph (strip the directive text but preserve inline markdown).
370
- let bodyHtml = "";
371
- const bodyChildren = blockquote.children.slice(1); // skip first paragraph (directive line)
372
- for (const child of bodyChildren) {
373
- bodyHtml += serializeNodeToHtml(child);
374
- }
375
- // Handle content from the first paragraph after the directive.
376
- // If remainingContent exists (text after directive on same line),
377
- // or if the first paragraph has children after the directive text,
378
- // we need to serialize them properly to preserve inline markdown.
379
- const firstPara = blockquote.children[0];
380
- if (firstPara && firstPara.type === "paragraph") {
381
- const firstParaHtml = serializeFirstParagraphAfterDirective(firstPara, calloutPattern);
382
- if (firstParaHtml) {
383
- bodyHtml = firstParaHtml + bodyHtml;
384
- }
385
- }
386
- const iconColor = config.iconColor || config.color;
387
- // ARIA role based on semantic type
388
- const ariaRole = getAriaRole(parsed.type);
389
- const ariaLabel = `role="${ariaRole}"`;
390
- if (parsed.collapsible) {
391
- // Collapsible callout → native <details>/<summary> (no JS needed)
392
- const openAttr = parsed.collapsibleOpen ? " open" : "";
393
- const calloutHtml = [
394
- `<details class="${options.calloutClass} ${config.className} collapsible"${dataAttr} ${ariaLabel}${openAttr}>`,
395
- ` <summary class="${options.calloutTitleClass}" style="color:${iconColor}">`,
396
- ` <span class="callout-icon" style="color:${iconColor}">${config.icon}</span>`,
397
- ` <span class="callout-title-text">${escapeHtml(title)}</span>`,
398
- ` </summary>`,
399
- ` <div class="${options.calloutBodyClass}">`,
400
- bodyHtml,
401
- ` </div>`,
402
- `</details>`,
403
- ].join("\n");
404
- return html(calloutHtml);
405
- }
406
- // Regular callout → semantic <aside> element
407
- const calloutHtml = [
408
- `<aside class="${options.calloutClass} ${config.className}"${dataAttr} ${ariaLabel}>`,
409
- ` <div class="${options.calloutTitleClass}" style="color:${iconColor}">`,
410
- ` <span class="callout-icon" style="color:${iconColor}">${config.icon}</span>`,
411
- ` <span class="callout-title-text">${escapeHtml(title)}</span>`,
412
- ` </div>`,
413
- ` <div class="${options.calloutBodyClass}">`,
414
- bodyHtml,
415
- ` </div>`,
416
- `</aside>`,
417
- ].join("\n");
418
- return html(calloutHtml);
419
- }
420
- // ---------------------------------------------------------------------------
421
- // Helper: Map callout type to ARIA role
422
- // ---------------------------------------------------------------------------
423
- function getAriaRole(type) {
424
- switch (type) {
425
- case "warning":
426
- case "attention":
427
- case "caution":
428
- case "danger":
429
- case "error":
430
- case "fail":
431
- case "failure":
432
- case "missing":
433
- return "alert";
434
- case "note":
435
- case "info":
436
- case "abstract":
437
- case "summary":
438
- case "tldr":
439
- case "example":
440
- case "cite":
441
- case "quote":
442
- return "note";
443
- case "tip":
444
- case "hint":
445
- case "important":
446
- case "success":
447
- case "check":
448
- case "done":
449
- case "todo":
450
- return "note";
451
- case "question":
452
- case "help":
453
- case "faq":
454
- case "bug":
455
- return "note";
456
- default:
457
- return "note";
458
- }
459
- }
460
- // ---------------------------------------------------------------------------
461
- // Helper: Serialize the first paragraph's content after the directive,
462
- // preserving inline markdown (bold, italic, code, links, etc.)
463
- // ---------------------------------------------------------------------------
464
- function serializeFirstParagraphAfterDirective(paragraph, calloutPattern) {
465
- // Find the text node containing the directive and split it
466
- const children = paragraph.children;
467
- let foundDirective = false;
468
- const remainingChildren = [];
469
- for (let i = 0; i < children.length; i++) {
470
- const child = children[i];
471
- if (!foundDirective && child.type === "text") {
472
- const textVal = child.value;
473
- const match = textVal.match(calloutPattern);
474
- if (match) {
475
- foundDirective = true;
476
- // Get the text after the full match (includes custom title + newline + body)
477
- const afterDirective = textVal.slice(match[0].length);
478
- // If there's a custom title captured by regex, strip it from remaining text
479
- // match[3] is the custom title (if any), but the regex already consumed it
480
- // The remaining text after match[0] may start with a newline + body text
481
- // or just be the custom title text that was already captured.
482
- // After the directive + optional fold marker + optional title,
483
- // what remains on this line (after the title) is body content.
484
- // But the regex captures the title as match[3]. The text after
485
- // match[0] could be empty, a newline, or continuation text.
486
- if (afterDirective.trim()) {
487
- // Check if what remains includes a newline (body content after title)
488
- const newlineIdx = afterDirective.indexOf("\n");
489
- if (newlineIdx !== -1) {
490
- // Content after newline is body content
491
- const bodyText = afterDirective.slice(newlineIdx + 1).trim();
492
- if (bodyText) {
493
- remainingChildren.push({ type: "text", value: bodyText });
494
- }
495
- }
496
- // If no newline, the remaining text is part of the title (already captured)
497
- // or there's nothing left.
498
- }
499
- // Also include any subsequent children (after the text node with the directive)
500
- for (let j = i + 1; j < children.length; j++) {
501
- remainingChildren.push(children[j]);
502
- }
503
- break;
504
- }
505
- else {
506
- // Text node doesn't contain directive (shouldn't happen but handle gracefully)
507
- remainingChildren.push(child);
508
- }
509
- }
510
- else if (foundDirective) {
511
- remainingChildren.push(child);
512
- }
513
- // If we haven't found the directive yet and it's not a text node, skip it
514
- // (the directive is always in a text node)
515
- }
516
- if (remainingChildren.length === 0)
517
- return "";
518
- // Serialize the remaining children as inline HTML
519
- const innerHtml = remainingChildren.map((c) => inlineNodeToHtml(c)).join("");
520
- return innerHtml.trim() ? `<p>${innerHtml}</p>\n` : "";
521
- }
522
- // ---------------------------------------------------------------------------
523
- // Helper: Serialize mdast nodes to simple HTML
524
- // ---------------------------------------------------------------------------
525
- function serializeNodeToHtml(node, indent = "") {
526
- switch (node.type) {
527
- case "paragraph": {
528
- const inner = node.children.map((c) => inlineNodeToHtml(c)).join("");
529
- return `${indent}<p>${inner}</p>\n`;
530
- }
531
- case "heading": {
532
- const inner = node.children.map((c) => inlineNodeToHtml(c)).join("");
533
- return `${indent}<h${node.depth}>${inner}</h${node.depth}>\n`;
534
- }
535
- case "list": {
536
- const tag = node.ordered ? "ol" : "ul";
537
- const items = node.children.map((c) => serializeNodeToHtml(c, indent + " ")).join("");
538
- return `${indent}<${tag}>\n${items}${indent}</${tag}>\n`;
539
- }
540
- case "listItem": {
541
- const inner = node.children.map((c) => serializeNodeToHtml(c, indent + " ")).join("");
542
- return `${indent}<li>\n${inner}${indent}</li>\n`;
543
- }
544
- case "code": {
545
- const lang = node.lang ? ` class="language-${node.lang}"` : "";
546
- return `${indent}<pre><code${lang}>${escapeHtml(node.value)}</code></pre>\n`;
547
- }
548
- case "blockquote": {
549
- const inner = node.children.map((c) => serializeNodeToHtml(c, indent + " ")).join("");
550
- return `${indent}<blockquote>\n${inner}${indent}</blockquote>\n`;
551
- }
552
- case "thematicBreak": {
553
- return `${indent}<hr />\n`;
554
- }
555
- default:
556
- return "";
557
- }
558
- }
559
- function inlineNodeToHtml(node) {
560
- switch (node.type) {
561
- case "text":
562
- return escapeHtml(node.value);
563
- case "strong":
564
- return `<strong>${node.children.map((c) => inlineNodeToHtml(c)).join("")}</strong>`;
565
- case "emphasis":
566
- return `<em>${node.children.map((c) => inlineNodeToHtml(c)).join("")}</em>`;
567
- case "inlineCode":
568
- return `<code>${escapeHtml(node.value)}</code>`;
569
- case "link":
570
- return `<a href="${escapeHtml(node.url)}">${node.children.map((c) => inlineNodeToHtml(c)).join("")}</a>`;
571
- case "image":
572
- return `<img src="${escapeHtml(node.url)}" alt="${escapeHtml(node.alt || "")}" />`;
573
- case "delete":
574
- return `<del>${node.children.map((c) => inlineNodeToHtml(c)).join("")}</del>`;
575
- case "html":
576
- return node.value;
577
- default:
578
- if (node.children) {
579
- return node.children.map((c) => inlineNodeToHtml(c)).join("");
580
- }
581
- return "";
582
- }
583
- }
584
- // ---------------------------------------------------------------------------
585
- // Helper: Escape HTML entities
586
- // ---------------------------------------------------------------------------
587
- function escapeHtml(str) {
588
- return str
589
- .replace(/&/g, "&amp;")
590
- .replace(/</g, "&lt;")
591
- .replace(/>/g, "&gt;")
592
- .replace(/"/g, "&quot;")
593
- .replace(/'/g, "&#039;");
594
- }
595
- // ---------------------------------------------------------------------------
596
- // Plugin implementation
597
- // ---------------------------------------------------------------------------
598
- export const remarkCalloutBlocks = (userOptions) => {
599
- const options = {
600
- ...DEFAULT_OPTIONS,
601
- ...userOptions,
602
- };
603
- const configMap = buildCalloutConfigMap(options);
604
- const calloutPattern = options.calloutPattern || buildCalloutPattern(configMap);
605
- return (tree) => {
606
- visit(tree, "blockquote", (node, index, parent) => {
607
- if (index === undefined || !parent)
608
- return;
609
- const parsed = parseCalloutDirective(node, calloutPattern, configMap);
610
- if (parsed) {
611
- const calloutNode = buildCalloutHtml(parsed, node, calloutPattern, configMap, options);
612
- parent.children[index] = calloutNode;
613
- }
614
- else if (options.enhanceBlockquotes) {
615
- const enhancedNode = html([
616
- `<div class="${options.blockquoteClass}">`,
617
- ` <blockquote>`,
618
- node.children.map((c) => serializeNodeToHtml(c, " ")).join(""),
619
- ` </blockquote>`,
620
- `</div>`,
621
- ].join("\n"));
622
- parent.children[index] = enhancedNode;
623
- }
624
- });
625
- };
626
- };
627
- export { BUILTIN_CALLOUTS };
628
- //# sourceMappingURL=remark-callout-blocks.js.map