@dr-ishaan/remake-blocks 1.2.0 → 1.4.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.
@@ -185,6 +185,120 @@ 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
+ // ── 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>`,
249
+ };
250
+ const EMOJI_ICONS = {
251
+ note: "ℹ️",
252
+ tip: "💡",
253
+ important: "⚠️",
254
+ warning: "⚠️",
255
+ caution: "⛔",
256
+ abstract: "📋",
257
+ info: "ℹ️",
258
+ success: "✅",
259
+ question: "❓",
260
+ failure: "❌",
261
+ danger: "⚡",
262
+ quote: "💬",
263
+ bug: "🐛",
264
+ example: "📝",
265
+ todo: "☑️",
266
+ summary: "📋",
267
+ tldr: "⚡",
268
+ hint: "💡",
269
+ check: "✅",
270
+ done: "✅",
271
+ help: "❓",
272
+ faq: "❓",
273
+ attention: "⚠️",
274
+ fail: "❌",
275
+ missing: "🚫",
276
+ error: "❌",
277
+ cite: "📌",
278
+ };
279
+ /**
280
+ * Resolve the icon for a given callout type based on the iconSet option.
281
+ * Returns the icon string (SVG or emoji) or empty string if not found.
282
+ */
283
+ function resolveIcon(type, config, iconSet) {
284
+ // Custom callouts always use their configured icon, regardless of iconSet.
285
+ // We detect this by checking if the type is NOT in BUILTIN_CALLOUTS.
286
+ // (config may be a normalized custom callout — its icon is the user's choice.)
287
+ const isBuiltin = BUILTIN_CALLOUTS.some((b) => b.type === type);
288
+ if (!isBuiltin) {
289
+ return config.icon;
290
+ }
291
+ // For builtins, swap icon based on iconSet option.
292
+ if (iconSet === "lucide")
293
+ return LUCIDE_ICONS[type] ?? config.icon;
294
+ if (iconSet === "emoji")
295
+ return EMOJI_ICONS[type] ?? config.icon;
296
+ if (iconSet === "none")
297
+ return "";
298
+ // Default: octicon (the builtin's own icon)
299
+ return config.icon;
300
+ }
301
+ // ---------------------------------------------------------------------------
188
302
  // Default plugin options
189
303
  // ---------------------------------------------------------------------------
190
304
  const DEFAULT_OPTIONS = {
@@ -213,6 +327,11 @@ const DEFAULT_OPTIONS = {
213
327
  props: {},
214
328
  build: undefined,
215
329
  tags: {},
330
+ // v1.3.0 defaults
331
+ enableDirectiveSyntax: false,
332
+ // v1.4.0 defaults
333
+ labels: {},
334
+ enableMkDocsSyntax: false,
216
335
  };
217
336
  // ---------------------------------------------------------------------------
218
337
  // Helper: Validate + normalize a single custom callout config.
@@ -321,6 +440,7 @@ function buildCalloutPattern(configMap, enableDisclosures) {
321
440
  // Supported keys (v1.2.0):
322
441
  // - icon: true|false — override showIndicator per-callout
323
442
  // - appearance: default|minimal|simple|hidden — override appearance per-callout
443
+ // - inline: inline|inline-end — v1.3.0+: float left/right (responsive)
324
444
  // Unknown keys are silently ignored.
325
445
  // ---------------------------------------------------------------------------
326
446
  function parseOverrides(overridesBlock) {
@@ -330,7 +450,23 @@ function parseOverrides(overridesBlock) {
330
450
  if (!inner)
331
451
  return undefined;
332
452
  const result = {};
333
- // Match key=value pairs. Values can be unquoted barewords or "quoted strings".
453
+ // First pass: handle bare keywords (no `=` sign).
454
+ // Supported bare keywords: `inline`, `inline-end`
455
+ // These set the inline override without needing `inline=true`.
456
+ // Note: `inline-end` must be matched BEFORE `inline` to avoid the prefix
457
+ // matching, so we use a single regex with `inline-end` first in the
458
+ // alternation and consume the entire match.
459
+ const bareKeywordRe = /\b(inline-end|inline)\b(?!\s*=)/gi;
460
+ let bareMatch;
461
+ while ((bareMatch = bareKeywordRe.exec(inner)) !== null) {
462
+ const kw = bareMatch[1].toLowerCase();
463
+ if (kw === "inline-end")
464
+ result.inline = "inline-end";
465
+ else if (kw === "inline")
466
+ result.inline = "inline";
467
+ }
468
+ // Second pass: handle key=value pairs.
469
+ // Values can be unquoted barewords or "quoted strings".
334
470
  const pairRe = /(\w[\w-]*)\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s}]+))/g;
