Utilities Reference

Helper functions and utilities for EmberDocs development.

Content Parsing

parseMarkdown()

Parse markdown file content with YAML frontmatter.

typescript
function parseMarkdown(
  input: string,
  options?: ParseOptions
): ContentData

interface ParseOptions {
  generateToc?: boolean;        // Default: true
  strictFrontmatter?: boolean;  // Default: false
}

interface ContentData {
  frontmatter: Record<string, any>;
  body: string;
  toc: TocEntry[];
}

Example:

typescript
import { parseMarkdown } from '@/lib/content';

const markdown = `---
title: "My Doc"
slug: "my-doc"
---

# Hello

This is content.`;

const parsed = parseMarkdown(markdown);
console.log(parsed.frontmatter.title); // "My Doc"
console.log(parsed.body); // "# Hello\n\nThis is content."
console.log(parsed.toc); // Array of headings

// Parse without TOC generation
const parsedNoToc = parseMarkdown(markdown, { generateToc: false });

generateSlug()

Generate URL-safe slug from text (used internally for heading IDs).

typescript
function generateSlug(text: string): string

Example:

typescript
import { generateSlug } from '@/lib/content';

generateSlug('Getting Started'); // "getting-started"
generateSlug('API & Reference'); // "api-and-reference"
generateSlug('Hello World!'); // "hello-world"

Behavior:

  • Converts to lowercase
  • Replaces & with and
  • Removes special characters
  • Replaces spaces with hyphens
  • Trims leading/trailing hyphens

Search Functions

buildSearchIndex()

Generate full-text search index from document directory.

typescript
async function buildSearchIndex(docsPath: string): Promise<SearchIndex>

Example:

typescript
import { buildSearchIndex, saveIndexToFile } from '@/lib/search';

const index = await buildSearchIndex('./docs');
await saveIndexToFile('./public/search-index.json', index);

Returns:

typescript
interface SearchIndex {
  documents: Record<string, SearchDocument>;
  index?: any; // FlexSearch index (built client-side)
  metadata: {
    buildTime: number;
    documentCount: number;
    indexedWords: number;
  };
}

querySearchIndex()

Search pre-built index with natural language query. Note: This is a server-side fallback. Client-side search uses FlexSearch directly.

typescript
function querySearchIndex(
  index: SearchIndex,
  documents: Record<string, SearchDocument>,
  query: string,
  limit?: number
): SearchResult[]

Example:

typescript
import { querySearchIndex, buildSearchIndex } from '@/lib/search';

// Build index
const searchIndex = await buildSearchIndex('./docs');

// Search
const results = querySearchIndex(searchIndex, searchIndex.documents, "authentication", 10);

results.forEach(result => {
  console.log(result.title);    // Document title
  console.log(result.excerpt);  // Preview text
  console.log(result.path);     // URL path
  console.log(result.score);    // Relevance score
});

serializeIndex()

Serialize search index to JSON string.

typescript
function serializeIndex(index: SearchIndex): string

deserializeIndex()

Convert JSON serialized index back to SearchIndex format.

typescript
function deserializeIndex(serialized: string): SearchIndex

Example:

typescript
import { serializeIndex, deserializeIndex, buildSearchIndex } from '@/lib/search';

// Build and serialize
const index = await buildSearchIndex('./docs');
const json = serializeIndex(index);

// Later, deserialize
const restored = deserializeIndex(json);

saveIndexToFile()

Write search index to JSON file.

typescript
async function saveIndexToFile(
  filePath: string,
  index: SearchIndex
): Promise<void>

discoverDocuments()

Discover all markdown documents from a directory structure.

typescript
function discoverDocuments(
  rootPath: string,
  currentPath?: string,
  parentId?: string | null
): NavigationNode[]

Example:

typescript
import { discoverDocuments, buildNavigationTree } from '@/lib/navigation';

// Discover all documents
const nodes = discoverDocuments('./docs/examples');

// Build hierarchical tree
const { roots, nodeMap } = buildNavigationTree(nodes);

// Access nested document
const deepDoc = nodeMap['deep/nesting/many/levels/deep-doc'];
console.log(deepDoc.title); // Document title from frontmatter

buildNavigationTree()

Build hierarchical tree structure from flat node list.

typescript
function buildNavigationTree(nodes: NavigationNode[]): NavigationStructure

Returns:

typescript
interface NavigationStructure {
  roots: NavigationNode[];
  nodeMap: Record<string, NavigationNode>;
  versions: Version[];
  currentVersion?: string;
}

getBreadcrumbs()

Generate breadcrumb trail for current path.

typescript
function getBreadcrumbs(
  structure: NavigationStructure,
  pathname: string
): BreadcrumbItem[]

Example:

typescript
import { discoverDocuments, buildNavigationTree, getBreadcrumbs } from '@/lib/navigation';

const nodes = discoverDocuments('./docs');
const nav = buildNavigationTree(nodes);
const breadcrumbs = getBreadcrumbs(nav, '/docs/guides/advanced-features');

