QuartzにはデフォルトでExplorerというノートの一覧を左サイドに表示する機能がある

この一覧を更新順にソートしたい

結論

contentIndex.tsx内のdateを削除している行をコメントアウトする

contentIndex.tsx
- delete content.date
+ // delete content.date

quartz.layout.tsにソート関数を実装してExplorerに与える

quarts.layout.ts
import { Options } from "./quartz/components/Explorer"
...
const sortFn: Options["sortFn"] = (a, b) => {
  if (a.data?.date && b.data?.date) {
    const aDate = new Date(a.data?.date)
    const bDate = new Date(b.data?.date)
    const order = bDate.getTime() - aDate.getTime()
    if (order != 0) return order
  }
  return a.displayName.localeCompare(b.displayName)
}
...
export const defaultContentPageLayout: PageLayout = {
  ...
  left: [
    ...
    Component.Explorer({ sortFn }),
  ],
  ...
}

紆余曲折

軽く調べると、quartz.layout.tsからソート関数を差し込めることがわかる

quartz.layout.ts
export const defaultContentPageLayout: PageLayout = {
  ...
  left: [
    ...
    Component.Explorer({ sortFn }),
  ],
}

ソート関数は以下のような形をしている

const sortFn: Options["sortFn"] = (a, b) => {
  return 1 // or -1
}

abFileTrieNode<ContentDetails>型のオブジェクトである
ContentDetailsの定義を見てみると

contentIndex.tsx
export type ContentDetails = {
  slug: FullSlug
  filePath: FilePath
  title: string
  links: SimpleSlug[]
  tags: string[]
  content: string
  richContent?: string
  date?: Date
  description?: string
}

dateあるじゃん!
じゃあということでソート関数を実装する

const sortFn: Options["sortFn"]: (a, b) => {
  const aDate = a.data?.date
  const bDate = b.data?.date
  if (aDate && bDate) {
    const order = bDate.getTime() - aDate.getTime()
    if (order != 0) return order
  }
  return a.displayName.localeCompare(b.displayName)
}

これでビルドしてみるとdateundefinedですよって言われる
なのでdateが格納されるところを見に行く

実は、ExplorercontentIndex.jsonを非同期に受け取って表示している
そのjsonが作られているところを見に行くと

contentIndex.tsx
const simplifiedIndex = Object.fromEntries(
  Array.from(linkIndex).map(([slug, content]) => {
    // remove description and from content index as nothing downstream
    // actually uses it. we only keep it in the index as we need it
    // for the RSS feed
    delete content.description
    delete content.date
    return [slug, content]
  }),
)
 
yield write({
  ctx,
  content: JSON.stringify(simplifiedIndex),
  slug: fp,
  ext: ".json",
})

なんとdateが消されている!
これをコメントアウトする

今度こそ上手くいっただろうとビルドすると
dategetTimeメソッドなんてないですよって言われる

実は、jsonをパースする都合上ContentDetailsdateDate型にキャストされずにstring型のままになっている

ソート関数側でDateオブジェクトを作ってやると上手くいった

余談

このあと、フォルダ機能を使わないのと日付を見せてほしいという理由でRecentNotesをいじって使うことにした
その結果ほとんどExplorerと同じロジックを実装することになった
今思えばExplorerの見た目を変えるほうがまだ修正コストが低かったかもしれない