@dr-ishaan/remake-blocks 1.3.0 → 1.4.1

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 +1 @@
1
- {"version":3,"file":"remark-remake-blocks.d.ts","sourceRoot":"","sources":["../src/remark-remake-blocks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAqC,MAAM,OAAO,CAAC;AACrE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAEtC,OAAO,KAAK,EACV,mBAAmB,EACnB,aAAa,EAId,MAAM,YAAY,CAAC;AAMpB,QAAA,MAAM,gBAAgB,EAAE,aAAa,EAsKpC,CAAC;AA8JF,iBAAS,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE5C;AAUD,iBAAS,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAW9D;AAuMD,iBAAS,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAOvC;AAupBD,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,CAAC,mBAAmB,CAAC,CAAC,EAAE,IAAI,CA8FnE,CAAC;AAiFF,eAAO,MAAM,mBAAmB,oDAAqB,CAAC;AAItD,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,aAAa,EAAE,CAAC;AAEtD,OAAO,EAAE,gBAAgB,EAAE,CAAC"}
1
+ {"version":3,"file":"remark-remake-blocks.d.ts","sourceRoot":"","sources":["../src/remark-remake-blocks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAqC,MAAM,OAAO,CAAC;AACrE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAEtC,OAAO,KAAK,EACV,mBAAmB,EACnB,aAAa,EAId,MAAM,YAAY,CAAC;AAMpB,QAAA,MAAM,gBAAgB,EAAE,aAAa,EAsKpC,CAAC;AA+LF,iBAAS,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE5C;AAUD,iBAAS,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAW9D;AAmPD,iBAAS,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAOvC;AAmzBD,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,CAAC,mBAAmB,CAAC,CAAC,EAAE,IAAI,CAoGnE,CAAC;AAiFF,eAAO,MAAM,mBAAmB,oDAAqB,CAAC;AAItD,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,aAAa,EAAE,CAAC;AAEtD,OAAO,EAAE,gBAAgB,EAAE,CAAC"}
@@ -218,6 +218,34 @@ const LUCIDE_ICONS = {
218
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
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
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
+ // ── v1.4.0: Extra Lucide icons for per-callout named icon ({icon="rocket"}) ──
222
+ rocket: `<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="M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09z"/><path d="m12 15-3-3a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 0 1-4 2z"/><path d="M9 12H4s.55-3.03 2-4c1.62-1.08 5 0 5 0"/><path d="M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5"/></svg>`,
223
+ heart: `<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="M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7Z"/></svg>`,
224
+ star: `<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="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>`,
225
+ bell: `<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="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9"/><path d="M10.3 21a1.94 1.94 0 0 0 3.4 0"/></svg>`,
226
+ flag: `<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="M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z"/><line x1="4" x2="4" y1="22" y2="15"/></svg>`,
227
+ bookmark: `<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="m19 21-7-4-7 4V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v16z"/></svg>`,
228
+ lightbulb: `<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>`,
229
+ fire: `<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.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z"/></svg>`,
230
+ zap: `<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>`,
231
+ shield: `<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="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"/></svg>`,
232
+ code: `<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"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>`,
233
+ book: `<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="M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H20v20H6.5a2.5 2.5 0 0 1 0-5H20"/></svg>`,
234
+ pencil: `<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="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/><path d="m15 5 4 4"/></svg>`,
235
+ eye: `<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 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z"/><circle cx="12" cy="12" r="3"/></svg>`,
236
+ globe: `<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 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20"/><path d="M2 12h20"/></svg>`,
237
+ lock: `<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="11" x="3" y="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>`,
238
+ key: `<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.5 7.5 2.3 2.3a1 1 0 0 0 1.4 0l2.1-2.1a1 1 0 0 0 0-1.4L19 4"/><path d="m21 2-9.6 9.6"/><circle cx="7.5" cy="15.5" r="5.5"/></svg>`,
239
+ clock: `<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"/><polyline points="12 6 12 12 16 14"/></svg>`,
240
+ calendar: `<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="4" rx="2" ry="2"/><line x1="16" x2="16" y1="2" y2="6"/><line x1="8" x2="8" y1="2" y2="6"/><line x1="3" x2="21" y1="10" y2="10"/></svg>`,
241
+ package: `<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="m7.5 4.27 9 5.15"/><path d="M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z"/><path d="m3.3 7 8.7 5 8.7-5"/><path d="M12 22V12"/></svg>`,
242
+ download: `<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 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" x2="12" y1="15" y2="3"/></svg>`,
243
+ upload: `<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 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" x2="12" y1="3" y2="15"/></svg>`,
244
+ link: `<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 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71a5 5 0 0 0-.54 7.54"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71a5 5 0 0 0 .54-7.54"/></svg>`,
245
+ settings: `<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.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/><circle cx="12" cy="12" r="3"/></svg>`,
246
+ terminal: `<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"><polyline points="4 17 10 11 4 5"/><line x1="12" x2="20" y1="19" y2="19"/></svg>`,
247
+ database: `<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"><ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M3 5V19A9 3 0 0 0 21 19V5"/><path d="M3 12A9 3 0 0 0 21 12"/></svg>`,
248
+ cloud: `<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="M17.5 19H9a7 7 0 1 1 6.71-9h1.79a4.5 4.5 0 1 1 0 9Z"/></svg>`,
221
249
  };
222
250
  const EMOJI_ICONS = {
223
251
  note: "ℹ️",
@@ -301,6 +329,9 @@ const DEFAULT_OPTIONS = {
301
329
  tags: {},
302
330
  // v1.3.0 defaults
303
331
  enableDirectiveSyntax: false,
332
+ // v1.4.0 defaults
333
+ labels: {},
334
+ enableMkDocsSyntax: false,
304
335
  };
305
336
  // ---------------------------------------------------------------------------
306
337
  // Helper: Validate + normalize a single custom callout config.
@@ -446,6 +477,11 @@ function parseOverrides(overridesBlock) {
446
477
  result.icon = false;
447
478
  else if (value === "true" || value === "1" || value === "yes")
448
479
  result.icon = true;
480
+ else if (value.length > 0) {
481
+ // v1.4.0: per-callout named icon (e.g. {icon="rocket"} uses Lucide rocket icon)
482
+ result.iconName = value;
483
+ result.icon = true; // ensure icon is shown
484
+ }
449
485
  }
450
486
  else if (key === "appearance") {
451
487
  if (value === "default" || value === "minimal" || value === "simple" || value === "hidden") {
@@ -467,11 +503,52 @@ function parseOverrides(overridesBlock) {
467
503
  }
468
504
  // Unknown keys silently ignored
469
505
  }
470
- if (result.icon === undefined && result.appearance === undefined && result.inline === undefined)
506
+ if (result.icon === undefined && result.appearance === undefined && result.inline === undefined && result.iconName === undefined)
471
507
  return undefined;
472
508
  return result;
473
509
  }
474
510
  // ---------------------------------------------------------------------------
511
+ // v1.4.0: Parse directive attribute block `{#id .class key="value"}`
512
+ // Supports the remark-directive attribute syntax:
513
+ // #id — sets element id
514
+ // .class-name — adds CSS class
515
+ // key="value" — adds arbitrary HTML attribute
516
+ // key=value — adds arbitrary HTML attribute (bareword)
517
+ // ---------------------------------------------------------------------------
518
+ function parseDirectiveAttrs(attrsBlock) {
519
+ if (!attrsBlock)
520
+ return undefined;
521
+ const inner = attrsBlock.trim().replace(/^\{|\}$/g, "").trim();
522
+ if (!inner)
523
+ return undefined;
524
+ const result = { classes: [], attrs: {} };
525
+ // Match #id, .class, key="value", key='value', key=value
526
+ const tokenRe = /(#)([\w-]+)|(\.)([\w-]+)|(\w[\w-]*)\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s}]+))/g;
527
+ let m;
528
+ while ((m = tokenRe.exec(inner)) !== null) {
529
+ if (m[1] === "#" && m[2]) {
530
+ result.id = m[2];
531
+ }
532
+ else if (m[3] === "." && m[4]) {
533
+ result.classes.push(m[4]);
534
+ }
535
+ else if (m[5]) {
536
+ const key = m[5];
537
+ const value = (m[6] ?? m[7] ?? m[8] ?? "").trim();
538
+ // Block event handlers and style for security
539
+ if (key.toLowerCase().startsWith("on"))
540
+ continue;
541
+ if (key.toLowerCase() === "style")
542
+ continue;
543
+ result.attrs[key] = value;
544
+ }
545
+ }
546
+ if (!result.id && (!result.classes || result.classes.length === 0) && Object.keys(result.attrs || {}).length === 0) {
547
+ return undefined;
548
+ }
549
+ return result;
550
+ }
551
+ // ---------------------------------------------------------------------------
475
552
  // Helper: Parse the first paragraph of a blockquote to detect a callout
476
553
  // ---------------------------------------------------------------------------
477
554
  function parseCalloutDirective(blockquote, calloutPattern, configMap, enableDisclosures) {
@@ -646,8 +723,13 @@ function buildCalloutHtml(parsed, blockquote, calloutPattern, configMap, options
646
723
  }
647
724
  // ── Regular / Collapsible Callout ──────────────────────────────────
648
725
  const config = configMap.get(parsed.type);
649
- const title = parsed.customTitle || config.defaultTitle;
726
+ // v1.4.0: i18n label customization — labels option overrides defaultTitle
727
+ // Use || instead of ?? so empty strings also fall back to default
728
+ const defaultTitle = options.labels?.[parsed.type] || config.defaultTitle;
729
+ const title = parsed.customTitle || defaultTitle;
650
730
  const dataAttr = options.dataCalloutType ? ` data-callout-type="${escapeAttribute(parsed.type)}"` : "";
731
+ // v1.4.0: data-collapsible attribute (for CSS targeting of collapsible vs non-collapsible)
732
+ const collapsibleDataAttr = ` data-collapsible="${parsed.collapsible ? "true" : "false"}"`;
651
733
  const bodyHtml = buildBodyHtml(blockquote, calloutPattern, options);
652
734
  // v1.2.0: resolve per-callout visuals (icon visibility + appearance)
653
735
  const { showIcon, appearance } = resolveVisuals(parsed, options);
@@ -666,9 +748,6 @@ function buildCalloutHtml(parsed, blockquote, calloutPattern, configMap, options
666
748
  // Sanitize colors before interpolating into style attribute
667
749
  const safeIconColor = sanitizeColor(config.iconColor || config.color, "#57606a");
668
750
  // 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
751
  const ariaRole = "note";
673
752
  // Escape className + calloutClass to prevent attribute breakout
674
753
  const safeCalloutClass = escapeAttribute(options.calloutClass);
@@ -680,14 +759,39 @@ function buildCalloutHtml(parsed, blockquote, calloutPattern, configMap, options
680
759
  // v1.2.0: resolve per-type props (dir, style, data-*, etc.)
681
760
  const extraProps = resolveProps(options.props, parsed.type, parsed);
682
761
  const extraAttrs = formatPropsAsAttrs(extraProps);
762
+ // v1.4.0: directive attributes (#id, .class, key="value")
763
+ let directiveId = "";
764
+ let directiveClasses = "";
765
+ let directiveAttrs = "";
766
+ if (parsed.directiveAttrs) {
767
+ if (parsed.directiveAttrs.id) {
768
+ directiveId = ` id="${escapeAttribute(parsed.directiveAttrs.id)}"`;
769
+ }
770
+ if (parsed.directiveAttrs.classes && parsed.directiveAttrs.classes.length > 0) {
771
+ directiveClasses = " " + parsed.directiveAttrs.classes.map(c => escapeAttribute(c)).join(" ");
772
+ }
773
+ if (parsed.directiveAttrs.attrs) {
774
+ directiveAttrs = formatPropsAsAttrs(parsed.directiveAttrs.attrs);
775
+ }
776
+ }
683
777
  // v1.2.0: appearance class
684
778
  const appearanceClass = appearance !== "default" ? ` callout-${appearance}` : "";
685
779
  // v1.3.0: inline floating class (responsive float left/right)
686
780
  const inlineClass = parsed.overrides?.inline ? ` callout-${parsed.overrides.inline}` : "";
687
781
  // v1.2.0: icon HTML — only render if showIcon AND appearance allows it
688
782
  // v1.3.0: resolve icon based on iconSet option (octicon|lucide|emoji|none)
783
+ // v1.4.0: per-callout named icon override ({icon="rocket"})
689
784
  const renderIcon = showIcon && appearance !== "simple" && appearance !== "hidden";
690
- const resolvedIcon = renderIcon ? resolveIcon(parsed.type, config, options.iconSet) : "";
785
+ let resolvedIcon = "";
786
+ if (renderIcon) {
787
+ if (parsed.overrides?.iconName) {
788
+ // v1.4.0: per-callout named icon (Lucide icon by name)
789
+ resolvedIcon = LUCIDE_ICONS[parsed.overrides.iconName] ?? resolveIcon(parsed.type, config, options.iconSet);
790
+ }
791
+ else {
792
+ resolvedIcon = resolveIcon(parsed.type, config, options.iconSet);
793
+ }
794
+ }
691
795
  const effectiveRenderIcon = renderIcon && resolvedIcon.length > 0;
692
796
  const iconHtml = effectiveRenderIcon
693
797
  ? `<span class="callout-icon" style="color:${safeIconColor}" aria-hidden="true">${resolvedIcon}</span>`
@@ -735,7 +839,7 @@ function buildCalloutHtml(parsed, blockquote, calloutPattern, configMap, options
735
839
  if (parsed.collapsible) {
736
840
  const openAttr = parsed.collapsibleOpen ? " open" : "";
737
841
  return html([
738
- `<${containerTag} class="${safeCalloutClass} ${safeConfigClassName} collapsible${appearanceClass}${inlineClass}"${dataAttr} role="${ariaRole}"${labelledby}${dirAuto}${openAttr}${extraAttrs}>`,
842
+ `<${containerTag} class="${safeCalloutClass} ${safeConfigClassName} collapsible${appearanceClass}${inlineClass}${directiveClasses}"${dataAttr}${collapsibleDataAttr} role="${ariaRole}"${labelledby}${dirAuto}${openAttr}${directiveId}${extraAttrs}${directiveAttrs}>`,
739
843
  titleBlockWithDir,
740
844
  ` <${bodyTag} class="${safeCalloutBodyClass}">`,
741
845
  bodyHtml,
@@ -744,7 +848,7 @@ function buildCalloutHtml(parsed, blockquote, calloutPattern, configMap, options
744
848
  ].filter(Boolean).join("\n"));
745
849
  }
746
850
  return html([
747
- `<${containerTag} class="${safeCalloutClass} ${safeConfigClassName}${appearanceClass}${inlineClass}"${dataAttr} role="${ariaRole}"${labelledby}${dirAuto}${extraAttrs}>`,
851
+ `<${containerTag} class="${safeCalloutClass} ${safeConfigClassName}${appearanceClass}${inlineClass}${directiveClasses}"${dataAttr}${collapsibleDataAttr} role="${ariaRole}"${labelledby}${dirAuto}${directiveId}${extraAttrs}${directiveAttrs}>`,
748
852
  titleBlockWithDir,
749
853
  ` <${bodyTag} class="${safeCalloutBodyClass}">`,
750
854
  bodyHtml,
@@ -995,6 +1099,8 @@ function transformDirectiveSyntax(tree, configMap, options) {
995
1099
  const config = configMap.get(type);
996
1100
  const customTitle = title?.trim() || undefined;
997
1101
  const overrides = parseOverrides(overridesBlock);
1102
+ // v1.4.0: parse directive attributes (#id, .class, key="value")
1103
+ const directiveAttrs = parseDirectiveAttrs(overridesBlock);
998
1104
  // Check for fold override
999
1105
  let foldMarker = "";
1000
1106
  if (overridesBlock) {
@@ -1011,6 +1117,7 @@ function transformDirectiveSyntax(tree, configMap, options) {
1011
1117
  collapsibleOpen,
1012
1118
  isDisclosure: false,
1013
1119
  overrides,
1120
+ directiveAttrs,
1014
1121
  };
1015
1122
  // Build body HTML from the bodyText
1016
1123
  const bodyLines = bodyText.split("\n");
@@ -1039,25 +1146,114 @@ function transformDirectiveSyntax(tree, configMap, options) {
1039
1146
  tree.children = newChildren;
1040
1147
  }
1041
1148
  // ---------------------------------------------------------------------------
1149
+ // v1.4.0: MkDocs admonition syntax transformer (!!! / ??? / ???+)
1150
+ // ---------------------------------------------------------------------------
1151
+ // Detects !!! type, ??? type, ???+ type paragraphs and converts them to
1152
+ // callouts. This enables content portability with MkDocs Material.
1153
+ //
1154
+ // Supported syntax:
1155
+ // !!! note — non-collapsible callout
1156
+ // ??? note — collapsed callout (same as > [!NOTE]-)
1157
+ // ???+ note — expanded collapsible callout (same as > [!NOTE]+)
1158
+ // !!! note "Title" — with custom title (quoted)
1159
+ // !!! note "" — no title (strips default)
1160
+ //
1161
+ // The body is indented 4 spaces under the directive.
1162
+ function transformMkDocsSyntax(tree, configMap, options) {
1163
+ if (!tree.children)
1164
+ return;
1165
+ const newChildren = [];
1166
+ let i = 0;
1167
+ while (i < tree.children.length) {
1168
+ const child = tree.children[i];
1169
+ if (child.type === "paragraph") {
1170
+ const text = extractTextContent(child);
1171
+ // Check for !!! type, ??? type, or ???+ type
1172
+ // Pattern: (!!!|???\+?) type ["Title"]\n body (4-space indented)
1173
+ if (text.startsWith("!!!") || text.startsWith("???")) {
1174
+ // Determine the marker: !!!, ???, or ???+
1175
+ let marker = "";
1176
+ let markerEnd = 3;
1177
+ if (text.startsWith("???+")) {
1178
+ marker = "???+";
1179
+ markerEnd = 4;
1180
+ }
1181
+ else if (text.startsWith("???")) {
1182
+ marker = "???";
1183
+ markerEnd = 3;
1184
+ }
1185
+ else if (text.startsWith("!!!")) {
1186
+ marker = "!!!";
1187
+ markerEnd = 3;
1188
+ }
1189
+ const afterMarker = text.slice(markerEnd);
1190
+ // Parse type name
1191
+ const typeMatch = afterMarker.match(/^\s+([a-zA-Z][\w-]*)/);
1192
+ if (typeMatch) {
1193
+ const type = typeMatch[1].toLowerCase();
1194
+ let remaining = afterMarker.slice(typeMatch[0].length);
1195
+ // Parse optional "Title" (quoted)
1196
+ let title;
1197
+ const titleMatch = remaining.match(/^\s+"([^"]*)"/);
1198
+ if (titleMatch) {
1199
+ title = titleMatch[1];
1200
+ remaining = remaining.slice(titleMatch[0].length);
1201
+ }
1202
+ // Must be followed by \n
1203
+ if (remaining.startsWith("\n")) {
1204
+ remaining = remaining.slice(1);
1205
+ // Only convert if the type is a known callout type
1206
+ if (configMap.has(type)) {
1207
+ const config = configMap.get(type);
1208
+ const customTitle = title !== undefined ? (title.trim() || undefined) : undefined;
1209
+ // Determine collapsible state from marker
1210
+ const collapsible = marker === "???" || marker === "???+";
1211
+ const collapsibleOpen = marker === "???+";
1212
+ const parsed = {
1213
+ type: type,
1214
+ customTitle,
1215
+ collapsible,
1216
+ collapsibleOpen,
1217
+ isDisclosure: false,
1218
+ };
1219
+ // Build body HTML from the remaining text
1220
+ // Strip 4-space indentation from each line
1221
+ const bodyLines = remaining.split("\n").map((line) => {
1222
+ if (line.startsWith(" "))
1223
+ return line.slice(4);
1224
+ return line;
1225
+ });
1226
+ const bodyHtml = bodyLines
1227
+ .map((line) => {
1228
+ if (line.trim() === "")
1229
+ return "";
1230
+ const escaped = options.allowDangerousHtml ? line : escapeHtml(line);
1231
+ return `<p>${escaped}</p>\n`;
1232
+ })
1233
+ .join("");
1234
+ const calloutHtml = buildCalloutFromParts(parsed, config, bodyHtml, options);
1235
+ newChildren.push(html(calloutHtml));
1236
+ i++;
1237
+ continue;
1238
+ }
1239
+ }
1240
+ }
1241
+ }
1242
+ }
1243
+ newChildren.push(child);
1244
+ i++;
1245
+ }
1246
+ tree.children = newChildren;
1247
+ }
1248
+ // ---------------------------------------------------------------------------
1042
1249
  // Helper: Build callout HTML from parsed parts (used by directive syntax)
1043
1250
  // ---------------------------------------------------------------------------
1044
1251
  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;
1252
+ // v1.4.0: i18n label customization
1253
+ const defaultTitle = options.labels?.[parsed.type] || config.defaultTitle;
1254
+ const title = parsed.customTitle || defaultTitle;
1060
1255
  const dataAttr = options.dataCalloutType ? ` data-callout-type="${escapeAttribute(parsed.type)}"` : "";
1256
+ const collapsibleDataAttr = ` data-collapsible="${parsed.collapsible ? "true" : "false"}"`;
1061
1257
  const safeIconColor = sanitizeColor(config.iconColor || config.color, "#57606a");
1062
1258
  const ariaRole = "note";
1063
1259
  const safeCalloutClass = escapeAttribute(options.calloutClass);
@@ -1065,10 +1261,32 @@ function buildCalloutFromParts(parsed, config, bodyHtml, options) {
1065
1261
  const safeCalloutTitleClass = escapeAttribute(options.calloutTitleClass);
1066
1262
  const safeCalloutBodyClass = escapeAttribute(options.calloutBodyClass);
1067
1263
  const titleId = generateCalloutId(parsed.type);
1264
+ // v1.4.0: directive attributes
1265
+ let directiveId = "";
1266
+ let directiveClasses = "";
1267
+ let directiveAttrs = "";
1268
+ if (parsed.directiveAttrs) {
1269
+ if (parsed.directiveAttrs.id) {
1270
+ directiveId = ` id="${escapeAttribute(parsed.directiveAttrs.id)}"`;
1271
+ }
1272
+ if (parsed.directiveAttrs.classes && parsed.directiveAttrs.classes.length > 0) {
1273
+ directiveClasses = " " + parsed.directiveAttrs.classes.map(c => escapeAttribute(c)).join(" ");
1274
+ }
1275
+ if (parsed.directiveAttrs.attrs) {
1276
+ directiveAttrs = formatPropsAsAttrs(parsed.directiveAttrs.attrs);
1277
+ }
1278
+ }
1068
1279
  const { showIcon, appearance } = resolveVisuals(parsed, options);
1069
- const resolvedIcon = showIcon && appearance !== "simple" && appearance !== "hidden"
1070
- ? resolveIcon(parsed.type, config, options.iconSet)
1071
- : "";
1280
+ // v1.4.0: per-callout named icon
1281
+ let resolvedIcon = "";
1282
+ if (showIcon && appearance !== "simple" && appearance !== "hidden") {
1283
+ if (parsed.overrides?.iconName) {
1284
+ resolvedIcon = LUCIDE_ICONS[parsed.overrides.iconName] ?? resolveIcon(parsed.type, config, options.iconSet);
1285
+ }
1286
+ else {
1287
+ resolvedIcon = resolveIcon(parsed.type, config, options.iconSet);
1288
+ }
1289
+ }
1072
1290
  const effectiveRenderIcon = showIcon && appearance !== "simple" && appearance !== "hidden" && resolvedIcon.length > 0;
1073
1291
  const iconSpan = effectiveRenderIcon
1074
1292
  ? `<span class="callout-icon" style="color:${safeIconColor}" aria-hidden="true">${resolvedIcon}</span>`
@@ -1088,7 +1306,7 @@ function buildCalloutFromParts(parsed, config, bodyHtml, options) {
1088
1306
  if (parsed.collapsible) {
1089
1307
  const openAttr = parsed.collapsibleOpen ? " open" : "";
1090
1308
  return [
1091
- `<details class="${safeCalloutClass} ${safeConfigClassName} collapsible${appearanceClass}${inlineClass}"${dataAttr} role="${ariaRole}"${labelledby} dir="auto"${openAttr}>`,
1309
+ `<details class="${safeCalloutClass} ${safeConfigClassName} collapsible${appearanceClass}${inlineClass}${directiveClasses}"${dataAttr}${collapsibleDataAttr} role="${ariaRole}"${labelledby} dir="auto"${openAttr}${directiveId}${directiveAttrs}>`,
1092
1310
  titleBlock.replace(/<div /, "<summary ").replace(/<\/div>$/, "</summary>"),
1093
1311
  ` <div class="${safeCalloutBodyClass}">`,
1094
1312
  bodyHtml,
@@ -1097,7 +1315,7 @@ function buildCalloutFromParts(parsed, config, bodyHtml, options) {
1097
1315
  ].filter(Boolean).join("\n");
1098
1316
  }
1099
1317
  return [
1100
- `<aside class="${safeCalloutClass} ${safeConfigClassName}${appearanceClass}${inlineClass}"${dataAttr} role="${ariaRole}"${labelledby} dir="auto">`,
1318
+ `<aside class="${safeCalloutClass} ${safeConfigClassName}${appearanceClass}${inlineClass}${directiveClasses}"${dataAttr}${collapsibleDataAttr} role="${ariaRole}"${labelledby} dir="auto"${directiveId}${directiveAttrs}>`,
1101
1319
  titleBlock,
1102
1320
  ` <div class="${safeCalloutBodyClass}">`,
1103
1321
  bodyHtml,
@@ -1125,6 +1343,11 @@ export const remarkRemakeBlocks = (userOptions) => {
1125
1343
  if (options.enableDirectiveSyntax) {
1126
1344
  transformDirectiveSyntax(tree, configMap, options);
1127
1345
  }
1346
+ // v1.4.0: Pass 0b — Transform !!! note / ??? note / ???+ note MkDocs admonition syntax
1347
+ // into callouts (MkDocs Material content portability).
1348
+ if (options.enableMkDocsSyntax) {
1349
+ transformMkDocsSyntax(tree, configMap, options);
1350
+ }
1128
1351
  // ── Pass 1: Transform blockquotes → callouts / disclosures ──────
1129
1352
  // We must process DEEPEST blockquotes first (inside-out) so that
1130
1353
  // nested [!] directives are converted before their parents read them.