Maintain Directives In Extended Markdown Render Pipeline
Maintain Directives as part of our Extended Markdown
Overview
To enhance our Extended Markdown capabilities, we have successfully integrated a custom
remark-directive
to render custom components using the directive syntax to parse relevant data.The current custom component of concern is
Figma-Object--Display.astro
, which can render Figma objects specified in Markdown using a unique link (with authorization credentials stored in the .env variables.)Render Pipeline Architecture
The directive rendering pipeline involves multiple files working together to transform markdown directives into rendered components:
graph TB
MD["Markdown File<br|>with directives"] --> AC["astro.config.mjs<br|>(Remark Plugins)"]
AC --> RP1["remarkDirective<br|>(Parse directive syntax)"]
RP1 --> RP2["remarkDirectiveToComponent<br|>(Preserve directive nodes)"]
RP2 --> OA["OneArticle.astro<br|>(Layout)"]
OA --> OAOP["OneArticleOnPage.astro<br|>(Article Component)"]
OAOP --> AM["AstroMarkdown.astro<br|>(Markdown Renderer)"]
AM --> FO["Figma-Object--Display.astro<br|>(::figma-embed)"]
AM --> TS["ToolShowcaseIsland.astro<br|>(:::tool-showcase)"]
AM --> SD["SlidesDirective.astro<br|>(:::slides)"]
FO --> HTML1["Rendered HTML<br|>with Figma iframe"]
TS --> HTML2["Rendered HTML<br|>with tool carousel"]
SD --> HTML3["Rendered HTML<br|>with slides embed"]
style MD fill:#f9f,stroke:#333,stroke-width:2px
style AC fill:#9ff,stroke:#333,stroke-width:2px
style RP1 fill:#ff9,stroke:#333,stroke-width:2px
style RP2 fill:#ff9,stroke:#333,stroke-width:2px
style OA fill:#9f9,stroke:#333,stroke-width:2px
style OAOP fill:#9f9,stroke:#333,stroke-width:2px
style AM fill:#99f,stroke:#333,stroke-width:2px
style FO fill:#f99,stroke:#333,stroke-width:2px
style TS fill:#f99,stroke:#333,stroke-width:2px
style SD fill:#f99,stroke:#333,stroke-width:2px
style HTML1 fill:#fff,stroke:#333,stroke-width:2px
style HTML2 fill:#fff,stroke:#333,stroke-width:2px
style HTML3 fill:#fff,stroke:#333,stroke-width:2px
Installation and Setup
- Install the Lossless Group's
remark-directive
Package: Install the custom fork directly from the GitHub repository using pnpm. This approach is more practical for most use cases as it treats the fork as a dependency rather than requiring local development setup.
bashpnpm add https://github.com/lossless-group/remark-directive.git
This will install the package and maintain the connection to the remote repository for updates.a. - [ ] Alternative: Fork and Clone for Development: Only use this approach if you need to make changes to theremark-directive
package itself:bashgit clone https://github.com/lossless-group/remark-directive.git cd remark-directive npm install
Directive Syntax
- Agree on directive syntax in markdown files
Leaf Directive Example (single-line with attributes)markdown::figma-embed{src="https://www.figma.com/object-link"}
Leaf Directive with additional argumentsmarkdown::figma-embed{ src="https://www.figma.com/design/abc123/My-Design" auth-user="mpstaton" width="800" height="600" }
Container Directive Example (multi-line with content)markdown:::tool-showcase - [[Tooling/AI-Toolkit/Tool Name|Display Name]] - [[vertical-toolkits/Category/Another Tool|Another Tool]] :::
Agree on Conventions for Defining Directives and mapping to Components
- Agree on conventions for defining directives and mapping to components
While right now we are only focused on a Figma object renderer, we have established conventions for defining directives and mapping to components for future extensibility.Directive Naming Convention:
- Use kebab-case for directive names
- Include the service/tool name as prefix:
::figma-embed
,::miro-board
,::notion-page
- Use descriptive suffixes for different render types:
-embed
,-display
,-preview
,-showcase
Component File Convention:
- Components should follow the pattern:
{Service}-{Type}--{Action}.astro
- Examples:
Figma-Object--Display.astro
,Miro-Board--Embed.astro
,Notion-Page--Preview.astro
,ToolShowcaseIsland.astro
- Use PascalCase for component files to match Astro conventions
- Server island components should include "Island" suffix for client-side functionality
Directive-to-Component Mapping Implementation:
The mapping is defined insrc/utils/markdown/remark-directives.ts
:typescriptexport const directiveComponentMap: Record<string, string> = { 'figma-embed': 'Figma-Object--Display.astro', 'tool-showcase': 'ToolShowcaseIsland.astro', // Future components following the same pattern: // 'miro-board': 'Miro-Board--Embed.astro', // 'notion-page': 'Notion-Page--Preview.astro', // 'youtube-video': 'YouTube-Video--Embed.astro', // 'github-gist': 'GitHub-Gist--Display.astro', };
The remarkDirectiveToComponent Plugin:
typescriptexport function remarkDirectiveToComponent() { return (tree: any) => { visit(tree, (node: any) => { if (node.type === 'leafDirective' || node.type === 'containerDirective') { const directiveName = node.name; // Validate that this is a supported directive if (isSupportedDirective(directiveName)) { // Leave the node as-is for AstroMarkdown.astro to handle // Just add some debug info if needed if (process.env.DEBUG_AST === 'true') { console.log(`[remarkDirectiveToComponent] Preserving directive: ${directiveName}`); } } else { // For unsupported directives, log a warning but preserve the node console.warn(`[remarkDirectiveToComponent] Unknown directive: ${directiveName}`); } // Always preserve the original directive node - don't transform to HTML // AstroMarkdown.astro will handle the actual rendering } }); }; }
Required Props Convention:
src
orurl
: The primary resource URL (required)auth-user
: User identifier for authorization (optional, falls back to default)width
/height
: Dimensions (optional, component provides defaults)- Component-specific props as needed
Authentication Pattern:
- Environment variables:
{SERVICE}_{USER}_TOKEN
(e.g.,FIGMA_MPSTATON_TOKEN
) - Default user fallback:
{SERVICE}_DEFAULT_TOKEN
- Components should handle missing auth gracefully
Error Handling Convention:
- Components should render fallback content when authentication fails
- Display helpful error messages in development mode
- Log authentication issues for debugging
Configuration
- Configure astro.config.mjs: The remark-directive plugin is configured in
astro.config.mjs
with a two-step process:
javascript// Import directive-related modules import remarkDirective from 'remark-directive'; import { directiveComponentMap, remarkDirectiveToComponent } from './src/utils/markdown/remark-directives.ts'; // In the markdown configuration: remarkPlugins: [ /** @type {any} */ (normalizeShellLangs), /** @type {any} */ (remarkTableOfContents), /** @type {any} */ (remarkDirective), // Parse directive syntax /** @type {any} */ (remarkDirectiveToComponent), // Transform directives to components ],
TheremarkDirective
plugin parses the directive syntax, whileremarkDirectiveToComponent
preserves the directive nodes in the AST for later processing byAstroMarkdown.astro
.
Custom Component Implementation
- Create Custom Component: The
Figma-Object--Display.astro
component has been developed with advanced features:
Component Features:
- Smart URL Parsing: Extracts file ID, node ID, and prototype status from Figma URLs
- Metadata Fetching: Uses Figma API to fetch node information for optimal sizing
- Authentication Support: Handles user-specific tokens with fallback patterns
- Intelligent Defaults: Sets optimal embed parameters based on content type
- Responsive Design: Calculates dimensions based on frame aspect ratios
Key Implementation Details:
typescript// Parse Figma URL to extract metadata function parseFigmaUrl(url: string) { const urlObj = new URL(url); const pathParts = urlObj.pathname.split('/'); const fileId = pathParts[2]; // /design/FILE_ID/... const nodeId = urlObj.searchParams.get('node-id'); const isPrototype = urlObj.pathname.includes('/proto/'); return { fileId, nodeId, isPrototype }; } // Fetch node metadata from Figma API async function fetchNodeMetadata(fileId: string, nodeId: string, headers: any) { try { const response = await fetch(`https://api.figma.com/v1/files/${fileId}/nodes?ids=${nodeId}`, { headers }); if (!response.ok) { console.warn('Failed to fetch Figma node metadata:', response.status); return null; } const data = await response.json(); const nodeData = data.nodes?.[nodeId]?.document; if (nodeData) { return { type: nodeData.type, name: nodeData.name, absoluteBoundingBox: nodeData.absoluteBoundingBox, backgroundColor: nodeData.backgroundColor, isFrame: nodeData.type === 'FRAME' }; } } catch (error) { console.warn('Error fetching Figma metadata:', error); } return null; }
- Integration with remark-directive: The directive parsing is handled automatically by the
remark-directive
plugin, and the component is rendered throughAstroMarkdown.astro
without needing to modify the remark-directive package itself.
Tool Showcase Directive Implementation
Overview
The
tool-showcase
directive enables rendering interactive tool carousels from backlink lists in markdown. It supports both vertical-toolkits
and tooling
collections, making it versatile for different content types.Component Architecture
The
ToolShowcaseIsland.astro
component is implemented as a server island, allowing it to:- Fetch tool data from multiple Astro content collections
- Parse backlink syntax from container directive content
- Render interactive carousel components with tool metadata
Key Implementation Details
typescript
// Server-side data fetching across multiple collections
const [verticalToolkits, tooling] = await Promise.all([
getCollection('vertical-toolkits').catch(() => []),
getCollection('tooling').catch(() => [])
]);
// Combine all tools from different collections
const allTools = [...verticalToolkits, ...tooling];
// Parse backlink patterns like [[path/to/tool|Display Name]]
function parseBacklinks(content: string): Array<{path: string, displayName?: string}> {
const backlinkRegex = /\[\[([^\]|]+)(?:\|([^\]]+))?\]\]/g;
const backlinks = [];
let match;
while ((match = backlinkRegex.exec(content)) !== null) {
backlinks.push({
path: match[1].trim(),
displayName: match[2] ? match[2].trim() : undefined
});
}
return backlinks;
}
Container Directive Processing
The component handles container directives by parsing their content for markdown list items:
typescript
// In AstroMarkdown.astro - tool-showcase directive handling
if (directiveName === 'tool-showcase') {
let toolPaths = [];
if (node.type === "containerDirective" && node.children) {
// Find list nodes in the container
const listNodes = node.children.filter(child => child.type === 'list');
for (const listNode of listNodes) {
if (listNode.children) {
for (const listItem of listNode.children) {
if (listItem.type === 'listItem' && listItem.children) {
// Extract text content from list item
const textContent = extractTextFromNode(listItem);
const backlinks = parseBacklinks(textContent);
toolPaths.push(...backlinks);
}
}
}
}
}
if (toolPaths.length > 0) {
return <ToolShowcaseIsland toolPaths={toolPaths} />;
}
}
Handling Directives with AstroMarkdown
- Support Both Leaf and Container Directives in AstroMarkdown: The
AstroMarkdown.astro
component handles both directive types fromremark-directive
: - Leaf Directives: Single-line syntax like
::figma-embed{ src="..." width="800" }
- Container Directives: Multi-line syntax with triple colons:markdown
:::figma-embed src="..." width="800" height="600" :::
- Directive Rendering in AstroMarkdown.astro: The component includes specific handling for directive nodes:
typescript{/* Handle directive nodes from remark-directive */} {(node.type === "leafDirective" || node.type === "containerDirective") && (() => { const directiveName = node.name; const props = node.attributes || {}; if (directiveName === 'figma-embed') { const figmaUrl = props.src || props.url || ''; const width = props.width || '100%'; const height = props.height || '500px'; const authUser = props['auth-user'] || ''; return ( <> <div class="figma-embed-container" data-directive="figma-embed"> <div class="figma-embed-wrapper"> <iframe src={`https://www.figma.com/embed?embed_host=lossless.group&url=${encodeURIComponent(figmaUrl)}&initial_view=design&scaling=contain&hide_ui=true`} allowfullscreen loading="lazy" title="Figma embed" style={`border: none; width: ${width}; height: ${height}; border-radius: 8px; max-width: 100%;`} ></iframe> </div> <div class="figma-embed-footer"> <a href={figmaUrl} target="_blank" rel="noopener">Open in Figma →</a> </div> </div> {/* Styles omitted for brevity */} </> ); } if (directiveName === 'tool-showcase') { // Parse the container content for markdown list items with backlinks let toolPaths = []; if (node.type === "containerDirective" && node.children) { // Find list nodes in the container const listNodes = node.children.filter(child => child.type === 'list'); for (const listNode of listNodes) { if (listNode.children) { for (const listItem of listNode.children) { if (listItem.type === 'listItem' && listItem.children) { // Extract text content from list item const textContent = extractTextFromNode(listItem); const backlinks = parseBacklinks(textContent); toolPaths.push(...backlinks); } } } } } if (toolPaths.length > 0) { return <ToolShowcaseIsland toolPaths={toolPaths} />; } } // Handle other directive types or show debug info return ( <div class="unknown-directive" data-directive={directiveName}> <p>Unknown directive: <code>{directiveName}</code></p> <details> <summary>Debug Info</summary> <pre>{JSON.stringify({ name: directiveName, attributes: props }, null, 2)}</pre> </details> </div> ); })()}
Processing Flow Diagram
The following diagram shows how a directive flows through the processing pipeline:
sequenceDiagram
participant MD as Markdown File
participant RD as remarkDirective
participant RDC as remarkDirectiveToComponent
participant OA as OneArticle.astro
participant OAOP as OneArticleOnPage.astro
participant AM as AstroMarkdown.astro
participant FO as Figma-Object--Display.astro
participant TS as ToolShowcaseIsland.astro
participant CC as Content Collections
Note over MD: Leaf Directive Processing
MD->>RD: ::figma-embed{src="..."}
RD->>RD: Parse directive syntax
RD->>RDC: AST with directive nodes
RDC->>RDC: Validate & preserve nodes
RDC->>OA: Processed MDAST
OA->>OA: Process with remark plugins
OA->>OAOP: Pass transformedMdast
OAOP->>AM: Render with AstroMarkdown
AM->>AM: Check node.type === "leafDirective"
AM->>AM: Extract props from attributes
AM->>FO: Render Figma component
FO->>FO: Parse URL, fetch metadata
FO-->>MD: Rendered iframe HTML
Note over MD: Container Directive Processing
MD->>RD: :::tool-showcase<br/>- tool1<br/>- tool2<br/>:::
RD->>RD: Parse container directive
RD->>RDC: AST with containerDirective node
RDC->>RDC: Validate & preserve nodes
RDC->>OA: Processed MDAST
OA->>OAOP: Pass transformedMdast
OAOP->>AM: Render with AstroMarkdown
AM->>AM: Check node.type === "containerDirective"
AM->>AM: Parse list items for backlinks
AM->>TS: Render ToolShowcase with toolPaths
TS->>CC: Fetch tool data from collections
CC-->>TS: Return tool metadata
TS-->>MD: Rendered carousel HTML
Layout Integration
The directive rendering is integrated into the layout hierarchy:
OneArticle.astro
- Processes markdown content through remark plugins
- Passes the transformed MDAST to OneArticleOnPage
typescript
// Process with our custom remark plugins to get MDAST
const processor = unified()
.use(remarkParse) // 1. Parse markdown to MDAST
.use(remarkGfm)
.use(remarkDirective) // 2. Parse directive syntax
.use(remarkDirectiveToComponent) // 3. Preserve directives for AstroMarkdown
.use(remarkImages)
.use(remarkBacklinks)
.use(remarkCitations)
.use(remarkTableOfContents)
// First parse to MDAST
const mdast = processor.parse(content || '');
const transformedMdast = await processor.run(mdast);
OneArticleOnPage.astro
- Receives the transformed MDAST
- Passes it to AstroMarkdown for rendering
typescript
<AstroMarkdown
node={{
type: 'root',
children: children.filter(child => child?.type !== 'tableOfContents'),
data: { hProperties }
}}
data={dataForMarkdown}
/>
- Testing: The directive rendering has been successfully implemented and tested. The Figma component properly parses URLs, fetches metadata when possible, and renders with intelligent defaults.
- Documentation: This blueprint now serves as comprehensive documentation of the directive rendering pipeline, including:
- Architecture diagrams showing component relationships
- Code snippets demonstrating key implementation details
- Processing flow diagrams showing data transformation
- Complete working examples of all components involved
Usage
Figma Embed Directive
To embed a Figma object, use a leaf directive:
markdown
::figma-embed{src="https://www.figma.com/design/abc123/My-Design"}
This renders the Figma object using the
Figma-Object--Display.astro
component.Tool Showcase Directive
To create an interactive tool carousel, use a container directive with backlink lists:
markdown
:::tool-showcase
- [[Tooling/AI-Toolkit/Tool Name|Display Name]]
- [[vertical-toolkits/Category/Another Tool|Another Tool]]
- [[Tooling/Enterprise/Third Tool|Third Tool]]
:::
This renders an interactive carousel using the
ToolShowcaseIsland.astro
server island component, which fetches tool metadata from the content collections.Slideshows as Directives (Planned)
Overview
The
slides
directive will enable embedding markdown-based presentations directly within content pages. This builds on our existing SlidesEmbed.astro
component but will require adaptation for the directive pattern.Proposed Syntax
The slides system will support two methods of embedding presentations:
1. Custom Codeblock Method (Existing)
As implemented in the current system, using special codeblocks:
markdown
:::slides
slides/introduction-to-ai
slides/advanced-concepts
slides/case-studies
:::
2. Directive Method (New Addition)
Using the container directive syntax to align with our other directives:
markdown
:::slides
- [[slides/introduction-to-ai|Introduction to AI]]
- [[slides/advanced-concepts|Advanced Concepts]]
- [[slides/case-studies|Case Studies]]
:::
Both methods will render the same output, giving authors flexibility in how they embed presentations.
Architecture Considerations
The directive implementation will complement the existing custom codeblock approach:
Existing System (Custom Codeblocks)
- Already implemented and working
- Processed during the markdown parsing phase
- Uses special language identifier in code blocks
New Directive Addition
- Follows the established directive pattern (like tool-showcase)
- Processed by AstroMarkdown.astro
- Provides backlink syntax support
Implementation Approach
- Keep the existing custom codeblock functionality unchanged
- Add directive support that:
- Parses backlink paths from the container directive
- Converts them to the same format as custom codeblocks
- Delegates to the existing
SlidesEmbed.astro
component
- Both methods ultimately use the same rendering component
Implementation Plan
1. Update Directive Mapping
typescript
export const directiveComponentMap: Record<string, string> = {
'figma-embed': 'Figma-Object--Display.astro',
'tool-showcase': 'ToolShowcaseIsland.astro',
'slides': 'SlidesDirective.astro', // New mapping
};
2. Create SlidesDirective.astro
typescript
---
import { getCollection } from 'astro:content';
import SlidesEmbed from './SlidesEmbed.astro';
export interface Props {
slidePaths: Array<{path: string, displayName?: string}>;
}
const { slidePaths } = Astro.props;
// Fetch slide data from content collection
const slides = await getCollection('slides');
const selectedSlides = slidePaths
.map(({ path }) => {
// Normalize path (remove collection prefix if present)
const normalizedPath = path.replace(/^slides\//, '');
return slides.find(slide => slide.slug === normalizedPath);
})
.filter(Boolean)
.map(slide => ({
path: `slides/${slide.slug}`,
title: slide.data.title || slide.slug
}));
---
<SlidesEmbed slides={selectedSlides} config={{
theme: 'black',
transition: 'slide',
controls: true,
progress: true
}} />
3. Add Directive Processing in AstroMarkdown
typescript
if (directiveName === 'slides') {
let slidePaths = [];
if (node.type === "containerDirective" && node.children) {
// Parse list items for slide backlinks
const listNodes = node.children.filter(child => child.type === 'list');
for (const listNode of listNodes) {
if (listNode.children) {
for (const listItem of listNode.children) {
if (listItem.type === 'listItem' && listItem.children) {
const textContent = extractTextFromNode(listItem);
const backlinks = parseBacklinks(textContent);
slidePaths.push(...backlinks);
}
}
}
}
}
if (slidePaths.length > 0) {
return <SlidesDirective slidePaths={slidePaths} />;
}
}
Configuration Options
Both methods support optional configuration. If no attributes are provided, slides render with default styling:
Default Rendering (No Attributes)
markdown
:::
slides/intro
slides/demo
:::
Or with directive syntax:
markdown
:::slides
- [[slides/intro|Introduction]]
- [[slides/demo|Live Demo]]
:::
With Custom Configuration
markdown
:::slides{theme="white" transition="fade"}
slides/intro
slides/demo
:::
Or with directive syntax:
markdown
:::slides{theme="white" transition="fade"}
- [[slides/intro|Introduction]]
- [[slides/demo|Live Demo]]
:::
Benefits of Directive Approach
- Content Integration: Presentations can be embedded directly in documentation
- Reusability: Same slides can be embedded in multiple locations
- Consistency: Follows established directive patterns
- Flexibility: Supports both simple lists and complex configurations
Technical Challenges
- Route Handling: Ensure embedded slide routes work correctly
- Performance: Consider lazy loading for multiple presentations
- Responsive Design: Adapt iframe dimensions for mobile devices
- Error Handling: Gracefully handle missing or invalid slide paths
Future Enhancements
- Support for external slide sources (URLs)
- Custom themes per directive
- Presentation navigation controls
- Export to PDF functionality
- Speaker notes integration
If Client Loading is Needed: Implement in Server Islands and Svelte
Note: This section documents the pattern for future implementation if client-side interactivity is needed for the slides directive. We are not implementing this now, but may need to later.
When a directive requires client-side interactivity (like the ToolShowcase carousel with its navigation controls and touch gestures), the implementation pattern involves two components:
1. Server Island Component (Astro)
The
.astro
component acts as a server-side data fetcher and bridge: typescript
// SlidesDirectiveIsland.astro
---
import { getCollection } from 'astro:content';
import SlidesCarousel from './SlidesCarousel.svelte';
interface Props {
slidePaths: Array<{path: string, displayName?: string}>;
}
const { slidePaths } = Astro.props;
// Server-side data fetching
const slides = await getCollection('slides');
const matchedSlides = slidePaths
.map(({ path }) => {
const normalizedPath = path.replace(/^slides\//, '');
return slides.find(slide => slide.slug === normalizedPath);
})
.filter(Boolean);
// Transform data for client component
const slidesData = matchedSlides.map(slide => ({
title: slide.data.title,
path: `slides/${slide.slug}`,
// ... other needed properties
}));
---
{slidesData.length > 0 ? (
<SlidesCarousel slides={slidesData} client:load />
) : (
<div class="slides-error">No matching slides found</div>
)}
Key aspects:
- Handles server-side data fetching from content collections
- Transforms data into a format suitable for the client component
- Uses
client:load
directive to hydrate the Svelte component - Provides error handling for missing content
2. Client Component (Svelte)
The
.svelte
component provides the interactive functionality: typescript
// SlidesCarousel.svelte
<script lang="ts">
export let slides: Array<{ title: string; path: string }> = [];
let currentSlide = 0;
function nextSlide() {
currentSlide = (currentSlide + 1) % slides.length;
}
function prevSlide() {
currentSlide = currentSlide === 0 ? slides.length - 1 : currentSlide - 1;
}
// Touch handling, keyboard navigation, etc.
</script>
<!-- Interactive carousel UI -->
Key aspects:
- Manages client-side state (current slide, navigation)
- Handles user interactions (touch, click, keyboard)
- Provides smooth transitions and animations
- Fully interactive without server round-trips
3. Directive Integration
The AstroMarkdown component would then use the island component:
typescript
if (directiveName === 'slides') {
// ... parse slidePaths from directive content ...
if (slidePaths.length > 0) {
return <SlidesDirectiveIsland slidePaths={slidePaths} />;
}
}
This pattern separates concerns effectively:
- Server-side: Data fetching, content resolution, SEO
- Client-side: Interactivity, animations, user controls
- Bridge: The island component connects both worlds
Maintenance
- Regular Updates: Keep the
remark-directive
package and its dependencies updated to ensure compatibility with future Markdown rendering enhancements. - Feedback and Iterations: Continuously gather feedback from users regarding the directive's functionality and make necessary improvements or extensions.
This document will serve as a guideline for integrating and maintaining the Figma object rendering functionality as part of our Extended Markdown capabilities.
Component Styling and Breakout Containers
The Figma embed component includes responsive styling that integrates with the site's design system:
css
.figma-embed-container {
margin: 1.5rem 0;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
background: rgba(255, 255, 255, 0.02);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.figma-embed-footer {
padding: 0.75rem 1rem;
background: rgba(255, 255, 255, 0.05);
border-top: 1px solid rgba(255, 255, 255, 0.1);
font-size: 0.75rem;
color: #9ca3af;
text-align: right;
}
Environment Variables and Authentication
The component supports flexible authentication patterns:
- User-specific tokens:
FIGMA_{USER}_TOKEN
(e.g.,FIGMA_MPSTATON_TOKEN
) - Default token:
FIGMA_EMBED_USER_TOKEN
orFIGMA_DEFAULT_TOKEN
- Token usage: Only used for API metadata fetching, not required for basic embeds
Summary of Key Files
- astro.config.mjs: Configures remark plugins in the correct order
- remark-directives.ts: Defines directive-to-component mapping and preservation logic
- OneArticle.astro: Layout that processes markdown through the plugin pipeline
- OneArticleOnPage.astro: Article component that passes content to AstroMarkdown
- AstroMarkdown.astro: Core renderer that handles directive nodes and renders components
- Figma-Object--Display.astro: Figma embed component with smart URL parsing and metadata fetching
- ToolShowcaseIsland.astro: Server island component for rendering tool carousels from backlink lists
Future Work
- Tool Showcase Directive: Successfully implemented container directive for rendering tool carousels from backlink lists
- Add support for directives in the Obsidian native markdown editor, which requires a different way of handling styles for the custom components and will not be in Astro.
- Implement additional directive types following the established patterns (Miro, Notion, etc.)
- Add support for directive transformations during build time for better performance
- Create a directive preview mode for the development environment
- Extend tool-showcase directive to support additional content collection types
- Add filtering and sorting capabilities to tool carousel components