1import {PluginOptions, Guide} from './types'; 2import {LoadContext} from '@docusaurus/types'; 3 4import _ from 'lodash'; 5import fs from 'fs-extra'; 6import globby from 'globby'; 7import humanizeString from 'humanize-string'; 8import path from 'path'; 9import {parse, normalizeUrl, aliasedSitePath} from '@docusaurus/utils'; 10import readingTime from 'reading-time'; 11import titleize from 'titleize'; 12 13export function truncate(fileString: string, truncateMarker: RegExp) { 14 return fileString.split(truncateMarker, 1).shift()!; 15} 16 17export async function generateGuides( 18 guideDir: string, 19 {siteConfig, siteDir}: LoadContext, 20 options: PluginOptions, 21) { 22 const {include, routeBasePath, truncateMarker} = options; 23 24 if (!fs.existsSync(guideDir)) { 25 return []; 26 } 27 28 const {baseUrl = ''} = siteConfig; 29 const guideFiles = await globby(include, { 30 cwd: guideDir, 31 }); 32 33 const guides: Guide[] = []; 34 35 await Promise.all( 36 guideFiles.map(async (relativeSource: string) => { 37 const source = path.join(guideDir, relativeSource); 38 const aliasedSource = aliasedSitePath(source, siteDir); 39 const fileString = await fs.readFile(source, 'utf-8'); 40 const readingStats = readingTime(fileString); 41 const {frontMatter, content, excerpt} = parse(fileString); 42 43 if (frontMatter.draft && process.env.NODE_ENV === 'production') { 44 return; 45 } 46 47 let categoryParts = relativeSource.split('/').slice(0, -1); 48 let categories = []; 49 50 while (categoryParts.length > 0) { 51 let name = categoryParts[categoryParts.length - 1]; 52 let title = titleize(humanizeString(name)); 53 54 let description = null; 55 56 switch(name) { 57 case 'advanced': 58 description = 'Go beyond the basics, become a Vector pro, and extract the full potential of Vector.'; 59 break; 60 61 case 'getting-started': 62 description = 'Take Vector from zero to production in under 10 minutes.'; 63 break; 64 65 case 'integrate': 66 description = 'Simple step-by-step integration guides.' 67 break; 68 } 69 70 categories.unshift({ 71 name: name, 72 title: title, 73 description: description, 74 permalink: normalizeUrl([baseUrl, routeBasePath, categoryParts.join('/')]) 75 }); 76 categoryParts.pop(); 77 } 78 79 let linkName = relativeSource.replace(/\.mdx?$/, ''); 80 let seriesPosition = frontMatter.series_position; 81 let tags = frontMatter.tags || []; 82 let title = frontMatter.title || linkName; 83 let coverLabel = frontMatter.cover_label || title; 84 85 guides.push({ 86 id: frontMatter.id || frontMatter.title, 87 metadata: { 88 categories: categories, 89 coverLabel: coverLabel, 90 description: frontMatter.description || excerpt, 91 permalink: normalizeUrl([ 92 baseUrl, 93 routeBasePath, 94 frontMatter.id || linkName, 95 ]), 96 readingTime: readingStats.text, 97 seriesPosition: seriesPosition, 98 sort: frontMatter.sort, 99 source: aliasedSource, 100 tags: tags, 101 title: title, 102 truncated: truncateMarker?.test(content) || false, 103 }, 104 }); 105 }), 106 ); 107 108 return _.sortBy(guides, [ 109 ((guide) => { 110 let categories = guide.metadata.categories; 111 112 if (categories[0].name == 'getting-started') { 113 return ['AA'].concat(categories.map(category => category.name).slice(1)); 114 } else { 115 return categories; 116 } 117 }), 118 'metadata.seriesPosition', 119 ((guide) => guide.metadata.coverLabel.toLowerCase()) 120 ]); 121} 122 123export function linkify( 124 fileContent: string, 125 siteDir: string, 126 guidePath: string, 127 guides: Guide[], 128) { 129 let fencedBlock = false; 130 const lines = fileContent.split('\n').map(line => { 131 if (line.trim().startsWith('```')) { 132 fencedBlock = !fencedBlock; 133 } 134 135 if (fencedBlock) return line; 136 137 let modifiedLine = line; 138 const mdRegex = /(?:(?:\]\()|(?:\]:\s?))(?!https)([^'")\]\s>]+\.mdx?)/g; 139 let mdMatch = mdRegex.exec(modifiedLine); 140 141 while (mdMatch !== null) { 142 const mdLink = mdMatch[1]; 143 const aliasedPostSource = `@site/${path.relative( 144 siteDir, 145 path.resolve(guidePath, mdLink), 146 )}`; 147 let guidePermalink = null; 148 149 guides.forEach(guide => { 150 if (guide.metadata.source === aliasedPostSource) { 151 guidePermalink = guide.metadata.permalink; 152 } 153 }); 154 155 if (guidePermalink) { 156 modifiedLine = modifiedLine.replace(mdLink, guidePermalink); 157 } 158 159 mdMatch = mdRegex.exec(modifiedLine); 160 } 161 162 return modifiedLine; 163 }); 164 165 return lines.join('\n'); 166} 167