Skip to content
This repository has been archived by the owner on Nov 10, 2023. It is now read-only.

サイト内検索機能 #55

Open
book000 opened this issue Mar 19, 2023 · 16 comments · May be fixed by #72
Open

サイト内検索機能 #55

book000 opened this issue Mar 19, 2023 · 16 comments · May be fixed by #72
Assignees
Labels
✨feature-request New feature or request

Comments

@book000
Copy link
Member

book000 commented Mar 19, 2023

ルールとか探すときにワード検索できたら楽ですよねってやつです
検索画面とか作るのがなかなかめんどくさそうですが…。

@Hiratake
Copy link
Member

SSGだから外部サービスに頼るしかないのではなどと思ってますが

@book000
Copy link
Member Author

book000 commented Mar 19, 2023

たしかに…

@Hiratake
Copy link
Member

https://www.algolia.com/
Algoliaとかよく見るけど、おいくら万円くらいかかるのかわからん

@book000
Copy link
Member Author

book000 commented Mar 19, 2023

10,000 search + 10,000 recommend requests/mo and 10,000 records/mo

@Hiratake
Copy link
Member

#26 と重複?こっちは記事の絞り込み的なやつかな

@book000
Copy link
Member Author

book000 commented Mar 19, 2023

クレカ登録不要ならまあ上限いったらごめんなさいにすればいいかしらねえ

#26 はブログ記事に限っての絞り込みなのでちょっと違います。こっちはブログ以外に焦点を当てた検索って感じ。例えば「投票について書かれてたルールページどこだっけ?」とか

@Hiratake
Copy link
Member

10,000 search + 10,000 recommend requests/mo and 10,000 records/mo

無料である程度使えるなら、とりあえず使ってみるのはありかねえ

#26 はブログ記事に限っての絞り込みなのでちょっと違います。

issueの件名変えたほうが良きかねえ。ブログ記事の絞り込み機能とか。こっちは ContentList.vue で一覧データ取得はやってるから、カテゴリ(まだカテゴリ自体存在しないけど)とか著者での絞り込みとかならできそう。しらんけど。

@book000
Copy link
Member Author

book000 commented Mar 19, 2023

issueの件名変えたほうが良きかねえ。

かえました

@Hiratake
Copy link
Member

:igyo:

@book000
Copy link
Member Author

book000 commented Mar 20, 2023

ドキュメント更新時にドキュメントを Algolia に投げるコードを書く感じかなあ

https://sunday-morning.app/posts/2021-03-27-generate-contentful-algolia-index

@book000
Copy link
Member Author

book000 commented Mar 20, 2023

検索結果の表示と表示結果からの遷移を考えると、アンカーが付けられる各タイトル部分とそれ以下のドキュメントでインデックスつけたほうがよさそう。

@book000
Copy link
Member Author

book000 commented Mar 20, 2023

@book000
Copy link
Member Author

book000 commented Mar 20, 2023

とりあえずざっとMarkdownからASTつくってheadingとそれ以下のコンテンツに分ける処理

/* eslint-disable no-console */
import { fromMarkdown } from 'mdast-util-from-markdown'
import { gfmTable } from 'micromark-extension-gfm-table'
import { gfmTableFromMarkdown } from 'mdast-util-gfm-table'
import fs from 'node:fs'
import { toString } from 'mdast-util-to-string'
import { Content } from 'mdast-util-from-markdown/lib'
import { Logger } from '@book000/node-utils'
import matter from 'gray-matter'

const htmlCommentRegex = /<!--[\S\s]*?-->/g

async function parse(content: string) {
  const ast = fromMarkdown(content, {
    extensions: [gfmTable],
    mdastExtensions: [gfmTableFromMarkdown],
  })

  // headingとそれ以下の内容を取得
  const results: {
    title: string | null
    asts: Content[]
    content: string | null
  }[] = []
  for (const node of ast.children) {
    if (node.type === 'heading') {
      // 前項目のコンテンツ処理
      const last = results[results.length - 1]
      if (last) {
        last.content = toString(last.asts).replace(htmlCommentRegex, '').trim()
      }

      // 新しい項目追加
      const title = toString(node)
      results.push({
        title,
        asts: [],
        content: null,
      })
      continue
    }

    // headingでない場合は前項目に追加
    const last = results[results.length - 1]
    if (last) {
      last.asts.push(node)
    } else {
      results.push({
        title: null,
        asts: [node],
        content: null,
      })
    }
  }

  return results
}

function getFiles(directory: string): string[] {
  const files = fs.readdirSync(directory)
  const results: string[] = []
  for (const file of files) {
    const path = `${directory}/${file}`
    if (fs.statSync(path).isDirectory()) {
      results.push(...getFiles(path))
    } else {
      results.push(path)
    }
  }
  return results
}

async function main() {
  const logger = Logger.configure('main')
  const directory = './files'
  const files = getFiles(directory)
  const results: {
    pageTitle: string
    path: string
    heading: string | null
    content: string
  }[] = []
  for (const path of files) {
    if (!path.endsWith('.md')) {
      continue
    }
    logger.info(`Parsing ${path}...`)
    const frontMatter = matter(fs.readFileSync(path, 'utf8'))
    const parsed = await parse(frontMatter.content)

    for (const p of parsed) {
      if (!p.content) {
        continue
      }

      results.push({
        pageTitle: frontMatter.data.title,
        path: path.replace(directory, '').replace('.md', ''),
        heading: p.title,
        content: p.content,
      })
    }
  }

  fs.writeFileSync('./results.json', JSON.stringify(results, null, 2), 'utf8')
}

;(async () => {
  await main()
})()

@Hiratake Hiratake added the ✨feature-request New feature or request label Mar 20, 2023
@book000
Copy link
Member Author

book000 commented Mar 25, 2023

generateしたら /search ルートが消えました(どこからもリンクされていないので)。静的ルート書かなきゃならなそう…
クライアントサイドでalgoliaにリクエストを出していいのか、よくわかりません

動かすためには .envALGOLIA_API_KEYALGOLIA_APPLICATION_ID を設定しなければなりません。値は https://www.algolia.com/account/api-keys/all?applicationId=RF6OC1MM2P にて閲覧可能。

https://github.com/book000/jaoweb4/tree/feat/algolia-search

@book000 book000 linked a pull request Mar 27, 2023 that will close this issue
@book000
Copy link
Member Author

book000 commented Mar 28, 2023

DocSearch とやらを公開したら使うのがよさそう?
https://docsearch.algolia.com/

@Hiratake
Copy link
Member

そっちのが一般的なんかねえ。
とりあえず、検索が絶対なきゃならんってわけでもないと思うので、公開後に両方試してみて比較してからでもいいと思う。

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
✨feature-request New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants