@@ -398,6 +398,7 @@ <h2>Links sent in previous newsletters</h2>
398398 let quotations = [ ] ;
399399 let tils = [ ] ;
400400 let notes = [ ] ;
401+ let chapters = [ ] ;
401402 let previousLinks = [ ] ;
402403 let storyOrder = [ ] ;
403404 let newsletterHTML = '' ;
@@ -558,8 +559,8 @@ <h2>Links sent in previous newsletters</h2>
558559 'guide_slug', g.slug
559560 ) as json,
560561 'https://simonwillison.net/guides/' || g.slug || '/' || c.slug || '/' as external_url
561- from blog_chapter c
562- join blog_guide g on c.guide_id = g.id
562+ from guides_chapter c
563+ join guides_guide g on c.guide_id = g.id
563564 where c.is_draft = 0
564565 union all
565566 select
@@ -579,7 +580,7 @@ <h2>Links sent in previous newsletters</h2>
579580 type,
580581 title,
581582 case
582- when type = 'til'
583+ when type in ( 'til', 'chapter')
583584 then external_url
584585 else 'https://simonwillison.net/' || strftime('%Y/', created)
585586 || substr('JanFebMarAprMayJunJulAugSepOctNovDec', (strftime('%m', created) - 1) * 3 + 1, 3) ||
@@ -713,6 +714,33 @@ <h2>Links sent in previous newsletters</h2>
713714 return await response . json ( ) ;
714715 }
715716
717+ // Render first three paragraphs of a chapter with word count suffix
718+ function renderChapterExcerpt ( body , chapterUrl ) {
719+ const fullHtml = marked . parse ( body ) ;
720+ const tempDiv = document . createElement ( 'div' ) ;
721+ tempDiv . innerHTML = fullHtml ;
722+ const paragraphs = tempDiv . querySelectorAll ( ':scope > p' ) ;
723+
724+ if ( paragraphs . length <= 3 ) return fullHtml ;
725+
726+ const excerptDiv = document . createElement ( 'div' ) ;
727+ for ( let i = 0 ; i < 3 ; i ++ ) {
728+ excerptDiv . appendChild ( paragraphs [ i ] . cloneNode ( true ) ) ;
729+ }
730+ let excerptHtml = excerptDiv . innerHTML ;
731+
732+ const wordCount = body . split ( / \s + / ) . filter ( w => w . length > 0 ) . length ;
733+ const formattedCount = wordCount . toLocaleString ( ) ;
734+ const suffix = ` <span style="font-size: 0.9em">[... <a href="${ chapterUrl } ">${ formattedCount } word${ wordCount !== 1 ? 's' : '' } </a>]</span>` ;
735+ const lastP = excerptHtml . lastIndexOf ( '</p>' ) ;
736+ if ( lastP !== - 1 ) {
737+ excerptHtml = excerptHtml . substring ( 0 , lastP ) + suffix + excerptHtml . substring ( lastP ) ;
738+ } else {
739+ excerptHtml += suffix ;
740+ }
741+ return excerptHtml ;
742+ }
743+
716744 // Filter content based on settings
717745 function filterContent ( ) {
718746 const skipExisting = skipExistingInput . checked ;
@@ -765,12 +793,27 @@ <h2>Links sent in previous newsletters</h2>
765793 } ) ;
766794 }
767795
796+ // Always render chapter HTML from markdown
797+ filtered = filtered . map ( e => {
798+ if ( e . type === 'chapter' && e . json !== 'null' ) {
799+ const info = typeof e . json === 'string' ? JSON . parse ( e . json ) : e . json ;
800+ const entry = { ...e } ;
801+ const chapterUrl = `https://simonwillison.net/guides/${ info . guide_slug } /${ info . chapter_slug } /` ;
802+ const guideUrl = `https://simonwillison.net/guides/${ info . guide_slug } /` ;
803+ const excerptHtml = renderChapterExcerpt ( info . body , chapterUrl ) ;
804+ entry . html = `<p style="font-size: 0.85em; color: #999; margin: 0 0 -0.2em 0; line-height: 1.2;"><a href="${ guideUrl } " style="color: #999; text-decoration: none;">${ info . guide_title } </a> ></p><h3 style="margin-top: 0.2em; margin-bottom: 0.5em;"><a href="${ chapterUrl } ">${ info . title } </a> - ${ info . created } </h3>${ excerptHtml } ` ;
805+ return entry ;
806+ }
807+ return e ;
808+ } ) ;
809+
768810 content = filtered ;
769811 entries = content . filter ( e => e . type === 'entry' ) ;
770812 blogmarks = content . filter ( e => e . type === 'blogmark' ) ;
771813 quotations = content . filter ( e => e . type === 'quotation' ) ;
772814 tils = content . filter ( e => e . type === 'til' ) ;
773815 notes = content . filter ( e => e . type === 'note' ) ;
816+ chapters = content . filter ( e => e . type === 'chapter' ) ;
774817
775818 // Initialize story order
776819 storyOrder = entries . map ( e => `${ e . id } : ${ e . title } ` ) . reverse ( ) ;
@@ -1007,6 +1050,9 @@ <h2>Links sent in previous newsletters</h2>
10071050 if ( notes . length ) {
10081051 extras . push ( `${ notes . length } note${ notes . length > 1 ? 's' : '' } ` ) ;
10091052 }
1053+ if ( chapters . length ) {
1054+ extras . push ( `${ chapters . length } guide chapter${ chapters . length > 1 ? 's' : '' } ` ) ;
1055+ }
10101056 if ( extras . length ) {
10111057 headerHtml += `<p>Plus ${ extras . join ( ' and ' ) } </p>` ;
10121058 }
@@ -1056,6 +1102,7 @@ <h2>Links sent in previous newsletters</h2>
10561102 quotations = quotations . filter ( e => ! ( type === 'quotation' && String ( e . id ) === String ( id ) ) ) ;
10571103 tils = tils . filter ( e => ! ( type === 'til' && String ( e . id ) === String ( id ) ) ) ;
10581104 notes = notes . filter ( e => ! ( type === 'note' && String ( e . id ) === String ( id ) ) ) ;
1105+ chapters = chapters . filter ( e => ! ( type === 'chapter' && String ( e . id ) === String ( id ) ) ) ;
10591106 generateNewsletter ( ) ;
10601107 }
10611108
0 commit comments