-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathsearch.mjs
142 lines (126 loc) · 4.28 KB
/
search.mjs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
import Markdoc from '@markdoc/markdoc'
import { slugifyWithCounter } from '@sindresorhus/slugify'
import glob from 'fast-glob'
import * as fs from 'fs'
import * as path from 'path'
import { createLoader } from 'simple-functional-loader'
import * as url from 'url'
const __filename = url.fileURLToPath(import.meta.url)
const slugify = slugifyWithCounter()
function toString(node) {
let str =
node.type === 'text' && typeof node.attributes?.content === 'string'
? node.attributes.content
: ''
if ('children' in node) {
for (let child of node.children) {
str += toString(child)
}
}
return str
}
function extractSections(node, sections, isRoot = true) {
if (isRoot) {
slugify.reset()
}
if (node.type === 'heading' || node.type === 'paragraph') {
let content = toString(node).trim()
if (node.type === 'heading' && node.attributes.level <= 2) {
let hash = node.attributes?.id ?? slugify(content)
sections.push([content, hash, []])
} else {
sections.at(-1)[2].push(content)
}
} else if ('children' in node) {
for (let child of node.children) {
extractSections(child, sections, false)
}
}
}
export default function withSearch(nextConfig = {}) {
let cache = new Map()
return Object.assign({}, nextConfig, {
webpack(config, options) {
config.module.rules.push({
test: __filename,
use: [
createLoader(function () {
let pagesDir = path.resolve('./src/app')
this.addContextDependency(pagesDir)
let files = glob.sync('**/page.md', { cwd: pagesDir })
let data = files.map((file) => {
let url =
file === 'page.md' ? '/' : `/${file.replace(/\/page\.md$/, '')}`
let md = fs.readFileSync(path.join(pagesDir, file), 'utf8')
let sections
let ast = Markdoc.parse(md)
let keywords =
ast.attributes?.frontmatter
?.match(/^keywords:\s*(.*?)\s*$/m)?.[1]
.split(/,\s+/) || []
if (cache.get(file)?.[0] === md) {
sections = cache.get(file)[1]
} else {
let title =
ast.attributes?.frontmatter?.match(
/^title:\s*(.*?)\s*$/m,
)?.[1]
sections = [[title, null, []]]
extractSections(ast, sections)
cache.set(file, [md, sections])
}
return { url, sections, keywords }
})
// When this file is imported within the application
// the following module is loaded:
return `
import FlexSearch from 'flexsearch'
let sectionIndex = new FlexSearch.Document({
tokenize: 'full',
document: {
id: 'url',
index: 'content',
store: ['title', 'pageTitle', 'keywords'],
},
context: {
resolution: 9,
depth: 2,
bidirectional: true
}
})
let data = ${JSON.stringify(data)}
for (let { url, sections, keywords } of data) {
for (let [title, hash, content] of sections) {
sectionIndex.add({
url: url + (hash ? ('#' + hash) : ''),
title,
content: [title, ...keywords, ...content ].join('\\n'),
pageTitle: hash ? sections[0][0] : undefined,
})
}
}
export function search(query, options = {}) {
let result = sectionIndex.search(query, {
...options,
enrich: true,
})
if (result.length === 0) {
return []
}
return result[0].result.map((item) => ({
url: item.id,
title: item.doc.title,
pageTitle: item.doc.pageTitle,
}))
}
`
}),
],
})
if (typeof nextConfig.webpack === 'function') {
return nextConfig.webpack(config, options)
}
return config
},
})
}