@@ -19,23 +19,50 @@ const truncate = (value, max) => {
1919 return s . length > max ? s . slice ( 0 , max - 1 ) . trimEnd ( ) + '…' : s
2020}
2121
22- // opts: { client, appUrl, comment?, board?, image?, pageTitle? }
23- export const buildPreviewHtml = ( { client , appUrl , comment , board , image , pageTitle } ) => {
24- const siteName = client . siteName
25- const where = board ? `/ ${ board } /` : comment ?. communityAddress || ''
22+ const firstLine = ( value ) => ( value ? String ( value ) . split ( '\n' ) . find ( ( l ) => l . trim ( ) ) : undefined )
23+
24+ const TITLE_SEP = ' - '
25+ const capitalize = ( s ) => ( s ? s . charAt ( 0 ) . toUpperCase ( ) + s . slice ( 1 ) : s )
2626
27- const firstLine = comment ?. content ?. split ( '\n' ) . find ( ( l ) => l . trim ( ) )
28- const title = truncate ( comment ?. title || firstLine || pageTitle || siteName , 70 ) || siteName
27+ // Build the page title and description, modelled on 4chan's:
28+ // title: "/tv/ - Television & Film - <subject> - 5chan"
29+ // desc: "<post text> — "/tv/ - Television & Film" on 5chan, a serverless imageboard."
30+ // `label` is the board's display name (nice directory title, else the community
31+ // address); it can be null for non-board pages.
32+ const buildMeta = ( { kind, comment, label, siteName, tagline } ) => {
33+ const onSite = tagline ? `${ siteName } , ${ tagline } ` : siteName
34+ const context = label ? `"${ label } " on ${ onSite } ` : onSite // board clause for descriptions
35+ const boardPart = label ? `${ label } ${ TITLE_SEP } ` : ''
2936
30- let description
31- if ( comment ) {
32- const body = comment . content ?. trim ( )
33- description = truncate ( body || ( where ? `Thread in ${ where } on ${ siteName } ` : `A post on ${ siteName } ` ) , 200 )
34- } else if ( board ) {
35- description = `Browse ${ where } on ${ siteName } `
36- } else {
37- description = `Shared from ${ siteName } `
37+ if ( kind === 'thread' && comment ) {
38+ const subject = truncate ( comment . title || firstLine ( comment . content ) , 65 )
39+ const body = truncate ( comment . content || comment . title , 120 )
40+ return {
41+ title : subject ? `${ boardPart } ${ subject } ${ TITLE_SEP } ${ siteName } ` : `${ boardPart } ${ siteName } ` ,
42+ description : body ? `${ body } — ${ context } .` : `${ capitalize ( context ) } .` ,
43+ }
44+ }
45+ if ( kind === 'catalog' ) {
46+ return {
47+ title : `${ boardPart } Catalog${ TITLE_SEP } ${ siteName } ` ,
48+ description : label ? `Browse the "${ label } " catalog on ${ onSite } .` : `Browse ${ onSite } .` ,
49+ }
50+ }
51+ // board home / fallback
52+ return {
53+ title : label ? `${ label } ${ TITLE_SEP } ${ siteName } ` : siteName ,
54+ description : `${ capitalize ( context ) } .` ,
3855 }
56+ }
57+
58+ // opts: { client, appUrl, comment?, board?, boardTitle?, image?, kind }
59+ // kind: 'thread' | 'catalog' | 'board' (default 'board')
60+ export const buildPreviewHtml = ( { client, appUrl, comment, board, boardTitle, image, kind = 'board' } ) => {
61+ const siteName = client . siteName
62+ // Board display label: nice directory title > community address > /board/ > none.
63+ const label = boardTitle || comment ?. communityAddress || ( board ? `/${ board } /` : null )
64+
65+ const { title, description } = buildMeta ( { kind, comment, label, siteName, tagline : client . tagline } )
3966
4067 const twitterCard = image ? 'summary_large_image' : 'summary'
4168 const favicon = `${ client . appBaseUrl } /favicon.ico`
@@ -71,4 +98,4 @@ export const buildPreviewHtml = ({ client, appUrl, comment, board, image, pageTi
7198</html>`
7299}
73100
74- export const __test__ = { escapeHtml, jsString, truncate }
101+ export const __test__ = { escapeHtml, jsString, truncate, buildMeta }
0 commit comments