diff --git a/config/authors.mjs b/config/authors.mjs index 7d168a61cb4..4708619dd5f 100644 --- a/config/authors.mjs +++ b/config/authors.mjs @@ -1,6 +1,48 @@ +/* + * A list of (documentation) authors. + * The ID is the (future v3) FreeSewing user ID (zero for now) + * The name is what we'll use as display name + */ export const authors = { - joostdecock: { - id: 1, - name: 'Joost De Cock', - }, + joostdecock: { id: 0, name: 'Joost De Cock' }, + benjamesben: { id: 0, name: 'Benjamin' }, + nikhil: { id: 0, name: 'nikhil' }, + jackseye: { id: 0, name: 'jackseye' }, + 'Annie Kao': { id: 0, name: 'Annie Kao' }, + Bart: { id: 0, name: 'Bart' }, + 'Enoch Riese': { id: 0, name: 'Enoch Riese' }, + Zee: { id: 0, name: 'Zee' }, + 'James Bradbury': { id: 0, name: 'James Bradbury' }, + jgfichte: { id: 0, name: 'jgfichte' }, + Tríona: { id: 0, name: 'Tríona' }, + starfetch: { id: 0, name: 'starfetch' }, + bobgeorgethe3rd: { id: 0, name: 'starfetch' }, + 'Glenn Matthews': { id: 0, name: 'Glenn Matthews' }, + 'Raphael Sizemore': { id: 0, name: 'Raphael Sizemore' }, + 'Joe Schofield': { id: 0, name: 'Joe Schofield' }, + mergerg: { id: 0, name: 'starfetch' }, + woutervdub: { id: 0, name: 'Wouter van Wageningen' }, + 'anna-puk': { id: 0, name: 'Anna Puk' }, + 'Nick Dower': { id: 0, name: 'Nick Dower' }, + 'Sanne Kalkman': { id: 0, name: 'Sanne Kalkman' }, + 'Darigov Research': { id: 0, name: 'Darigov Research' }, + 'Jeroen Hoek': { id: 0, name: 'Jeroen Hoek' }, + Natalia: { id: 0, name: 'Natalia Sayang' }, + chri5b: { id: 0, name: 'chri5b' }, + tangerineshark: { id: 0, name: 'tangerineshark' }, + 'MA-TATAS': { id: 0, name: 'MA-TATAS' }, +} + +/* + * Maps git commiter name to author for those cases where the + * name in the authors table is different + */ +export const gitToAuthor = { + 'Joost De Cock': 'joostdecock', + 'Benjamin F': 'benjamesben', + SeaZeeZee: 'Zee', + 'Wouter van Wageningen': 'woutervdub', + 'bobgeorgethe3rd@googlemail.com': 'bobgeorgethe3rd', + '70777269+tangerineshark@users.noreply.github.com': 'tangerineshark', + 'thijs.assies@gmail.com': 'MA-TATAS', } diff --git a/sites/shared/components/wrappers/mdx.mjs b/sites/shared/components/wrappers/mdx.mjs index 7b63dbd3759..8a9141d6d60 100644 --- a/sites/shared/components/wrappers/mdx.mjs +++ b/sites/shared/components/wrappers/mdx.mjs @@ -2,6 +2,7 @@ import { components as baseComponents } from 'shared/components/mdx/index.mjs' // List of authors import { authors as allAuthors } from 'config/authors.mjs' +import { docUpdates } from 'site/prebuild/doc-updates.mjs' // FreeSewing config import { freeSewingConfig } from 'shared/config/freesewing.config.mjs' // Components @@ -55,7 +56,7 @@ const Ul = ({ children }) => ( ) const MetaData = ({ authors = [], maintainers = [], updated = '20220825', locale, slug, t }) => ( -
+
{locale === 'en' ? (
{t('helpImproveDocs')} @@ -84,7 +85,7 @@ const MetaData = ({ authors = [], maintainers = [], updated = '20220825', locale {authors.length > 0 ? ( <>
  • {t('authors')}:
  • - + ) : null} @@ -109,11 +110,16 @@ export const MdxWrapper = ({ MDX, frontmatter = {}, components = {} }) => { const allComponents = { ...baseComponents, ...components } const { locale, slug } = useContext(NavigationContext) - const { authors = [], maintainers = [] } = frontmatter || {} + const updates = docUpdates[slug] || {} return (
    - +
    ) diff --git a/sites/shared/prebuild/git.mjs b/sites/shared/prebuild/git.mjs new file mode 100644 index 00000000000..4794de3bea1 --- /dev/null +++ b/sites/shared/prebuild/git.mjs @@ -0,0 +1,94 @@ +import execa from 'execa' +import { gitToAuthor, authors as authorInfo } from '../../../config/authors.mjs' +import path from 'path' +import fs from 'fs' +import rdir from 'recursive-readdir' +import { getMdxFileList, fileToSlug } from './docs.mjs' + +const divider = '____' + +const parseLog = (line) => line.split(divider).map((item) => item.trim()) + +/* + * Extracts git authors and last modification date from git log. + * Strictly speaking, it's the last commit date, but you get the idea. + */ +export const getGitMetadata = async (file, site) => { + const slug = fileToSlug(file, site, 'en') + const log = await execa.command( + `git log --pretty="format:%cs${divider}%aN${divider}%aE" ${file}`, + { shell: true } + ) + + const authors = new Set() + let lastUpdated = false + for (const line of log.stdout.split('\n')) { + const [date, author, email] = parseLog(line) + if (!lastUpdated) lastUpdated = date.split('-').join('') + let key = false + if (typeof authorInfo[author] !== 'undefined') key = author + else { + if (typeof gitToAuthor[author] !== 'undefined') { + key = gitToAuthor[author] + } else if (typeof gitToAuthor[email] !== 'undefined') { + key = gitToAuthor[email] + } + } + if (!key) throw `Git author email ${email} is unknown in the git-to-author table` + else authors.add(key) + } + + return { + lastUpdated, + authors, + slug, + } +} + +/* + * Main method that does what needs doing + */ +export const prebuildGitData = async (site) => { + // Say hi + console.log() + console.log(`Prebuilding git author data for freesewing.${site}`) + + // Setup MDX root path + const root = ['..', '..', 'markdown', site] + if (site === 'org') root.push('docs') + const mdxRoot = path.resolve(...root) + + const pages = {} + + // Get list of filenames + const list = await getMdxFileList(mdxRoot, 'en') + + // Loop over files + for (const file of list) { + const { lastUpdated, authors, slug } = await getGitMetadata(file, site) + pages[slug] = { lastUpdated, authors: [...authors] } + } + // Write page to disk + const dir = path.resolve('..', site, 'prebuild') + fs.mkdirSync(dir, { recursive: true }) + fs.writeFileSync( + path.resolve(dir, `doc-updates.mjs`), + `export const docUpdates = ${JSON.stringify(pages)}` + ) + + // How about some stats + const stats = {} + for (const slug in pages) { + for (const author of pages[slug].authors) { + if (typeof stats[author] === 'undefined') stats[author] = 0 + stats[author]++ + } + } + + fs.writeFileSync( + path.resolve(dir, `doc-stats.mjs`), + `export const docStats = ${JSON.stringify(stats, null, 2)}` + ) + + return pages +} diff --git a/sites/shared/prebuild/index.mjs b/sites/shared/prebuild/index.mjs index d418843ae12..ef7b8751614 100644 --- a/sites/shared/prebuild/index.mjs +++ b/sites/shared/prebuild/index.mjs @@ -1,6 +1,6 @@ import { prebuildDocs } from './docs.mjs' -//import { prebuildMdx } from './mdx.mjs' import { prebuildNavigation } from './navigation.mjs' +import { prebuildGitData } from './git.mjs' import { prebuildContributors } from './contributors.mjs' import { prebuildPatrons } from './patrons.mjs' import { prebuildI18n } from './i18n.mjs' @@ -10,13 +10,12 @@ import { generateOgImage } from './og/index.mjs' const run = async () => { const SITE = process.env.SITE || 'lab' - if (SITE === 'org') { - prebuildDesigns() - const docPages = await prebuildDocs(SITE) - const posts = {} - prebuildNavigation(docPages, posts, SITE) - } else if (SITE === 'dev') { + let docPages + if (['org', 'dev'].includes(SITE)) { + await prebuildGitData(SITE) const docPages = await prebuildDocs(SITE) + prebuildNavigation(docPages, false, SITE) + if (SITE === 'org') prebuildDesigns() if (process.env.GENERATE_OG_IMAGES) { // Create og image for the home page await generateOgImage({ @@ -35,7 +34,6 @@ const run = async () => { lead: '404', }) } - prebuildNavigation(docPages, false, SITE) } else await prebuildLab() await prebuildI18n(SITE) diff --git a/sites/shared/prebuild/mdx.mjs b/sites/shared/prebuild/mdx.mjs deleted file mode 100644 index fa12afdcfae..00000000000 --- a/sites/shared/prebuild/mdx.mjs +++ /dev/null @@ -1,166 +0,0 @@ -import path from 'path' -import fs from 'fs' -import rdir from 'recursive-readdir' -import { unified } from 'unified' -import remarkParser from 'remark-parse' -import remarkCompiler from 'remark-stringify' -import remarkFrontmatter from 'remark-frontmatter' -import remarkFrontmatterExtractor from 'remark-extract-frontmatter' -import { readSync } from 'to-vfile' -import yaml from 'js-yaml' -import { mdIntro } from './md-intro.mjs' -import { generateOgImage } from './og/index.mjs' - -/* - * There's an issue in crowdin where it changes the frontmatter marker: - * --- - * into this: - * - - - - * which breaks stuff. So this method takes the input and replaces all - * - - - with --- - */ -export const fixCrowdinBugs = (md) => { - md.value = md.value.split('- - -\n').join('---\n') - return md -} - -/* - * Helper method to get a list of MDX files in a folder. - * Will traverse recursively to get all files from a given root folder. - * - * Parameters: - * - * - folder: the root folder to look in - * - lang: the language files to looks for - * - * Exported because it's also used by the Algolia index script - */ -export const getMdxFileList = async (folder, lang) => { - let allFiles - try { - allFiles = await rdir(folder) - } catch (err) { - console.log(err) - return false - } - - // Filter out all that's not a language-specific markdown file - // and avoid including the 'ui' files - const files = [] - for (const file of allFiles) { - if ( - file.slice(-5) === `${lang}.md` && - file.indexOf('/ui/') === -1 && - file.indexOf('/uimd/') === -1 - ) - files.push(file) - } - - return files.sort() -} - -/* - * Helper method to get the website slug (path) from the file path - */ -export const fileToSlug = (file, site, lang) => - file.slice(-6) === `/${lang}.md` ? file.split(`/markdown/${site}/`).pop().slice(0, -6) : false - -/* - * Helper method to get the title and meta data from an MDX file - * - * Parameters: - * - * - file: the full path to the file - */ -const mdxMetaInfo = async (file) => { - let result - try { - result = await unified() - .use(remarkParser) - .use(remarkCompiler) - .use(remarkFrontmatter) - .use(remarkFrontmatterExtractor, { yaml: yaml.load }) - .process(fixCrowdinBugs(readSync(file, { encoding: 'utf-8' }))) - } catch (err) { - console.log(err) - } - - return result -} - -/* - * Main method that does what needs doing - */ -export const prebuildMdx = async (site) => { - // Say hi - console.log() - console.log(`Prebuilding MDX for freesewing.${site}`) - - // Setup MDX root path - const mdxRoot = path.resolve('..', '..', 'markdown', site) - - // Inform if we're also generating OG images - if (process.env.GENERATE_OG_IMAGES) { - console.log('⚙ Also generating Open Graph images (this takes a while)') - console.log(' Unset the GENERATE_OG_IMAGES env var to skip this step)') - } else { - console.log('⏩ Not generating Open Graph images as it takes a while') - console.log(' Set GENERATE_OG_IMAGES env var to include this step') - } - // Loop over locales - const pages = {} - const locales = site === 'dev' ? ['en'] : ['en', 'fr', 'es', 'nl', 'de'] - for (const lang of locales) { - console.log(` - Language: ${lang}`) - - // Get list of filenames - const list = await getMdxFileList(mdxRoot, lang) - - // Parse them for title and intro - pages[lang] = {} - for (const file of list) { - const slug = fileToSlug(file, site, lang) - if (slug) { - const meta = await mdxMetaInfo(file) - if (meta.data?.title) { - pages[lang][slug] = { t: meta.data.title } - if (meta.data.order) pages[lang][slug].o = `${meta.data.order}${meta.data.title}` - } else { - if (pages.en[slug]) { - console.log(`⚠️l Falling back to EN metadata for ${slug}`) - pages[lang][slug] = pages.en[slug] - } else { - console.log(`❌ [${lang}] Failed to extract meta info from: ${slug}`) - if (meta.messages.length > 0) console.log(meta.messages) - } - } - if (process.env.GENERATE_OG_IMAGES) { - // Create og image - const intro = await mdIntro(lang, site, slug) - await generateOgImage({ lang, site, slug, title: meta.data.title, intro }) - } - } - } - - fs.writeFileSync( - path.resolve('..', site, 'prebuild', `mdx.${lang}.js`), - `export default ${JSON.stringify(pages[lang])}` - ) - } - - // Create wrapper - fs.writeFileSync( - path.resolve('..', site, 'prebuild', `mdx.js`), - locales.map((l) => `import ${l} from './mdx.${l}.js'`).join('\n') + - '\n\n' + - `export default { ${locales.join()} }` - ) - - // Write list of all MDX paths (in one language) - fs.writeFileSync( - path.resolve('..', site, 'prebuild', `mdx.paths.mjs`), - `export const mdxPaths = ${JSON.stringify(Object.keys(pages.en))}` - ) - - return pages -}