Skip to content

Commit

Permalink
Refactor api.data.ts: Add comments and improve code clarity
Browse files Browse the repository at this point in the history
- Added comments to explain the purpose and functionality of key functions.
- Clarified the logic behind header parsing, slug generation, and file caching.
- Documented the use of regular expressions for header cleaning and anchor extraction.
- Improved readability and maintainability of the codebase.
  • Loading branch information
anjiri1684 authored Nov 7, 2024
1 parent f7a57b8 commit 9bb7209
Showing 1 changed file with 88 additions and 58 deletions.
146 changes: 88 additions & 58 deletions src/api/api.data.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
// api.data.ts
// a file ending with data.(j|t)s will be evaluated in Node.js
import fs from 'fs'
import path from 'path'
import type { MultiSidebarConfig } from '@vue/theme/src/vitepress/config.ts'
import { sidebar } from '../../.vitepress/config'

// Interface defining the structure of a single header in the API
interface APIHeader {
anchor: string
text: string
}

// Interface defining the structure of an API group with text, anchor, and items
export interface APIGroup {
text: string
anchor: string
Expand All @@ -20,79 +21,108 @@ export interface APIGroup {
}[]
}

// declare resolved data type
// Declare the resolved data type for API groups
export declare const data: APIGroup[]

export default {
// declare files that should trigger HMR
watch: './*.md',
// read from fs and generate the data
load(): APIGroup[] {
return (sidebar as MultiSidebarConfig)['/api/'].map((group) => ({
text: group.text,
anchor: slugify(group.text),
items: group.items.map((item) => ({
...item,
headers: parsePageHeaders(item.link)
}))
}))
}
// Utility function to generate a slug from a string (used for anchor links)
function slugify(text: string): string {
return (
text
// Replace special characters and spaces with hyphens
.replace(/[\s~`!@#$%^&*()\-_+=[\]{}|\\;:"'<>,.?/]+/g, '-')
// Remove continuous separators
.replace(/-{2,}/g, '-')
// Remove leading/trailing hyphens
.replace(/^-+|-+$/g, '')
// Ensure it doesn't start with a number (e.g. #121)
.replace(/^(\d)/, '_$1')
// Convert to lowercase
.toLowerCase()
)
}

const headersCache = new Map<
string,
{
headers: APIHeader[]
timestamp: number
}
>()

function parsePageHeaders(link: string) {
const fullPath = path.join(__dirname, '../', link) + '.md'
const timestamp = fs.statSync(fullPath).mtimeMs
// Utility function to parse headers from a markdown file at a given link
function parsePageHeaders(link: string): APIHeader[] {
const fullPath = path.join(__dirname, '../', link) + '.md' // Resolve the full file path
const timestamp = fs.statSync(fullPath).mtimeMs // Get the last modified timestamp of the file

// Check if the file is cached and if its timestamp matches
const cached = headersCache.get(fullPath)
if (cached && timestamp === cached.timestamp) {
return cached.headers
return cached.headers // Return cached headers if they're up-to-date
}

const src = fs.readFileSync(fullPath, 'utf-8')
const h2s = src.match(/^## [^\n]+/gm)
const src = fs.readFileSync(fullPath, 'utf-8') // Read the markdown file
const headers = extractHeadersFromMarkdown(src) // Extract headers from the file content

// Store the extracted headers along with the file's timestamp in the cache
headersCache.set(fullPath, {
timestamp,
headers
})

return headers
}

// Helper function to extract all headers (h2) from markdown content
function extractHeadersFromMarkdown(src: string): APIHeader[] {
const h2s = src.match(/^## [^\n]+/gm) // Match all h2 headers (## header)
const anchorRE = /\{#([^}]+)\}/ // Regular expression to match the anchor link in header (e.g. {#some-anchor})
let headers: APIHeader[] = []

if (h2s) {
const anchorRE = /\{#([^}]+)\}/
// Process each h2 header and extract text and anchor
headers = h2s.map((h) => {
const text = h
.slice(2)
.replace(/<sup class=.*/, '')
.replace(/\\</g, '<')
.replace(/`([^`]+)`/g, '$1')
.replace(anchorRE, '') // hidden anchor tag
.trim()
const anchor = h.match(anchorRE)?.[1] ?? slugify(text)
const text = cleanHeaderText(h, anchorRE) // Clean up header text
const anchor = extractAnchor(h, anchorRE, text) // Extract or generate anchor
return { text, anchor }
})
}
headersCache.set(fullPath, {
timestamp,
headers
})

return headers
}

// same as vitepress' slugify logic
function slugify(text: string): string {
return (
text
// Replace special characters
.replace(/[\s~`!@#$%^&*()\-_+=[\]{}|\\;:"'<>,.?/]+/g, '-')
// Remove continuous separators
.replace(/-{2,}/g, '-')
// Remove prefixing and trailing separators
.replace(/^-+|-+$/g, '')
// ensure it doesn't start with a number (#121)
.replace(/^(\d)/, '_$1')
// lowercase
.toLowerCase()
)
// Helper function to clean up header text (e.g., remove superscript, code formatting)
function cleanHeaderText(h: string, anchorRE: RegExp): string {
return h
.slice(2) // Remove the "##" part of the header
.replace(/<sup class=.*/, '') // Remove superscript (e.g., <sup> tags)
.replace(/\\</g, '<') // Decode escaped characters like \<
.replace(/`([^`]+)`/g, '$1') // Remove inline code formatting (e.g., `code`)
.replace(anchorRE, '') // Remove anchor tags (e.g., {#anchor})
.trim() // Trim leading/trailing whitespace
}

// Helper function to extract the anchor link from a header (or generate one if missing)
function extractAnchor(h: string, anchorRE: RegExp, text: string): string {
const anchorMatch = h.match(anchorRE) // Match anchor if it exists
return anchorMatch?.[1] ?? slugify(text) // If no anchor, generate one using slugify
}

// Cache for storing headers and their associated timestamps to avoid re-reading files
const headersCache = new Map<
string,
{
headers: APIHeader[]
timestamp: number
}
>()

// Main export function for loading the API data
export default {
// Declare files that should trigger Hot Module Replacement (HMR)
watch: './*.md',

// Load API data and process sidebar items
load(): APIGroup[] {
// Generate the API group data by processing the sidebar configuration
return (sidebar as MultiSidebarConfig)['/api/'].map((group) => ({
text: group.text, // Text of the group (e.g., 'API')
anchor: slugify(group.text), // Generate anchor for the group title
items: group.items.map((item) => ({
...item, // Spread the original item properties
headers: parsePageHeaders(item.link), // Parse the headers from the item's markdown link
}))
}))
}
}

0 comments on commit 9bb7209

Please sign in to comment.