import * as fs from 'fs-extra'; import { insertContentIntoFile, rootDir } from '../tool-utils'; import { getPlans } from './utils/plans'; import { pressCarouselItems } from './utils/pressCarousel'; import { getMarkdownIt, loadMustachePartials, markdownToPageHtml, renderMustache } from './utils/render'; import { Env, PlanPageParams, Sponsors, StripePublicConfig, TemplateParams } from './utils/types'; const dirname = require('path').dirname; const glob = require('glob'); const path = require('path'); const env = Env.Prod; const websiteAssetDir = `${rootDir}/Assets/WebsiteAssets`; const mainTemplateHtml = fs.readFileSync(`${websiteAssetDir}/templates/main-new.mustache`, 'utf8'); const frontTemplateHtml = fs.readFileSync(`${websiteAssetDir}/templates/front.mustache`, 'utf8'); const plansTemplateHtml = fs.readFileSync(`${websiteAssetDir}/templates/plans.mustache`, 'utf8'); const stripeConfigs: Record = JSON.parse(fs.readFileSync(`${rootDir}/packages/server/stripeConfig.json`, 'utf8')); const partialDir = `${websiteAssetDir}/templates/partials`; const stripeConfig = stripeConfigs[env]; let tocMd_: string = null; let tocHtml_: string = null; const tocRegex_ = /([^]*)/; function tocMd() { if (tocMd_) return tocMd_; const md = fs.readFileSync(`${rootDir}/README.md`, 'utf8'); const toc = md.match(tocRegex_); tocMd_ = toc[1]; return tocMd_; } const donateLinksRegex_ = /([^]*)/; async function getDonateLinks() { const md = await fs.readFile(`${rootDir}/README.md`, 'utf8'); const matches = md.match(donateLinksRegex_); if (!matches) throw new Error('Cannot fetch donate links'); return ``; } function replaceGitHubByWebsiteLinks(md: string) { // let output = md.replace(/https:\/\/github.com\/laurent22\/joplin\/blob\/master\/readme\/(.*?)\/index\.md(#[^\s)]+|)/g, 'https://joplinapp.org/$1'); return md .replace(/https:\/\/github.com\/laurent22\/joplin\/blob\/dev\/readme\/(.*?)\.md(#[^\s)]+|)/g, '/$1/$2') .replace(/https:\/\/github.com\/laurent22\/joplin\/blob\/dev\/README\.md(#[^\s)]+|)/g, '/help/$1'); } function tocHtml() { if (tocHtml_) return tocHtml_; const markdownIt = getMarkdownIt(); let md = tocMd(); md = md.replace(/# Table of contents/, ''); md = replaceGitHubByWebsiteLinks(md); tocHtml_ = markdownIt.render(md); tocHtml_ = `
${tocHtml_}
`; return tocHtml_; } function defaultTemplateParams(): TemplateParams { const baseUrl = ''; return { env, baseUrl: baseUrl, imageBaseUrl: `${baseUrl}/images`, cssBaseUrl: `${baseUrl}/css`, jsBaseUrl: `${baseUrl}/js`, tocHtml: tocHtml(), yyyy: (new Date()).getFullYear().toString(), templateHtml: mainTemplateHtml, forumUrl: 'https://discourse.joplinapp.org/', showToc: true, showImproveThisDoc: true, showJoplinCloudLinks: false, navbar: { isFrontPage: false, }, }; } function renderPageToHtml(md: string, targetPath: string, templateParams: TemplateParams) { // Remove the header because it's going to be added back as HTML md = md.replace(/# Joplin\n/, ''); templateParams = { ...defaultTemplateParams(), ...templateParams, }; const title = []; if (!templateParams.title) { title.push('Joplin - an open source note taking and to-do application with synchronisation capabilities'); } else { title.push(templateParams.title); title.push('Joplin'); } md = replaceGitHubByWebsiteLinks(md); if (templateParams.donateLinksMd) { md = `${templateParams.donateLinksMd}\n\n${md}`; } templateParams.pageTitle = title.join(' | '); const html = templateParams.contentHtml ? renderMustache(templateParams.contentHtml, templateParams) : markdownToPageHtml(md, templateParams); const folderPath = dirname(targetPath); fs.mkdirpSync(folderPath); fs.writeFileSync(targetPath, html); } async function readmeFileTitle(sourcePath: string) { const md = await fs.readFile(sourcePath, 'utf8'); const r = md.match(/(^|\n)# (.*)/); if (!r) { throw new Error(`Could not determine title for Markdown file: ${sourcePath}`); } else { return r[2]; } } function renderFileToHtml(sourcePath: string, targetPath: string, templateParams: TemplateParams) { const md = fs.readFileSync(sourcePath, 'utf8'); return renderPageToHtml(md, targetPath, templateParams); } function makeHomePageMd() { let md = fs.readFileSync(`${rootDir}/README.md`, 'utf8'); md = md.replace(tocRegex_, ''); // HACK: GitHub needs the \| or the inline code won't be displayed correctly inside the table, // while MarkdownIt doesn't and will in fact display the \. So we remove it here. md = md.replace(/\\\| bash/g, '| bash'); return md; } async function createDownloadButtonsHtml(readmeMd: string): Promise> { const output: Record = {}; output['windows'] = readmeMd.match(/()/)[0]; output['macOs'] = readmeMd.match(/()/)[0]; output['linux'] = readmeMd.match(/()/)[0]; output['android'] = readmeMd.match(/(Get it on Windows // Get it on macOS // Get it on Linux // Get it on Google Play // Get it on the App Store } async function updateDownloadPage(downloadButtonsHtml: Record) { const desktopButtonsHtml = [ downloadButtonsHtml['windows'], downloadButtonsHtml['macOs'], downloadButtonsHtml['linux'], ]; const mobileButtonsHtml = [ downloadButtonsHtml['android'], downloadButtonsHtml['ios'], ]; await insertContentIntoFile(`${rootDir}/readme/download.md`, '', '', desktopButtonsHtml.join(' ')); await insertContentIntoFile(`${rootDir}/readme/download.md`, '', '', mobileButtonsHtml.join(' ')); } async function loadSponsors(): Promise { const sponsorsPath = `${rootDir}/packages/tools/sponsors.json`; return JSON.parse(await fs.readFile(sponsorsPath, 'utf8')); } async function main() { await fs.remove(`${rootDir}/docs`); await fs.copy(websiteAssetDir, `${rootDir}/docs`); const sponsors = await loadSponsors(); const partials = await loadMustachePartials(partialDir); const readmeMd = makeHomePageMd(); const downloadButtonsHtml = await createDownloadButtonsHtml(readmeMd); await updateDownloadPage(downloadButtonsHtml); // ============================================================= // HELP PAGE // ============================================================= renderPageToHtml(readmeMd, `${rootDir}/docs/help/index.html`, { sourceMarkdownFile: 'README.md', partials, sponsors }); // ============================================================= // FRONT PAGE // ============================================================= renderPageToHtml('', `${rootDir}/docs/index.html`, { templateHtml: frontTemplateHtml, partials, pressCarouselRegular: { id: 'carouselRegular', items: pressCarouselItems(), }, pressCarouselMobile: { id: 'carouselMobile', items: pressCarouselItems(), }, sponsors, navbar: { isFrontPage: true, }, }); // ============================================================= // PLANS PAGE // ============================================================= const planPageFaqMd = await fs.readFile(`${rootDir}/readme/faq_joplin_cloud.md`, 'utf8'); const planPageFaqHtml = getMarkdownIt().render(planPageFaqMd, {}); const planPageParams: PlanPageParams = { ...defaultTemplateParams(), partials, templateHtml: plansTemplateHtml, plans: getPlans(stripeConfig), faqHtml: planPageFaqHtml, stripeConfig, }; const planPageContentHtml = renderMustache('', planPageParams); renderPageToHtml('', `${rootDir}/docs/plans/index.html`, { ...defaultTemplateParams(), pageName: 'plans', partials, showToc: false, showImproveThisDoc: false, contentHtml: planPageContentHtml, title: 'Joplin Cloud Plans', }); // ============================================================= // All other pages are generated dynamically from the // Markdown files under /readme // ============================================================= const mdFiles = glob.sync(`${rootDir}/readme/**/*.md`).map((f: string) => f.substr(rootDir.length + 1)); const sources = []; const donateLinksMd = await getDonateLinks(); for (const mdFile of mdFiles) { const title = await readmeFileTitle(`${rootDir}/${mdFile}`); const targetFilePath = `${mdFile.replace(/\.md/, '').replace(/readme\//, 'docs/')}/index.html`; sources.push([mdFile, targetFilePath, { title: title, donateLinksMd: mdFile === 'readme/donate.md' ? '' : donateLinksMd, showToc: mdFile !== 'readme/download.md', }]); } for (const source of sources) { source[2].sourceMarkdownFile = source[0]; source[2].sourceMarkdownName = path.basename(source[0], path.extname(source[0])); renderFileToHtml(`${rootDir}/${source[0]}`, `${rootDir}/${source[1]}`, { ...source[2], templateHtml: mainTemplateHtml, partials, }); } } main().catch((error) => { console.error(error); });