335
471
  let m;
336
472
  while ((m = pairRe.exec(inner)) !== null) {
@@ -341,19 +477,78 @@ function parseOverrides(overridesBlock) {
341
477
  result.icon = false;
342
478
  else if (value === "true" || value === "1" || value === "yes")
343
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
+ }
344
485
  }
345
486
  else if (key === "appearance") {
346
487
  if (value === "default" || value === "minimal" || value === "simple" || value === "hidden") {
347
488
  result.appearance = value;
348
489
  }
349
490
  }
491
+ else if (key === "inline") {
492
+ // v1.3.0+: inline floating callouts
493
+ if (value === "true" || value === "left" || value === "inline") {
494
+ result.inline = "inline";
495
+ }
496
+ else if (value === "right" || value === "inline-end" || value === "end") {
497
+ result.inline = "inline-end";
498
+ }
499
+ else if (value === "false" || value === "0" || value === "no") {
500
+ // explicitly disable inline (clear any bare-keyword setting)
501
+ result.inline = undefined;
502
+ }
503
+ }
350
504
  // Unknown keys silently ignored
351
505
  }
352
- if (result.icon === undefined && result.appearance === undefined)
506
+ if (result.icon === undefined && result.appearance === undefined && result.inline === undefined && result.iconName === undefined)
353
507
  return undefined;
354
508
  return result;
355
509
  }
356
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
+ // ---------------------------------------------------------------------------
357
552
  // Helper: Parse the first paragraph of a blockquote to detect a callout
358
553
  // ---------------------------------------------------------------------------
