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