11/**
22 * @import {Root} from 'hast'
33 * @import {DataMap} from 'vfile'
4+ * @import {Entry as FeedEntry} from 'xast-util-feed'
45 * @import {Entry as SitemapEntry} from 'xast-util-sitemap'
56 * @import {Human} from '../data/humans.js'
67 * @import {Release} from '../data/releases.js'
2930import assert from 'node:assert/strict'
3031import fs from 'node:fs/promises'
3132import { glob } from 'glob'
33+ import { fromHtml } from 'hast-util-from-html'
34+ import { isElement } from 'hast-util-is-element'
35+ import { sanitize } from 'hast-util-sanitize'
36+ import { select } from 'hast-util-select'
37+ import { toHtml } from 'hast-util-to-html'
38+ import { urlAttributes } from 'html-url-attributes'
3239import { read , write } from 'to-vfile'
40+ import { visit } from 'unist-util-visit'
3341import { matter } from 'vfile-matter'
3442import { reporter } from 'vfile-reporter'
3543import { VFile } from 'vfile'
44+ import { rss } from 'xast-util-feed'
3645import { sitemap } from 'xast-util-sitemap'
3746import { toXml } from 'xast-util-to-xml'
3847import yaml from 'yaml'
@@ -416,6 +425,9 @@ page(
416425
417426/** @type {Array<SitemapEntry> } */
418427const sitemapEntries = [ ]
428+ /** @type {Array<VFile> } */
429+ const learnFiles = [ ]
430+ const now = new Date ( )
419431
420432for ( const render of tasks ) {
421433 const { tree, file} = await render ( )
@@ -428,14 +440,18 @@ for (const render of tasks) {
428440 const modified = matter . modified || meta . modified
429441 assert ( pathname )
430442 sitemapEntries . push ( { url : new URL ( pathname , origin ) . href , modified} )
443+
444+ if ( matter . group ) {
445+ learnFiles . push ( file )
446+ }
431447}
432448
433449await fs . writeFile (
434- new URL ( '../build/sitemap.xml ' , import . meta. url ) ,
435- toXml ( sitemap ( sitemapEntries ) )
450+ new URL ( '../build/CNAME ' , import . meta. url ) ,
451+ new URL ( origin ) . host + '\n'
436452)
437453
438- console . log ( '✔ `/sitemap.xml `' )
454+ console . error ( '✔ `/CNAME `' )
439455
440456await fs . writeFile (
441457 new URL ( '../build/robots.txt' , import . meta. url ) ,
@@ -447,7 +463,104 @@ await fs.writeFile(
447463 ] . join ( '\n' )
448464)
449465
450- console . log ( '✔ `/robots.txt`' )
466+ console . error ( '✔ `/robots.txt`' )
467+
468+ await fs . writeFile (
469+ new URL ( '../build/sitemap.xml' , import . meta. url ) ,
470+ toXml ( sitemap ( sitemapEntries ) )
471+ )
472+
473+ console . error ( '✔ `/sitemap.xml`' )
474+
475+ learnFiles . sort ( function ( a , b ) {
476+ assert ( a . data . matter ?. published )
477+ assert ( b . data . matter ?. published )
478+ return (
479+ new Date ( b . data . matter . published ) . valueOf ( ) -
480+ new Date ( a . data . matter . published ) . valueOf ( )
481+ )
482+ } )
483+
484+ const newestLearnFiles = learnFiles . slice ( 0 , 10 )
485+ /** @type {Array<FeedEntry> } */
486+ const learnEntries = [ ]
487+
488+ for ( const file of newestLearnFiles ) {
489+ const tree = fromHtml ( file . value )
490+ const body = select ( 'main' , tree )
491+ assert ( body )
492+ const fragment = sanitize ( body )
493+
494+ const { matter, meta} = file . data
495+ assert ( matter )
496+ assert ( meta )
497+ assert ( meta . pathname )
498+ const base = new URL ( meta . pathname , origin )
499+
500+ visit ( fragment , 'element' , function ( node , index , parent ) {
501+ // Make URLs absolute.
502+ for ( const property in node . properties ) {
503+ if (
504+ Object . hasOwn ( urlAttributes , property ) &&
505+ isElement ( node , urlAttributes [ property ] ) &&
506+ node . properties [ property ] != null
507+ ) {
508+ node . properties [ property ] = new URL (
509+ String ( node . properties [ property ] ) ,
510+ base
511+ ) . href
512+ }
513+ }
514+
515+ if ( parent && typeof index === 'number' ) {
516+ // Drop empty spans, left from syntax highlighting.
517+ if (
518+ node . tagName === 'span' &&
519+ Object . keys ( node . properties ) . length === 0
520+ ) {
521+ parent . children . splice ( index , 1 , ...node . children )
522+ return index
523+ }
524+
525+ // Drop tooltips from twoslash.
526+ if (
527+ node . tagName === 'div' &&
528+ typeof node . properties . id === 'string' &&
529+ node . properties . id . startsWith ( 'user-content-rehype-twoslash' )
530+ ) {
531+ parent . children . splice ( index , 1 )
532+ return index
533+ }
534+ }
535+ } )
536+
537+ learnEntries . push ( {
538+ author : matter . author ,
539+ descriptionHtml : toHtml ( fragment ) ,
540+ description : matter . description ,
541+ modified : matter . modified ,
542+ published : matter . published ,
543+ title : matter . title ,
544+ url : base . href
545+ } )
546+ }
547+
548+ await fs . writeFile (
549+ new URL ( '../build/rss.xml' , import . meta. url ) ,
550+ toXml (
551+ rss (
552+ {
553+ feedUrl : new URL ( 'rss.xml' , origin ) . href ,
554+ lang : 'en' ,
555+ title : 'unified - learn' ,
556+ url : origin
557+ } ,
558+ learnEntries
559+ )
560+ ) + '\n'
561+ )
562+
563+ console . error ( '✔ `/rss.xml`' )
451564
452565/**
453566 *
0 commit comments