// Returns:
// [
//   { href: '/docs', label: 'Docs' },
//   { href: '/docs/guides', label: 'Guides' },
//   { href: '/docs/guides/advanced-features', label: 'Advanced Features' }
// ]

findNodeByPath()

Find navigation node by pathname.

typescript
function findNodeByPath(
  structure: NavigationStructure,
  pathname: string
): NavigationNode | undefined

detectVersions()

Detect Git tag versions for documentation versioning.

typescript
function detectVersions(baseDir?: string): Version[]

Returns:

typescript
interface Version {
  tag: string;
  label: string;
  isCurrent: boolean;
}

Example:

typescript
import { detectVersions } from '@/lib/navigation';

const versions = detectVersions();
// Returns array of Git tag versions
// Example: [{ tag: 'v1.0.0', label: 'v1.0.0', isCurrent: true }]
// Falls back to [{ tag: 'main', label: 'Latest', isCurrent: true }] if no tags

generateNavigation()

Complete navigation generation (discovers documents, builds tree, detects versions).

typescript
function generateNavigation(
  docsPath?: string,
  baseDir?: string
): NavigationStructure

Example:

typescript
import { generateNavigation } from '@/lib/navigation';

const nav = generateNavigation('./docs/examples');
console.log(nav.roots); // Root navigation nodes
console.log(nav.nodeMap); // All nodes by ID
console.log(nav.versions); // Git tag versions

flattenNavigation()

Convert navigation tree to flat list of all documents.

typescript
function flattenNavigation(nodes: NavigationNode[]): NavDocument[]

Returns:

typescript
interface NavDocument {
  title: string;
  path: string;
}

Example:

typescript
import { flattenNavigation } from '@/lib/nav-helpers';

const flat = flattenNavigation(nav.roots);
flat.forEach(doc => {
  console.log(`${doc.title} -> ${doc.path}`);
});

Type Definitions

Document Types

typescript
interface DocumentMetadata {
  title: string;
  slug: string;
  author?: string;
  date?: string;
  tags?: string[];
  published?: boolean;
  order?: number;
}

interface ContentData {
  frontmatter: Record<string, any>;
  body: string;
  toc: TocEntry[];
}

Search Types

typescript
interface SearchDocument {
  id: string;
  title: string;
  path: string;
  body: string;
  excerpt?: string;
  tags?: string[];
  headings?: string[];
}

interface SearchResult {
  id: string;
  title: string;
  path: string;
  excerpt: string;
  score: number;
  tags?: string[];
}

interface SearchIndex {
  documents: Record<string, SearchDocument>;
  index?: any; // FlexSearch index (built client-side)
  metadata: {
    buildTime: number;
    documentCount: number;
    indexedWords?: number;
  };
}
typescript
interface NavigationNode {
  id: string;
  title: string;
  path: string;
  type?: 'folder' | 'doc';
  parentId?: string | null;
  children?: NavigationNode[];
  frontmatter?: DocumentMetadata;
}

interface NavigationStructure {
  roots: NavigationNode[];
  nodeMap: Record<string, NavigationNode>;
  versions: Version[];
  currentVersion?: string;
}

interface Version {
  tag: string;
  label: string;
  isCurrent: boolean;
}

interface BreadcrumbItem {
  href: string;
  label: string;
}

getPrevNextDocs()

Get previous and next documents in navigation order.

typescript
function getPrevNextDocs(
  currentPath: string,
  navNodes: NavigationNode[]
): { prev?: NavDocument; next?: NavDocument }

Example:

typescript
import { getPrevNextDocs } from '@/lib/nav-helpers';

const { prev, next } = getPrevNextDocs('/docs/guides/advanced', nav.roots);
if (prev) console.log('Previous:', prev.title);
if (next) console.log('Next:', next.title);

Markdown Utilities

highlightCode()

Syntax highlight code block using Shiki.

typescript
async function highlightCode(code: string, language: string): Promise<string>

Example:

typescript
import { highlightCode } from '@/lib/highlight';

const html = await highlightCode('const x = 5;', 'typescript');
// Returns HTML with syntax highlighting

Note: This is used internally by the CodeBlock component. For most use cases, you'll use the CodeBlock component directly.

extractExcerpt()

Extract excerpt from text with query highlighting.

typescript
function extractExcerpt(
  text: string,
  query?: string,
  contextWords?: number
): string

Example:

typescript
import { extractExcerpt } from '@/lib/search';

const excerpt = extractExcerpt(longText, 'authentication', 2);
// Returns excerpt with query highlighted

flattenToc()

Flatten nested TOC structure to flat array.

typescript
function flattenToc(toc: TocEntry[]): TocEntry[]

Example:

typescript
import { parseMarkdown, flattenToc } from '@/lib/content';

const parsed = parseMarkdown(markdown);
const flatToc = flattenToc(parsed.toc);
// Returns all headings in flat array (no nesting)

Next Steps