@@ -151,56 +151,51 @@ export const ContentIndex: QuartzEmitterPlugin<Partial<Options>> = (opts) => {
151151 } )
152152
153153 if ( opts ?. includeTags ) {
154+ // Optimization: Build a map of tag -> content list once (reverse index)
155+ const tagsInput : Map < string , ContentDetails [ ] > = new Map ( )
156+
157+ // Iterate over the content index once to populate the reverse index
158+ for ( const [ _ , content ] of linkIndex ) {
159+ const tags = content . tags . flatMap ( getAllSegmentPrefixes )
160+ for ( const tag of new Set ( tags ) ) {
161+ // Use Set to avoid double counting per file
162+ if ( ! tagsInput . has ( tag ) ) {
163+ tagsInput . set ( tag , [ ] )
164+ }
165+ tagsInput . get ( tag ) ! . push ( content )
166+ }
167+ }
168+
154169 let sortedTags : string [ ] = [ ]
155170
156171 if ( opts . rssTags && opts . rssTags . length > 0 ) {
157172 // Deduplicate and slugify user-provided tags
158173 const userTags = new Set ( opts . rssTags . map ( ( tag ) => slugTag ( tag ) ) )
159174
160- // Only include user-specified tags that actually exist in the content
161- const availableTags = new Set < string > ( )
162- for ( const [ _ , content ] of linkIndex ) {
163- const tags = content . tags . flatMap ( getAllSegmentPrefixes )
164- for ( const tag of tags ) {
165- availableTags . add ( tag )
166- }
167- }
168- sortedTags = Array . from ( userTags ) . filter ( ( tag ) => availableTags . has ( tag ) )
175+ // Filter user tags to only those that exist in the content
176+ sortedTags = Array . from ( userTags ) . filter ( ( tag ) => tagsInput . has ( tag ) )
169177 } else if ( ( opts . rssTagsLimit ?? 0 ) > 0 ) {
170- const tagCounts : Map < string , number > = new Map ( )
171-
172- // Count tag occurrences across all files in the index
173- for ( const [ _ , content ] of linkIndex ) {
174- const tags = content . tags . flatMap ( getAllSegmentPrefixes )
175- for ( const tag of new Set ( tags ) ) {
176- // Use Set to avoid double counting per file
177- tagCounts . set ( tag , ( tagCounts . get ( tag ) ?? 0 ) + 1 )
178- }
179- }
180-
181- sortedTags = Array . from ( tagCounts . entries ( ) )
182- . sort ( ( a , b ) => b [ 1 ] - a [ 1 ] ) // Sort by frequency descending
178+ // Sort available tags by frequency (number of content items)
179+ sortedTags = Array . from ( tagsInput . entries ( ) )
180+ . sort ( ( a , b ) => b [ 1 ] . length - a [ 1 ] . length ) // Sort by frequency descending
183181 . slice ( 0 , opts . rssTagsLimit )
184182 . map ( ( [ tag ] ) => tag )
185183 }
186184
187- if (
188- sortedTags . length === 0 &&
189- ( ! opts . rssTags || opts . rssTags . length === 0 ) &&
190- ( opts . rssTagsLimit ?? 0 ) <= 0
191- ) {
185+ if ( sortedTags . length === 0 ) {
192186 console . warn (
193187 "[contentIndex] includeTags is enabled, but no tag-based RSS feeds will be generated. " +
194- "Either provide non-empty `rssTags` or set `rssTagsLimit` to a positive number." ,
188+ "Either provide non-empty `rssTags` matching content tags or set `rssTagsLimit` to a positive number." ,
195189 )
196190 }
191+
197192 for ( const tag of sortedTags ) {
198- const tagFilteredIndex = new Map (
199- Array . from ( linkIndex ) . filter ( ( [ _ , content ] ) => {
200- const fileTags = new Set ( content . tags . flatMap ( getAllSegmentPrefixes ) )
201- return fileTags . has ( tag )
202- } ) ,
203- )
193+ const tagContent = tagsInput . get ( tag )
194+ if ( ! tagContent ) continue // Should not happen given logic above
195+
196+ // Reconstruct a map for generateRSSFeed (it expects a ContentIndexMap )
197+ // We can optimize this by making generateRSSFeed accept an array, but for now we conform to the interface
198+ const tagFilteredIndex = new Map ( tagContent . map ( ( content ) => [ content . slug , content ] ) )
204199
205200 yield write ( {
206201 ctx,
0 commit comments