CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-nuxt--content

File-based content management system for Nuxt.js applications with powerful querying and Vue component rendering in Markdown

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

rendering.mddocs/

Content Rendering

Vue component system for rendering content with MDC (Markdown Components) support, custom component mapping, and prose styling. Enables rich content experiences with Vue components embedded in Markdown.

Capabilities

ContentRenderer Component

Primary component for rendering parsed content with full MDC support and customization options.

interface ContentRendererProps {
  /** Content object to render (required) */
  value: object;
  /** Render only excerpt portion of content */
  excerpt?: boolean;
  /** Root HTML tag for wrapper element */
  tag?: string;
  /** Custom component mapping for MDC rendering */
  components?: Record<string, Component>;
  /** Additional data passed to rendered components */
  data?: Record<string, unknown>;
  /** Use prose components instead of plain HTML tags */
  prose?: boolean;
  /** CSS classes for root element */
  class?: string | Record<string, boolean>;
  /** Tags to unwrap from rendered content */
  unwrap?: boolean | string;
}

interface ContentRendererSlots {
  /** Rendered when content value is empty or null */
  empty(): VNode[];
}

Usage Examples:

<template>
  <!-- Basic content rendering -->
  <ContentRenderer :value="article" />

  <!-- Render only excerpt -->
  <ContentRenderer :value="article" excerpt />

  <!-- Custom wrapper tag -->
  <ContentRenderer :value="article" tag="article" />

  <!-- Custom components for MDC -->
  <ContentRenderer 
    :value="article" 
    :components="{ 
      'custom-alert': AlertComponent,
      'code-block': CodeBlockComponent 
    }" 
  />

  <!-- Pass additional data to components -->
  <ContentRenderer 
    :value="article" 
    :data="{ author: currentUser, theme: 'dark' }" 
  />

  <!-- Use prose styling -->
  <ContentRenderer :value="article" prose class="prose-lg" />

  <!-- Handle empty content -->
  <ContentRenderer :value="article">
    <template #empty>
      <p>No content available</p>
    </template>
  </ContentRenderer>

  <!-- Unwrap specific tags -->
  <ContentRenderer :value="article" unwrap="p" />
</template>

<script setup>
import AlertComponent from '~/components/Alert.vue';
import CodeBlockComponent from '~/components/CodeBlock.vue';

const { data: article } = await queryCollection('articles')
  .path(useRoute().path)
  .first();
</script>

ContentPreviewMode Component

Component for enabling live content editing and preview functionality.

interface ContentPreviewModeProps {
  /** Authentication token for preview mode (required) */
  previewToken: string;
  /** API endpoint URL for preview data (required) */
  api: string;
  /** Preview initialization callback (required) */
  initializePreview: (preview: PreviewInstance) => void;
}

interface PreviewInstance {
  /** Enable preview mode */
  enable(): void;
  /** Disable preview mode */
  disable(): void;
  /** Check if preview is active */
  isActive(): boolean;
  /** Refresh preview data */
  refresh(): Promise<void>;
}

Usage Examples:

<template>
  <div>
    <!-- Content with preview mode -->
    <ContentRenderer :value="content" />
    
    <!-- Preview mode component -->
    <ContentPreviewMode
      :preview-token="previewToken"
      :api="previewApi"
      :initialize-preview="handlePreviewInit"
    />
  </div>
</template>

<script setup>
const previewToken = useCookie('preview-token');
const previewApi = '/api/preview';

let previewInstance: PreviewInstance;

const handlePreviewInit = (preview: PreviewInstance) => {
  previewInstance = preview;
  
  // Auto-enable preview if token exists
  if (previewToken.value) {
    preview.enable();
  }
};

// Toggle preview mode
const togglePreview = () => {
  if (previewInstance.isActive()) {
    previewInstance.disable();
  } else {
    previewInstance.enable();
  }
};
</script>

MDC Component Integration

Custom Component Registration

// nuxt.config.ts
export default defineNuxtConfig({
  content: {
    renderer: {
      // Global component mapping
      components: {
        'alert': '~/components/Alert.vue',
        'code-sandbox': '~/components/CodeSandbox.vue',
        'video-player': '~/components/VideoPlayer.vue'
      }
    }
  }
});

Component Usage in Markdown

<!-- In your .md files -->

# My Article

Regular markdown content here.

::alert{type="warning"}
This is a custom alert component with props!
::

::code-sandbox{id="vue-example"}
::

::video-player{src="/videos/demo.mp4" autoplay}
::

Custom Component Definition

<!-- components/Alert.vue -->
<template>
  <div :class="alertClasses">
    <icon :name="iconName" />
    <div class="alert-content">
      <slot />
    </div>
  </div>
</template>

<script setup>
interface Props {
  type?: 'info' | 'warning' | 'error' | 'success';
  title?: string;
}

const props = withDefaults(defineProps<Props>(), {
  type: 'info'
});

const alertClasses = computed(() => [
  'alert',
  `alert-${props.type}`
]);

const iconName = computed(() => {
  const icons = {
    info: 'information-circle',
    warning: 'exclamation-triangle',
    error: 'x-circle',
    success: 'check-circle'
  };
  return icons[props.type];
});
</script>

Tree Processing

Low-level utilities for working with content AST and tree structures.

/**
 * Compresses MDC tree to minimark format for storage/transport
 * @param input - MDC AST root node
 * @returns Compressed minimark tree
 */
function compressTree(input: MDCRoot): MinimarkTree;

/**
 * Decompresses minimark tree back to MDC format
 * @param input - Compressed tree data
 * @returns Full MDC AST root node
 */
function decompressTree(input: Tree): MDCRoot;

/**
 * Tree traversal utility for AST manipulation
 * @param tree - Tree to traverse
 * @param checker - Function to test if node should be visited
 * @param visitor - Function to transform matching nodes
 */
function visit(
  tree: Tree,
  checker: (node: Node) => boolean,
  visitor: (node: Node) => Node | undefined
): void;

Usage Examples:

import { compressTree, decompressTree, visit } from '@nuxt/content/runtime';

// Compress content for storage
const compressed = compressTree(content.body);

// Decompress for rendering
const fullTree = decompressTree(compressed);

// Transform nodes in tree
visit(tree, 
  (node) => node.type === 'element' && node.tag === 'img',
  (node) => {
    // Add lazy loading to images
    if (node.props) {
      node.props.loading = 'lazy';
    }
    return node;
  }
);

Types

interface MDCRoot {
  type: 'root';
  children: MDCNode[];
}

interface MDCNode {
  type: string;
  tag?: string;
  props?: Record<string, unknown>;
  children?: MDCNode[];
  value?: string;
}

interface MinimarkTree {
  type: 'root';
  children: MinimarkNode[];
}

interface MinimarkNode {
  type: string;
  tag?: string;
  props?: Record<string, unknown>;
  children?: MinimarkNode[];
  value?: string;
}

type Tree = MDCRoot | MinimarkTree;
type Node = MDCNode | MinimarkNode;

interface Component {
  name: string;
  props?: Record<string, unknown>;
  slots?: Record<string, () => VNode[]>;
}

Install with Tessl CLI

npx tessl i tessl/npm-nuxt--content

docs

collections.md

configuration.md

index.md

navigation-utilities.md

navigation.md

preview.md

querying.md

rendering.md

runtime-utilities.md

tile.json