/
pagefind.ts
227 lines (196 loc) Β· 6.48 KB
/
pagefind.ts
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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
import { merge } from "../core/utils.ts";
import { posix } from "../deps/path.ts";
import downloadBinary, { DownloadOptions } from "../deps/pagefind.ts";
import type { DeepPartial, Site } from "../core.ts";
export interface TranslationsOptions {
/** English default: "Search" */
placeholder: string;
/** English default: "Clear" */
clear_search: string;
/** English default: "Load more results" */
load_more: string;
/** English default: "Search this site" */
search_label: string;
/** English default: "Filters" */
filters_label: string;
/** English default: "No results for [SEARCH_TERM]" */
zero_results: string;
/** English default: "[COUNT] results for [SEARCH_TERM]" */
many_results: string;
/** English default: "[COUNT] result for [SEARCH_TERM]" */
one_result: string;
/** English default: "No results for [SEARCH_TERM]. Showing results for [DIFFERENT_TERM] instead" */
alt_search: string;
/** English default: "No results for [SEARCH_TERM]. Try one of the following searches:" */
search_suggestion: string;
/** English default: "Searching for [SEARCH_TERM]..." */
searching: string;
}
export interface UIOptions {
/** The container id to insert the search */
containerId: string;
/** Whether to show an image alongside each search result. */
showImages: boolean;
/**
* By default, Pagefind UI shows filters with no results alongside the count (0).
* Pass false to hide filters that have no remaining results.
*/
showEmptyFilters: boolean;
/**
* By default, Pagefind UI applies a CSS reset to itself.
* Pass false to omit this and inherit from your site styles.
*/
resetStyles: boolean;
/**
* A set of custom ui strings to use instead of the automatically detected language strings.
* See https://github.com/CloudCannon/pagefind/blob/main/pagefind_ui/translations/en.json for all available keys and initial values.
* The items in square brackets such as SEARCH_TERM will be substituted dynamically when the text is used.
*/
translations?: TranslationsOptions;
}
export interface IndexingOptions {
/** The folder to output search files into, relative to source. */
bundleDirectory: string;
/** The element that Pagefind should treat as the root of the document. */
rootSelector: string;
/** Configures the glob used by Pagefind to discover HTML files. */
glob: string;
/** Ignores any detected languages and creates a single index for the entire site as the provided language. */
forceLanguage: string | false;
/** Prints extra logging while indexing the site. */
verbose: boolean;
/** Extra element selectors that Pagefind should ignore when indexing */
excludeSelectors: string[];
}
export interface Options {
/** The options to download the binary file */
binary: DownloadOptions;
/** Options for the UI interface or false to disable it */
ui: UIOptions | false;
/** Options for the indexing process */
indexing: IndexingOptions;
}
const defaults: Options = {
binary: {
path: "./_bin/pagefind",
extended: false,
version: "v0.10.7",
},
ui: {
containerId: "search",
showImages: false,
showEmptyFilters: true,
resetStyles: true,
},
indexing: {
bundleDirectory: "pagefind",
rootSelector: "html",
glob: "**/*.html",
forceLanguage: false,
verbose: false,
excludeSelectors: [],
},
};
/** A plugin to generate a static full text search engine */
export default function (userOptions?: DeepPartial<Options>) {
const options = merge(defaults, userOptions);
return (site: Site) => {
const { ui, indexing } = options;
if (ui) {
site.process([".html"], (page) => {
const { document } = page;
if (!document) {
return;
}
const container = document.getElementById(ui.containerId);
// Insert UI styles and scripts
if (container) {
const styles = document.createElement("link");
styles.setAttribute("rel", "stylesheet");
styles.setAttribute(
"href",
site.url(
`${posix.join(indexing.bundleDirectory, "pagefind-ui.css")}`,
),
);
// Insert before other styles to allow overriding
const first = document.head.querySelector(
"link[rel=stylesheet],style",
);
if (first) {
document.head.insertBefore(styles, first);
} else {
document.head.append(styles);
}
const script = document.createElement("script");
script.setAttribute("type", "text/javascript");
script.setAttribute(
"src",
site.url(
`${posix.join(indexing.bundleDirectory, "pagefind-ui.js")}`,
),
);
const uiSettings = {
element: `#${ui.containerId}`,
showImages: ui.showImages,
showEmptyFilters: ui.showEmptyFilters,
resetStyles: ui.resetStyles,
bundlePath: site.url(posix.join(indexing.bundleDirectory, "/")),
baseUrl: site.url("/"),
translations: ui.translations,
};
const init = document.createElement("script");
init.setAttribute("type", "text/javascript");
init.innerHTML =
`window.addEventListener('DOMContentLoaded', () => { new PagefindUI(${
JSON.stringify(uiSettings)
}); });`;
document.head.append(script, init);
}
});
}
site.addEventListener("afterBuild", async () => {
const binary = await downloadBinary(options.binary);
const args = buildArguments(
options.indexing,
site.dest(),
);
const { code, stdout, stderr } = await new Deno.Command(binary, { args })
.output();
if (code !== 0) {
throw new Error(
`Pagefind exited with code ${code}
${stdout}
${stderr}`,
);
} else if (options.indexing.verbose) {
console.log(stdout);
}
});
};
}
function buildArguments(
options: Options["indexing"],
source: string,
): string[] {
const args = [
"--source",
source,
"--bundle-dir",
options.bundleDirectory,
"--root-selector",
options.rootSelector,
"--glob",
options.glob,
];
if (options.forceLanguage) {
args.push("--force-language", options.forceLanguage);
}
if (options.excludeSelectors.length > 0) {
args.push("--exclude-selectors", options.excludeSelectors.join(","));
}
if (options.verbose) {
args.push("--verbose");
}
return args;
}