@@ -234,8 +234,16 @@ function imgFigure(href, alt, title, kls) {
234234
235235// ── content loaders ───────────────────────────────────────────
236236
237- async function loadMusings ( ) {
238- const indices = await fg ( 'musings/**/index.md' , { cwd : SRC ( 'content' ) } ) ;
237+ // Post sections: each is a musings-shaped tree of markdown posts with its own
238+ // nav tab, index, category pages, tags, and year archives. Home page, feeds,
239+ // and gallery synthesis remain musings-only.
240+ const SECTIONS = {
241+ musings : { root : 'musings' , base : '/musings' , active : 'musings' } ,
242+ rv12is : { root : 'rv12is' , base : '/rv12is' , active : 'rv12is' } ,
243+ } ;
244+
245+ async function loadPosts ( root ) {
246+ const indices = await fg ( root + '/**/index.md' , { cwd : SRC ( 'content' ) } ) ;
239247 const posts = [ ] ;
240248 for ( const rel of indices ) {
241249 const full = path . join ( SRC ( 'content' ) , rel ) ;
@@ -246,7 +254,7 @@ async function loadMusings() {
246254 const fm = parsed . data ;
247255 const folder = path . dirname ( rel ) ; // musings/travel/japan-tips
248256 const segs = folder . split ( '/' ) ;
249- if ( segs [ 0 ] !== 'musings' ) { fail ( `Bad musings path: ${ rel } ` ) ; continue ; }
257+ if ( segs [ 0 ] !== root ) { fail ( `Bad ${ root } path: ${ rel } ` ) ; continue ; }
250258 const slug = segs [ segs . length - 1 ] ;
251259 const categorySegs = segs . slice ( 1 , - 1 ) ; // [travel]
252260 if ( ! fm . title ) { fail ( `${ rel } : frontmatter is missing required field 'title'` ) ; continue ; }
@@ -331,7 +339,7 @@ async function loadMusings() {
331339 url,
332340 folder,
333341 category : categorySegs ,
334- categoryPath : categorySegs . length ? '/musings /' + categorySegs . join ( '/' ) : '/musings' ,
342+ categoryPath : categorySegs . length ? '/' + root + ' /' + categorySegs . join ( '/' ) : '/' + root ,
335343 title : fm . title ,
336344 subtitle : fm . subtitle || '' ,
337345 date : new Date ( fm . date ) ,
@@ -549,13 +557,13 @@ async function renderHome(musings, mediaNodes) {
549557
550558// Build a folder/post tree for the sidebar.
551559// Returns an array of nodes: { type:'folder'|'post', name, url, path, children, post? }
552- function buildMusingsTree ( musings ) {
553- const root = { type : 'folder' , name : 'musings' , url : '/musings' , path : [ ] , children : [ ] } ;
560+ function buildSectionTree ( posts , section ) {
561+ const root = { type : 'folder' , name : section . root , url : section . base , path : [ ] , children : [ ] } ;
554562 const folders = new Map ( ) ; // key -> node
555563 folders . set ( '' , root ) ;
556564
557565 // Ensure all folder ancestors exist for each post.
558- for ( const m of musings ) {
566+ for ( const m of posts ) {
559567 let parentKey = '' ;
560568 let parentNode = root ;
561569 for ( let d = 0 ; d < m . category . length ; d ++ ) {
@@ -565,7 +573,7 @@ function buildMusingsTree(musings) {
565573 const node = {
566574 type : 'folder' ,
567575 name : segs [ segs . length - 1 ] ,
568- url : '/musings /' + key ,
576+ url : section . base + ' /' + key ,
569577 path : segs ,
570578 children : [ ] ,
571579 } ;
@@ -689,7 +697,7 @@ function synthesizeFromMusings(musings, existingNodes) {
689697// Uses nested <ul> so long names wrap naturally and don't overflow.
690698function renderTreeHtml ( tree , activeUrl , activeCategory ) {
691699 function walk ( node , depth ) {
692- const name = node . type === 'folder' ? ( depth === 0 ? 'musings/' : node . name + '/' ) : node . name ;
700+ const name = node . type === 'folder' ? node . name + '/' : node . name ;
693701 const isActive = node . url === activeUrl ;
694702 const inActive = ! isActive && activeCategory && node . type === 'folder' && depth > 0 && activeCategory . startsWith ( node . path . join ( '/' ) ) ;
695703 const cls = [ 'tree-item' ] ;
@@ -706,34 +714,37 @@ function renderTreeHtml(tree, activeUrl, activeCategory) {
706714 return '<ul class="tree-list">' + walk ( tree , 0 ) + '</ul>' ;
707715}
708716
709- // ── render: musings ─────────────────────────── ────────────────
717+ // ── render: post sections ( musings, rv12is, …) ────────────────
710718
711- async function renderMusings ( musings ) {
712- // Folder tree, used by all musings pages (index + post + category).
713- const tree = buildMusingsTree ( musings ) ;
714- ALL_POST_URLS = musings . map ( m => m . url ) ;
719+ async function renderPostSection ( posts , section ) {
720+ const sc = COPY [ section . root ] ;
721+ // Folder tree, used by all section pages (index + post + category).
722+ const tree = buildSectionTree ( posts , section ) ;
723+ allRoutes . add ( section . base ) ;
715724
716725 // Index page.
717726 const indexHtml = await renderPage ( 'musings-index' , {
718727 page : {
719- title : 'musings — ' + SITE . title ,
720- description : `All musings by ${ SITE . author } . ${ musings . length } posts on travel, code, aviation, home projects.` ,
721- url : '/musings' ,
722- bodyClass : pageHelpers . bodyClass ( 'musings' ) ,
728+ title : section . root + ' — ' + SITE . title ,
729+ description : ( sc . index . seo_description || '' )
730+ . replace ( '{count}' , posts . length ) . replace ( '{author}' , SITE . author ) ,
731+ url : section . base ,
732+ bodyClass : pageHelpers . bodyClass ( section . active ) ,
723733 type : 'website' ,
724734 } ,
725- active : 'musings' ,
726- posts : musings ,
727- treeHtml : renderTreeHtml ( tree , '/musings' , '' ) ,
735+ active : section . active ,
736+ section, sectionCopy : sc ,
737+ posts,
738+ treeHtml : renderTreeHtml ( tree , section . base , '' ) ,
728739 } ) ;
729- collectLinks ( '/musings' , indexHtml ) ;
730- await writeFile ( 'musings/ index.html', indexHtml ) ;
740+ collectLinks ( section . base , indexHtml ) ;
741+ await writeFile ( path . join ( section . root , ' index.html') , indexHtml ) ;
731742
732743 // Helper: pick up to N related posts that share the most tags with a given post.
733744 function relatedFor ( m , n = 3 ) {
734745 const mTags = new Set ( m . tags || [ ] ) ;
735746 if ( mTags . size === 0 ) return [ ] ;
736- return musings
747+ return posts
737748 . filter ( o => o . url !== m . url )
738749 . map ( o => ( { post : o , score : ( o . tags || [ ] ) . filter ( t => mTags . has ( t ) ) . length } ) )
739750 . filter ( x => x . score > 0 )
@@ -743,21 +754,22 @@ async function renderMusings(musings) {
743754 }
744755
745756 // Per-post — uses the same tree, marking the current item active.
746- for ( let i = 0 ; i < musings . length ; i ++ ) {
747- const m = musings [ i ] ;
748- const prev = musings [ i + 1 ] || null ;
749- const next = musings [ i - 1 ] || null ;
757+ for ( let i = 0 ; i < posts . length ; i ++ ) {
758+ const m = posts [ i ] ;
759+ const prev = posts [ i + 1 ] || null ;
760+ const next = posts [ i - 1 ] || null ;
750761 const html = await renderPage ( 'musing' , {
751762 page : {
752763 title : `${ m . title } — ${ SITE . title } ` ,
753764 description : m . seo . description || truncate ( m . preview , 158 ) ,
754765 keywords : m . seo . keywords || m . tags ,
755766 url : m . url ,
756- bodyClass : pageHelpers . bodyClass ( 'musings' ) ,
767+ bodyClass : pageHelpers . bodyClass ( section . active ) ,
757768 type : 'article' ,
758769 ogImage : m . featured ,
759770 } ,
760- active : 'musings' ,
771+ active : section . active ,
772+ section, sectionCopy : sc ,
761773 post : m ,
762774 prev,
763775 next,
@@ -786,7 +798,7 @@ async function renderMusings(musings) {
786798
787799 // Categories.
788800 const cats = new Map ( ) ; // path -> { segs, posts, children }
789- for ( const m of musings ) {
801+ for ( const m of posts ) {
790802 for ( let d = 1 ; d <= m . category . length ; d ++ ) {
791803 const segs = m . category . slice ( 0 , d ) ;
792804 const key = segs . join ( '/' ) ;
@@ -803,24 +815,25 @@ async function renderMusings(musings) {
803815 }
804816
805817 for ( const [ key , cat ] of cats ) {
806- const url = '/musings /' + key ;
807- const allDescendants = musings . filter ( m => m . category . slice ( 0 , cat . segs . length ) . join ( '/' ) === key ) ;
818+ const url = section . base + ' /' + key ;
819+ const allDescendants = posts . filter ( m => m . category . slice ( 0 , cat . segs . length ) . join ( '/' ) === key ) ;
808820 const directPosts = cat . posts . slice ( ) . sort ( ( a , b ) => b . date - a . date ) ;
809821 const children = [ ...cat . childKeys ] . map ( k => ( {
810822 key : k ,
811823 label : k . split ( '/' ) . pop ( ) ,
812- url : '/musings /' + k ,
813- count : musings . filter ( m => m . category . slice ( 0 , k . split ( '/' ) . length ) . join ( '/' ) === k ) . length ,
824+ url : section . base + ' /' + k ,
825+ count : posts . filter ( m => m . category . slice ( 0 , k . split ( '/' ) . length ) . join ( '/' ) === k ) . length ,
814826 } ) ) ;
815827 const html = await renderPage ( 'musings-category' , {
816828 page : {
817- title : `${ key } — musings — ${ SITE . title } ` ,
829+ title : `${ key } — ${ section . root } — ${ SITE . title } ` ,
818830 description : `Posts about ${ cat . segs . join ( ' / ' ) } by ${ SITE . author } .` ,
819831 url,
820- bodyClass : pageHelpers . bodyClass ( 'musings' ) ,
832+ bodyClass : pageHelpers . bodyClass ( section . active ) ,
821833 type : 'website' ,
822834 } ,
823- active : 'musings' ,
835+ active : section . active ,
836+ section, sectionCopy : sc ,
824837 categoryKey : key ,
825838 categorySegs : cat . segs ,
826839 posts : directPosts ,
@@ -830,82 +843,85 @@ async function renderMusings(musings) {
830843 randomPick : allDescendants [ Math . floor ( ( allDescendants . length || 1 ) / 2 ) % Math . max ( allDescendants . length , 1 ) ] ,
831844 } ) ;
832845 collectLinks ( url , html ) ;
833- await writeFile ( path . join ( 'musings' , key , 'index.html' ) , html ) ;
846+ await writeFile ( path . join ( section . root , key , 'index.html' ) , html ) ;
834847 allRoutes . add ( url ) ;
835848 }
836849
837850 // Tags.
838851 const tags = new Map ( ) ;
839- for ( const m of musings ) for ( const t of m . tags ) {
852+ for ( const m of posts ) for ( const t of m . tags ) {
840853 if ( ! tags . has ( t ) ) tags . set ( t , [ ] ) ;
841854 tags . get ( t ) . push ( m ) ;
842855 }
843856
844- // /musings /tags — alphabetical list of all tags + counts.
857+ // /<section> /tags — alphabetical list of all tags + counts.
845858 const tagList = [ ...tags . entries ( ) ]
846- . map ( ( [ name , posts ] ) => ( { name, slug : slugify ( name ) , count : posts . length } ) )
859+ . map ( ( [ name , tagged ] ) => ( { name, slug : slugify ( name ) , count : tagged . length } ) )
847860 . sort ( ( a , b ) => a . name . localeCompare ( b . name ) ) ;
848861 const tagsIndexHtml = await renderPage ( 'musings-tags-index' , {
849862 page : {
850863 title : `tags — ${ SITE . title } ` ,
851- description : `All tags used across ${ musings . length } musings .` ,
852- url : '/musings/ tags' ,
853- bodyClass : pageHelpers . bodyClass ( 'musings' ) ,
864+ description : `All tags used across ${ posts . length } posts .` ,
865+ url : ` ${ section . base } / tags` ,
866+ bodyClass : pageHelpers . bodyClass ( section . active ) ,
854867 type : 'website' ,
855868 } ,
856- active : 'musings' ,
869+ active : section . active ,
870+ section, sectionCopy : sc ,
857871 tagList,
858872 } ) ;
859- collectLinks ( '/musings/ tags' , tagsIndexHtml ) ;
860- await writeFile ( 'musings/ tags/ index.html', tagsIndexHtml ) ;
861- allRoutes . add ( '/musings/ tags' ) ;
873+ collectLinks ( ` ${ section . base } / tags` , tagsIndexHtml ) ;
874+ await writeFile ( path . join ( section . root , ' tags' , ' index.html') , tagsIndexHtml ) ;
875+ allRoutes . add ( ` ${ section . base } / tags` ) ;
862876
863- // /musings /YYYY — year archives.
877+ // /<section> /YYYY — year archives.
864878 const byYear = new Map ( ) ;
865- for ( const m of musings ) {
879+ for ( const m of posts ) {
866880 const y = String ( m . date . getUTCFullYear ( ) ) ;
867881 if ( ! byYear . has ( y ) ) byYear . set ( y , [ ] ) ;
868882 byYear . get ( y ) . push ( m ) ;
869883 }
870884 const allYears = [ ...byYear . keys ( ) ] . sort ( ) . reverse ( ) ;
871885 for ( const year of allYears ) {
872- const posts = byYear . get ( year ) ;
886+ const yearPosts = byYear . get ( year ) ;
873887 const otherYears = allYears . filter ( y => y !== year ) ;
874888 const yearHtml = await renderPage ( 'musings-year' , {
875889 page : {
876- title : `${ year } — musings — ${ SITE . title } ` ,
890+ title : `${ year } — ${ section . root } — ${ SITE . title } ` ,
877891 description : `Posts from ${ year } by ${ SITE . author } .` ,
878- url : `/musings /${ year } ` ,
879- bodyClass : pageHelpers . bodyClass ( 'musings' ) ,
892+ url : `${ section . base } /${ year } ` ,
893+ bodyClass : pageHelpers . bodyClass ( section . active ) ,
880894 type : 'website' ,
881895 } ,
882- active : 'musings' ,
896+ active : section . active ,
897+ section, sectionCopy : sc ,
883898 year,
884- posts,
899+ posts : yearPosts ,
885900 otherYears,
886901 } ) ;
887- collectLinks ( `/musings /${ year } ` , yearHtml ) ;
888- await writeFile ( `musings/ ${ year } / index.html` , yearHtml ) ;
889- allRoutes . add ( `/musings /${ year } ` ) ;
902+ collectLinks ( `${ section . base } /${ year } ` , yearHtml ) ;
903+ await writeFile ( path . join ( section . root , year , ' index.html' ) , yearHtml ) ;
904+ allRoutes . add ( `${ section . base } /${ year } ` ) ;
890905 }
891906
892- for ( const [ tag , posts ] of tags ) {
893- const url = `/musings /tag/${ slugify ( tag ) } ` ;
907+ for ( const [ tag , tagged ] of tags ) {
908+ const url = `${ section . base } /tag/${ slugify ( tag ) } ` ;
894909 const html = await renderPage ( 'musings-tag' , {
895910 page : {
896911 title : `#${ tag } — ${ SITE . title } ` ,
897912 description : `Posts tagged "${ tag } " by ${ SITE . author } .` ,
898- keywords : [ tag , ...posts . flatMap ( p => p . tags ) ] . slice ( 0 , 10 ) ,
913+ keywords : [ tag , ...tagged . flatMap ( p => p . tags ) ] . slice ( 0 , 10 ) ,
899914 url,
900- bodyClass : pageHelpers . bodyClass ( 'musings' ) ,
915+ bodyClass : pageHelpers . bodyClass ( section . active ) ,
901916 type : 'website' ,
902917 } ,
903- active : 'musings' ,
918+ active : section . active ,
919+ section, sectionCopy : sc ,
904920 tag,
905- posts,
921+ posts : tagged ,
906922 } ) ;
907923 collectLinks ( url , html ) ;
908- await writeFile ( `musings/ tag/ ${ slugify ( tag ) } / index.html` , html ) ;
924+ await writeFile ( path . join ( section . root , ' tag' , slugify ( tag ) , ' index.html' ) , html ) ;
909925 allRoutes . add ( url ) ;
910926 }
911927}
@@ -1227,8 +1243,9 @@ async function main() {
12271243 await ensureDir ( OUT ) ;
12281244
12291245 await copyDesign ( ) ;
1230- const musings = await loadMusings ( ) ;
1231- log ( `${ musings . length } musings` ) ;
1246+ const musings = await loadPosts ( 'musings' ) ;
1247+ const rvPosts = await loadPosts ( 'rv12is' ) ;
1248+ log ( `${ musings . length } musings, ${ rvPosts . length } rv12is posts` ) ;
12321249 const { nodes : realMediaNodes } = await loadMedia ( ) ;
12331250 const synthNodes = synthesizeFromMusings ( musings , realMediaNodes ) ;
12341251 const mediaNodes = realMediaNodes . concat ( synthNodes ) ;
@@ -1267,7 +1284,9 @@ async function main() {
12671284 log ( `${ realMediaNodes . length } real + ${ synthNodes . length } synthesized gallery nodes` ) ;
12681285
12691286 await renderHome ( musings , mediaNodes ) ;
1270- await renderMusings ( musings ) ;
1287+ ALL_POST_URLS = musings . map ( m => m . url ) ;
1288+ await renderPostSection ( musings , SECTIONS . musings ) ;
1289+ await renderPostSection ( rvPosts , SECTIONS . rv12is ) ;
12711290 await renderMedia ( mediaNodes ) ;
12721291 await renderChangelog ( ) ;
12731292 await renderLicense ( ) ;
0 commit comments