359
554
  function parseCalloutDirective(blockquote, calloutPattern, configMap, enableDisclosures) {
@@ -528,8 +723,13 @@ function buildCalloutHtml(parsed, blockquote, calloutPattern, configMap, options
528
723
  }
529
724
  // ── Regular / Collapsible Callout ──────────────────────────────────
530
725
  const config = configMap.get(parsed.type);
531
- 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;
532
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"}"`;
533
733
  const bodyHtml = buildBodyHtml(blockquote, calloutPattern, options);
534
734
  // v1.2.0: resolve per-callout visuals (icon visibility + appearance)
535
735
  const { showIcon, appearance } = resolveVisuals(parsed, options);
@@ -548,9 +748,6 @@ function buildCalloutHtml(parsed, blockquote, calloutPattern, configMap, options
548
748
  // Sanitize colors before interpolating into style attribute
549
749
  const safeIconColor = sanitizeColor(config.iconColor || config.color, "#57606a");
550
750
  // v1.2.0: WCAG fix — use role="note" for ALL callouts (was: role="alert" for warnings).
551
- // role="alert" is for dynamically-inserted content and causes aggressive immediate
552
- // announcement that disrupts screen reader users on static content.
553
- // Use role="note" + aria-labelledby for proper title→container association.
554
751
  const ariaRole = "note";
555
752
  // Escape className + calloutClass to prevent attribute breakout
556
753
  const safeCalloutClass = escapeAttribute(options.calloutClass);
@@ -562,12 +759,42 @@ function buildCalloutHtml(parsed, blockquote, calloutPattern, configMap, options
562
759
  // v1.2.0: resolve per-type props (dir, style, data-*, etc.)
563
760
  const extraProps = resolveProps(options.props, parsed.type, parsed);
564
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
+ }
565
777
  // v1.2.0: appearance class
566
778
  const appearanceClass = appearance !== "default" ? ` callout-${appearance}` : "";
779
+ // v1.3.0: inline floating class (responsive float left/right)
780
+ const inlineClass = parsed.overrides?.inline ? ` callout-${parsed.overrides.inline}` : "";
567
781
  // v1.2.0: icon HTML — only render if showIcon AND appearance allows it
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"})
568
784
  const renderIcon = showIcon && appearance !== "simple" && appearance !== "hidden";
569
- const iconHtml = renderIcon
570
- ? `<span class="callout-icon" style="color:${safeIconColor}" aria-hidden="true">${config.icon}</span>`
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
+ }
795
+ const effectiveRenderIcon = renderIcon && resolvedIcon.length > 0;
796
+ const iconHtml = effectiveRenderIcon
797
+ ? `<span class="callout-icon" style="color:${safeIconColor}" aria-hidden="true">${resolvedIcon}</span>`
571
798
  : "";
572
799
  // v1.2.0: title-text always rendered (unless appearance="hidden")
573
800
  const renderTitle = appearance !== "hidden";
@@ -582,8 +809,8 @@ function buildCalloutHtml(parsed, blockquote, calloutPattern, configMap, options
582
809
  const titleTextTag = tags.titleText || "span";
583
810
  const bodyTag = tags.body || "div";
584
811
  // Build icon span with custom tag if provided
585
- const iconSpan = renderIcon
586
- ? `<${iconTag} class="callout-icon" style="color:${safeIconColor}" aria-hidden="true">${config.icon}</${iconTag}>`
812
+ const iconSpan = effectiveRenderIcon
813
+ ? `<${iconTag} class="callout-icon" style="color:${safeIconColor}" aria-hidden="true">${resolvedIcon}</${iconTag}>`
587
814
  : "";
588
815
  // Build the title block (or skip for "hidden" appearance)
589
816
  const titleBlock = renderTitle
@@ -612,7 +839,7 @@ function buildCalloutHtml(parsed, blockquote, calloutPattern, configMap, options
612
839
  if (parsed.collapsible) {
613
840
  const openAttr = parsed.collapsibleOpen ? " open" : "";
614
841
  return html([
615
- `<${containerTag} class="${safeCalloutClass} ${safeConfigClassName} collapsible${appearanceClass}"${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}>`,
616
843
  titleBlockWithDir,
617
844
  ` <${bodyTag} class="${safeCalloutBodyClass}">`,
618
845
  bodyHtml,
@@ -621,7 +848,7 @@ function buildCalloutHtml(parsed, blockquote, calloutPattern, configMap, options
621
848
  ].filter(Boolean).join("\n"));
622
849
  }
623
850
  return html([
624
- `<${containerTag} class="${safeCalloutClass} ${safeConfigClassName}${appearanceClass}"${dataAttr} role="${ariaRole}"${labelledby}${dirAuto}${extraAttrs}>`,
851
+ `<${containerTag} class="${safeCalloutClass} ${safeConfigClassName}${appearanceClass}${inlineClass}${directiveClasses}"${dataAttr}${collapsibleDataAttr} role="${ariaRole}"${labelledby}${dirAuto}${directiveId}${extraAttrs}${directiveAttrs}>`,
625
852
  titleBlockWithDir,
626
853
  ` <${bodyTag} class="${safeCalloutBodyClass}">`,
627
854
  bodyHtml,
@@ -797,6 +1024,306 @@ function getBlockquoteDepth(node, parent, tree) {
797
1024
  return depth;
798
1025
  }
799
1026
  // ---------------------------------------------------------------------------
1027
+ // v1.3.0: Directive syntax transformer (:::type[Title] ... :::)
1028
+ // ---------------------------------------------------------------------------
1029
+ // Detects :::type paragraphs and converts them to callouts. This is a
1030
+ // lightweight alternative to installing remark-directive — we detect the
1031
+ // pattern in raw paragraph text and synthesize a ParsedCallout + HTML node.
1032
+ //
1033
+ // Supported syntax:
1034
+ // :::note — basic callout (default title)
1035
+ // :::note[Custom Title] — with custom title
1036
+ // :::note{fold=-} — with overrides (icon, appearance, fold, inline)
1037
+ // :::note[Title]{fold=-} — title + overrides
1038
+ // ::::note ... :::: — 4-colon variant (for nesting; same behavior)
1039
+ //
1040
+ // The closing ::: must be on its own line. Body content between the opening
1041
+ // and closing lines is rendered as the callout body.
1042
+ function transformDirectiveSyntax(tree, configMap, options) {
1043
+ if (!tree.children)
1044
+ return;
1045
+ const newChildren = [];
1046
+ let i = 0;
1047
+ while (i < tree.children.length) {
1048
+ const child = tree.children[i];
1049
+ // Check if this is a paragraph containing a :::type ... ::: directive
1050
+ if (child.type === "paragraph") {
1051
+ const text = extractTextContent(child);
1052
+ // Use string methods (more reliable than regex with ^ anchor in some Node versions)
1053
+ // Pattern: :::type[Title]{overrides}\n<body>\n:::
1054
+ // Note: We use startsWith() + manual parsing instead of /^.../ regex
1055
+ // because the ^ anchor has issues in some Node.js environments.
1056
+ if (text.startsWith(":::")) {
1057
+ // Count the colons
1058
+ let colonCount = 0;
1059
+ while (colonCount < text.length && text[colonCount] === ":")
1060
+ colonCount++;
1061
+ if (colonCount >= 3) {
1062
+ const afterColons = text.slice(colonCount);
1063
+ // Match type name (letters, digits, hyphens)
1064
+ const typeMatch = afterColons.match(/^([a-zA-Z][\w-]*)/);
1065
+ if (typeMatch) {
1066
+ const rawType = typeMatch[1];
1067
+ const type = rawType.toLowerCase();
1068
+ const afterType = afterColons.slice(typeMatch[0].length);
1069
+ // Parse optional [Title] and {overrides}
1070
+ let title;
1071
+ let overridesBlock;
1072
+ let remaining = afterType;
1073
+ // Optional [Title]
1074
+ if (remaining.startsWith("[")) {
1075
+ const closeIdx = remaining.indexOf("]");
1076
+ if (closeIdx !== -1) {
1077
+ title = remaining.slice(1, closeIdx);
1078
+ remaining = remaining.slice(closeIdx + 1);
1079
+ }
1080
+ }
1081
+ // Optional {overrides}
1082
+ if (remaining.startsWith("{")) {
1083
+ const closeIdx = remaining.indexOf("}");
1084
+ if (closeIdx !== -1) {
1085
+ overridesBlock = `{${remaining.slice(1, closeIdx)}}`;
1086
+ remaining = remaining.slice(closeIdx + 1);
1087
+ }
1088
+ }
1089
+ // Must be followed by \n
1090
+ if (remaining.startsWith("\n")) {
1091
+ remaining = remaining.slice(1);
1092
+ // Find the closing ::: (must be on its own line at the end)
1093
+ // Look for \n::: at the end
1094
+ const closingMatch = remaining.match(/\n(:{3,})$/);
1095
+ if (closingMatch) {
1096
+ const bodyText = remaining.slice(0, closingMatch.index);
1097
+ // Only convert if the type is a known callout type
1098
+ if (configMap.has(type)) {
1099
+ const config = configMap.get(type);
1100
+ const customTitle = title?.trim() || undefined;
1101
+ const overrides = parseOverrides(overridesBlock);
1102
+ // v1.4.0: parse directive attributes (#id, .class, key="value")
1103
+ const directiveAttrs = parseDirectiveAttrs(overridesBlock);
1104
+ // Check for fold override
1105
+ let foldMarker = "";
1106
+ if (overridesBlock) {
1107
+ const foldMatch = overridesBlock.match(/fold\s*=\s*([+-])/);
1108
+ if (foldMatch)
1109
+ foldMarker = foldMatch[1];
1110
+ }
1111
+ const collapsible = foldMarker === "+" || foldMarker === "-";
1112
+ const collapsibleOpen = foldMarker === "+";
1113
+ const parsed = {
1114
+ type: type,
1115
+ customTitle,
1116
+ collapsible,
1117
+ collapsibleOpen,
1118
+ isDisclosure: false,
1119
+ overrides,
1120
+ directiveAttrs,
1121
+ };
1122
+ // Build body HTML from the bodyText
1123
+ const bodyLines = bodyText.split("\n");
1124
+ const bodyHtml = bodyLines
1125
+ .map((line) => {
1126
+ if (line.trim() === "")
1127
+ return "";
1128
+ const escaped = options.allowDangerousHtml ? line : escapeHtml(line);
1129
+ return `<p>${escaped}</p>\n`;
1130
+ })
1131
+ .join("");
1132
+ const calloutHtml = buildCalloutFromParts(parsed, config, bodyHtml, options);
1133
+ newChildren.push(html(calloutHtml));
1134
+ i++;
1135
+ continue;
1136
+ }
1137
+ }
1138
+ }
1139
+ }
1140
+ }
1141
+ }
1142
+ }
1143
+ newChildren.push(child);
1144
+ i++;
1145
+ }
1146
+ tree.children = newChildren;
1147
+ }
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
+ // ---------------------------------------------------------------------------
1249
+ // Helper: Build callout HTML from parsed parts (used by directive syntax)
1250
+ // ---------------------------------------------------------------------------
1251
+ function buildCalloutFromParts(parsed, config, bodyHtml, options) {
1252
+ // v1.4.0: i18n label customization
1253
+ const defaultTitle = options.labels?.[parsed.type] || config.defaultTitle;
1254
+ const title = parsed.customTitle || defaultTitle;
1255
+ const dataAttr = options.dataCalloutType ? ` data-callout-type="${escapeAttribute(parsed.type)}"` : "";
1256
+ const collapsibleDataAttr = ` data-collapsible="${parsed.collapsible ? "true" : "false"}"`;
1257
+ const safeIconColor = sanitizeColor(config.iconColor || config.color, "#57606a");
1258
+ const ariaRole = "note";
1259
+ const safeCalloutClass = escapeAttribute(options.calloutClass);
1260
+ const safeConfigClassName = escapeAttribute(config.className);
1261
+ const safeCalloutTitleClass = escapeAttribute(options.calloutTitleClass);
1262
+ const safeCalloutBodyClass = escapeAttribute(options.calloutBodyClass);
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
+ }
1279
+ const { showIcon, appearance } = resolveVisuals(parsed, options);
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
+ }
1290
+ const effectiveRenderIcon = showIcon && appearance !== "simple" && appearance !== "hidden" && resolvedIcon.length > 0;
1291
+ const iconSpan = effectiveRenderIcon
1292
+ ? `<span class="callout-icon" style="color:${safeIconColor}" aria-hidden="true">${resolvedIcon}</span>`
1293
+ : "";
1294
+ const renderTitle = appearance !== "hidden";
1295
+ const labelledby = renderTitle ? ` aria-labelledby="${titleId}"` : "";
1296
+ const appearanceClass = appearance !== "default" ? ` callout-${appearance}` : "";
1297
+ const inlineClass = parsed.overrides?.inline ? ` callout-${parsed.overrides.inline}` : "";
1298
+ const titleBlock = renderTitle
1299
+ ? [
1300
+ ` <div class="${safeCalloutTitleClass}" style="color:${safeIconColor}" id="${titleId}" dir="auto">`,
1301
+ iconSpan && ` ${iconSpan}`,
1302
+ ` <span class="callout-title-text">${escapeHtml(title)}</span>`,
1303
+ ` </div>`,
1304
+ ].filter(Boolean).join("\n")
1305
+ : "";
1306
+ if (parsed.collapsible) {
1307
+ const openAttr = parsed.collapsibleOpen ? " open" : "";
1308
+ return [
1309
+ `<details class="${safeCalloutClass} ${safeConfigClassName} collapsible${appearanceClass}${inlineClass}${directiveClasses}"${dataAttr}${collapsibleDataAttr} role="${ariaRole}"${labelledby} dir="auto"${openAttr}${directiveId}${directiveAttrs}>`,
1310
+ titleBlock.replace(/<div /, "<summary ").replace(/<\/div>$/, "</summary>"),
1311
+ ` <div class="${safeCalloutBodyClass}">`,
1312
+ bodyHtml,
1313
+ ` </div>`,
1314
+ `</details>`,
1315
+ ].filter(Boolean).join("\n");
1316
+ }
1317
+ return [
1318
+ `<aside class="${safeCalloutClass} ${safeConfigClassName}${appearanceClass}${inlineClass}${directiveClasses}"${dataAttr}${collapsibleDataAttr} role="${ariaRole}"${labelledby} dir="auto"${directiveId}${directiveAttrs}>`,
1319
+ titleBlock,
1320
+ ` <div class="${safeCalloutBodyClass}">`,
1321
+ bodyHtml,
1322
+ ` </div>`,
1323
+ `</aside>`,
1324
+ ].filter(Boolean).join("\n");
1325
+ }
1326
+ // ---------------------------------------------------------------------------
800
1327
  // Plugin implementation
801
1328
  // ---------------------------------------------------------------------------
802
1329
  export const remarkRemakeBlocks = (userOptions) => {
@@ -811,6 +1338,16 @@ export const remarkRemakeBlocks = (userOptions) => {
811
1338
  // same HTML, including same ids). This is important for snapshot tests
812
1339
  // and for SSR/deterministic rendering.
813
1340
  resetCalloutIdCounter();
1341
+ // v1.3.0: Pass 0 — Transform :::type[Title]{overrides} ... ::: directive syntax
1342
+ // into callouts (Starlight/Docusaurus/MkDocs content portability).
1343
+ if (options.enableDirectiveSyntax) {
1344
+ transformDirectiveSyntax(tree, configMap, options);
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
+ }
814
1351
  // ── Pass 1: Transform blockquotes → callouts / disclosures ──────
815
1352
  // We must process DEEPEST blockquotes first (inside-out) so that
816
1353
  // nested [!] directives are converted before their parents read them.