2024-06-02 00:24:48 +00:00
const fs = require ( "fs" ) ;
const path = require ( "path" ) ;
2024-06-18 07:32:30 +00:00
const wordCounting = require ( "word-counting" ) ;
2024-06-02 00:24:48 +00:00
2024-06-06 03:08:02 +00:00
const util = require ( 'node:util' ) ;
const exec = util . promisify ( require ( 'node:child_process' ) . exec ) ;
2024-06-11 04:13:50 +00:00
const { Feed } = require ( 'feed' ) ;
2024-06-06 03:08:02 +00:00
2024-06-02 00:24:48 +00:00
function walk ( dir , cb ) {
const list = fs . readdirSync ( dir ) ;
return Promise . all ( list . map ( file => {
const resolvedFile = path . resolve ( dir , file ) ;
const stat = fs . statSync ( resolvedFile ) ;
if ( stat . isDirectory ( ) ) {
return walk ( resolvedFile , cb ) ;
} else {
return new Promise ( ( resolve ) => cb ( dir , resolvedFile , resolve ) ) ;
}
} ) ) ;
}
function toSlug ( string ) {
return string . toLowerCase ( ) . replaceAll ( ' ' , '-' ) ;
}
2024-06-18 07:32:30 +00:00
function moveImportStatementUp ( filePath , times = 1 ) {
let data = fs . readFileSync ( filePath ) . toString ( ) ;
const fd = fs . openSync ( filePath , "w+" ) ;
for ( let i = 0 ; i < times ; i ++ ) {
data = data . replace ( /'\.\.\//g , '\'' ) ;
}
fs . writeSync ( fd , data ) ;
fs . closeSync ( fd ) ;
}
2024-06-02 00:24:48 +00:00
( async ( ) => {
2024-06-04 03:20:36 +00:00
const blockRefs = { } ;
const blockLinks = { } ;
const indices = [ ] ;
2024-06-02 00:24:48 +00:00
await walk ( "./garden-output/logseq-pages" , ( dir , file , resolve ) => {
const filePath = path . resolve ( dir , file ) ;
const data = fs . readFileSync ( filePath ) . toString ( ) ;
2024-06-04 03:20:36 +00:00
const slug = path . basename ( file , ".md" ) . replaceAll ( '___' , '/' ) . replaceAll ( /%3F/gi , '' ) . replace ( 'what-is-content-' , 'what-is-content' ) ;
for ( const match of data . matchAll ( /(.*)\n\s*id:: (.*)/gm ) ) {
2024-06-02 00:24:48 +00:00
const text = match [ 1 ] ;
const id = match [ 2 ] ;
2024-06-04 03:20:36 +00:00
const link = ` /garden/ ${ slug } /index.md# ${ id } ` ;
blockLinks [ id ] = link ;
blockRefs [ id ] = ` [ ${ text } ]( ${ link } ) ` ;
}
if ( data . match ( /index: "true"/g ) ) {
indices . push ( slug ) ;
2024-06-02 00:24:48 +00:00
}
resolve ( ) ;
} ) ;
const pageLinks = { } ;
const taggedBy = { } ;
const tagged = { } ;
const referencedBy = { } ;
// Walk through the pages to make sure we get the canonical name page (pre-slug)
// The logseq-export README made it sound like even the title property is transformed sometimes
await walk ( "./Garden/pages" , ( dir , file , resolve ) => {
const filePath = path . resolve ( dir , file ) ;
2024-06-15 16:10:46 +00:00
let data = fs . readFileSync ( filePath ) . toString ( ) ;
2024-06-02 00:24:48 +00:00
if ( ! data . match ( /public::/g ) ) {
resolve ( ) ;
return ;
}
2024-06-15 16:10:46 +00:00
const startPrivate = data . indexOf ( "- private" ) ;
if ( startPrivate > 0 ) {
data = data . slice ( 0 , startPrivate ) ;
}
2024-06-02 00:24:48 +00:00
const name = path . basename ( file , ".md" ) . replaceAll ( '___' , '/' ) ;
2024-06-09 22:19:05 +00:00
const slug = toSlug ( name ) . replaceAll ( /%3F/gi , '' ) . replaceAll ( '\'' , '-' ) ;
2024-06-02 00:24:48 +00:00
const link = ` /garden/ ${ slug } /index.md ` ;
2024-06-04 03:20:36 +00:00
pageLinks [ name . replaceAll ( /%3F/gi , '?' ) ] = link ;
2024-06-02 00:24:48 +00:00
2024-06-04 03:20:36 +00:00
for ( const match of data . matchAll ( /alias:: (.*)/g ) ) {
2024-06-02 00:24:48 +00:00
match [ 1 ] . split ( ", " ) . forEach ( page => ( pageLinks [ page ] = link ) ) ;
}
2024-06-04 03:20:36 +00:00
for ( const match of data . matchAll ( /tags:: (.*)/g ) ) {
2024-06-02 00:24:48 +00:00
match [ 1 ] . split ( ", " ) . forEach ( page => {
const pageSlug = toSlug ( page ) ;
taggedBy [ pageSlug ] = [ ... ( taggedBy [ pageSlug ] ? ? [ ] ) , name ] ;
tagged [ slug ] = [ ... ( tagged [ slug ] ? ? [ ] ) , page ] ;
} ) ;
}
2024-06-04 03:20:36 +00:00
if ( ! indices . includes ( slug ) ) {
for ( const match of data . matchAll ( /\[\[([^\[\]]*)\]\]/g ) ) {
const pageSlug = toSlug ( match [ 1 ] ) ;
referencedBy [ pageSlug ] = [ ... ( referencedBy [ pageSlug ] ? ? [ ] ) , name ] ;
}
2024-06-02 00:24:48 +00:00
}
resolve ( ) ;
} ) ;
Object . keys ( referencedBy ) . forEach ( page => {
referencedBy [ page ] = Array . from ( new Set ( referencedBy [ page ] ) ) ;
} ) ;
2024-06-15 16:01:51 +00:00
pageLinks [ "NOW" ] = "/now/index" ;
2024-06-02 00:24:48 +00:00
2024-06-18 07:32:30 +00:00
await walk ( "./garden-output/logseq-pages" , async ( dir , file , resolve ) => {
2024-06-02 00:24:48 +00:00
const filePath = path . resolve ( dir , file ) ;
let data = fs . readFileSync ( filePath ) . toString ( ) ;
2024-06-18 07:32:30 +00:00
// Count word counts with a special set of transformations that should make it more accurate
const strippedData = data . replace ( /---\n[\S\s]*\n---/gm , '' ) . replaceAll ( /.*::.*/g , '' ) . replaceAll ( /\[([^\]]*)\]\(.*\)/g , '$1' ) ;
const wc = wordCounting ( strippedData ) . wordsCount ;
2024-06-02 00:24:48 +00:00
// Replace youtube embeds
data = data . replaceAll (
/{{video https:\/\/(?:www\.)?youtube\.com\/watch\?v=(.*)}}/g ,
'<iframe width="560" height="315" src="https://www.youtube.com/embed/$1" title="" frameBorder="0" allowFullScreen />' ) ;
// Replace internal links
data = data . replaceAll (
/]\(\/logseq-pages\/([^\)]*)\)/g ,
'](/garden/$1/index.md)' ) ;
// Replace block links
data = data . replaceAll (
/\(\((.*)\)\)/g ,
( _ , id ) => blockRefs [ id ] ) ;
// Remove id:: lines
2024-06-06 03:56:50 +00:00
data = data . replaceAll (
/(#+) (.*)\n\s*id:: (.*)/gm ,
( _ , h , title , id ) => ` <span id=" ${ id } "><h ${ h . length } > ${ title } </h ${ h . length } ></span> ` ) ;
2024-06-02 00:24:48 +00:00
data = data . replaceAll (
2024-06-02 04:54:34 +00:00
/(.*)\n\s*id:: (.*)/gm ,
'<span id="$2">$1</span>' ) ;
2024-06-02 00:24:48 +00:00
// Fix internal links with spaces not getting mapped
data = data . replaceAll (
/\[\[([^\[\]]*)\]\]/g ,
( _ , page ) => ` [ ${ page } ]( ${ pageLinks [ page ] } ) ` ) ;
2024-06-04 03:20:36 +00:00
// Fix internal asset links
data = data . replaceAll (
/\(\/logseq-assets\/([^\)]*)\)/g ,
'(/garden/$1)' ) ;
// Fix logseq block links
data = data . replaceAll (
/logseq:\/\/graph\/Garden\?block-id=([^\)]*)/g ,
( _ , block ) => ` ${ blockLinks [ block ] } ) ` ) ;
// Fix logseq page links
data = data . replaceAll (
/logseq:\/\/graph\/Garden\?page=([^\)]*)/g ,
( _ , page ) => ` ${ pageLinks [ page . replaceAll ( '%20' , ' ' ) ] } ) ` ) ;
2024-06-18 07:32:30 +00:00
// Wrap images
data = data . replaceAll (
/!\[([^\]]*)\]\(([^\)]*)\)/g ,
( _ , title , src ) => ` <div class="img-container"><img src=" ${ src } " title=" ${ title } "/></div> ` )
2024-06-02 00:24:48 +00:00
// Add tags and references
const title = path . basename ( file , ".md" ) ;
if ( title in tagged ) {
data = data . replaceAll (
/---\n\n/gm ,
` --- \n \n > Tags: ${ tagged [ title ] . map ( tag => ` [ ${ tag } ]( ${ pageLinks [ tag ] } ) ` ) . join ( ", " ) } \n \n ` ) ;
}
if ( title in taggedBy ) {
data = data . replaceAll (
/---\n\n/gm ,
` --- \n \n > Tagged by: ${ taggedBy [ title ] . map ( tag => ` [ ${ tag } ]( ${ pageLinks [ tag ] } ) ` ) . join ( ", " ) } \n \n ` ) ;
}
// TODO show context on references? Perhaps in a `::: info` block?
if ( title in referencedBy ) {
data = data . replaceAll (
/---\n\n/gm ,
` --- \n \n > Referenced by: ${ referencedBy [ title ] . map ( tag => ` [ ${ tag } ]( ${ pageLinks [ tag ] } ) ` ) . join ( ", " ) } \n \n ` ) ;
}
2024-06-15 16:01:51 +00:00
// Fix links to /now
2024-06-15 16:05:14 +00:00
data = data . replace ( 'NOW' , '/now' )
2024-06-18 07:32:30 +00:00
// Add header to the top
const relPath = path . relative ( "./garden-output/logseq-pages" , path . resolve ( ... filePath . split ( "___" ) ) ) . replaceAll ( /%3F/gi , '' ) . replace ( 'what-is-content-' , 'what-is-content' ) . replace ( '.md' , '/index.md' ) ;
2024-06-02 04:54:40 +00:00
data = data . replaceAll (
/---\n\n/gm ,
2024-06-18 07:32:30 +00:00
` prev: false \n next: false \n --- \n <script setup> \n import { data } from ' ${ path . relative ( path . resolve ( "site" , relPath ) , path . resolve ( "site" , "git.data.ts" ) ) . replaceAll ( '\\' , '/' ) } '; \n import { useData } from 'vitepress'; \n const pageData = useData(); \n </script> \n <h1 class="p-name"> ${ data . match ( /title: "(.+)"/ ) [ 1 ] } </h1> \n <p> ${ wc } words, ~ ${ Math . round ( wc / 183 ) } minute read. <span v-html="data[ \` site/ \$ {pageData.page.value.relativePath} \` ]" /></p> \n <hr/> \n \n ` ) ;
2024-06-02 00:24:48 +00:00
const fd = fs . openSync ( filePath , "w+" ) ;
fs . writeSync ( fd , data ) ;
fs . closeSync ( fd ) ;
resolve ( ) ;
} ) ;
2024-06-14 03:54:30 +00:00
fs . mkdirSync ( "./site/public/garden" , { recursive : true } ) ;
2024-06-02 00:24:48 +00:00
// Move everything from ./garden-output/logseq-pages into ./site/garden
await walk ( "./garden-output/logseq-pages" , ( dir , file , resolve ) => {
2024-06-04 03:20:36 +00:00
const folder = path . resolve ( "./site/garden" , ... path . basename ( file , ".md" ) . split ( '___' ) ) ;
2024-06-14 03:54:30 +00:00
fs . mkdirSync ( folder , { recursive : true } ) ;
2024-06-02 00:24:48 +00:00
fs . copyFileSync ( path . resolve ( dir , file ) , path . resolve ( folder , "index.md" ) ) ;
resolve ( ) ;
} ) ;
2024-06-04 03:20:36 +00:00
// Move everything from ./garden-output/logseq-assets into ./site/public/garden
2024-06-02 00:24:48 +00:00
await walk ( "./garden-output/logseq-assets" , ( dir , file , resolve ) => {
2024-06-04 03:20:36 +00:00
fs . copyFileSync ( path . resolve ( dir , file ) , path . resolve ( "./site/public/garden" , ... path . basename ( file ) . split ( '___' ) ) ) ;
2024-06-02 00:24:48 +00:00
resolve ( ) ;
} ) ;
2024-06-04 03:20:36 +00:00
// Copy the guide-to-incrementals pages to the old locations so links don't break
fs . mkdirSync ( './site/guide-to-incrementals' ) ;
fs . copyFileSync ( './site/garden/guide-to-incrementals/index.md' , './site/guide-to-incrementals/index.md' ) ;
2024-06-18 07:32:30 +00:00
moveImportStatementUp ( './site/guide-to-incrementals/index.md' ) ;
2024-06-04 03:20:36 +00:00
fs . mkdirSync ( './site/guide-to-incrementals/design' ) ;
fs . mkdirSync ( './site/guide-to-incrementals/design/criticism' ) ;
fs . copyFileSync ( './site/garden/guide-to-incrementals/navigating-criticism/index.md' , './site/guide-to-incrementals/design/criticism/index.md' ) ;
fs . mkdirSync ( './site/guide-to-incrementals/ludology' ) ;
fs . mkdirSync ( './site/guide-to-incrementals/ludology/appeal-developers' ) ;
fs . copyFileSync ( './site/garden/guide-to-incrementals/appeal-to-developers/index.md' , './site/guide-to-incrementals/ludology/appeal-developers/index.md' ) ;
fs . mkdirSync ( './site/guide-to-incrementals/ludology/appeal-gamers' ) ;
fs . copyFileSync ( './site/garden/guide-to-incrementals/appeal-to-players/index.md' , './site/guide-to-incrementals/ludology/appeal-gamers/index.md' ) ;
fs . mkdirSync ( './site/guide-to-incrementals/ludology/content' ) ;
// For what is content, also remove the - at the end
fs . cpSync ( './site/garden/guide-to-incrementals/what-is-content-' , './site/garden/guide-to-incrementals/what-is-content' , { recursive : true } ) ;
fs . copyFileSync ( './site/garden/guide-to-incrementals/what-is-content-/index.md' , './site/guide-to-incrementals/ludology/content/index.md' ) ;
fs . rmSync ( './site/garden/guide-to-incrementals/what-is-content-' , { recursive : true } ) ;
fs . mkdirSync ( './site/guide-to-incrementals/ludology/definition' ) ;
fs . copyFileSync ( './site/garden/guide-to-incrementals/defining-the-genre/index.md' , './site/guide-to-incrementals/ludology/definition/index.md' ) ;
2024-06-06 03:08:02 +00:00
2024-06-15 16:01:51 +00:00
fs . mkdirSync ( './site/now' ) ;
fs . renameSync ( './site/garden/now/index.md' , './site/now/index.md' ) ;
2024-06-18 07:32:30 +00:00
moveImportStatementUp ( './site/now/index.md' ) ;
2024-06-15 16:01:51 +00:00
2024-06-06 03:08:02 +00:00
// Build changelog
fs . mkdirSync ( "./site/changelog" ) ;
2024-06-11 04:13:50 +00:00
const feed = new Feed ( {
title : "The Paper Pilot's Digital Garden Changelog" ,
description : "A feed of updates made to my digital garden!" ,
id : "https://www.thepaperpilot.org/changelog/" ,
link : "https://www.thepaperpilot.org/changelog/" ,
language : "en" , // optional, used only in RSS 2.0, possible values: http://www.w3.org/TR/REC-html40/struct/dirlang.html#langcodes
// image: "http://example.com/image.png",
// favicon: "http://example.com/favicon.ico",
copyright : ` All rights reserved ${ new Date ( ) . getFullYear ( ) } , The Paper Pilot ` ,
// updated: new Date(2013, 6, 14), // optional, default = today
// generator: "awesome", // optional, default = 'Feed for Node.js'
feedLinks : {
rss : "https://www.thepaperpilot.org/changelog/rss" ,
json : "https://www.thepaperpilot.org/changelog/json" ,
atom : "https://www.thepaperpilot.org/changelog/atom"
} ,
author : {
name : "The Paper Pilot" ,
email : "thepaperpilot@incremental.social" ,
link : "https://www.thepaperpilot.org/"
}
} ) ;
2024-06-11 02:37:24 +00:00
const { stdout } = await exec ( 'git log --after="2024-06-03T0:0:0+0000" --pretty=%H origin/master -- site/garden' ) ;
2024-06-06 03:08:02 +00:00
const entries = await Promise . all ( stdout . split ( "\n" ) . filter ( p => p ) . map ( hash => new Promise ( async ( resolve ) => {
const { stdout : time } = await exec ( ` git show --quiet --format=%as ${ hash } ` ) ;
2024-06-11 03:03:14 +00:00
let { stdout : changes } = await exec ( ` git show --format="" --stat --relative ${ hash } . ` , { cwd : 'site/garden' } ) ;
2024-06-06 03:08:02 +00:00
changes = changes . replaceAll ( /\/index.md/g , '' ) ;
changes = changes . replaceAll (
/(\| +[0-9]+ \+*)(-+)/g ,
'$1<span style="color:#BF616A">$2</span>' ) ;
2024-06-11 03:03:14 +00:00
changes = changes . replaceAll (
/(\| +[0-9]+ )(\++)/g ,
'$1<span style="color:#A3BE8C">$2</span>' ) ;
2024-06-06 03:08:02 +00:00
const lines = changes . split ( '\n' ) ;
const summary = lines [ lines . length - 2 ] ;
changes = lines . slice ( 0 , - 2 ) . map ( line => {
const [ page , changes ] = line . split ( "|" ) . map ( p => p . trim ( ) ) ;
2024-06-11 03:31:47 +00:00
return ` <tr><td><a href="/garden/ ${ page } "> ${ page } </a></td><td style="font-family: monospace; white-space: nowrap;"> ${ changes } </td></tr> ` ;
2024-06-06 03:08:02 +00:00
} ) . join ( "\n" ) ;
2024-06-11 04:13:50 +00:00
const commitLink = ` https://code.incremental.social/thepaperpilot/pages/commit/ ${ hash } `
const content = ` <table>
< thead >
< tr >
< th style = "align: center" > Page < / t h >
< th style = "align: center" > Changes < / t h >
< / t r >
< / t h e a d >
< tbody >
$ { changes }
< / t b o d y >
< / t a b l e > ` ;
feed . addItem ( {
title : summary ,
id : commitLink ,
link : commitLink ,
description : summary ,
content ,
date : new Date ( time )
} ) ;
2024-06-06 03:08:02 +00:00
resolve (
2024-06-11 03:48:46 +00:00
` <article class="h-entry">
2024-06-11 03:31:47 +00:00
< h2 class = "p-name" > $ { summary } < / h 2 >
2024-06-17 16:48:04 +00:00
< div class = "e-content" >
2024-06-11 04:13:50 +00:00
< a class = "u-url" href = "${commitLink}" > Pushed on < time class = "dt-published" > $ { time } < / t i m e > < / a >
2024-06-06 03:08:02 +00:00
< table >
< thead >
< tr >
< th style = "align: center" > Page < / t h >
< th style = "align: center" > Changes < / t h >
< / t r >
< / t h e a d >
< tbody >
$ { changes }
< / t b o d y >
< / t a b l e >
2024-06-17 16:48:04 +00:00
< / d i v >
2024-06-06 03:08:02 +00:00
< / a r t i c l e > ` ) ;
} ) ) ) ;
2024-06-11 04:13:50 +00:00
let fd = fs . openSync ( "site/changelog/index.md" , "w+" ) ;
2024-06-06 03:08:02 +00:00
fs . writeSync ( fd ,
` ---
title : Changelog
2024-06-06 04:08:18 +00:00
prev : false
next : false
2024-06-06 03:08:02 +00:00
-- -
< section class = "h-feed" >
2024-06-11 04:24:16 +00:00
< h1 class = "p-name" > Changelog < / h 1 >
2024-06-06 04:08:18 +00:00
< p > This feed starts when I formatted the site to be a < a href = "/garden/digital-gardens/" > Digital Garden < /a>. If you'd like to look further into this site's history, check <a href="https:/ / code . incremental . social / thepaperpilot / pages / commits / branch / master " > here < / a > ! < / p >
2024-06-06 03:08:02 +00:00
$ { entries . join ( "\n\n" ) }
< / s e c t i o n >
2024-06-11 04:13:50 +00:00
` );
fs . closeSync ( fd ) ;
2024-06-11 04:31:03 +00:00
fs . mkdirSync ( "site/public/changelog" ) ;
2024-06-11 04:29:39 +00:00
2024-06-11 04:26:54 +00:00
fd = fs . openSync ( "site/public/changelog/rss" , "w+" ) ;
2024-06-11 04:13:50 +00:00
fs . writeSync ( fd , feed . rss2 ( ) ) ;
fs . closeSync ( fd ) ;
2024-06-11 04:26:54 +00:00
fd = fs . openSync ( "site/public/changelog/atom" , "w+" ) ;
2024-06-11 04:13:50 +00:00
fs . writeSync ( fd , feed . atom1 ( ) ) ;
fs . closeSync ( fd ) ;
2024-06-11 04:26:54 +00:00
fd = fs . openSync ( "site/public/changelog/json" , "w+" ) ;
2024-06-11 04:13:50 +00:00
fs . writeSync ( fd , feed . json1 ( ) ) ;
2024-06-06 03:08:02 +00:00
fs . closeSync ( fd ) ;
2024-06-17 05:08:45 +00:00
// Update commit info in footer
const commitLink = ( await exec ( ` git log -n 1 --format="https://code.incremental.social/thepaperpilot/pages/commit/%H" ` ) ) . stdout . replaceAll ( /\n$/g , '' ) ;
const commitTime = ( await exec ( ` git log -n 1 --date=format:"%A, %B %d, %Y at %X" --format=%ad ` ) ) . stdout . replaceAll ( /\n$/g , '' ) ;
fd = fs . openSync ( "site/.vitepress/theme/Layout.vue" , "w+" ) ;
let layoutData = fs . readFileSync ( "site/.vitepress/theme/Layout.vue.in" ) . toString ( ) ;
layoutData = layoutData . replace ( /COMMIT_LINK/g , commitLink ) ;
layoutData = layoutData . replace ( /COMMIT_TIME/g , commitTime ) ;
fs . writeSync ( fd , layoutData ) ;
fs . closeSync ( fd ) ;
2024-06-02 00:24:48 +00:00
} ) ( ) ;