2021-12-17 17:37:01 +00:00
import { readFileSync , readFile , mkdirpSync , writeFileSync , remove , copy } from 'fs-extra' ;
2021-07-10 10:16:13 +00:00
import { insertContentIntoFile , rootDir } from '../tool-utils' ;
import { pressCarouselItems } from './utils/pressCarousel' ;
import { getMarkdownIt , loadMustachePartials , markdownToPageHtml , renderMustache } from './utils/render' ;
2021-10-25 15:49:21 +00:00
import { AssetUrls , Env , OrgSponsor , PlanPageParams , Sponsors , TemplateParams } from './utils/types' ;
2021-07-31 13:42:56 +00:00
import { getPlans , loadStripeConfig } from '@joplin/lib/utils/joplinCloud' ;
2021-07-26 17:21:19 +00:00
import { shuffle } from '@joplin/lib/array' ;
2021-12-17 17:37:01 +00:00
import { stripOffFrontMatter } from './utils/frontMatter' ;
const moment = require ( 'moment' ) ;
2021-07-10 10:16:13 +00:00
const dirname = require ( 'path' ) . dirname ;
const glob = require ( 'glob' ) ;
const path = require ( 'path' ) ;
2021-10-25 15:49:21 +00:00
const md5File = require ( 'md5-file/promise' ) ;
2021-07-10 10:16:13 +00:00
2021-07-31 13:45:39 +00:00
const env = Env . Prod ;
2021-07-10 10:16:13 +00:00
const websiteAssetDir = ` ${ rootDir } /Assets/WebsiteAssets ` ;
2021-12-17 17:37:01 +00:00
const mainTemplateHtml = readFileSync ( ` ${ websiteAssetDir } /templates/main-new.mustache ` , 'utf8' ) ;
const frontTemplateHtml = readFileSync ( ` ${ websiteAssetDir } /templates/front.mustache ` , 'utf8' ) ;
const plansTemplateHtml = readFileSync ( ` ${ websiteAssetDir } /templates/plans.mustache ` , 'utf8' ) ;
2021-07-31 13:42:56 +00:00
const stripeConfig = loadStripeConfig ( env , ` ${ rootDir } /packages/server/stripeConfig.json ` ) ;
2021-07-10 10:16:13 +00:00
const partialDir = ` ${ websiteAssetDir } /templates/partials ` ;
2021-12-18 15:45:59 +00:00
const discussLink = 'https://discourse.joplinapp.org/c/news/9' ;
2021-07-10 10:16:13 +00:00
let tocMd_ : string = null ;
let tocHtml_ : string = null ;
const tocRegex_ = /<!-- TOC -->([^]*)<!-- TOC -->/ ;
function tocMd() {
if ( tocMd_ ) return tocMd_ ;
2021-12-17 17:37:01 +00:00
const md = readFileSync ( ` ${ rootDir } /README.md ` , 'utf8' ) ;
2021-07-10 10:16:13 +00:00
const toc = md . match ( tocRegex_ ) ;
tocMd_ = toc [ 1 ] ;
return tocMd_ ;
}
const donateLinksRegex_ = /<!-- DONATELINKS -->([^]*)<!-- DONATELINKS -->/ ;
async function getDonateLinks() {
2021-12-17 17:37:01 +00:00
const md = await readFile ( ` ${ rootDir } /README.md ` , 'utf8' ) ;
2021-07-10 10:16:13 +00:00
const matches = md . match ( donateLinksRegex_ ) ;
if ( ! matches ) throw new Error ( 'Cannot fetch donate links' ) ;
return ` <div class="donate-links"> \ n \ n ${ matches [ 1 ] . trim ( ) } \ n \ n</div> ` ;
}
function replaceGitHubByWebsiteLinks ( md : string ) {
return md
. replace ( /https:\/\/github.com\/laurent22\/joplin\/blob\/dev\/readme\/(.*?)\.md(#[^\s)]+|)/g , '/$1/$2' )
2021-08-09 12:17:58 +00:00
. replace ( /https:\/\/github.com\/laurent22\/joplin\/blob\/dev\/README\.md(#[^\s)]+|)/g , '/help/$1' )
. replace ( /https:\/\/raw.githubusercontent.com\/laurent22\/joplin\/dev\/Assets\/WebsiteAssets\/(.*?)/g , '/$1' ) ;
2021-07-10 10:16:13 +00:00
}
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_ = ` <div> ${ tocHtml_ } </div> ` ;
return tocHtml_ ;
}
2021-10-25 15:49:21 +00:00
const baseUrl = '' ;
const cssBasePath = ` ${ websiteAssetDir } /css ` ;
const cssBaseUrl = ` ${ baseUrl } /css ` ;
const jsBasePath = ` ${ websiteAssetDir } /js ` ;
const jsBaseUrl = ` ${ baseUrl } /js ` ;
2021-07-10 10:16:13 +00:00
2021-10-25 15:49:21 +00:00
async function getAssetUrls ( ) : Promise < AssetUrls > {
return {
css : {
fontawesome : ` ${ cssBaseUrl } /fontawesome-all.min.css?h= ${ await md5File ( ` ${ cssBasePath } /fontawesome-all.min.css ` ) } ` ,
site : ` ${ cssBaseUrl } /site.css?h= ${ await md5File ( ` ${ cssBasePath } /site.css ` ) } ` ,
} ,
js : {
script : ` ${ jsBaseUrl } /script.js?h= ${ await md5File ( ` ${ jsBasePath } /script.js ` ) } ` ,
} ,
} ;
}
function defaultTemplateParams ( assetUrls : AssetUrls ) : TemplateParams {
2021-07-10 10:16:13 +00:00
return {
env ,
2021-10-25 15:49:21 +00:00
baseUrl ,
2021-07-10 10:16:13 +00:00
imageBaseUrl : ` ${ baseUrl } /images ` ,
2021-10-25 15:49:21 +00:00
cssBaseUrl ,
jsBaseUrl ,
2021-07-10 10:16:13 +00:00
tocHtml : tocHtml ( ) ,
yyyy : ( new Date ( ) ) . getFullYear ( ) . toString ( ) ,
templateHtml : mainTemplateHtml ,
forumUrl : 'https://discourse.joplinapp.org/' ,
showToc : true ,
showImproveThisDoc : true ,
2021-07-19 09:58:21 +00:00
showJoplinCloudLinks : true ,
2021-07-10 10:16:13 +00:00
navbar : {
isFrontPage : false ,
} ,
2021-10-25 15:49:21 +00:00
assetUrls ,
2021-07-10 10:16:13 +00:00
} ;
}
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 = {
2021-10-25 15:49:21 +00:00
. . . defaultTemplateParams ( templateParams . assetUrls ) ,
2021-07-10 10:16:13 +00:00
. . . templateParams ,
} ;
2021-12-18 15:45:59 +00:00
templateParams . showBottomLinks = templateParams . showImproveThisDoc || ! ! templateParams . discussOnForumLink ;
2021-07-10 10:16:13 +00:00
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 ) ;
2021-12-17 17:37:01 +00:00
mkdirpSync ( folderPath ) ;
2021-07-10 10:16:13 +00:00
2021-12-17 17:37:01 +00:00
writeFileSync ( targetPath , html ) ;
2021-07-10 10:16:13 +00:00
}
async function readmeFileTitle ( sourcePath : string ) {
2021-12-17 17:37:01 +00:00
const md = await readFile ( sourcePath , 'utf8' ) ;
2021-07-10 10:16:13 +00:00
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 ) {
2021-12-17 17:37:01 +00:00
let md = readFileSync ( sourcePath , 'utf8' ) ;
md = stripOffFrontMatter ( md ) . doc ;
2021-07-10 10:16:13 +00:00
return renderPageToHtml ( md , targetPath , templateParams ) ;
}
function makeHomePageMd() {
2021-12-17 17:37:01 +00:00
let md = readFileSync ( ` ${ rootDir } /README.md ` , 'utf8' ) ;
2021-07-10 10:16:13 +00:00
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 < Record < string , string > > {
const output : Record < string , string > = { } ;
output [ 'windows' ] = readmeMd . match ( /(<a href=.*?Joplin-Setup-.*?<\/a>)/ ) [ 0 ] ;
output [ 'macOs' ] = readmeMd . match ( /(<a href=.*?Joplin-.*\.dmg.*?<\/a>)/ ) [ 0 ] ;
output [ 'linux' ] = readmeMd . match ( /(<a href=.*?Joplin-.*\.AppImage.*?<\/a>)/ ) [ 0 ] ;
output [ 'android' ] = readmeMd . match ( /(<a href='https:\/\/play.google.com\/store\/apps\/details\?id=net\.cozic\.joplin.*?<\/a>)/ ) [ 0 ] ;
output [ 'ios' ] = readmeMd . match ( /(<a href='https:\/\/itunes\.apple\.com\/us\/app\/joplin\/id1315599797.*?<\/a>)/ ) [ 0 ] ;
for ( const [ k , v ] of Object . entries ( output ) ) {
if ( ! v ) throw new Error ( ` Could not get download element for: ${ k } ` ) ;
}
return output ;
}
async function updateDownloadPage ( downloadButtonsHtml : Record < string , string > ) {
const desktopButtonsHtml = [
downloadButtonsHtml [ 'windows' ] ,
downloadButtonsHtml [ 'macOs' ] ,
downloadButtonsHtml [ 'linux' ] ,
] ;
const mobileButtonsHtml = [
downloadButtonsHtml [ 'android' ] ,
downloadButtonsHtml [ 'ios' ] ,
] ;
await insertContentIntoFile ( ` ${ rootDir } /readme/download.md ` , '<!-- DESKTOP-DOWNLOAD-LINKS -->' , '<!-- DESKTOP-DOWNLOAD-LINKS -->' , desktopButtonsHtml . join ( ' ' ) ) ;
await insertContentIntoFile ( ` ${ rootDir } /readme/download.md ` , '<!-- MOBILE-DOWNLOAD-LINKS -->' , '<!-- MOBILE-DOWNLOAD-LINKS -->' , mobileButtonsHtml . join ( ' ' ) ) ;
}
async function loadSponsors ( ) : Promise < Sponsors > {
const sponsorsPath = ` ${ rootDir } /packages/tools/sponsors.json ` ;
2021-12-17 17:37:01 +00:00
const output : Sponsors = JSON . parse ( await readFile ( sponsorsPath , 'utf8' ) ) ;
2021-07-26 17:21:19 +00:00
output . orgs = shuffle < OrgSponsor > ( output . orgs . map ( o = > {
if ( o . urlWebsite ) o . url = o . urlWebsite ;
return o ;
} ) ) ;
return output ;
2021-07-10 10:16:13 +00:00
}
2021-12-17 17:37:01 +00:00
const makeNewsFrontPage = async ( sourceFilePaths : string [ ] , targetFilePath : string , templateParams : TemplateParams ) = > {
const maxNewsPerPage = 20 ;
const frontPageMd : string [ ] = [ ] ;
for ( const mdFilePath of sourceFilePaths ) {
let md = await readFile ( mdFilePath , 'utf8' ) ;
const info = stripOffFrontMatter ( md ) ;
2021-12-18 15:45:59 +00:00
md = info . doc . trim ( ) ;
2021-12-17 17:37:01 +00:00
const dateString = moment ( info . created ) . format ( 'D MMM YYYY' ) ;
md = md . replace ( /^# (.*)/ , ` # [ $ 1](https://github.com/laurent22/joplin/blob/dev/readme/news/ ${ path . basename ( mdFilePath ) } ) \ n \ n*Published on ** ${ dateString } *** \ n \ n ` ) ;
2021-12-18 15:45:59 +00:00
md += ` \ n \ n* * * \ n \ n[<i class="fab fa-discourse"></i> Discuss on the forum]( ${ discussLink } ) ` ;
2021-12-17 17:37:01 +00:00
frontPageMd . push ( md ) ;
if ( frontPageMd . length >= maxNewsPerPage ) break ;
}
renderPageToHtml ( frontPageMd . join ( '\n\n* * *\n\n' ) , targetFilePath , templateParams ) ;
} ;
const isNewsFile = ( filePath : string ) : boolean = > {
return filePath . includes ( 'readme/news/' ) ;
} ;
2021-07-10 10:16:13 +00:00
async function main() {
2021-12-17 17:37:01 +00:00
await remove ( ` ${ rootDir } /docs ` ) ;
await copy ( websiteAssetDir , ` ${ rootDir } /docs ` ) ;
2021-07-10 10:16:13 +00:00
const sponsors = await loadSponsors ( ) ;
const partials = await loadMustachePartials ( partialDir ) ;
2021-10-25 15:49:21 +00:00
const assetUrls = await getAssetUrls ( ) ;
2021-07-10 10:16:13 +00:00
const readmeMd = makeHomePageMd ( ) ;
const downloadButtonsHtml = await createDownloadButtonsHtml ( readmeMd ) ;
await updateDownloadPage ( downloadButtonsHtml ) ;
// =============================================================
// HELP PAGE
// =============================================================
2021-10-25 15:49:21 +00:00
renderPageToHtml ( readmeMd , ` ${ rootDir } /docs/help/index.html ` , { sourceMarkdownFile : 'README.md' , partials , sponsors , assetUrls } ) ;
2021-07-10 10:16:13 +00:00
// =============================================================
// FRONT PAGE
// =============================================================
renderPageToHtml ( '' , ` ${ rootDir } /docs/index.html ` , {
templateHtml : frontTemplateHtml ,
partials ,
pressCarouselRegular : {
id : 'carouselRegular' ,
items : pressCarouselItems ( ) ,
} ,
pressCarouselMobile : {
id : 'carouselMobile' ,
items : pressCarouselItems ( ) ,
} ,
sponsors ,
navbar : {
isFrontPage : true ,
} ,
2021-07-12 09:37:58 +00:00
showToc : false ,
2021-10-25 15:49:21 +00:00
assetUrls ,
2021-07-10 10:16:13 +00:00
} ) ;
// =============================================================
// PLANS PAGE
// =============================================================
2021-12-17 17:37:01 +00:00
const planPageFaqMd = await readFile ( ` ${ rootDir } /readme/faq_joplin_cloud.md ` , 'utf8' ) ;
2021-07-10 10:16:13 +00:00
const planPageFaqHtml = getMarkdownIt ( ) . render ( planPageFaqMd , { } ) ;
const planPageParams : PlanPageParams = {
2021-10-25 15:49:21 +00:00
. . . defaultTemplateParams ( assetUrls ) ,
2021-07-10 10:16:13 +00:00
partials ,
templateHtml : plansTemplateHtml ,
plans : getPlans ( stripeConfig ) ,
faqHtml : planPageFaqHtml ,
stripeConfig ,
} ;
const planPageContentHtml = renderMustache ( '' , planPageParams ) ;
renderPageToHtml ( '' , ` ${ rootDir } /docs/plans/index.html ` , {
2021-10-25 15:49:21 +00:00
. . . defaultTemplateParams ( assetUrls ) ,
2021-07-10 10:16:13 +00:00
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 ( ) ;
2021-12-17 17:37:01 +00:00
const makeTargetFilePath = ( input : string ) : string = > {
if ( isNewsFile ( input ) ) {
return ` ${ input . replace ( /\.md/ , '' ) . replace ( /readme\/news\// , 'docs/news/' ) } /index.html ` ;
} else {
return ` ${ input . replace ( /\.md/ , '' ) . replace ( /readme\// , 'docs/' ) } /index.html ` ;
}
} ;
const newsFilePaths : string [ ] = [ ] ;
2021-07-10 10:16:13 +00:00
for ( const mdFile of mdFiles ) {
const title = await readmeFileTitle ( ` ${ rootDir } / ${ mdFile } ` ) ;
2021-12-17 17:37:01 +00:00
const targetFilePath = makeTargetFilePath ( mdFile ) ;
const isNews = isNewsFile ( mdFile ) ;
if ( isNews ) newsFilePaths . push ( mdFile ) ;
2021-07-10 10:16:13 +00:00
sources . push ( [ mdFile , targetFilePath , {
title : title ,
donateLinksMd : mdFile === 'readme/donate.md' ? '' : donateLinksMd ,
2021-12-17 17:37:01 +00:00
showToc : mdFile !== 'readme/download.md' && ! isNews ,
2021-07-10 10:16:13 +00:00
} ] ) ;
}
for ( const source of sources ) {
source [ 2 ] . sourceMarkdownFile = source [ 0 ] ;
source [ 2 ] . sourceMarkdownName = path . basename ( source [ 0 ] , path . extname ( source [ 0 ] ) ) ;
2021-12-18 15:31:08 +00:00
const sourceFilePath = ` ${ rootDir } / ${ source [ 0 ] } ` ;
2021-12-18 15:45:59 +00:00
const isNews = isNewsFile ( sourceFilePath ) ;
2021-12-18 15:31:08 +00:00
renderFileToHtml ( sourceFilePath , ` ${ rootDir } / ${ source [ 1 ] } ` , {
2021-07-10 10:16:13 +00:00
. . . source [ 2 ] ,
templateHtml : mainTemplateHtml ,
2021-12-18 15:45:59 +00:00
pageName : isNews ? 'news-item' : '' ,
discussOnForumLink : isNews ? discussLink : '' ,
showImproveThisDoc : ! isNews ,
2021-07-10 10:16:13 +00:00
partials ,
2021-10-25 15:49:21 +00:00
assetUrls ,
2021-07-10 10:16:13 +00:00
} ) ;
}
2021-12-17 17:37:01 +00:00
newsFilePaths . sort ( ( a , b ) = > {
return a . toLowerCase ( ) > b . toLowerCase ( ) ? - 1 : + 1 ;
} ) ;
await makeNewsFrontPage ( newsFilePaths , ` ${ rootDir } /docs/news/index.html ` , {
. . . defaultTemplateParams ( assetUrls ) ,
2021-12-17 17:58:49 +00:00
pageName : 'news' ,
2021-12-17 17:37:01 +00:00
partials ,
showToc : false ,
showImproveThisDoc : false ,
donateLinksMd ,
} ) ;
2021-07-10 10:16:13 +00:00
}
main ( ) . catch ( ( error ) = > {
console . error ( error ) ;
2021-12-17 14:08:52 +00:00
process . exit ( 1 ) ;
2021-07-10 10:16:13 +00:00
} ) ;