const fs = require('fs-extra'); const dirname = require('path').dirname; const Mustache = require('mustache'); const glob = require('glob'); const headerHtml = `<!doctype html> <html> <!-- !!! WARNING !!! This file was auto-generated from {{{sourceMarkdownFile}}} and any manual change made to it will be overwritten. To make a change to this file please modify the source Markdown file: https://github.com/laurent22/joplin/blob/dev/{{{sourceMarkdownFile}}} --> <head> <title>{{pageTitle}}</title> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="https://joplinapp.org/css/bootstrap.min.css"> <link rel="shortcut icon" type="image/x-icon" href="https://joplinapp.org/favicon.ico"> <!-- <link rel="stylesheet" href="https://joplinapp.org/css/fontawesome-all.min.css"> --> <link rel="stylesheet" href="https://joplinapp.org/css/fork-awesome.min.css"> <script src="https://joplinapp.org/js/jquery-3.2.1.slim.min.js"></script> <style> body { background-color: #F1F1F1; color: #333333; } .root { overflow: hidden; } a[href^="mailto:"] { word-break: break-all; } table { margin-bottom: 1em; } td, th { padding: .8em; border: 1px solid #ccc; } .page-markdown table pre, .page-markdown table blockquote { margin-bottom: 0; } .page-markdown table pre, .page-markdown table blockquote { margin-bottom: 0; } .page-markdown table pre { background-color: rgba(0,0,0,0); border: none; margin: 0; padding: 0; } h1, h2 { border-bottom: 1px solid #eaecef; padding-bottom: 0.3em; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-weight: 600; font-size: 2em; margin-bottom: 16px; } h2 { font-size: 1.6em; } h3 { font-size: 1.3em; } code { color: black; background-color: #eee; border: 1px solid #ccc; font-size: .85em; word-break: break-all; } pre code { border: none; } pre { font-size: .85em; } blockquote { font-size: 1em; color: #555; }; #toc ul { margin-bottom: 10px; } #toc > ul > li { margin-bottom: 10px; } #toc { padding-bottom: 1em; } .title { display: flex; align-items: center; } .title-icon { display: flex; height: 1em; } .title-text { display: flex; font-weight: normal; margin-bottom: .2em; margin-left: .5em; } .sub-title { font-weight: normal; } .container { background-color: white; padding: 0; box-shadow: 0 10px 20px #888888; } table.screenshots { margin-top: 2em; margin-bottom: 2em; } table.screenshots th { height: 3em; text-align: center; } table.screenshots th, table.screenshots td { border: 1px solid #C2C2C2; } img[align="left"] { margin-right: 10px; margin-bottom: 10px; } .mobile-screenshot { height: 40em; padding: 1em; } .cli-screenshot-wrapper { background-color: black; vertical-align: top; padding: 1em 2em 1em 1em; } .cli-screenshot { font-family: "Monaco", "Inconsolata", "CONSOLAS", "Deja Vu Sans Mono", "Droid Sans Mono", "Andale Mono", monospace; background-color: black; color: white; border: none; } .cli-screenshot .prompt { color: #48C2F0; } .top-screenshot { margin-top: 2em; text-align: center; } .header { position: relative; padding-left: 2em; padding-right: 2em; padding-top: 1em; padding-bottom: 1em; color: white; background-color: #2B2B3D; } .header a h1 { color: white; } .header a:hover { text-decoration: none; } .content { padding-left: 2em; padding-right: 2em; padding-bottom: 2em; padding-top: 2em; } .forkme { position: absolute; right: 0; top:0; } .nav-wrapper { position: relative; width: inherit; } .nav { background-color: black; display: flex; flex-direction: row; align-items: center; } .nav.sticky { position:fixed; top: 0; width: inherit; box-shadow: 0 0 10px #000000; } .nav a { color: white; display: inline-block; padding: .6em .9em .6em .9em; } .nav ul { padding-left: 2em; margin-bottom: 0; display: table-cell; display: flex; width: 100%; } .nav ul li { display: inline-block; padding: 0; } .nav li.selected { background-color: #222; font-weight: bold; } .nav-right { display: flex; text-align: right; vertical-align: middle; line-height: 0; margin-right: 10px; } .nav-right .share-btn { display: none; } .nav-right .small-share-btn { display: none; } .footer { padding: 2em; border-top: 1px solid #d4d4d4; margin-top: 2em; color: gray; font-size: .9em; } a.heading-anchor { display: inline-block; opacity: 0; width: 1.3em; font-size: 0.7em; margin-left: 0.4em; line-height: 1em; text-decoration: none; transition: opacity 0.3s; } a.heading-anchor:hover, h1:hover a.heading-anchor, h2:hover a.heading-anchor, h3:hover a.heading-anchor, h4:hover a.heading-anchor, h5:hover a.heading-anchor, h6:hover a.heading-anchor { opacity: 1; } @media (min-width: 992px) { .content{ display: flex; } #toc{ display: block!important; align-self: flex-start; width: 300px; position: sticky; top: 20px; left: 0; } .main{ width: calc(100% - 300px); } } .bottom-links { display: flex; justify-content: center; border-top: 1px solid #d4d4d4; margin-top: 30px; padding-top: 25px; } @media all and (min-width: 400px) { .nav-right .share-btn { display: inline-block; } .nav-right .small-share-btn { display: none; } } </style> </head> <body> <div class="container root page-{{sourceMarkdownName}}"> <div class="header"> <a class="forkme" href="https://github.com/laurent22/joplin"><img src="{{{imageBaseUrl}}}/ForkMe.png"/></a> <a href="https://joplinapp.org"><h1 class="title"><img class="title-icon" src="{{{imageBaseUrl}}}/Icon512.png"><span class="title-text">Joplin</span></h1></a> <p class="sub-title">An open source note taking and to-do application with synchronisation capabilities</p> </div> <div class="nav-wrapper"> <div class="nav"> <ul> <li class="{{selectedHome}}"><a href="{{baseUrl}}/" title="Home"><i class="fa fa-home"></i></a></li> <li><a href="https://discourse.joplinapp.org" title="Forum">Forum</a></li> <li><a class="help" href="#" title="Menu">Menu</a></li> <!-- <li><a class="gsod" href="https://joplinapp.org/gsod2020/" title="Google Season of Docs 2020">GSoD 2020</a></li> --> </ul> <div class="nav-right"> <!-- <iframe class="share-btn" src="https://www.facebook.com/plugins/share_button.php?href=http%3A%2F%2Fjoplinapp.org&layout=button&size=small&mobile_iframe=true&width=60&height=20&appId" width="60" height="20" style="border:none;overflow:hidden" scrolling="no" frameborder="0" allowTransparency="true"></iframe> <iframe class="share-btn" src="https://platform.twitter.com/widgets/tweet_button.html?url=http%3A%2F%2Fjoplinapp.org" width="62" height="20" title="Tweet" style="border: 0; overflow: hidden;"></iframe> --> <iframe class="share-btn share-btn-github" src="https://ghbtns.com/github-btn.html?user=laurent22&repo=joplin&type=star&count=true" frameborder="0" scrolling="0" width="100px" height="20px"></iframe> </div> </div> </div> <div class="content"> {{{tocHtml}}} <div class="main"> `; const footerHtmlTemplate = ` </div></div> <div class="footer"> Copyright (C) 2016-YYYY Laurent Cozic </div> </body> </html> `; const footerHtml = footerHtmlTemplate.replace('YYYY', new Date().getFullYear()); // const screenshotHtml = ` // <table class="screenshots"> // <tr> // <th> // Mobile // </th> // <th> // Command line // </th> // </tr> // <tr> // <td> // <img class="mobile-screenshot" src="docs/images/Mobile.png"/> // </td> // <td class="cli-screenshot-wrapper"> // <pre class="cli-screenshot"> // <span class="prompt">joplin:/My notebook$</span> ls -n 12 // [ ] 8am conference call ☎ // [ ] Make vet appointment // [ ] Go pick up parcel // [ ] Pay flat rent 💸 // [X] Book ferry 🚢 // [X] Deploy Joplin app // Open source stuff // Swimming pool time table 🏊 // Grocery shopping list 📝 // Work itinerary // Tuesday random note // Vacation plans ☀ // </pre> // </td> // </tr> // </table> // `; const scriptHtml = ` <script> function stickyHeader() { return; // Disabled if ($(window).scrollTop() > 179) { $('.nav').addClass('sticky'); } else { $('.nav').removeClass('sticky'); } } $('#toc').hide(); $('.help').click(function(event) { event.preventDefault(); $('#toc').show(); }); $(window).scroll(function() { stickyHeader(); }); (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); ga('create', 'UA-103586105-1', 'auto'); ga('send', 'pageview'); </script> `; const rootDir = dirname(dirname(__dirname)); function markdownToHtml(md, templateParams) { const MarkdownIt = require('markdown-it'); const markdownIt = new MarkdownIt({ breaks: true, linkify: true, html: true, }); markdownIt.core.ruler.push('checkbox', state => { const tokens = state.tokens; const Token = state.Token; const doneNames = []; const headingTextToAnchorName = (text, doneNames) => { const allowed = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; let lastWasDash = true; let output = ''; for (let i = 0; i < text.length; i++) { const c = text[i]; if (allowed.indexOf(c) < 0) { if (lastWasDash) continue; lastWasDash = true; output += '-'; } else { lastWasDash = false; output += c; } } output = output.toLowerCase(); while (output.length && output[output.length - 1] === '-') { output = output.substr(0, output.length - 1); } let temp = output; let index = 1; while (doneNames.indexOf(temp) >= 0) { temp = `${output}-${index}`; index++; } output = temp; return output; }; const createAnchorTokens = anchorName => { const output = []; { const token = new Token('heading_anchor_open', 'a', 1); token.attrs = [ ['name', anchorName], ['href', `#${anchorName}`], ['class', 'heading-anchor'], ]; output.push(token); } { const token = new Token('text', '', 0); token.content = '🔗'; output.push(token); } { const token = new Token('heading_anchor_close', 'a', -1); output.push(token); } return output; }; let insideHeading = false; for (let i = 0; i < tokens.length; i++) { const token = tokens[i]; if (token.type === 'heading_open') { insideHeading = true; continue; } if (token.type === 'heading_close') { insideHeading = false; continue; } if (insideHeading && token.type === 'inline') { const anchorName = headingTextToAnchorName(token.content, doneNames); doneNames.push(anchorName); const anchorTokens = createAnchorTokens(anchorName); // token.children = anchorTokens.concat(token.children); token.children = token.children.concat(anchorTokens); } } }); const improveDocHtml = ` <div class="bottom-links"> <a href="https://github.com/laurent22/joplin/blob/dev/{{{sourceMarkdownFile}}}"> <i class="fa fa-github"></i> Improve this doc </a> </div>`; return Mustache.render(headerHtml, templateParams) + markdownIt.render(md) + Mustache.render(improveDocHtml, templateParams) + scriptHtml + footerHtml; } let tocMd_ = null; let tocHtml_ = null; const tocRegex_ = /<!-- TOC -->([^]*)<!-- TOC -->/; function tocMd() { if (tocMd_) return tocMd_; const md = fs.readFileSync(`${rootDir}/README.md`, 'utf8'); const toc = md.match(tocRegex_); tocMd_ = toc[1]; return tocMd_; } function replaceGitHubByJoplinAppLinks(md) { // 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, 'https://joplinapp.org/$1/$2'); } function tocHtml() { if (tocHtml_) return tocHtml_; const MarkdownIt = require('markdown-it'); const markdownIt = new MarkdownIt(); let md = tocMd(); md = md.replace(/# Table of contents/, ''); md = replaceGitHubByJoplinAppLinks(md); tocHtml_ = markdownIt.render(md); tocHtml_ = `<div id="toc">${tocHtml_}</div>`; return tocHtml_; } function renderMdToHtml(md, targetPath, templateParams) { // Remove the header because it's going to be added back as HTML md = md.replace(/# Joplin\n/, ''); templateParams.baseUrl = 'https://joplinapp.org'; templateParams.imageBaseUrl = `${templateParams.baseUrl}/images`; templateParams.tocHtml = tocHtml(); 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 = replaceGitHubByJoplinAppLinks(md); templateParams.pageTitle = title.join(' | '); const html = markdownToHtml(md, templateParams); const folderPath = dirname(targetPath); fs.mkdirpSync(folderPath); fs.writeFileSync(targetPath, html); } async function readmeFileTitle(sourcePath) { 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, targetPath, templateParams) { const md = fs.readFileSync(sourcePath, 'utf8'); return renderMdToHtml(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 main() { await fs.remove(`${rootDir}/docs`); await fs.copy(`${rootDir}/Assets/WebsiteAssets`, `${rootDir}/docs`); renderMdToHtml(makeHomePageMd(), `${rootDir}/docs/index.html`, { sourceMarkdownFile: 'README.md' }); const mdFiles = glob.sync(`${rootDir}/readme/**/*.md`, { ignore: [ // '**/node_modules/**', ], }).map(f => f.substr(rootDir.length + 1)); const sources = []; 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 }]); } const path = require('path'); 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]); } } main().catch((error) => { console.error(error); });