UNPKG

cascade-cards-source-markdown

Version:

Cascade Cards Markdown data source adapter

212 lines (210 loc) 7.5 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { MarkdownSource: () => MarkdownSource, markdownSource: () => markdownSource }); module.exports = __toCommonJS(index_exports); // src/markdown-source.ts var import_promises = __toESM(require("fs/promises"), 1); var import_path = __toESM(require("path"), 1); var import_gray_matter = __toESM(require("gray-matter"), 1); var import_remark = require("remark"); var import_remark_html = __toESM(require("remark-html"), 1); var import_fast_glob = __toESM(require("fast-glob"), 1); var MarkdownSource = class { name = "markdown"; options; cache = /* @__PURE__ */ new Map(); fileMap = /* @__PURE__ */ new Map(); // term -> filePath initialized = false; constructor(options) { this.options = { baseDir: process.cwd(), cache: true, termResolver: (term) => this.defaultTermResolver(term), extractLinks: true, ...options }; } async resolve(term) { await this.ensureInitialized(); const filePath = this.options.termResolver(term); if (!filePath) { return null; } if (this.options.cache && this.cache.has(filePath)) { const cached = this.cache.get(filePath); return this.toDataSourceContent(cached); } try { const parsed = await this.parseMarkdownFile(filePath); if (this.options.cache) { this.cache.set(filePath, parsed); } return this.toDataSourceContent(parsed); } catch (error) { console.warn(`Failed to load markdown file for term "${term}":`, error); return null; } } async ensureInitialized() { if (this.initialized) return; try { const files = await (0, import_fast_glob.default)(this.options.glob, { cwd: this.options.baseDir, absolute: true }); for (const filePath of files) { const relativePath = import_path.default.relative(this.options.baseDir, filePath); const slug = this.pathToSlug(relativePath); this.fileMap.set(slug, filePath); const basename = import_path.default.basename(filePath, import_path.default.extname(filePath)); this.fileMap.set(basename.toLowerCase(), filePath); } this.initialized = true; } catch (error) { console.error("Failed to initialize markdown source:", error); throw error; } } defaultTermResolver(term) { const normalizedTerm = term.toLowerCase(); if (this.fileMap.has(normalizedTerm)) { return this.fileMap.get(normalizedTerm); } const variations = [ normalizedTerm.replace(/\s+/g, "-"), normalizedTerm.replace(/\s+/g, "_"), normalizedTerm.replace(/[^a-z0-9]/g, ""), normalizedTerm.replace(/[^a-z0-9]/g, "-") ]; for (const variation of variations) { if (this.fileMap.has(variation)) { return this.fileMap.get(variation); } } return null; } pathToSlug(relativePath) { return relativePath.replace(/\.md$/, "").replace(/\\/g, "/").toLowerCase(); } async parseMarkdownFile(filePath) { const content = await import_promises.default.readFile(filePath, "utf-8"); const { data: frontmatter, content: markdownContent } = (0, import_gray_matter.default)(content); const processor = (0, import_remark.remark)().use(import_remark_html.default, { sanitize: false }); const result = await processor.process(markdownContent); const html = result.toString(); const title = frontmatter.title || this.extractTitleFromMarkdown(markdownContent) || import_path.default.basename(filePath, import_path.default.extname(filePath)); const links = []; if (this.options.extractLinks) { links.push(...this.extractLinksFromContent(markdownContent, frontmatter)); } const slug = this.pathToSlug(import_path.default.relative(this.options.baseDir, filePath)); return { title, content: markdownContent, html, frontmatter, filePath, slug, links }; } extractTitleFromMarkdown(content) { const match = content.match(/^#\s+(.+)$/m); return match ? match[1].trim() : null; } extractLinksFromContent(content, frontmatter) { const links = []; if (frontmatter.related && Array.isArray(frontmatter.related)) { links.push(...frontmatter.related.map( (item) => typeof item === "string" ? { term: item } : { term: item.term, label: item.label } )); } if (frontmatter.tags && Array.isArray(frontmatter.tags)) { links.push(...frontmatter.tags.map((tag) => ({ term: tag }))); } const linkRegex = /\[([^\]]+)\]\(([^)]+)\)/g; let match; while ((match = linkRegex.exec(content)) !== null) { const [, label, href] = match; if (!href.startsWith("http") && !href.startsWith("mailto:")) { const term = href.replace(/\.md$/, "").replace(/^\//, ""); links.push({ term, label }); } } const wikiLinkRegex = /\[\[([^\]]+)\]\]/g; while ((match = wikiLinkRegex.exec(content)) !== null) { const reference = match[1]; const [term, label] = reference.includes("|") ? reference.split("|", 2) : [reference, void 0]; links.push({ term: term.trim(), label: label?.trim() }); } return links; } toDataSourceContent(parsed) { return { title: parsed.title, html: parsed.html, markdown: parsed.content, links: parsed.links, meta: { ...parsed.frontmatter, filePath: parsed.filePath, slug: parsed.slug } }; } // Utility methods for external use async getAllTerms() { await this.ensureInitialized(); return Array.from(this.fileMap.keys()); } async clearCache() { this.cache.clear(); } async reloadFiles() { this.initialized = false; this.cache.clear(); this.fileMap.clear(); await this.ensureInitialized(); } }; function markdownSource(options) { return new MarkdownSource(options); } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { MarkdownSource, markdownSource }); //# sourceMappingURL=index.cjs.map