uniweb 0.2.17 → 0.2.19

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uniweb",
3
- "version": "0.2.17",
3
+ "version": "0.2.19",
4
4
  "description": "Create structured Vite + React sites with content/code separation",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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
  }