@zeropress/build-pages 0.5.6 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +105 -24
- package/action.yml +3 -0
- package/dist/action.js +1814 -334
- package/dist/prebuild.js +157 -24
- package/package.json +2 -2
- package/schemas/zeropress-build-pages.config.v0.1.schema.json +33 -12
- package/src/action.js +2 -1
- package/src/index.js +154 -3
- package/src/prebuild.js +178 -23
- package/themes/docs/assets/style.css +212 -0
- package/themes/docs/assets/theme.js +121 -0
- package/themes/docs/layout.html +20 -4
- package/themes/docs/page.html +2 -2
- package/themes/docs/partials/theme-scripts.html +1 -0
- package/themes/docs/post.html +1 -1
- package/themes/docs/theme.json +5 -6
package/src/prebuild.js
CHANGED
|
@@ -6,6 +6,7 @@ import matter from 'gray-matter';
|
|
|
6
6
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
7
7
|
const rootDir = process.cwd();
|
|
8
8
|
const sourceDir = resolveEnvPath(['ZEROPRESS_BUILD_PAGES_SOURCE'], 'docs');
|
|
9
|
+
const publicDir = resolveEnvPath(['ZEROPRESS_BUILD_PAGES_PUBLIC_DIR'], sourceDir);
|
|
9
10
|
const defaultConfigPath = path.join(sourceDir, '.zeropress', 'config.json');
|
|
10
11
|
const configPath = resolveOptionalEnvPath(['ZEROPRESS_BUILD_PAGES_CONFIG'], defaultConfigPath);
|
|
11
12
|
const outDir = path.join(rootDir, '.zeropress');
|
|
@@ -16,7 +17,13 @@ const skipUntitledMarkdown = readBooleanEnv('ZEROPRESS_SKIP_UNTITLED_MARKDOWN');
|
|
|
16
17
|
const copyMarkdownSource = readBooleanEnv('ZEROPRESS_COPY_MARKDOWN_SOURCE', true);
|
|
17
18
|
const FRONT_PAGE_TYPES = new Set(['theme_index', 'markdown', 'html']);
|
|
18
19
|
const BUILD_PAGES_CONFIG_SCHEMA_URL = 'https://zeropress.dev/schemas/zeropress-build-pages.config.v0.1.schema.json';
|
|
19
|
-
const PREVIEW_DATA_SCHEMA_URL = 'https://zeropress.dev/schemas/preview-data.v0.
|
|
20
|
+
const PREVIEW_DATA_SCHEMA_URL = 'https://zeropress.dev/schemas/preview-data.v0.6.schema.json';
|
|
21
|
+
const FRONT_MATTER_DATA_KEY_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_]*(?:-[a-zA-Z0-9_]+)*$/;
|
|
22
|
+
const FRONT_MATTER_DATA_MAX_DEPTH = 4;
|
|
23
|
+
const FRONT_MATTER_DATA_MAX_KEYS = 64;
|
|
24
|
+
const FRONT_MATTER_DATA_MAX_ARRAY_LENGTH = 256;
|
|
25
|
+
const FRONT_MATTER_DISCOVERABILITY_VALUES = new Set(['default', 'noindex', 'delist']);
|
|
26
|
+
const markdownDiscoverExcludeRoots = buildMarkdownDiscoverExcludeRoots();
|
|
20
27
|
|
|
21
28
|
class PrebuildMarkdownError extends Error {
|
|
22
29
|
constructor(sourcePath, reason, expected = '', code = 'invalid_markdown') {
|
|
@@ -99,6 +106,8 @@ async function main() {
|
|
|
99
106
|
...frontMatter.meta,
|
|
100
107
|
...(copyMarkdownSource ? { source_markdown_url: buildSourceMarkdownUrl(sourcePath) } : {}),
|
|
101
108
|
},
|
|
109
|
+
...(frontMatter.data !== undefined ? { data: frontMatter.data } : {}),
|
|
110
|
+
...(frontMatter.discoverability !== 'default' ? { discoverability: frontMatter.discoverability } : {}),
|
|
102
111
|
content: rewriteMarkdownLinks(bodyMarkdown, sourcePath, routeBySourcePath),
|
|
103
112
|
document_type: 'markdown',
|
|
104
113
|
excerpt: frontMatter.description || extractExcerpt(bodyMarkdown, title),
|
|
@@ -115,7 +124,7 @@ async function main() {
|
|
|
115
124
|
|
|
116
125
|
const previewData = {
|
|
117
126
|
$schema: PREVIEW_DATA_SCHEMA_URL,
|
|
118
|
-
version: '0.
|
|
127
|
+
version: '0.6',
|
|
119
128
|
generator: 'zeropress-build-pages',
|
|
120
129
|
generated_at: new Date().toISOString(),
|
|
121
130
|
site,
|
|
@@ -222,18 +231,21 @@ function buildSiteData(config, frontPage) {
|
|
|
222
231
|
title: configuredSite.title,
|
|
223
232
|
description: configuredSite.description,
|
|
224
233
|
url: configuredSite.url,
|
|
225
|
-
|
|
234
|
+
media_base_url: '',
|
|
226
235
|
locale: 'en-US',
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
236
|
+
posts_per_page: 10,
|
|
237
|
+
datetime_display: 'static',
|
|
238
|
+
date_style: 'medium',
|
|
239
|
+
time_style: 'none',
|
|
230
240
|
timezone: 'UTC',
|
|
231
241
|
permalinks: defaultPermalinks(),
|
|
232
242
|
front_page: frontPage,
|
|
233
243
|
post_index: {
|
|
234
244
|
enabled: false,
|
|
235
245
|
},
|
|
236
|
-
|
|
246
|
+
disallow_comments: true,
|
|
247
|
+
expose_generator: configuredSite.expose_generator !== false,
|
|
248
|
+
search: configuredSite.search !== false,
|
|
237
249
|
indexing: configuredSite.indexing !== false,
|
|
238
250
|
};
|
|
239
251
|
|
|
@@ -269,11 +281,13 @@ function normalizeSiteConfig(value) {
|
|
|
269
281
|
}
|
|
270
282
|
|
|
271
283
|
const configuredSite = isPlainObject(value) ? value : {};
|
|
272
|
-
assertKnownConfigKeys(configuredSite, ['title', 'description', 'url', 'indexing', 'footer'], 'site');
|
|
284
|
+
assertKnownConfigKeys(configuredSite, ['title', 'description', 'url', 'expose_generator', 'search', 'indexing', 'footer'], 'site');
|
|
273
285
|
const site = {
|
|
274
286
|
title: readConfigString(configuredSite.title, 'Documentation'),
|
|
275
287
|
description: readConfigString(configuredSite.description, 'A documentation site.'),
|
|
276
288
|
url: readEnv('ZEROPRESS_SITE_URL', readConfigString(configuredSite.url, '')),
|
|
289
|
+
expose_generator: readConfigBoolean(configuredSite.expose_generator, true, 'site.expose_generator'),
|
|
290
|
+
search: readConfigBoolean(configuredSite.search, true, 'site.search'),
|
|
277
291
|
indexing: readConfigBoolean(configuredSite.indexing, true, 'site.indexing'),
|
|
278
292
|
};
|
|
279
293
|
|
|
@@ -300,19 +314,11 @@ function normalizeFooter(value) {
|
|
|
300
314
|
footer.copyright_text = copyrightText;
|
|
301
315
|
}
|
|
302
316
|
|
|
303
|
-
if (value.attribution !== undefined
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
if (isPlainObject(value.attribution)) {
|
|
307
|
-
assertKnownConfigKeys(value.attribution, ['enabled'], 'site.footer.attribution');
|
|
308
|
-
if (value.attribution.enabled !== undefined && typeof value.attribution.enabled !== 'boolean') {
|
|
309
|
-
throw new PrebuildConfigError('site.footer.attribution.enabled must be a boolean when provided.');
|
|
317
|
+
if (value.attribution !== undefined) {
|
|
318
|
+
if (typeof value.attribution !== 'boolean') {
|
|
319
|
+
throw new PrebuildConfigError('site.footer.attribution must be a boolean when provided.');
|
|
310
320
|
}
|
|
311
|
-
|
|
312
|
-
if (isPlainObject(value.attribution) && typeof value.attribution.enabled === 'boolean') {
|
|
313
|
-
footer.attribution = {
|
|
314
|
-
enabled: value.attribution.enabled,
|
|
315
|
-
};
|
|
321
|
+
footer.attribution = value.attribution;
|
|
316
322
|
}
|
|
317
323
|
|
|
318
324
|
return Object.keys(footer).length ? footer : undefined;
|
|
@@ -712,12 +718,29 @@ function normalizeMenuItem(item, pathLabel) {
|
|
|
712
718
|
url,
|
|
713
719
|
type: readConfigString(item.type, 'custom'),
|
|
714
720
|
target: readConfigString(item.target, '_self'),
|
|
721
|
+
...(item.meta !== undefined ? { meta: normalizeMenuItemMeta(item.meta, `${pathLabel}.meta`) } : {}),
|
|
715
722
|
children: Array.isArray(item.children)
|
|
716
723
|
? item.children.map((child, index) => normalizeMenuItem(child, `${pathLabel}.children[${index}]`))
|
|
717
724
|
: [],
|
|
718
725
|
};
|
|
719
726
|
}
|
|
720
727
|
|
|
728
|
+
function normalizeMenuItemMeta(value, pathLabel) {
|
|
729
|
+
if (!isPlainObject(value)) {
|
|
730
|
+
throw new PrebuildConfigError(`${pathLabel} must be an object when provided.`);
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
const meta = {};
|
|
734
|
+
for (const [key, metaValue] of Object.entries(value)) {
|
|
735
|
+
if (!isPreviewMetaValue(metaValue)) {
|
|
736
|
+
throw new PrebuildConfigError(`${pathLabel}.${key} must be a string, number, boolean, or null.`);
|
|
737
|
+
}
|
|
738
|
+
meta[key] = metaValue;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
return meta;
|
|
742
|
+
}
|
|
743
|
+
|
|
721
744
|
function defaultMenus() {
|
|
722
745
|
return {
|
|
723
746
|
primary: {
|
|
@@ -741,6 +764,7 @@ function buildPrebuildReport({
|
|
|
741
764
|
return {
|
|
742
765
|
generated_at: new Date().toISOString(),
|
|
743
766
|
source_dir: formatSourcePath(sourceDir),
|
|
767
|
+
public_dir: formatSourcePath(publicDir),
|
|
744
768
|
config_path: formatSourcePath(configPath),
|
|
745
769
|
build_pages_config_path: formatSourcePath(buildPagesConfigPath),
|
|
746
770
|
preview_data_path: formatSourcePath(previewDataPath),
|
|
@@ -767,7 +791,8 @@ function buildPrebuildReport({
|
|
|
767
791
|
function printPrebuildSummary(report) {
|
|
768
792
|
const lines = [
|
|
769
793
|
'ZeroPress build report',
|
|
770
|
-
`-
|
|
794
|
+
`- Source root: ${report.source_dir}`,
|
|
795
|
+
`- Public root: ${report.public_dir}`,
|
|
771
796
|
`- Markdown discovered: ${report.markdown.discovered}`,
|
|
772
797
|
`- Markdown pages generated: ${report.markdown.generated_pages}`,
|
|
773
798
|
`- Markdown skipped: ${report.markdown.skipped}`,
|
|
@@ -844,7 +869,9 @@ function normalizePublishedFrontMatter(frontMatter, sourcePath) {
|
|
|
844
869
|
title: normalizeFrontMatterTitle(frontMatter.title, sourcePath),
|
|
845
870
|
description: normalizeFrontMatterDescription(frontMatter.description, sourcePath),
|
|
846
871
|
path: normalizeFrontMatterRoutePath(frontMatter.path, sourcePath),
|
|
872
|
+
discoverability: normalizeFrontMatterDiscoverability(frontMatter.discoverability, sourcePath),
|
|
847
873
|
meta: normalizeFrontMatterMeta(frontMatter.meta, sourcePath),
|
|
874
|
+
data: normalizeFrontMatterData(frontMatter.data, sourcePath),
|
|
848
875
|
};
|
|
849
876
|
}
|
|
850
877
|
|
|
@@ -900,13 +927,28 @@ function normalizeFrontMatterRoutePath(value, sourcePath) {
|
|
|
900
927
|
throw new PrebuildMarkdownError(
|
|
901
928
|
sourcePath,
|
|
902
929
|
'front matter path must be a safe generated route path.',
|
|
903
|
-
' path: guides/install\n path: spec/preview-data-v0.
|
|
930
|
+
' path: guides/install\n path: spec/preview-data-v0.6',
|
|
904
931
|
);
|
|
905
932
|
}
|
|
906
933
|
|
|
907
934
|
return routePath;
|
|
908
935
|
}
|
|
909
936
|
|
|
937
|
+
function normalizeFrontMatterDiscoverability(value, sourcePath) {
|
|
938
|
+
if (value === undefined) {
|
|
939
|
+
return 'default';
|
|
940
|
+
}
|
|
941
|
+
if (typeof value === 'string' && FRONT_MATTER_DISCOVERABILITY_VALUES.has(value)) {
|
|
942
|
+
return value;
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
throw new PrebuildMarkdownError(
|
|
946
|
+
sourcePath,
|
|
947
|
+
`front matter discoverability must be one of: ${Array.from(FRONT_MATTER_DISCOVERABILITY_VALUES).join(', ')}.`,
|
|
948
|
+
' discoverability: default\n discoverability: noindex\n discoverability: delist',
|
|
949
|
+
);
|
|
950
|
+
}
|
|
951
|
+
|
|
910
952
|
function isSafeRoutePathSegment(segment) {
|
|
911
953
|
return (
|
|
912
954
|
/^[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?$/.test(segment)
|
|
@@ -943,11 +985,102 @@ function isPreviewMetaValue(value) {
|
|
|
943
985
|
return (
|
|
944
986
|
value === null
|
|
945
987
|
|| typeof value === 'string'
|
|
946
|
-
|| typeof value === 'number'
|
|
988
|
+
|| (typeof value === 'number' && Number.isFinite(value))
|
|
947
989
|
|| typeof value === 'boolean'
|
|
948
990
|
);
|
|
949
991
|
}
|
|
950
992
|
|
|
993
|
+
function normalizeFrontMatterData(value, sourcePath) {
|
|
994
|
+
if (value === undefined) {
|
|
995
|
+
return undefined;
|
|
996
|
+
}
|
|
997
|
+
if (!isPlainObject(value)) {
|
|
998
|
+
throw new PrebuildMarkdownError(
|
|
999
|
+
sourcePath,
|
|
1000
|
+
'front matter data must be an object when provided.',
|
|
1001
|
+
);
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
validateFrontMatterDataObject(value, sourcePath, 'data', 0);
|
|
1005
|
+
return value;
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
function validateFrontMatterDataValue(value, sourcePath, pathLabel, depth) {
|
|
1009
|
+
if (value === null || typeof value === 'string' || typeof value === 'boolean') {
|
|
1010
|
+
return;
|
|
1011
|
+
}
|
|
1012
|
+
if (typeof value === 'number') {
|
|
1013
|
+
if (!Number.isFinite(value)) {
|
|
1014
|
+
throw new PrebuildMarkdownError(
|
|
1015
|
+
sourcePath,
|
|
1016
|
+
`front matter ${pathLabel} must be a finite number.`,
|
|
1017
|
+
);
|
|
1018
|
+
}
|
|
1019
|
+
return;
|
|
1020
|
+
}
|
|
1021
|
+
if (Array.isArray(value)) {
|
|
1022
|
+
validateFrontMatterDataArray(value, sourcePath, pathLabel, depth);
|
|
1023
|
+
return;
|
|
1024
|
+
}
|
|
1025
|
+
if (isPlainObject(value)) {
|
|
1026
|
+
validateFrontMatterDataObject(value, sourcePath, pathLabel, depth);
|
|
1027
|
+
return;
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
throw new PrebuildMarkdownError(
|
|
1031
|
+
sourcePath,
|
|
1032
|
+
`front matter ${pathLabel} must be JSON-safe structured data.`,
|
|
1033
|
+
);
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
function validateFrontMatterDataObject(object, sourcePath, pathLabel, depth) {
|
|
1037
|
+
if (depth > FRONT_MATTER_DATA_MAX_DEPTH) {
|
|
1038
|
+
throw new PrebuildMarkdownError(
|
|
1039
|
+
sourcePath,
|
|
1040
|
+
`front matter ${pathLabel} nesting must not exceed ${FRONT_MATTER_DATA_MAX_DEPTH} container levels.`,
|
|
1041
|
+
);
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
const entries = Object.entries(object);
|
|
1045
|
+
if (entries.length > FRONT_MATTER_DATA_MAX_KEYS) {
|
|
1046
|
+
throw new PrebuildMarkdownError(
|
|
1047
|
+
sourcePath,
|
|
1048
|
+
`front matter ${pathLabel} must not contain more than ${FRONT_MATTER_DATA_MAX_KEYS} keys.`,
|
|
1049
|
+
);
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
for (const [key, dataValue] of entries) {
|
|
1053
|
+
const childLabel = `${pathLabel}.${key}`;
|
|
1054
|
+
if (!FRONT_MATTER_DATA_KEY_PATTERN.test(key)) {
|
|
1055
|
+
throw new PrebuildMarkdownError(
|
|
1056
|
+
sourcePath,
|
|
1057
|
+
`front matter ${childLabel} uses an invalid key.`,
|
|
1058
|
+
);
|
|
1059
|
+
}
|
|
1060
|
+
validateFrontMatterDataValue(dataValue, sourcePath, childLabel, depth + 1);
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
function validateFrontMatterDataArray(array, sourcePath, pathLabel, depth) {
|
|
1065
|
+
if (depth > FRONT_MATTER_DATA_MAX_DEPTH) {
|
|
1066
|
+
throw new PrebuildMarkdownError(
|
|
1067
|
+
sourcePath,
|
|
1068
|
+
`front matter ${pathLabel} nesting must not exceed ${FRONT_MATTER_DATA_MAX_DEPTH} container levels.`,
|
|
1069
|
+
);
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
if (array.length > FRONT_MATTER_DATA_MAX_ARRAY_LENGTH) {
|
|
1073
|
+
throw new PrebuildMarkdownError(
|
|
1074
|
+
sourcePath,
|
|
1075
|
+
`front matter ${pathLabel} must not contain more than ${FRONT_MATTER_DATA_MAX_ARRAY_LENGTH} items.`,
|
|
1076
|
+
);
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
array.forEach((dataValue, index) => {
|
|
1080
|
+
validateFrontMatterDataValue(dataValue, sourcePath, `${pathLabel}[${index}]`, depth + 1);
|
|
1081
|
+
});
|
|
1082
|
+
}
|
|
1083
|
+
|
|
951
1084
|
function formatFrontMatterValue(value) {
|
|
952
1085
|
if (typeof value === 'string') {
|
|
953
1086
|
return `"${value}"`;
|
|
@@ -1007,6 +1140,10 @@ async function listMarkdownFiles(dir) {
|
|
|
1007
1140
|
}
|
|
1008
1141
|
|
|
1009
1142
|
const entryPath = path.join(dir, entry.name);
|
|
1143
|
+
if (isMarkdownDiscoverExcluded(entryPath)) {
|
|
1144
|
+
continue;
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1010
1147
|
if (entry.isDirectory()) {
|
|
1011
1148
|
files.push(...await listMarkdownFiles(entryPath));
|
|
1012
1149
|
continue;
|
|
@@ -1020,6 +1157,24 @@ async function listMarkdownFiles(dir) {
|
|
|
1020
1157
|
return files.sort((left, right) => left.localeCompare(right));
|
|
1021
1158
|
}
|
|
1022
1159
|
|
|
1160
|
+
function buildMarkdownDiscoverExcludeRoots() {
|
|
1161
|
+
if (samePath(sourceDir, publicDir) || !isPathInside(sourceDir, publicDir)) {
|
|
1162
|
+
return [];
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
return [publicDir];
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
function isMarkdownDiscoverExcluded(entryPath) {
|
|
1169
|
+
return markdownDiscoverExcludeRoots.some((excludeRoot) => (
|
|
1170
|
+
samePath(entryPath, excludeRoot) || isPathInside(excludeRoot, entryPath)
|
|
1171
|
+
));
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
function samePath(firstPath, secondPath) {
|
|
1175
|
+
return path.resolve(firstPath) === path.resolve(secondPath);
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1023
1178
|
function shouldIgnoreMarkdownDiscoverEntry(name) {
|
|
1024
1179
|
const basename = String(name || '');
|
|
1025
1180
|
const lowerName = basename.toLowerCase();
|
|
@@ -12,6 +12,15 @@
|
|
|
12
12
|
--prose-ink: #344054;
|
|
13
13
|
--pre-bg: #101828;
|
|
14
14
|
--pre-ink: #f8fafc;
|
|
15
|
+
--syntax-comment: #98a2b3;
|
|
16
|
+
--syntax-keyword: #f472b6;
|
|
17
|
+
--syntax-title: #93c5fd;
|
|
18
|
+
--syntax-string: #86efac;
|
|
19
|
+
--syntax-number: #fbbf24;
|
|
20
|
+
--syntax-attr: #67e8f9;
|
|
21
|
+
--syntax-built-in: #c4b5fd;
|
|
22
|
+
--syntax-deletion: #fca5a5;
|
|
23
|
+
--syntax-addition: #bbf7d0;
|
|
15
24
|
--button-ink: #ffffff;
|
|
16
25
|
--alert-note: #2563eb;
|
|
17
26
|
--alert-tip: #059669;
|
|
@@ -99,12 +108,31 @@ a {
|
|
|
99
108
|
gap: 1.5rem;
|
|
100
109
|
}
|
|
101
110
|
|
|
111
|
+
.visually-hidden {
|
|
112
|
+
position: absolute;
|
|
113
|
+
width: 1px;
|
|
114
|
+
height: 1px;
|
|
115
|
+
padding: 0;
|
|
116
|
+
overflow: hidden;
|
|
117
|
+
clip: rect(0, 0, 0, 0);
|
|
118
|
+
white-space: nowrap;
|
|
119
|
+
border: 0;
|
|
120
|
+
}
|
|
121
|
+
|
|
102
122
|
.brand {
|
|
103
123
|
color: var(--ink);
|
|
104
124
|
font-weight: 750;
|
|
105
125
|
text-decoration: none;
|
|
106
126
|
}
|
|
107
127
|
|
|
128
|
+
.site-header__actions {
|
|
129
|
+
display: flex;
|
|
130
|
+
align-items: center;
|
|
131
|
+
justify-content: flex-end;
|
|
132
|
+
gap: 1rem;
|
|
133
|
+
min-width: 0;
|
|
134
|
+
}
|
|
135
|
+
|
|
108
136
|
.site-nav ul {
|
|
109
137
|
list-style: none;
|
|
110
138
|
margin: 0;
|
|
@@ -129,6 +157,109 @@ a {
|
|
|
129
157
|
color: var(--accent);
|
|
130
158
|
}
|
|
131
159
|
|
|
160
|
+
.site-search {
|
|
161
|
+
position: relative;
|
|
162
|
+
width: min(22rem, 34vw);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.site-search__form {
|
|
166
|
+
display: flex;
|
|
167
|
+
align-items: center;
|
|
168
|
+
gap: 0.4rem;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
.site-search__input {
|
|
172
|
+
min-width: 0;
|
|
173
|
+
width: 100%;
|
|
174
|
+
height: 2.35rem;
|
|
175
|
+
padding: 0 0.75rem;
|
|
176
|
+
border: 1px solid var(--line);
|
|
177
|
+
border-radius: 6px;
|
|
178
|
+
background: var(--surface);
|
|
179
|
+
color: var(--ink);
|
|
180
|
+
font: inherit;
|
|
181
|
+
font-size: 0.92rem;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.site-search__input:focus {
|
|
185
|
+
border-color: var(--accent);
|
|
186
|
+
outline: 2px solid color-mix(in srgb, var(--accent) 22%, transparent);
|
|
187
|
+
outline-offset: 1px;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.site-search__button {
|
|
191
|
+
height: 2.35rem;
|
|
192
|
+
padding: 0 0.75rem;
|
|
193
|
+
border: 0;
|
|
194
|
+
border-radius: 6px;
|
|
195
|
+
background: var(--accent);
|
|
196
|
+
color: var(--button-ink);
|
|
197
|
+
font: inherit;
|
|
198
|
+
font-size: 0.88rem;
|
|
199
|
+
font-weight: 750;
|
|
200
|
+
cursor: pointer;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.site-search__panel {
|
|
204
|
+
position: absolute;
|
|
205
|
+
top: calc(100% + 0.55rem);
|
|
206
|
+
right: 0;
|
|
207
|
+
width: min(31rem, calc(100vw - 2rem));
|
|
208
|
+
max-height: min(31rem, calc(100vh - 6rem));
|
|
209
|
+
overflow: auto;
|
|
210
|
+
padding: 0.85rem;
|
|
211
|
+
border: 1px solid var(--line);
|
|
212
|
+
border-radius: var(--radius);
|
|
213
|
+
background: var(--surface);
|
|
214
|
+
box-shadow: 0 20px 45px rgb(15 23 42 / 18%);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.site-search__status {
|
|
218
|
+
margin: 0 0 0.75rem;
|
|
219
|
+
color: var(--muted);
|
|
220
|
+
font-size: 0.86rem;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.site-search__results {
|
|
224
|
+
list-style: none;
|
|
225
|
+
margin: 0;
|
|
226
|
+
padding: 0;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.site-search__results li + li {
|
|
230
|
+
margin-top: 0.65rem;
|
|
231
|
+
padding-top: 0.65rem;
|
|
232
|
+
border-top: 1px solid var(--line);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
.site-search__results a {
|
|
236
|
+
display: inline-block;
|
|
237
|
+
color: var(--ink);
|
|
238
|
+
font-weight: 750;
|
|
239
|
+
text-decoration: none;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
.site-search__results a:hover {
|
|
243
|
+
color: var(--accent);
|
|
244
|
+
text-decoration: underline;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
.site-search__results span {
|
|
248
|
+
display: block;
|
|
249
|
+
margin-top: 0.1rem;
|
|
250
|
+
color: var(--accent);
|
|
251
|
+
font-size: 0.75rem;
|
|
252
|
+
font-weight: 750;
|
|
253
|
+
text-transform: uppercase;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
.site-search__results p {
|
|
257
|
+
margin: 0.25rem 0 0;
|
|
258
|
+
color: var(--muted);
|
|
259
|
+
font-size: 0.88rem;
|
|
260
|
+
line-height: 1.45;
|
|
261
|
+
}
|
|
262
|
+
|
|
132
263
|
.hero {
|
|
133
264
|
background: var(--surface);
|
|
134
265
|
border-bottom: 1px solid var(--line);
|
|
@@ -372,6 +503,71 @@ h1 {
|
|
|
372
503
|
padding: 0;
|
|
373
504
|
}
|
|
374
505
|
|
|
506
|
+
.prose pre code.hljs {
|
|
507
|
+
color: var(--pre-ink);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
.prose .hljs-comment,
|
|
511
|
+
.prose .hljs-quote {
|
|
512
|
+
color: var(--syntax-comment);
|
|
513
|
+
font-style: italic;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
.prose .hljs-keyword,
|
|
517
|
+
.prose .hljs-selector-tag,
|
|
518
|
+
.prose .hljs-subst {
|
|
519
|
+
color: var(--syntax-keyword);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
.prose .hljs-title,
|
|
523
|
+
.prose .hljs-section,
|
|
524
|
+
.prose .hljs-name,
|
|
525
|
+
.prose .hljs-selector-id,
|
|
526
|
+
.prose .hljs-selector-class {
|
|
527
|
+
color: var(--syntax-title);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
.prose .hljs-string,
|
|
531
|
+
.prose .hljs-regexp,
|
|
532
|
+
.prose .hljs-symbol,
|
|
533
|
+
.prose .hljs-bullet {
|
|
534
|
+
color: var(--syntax-string);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
.prose .hljs-number,
|
|
538
|
+
.prose .hljs-literal {
|
|
539
|
+
color: var(--syntax-number);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
.prose .hljs-attr,
|
|
543
|
+
.prose .hljs-attribute,
|
|
544
|
+
.prose .hljs-variable,
|
|
545
|
+
.prose .hljs-template-variable {
|
|
546
|
+
color: var(--syntax-attr);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
.prose .hljs-built_in,
|
|
550
|
+
.prose .hljs-type,
|
|
551
|
+
.prose .hljs-class .hljs-title {
|
|
552
|
+
color: var(--syntax-built-in);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
.prose .hljs-deletion {
|
|
556
|
+
color: var(--syntax-deletion);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
.prose .hljs-addition {
|
|
560
|
+
color: var(--syntax-addition);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
.prose .hljs-emphasis {
|
|
564
|
+
font-style: italic;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
.prose .hljs-strong {
|
|
568
|
+
font-weight: 800;
|
|
569
|
+
}
|
|
570
|
+
|
|
375
571
|
.prose table {
|
|
376
572
|
display: block;
|
|
377
573
|
width: 100%;
|
|
@@ -544,10 +740,26 @@ h1, h2, h3, h4, h5, h6 {
|
|
|
544
740
|
padding: 1rem 0;
|
|
545
741
|
}
|
|
546
742
|
|
|
743
|
+
.site-header__actions {
|
|
744
|
+
width: 100%;
|
|
745
|
+
align-items: stretch;
|
|
746
|
+
flex-direction: column;
|
|
747
|
+
}
|
|
748
|
+
|
|
547
749
|
.site-nav ul {
|
|
548
750
|
justify-content: flex-start;
|
|
549
751
|
}
|
|
550
752
|
|
|
753
|
+
.site-search {
|
|
754
|
+
width: 100%;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
.site-search__panel {
|
|
758
|
+
left: 0;
|
|
759
|
+
right: auto;
|
|
760
|
+
width: 100%;
|
|
761
|
+
}
|
|
762
|
+
|
|
551
763
|
.docs-grid,
|
|
552
764
|
.npm-links,
|
|
553
765
|
.doc-layout,
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
const searchRoot = document.querySelector('[data-site-search]');
|
|
2
|
+
|
|
3
|
+
if (searchRoot) {
|
|
4
|
+
const form = searchRoot.querySelector('[data-site-search-form]');
|
|
5
|
+
const input = searchRoot.querySelector('[data-site-search-input]');
|
|
6
|
+
const panel = searchRoot.querySelector('[data-site-search-panel]');
|
|
7
|
+
const status = searchRoot.querySelector('[data-site-search-status]');
|
|
8
|
+
const resultsList = searchRoot.querySelector('[data-site-search-results]');
|
|
9
|
+
let searchApiPromise;
|
|
10
|
+
let debounceTimer;
|
|
11
|
+
let requestId = 0;
|
|
12
|
+
|
|
13
|
+
const loadSearchApi = () => {
|
|
14
|
+
searchApiPromise ||= import('/_zeropress/search.js');
|
|
15
|
+
return searchApiPromise;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const clearResults = () => {
|
|
19
|
+
resultsList.replaceChildren();
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const showPanel = () => {
|
|
23
|
+
panel.hidden = false;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const hidePanel = () => {
|
|
27
|
+
panel.hidden = true;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const setStatus = (message) => {
|
|
31
|
+
status.textContent = message;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const renderResults = async (items) => {
|
|
35
|
+
clearResults();
|
|
36
|
+
if (!items.length) {
|
|
37
|
+
setStatus('No results found.');
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
setStatus(`${items.length} result${items.length === 1 ? '' : 's'} found.`);
|
|
42
|
+
const fragment = document.createDocumentFragment();
|
|
43
|
+
|
|
44
|
+
for (const item of items) {
|
|
45
|
+
const data = await item.data();
|
|
46
|
+
const title = data.meta?.title || data.url;
|
|
47
|
+
const excerpt = data.plain_excerpt || data.excerpt || '';
|
|
48
|
+
const listItem = document.createElement('li');
|
|
49
|
+
const link = document.createElement('a');
|
|
50
|
+
const meta = document.createElement('span');
|
|
51
|
+
const summary = document.createElement('p');
|
|
52
|
+
|
|
53
|
+
link.href = data.url;
|
|
54
|
+
link.textContent = title;
|
|
55
|
+
meta.textContent = data.meta?.type || 'page';
|
|
56
|
+
summary.textContent = excerpt;
|
|
57
|
+
|
|
58
|
+
listItem.append(link, meta, summary);
|
|
59
|
+
fragment.append(listItem);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
resultsList.append(fragment);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const runSearch = async () => {
|
|
66
|
+
const query = input.value.trim();
|
|
67
|
+
const currentRequest = ++requestId;
|
|
68
|
+
showPanel();
|
|
69
|
+
|
|
70
|
+
if (query.length < 2) {
|
|
71
|
+
clearResults();
|
|
72
|
+
setStatus('Type at least two characters.');
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
setStatus('Searching...');
|
|
77
|
+
clearResults();
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
const searchApi = await loadSearchApi();
|
|
81
|
+
const response = await searchApi.search(query, { limit: 8 });
|
|
82
|
+
if (currentRequest === requestId) {
|
|
83
|
+
await renderResults(response.results || []);
|
|
84
|
+
}
|
|
85
|
+
} catch {
|
|
86
|
+
if (currentRequest === requestId) {
|
|
87
|
+
clearResults();
|
|
88
|
+
setStatus('Search is not available for this build.');
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
input.addEventListener('input', () => {
|
|
94
|
+
window.clearTimeout(debounceTimer);
|
|
95
|
+
debounceTimer = window.setTimeout(runSearch, 160);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
input.addEventListener('focus', () => {
|
|
99
|
+
if (input.value.trim()) {
|
|
100
|
+
showPanel();
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
form.addEventListener('submit', (event) => {
|
|
105
|
+
event.preventDefault();
|
|
106
|
+
runSearch();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
document.addEventListener('keydown', (event) => {
|
|
110
|
+
if (event.key === 'Escape') {
|
|
111
|
+
hidePanel();
|
|
112
|
+
input.blur();
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
document.addEventListener('click', (event) => {
|
|
117
|
+
if (!searchRoot.contains(event.target)) {
|
|
118
|
+
hidePanel();
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
}
|