/** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ import fs from 'fs-extra'; import globby from 'globby'; import path from 'path'; import { parseMarkdownFile, normalizeUrl, aliasedSitePath, } from '@docusaurus/utils'; import {LoadContext, PluginOptions} from '@docusaurus/types'; import dedent from 'dedent'; import slugify from 'speakingurl'; import {CourseConfig, CourseLesson, CourseModuleConfig} from './types'; export function truncate(fileString: string, truncateMarker: RegExp) { return fileString.split(truncateMarker, 1).shift()!; } export async function generateCourseLessons( courseSettings: CourseConfig, {siteConfig, siteDir}: LoadContext, ) { const courseDir = path.resolve(siteDir, 'course'); if (!fs.existsSync(courseDir)) { return []; } const {baseUrl = ''} = siteConfig; const courseFiles = await globby( [ '**/*.{md,mdx}', '!*/*/{public,protected,code}/**/*.{md,mdx}', '!{public,protected,code}/**/*.{md,mdx}', // ignore top-level '!*/{public,protected,code}/**/*.{md,mdx}', // ignore module-level ], { cwd: courseDir, }, ); const coursePosts: CourseLesson[] = []; let lastModule: null | string = null; let moduleIdx = -1; await Promise.all( courseFiles.map(async (relativeSource: string, idx: number) => { const source = path.join(courseDir, relativeSource); const aliasedSource = aliasedSitePath(source, siteDir); const refDir = path.parse(courseDir).dir; const relativePath = path.relative(refDir, source); const courseFileName = path.basename(relativeSource); const moduleFolder = path.resolve(path.join(path.dirname(source), '..')); const moduleFolderBase = path.basename(moduleFolder); const moduleConfigPath = path.join(moduleFolder, 'config.js'); const lessonFolderBase = path.basename(path.dirname(relativeSource)); // count modules if (lastModule != moduleFolderBase) { lastModule = moduleFolderBase; moduleIdx += 1; } // const fileString = await fs.readFile(source, 'utf-8'); const {frontMatter, content, excerpt} = await parseMarkdownFile(source); let moduleSettings: CourseModuleConfig = {title: 'n/a'}; if (fs.existsSync(moduleConfigPath)) { moduleSettings = require(moduleConfigPath)({dedent}); } moduleSettings.order = moduleIdx; let date; // Extract date and title from filename. let linkName = courseFileName.replace(/\.mdx?$/, ''); // Prefer user-defined date. if (frontMatter.date) { date = new Date(frontMatter.date as string); } // Use file create time for Course. date = date || (await fs.stat(source)).birthtime; frontMatter.title = frontMatter.title || linkName; try { const slug = (frontMatter.slug as string) || slugify(frontMatter.title as string); const permalink = normalizeUrl([baseUrl, 'courses', courseSettings.permalink, slug]) + '/'; coursePosts.push({ id: (frontMatter.id || frontMatter.title) as string, metadata: { permalink, source: aliasedSource, description: (frontMatter.description as string) || excerpt!, date, title: frontMatter.title as string, privateVideoUrl: frontMatter.privateVideoUrl as string, courseSlug: courseSettings.slug, moduleSlug: moduleSettings.slug || '', moduleFolder: moduleFolderBase, lessonFolder: lessonFolderBase, module: moduleSettings, order: idx, }, }); } catch (err) { console.log('WARNING: Error processing. Skipping: ', source, err); // throw err; } }), ); coursePosts.sort((a, b) => a.metadata.source.localeCompare(b.metadata.source), ); return coursePosts; }