QuartzにScrapbox(現Cosense)のような2ホップリンクを実装する

仕様

2ホップリンク - namaraii.comで述べられている通り、
自身のリンク先のバックリンクを2ホップリンクとして表示する

逆に、自身のバックリンクの関連リンクは表示しない

また、1ホップリンクはアウトゴーイングとバックリンクを結合して表示する

1ホップリンク2ホップリンクの順で表示し、重複は取り除く

使い方

を所定の場所に配置して、quartz/components/index.tsに取り込む

quartz/components/index.ts
...
import TwohopLinks from "./TwohopLinks"
 
export {
  ...
  TwohopLinks,
}

quartz/quartz.layout.tsに設定すれば使用できる

quartz/quartz.layout.ts
...
export const sharedPageComponents: SharedLayout = {
  ...
  afterBody: [
    ...
    Component.TwohopLinks(),
  ],
  ...
}

動作確認

以下のようにリンクを用意した

flowchart LR
    A["Quartzに2HLを実装する"]
    B["テスト2"]
    C["テスト1"]
    A & B --> C
    style A stroke:orange

2-Hop-Linkのテスト1

実装

quartz/components/Backlinks.tsxをベースに実装した

データの準備

Backlinks.tsxを覗くと、allFilesから現在のsluglinksに含むものをフィルタしている

Backlinks.tsx
const backlinkFiles = allFiles.filter((file) => (
  file.links?.includes(slug)
))

2ホップリンクを実現するには任意のファイルのバックリンクを取得する必要があるので、slugをキーにした逆引きマップを作る

TwohopLinks.tsx
const backlinksMap = allFiles.reduce(
  (map, file) => {
    file.links?.forEach((link) => {
      if (!map[link]) map[link] = []
      map[link].push(file)
    })
    return map
  },
  {} as Record<SimpleSlug, Data[]>,
)
const getBacklinks = (slug: SimpleSlug) => (
  backlinksMap[slug] ?? []
)

重複を許さず表示したいので
リンクを既に使っているかどうかをuniqueLinksで管理する
indexページと自身のslugを初期値として与える

TwohopLinks.tsx
const uniqueLinks = new Set<SimpleSlug>()
uniqueLinks.add("/" as SimpleSlug)
uniqueLinks.add(slug)

uniqueLinksを見ながらoutlinkFilesbacklinkFilesを作り
結合してallLinkFilesを得る

TwohopLinks.tsx
const outlinkFiles = allFiles.filter((file) => {
  const s = simplifySlug(file.slug!)
  if (!fileData.links?.includes(s)) return false
  if (uniqueLinks.has(s)) return false
  uniqueLinks.add(s)
  return true
})
 
const backlinkFiles = getBacklinks(slug).filter((file) => {
  const s = simplifySlug(file.slug!)
  if (uniqueLinks.has(s)) return false
  uniqueLinks.add(s)
  return true
})
 
const allLinkFiles = outlinkFiles.concat(backlinkFiles)

表示

allLinkFilesを表示する

TwohopLinks.tsx
{allLinkFiles.map((file) => <li>...</li>)}

outlinkFilesの各要素に対してバックリンクを表示する

TwohopLinks.tsx
{outlinkFiles.map((file) => {
  const os = simplifySlug(file.slug!)
  const hops = getBacklinks(os).filter((hop) => {
    const hs = simplifySlug(hop.slug!)
    if (uniqueLinks.has(hs)) return false
    uniqueLinks.add(hs)
    return true
  })
  hops.map((hop) => <li>...</li>)
})}