feat: Allow custom sorting of FolderPage and TagPage (#1250)
This commit is contained in:
		@@ -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,
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user