@dr-ishaan/remake-blocks 1.1.0 → 1.3.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.
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/remark-remake-blocks.d.ts +4 -0
- package/dist/remark-remake-blocks.d.ts.map +1 -1
- package/dist/remark-remake-blocks.js +576 -28
- package/dist/remark-remake-blocks.js.map +1 -1
- package/dist/sanitize-schema.json +50 -0
- package/dist/styles.css +39 -0
- package/dist/theme/docusaurus.css +610 -0
- package/dist/theme/github.css +660 -0
- package/dist/theme/obsidian.css +610 -0
- package/dist/theme/vitepress.css +603 -0
- package/dist/types.d.ts +137 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +19 -5
|
@@ -185,6 +185,92 @@ const BUILTIN_CALLOUTS = [
|
|
|
185
185
|
},
|
|
186
186
|
];
|
|
187
187
|
// ---------------------------------------------------------------------------
|
|
188
|
+
// v1.3.0: Alternative icon sets (Lucide + Emoji)
|
|
189
|
+
// ---------------------------------------------------------------------------
|
|
190
|
+
// These are keyed by callout type. When iconSet === "lucide" or "emoji",
|
|
191
|
+
// the plugin uses these icons instead of the default Octicon SVGs.
|
|
192
|
+
// Custom callouts always use their configured `icon` regardless of iconSet.
|
|
193
|
+
const LUCIDE_ICONS = {
|
|
194
|
+
note: `<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>`,
|
|
195
|
+
tip: `<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="M9 18h6"/><path d="M10 22h4"/><path d="M15.09 14c.18-.98.65-1.74 1.41-2.5A4.65 4.65 0 0 0 18 8 6 6 0 0 0 6 8c0 1 .23 2.23 1.5 3.5A4.61 4.61 0 0 1 8.91 14"/></svg>`,
|
|
196
|
+
important: `<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="m12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83Z"/><path d="m22 17.65-9.17 4.16a2 2 0 0 1-1.66 0L2 17.65"/><path d="m22 12.65-9.17 4.16a2 2 0 0 1-1.66 0L2 12.65"/></svg>`,
|
|
197
|
+
warning: `<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.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg>`,
|
|
198
|
+
caution: `<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="m15 9-6 6"/><path d="m9 9 6 6"/></svg>`,
|
|
199
|
+
abstract: `<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"/><path d="M16 13H8"/><path d="M16 17H8"/><path d="M10 9H8"/></svg>`,
|
|
200
|
+
info: `<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>`,
|
|
201
|
+
success: `<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="m9 11 3 3L22 4"/></svg>`,
|
|
202
|
+
question: `<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>`,
|
|
203
|
+
failure: `<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="m15 9-6 6"/><path d="m9 9 6 6"/></svg>`,
|
|
204
|
+
danger: `<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="m13 2-3 7h7l-3 7"/><path d="M12 22a10 10 0 1 1 0-20"/></svg>`,
|
|
205
|
+
quote: `<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.031V20c0 1 0 1 1 1z"/><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>`,
|
|
206
|
+
bug: `<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>`,
|
|
207
|
+
example: `<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.5z"/><polyline points="14 2 14 8 20 8"/><path d="M16 13H8"/><path d="M16 17H8"/><path d="M10 9H8"/></svg>`,
|
|
208
|
+
todo: `<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>`,
|
|
209
|
+
summary: `<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"/><path d="M16 13H8"/><path d="M16 17H8"/></svg>`,
|
|
210
|
+
tldr: `<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="m13 2-3 7h7l-3 7"/><path d="M12 22a10 10 0 1 1 0-20"/></svg>`,
|
|
211
|
+
hint: `<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>`,
|
|
212
|
+
check: `<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>`,
|
|
213
|
+
done: `<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="m9 11 3 3L22 4"/></svg>`,
|
|
214
|
+
help: `<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>`,
|
|
215
|
+
faq: `<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="M10 7h.01"/><path d="M14 7h.01"/><path d="M8 11h.01"/><path d="M12 11h.01"/><path d="M16 11h.01"/></svg>`,
|
|
216
|
+
attention: `<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>`,
|
|
217
|
+
fail: `<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"/><path d="m15 9-6 6"/><path d="m9 9 6 6"/></svg>`,
|
|
218
|
+
missing: `<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"/><path d="m21 21-4.3-4.3"/><path d="M8 11h6"/></svg>`,
|
|
219
|
+
error: `<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"/><path d="M12 8v4"/><path d="M12 16h.01"/></svg>`,
|
|
220
|
+
cite: `<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>`,
|
|
221
|
+
};
|
|
222
|
+
const EMOJI_ICONS = {
|
|
223
|
+
note: "ℹ️",
|
|
224
|
+
tip: "💡",
|
|
225
|
+
important: "⚠️",
|
|
226
|
+
warning: "⚠️",
|
|
227
|
+
caution: "⛔",
|
|
228
|
+
abstract: "📋",
|
|
229
|
+
info: "ℹ️",
|
|
230
|
+
success: "✅",
|
|
231
|
+
question: "❓",
|
|
232
|
+
failure: "❌",
|
|
233
|
+
danger: "⚡",
|
|
234
|
+
quote: "💬",
|
|
235
|
+
bug: "🐛",
|
|
236
|
+
example: "📝",
|
|
237
|
+
todo: "☑️",
|
|
238
|
+
summary: "📋",
|
|
239
|
+
tldr: "⚡",
|
|
240
|
+
hint: "💡",
|
|
241
|
+
check: "✅",
|
|
242
|
+
done: "✅",
|
|
243
|
+
help: "❓",
|
|
244
|
+
faq: "❓",
|
|
245
|
+
attention: "⚠️",
|
|
246
|
+
fail: "❌",
|
|
247
|
+
missing: "🚫",
|
|
248
|
+
error: "❌",
|
|
249
|
+
cite: "📌",
|
|
250
|
+
};
|
|
251
|
+
/**
|
|
252
|
+
* Resolve the icon for a given callout type based on the iconSet option.
|
|
253
|
+
* Returns the icon string (SVG or emoji) or empty string if not found.
|
|
254
|
+
*/
|
|
255
|
+
function resolveIcon(type, config, iconSet) {
|
|
256
|
+
// Custom callouts always use their configured icon, regardless of iconSet.
|
|
257
|
+
// We detect this by checking if the type is NOT in BUILTIN_CALLOUTS.
|
|
258
|
+
// (config may be a normalized custom callout — its icon is the user's choice.)
|
|
259
|
+
const isBuiltin = BUILTIN_CALLOUTS.some((b) => b.type === type);
|
|
260
|
+
if (!isBuiltin) {
|
|
261
|
+
return config.icon;
|
|
262
|
+
}
|
|
263
|
+
// For builtins, swap icon based on iconSet option.
|
|
264
|
+
if (iconSet === "lucide")
|
|
265
|
+
return LUCIDE_ICONS[type] ?? config.icon;
|
|
266
|
+
if (iconSet === "emoji")
|
|
267
|
+
return EMOJI_ICONS[type] ?? config.icon;
|
|
268
|
+
if (iconSet === "none")
|
|
269
|
+
return "";
|
|
270
|
+
// Default: octicon (the builtin's own icon)
|
|
271
|
+
return config.icon;
|
|
272
|
+
}
|
|
273
|
+
// ---------------------------------------------------------------------------
|
|
188
274
|
// Default plugin options
|
|
189
275
|
// ---------------------------------------------------------------------------
|
|
190
276
|
const DEFAULT_OPTIONS = {
|
|
@@ -205,6 +291,16 @@ const DEFAULT_OPTIONS = {
|
|
|
205
291
|
accordionClass: "disclosure-accordion",
|
|
206
292
|
enableTreeView: true,
|
|
207
293
|
allowDangerousHtml: false,
|
|
294
|
+
// v1.2.0 defaults
|
|
295
|
+
aliases: {},
|
|
296
|
+
showIndicator: true,
|
|
297
|
+
iconSet: "octicon",
|
|
298
|
+
appearance: "default",
|
|
299
|
+
props: {},
|
|
300
|
+
build: undefined,
|
|
301
|
+
tags: {},
|
|
302
|
+
// v1.3.0 defaults
|
|
303
|
+
enableDirectiveSyntax: false,
|
|
208
304
|
};
|
|
209
305
|
// ---------------------------------------------------------------------------
|
|
210
306
|
// Helper: Validate + normalize a single custom callout config.
|
|
@@ -257,7 +353,7 @@ function sanitizeColor(value, fallback) {
|
|
|
257
353
|
return trimmed;
|
|
258
354
|
}
|
|
259
355
|
// ---------------------------------------------------------------------------
|
|
260
|
-
// Helper: Build the callout configuration map
|
|
356
|
+
// Helper: Build the callout configuration map (with alias resolution)
|
|
261
357
|
// ---------------------------------------------------------------------------
|
|
262
358
|
function buildCalloutConfigMap(options) {
|
|
263
359
|
const map = new Map();
|
|
@@ -273,10 +369,28 @@ function buildCalloutConfigMap(options) {
|
|
|
273
369
|
}
|
|
274
370
|
}
|
|
275
371
|
}
|
|
372
|
+
// Register aliases: each alias points to its canonical config.
|
|
373
|
+
// Aliases are case-insensitive (lowercased on registration AND lookup).
|
|
374
|
+
if (options.aliases) {
|
|
375
|
+
for (const [canonical, aliasList] of Object.entries(options.aliases)) {
|
|
376
|
+
const canonicalLower = canonical.toLowerCase();
|
|
377
|
+
const canonicalConfig = map.get(canonicalLower);
|
|
378
|
+
if (!canonicalConfig || !Array.isArray(aliasList))
|
|
379
|
+
continue;
|
|
380
|
+
for (const alias of aliasList) {
|
|
381
|
+
if (typeof alias !== "string" || alias.trim() === "")
|
|
382
|
+
continue;
|
|
383
|
+
map.set(alias.toLowerCase(), canonicalConfig);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
276
387
|
return map;
|
|
277
388
|
}
|
|
278
389
|
// ---------------------------------------------------------------------------
|
|
279
|
-
// Helper: Build the dynamic regex matching all directives
|
|
390
|
+
// Helper: Build the dynamic regex matching all directives.
|
|
391
|
+
// Captures: (1) type (or empty for disclosure), (2) fold marker + or -,
|
|
392
|
+
// (3) optional inline title text (everything until end-of-line or `{`),
|
|
393
|
+
// (4) optional `{key=value key=value}` overrides block.
|
|
280
394
|
// ---------------------------------------------------------------------------
|
|
281
395
|
function buildCalloutPattern(configMap, enableDisclosures) {
|
|
282
396
|
const allDirectives = Array.from(configMap.keys())
|
|
@@ -286,7 +400,76 @@ function buildCalloutPattern(configMap, enableDisclosures) {
|
|
|
286
400
|
allDirectives.unshift("");
|
|
287
401
|
}
|
|
288
402
|
const typePattern = allDirectives.join("|");
|
|
289
|
-
|
|
403
|
+
// Group 3 (title) is non-greedy and stops at `{` (start of overrides) or end of line.
|
|
404
|
+
// Group 4 (overrides) captures `{...}` if present.
|
|
405
|
+
return new RegExp(`^\\[!(${typePattern})\\]([+-]?)(?:[^\\S\\n]+([^\\n{]*))?(\\s*\\{[^\\n]*\\})?`, "i");
|
|
406
|
+
}
|
|
407
|
+
// ---------------------------------------------------------------------------
|
|
408
|
+
// Helper: Parse `{key=value key=value}` overrides block into an object.
|
|
409
|
+
// Supported keys (v1.2.0):
|
|
410
|
+
// - icon: true|false — override showIndicator per-callout
|
|
411
|
+
// - appearance: default|minimal|simple|hidden — override appearance per-callout
|
|
412
|
+
// - inline: inline|inline-end — v1.3.0+: float left/right (responsive)
|
|
413
|
+
// Unknown keys are silently ignored.
|
|
414
|
+
// ---------------------------------------------------------------------------
|
|
415
|
+
function parseOverrides(overridesBlock) {
|
|
416
|
+
if (!overridesBlock)
|
|
417
|
+
return undefined;
|
|
418
|
+
const inner = overridesBlock.trim().replace(/^\{|\}$/g, "").trim();
|
|
419
|
+
if (!inner)
|
|
420
|
+
return undefined;
|
|
421
|
+
const result = {};
|
|
422
|
+
// First pass: handle bare keywords (no `=` sign).
|
|
423
|
+
// Supported bare keywords: `inline`, `inline-end`
|
|
424
|
+
// These set the inline override without needing `inline=true`.
|
|
425
|
+
// Note: `inline-end` must be matched BEFORE `inline` to avoid the prefix
|
|
426
|
+
// matching, so we use a single regex with `inline-end` first in the
|
|
427
|
+
// alternation and consume the entire match.
|
|
428
|
+
const bareKeywordRe = /\b(inline-end|inline)\b(?!\s*=)/gi;
|
|
429
|
+
let bareMatch;
|
|
430
|
+
while ((bareMatch = bareKeywordRe.exec(inner)) !== null) {
|
|
431
|
+
const kw = bareMatch[1].toLowerCase();
|
|
432
|
+
if (kw === "inline-end")
|
|
433
|
+
result.inline = "inline-end";
|
|
434
|
+
else if (kw === "inline")
|
|
435
|
+
result.inline = "inline";
|
|
436
|
+
}
|
|
437
|
+
// Second pass: handle key=value pairs.
|
|
438
|
+
// Values can be unquoted barewords or "quoted strings".
|
|
439
|
+
const pairRe = /(\w[\w-]*)\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s}]+))/g;
|
|
440
|
+
let m;
|
|
441
|
+
while ((m = pairRe.exec(inner)) !== null) {
|
|
442
|
+
const key = m[1].toLowerCase();
|
|
443
|
+
const value = (m[2] ?? m[3] ?? m[4] ?? "").trim();
|
|
444
|
+
if (key === "icon") {
|
|
445
|
+
if (value === "false" || value === "0" || value === "no")
|
|
446
|
+
result.icon = false;
|
|
447
|
+
else if (value === "true" || value === "1" || value === "yes")
|
|
448
|
+
result.icon = true;
|
|
449
|
+
}
|
|
450
|
+
else if (key === "appearance") {
|
|
451
|
+
if (value === "default" || value === "minimal" || value === "simple" || value === "hidden") {
|
|
452
|
+
result.appearance = value;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
else if (key === "inline") {
|
|
456
|
+
// v1.3.0+: inline floating callouts
|
|
457
|
+
if (value === "true" || value === "left" || value === "inline") {
|
|
458
|
+
result.inline = "inline";
|
|
459
|
+
}
|
|
460
|
+
else if (value === "right" || value === "inline-end" || value === "end") {
|
|
461
|
+
result.inline = "inline-end";
|
|
462
|
+
}
|
|
463
|
+
else if (value === "false" || value === "0" || value === "no") {
|
|
464
|
+
// explicitly disable inline (clear any bare-keyword setting)
|
|
465
|
+
result.inline = undefined;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
// Unknown keys silently ignored
|
|
469
|
+
}
|
|
470
|
+
if (result.icon === undefined && result.appearance === undefined && result.inline === undefined)
|
|
471
|
+
return undefined;
|
|
472
|
+
return result;
|
|
290
473
|
}
|
|
291
474
|
// ---------------------------------------------------------------------------
|
|
292
475
|
// Helper: Parse the first paragraph of a blockquote to detect a callout
|
|
@@ -328,7 +511,9 @@ function parseCalloutDirective(blockquote, calloutPattern, configMap, enableDisc
|
|
|
328
511
|
remainingContent = afterNewline.trim() || undefined;
|
|
329
512
|
}
|
|
330
513
|
}
|
|
331
|
-
|
|
514
|
+
// v1.2.0: parse per-callout overrides from `{key=value}` block (match[4])
|
|
515
|
+
const overrides = parseOverrides(match[4]);
|
|
516
|
+
return { type, customTitle, remainingContent, collapsible: effectiveCollapsible, collapsibleOpen: effectiveCollapsibleOpen, isDisclosure, overrides };
|
|
332
517
|
}
|
|
333
518
|
// ---------------------------------------------------------------------------
|
|
334
519
|
// Helpers: extractTextContent, html, escapeHtml
|
|
@@ -377,6 +562,83 @@ function buildBodyHtml(blockquote, calloutPattern, options) {
|
|
|
377
562
|
// ---------------------------------------------------------------------------
|
|
378
563
|
// Helper: Build the callout HTML
|
|
379
564
|
// ---------------------------------------------------------------------------
|
|
565
|
+
// ---------------------------------------------------------------------------
|
|
566
|
+
// v1.2.0 helpers: stable id generation, props formatting, override resolution
|
|
567
|
+
// ---------------------------------------------------------------------------
|
|
568
|
+
// Per-tree counter for callout ids. Reset at the start of each plugin run
|
|
569
|
+
// (each `transform(tree)` invocation) so the same markdown always produces
|
|
570
|
+
// the same output HTML (idempotency / determinism for snapshot tests).
|
|
571
|
+
let __calloutIdCounter = 0;
|
|
572
|
+
function resetCalloutIdCounter() {
|
|
573
|
+
__calloutIdCounter = 0;
|
|
574
|
+
}
|
|
575
|
+
function generateCalloutId(type) {
|
|
576
|
+
__calloutIdCounter += 1;
|
|
577
|
+
// Sanitize type for use in an HTML id (letters/digits/hyphen only).
|
|
578
|
+
const safeType = String(type).replace(/[^a-zA-Z0-9-]/g, "-") || "callout";
|
|
579
|
+
return `callout-${safeType}-${__calloutIdCounter.toString(36)}`;
|
|
580
|
+
}
|
|
581
|
+
function formatPropsAsAttrs(props) {
|
|
582
|
+
if (!props)
|
|
583
|
+
return "";
|
|
584
|
+
let out = "";
|
|
585
|
+
for (const [key, value] of Object.entries(props)) {
|
|
586
|
+
if (value === undefined || value === null)
|
|
587
|
+
continue;
|
|
588
|
+
// Sanitize the key: allow letters, digits, hyphens, colons (for namespaces like xml:lang)
|
|
589
|
+
const safeKey = key.replace(/[^a-zA-Z0-9:_-]/g, "");
|
|
590
|
+
if (!safeKey)
|
|
591
|
+
continue;
|
|
592
|
+
// Don't allow `style` or event handlers to be set via props (defense in depth)
|
|
593
|
+
if (safeKey.toLowerCase() === "style")
|
|
594
|
+
continue;
|
|
595
|
+
if (safeKey.toLowerCase().startsWith("on"))
|
|
596
|
+
continue;
|
|
597
|
+
out += ` ${safeKey}="${escapeAttribute(String(value))}"`;
|
|
598
|
+
}
|
|
599
|
+
return out;
|
|
600
|
+
}
|
|
601
|
+
function resolveProps(propsConfig, type, parsed) {
|
|
602
|
+
if (!propsConfig)
|
|
603
|
+
return {};
|
|
604
|
+
const entry = propsConfig[type] ?? propsConfig[type.toLowerCase()];
|
|
605
|
+
if (!entry)
|
|
606
|
+
return {};
|
|
607
|
+
if (typeof entry === "function") {
|
|
608
|
+
try {
|
|
609
|
+
return entry(parsed) || {};
|
|
610
|
+
}
|
|
611
|
+
catch {
|
|
612
|
+
return {};
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
if (typeof entry === "object")
|
|
616
|
+
return entry;
|
|
617
|
+
return {};
|
|
618
|
+
}
|
|
619
|
+
// ---------------------------------------------------------------------------
|
|
620
|
+
// Helper: Resolve effective icon visibility + appearance, merging global
|
|
621
|
+
// options with per-callout overrides.
|
|
622
|
+
// ---------------------------------------------------------------------------
|
|
623
|
+
function resolveVisuals(parsed, options) {
|
|
624
|
+
let showIcon = options.showIndicator !== false; // default true
|
|
625
|
+
let appearance = options.appearance ?? "default";
|
|
626
|
+
if (parsed.overrides) {
|
|
627
|
+
if (parsed.overrides.icon === false)
|
|
628
|
+
showIcon = false;
|
|
629
|
+
else if (parsed.overrides.icon === true)
|
|
630
|
+
showIcon = true;
|
|
631
|
+
if (parsed.overrides.appearance)
|
|
632
|
+
appearance = parsed.overrides.appearance;
|
|
633
|
+
}
|
|
634
|
+
// "hidden" appearance implies no icon
|
|
635
|
+
if (appearance === "hidden")
|
|
636
|
+
showIcon = false;
|
|
637
|
+
return { showIcon, appearance };
|
|
638
|
+
}
|
|
639
|
+
// ---------------------------------------------------------------------------
|
|
640
|
+
// Helper: Build the callout HTML (v1.2.0 — with all community-inspired features)
|
|
641
|
+
// ---------------------------------------------------------------------------
|
|
380
642
|
function buildCalloutHtml(parsed, blockquote, calloutPattern, configMap, options, depth = 0) {
|
|
381
643
|
// ── Disclosure Widget ──────────────────────────────────────────────
|
|
382
644
|
if (parsed.isDisclosure) {
|
|
@@ -387,39 +649,108 @@ function buildCalloutHtml(parsed, blockquote, calloutPattern, configMap, options
|
|
|
387
649
|
const title = parsed.customTitle || config.defaultTitle;
|
|
388
650
|
const dataAttr = options.dataCalloutType ? ` data-callout-type="${escapeAttribute(parsed.type)}"` : "";
|
|
389
651
|
const bodyHtml = buildBodyHtml(blockquote, calloutPattern, options);
|
|
652
|
+
// v1.2.0: resolve per-callout visuals (icon visibility + appearance)
|
|
653
|
+
const { showIcon, appearance } = resolveVisuals(parsed, options);
|
|
654
|
+
// v1.2.0: build custom render escape hatch
|
|
655
|
+
if (typeof options.build === "function") {
|
|
656
|
+
try {
|
|
657
|
+
const customHtml = options.build(parsed, config, bodyHtml, options);
|
|
658
|
+
if (typeof customHtml === "string" && customHtml.length > 0) {
|
|
659
|
+
return html(customHtml);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
catch {
|
|
663
|
+
// Fall through to default renderer on error
|
|
664
|
+
}
|
|
665
|
+
}
|
|
390
666
|
// Sanitize colors before interpolating into style attribute
|
|
391
667
|
const safeIconColor = sanitizeColor(config.iconColor || config.color, "#57606a");
|
|
392
|
-
|
|
668
|
+
// v1.2.0: WCAG fix — use role="note" for ALL callouts (was: role="alert" for warnings).
|
|
669
|
+
// role="alert" is for dynamically-inserted content and causes aggressive immediate
|
|
670
|
+
// announcement that disrupts screen reader users on static content.
|
|
671
|
+
// Use role="note" + aria-labelledby for proper title→container association.
|
|
672
|
+
const ariaRole = "note";
|
|
393
673
|
// Escape className + calloutClass to prevent attribute breakout
|
|
394
674
|
const safeCalloutClass = escapeAttribute(options.calloutClass);
|
|
395
675
|
const safeConfigClassName = escapeAttribute(config.className);
|
|
396
676
|
const safeCalloutTitleClass = escapeAttribute(options.calloutTitleClass);
|
|
397
677
|
const safeCalloutBodyClass = escapeAttribute(options.calloutBodyClass);
|
|
678
|
+
// v1.2.0: stable title id for aria-labelledby
|
|
679
|
+
const titleId = generateCalloutId(parsed.type);
|
|
680
|
+
// v1.2.0: resolve per-type props (dir, style, data-*, etc.)
|
|
681
|
+
const extraProps = resolveProps(options.props, parsed.type, parsed);
|
|
682
|
+
const extraAttrs = formatPropsAsAttrs(extraProps);
|
|
683
|
+
// v1.2.0: appearance class
|
|
684
|
+
const appearanceClass = appearance !== "default" ? ` callout-${appearance}` : "";
|
|
685
|
+
// v1.3.0: inline floating class (responsive float left/right)
|
|
686
|
+
const inlineClass = parsed.overrides?.inline ? ` callout-${parsed.overrides.inline}` : "";
|
|
687
|
+
// v1.2.0: icon HTML — only render if showIcon AND appearance allows it
|
|
688
|
+
// v1.3.0: resolve icon based on iconSet option (octicon|lucide|emoji|none)
|
|
689
|
+
const renderIcon = showIcon && appearance !== "simple" && appearance !== "hidden";
|
|
690
|
+
const resolvedIcon = renderIcon ? resolveIcon(parsed.type, config, options.iconSet) : "";
|
|
691
|
+
const effectiveRenderIcon = renderIcon && resolvedIcon.length > 0;
|
|
692
|
+
const iconHtml = effectiveRenderIcon
|
|
693
|
+
? `<span class="callout-icon" style="color:${safeIconColor}" aria-hidden="true">${resolvedIcon}</span>`
|
|
694
|
+
: "";
|
|
695
|
+
// v1.2.0: title-text always rendered (unless appearance="hidden")
|
|
696
|
+
const renderTitle = appearance !== "hidden";
|
|
697
|
+
const titleTextHtml = renderTitle
|
|
698
|
+
? `<span class="callout-title-text">${escapeHtml(title)}</span>`
|
|
699
|
+
: "";
|
|
700
|
+
// v1.2.0: tags option (override element names)
|
|
701
|
+
const tags = options.tags ?? {};
|
|
702
|
+
const containerTag = tags.container || (parsed.collapsible ? "details" : "aside");
|
|
703
|
+
const titleTag = tags.title || (parsed.collapsible ? "summary" : "div");
|
|
704
|
+
const iconTag = tags.icon || "span";
|
|
705
|
+
const titleTextTag = tags.titleText || "span";
|
|
706
|
+
const bodyTag = tags.body || "div";
|
|
707
|
+
// Build icon span with custom tag if provided
|
|
708
|
+
const iconSpan = effectiveRenderIcon
|
|
709
|
+
? `<${iconTag} class="callout-icon" style="color:${safeIconColor}" aria-hidden="true">${resolvedIcon}</${iconTag}>`
|
|
710
|
+
: "";
|
|
711
|
+
// Build the title block (or skip for "hidden" appearance)
|
|
712
|
+
const titleBlock = renderTitle
|
|
713
|
+
? [
|
|
714
|
+
` <${titleTag} class="${safeCalloutTitleClass}" style="color:${safeIconColor}" id="${titleId}">`,
|
|
715
|
+
iconSpan && ` ${iconSpan}`,
|
|
716
|
+
` <${titleTextTag} class="callout-title-text">${escapeHtml(title)}</${titleTextTag}>`,
|
|
717
|
+
` </${titleTag}>`,
|
|
718
|
+
].filter(Boolean).join("\n")
|
|
719
|
+
: "";
|
|
720
|
+
// v1.2.0: aria-labelledby on container (only if title rendered)
|
|
721
|
+
const labelledby = renderTitle ? ` aria-labelledby="${titleId}"` : "";
|
|
722
|
+
// v1.2.0: dir="auto" on container + title for RTL/Unicode (community best practice)
|
|
723
|
+
const dirAuto = ` dir="auto"`;
|
|
724
|
+
const titleDirAuto = renderTitle ? ` dir="auto"` : "";
|
|
725
|
+
// Rebuild title block with dir="auto" (already in <title> opening tag below)
|
|
726
|
+
// For simplicity, we'll inline the title block here with all attributes:
|
|
727
|
+
const titleBlockWithDir = renderTitle
|
|
728
|
+
? [
|
|
729
|
+
` <${titleTag} class="${safeCalloutTitleClass}" style="color:${safeIconColor}" id="${titleId}"${titleDirAuto}>`,
|
|
730
|
+
iconSpan && ` ${iconSpan}`,
|
|
731
|
+
` <${titleTextTag} class="callout-title-text">${escapeHtml(title)}</${titleTextTag}>`,
|
|
732
|
+
` </${titleTag}>`,
|
|
733
|
+
].filter(Boolean).join("\n")
|
|
734
|
+
: "";
|
|
398
735
|
if (parsed.collapsible) {
|
|
399
736
|
const openAttr = parsed.collapsibleOpen ? " open" : "";
|
|
400
737
|
return html([
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
`
|
|
404
|
-
` <span class="callout-title-text">${escapeHtml(title)}</span>`,
|
|
405
|
-
` </summary>`,
|
|
406
|
-
` <div class="${safeCalloutBodyClass}">`,
|
|
738
|
+
`<${containerTag} class="${safeCalloutClass} ${safeConfigClassName} collapsible${appearanceClass}${inlineClass}"${dataAttr} role="${ariaRole}"${labelledby}${dirAuto}${openAttr}${extraAttrs}>`,
|
|
739
|
+
titleBlockWithDir,
|
|
740
|
+
` <${bodyTag} class="${safeCalloutBodyClass}">`,
|
|
407
741
|
bodyHtml,
|
|
408
|
-
`
|
|
409
|
-
|
|
410
|
-
].join("\n"));
|
|
742
|
+
` </${bodyTag}>`,
|
|
743
|
+
`</${containerTag}>`,
|
|
744
|
+
].filter(Boolean).join("\n"));
|
|
411
745
|
}
|
|
412
746
|
return html([
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
`
|
|
416
|
-
` <span class="callout-title-text">${escapeHtml(title)}</span>`,
|
|
417
|
-
` </div>`,
|
|
418
|
-
` <div class="${safeCalloutBodyClass}">`,
|
|
747
|
+
`<${containerTag} class="${safeCalloutClass} ${safeConfigClassName}${appearanceClass}${inlineClass}"${dataAttr} role="${ariaRole}"${labelledby}${dirAuto}${extraAttrs}>`,
|
|
748
|
+
titleBlockWithDir,
|
|
749
|
+
` <${bodyTag} class="${safeCalloutBodyClass}">`,
|
|
419
750
|
bodyHtml,
|
|
420
|
-
`
|
|
421
|
-
|
|
422
|
-
].join("\n"));
|
|
751
|
+
` </${bodyTag}>`,
|
|
752
|
+
`</${containerTag}>`,
|
|
753
|
+
].filter(Boolean).join("\n"));
|
|
423
754
|
}
|
|
424
755
|
// ---------------------------------------------------------------------------
|
|
425
756
|
// Helper: Build disclosure widget HTML (with tree view support)
|
|
@@ -428,6 +759,18 @@ function buildDisclosureHtml(parsed, blockquote, calloutPattern, options, depth
|
|
|
428
759
|
const title = parsed.customTitle || "Details";
|
|
429
760
|
const openAttr = parsed.collapsibleOpen ? " open" : "";
|
|
430
761
|
const bodyHtml = buildBodyHtml(blockquote, calloutPattern, options);
|
|
762
|
+
// v1.2.0: build custom render escape hatch
|
|
763
|
+
if (typeof options.build === "function") {
|
|
764
|
+
try {
|
|
765
|
+
const customHtml = options.build(parsed, undefined, bodyHtml, options);
|
|
766
|
+
if (typeof customHtml === "string" && customHtml.length > 0) {
|
|
767
|
+
return html(customHtml);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
catch {
|
|
771
|
+
// Fall through to default renderer on error
|
|
772
|
+
}
|
|
773
|
+
}
|
|
431
774
|
// Tree view: add depth class for nested disclosures
|
|
432
775
|
const safeDisclosureClass = escapeAttribute(options.disclosureClass);
|
|
433
776
|
const safeDisclosureTitleClass = escapeAttribute(options.disclosureTitleClass);
|
|
@@ -436,13 +779,20 @@ function buildDisclosureHtml(parsed, blockquote, calloutPattern, options, depth
|
|
|
436
779
|
const treeAttr = options.enableTreeView && depth > 0
|
|
437
780
|
? ` class="${safeDisclosureClass} disclosure-tree" data-depth="${safeDepth}"`
|
|
438
781
|
: ` class="${safeDisclosureClass}"`;
|
|
782
|
+
// v1.2.0: stable title id + aria-labelledby + dir="auto"
|
|
783
|
+
const titleId = generateCalloutId("disclosure");
|
|
784
|
+
// v1.2.0: tags option (override element names for disclosures too)
|
|
785
|
+
const tags = options.tags ?? {};
|
|
786
|
+
const containerTag = tags.container || "details";
|
|
787
|
+
const titleTag = tags.title || "summary";
|
|
788
|
+
const bodyTag = tags.body || "div";
|
|
439
789
|
const disclosureHtml = [
|
|
440
|
-
|
|
441
|
-
`
|
|
442
|
-
`
|
|
790
|
+
`<${containerTag}${treeAttr} aria-labelledby="${titleId}" dir="auto"${openAttr}>`,
|
|
791
|
+
` <${titleTag} class="${safeDisclosureTitleClass}" id="${titleId}" dir="auto">${escapeHtml(title)}</${titleTag}>`,
|
|
792
|
+
` <${bodyTag} class="${safeDisclosureBodyClass}">`,
|
|
443
793
|
bodyHtml,
|
|
444
|
-
`
|
|
445
|
-
|
|
794
|
+
` </${bodyTag}>`,
|
|
795
|
+
`</${containerTag}>`,
|
|
446
796
|
].join("\n");
|
|
447
797
|
return html(disclosureHtml);
|
|
448
798
|
}
|
|
@@ -570,6 +920,192 @@ function getBlockquoteDepth(node, parent, tree) {
|
|
|
570
920
|
return depth;
|
|
571
921
|
}
|
|
572
922
|
// ---------------------------------------------------------------------------
|
|
923
|
+
// v1.3.0: Directive syntax transformer (:::type[Title] ... :::)
|
|
924
|
+
// ---------------------------------------------------------------------------
|
|
925
|
+
// Detects :::type paragraphs and converts them to callouts. This is a
|
|
926
|
+
// lightweight alternative to installing remark-directive — we detect the
|
|
927
|
+
// pattern in raw paragraph text and synthesize a ParsedCallout + HTML node.
|
|
928
|
+
//
|
|
929
|
+
// Supported syntax:
|
|
930
|
+
// :::note — basic callout (default title)
|
|
931
|
+
// :::note[Custom Title] — with custom title
|
|
932
|
+
// :::note{fold=-} — with overrides (icon, appearance, fold, inline)
|
|
933
|
+
// :::note[Title]{fold=-} — title + overrides
|
|
934
|
+
// ::::note ... :::: — 4-colon variant (for nesting; same behavior)
|
|
935
|
+
//
|
|
936
|
+
// The closing ::: must be on its own line. Body content between the opening
|
|
937
|
+
// and closing lines is rendered as the callout body.
|
|
938
|
+
function transformDirectiveSyntax(tree, configMap, options) {
|
|
939
|
+
if (!tree.children)
|
|
940
|
+
return;
|
|
941
|
+
const newChildren = [];
|
|
942
|
+
let i = 0;
|
|
943
|
+
while (i < tree.children.length) {
|
|
944
|
+
const child = tree.children[i];
|
|
945
|
+
// Check if this is a paragraph containing a :::type ... ::: directive
|
|
946
|
+
if (child.type === "paragraph") {
|
|
947
|
+
const text = extractTextContent(child);
|
|
948
|
+
// Use string methods (more reliable than regex with ^ anchor in some Node versions)
|
|
949
|
+
// Pattern: :::type[Title]{overrides}\n<body>\n:::
|
|
950
|
+
// Note: We use startsWith() + manual parsing instead of /^.../ regex
|
|
951
|
+
// because the ^ anchor has issues in some Node.js environments.
|
|
952
|
+
if (text.startsWith(":::")) {
|
|
953
|
+
// Count the colons
|
|
954
|
+
let colonCount = 0;
|
|
955
|
+
while (colonCount < text.length && text[colonCount] === ":")
|
|
956
|
+
colonCount++;
|
|
957
|
+
if (colonCount >= 3) {
|
|
958
|
+
const afterColons = text.slice(colonCount);
|
|
959
|
+
// Match type name (letters, digits, hyphens)
|
|
960
|
+
const typeMatch = afterColons.match(/^([a-zA-Z][\w-]*)/);
|
|
961
|
+
if (typeMatch) {
|
|
962
|
+
const rawType = typeMatch[1];
|
|
963
|
+
const type = rawType.toLowerCase();
|
|
964
|
+
const afterType = afterColons.slice(typeMatch[0].length);
|
|
965
|
+
// Parse optional [Title] and {overrides}
|
|
966
|
+
let title;
|
|
967
|
+
let overridesBlock;
|
|
968
|
+
let remaining = afterType;
|
|
969
|
+
// Optional [Title]
|
|
970
|
+
if (remaining.startsWith("[")) {
|
|
971
|
+
const closeIdx = remaining.indexOf("]");
|
|
972
|
+
if (closeIdx !== -1) {
|
|
973
|
+
title = remaining.slice(1, closeIdx);
|
|
974
|
+
remaining = remaining.slice(closeIdx + 1);
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
// Optional {overrides}
|
|
978
|
+
if (remaining.startsWith("{")) {
|
|
979
|
+
const closeIdx = remaining.indexOf("}");
|
|
980
|
+
if (closeIdx !== -1) {
|
|
981
|
+
overridesBlock = `{${remaining.slice(1, closeIdx)}}`;
|
|
982
|
+
remaining = remaining.slice(closeIdx + 1);
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
// Must be followed by \n
|
|
986
|
+
if (remaining.startsWith("\n")) {
|
|
987
|
+
remaining = remaining.slice(1);
|
|
988
|
+
// Find the closing ::: (must be on its own line at the end)
|
|
989
|
+
// Look for \n::: at the end
|
|
990
|
+
const closingMatch = remaining.match(/\n(:{3,})$/);
|
|
991
|
+
if (closingMatch) {
|
|
992
|
+
const bodyText = remaining.slice(0, closingMatch.index);
|
|
993
|
+
// Only convert if the type is a known callout type
|
|
994
|
+
if (configMap.has(type)) {
|
|
995
|
+
const config = configMap.get(type);
|
|
996
|
+
const customTitle = title?.trim() || undefined;
|
|
997
|
+
const overrides = parseOverrides(overridesBlock);
|
|
998
|
+
// Check for fold override
|
|
999
|
+
let foldMarker = "";
|
|
1000
|
+
if (overridesBlock) {
|
|
1001
|
+
const foldMatch = overridesBlock.match(/fold\s*=\s*([+-])/);
|
|
1002
|
+
if (foldMatch)
|
|
1003
|
+
foldMarker = foldMatch[1];
|
|
1004
|
+
}
|
|
1005
|
+
const collapsible = foldMarker === "+" || foldMarker === "-";
|
|
1006
|
+
const collapsibleOpen = foldMarker === "+";
|
|
1007
|
+
const parsed = {
|
|
1008
|
+
type: type,
|
|
1009
|
+
customTitle,
|
|
1010
|
+
collapsible,
|
|
1011
|
+
collapsibleOpen,
|
|
1012
|
+
isDisclosure: false,
|
|
1013
|
+
overrides,
|
|
1014
|
+
};
|
|
1015
|
+
// Build body HTML from the bodyText
|
|
1016
|
+
const bodyLines = bodyText.split("\n");
|
|
1017
|
+
const bodyHtml = bodyLines
|
|
1018
|
+
.map((line) => {
|
|
1019
|
+
if (line.trim() === "")
|
|
1020
|
+
return "";
|
|
1021
|
+
const escaped = options.allowDangerousHtml ? line : escapeHtml(line);
|
|
1022
|
+
return `<p>${escaped}</p>\n`;
|
|
1023
|
+
})
|
|
1024
|
+
.join("");
|
|
1025
|
+
const calloutHtml = buildCalloutFromParts(parsed, config, bodyHtml, options);
|
|
1026
|
+
newChildren.push(html(calloutHtml));
|
|
1027
|
+
i++;
|
|
1028
|
+
continue;
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
newChildren.push(child);
|
|
1037
|
+
i++;
|
|
1038
|
+
}
|
|
1039
|
+
tree.children = newChildren;
|
|
1040
|
+
}
|
|
1041
|
+
// ---------------------------------------------------------------------------
|
|
1042
|
+
// Helper: Build callout HTML from parsed parts (used by directive syntax)
|
|
1043
|
+
// ---------------------------------------------------------------------------
|
|
1044
|
+
function buildCalloutFromParts(parsed, config, bodyHtml, options) {
|
|
1045
|
+
// Reuse the existing buildCalloutHtml by synthesizing a fake blockquote
|
|
1046
|
+
// structure. We construct a minimal Blockquote-compatible object that
|
|
1047
|
+
// buildBodyHtml can process.
|
|
1048
|
+
const fakeBlockquote = {
|
|
1049
|
+
type: "blockquote",
|
|
1050
|
+
children: [
|
|
1051
|
+
{
|
|
1052
|
+
type: "paragraph",
|
|
1053
|
+
children: [{ type: "text", value: "" }],
|
|
1054
|
+
},
|
|
1055
|
+
],
|
|
1056
|
+
};
|
|
1057
|
+
// We need to call buildCalloutHtml which expects a real blockquote.
|
|
1058
|
+
// Instead, inline the essential rendering logic here.
|
|
1059
|
+
const title = parsed.customTitle || config.defaultTitle;
|
|
1060
|
+
const dataAttr = options.dataCalloutType ? ` data-callout-type="${escapeAttribute(parsed.type)}"` : "";
|
|
1061
|
+
const safeIconColor = sanitizeColor(config.iconColor || config.color, "#57606a");
|
|
1062
|
+
const ariaRole = "note";
|
|
1063
|
+
const safeCalloutClass = escapeAttribute(options.calloutClass);
|
|
1064
|
+
const safeConfigClassName = escapeAttribute(config.className);
|
|
1065
|
+
const safeCalloutTitleClass = escapeAttribute(options.calloutTitleClass);
|
|
1066
|
+
const safeCalloutBodyClass = escapeAttribute(options.calloutBodyClass);
|
|
1067
|
+
const titleId = generateCalloutId(parsed.type);
|
|
1068
|
+
const { showIcon, appearance } = resolveVisuals(parsed, options);
|
|
1069
|
+
const resolvedIcon = showIcon && appearance !== "simple" && appearance !== "hidden"
|
|
1070
|
+
? resolveIcon(parsed.type, config, options.iconSet)
|
|
1071
|
+
: "";
|
|
1072
|
+
const effectiveRenderIcon = showIcon && appearance !== "simple" && appearance !== "hidden" && resolvedIcon.length > 0;
|
|
1073
|
+
const iconSpan = effectiveRenderIcon
|
|
1074
|
+
? `<span class="callout-icon" style="color:${safeIconColor}" aria-hidden="true">${resolvedIcon}</span>`
|
|
1075
|
+
: "";
|
|
1076
|
+
const renderTitle = appearance !== "hidden";
|
|
1077
|
+
const labelledby = renderTitle ? ` aria-labelledby="${titleId}"` : "";
|
|
1078
|
+
const appearanceClass = appearance !== "default" ? ` callout-${appearance}` : "";
|
|
1079
|
+
const inlineClass = parsed.overrides?.inline ? ` callout-${parsed.overrides.inline}` : "";
|
|
1080
|
+
const titleBlock = renderTitle
|
|
1081
|
+
? [
|
|
1082
|
+
` <div class="${safeCalloutTitleClass}" style="color:${safeIconColor}" id="${titleId}" dir="auto">`,
|
|
1083
|
+
iconSpan && ` ${iconSpan}`,
|
|
1084
|
+
` <span class="callout-title-text">${escapeHtml(title)}</span>`,
|
|
1085
|
+
` </div>`,
|
|
1086
|
+
].filter(Boolean).join("\n")
|
|
1087
|
+
: "";
|
|
1088
|
+
if (parsed.collapsible) {
|
|
1089
|
+
const openAttr = parsed.collapsibleOpen ? " open" : "";
|
|
1090
|
+
return [
|
|
1091
|
+
`<details class="${safeCalloutClass} ${safeConfigClassName} collapsible${appearanceClass}${inlineClass}"${dataAttr} role="${ariaRole}"${labelledby} dir="auto"${openAttr}>`,
|
|
1092
|
+
titleBlock.replace(/<div /, "<summary ").replace(/<\/div>$/, "</summary>"),
|
|
1093
|
+
` <div class="${safeCalloutBodyClass}">`,
|
|
1094
|
+
bodyHtml,
|
|
1095
|
+
` </div>`,
|
|
1096
|
+
`</details>`,
|
|
1097
|
+
].filter(Boolean).join("\n");
|
|
1098
|
+
}
|
|
1099
|
+
return [
|
|
1100
|
+
`<aside class="${safeCalloutClass} ${safeConfigClassName}${appearanceClass}${inlineClass}"${dataAttr} role="${ariaRole}"${labelledby} dir="auto">`,
|
|
1101
|
+
titleBlock,
|
|
1102
|
+
` <div class="${safeCalloutBodyClass}">`,
|
|
1103
|
+
bodyHtml,
|
|
1104
|
+
` </div>`,
|
|
1105
|
+
`</aside>`,
|
|
1106
|
+
].filter(Boolean).join("\n");
|
|
1107
|
+
}
|
|
1108
|
+
// ---------------------------------------------------------------------------
|
|
573
1109
|
// Plugin implementation
|
|
574
1110
|
// ---------------------------------------------------------------------------
|
|
575
1111
|
export const remarkRemakeBlocks = (userOptions) => {
|
|
@@ -580,6 +1116,15 @@ export const remarkRemakeBlocks = (userOptions) => {
|
|
|
580
1116
|
const configMap = buildCalloutConfigMap(options);
|
|
581
1117
|
const calloutPattern = options.calloutPattern || buildCalloutPattern(configMap, options.enableDisclosures);
|
|
582
1118
|
return (tree) => {
|
|
1119
|
+
// v1.2.0: reset per-tree counter for idempotent output (same markdown →
|
|
1120
|
+
// same HTML, including same ids). This is important for snapshot tests
|
|
1121
|
+
// and for SSR/deterministic rendering.
|
|
1122
|
+
resetCalloutIdCounter();
|
|
1123
|
+
// v1.3.0: Pass 0 — Transform :::type[Title]{overrides} ... ::: directive syntax
|
|
1124
|
+
// into callouts (Starlight/Docusaurus/MkDocs content portability).
|
|
1125
|
+
if (options.enableDirectiveSyntax) {
|
|
1126
|
+
transformDirectiveSyntax(tree, configMap, options);
|
|
1127
|
+
}
|
|
583
1128
|
// ── Pass 1: Transform blockquotes → callouts / disclosures ──────
|
|
584
1129
|
// We must process DEEPEST blockquotes first (inside-out) so that
|
|
585
1130
|
// nested [!] directives are converted before their parents read them.
|
|
@@ -720,5 +1265,8 @@ function isDisclosureHtml(htmlStr) {
|
|
|
720
1265
|
// Backward-compatible alias
|
|
721
1266
|
// ---------------------------------------------------------------------------
|
|
722
1267
|
export const remarkCalloutBlocks = remarkRemakeBlocks;
|
|
1268
|
+
// Export helpers for users who supply a custom `build` function.
|
|
1269
|
+
// These let `build` authors reuse the plugin's escaping logic.
|
|
1270
|
+
export { escapeHtml, escapeAttribute, sanitizeColor };
|
|
723
1271
|
export { BUILTIN_CALLOUTS };
|
|
724
1272
|
//# sourceMappingURL=remark-remake-blocks.js.map
|