feat: Allow custom sorting of FolderPage and TagPage (#1250)
This commit is contained in:
		
							parent
							
								
									596e06ab0e
								
							
						
					
					
						commit
						ea92ed4f45
					
				| @ -27,10 +27,12 @@ export function byDateAndAlphabetical( | ||||
| 
 | ||||
| type Props = { | ||||
|   limit?: number | ||||
|   sort?: (f1: QuartzPluginData, f2: QuartzPluginData) => number | ||||
| } & QuartzComponentProps | ||||
| 
 | ||||
| export const PageList: QuartzComponent = ({ cfg, fileData, allFiles, limit }: Props) => { | ||||
|   let list = allFiles.sort(byDateAndAlphabetical(cfg)) | ||||
| export const PageList: QuartzComponent = ({ cfg, fileData, allFiles, limit, sort }: Props) => { | ||||
|   const sorter = sort ?? byDateAndAlphabetical(cfg) | ||||
|   let list = allFiles.sort(sorter) | ||||
|   if (limit) { | ||||
|     list = list.slice(0, limit) | ||||
|   } | ||||
|  | ||||
| @ -7,12 +7,14 @@ import { stripSlashes, simplifySlug } from "../../util/path" | ||||
| import { Root } from "hast" | ||||
| import { htmlToJsx } from "../../util/jsx" | ||||
| import { i18n } from "../../i18n" | ||||
| import { QuartzPluginData } from "../../plugins/vfile" | ||||
| 
 | ||||
| interface FolderContentOptions { | ||||
|   /** | ||||
|    * Whether to display number of folders | ||||
|    */ | ||||
|   showFolderCount: boolean | ||||
|   sort?: (f1: QuartzPluginData, f2: QuartzPluginData) => number | ||||
| } | ||||
| 
 | ||||
| const defaultOptions: FolderContentOptions = { | ||||
| @ -37,6 +39,7 @@ export default ((opts?: Partial<FolderContentOptions>) => { | ||||
|     const classes = ["popover-hint", ...cssClasses].join(" ") | ||||
|     const listProps = { | ||||
|       ...props, | ||||
|       sort: options.sort, | ||||
|       allFiles: allPagesInFolder, | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -7,107 +7,109 @@ import { Root } from "hast" | ||||
| import { htmlToJsx } from "../../util/jsx" | ||||
| import { i18n } from "../../i18n" | ||||
| 
 | ||||
| const numPages = 10 | ||||
| const TagContent: QuartzComponent = (props: QuartzComponentProps) => { | ||||
|   const { tree, fileData, allFiles, cfg } = props | ||||
|   const slug = fileData.slug | ||||
| export default ((opts?: { sort?: (f1: QuartzPluginData, f2: QuartzPluginData) => number }) => { | ||||
|   const numPages = 10 | ||||
|   const TagContent: QuartzComponent = (props: QuartzComponentProps) => { | ||||
|     const { tree, fileData, allFiles, cfg } = props | ||||
|     const slug = fileData.slug | ||||
| 
 | ||||
|   if (!(slug?.startsWith("tags/") || slug === "tags")) { | ||||
|     throw new Error(`Component "TagContent" tried to render a non-tag page: ${slug}`) | ||||
|   } | ||||
| 
 | ||||
|   const tag = simplifySlug(slug.slice("tags/".length) as FullSlug) | ||||
|   const allPagesWithTag = (tag: string) => | ||||
|     allFiles.filter((file) => | ||||
|       (file.frontmatter?.tags ?? []).flatMap(getAllSegmentPrefixes).includes(tag), | ||||
|     ) | ||||
| 
 | ||||
|   const content = | ||||
|     (tree as Root).children.length === 0 | ||||
|       ? fileData.description | ||||
|       : htmlToJsx(fileData.filePath!, tree) | ||||
|   const cssClasses: string[] = fileData.frontmatter?.cssclasses ?? [] | ||||
|   const classes = ["popover-hint", ...cssClasses].join(" ") | ||||
|   if (tag === "/") { | ||||
|     const tags = [ | ||||
|       ...new Set( | ||||
|         allFiles.flatMap((data) => data.frontmatter?.tags ?? []).flatMap(getAllSegmentPrefixes), | ||||
|       ), | ||||
|     ].sort((a, b) => a.localeCompare(b)) | ||||
|     const tagItemMap: Map<string, QuartzPluginData[]> = new Map() | ||||
|     for (const tag of tags) { | ||||
|       tagItemMap.set(tag, allPagesWithTag(tag)) | ||||
|     } | ||||
|     return ( | ||||
|       <div class={classes}> | ||||
|         <article> | ||||
|           <p>{content}</p> | ||||
|         </article> | ||||
|         <p>{i18n(cfg.locale).pages.tagContent.totalTags({ count: tags.length })}</p> | ||||
|         <div> | ||||
|           {tags.map((tag) => { | ||||
|             const pages = tagItemMap.get(tag)! | ||||
|             const listProps = { | ||||
|               ...props, | ||||
|               allFiles: pages, | ||||
|             } | ||||
| 
 | ||||
|             const contentPage = allFiles.filter((file) => file.slug === `tags/${tag}`).at(0) | ||||
| 
 | ||||
|             const root = contentPage?.htmlAst | ||||
|             const content = | ||||
|               !root || root?.children.length === 0 | ||||
|                 ? contentPage?.description | ||||
|                 : htmlToJsx(contentPage.filePath!, root) | ||||
| 
 | ||||
|             return ( | ||||
|               <div> | ||||
|                 <h2> | ||||
|                   <a class="internal tag-link" href={`../tags/${tag}`}> | ||||
|                     {tag} | ||||
|                   </a> | ||||
|                 </h2> | ||||
|                 {content && <p>{content}</p>} | ||||
|                 <div class="page-listing"> | ||||
|                   <p> | ||||
|                     {i18n(cfg.locale).pages.tagContent.itemsUnderTag({ count: pages.length })} | ||||
|                     {pages.length > numPages && ( | ||||
|                       <> | ||||
|                         {" "} | ||||
|                         <span> | ||||
|                           {i18n(cfg.locale).pages.tagContent.showingFirst({ count: numPages })} | ||||
|                         </span> | ||||
|                       </> | ||||
|                     )} | ||||
|                   </p> | ||||
|                   <PageList limit={numPages} {...listProps} /> | ||||
|                 </div> | ||||
|               </div> | ||||
|             ) | ||||
|           })} | ||||
|         </div> | ||||
|       </div> | ||||
|     ) | ||||
|   } else { | ||||
|     const pages = allPagesWithTag(tag) | ||||
|     const listProps = { | ||||
|       ...props, | ||||
|       allFiles: pages, | ||||
|     if (!(slug?.startsWith("tags/") || slug === "tags")) { | ||||
|       throw new Error(`Component "TagContent" tried to render a non-tag page: ${slug}`) | ||||
|     } | ||||
| 
 | ||||
|     return ( | ||||
|       <div class={classes}> | ||||
|         <article>{content}</article> | ||||
|         <div class="page-listing"> | ||||
|           <p>{i18n(cfg.locale).pages.tagContent.itemsUnderTag({ count: pages.length })}</p> | ||||
|     const tag = simplifySlug(slug.slice("tags/".length) as FullSlug) | ||||
|     const allPagesWithTag = (tag: string) => | ||||
|       allFiles.filter((file) => | ||||
|         (file.frontmatter?.tags ?? []).flatMap(getAllSegmentPrefixes).includes(tag), | ||||
|       ) | ||||
| 
 | ||||
|     const content = | ||||
|       (tree as Root).children.length === 0 | ||||
|         ? fileData.description | ||||
|         : htmlToJsx(fileData.filePath!, tree) | ||||
|     const cssClasses: string[] = fileData.frontmatter?.cssclasses ?? [] | ||||
|     const classes = ["popover-hint", ...cssClasses].join(" ") | ||||
|     if (tag === "/") { | ||||
|       const tags = [ | ||||
|         ...new Set( | ||||
|           allFiles.flatMap((data) => data.frontmatter?.tags ?? []).flatMap(getAllSegmentPrefixes), | ||||
|         ), | ||||
|       ].sort((a, b) => a.localeCompare(b)) | ||||
|       const tagItemMap: Map<string, QuartzPluginData[]> = new Map() | ||||
|       for (const tag of tags) { | ||||
|         tagItemMap.set(tag, allPagesWithTag(tag)) | ||||
|       } | ||||
|       return ( | ||||
|         <div class={classes}> | ||||
|           <article> | ||||
|             <p>{content}</p> | ||||
|           </article> | ||||
|           <p>{i18n(cfg.locale).pages.tagContent.totalTags({ count: tags.length })}</p> | ||||
|           <div> | ||||
|             <PageList {...listProps} /> | ||||
|             {tags.map((tag) => { | ||||
|               const pages = tagItemMap.get(tag)! | ||||
|               const listProps = { | ||||
|                 ...props, | ||||
|                 allFiles: pages, | ||||
|               } | ||||
| 
 | ||||
|               const contentPage = allFiles.filter((file) => file.slug === `tags/${tag}`).at(0) | ||||
| 
 | ||||
|               const root = contentPage?.htmlAst | ||||
|               const content = | ||||
|                 !root || root?.children.length === 0 | ||||
|                   ? contentPage?.description | ||||
|                   : htmlToJsx(contentPage.filePath!, root) | ||||
| 
 | ||||
|               return ( | ||||
|                 <div> | ||||
|                   <h2> | ||||
|                     <a class="internal tag-link" href={`../tags/${tag}`}> | ||||
|                       {tag} | ||||
|                     </a> | ||||
|                   </h2> | ||||
|                   {content && <p>{content}</p>} | ||||
|                   <div class="page-listing"> | ||||
|                     <p> | ||||
|                       {i18n(cfg.locale).pages.tagContent.itemsUnderTag({ count: pages.length })} | ||||
|                       {pages.length > numPages && ( | ||||
|                         <> | ||||
|                           {" "} | ||||
|                           <span> | ||||
|                             {i18n(cfg.locale).pages.tagContent.showingFirst({ count: numPages })} | ||||
|                           </span> | ||||
|                         </> | ||||
|                       )} | ||||
|                     </p> | ||||
|                     <PageList limit={numPages} {...listProps} sort={opts?.sort} /> | ||||
|                   </div> | ||||
|                 </div> | ||||
|               ) | ||||
|             })} | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|     ) | ||||
|   } | ||||
| } | ||||
|       ) | ||||
|     } else { | ||||
|       const pages = allPagesWithTag(tag) | ||||
|       const listProps = { | ||||
|         ...props, | ||||
|         allFiles: pages, | ||||
|       } | ||||
| 
 | ||||
| TagContent.css = style + PageList.css | ||||
| export default (() => TagContent) satisfies QuartzComponentConstructor | ||||
|       return ( | ||||
|         <div class={classes}> | ||||
|           <article>{content}</article> | ||||
|           <div class="page-listing"> | ||||
|             <p>{i18n(cfg.locale).pages.tagContent.itemsUnderTag({ count: pages.length })}</p> | ||||
|             <div> | ||||
|               <PageList {...listProps} /> | ||||
|             </div> | ||||
|           </div> | ||||
|         </div> | ||||
|       ) | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   TagContent.css = style + PageList.css | ||||
|   return TagContent | ||||
| }) satisfies QuartzComponentConstructor | ||||
|  | ||||
| @ -3,7 +3,7 @@ import { QuartzComponentProps } from "../../components/types" | ||||
| import HeaderConstructor from "../../components/Header" | ||||
| import BodyConstructor from "../../components/Body" | ||||
| import { pageResources, renderPage } from "../../components/renderPage" | ||||
| import { ProcessedContent, defaultProcessedContent } from "../vfile" | ||||
| import { ProcessedContent, QuartzPluginData, defaultProcessedContent } from "../vfile" | ||||
| import { FullPageLayout } from "../../cfg" | ||||
| import path from "path" | ||||
| import { | ||||
| @ -21,11 +21,13 @@ import { write } from "./helpers" | ||||
| import { i18n } from "../../i18n" | ||||
| import DepGraph from "../../depgraph" | ||||
| 
 | ||||
| export const FolderPage: QuartzEmitterPlugin<Partial<FullPageLayout>> = (userOpts) => { | ||||
| export const FolderPage: QuartzEmitterPlugin< | ||||
|   Partial<FullPageLayout> & { sort?: (f1: QuartzPluginData, f2: QuartzPluginData) => number } | ||||
| > = (userOpts) => { | ||||
|   const opts: FullPageLayout = { | ||||
|     ...sharedPageComponents, | ||||
|     ...defaultListPageLayout, | ||||
|     pageBody: FolderContent(), | ||||
|     pageBody: FolderContent({ sort: userOpts?.sort }), | ||||
|     ...userOpts, | ||||
|   } | ||||
| 
 | ||||
|  | ||||
| @ -3,7 +3,7 @@ import { QuartzComponentProps } from "../../components/types" | ||||
| import HeaderConstructor from "../../components/Header" | ||||
| import BodyConstructor from "../../components/Body" | ||||
| import { pageResources, renderPage } from "../../components/renderPage" | ||||
| import { ProcessedContent, defaultProcessedContent } from "../vfile" | ||||
| import { ProcessedContent, QuartzPluginData, defaultProcessedContent } from "../vfile" | ||||
| import { FullPageLayout } from "../../cfg" | ||||
| import { | ||||
|   FilePath, | ||||
| @ -18,11 +18,13 @@ import { write } from "./helpers" | ||||
| import { i18n } from "../../i18n" | ||||
| import DepGraph from "../../depgraph" | ||||
| 
 | ||||
| export const TagPage: QuartzEmitterPlugin<Partial<FullPageLayout>> = (userOpts) => { | ||||
| export const TagPage: QuartzEmitterPlugin< | ||||
|   Partial<FullPageLayout> & { sort?: (f1: QuartzPluginData, f2: QuartzPluginData) => number } | ||||
| > = (userOpts) => { | ||||
|   const opts: FullPageLayout = { | ||||
|     ...sharedPageComponents, | ||||
|     ...defaultListPageLayout, | ||||
|     pageBody: TagContent(), | ||||
|     pageBody: TagContent({ sort: userOpts?.sort }), | ||||
|     ...userOpts, | ||||
|   } | ||||
| 
 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user