@@ -5,13 +5,9 @@ import { getIndexablePages } from '@/lib/sitemap';
5
5
import { getMarkdownForPagesTree } from '@/routes/llms' ;
6
6
import { type RevisionPageDocument , type RevisionPageGroup , RevisionPageType } from '@gitbook/api' ;
7
7
import type { Root } from 'mdast' ;
8
- import { fromMarkdown } from 'mdast-util-from-markdown' ;
9
- import { frontmatterFromMarkdown } from 'mdast-util-frontmatter' ;
10
- import { gfmFromMarkdown } from 'mdast-util-gfm' ;
11
8
import { toMarkdown } from 'mdast-util-to-markdown' ;
12
- import { frontmatter } from 'micromark-extension-frontmatter' ;
13
- import { gfm } from 'micromark-extension-gfm' ;
14
- import { remove } from 'unist-util-remove' ;
9
+ import remarkParse from 'remark-parse' ;
10
+ import { unified } from 'unified' ;
15
11
16
12
/**
17
13
* Generate a markdown version of a page.
@@ -45,7 +41,7 @@ export async function servePageMarkdown(context: GitBookSiteContext, pagePath: s
45
41
) ;
46
42
47
43
// Handle empty document pages which have children
48
- if ( isEmptyPage ( markdown ) && page . pages . length > 0 ) {
44
+ if ( isEmptyMarkdownPage ( markdown ) && page . pages . length > 0 ) {
49
45
return servePageGroup ( context , page ) ;
50
46
}
51
47
@@ -60,26 +56,51 @@ export async function servePageMarkdown(context: GitBookSiteContext, pagePath: s
60
56
* Determine if a page is empty.
61
57
* A page is empty if it has no content or only a title.
62
58
*/
63
- function isEmptyPage ( pageMarkdown : string ) : boolean {
64
- const trimmedMarkdown = pageMarkdown . trim ( ) ;
65
-
66
- // Check if the markdown is empty
67
- if ( ! trimmedMarkdown ) {
68
- return true ;
59
+ function isEmptyMarkdownPage ( markdown : string ) : boolean {
60
+ // Remove frontmatter
61
+ const stripped = markdown
62
+ . trim ( )
63
+ . replace ( / ^ - - - \n [ \s \S ] * ?\n - - - \n ? / g, '' )
64
+ . trim ( ) ;
65
+
66
+ // Fast path: try to quickly detect obvious matches
67
+ if ( / ^ [ \t ] * # .+ $ / m. test ( stripped ) ) {
68
+ // If there's a single heading line or empty lines, and no other content
69
+ return (
70
+ / ^ # { 1 , 6 } .+ \s * $ / . test ( stripped ) &&
71
+ ! / \n \S + / g. test ( stripped . split ( '\n' ) . slice ( 1 ) . join ( '\n' ) )
72
+ ) ;
69
73
}
70
74
71
- // Parse the markdown to check if it only contains a title
75
+ // Fallback: parse with remark for safety
76
+ const tree = unified ( ) . use ( remarkParse ) . parse ( stripped ) as Root ;
72
77
73
- const tree = fromMarkdown ( pageMarkdown , {
74
- extensions : [ frontmatter ( [ 'yaml' ] ) , gfm ( ) ] ,
75
- mdastExtensions : [ frontmatterFromMarkdown ( [ 'yaml' ] ) , gfmFromMarkdown ( ) ] ,
76
- } ) ;
78
+ let seenHeading = false ;
77
79
78
- // Remove frontmatter
79
- remove ( tree , 'yaml' ) ;
80
+ for ( const node of tree . children ) {
81
+ if ( node . type === 'heading' ) {
82
+ if ( seenHeading ) {
83
+ return false ;
84
+ }
85
+ seenHeading = true ;
86
+ continue ;
87
+ }
88
+
89
+ // Allow empty whitespace-only text nodes (e.g., extra newlines)
90
+ if (
91
+ node . type === 'paragraph' &&
92
+ node . children . length === 1 &&
93
+ node . children [ 0 ] . type === 'text' &&
94
+ ! node . children [ 0 ] . value . trim ( )
95
+ ) {
96
+ continue ;
97
+ }
98
+
99
+ // Anything else is disallowed
100
+ return false ;
101
+ }
80
102
81
- // If the page has no content or only a title, it is empty
82
- return tree . children . length <= 1 && tree . children [ 0 ] ?. type === 'heading' ;
103
+ return seenHeading ;
83
104
}
84
105
85
106
/**
0 commit comments