uniweb 0.2.17 → 0.2.20
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/package.json
CHANGED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
## Search Functionality
|
|
2
|
+
|
|
3
|
+
Uniweb sites support full-text search out of the box. Search indexes are generated automatically at build time.
|
|
4
|
+
|
|
5
|
+
### Enabling Search in Your Foundation
|
|
6
|
+
|
|
7
|
+
To add search to your site, your foundation needs the **Fuse.js** library:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
cd foundation
|
|
11
|
+
pnpm add fuse.js
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Then use the search helpers from `@uniweb/kit`:
|
|
15
|
+
|
|
16
|
+
```jsx
|
|
17
|
+
import { useSearch } from '@uniweb/kit/search'
|
|
18
|
+
|
|
19
|
+
function SearchComponent({ website }) {
|
|
20
|
+
const { query, results, isLoading, clear } = useSearch(website)
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<div>
|
|
24
|
+
<input
|
|
25
|
+
type="search"
|
|
26
|
+
placeholder="Search..."
|
|
27
|
+
onChange={e => query(e.target.value)}
|
|
28
|
+
/>
|
|
29
|
+
{isLoading && <span>Searching...</span>}
|
|
30
|
+
<ul>
|
|
31
|
+
{results.map(result => (
|
|
32
|
+
<li key={result.id}>
|
|
33
|
+
<a href={result.href}>{result.title}</a>
|
|
34
|
+
<p dangerouslySetInnerHTML=\{{ __html: result.snippetHtml }} />
|
|
35
|
+
</li>
|
|
36
|
+
))}
|
|
37
|
+
</ul>
|
|
38
|
+
</div>
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Search Configuration
|
|
44
|
+
|
|
45
|
+
Search is enabled by default. To customize, add to `site/site.yml`:
|
|
46
|
+
|
|
47
|
+
```yaml
|
|
48
|
+
search:
|
|
49
|
+
enabled: true
|
|
50
|
+
exclude:
|
|
51
|
+
routes: [/admin, /private] # Don't index these routes
|
|
52
|
+
components: [Debug] # Don't index these component types
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### How It Works
|
|
56
|
+
|
|
57
|
+
1. **Build time**: Uniweb extracts text from all pages and sections
|
|
58
|
+
2. **Output**: A `search-index.json` file is generated in `dist/`
|
|
59
|
+
3. **Runtime**: Components fetch and search the index client-side
|
|
60
|
+
4. **Multi-locale**: Each locale gets its own search index
|
|
61
|
+
|
|
62
|
+
### Search API
|
|
63
|
+
|
|
64
|
+
The `website` object provides these methods:
|
|
65
|
+
|
|
66
|
+
```javascript
|
|
67
|
+
// Check if search is available
|
|
68
|
+
website.isSearchEnabled() // Returns boolean
|
|
69
|
+
|
|
70
|
+
// Get the URL for the search index
|
|
71
|
+
website.getSearchIndexUrl() // "/search-index.json" or "/es/search-index.json"
|
|
72
|
+
|
|
73
|
+
// Get full search configuration
|
|
74
|
+
website.getSearchConfig() // { enabled, indexUrl, locale, include, exclude }
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Search Result Format
|
|
78
|
+
|
|
79
|
+
Results from `useSearch()` include:
|
|
80
|
+
|
|
81
|
+
| Field | Description |
|
|
82
|
+
|-------|-------------|
|
|
83
|
+
| `id` | Unique result identifier |
|
|
84
|
+
| `type` | `"page"` or `"section"` |
|
|
85
|
+
| `route` | Page route |
|
|
86
|
+
| `href` | Full link including anchor |
|
|
87
|
+
| `title` | Result title |
|
|
88
|
+
| `pageTitle` | Parent page title (for sections) |
|
|
89
|
+
| `snippetHtml` | Excerpt with `<mark>` highlighted matches |
|
|
90
|
+
| `snippetText` | Plain text excerpt |
|
|
91
|
+
|
|
92
|
+
### Without @uniweb/kit
|
|
93
|
+
|
|
94
|
+
If you prefer not to use the kit helpers, you can fetch the index directly:
|
|
95
|
+
|
|
96
|
+
```javascript
|
|
97
|
+
// Fetch the search index
|
|
98
|
+
const response = await fetch(website.getSearchIndexUrl())
|
|
99
|
+
const index = await response.json()
|
|
100
|
+
|
|
101
|
+
// Use with Fuse.js directly
|
|
102
|
+
import Fuse from 'fuse.js'
|
|
103
|
+
const fuse = new Fuse(index.entries, {
|
|
104
|
+
keys: ['title', 'content'],
|
|
105
|
+
threshold: 0.35,
|
|
106
|
+
includeMatches: true
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
const results = fuse.search('your query')
|
|
110
|
+
```
|
|
@@ -3,13 +3,19 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import fs from 'node:fs/promises'
|
|
6
|
-
import { existsSync } from 'node:fs'
|
|
6
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs'
|
|
7
7
|
import path from 'node:path'
|
|
8
|
+
import { fileURLToPath } from 'node:url'
|
|
8
9
|
import Handlebars from 'handlebars'
|
|
9
10
|
|
|
11
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
12
|
+
|
|
10
13
|
// Cache for compiled templates
|
|
11
14
|
const templateCache = new Map()
|
|
12
15
|
|
|
16
|
+
// Track if partials have been registered
|
|
17
|
+
let partialsRegistered = false
|
|
18
|
+
|
|
13
19
|
// Store for version data (set by registerVersions)
|
|
14
20
|
let versionData = {}
|
|
15
21
|
|
|
@@ -45,6 +51,40 @@ export function clearMissingVersions() {
|
|
|
45
51
|
missingVersions.clear()
|
|
46
52
|
}
|
|
47
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Register Handlebars partials from the partials directory
|
|
56
|
+
* Partials are available as {{> partial-name}} in templates
|
|
57
|
+
*/
|
|
58
|
+
function registerPartials() {
|
|
59
|
+
if (partialsRegistered) return
|
|
60
|
+
|
|
61
|
+
const partialsDir = path.join(__dirname, '..', 'partials')
|
|
62
|
+
|
|
63
|
+
if (!existsSync(partialsDir)) {
|
|
64
|
+
partialsRegistered = true
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
const files = readdirSync(partialsDir)
|
|
70
|
+
|
|
71
|
+
for (const file of files) {
|
|
72
|
+
if (file.endsWith('.hbs') || file.endsWith('.md')) {
|
|
73
|
+
const partialName = file.replace(/\.(hbs|md)$/, '')
|
|
74
|
+
const partialPath = path.join(partialsDir, file)
|
|
75
|
+
const partialContent = readFileSync(partialPath, 'utf8')
|
|
76
|
+
|
|
77
|
+
Handlebars.registerPartial(partialName, partialContent)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
partialsRegistered = true
|
|
82
|
+
} catch (err) {
|
|
83
|
+
console.warn('Warning: Could not register partials:', err.message)
|
|
84
|
+
partialsRegistered = true
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
48
88
|
/**
|
|
49
89
|
* Handlebars helper to get a package version
|
|
50
90
|
* Usage: {{version "@uniweb/build"}} or {{version "build"}}
|
|
@@ -99,6 +139,9 @@ function findUnresolvedPlaceholders(content) {
|
|
|
99
139
|
* Load and compile a Handlebars template with caching
|
|
100
140
|
*/
|
|
101
141
|
async function loadTemplate(templatePath) {
|
|
142
|
+
// Ensure partials are registered before first template load
|
|
143
|
+
registerPartials()
|
|
144
|
+
|
|
102
145
|
if (templateCache.has(templatePath)) {
|
|
103
146
|
return templateCache.get(templatePath)
|
|
104
147
|
}
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
// Built-in templates (file-based in cli/templates/)
|
|
6
6
|
export const BUILTIN_TEMPLATES = ['single', 'multi', 'template']
|
|
7
7
|
|
|
8
|
-
// Official templates from @uniweb/templates package
|
|
9
|
-
export const OFFICIAL_TEMPLATES = ['marketing', '
|
|
8
|
+
// Official templates from @uniweb/templates package (downloaded from GitHub releases)
|
|
9
|
+
export const OFFICIAL_TEMPLATES = ['marketing', 'academic', 'docs']
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Parse a template identifier and determine its source type
|