1- import { visit } from 'unist-util-visit' ;
1+ import { visit , EXIT } from 'unist-util-visit' ;
22
33/**
4- * Remark plugin to transform directives (::: tip, ::: note, ::: steps, etc.) into custom components
5- * Works with remark-directive to convert container directives into Svelte components
4+ * Remark plugin to transform MDC components (::tip, ::note, ::steps, etc.) into custom Svelte components
5+ * Works with remark-mdc to convert MDC components into Svelte components
66 *
7- * Supported directives:
8- * - :::tip - renders as Note component with variant="tip"
9- * - :::note - renders as Note component with variant="note"
10- * - :::warning - renders as Note component with variant="warning"
11- * - :::caution - renders as Note component with variant="caution"
12- * - :::steps - renders as Steps component
7+ * Supported components:
8+ * - ::tip / :::tip - renders as Note component with variant="tip"
9+ * - ::note / :::note - renders as Note component with variant="note"
10+ * - ::warning / :::warning - renders as Note component with variant="warning"
11+ * - ::caution / :::caution - renders as Note component with variant="caution"
12+ * - ::steps / :::steps - renders as Steps component
13+ * - ::tabs / :::tabs - renders as Tabs component (supports nested ::tab)
14+ * - ::tab / :::tab - renders as Tab component (used inside tabs, supports icon attribute)
15+ * - :icon - renders as Icon component (inline icon with name attribute)
1316 *
1417 * @returns {(tree: any) => void } A remark transformer function
1518 */
1619export function remarkDirectives ( ) {
1720 return ( tree ) => {
1821 const componentsToImport = new Set ( ) ;
1922
23+ // Process MDC components (remark-mdc creates leafComponent and containerComponent nodes)
2024 visit ( tree , ( node ) => {
21- // Handle container directives (:::directive )
22- if ( node . type === 'containerDirective ' ) {
23- const directiveName = node . name ;
25+ // Handle both leafComponent (::component) and containerComponent (::component...:: )
26+ if ( node . type === 'leafComponent' || node . type === 'containerComponent ') {
27+ const componentName = node . name ;
2428
2529 // Alert variants all use the Note component
2630 const alertVariants = [ 'tip' , 'note' , 'warning' , 'caution' ] ;
2731
28- // Map directive names to component names and variants
29- let componentName ;
32+ // Map component names to Svelte component names and variants
33+ let svelteComponent ;
3034 let variant ;
3135
32- if ( alertVariants . includes ( directiveName ) ) {
33- componentName = 'Note' ;
34- variant = directiveName ;
35- } else if ( directiveName === 'steps' ) {
36- componentName = 'Steps' ;
36+ if ( alertVariants . includes ( componentName ) ) {
37+ svelteComponent = 'Note' ;
38+ variant = componentName ;
39+ } else if ( componentName === 'steps' ) {
40+ svelteComponent = 'Steps' ;
41+ } else if ( componentName === 'tabs' ) {
42+ svelteComponent = 'Tabs' ;
43+ } else if ( componentName === 'tab' ) {
44+ svelteComponent = 'Tab' ;
3745 } else {
38- // Unknown directive , skip
46+ // Unknown component , skip transformation
3947 return ;
4048 }
4149
4250 // Track which components we need to import
43- componentsToImport . add ( componentName ) ;
51+ componentsToImport . add ( svelteComponent ) ;
4452
45- // Get directive attributes
53+ // Get component attributes from MDC
4654 const attributes = node . attributes || { } ;
4755
48- // Convert the directive into a custom component in the HTML tree
56+ // Convert the MDC component into a component that rehype can handle
4957 // We set data.hName to tell rehype to convert this to the component
5058 const data = node . data || ( node . data = { } ) ;
51- data . hName = componentName ;
59+ data . hName = svelteComponent ;
5260 data . hProperties = {
5361 ...attributes ,
5462 // Pass variant for alert components
55- ...( variant && { variant } ) ,
56- // Pass any directive label as a prop
57- ...( node . attributes ?. label && { label : node . attributes . label } )
63+ ...( variant && { variant } )
5864 } ;
5965 }
6066
61- // Handle text directives (:directive[text]) if needed
62- if ( node . type === 'textDirective' ) {
63- // Can be used for inline directives if needed in the future
64- }
67+ // Handle inline text components (:component)
68+ if ( node . type === 'textComponent' ) {
69+ const componentName = node . name ;
70+
71+ // Support :icon{name="i-lucide-code"} syntax
72+ if ( componentName === 'icon' ) {
73+ componentsToImport . add ( 'Icon' ) ;
6574
66- // Handle leaf directives (::directive) if needed
67- if ( node . type === 'leafDirective' ) {
68- // Can be used for self-closing directives if needed in the future
75+ const data = node . data || ( node . data = { } ) ;
76+ data . hName = 'Icon' ;
77+ data . hProperties = {
78+ ...( node . attributes || { } )
79+ } ;
80+ }
6981 }
7082 } ) ;
7183
@@ -83,9 +95,10 @@ export function remarkDirectives() {
8395 hasScript = true ;
8496 node . value = node . value . replace (
8597 / < s c r i p t [ ^ > ] * > / ,
98+ /** @param {string } match */
8699 ( match ) => `${ match } \n${ importStatements } `
87100 ) ;
88- return visit . EXIT ;
101+ return EXIT ;
89102 }
90103 } ) ;
91104
0 commit comments