reroute-js 0.20.0 → 0.20.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/_/basic/package.json +6 -6
- package/_/basic/src/client/App.tsx +2 -2
- package/_/basic/src/index.ts +1 -1
- package/_/blog/package.json +7 -7
- package/_/blog/src/client/App.tsx +3 -3
- package/_/blog/src/index.ts +1 -1
- package/_/redirects/package.json +7 -7
- package/_/rss/package.json +7 -7
- package/_/search/package.json +28 -0
- package/_/search/reroute.config.ts +37 -0
- package/_/search/src/client/App.tsx +11 -0
- package/_/search/src/client/components/SearchBox.tsx +325 -0
- package/_/search/src/client/components/SearchResults.tsx +114 -0
- package/_/search/src/client/index.html +27 -0
- package/_/search/src/client/index.tsx +8 -0
- package/_/search/src/client/lib/markdown-components.tsx +72 -0
- package/_/search/src/client/routes/[404].tsx +49 -0
- package/_/search/src/client/routes/blog/[slug].tsx +109 -0
- package/_/search/src/client/routes/blog/content/building-rest-apis.md +155 -0
- package/_/search/src/client/routes/blog/content/getting-started-with-react.md +92 -0
- package/_/search/src/client/routes/blog/content/typescript-best-practices.md +138 -0
- package/_/search/src/client/routes/docs/[slug].tsx +108 -0
- package/_/search/src/client/routes/docs/content/api-reference.md +425 -0
- package/_/search/src/client/routes/docs/content/content-collections.md +300 -0
- package/_/search/src/client/routes/docs/content/installation.md +142 -0
- package/_/search/src/client/routes/docs/content/routing.md +235 -0
- package/_/search/src/client/routes/index.tsx +113 -0
- package/_/search/src/client/routes/search.tsx +88 -0
- package/_/search/src/index.ts +10 -0
- package/_/search/tsconfig.json +26 -0
- package/_/sitemap/package.json +7 -7
- package/_/store/package.json +7 -7
- package/_/store/src/client/App.tsx +2 -9
- package/_/store/src/index.ts +1 -1
- package/_/streaming/package.json +7 -7
- package/cli/bin.d.ts +1 -1
- package/cli/bin.js +93 -19
- package/cli/bin.js.map +6 -6
- package/cli/index.d.ts +1 -1
- package/cli/index.js +4 -4
- package/cli/index.js.map +1 -1
- package/cli/src/cli.d.ts +1 -1
- package/cli/src/commands/analyze.d.ts +1 -1
- package/cli/src/commands/build.d.ts +1 -1
- package/cli/src/commands/build.d.ts.map +1 -1
- package/cli/src/commands/dev.d.ts +1 -1
- package/cli/src/commands/gen.d.ts +1 -1
- package/cli/src/commands/gen.d.ts.map +1 -1
- package/cli/src/commands/index.d.ts +1 -1
- package/cli/src/commands/init.d.ts +1 -1
- package/cli/src/commands/lib/command.d.ts +1 -1
- package/cli/src/commands/lib/env.d.ts +1 -1
- package/cli/src/commands/lib/index.d.ts +1 -1
- package/cli/src/commands/lib/log.d.ts +1 -1
- package/cli/src/commands/lib/markdown/availability.d.ts +1 -1
- package/cli/src/commands/lib/markdown/index.d.ts +1 -1
- package/cli/src/commands/lib/markdown/processor.d.ts +1 -1
- package/cli/src/commands/lib/production.d.ts +1 -1
- package/cli/src/commands/lib/server.d.ts +1 -1
- package/cli/src/commands/lib/streaming/analyzer.d.ts +1 -1
- package/cli/src/commands/lib/streaming/suspense.d.ts +1 -1
- package/cli/src/commands/lib/tailwind.d.ts +1 -1
- package/cli/src/commands/lib/version.d.ts +1 -1
- package/cli/src/commands/og.d.ts +1 -1
- package/cli/src/commands/start.d.ts +1 -1
- package/cli/src/index.d.ts +1 -1
- package/core/index.d.ts +1 -1
- package/core/index.js +11 -4
- package/core/index.js.map +4 -4
- package/core/src/bundler/hash.d.ts +1 -1
- package/core/src/bundler/index.d.ts +1 -1
- package/core/src/config.d.ts +1 -1
- package/core/src/config.d.ts.map +1 -1
- package/core/src/content/discovery.d.ts +1 -1
- package/core/src/content/index.d.ts +1 -1
- package/core/src/content/metadata.d.ts +1 -1
- package/core/src/index.d.ts +1 -1
- package/core/src/llms/extractor.d.ts +1 -1
- package/core/src/llms/formatter.d.ts +1 -1
- package/core/src/llms/full-generator.d.ts +1 -1
- package/core/src/llms/index-generator.d.ts +1 -1
- package/core/src/llms/index.d.ts +1 -1
- package/core/src/og/discovery.d.ts +1 -1
- package/core/src/og/index.d.ts +1 -1
- package/core/src/og/meta.d.ts +1 -1
- package/core/src/og/render.d.ts +1 -1
- package/core/src/og/types.d.ts +1 -1
- package/core/src/robots/discovery.d.ts +1 -1
- package/core/src/robots/generator.d.ts +1 -1
- package/core/src/robots/index.d.ts +1 -1
- package/core/src/robots/policies.d.ts +1 -1
- package/core/src/rss/discovery.d.ts +1 -1
- package/core/src/rss/generator.d.ts +1 -1
- package/core/src/rss/index.d.ts +1 -1
- package/core/src/sitemap/discovery.d.ts +1 -1
- package/core/src/sitemap/generator.d.ts +1 -1
- package/core/src/sitemap/index.d.ts +1 -1
- package/core/src/ssr/index.d.ts +1 -1
- package/core/src/ssr/lib/cache.d.ts +1 -1
- package/core/src/ssr/lib/collections.d.ts +1 -1
- package/core/src/ssr/lib/compression.d.ts +1 -1
- package/core/src/ssr/lib/compute/content.d.ts +1 -1
- package/core/src/ssr/lib/compute/index.d.ts +1 -1
- package/core/src/ssr/lib/compute/layouts.d.ts +1 -1
- package/core/src/ssr/lib/compute/routes.d.ts +1 -1
- package/core/src/ssr/lib/data.d.ts +1 -1
- package/core/src/ssr/lib/html.d.ts +1 -1
- package/core/src/ssr/lib/html.d.ts.map +1 -1
- package/core/src/ssr/lib/imports.d.ts +1 -1
- package/core/src/ssr/lib/index.d.ts +1 -1
- package/core/src/ssr/lib/layouts.d.ts +1 -1
- package/core/src/ssr/lib/metadata.d.ts +1 -1
- package/core/src/ssr/lib/mime.d.ts +1 -1
- package/core/src/ssr/lib/modules.d.ts +1 -1
- package/core/src/ssr/lib/path.d.ts +1 -1
- package/core/src/ssr/lib/preload.d.ts +1 -1
- package/core/src/ssr/lib/scripts/collections.d.ts +1 -1
- package/core/src/ssr/lib/scripts/data.d.ts +1 -1
- package/core/src/ssr/lib/scripts/escape.d.ts +1 -1
- package/core/src/ssr/lib/scripts/feeds.d.ts +1 -1
- package/core/src/ssr/lib/scripts/index.d.ts +1 -1
- package/core/src/ssr/lib/seed.d.ts +1 -1
- package/core/src/ssr/lib/setup.d.ts +1 -1
- package/core/src/ssr/lib/styles.d.ts +1 -1
- package/core/src/ssr/lib/template.d.ts +1 -1
- package/core/src/ssr/lib/types.d.ts +1 -1
- package/core/src/ssr/render.d.ts +1 -1
- package/core/src/ssr/stream.d.ts +1 -1
- package/elysia/index.d.ts +1 -1
- package/elysia/index.js +14 -4
- package/elysia/index.js.map +5 -5
- package/elysia/src/index.d.ts +1 -1
- package/elysia/src/libs/cache.d.ts +1 -1
- package/elysia/src/libs/caching.d.ts +1 -1
- package/elysia/src/libs/http.d.ts +1 -1
- package/elysia/src/libs/image.d.ts +1 -1
- package/elysia/src/libs/index.d.ts +1 -1
- package/elysia/src/libs/llms.d.ts +1 -1
- package/elysia/src/libs/response.d.ts +1 -1
- package/elysia/src/libs/serving.d.ts +1 -1
- package/elysia/src/plugin.d.ts +1 -1
- package/elysia/src/plugin.d.ts.map +1 -1
- package/elysia/src/routes/artifacts.d.ts +1 -1
- package/elysia/src/routes/content.d.ts +1 -1
- package/elysia/src/routes/image.d.ts +1 -1
- package/elysia/src/routes/index.d.ts +1 -1
- package/elysia/src/routes/internal.d.ts +1 -1
- package/elysia/src/routes/llms.d.ts +1 -1
- package/elysia/src/routes/og.d.ts +1 -1
- package/elysia/src/routes/redirects.d.ts +1 -1
- package/elysia/src/routes/robots.d.ts +1 -1
- package/elysia/src/routes/rss.d.ts +1 -1
- package/elysia/src/routes/search.d.ts +1 -1
- package/elysia/src/routes/sitemap.d.ts +1 -1
- package/elysia/src/routes/ssr.d.ts +1 -1
- package/elysia/src/routes/static.d.ts +1 -1
- package/elysia/src/types.d.ts +1 -1
- package/package.json +1 -1
- package/react/index.d.ts +1 -1
- package/react/index.js +14 -17
- package/react/index.js.map +3 -3
- package/react/src/components/ClientOnly.d.ts +1 -1
- package/react/src/components/ContentRoute.d.ts +1 -1
- package/react/src/components/Image.d.ts +1 -1
- package/react/src/components/LazyRoute.d.ts +1 -1
- package/react/src/components/Link.d.ts +1 -1
- package/react/src/components/Markdown.d.ts +1 -1
- package/react/src/components/Outlet.d.ts +1 -1
- package/react/src/components/index.d.ts +1 -1
- package/react/src/hooks/index.d.ts +1 -1
- package/react/src/hooks/useContent.d.ts +1 -1
- package/react/src/hooks/useData.d.ts +1 -1
- package/react/src/hooks/useFeed.d.ts +1 -1
- package/react/src/hooks/useLayoutData.d.ts +1 -1
- package/react/src/hooks/useLlms.d.ts +1 -1
- package/react/src/hooks/useNavigate.d.ts +1 -1
- package/react/src/hooks/useParams.d.ts +1 -1
- package/react/src/hooks/useRouter.d.ts +1 -1
- package/react/src/hooks/useSearch.d.ts +1 -1
- package/react/src/hooks/useSearchParams.d.ts +1 -1
- package/react/src/hooks/useToc.d.ts +1 -1
- package/react/src/index.d.ts +1 -1
- package/react/src/lib/collection.d.ts +1 -1
- package/react/src/lib/content.d.ts +1 -1
- package/react/src/lib/head.d.ts +1 -1
- package/react/src/lib/index.d.ts +1 -1
- package/react/src/lib/lazy-route.d.ts +1 -1
- package/react/src/lib/route-loader.d.ts +1 -1
- package/react/src/providers/ContentProvider.d.ts +1 -1
- package/react/src/providers/RerouteProvider.d.ts +5 -7
- package/react/src/providers/RerouteProvider.d.ts.map +1 -1
- package/react/src/providers/RouterProvider.d.ts +1 -1
- package/react/src/providers/index.d.ts +1 -1
- package/react/src/types/any.d.ts +1 -1
- package/react/src/types/index.d.ts +1 -1
- package/react/src/types/router.d.ts +1 -1
package/_/basic/package.json
CHANGED
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
"version": "0.0.0",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"types": "tsc --noEmit",
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
"gen": "reroute gen",
|
|
8
|
+
"start": "reroute start --prod",
|
|
9
|
+
"dev": "reroute dev",
|
|
10
|
+
"build": "reroute build",
|
|
11
|
+
"kill": "bunx kill-port 3000",
|
|
12
|
+
"analyze": "reroute analyze --prod --open"
|
|
13
13
|
},
|
|
14
14
|
"private": true,
|
|
15
15
|
"dependencies": {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { RerouteProvider } from 'reroute-js/react';
|
|
2
|
-
import {
|
|
2
|
+
import { bundle } from '../../.reroute';
|
|
3
3
|
|
|
4
4
|
interface AppProps {
|
|
5
5
|
pathname?: string;
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
export default function App({ pathname }: AppProps = {}) {
|
|
9
|
-
return <RerouteProvider
|
|
9
|
+
return <RerouteProvider pathname={pathname} {...bundle} />;
|
|
10
10
|
}
|
package/_/basic/src/index.ts
CHANGED
|
@@ -9,6 +9,6 @@ const app = new Elysia()
|
|
|
9
9
|
console.log('[api] log from my api')
|
|
10
10
|
return { message: 'Hello from server' }
|
|
11
11
|
})
|
|
12
|
-
.listen(
|
|
12
|
+
.listen(Bun.env.PORT || '3000');
|
|
13
13
|
|
|
14
14
|
console.log(`[app] Running at ${app.server?.hostname}:${app.server?.port}`);
|
package/_/blog/package.json
CHANGED
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
"description": "A Reroute application",
|
|
4
4
|
"version": "0.0.0",
|
|
5
5
|
"scripts": {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
6
|
+
"types": "tsc --noEmit",
|
|
7
|
+
"gen": "reroute gen",
|
|
8
|
+
"start": "PORT=3001 reroute start --prod",
|
|
9
|
+
"dev": "PORT=3001 reroute dev",
|
|
10
|
+
"build": "reroute build",
|
|
11
|
+
"kill": "bunx kill-port 3001",
|
|
12
|
+
"analyze": "reroute analyze --prod --open"
|
|
13
13
|
},
|
|
14
14
|
"private": true,
|
|
15
15
|
"dependencies": {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { RerouteProvider } from 'reroute-js/react';
|
|
2
|
-
import {
|
|
2
|
+
import { bundle } from '../../.reroute';
|
|
3
3
|
|
|
4
4
|
interface AppProps {
|
|
5
5
|
pathname?: string;
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
export default function App({ pathname }: AppProps = {}) {
|
|
9
|
-
console.log('[app]
|
|
10
|
-
return <RerouteProvider
|
|
9
|
+
console.log('[app] blog mounted');
|
|
10
|
+
return <RerouteProvider pathname={pathname} {...bundle} />;
|
|
11
11
|
}
|
package/_/blog/src/index.ts
CHANGED
|
@@ -9,7 +9,7 @@ const app = new Elysia()
|
|
|
9
9
|
console.log('[api] log from my api');
|
|
10
10
|
return { message: 'Hello from server' };
|
|
11
11
|
})
|
|
12
|
-
.listen(
|
|
12
|
+
.listen(Bun.env.PORT || '3001');
|
|
13
13
|
|
|
14
14
|
console.log(
|
|
15
15
|
`[app] Blog running at ${app.server?.hostname}:${app.server?.port} on ${Bun.env.NODE_ENV || 'development'}`,
|
package/_/redirects/package.json
CHANGED
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
"description": "A Reroute application",
|
|
5
5
|
"private": true,
|
|
6
6
|
"scripts": {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
7
|
+
"types": "tsc --noEmit",
|
|
8
|
+
"gen": "reroute gen",
|
|
9
|
+
"start": "PORT=3002 reroute start --prod",
|
|
10
|
+
"dev": "PORT=3002 reroute dev",
|
|
11
|
+
"build": "reroute build",
|
|
12
|
+
"kill": "bunx kill-port 3002",
|
|
13
|
+
"analyze": "reroute analyze --prod --open"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
16
|
"reroute-js": "latest",
|
package/_/rss/package.json
CHANGED
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
"description": "A Reroute application",
|
|
5
5
|
"private": true,
|
|
6
6
|
"scripts": {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
7
|
+
"types": "tsc --noEmit",
|
|
8
|
+
"gen": "reroute gen",
|
|
9
|
+
"start": "PORT=3003 reroute start --prod",
|
|
10
|
+
"dev": "PORT=3003 reroute dev",
|
|
11
|
+
"build": "reroute build",
|
|
12
|
+
"kill": "bunx kill-port 3003",
|
|
13
|
+
"analyze": "reroute analyze --prod --open"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
16
|
"reroute-js": "latest",
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "reroute-search-example",
|
|
3
|
+
"description": "Search functionality example with content indexing",
|
|
4
|
+
"version": "0.0.0",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"types": "tsc --noEmit",
|
|
7
|
+
"gen": "reroute gen",
|
|
8
|
+
"start": "PORT=3004 reroute start --prod",
|
|
9
|
+
"dev": "PORT=3004 reroute dev",
|
|
10
|
+
"build": "reroute build",
|
|
11
|
+
"kill": "bunx kill-port 3004"
|
|
12
|
+
},
|
|
13
|
+
"private": true,
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"reroute-js": "latest",
|
|
16
|
+
"elysia": "^1.4.12",
|
|
17
|
+
"react": "^19.2.0",
|
|
18
|
+
"react-dom": "^19.2.0",
|
|
19
|
+
"sharp": "^0.34.4"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/bun": "latest",
|
|
23
|
+
"@types/react": "^19.2.2",
|
|
24
|
+
"@types/react-dom": "^19.2.2",
|
|
25
|
+
"typescript": "^5"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { defineConfig } from 'reroute-js/core';
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
options: {
|
|
5
|
+
app: './src/client/App',
|
|
6
|
+
maxAge: 3600,
|
|
7
|
+
compression: true,
|
|
8
|
+
},
|
|
9
|
+
markdown: {
|
|
10
|
+
components: './src/client/lib/markdown-components',
|
|
11
|
+
enableGfm: true,
|
|
12
|
+
theme: 'github-dark',
|
|
13
|
+
},
|
|
14
|
+
// 🔍 Search configuration
|
|
15
|
+
search: {
|
|
16
|
+
// Enable search index generation
|
|
17
|
+
enabled: true,
|
|
18
|
+
|
|
19
|
+
// Index specific collections (or omit to index all)
|
|
20
|
+
collections: ['blog', 'docs'],
|
|
21
|
+
|
|
22
|
+
// Include full content text in search (default: false)
|
|
23
|
+
// Set to false for lightweight index with only headings + metadata
|
|
24
|
+
includeContent: true,
|
|
25
|
+
|
|
26
|
+
// Maximum content length to index (if includeContent is true)
|
|
27
|
+
contentLimit: 5000,
|
|
28
|
+
|
|
29
|
+
// Meta fields to index from frontmatter
|
|
30
|
+
metaFields: ['title', 'description', 'excerpt', 'tags', 'category'],
|
|
31
|
+
|
|
32
|
+
// Heading level range to extract
|
|
33
|
+
minHeadingLevel: 1,
|
|
34
|
+
maxHeadingLevel: 6,
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { RerouteProvider } from 'reroute-js/react';
|
|
2
|
+
import { bundle } from '../../.reroute';
|
|
3
|
+
|
|
4
|
+
interface AppProps {
|
|
5
|
+
pathname?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export default function App({ pathname }: AppProps = {}) {
|
|
9
|
+
return <RerouteProvider pathname={pathname} {...bundle} />;
|
|
10
|
+
}
|
|
11
|
+
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
|
|
2
|
+
import { useNavigate, useRouter, useSearch } from 'reroute-js/react';
|
|
3
|
+
import { useEffect, useState } from 'react';
|
|
4
|
+
import SearchResults from './SearchResults';
|
|
5
|
+
|
|
6
|
+
interface SearchBoxProps {
|
|
7
|
+
initialQuery?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function SearchBox({ initialQuery = '' }: SearchBoxProps = {}) {
|
|
11
|
+
const router = useRouter();
|
|
12
|
+
|
|
13
|
+
// Initialize from URL search params (works on both server and client)
|
|
14
|
+
const urlQuery = router.searchParams.get('q') || initialQuery || '';
|
|
15
|
+
const [query, setQuery] = useState(urlQuery);
|
|
16
|
+
const [selectedCollections, setSelectedCollections] = useState<string[]>([]);
|
|
17
|
+
const [page, setPage] = useState(1);
|
|
18
|
+
const navigate = useNavigate();
|
|
19
|
+
const [mounted, setMounted] = useState(false);
|
|
20
|
+
|
|
21
|
+
// Mark as mounted after first render
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
setMounted(true);
|
|
24
|
+
}, []);
|
|
25
|
+
|
|
26
|
+
// Sync search query to URL as user types (client-side only, no history entries)
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
if (!mounted || typeof window === 'undefined') {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const url = new URL(window.location.href);
|
|
33
|
+
const currentQ = url.searchParams.get('q') || '';
|
|
34
|
+
|
|
35
|
+
// Only update if different to avoid loops
|
|
36
|
+
if (currentQ !== query) {
|
|
37
|
+
if (query) {
|
|
38
|
+
url.searchParams.set('q', query);
|
|
39
|
+
} else {
|
|
40
|
+
url.searchParams.delete('q');
|
|
41
|
+
}
|
|
42
|
+
window.history.replaceState({}, '', url.toString());
|
|
43
|
+
}
|
|
44
|
+
}, [query, mounted]);
|
|
45
|
+
|
|
46
|
+
const { results, isLoading, error, total, totalPages, pageSize } = useSearch({
|
|
47
|
+
query,
|
|
48
|
+
page,
|
|
49
|
+
pageSize: 10,
|
|
50
|
+
collections: selectedCollections.length > 0 ? selectedCollections : undefined,
|
|
51
|
+
debounce: 400,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const toggleCollection = (collection: string) => {
|
|
55
|
+
setSelectedCollections((prev) =>
|
|
56
|
+
prev.includes(collection)
|
|
57
|
+
? prev.filter((c) => c !== collection)
|
|
58
|
+
: [...prev, collection],
|
|
59
|
+
);
|
|
60
|
+
setPage(1); // Reset to first page when filters change
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<div
|
|
65
|
+
style={{
|
|
66
|
+
background: '#fff',
|
|
67
|
+
padding: '2rem',
|
|
68
|
+
borderRadius: '12px',
|
|
69
|
+
boxShadow: '0 2px 8px rgba(0,0,0,0.1)',
|
|
70
|
+
}}
|
|
71
|
+
>
|
|
72
|
+
{/* Search Input */}
|
|
73
|
+
<div style={{ marginBottom: '1.5rem' }}>
|
|
74
|
+
<label
|
|
75
|
+
htmlFor="search-input"
|
|
76
|
+
style={{
|
|
77
|
+
display: 'block',
|
|
78
|
+
marginBottom: '0.5rem',
|
|
79
|
+
fontWeight: 600,
|
|
80
|
+
fontSize: '1.125rem',
|
|
81
|
+
}}
|
|
82
|
+
>
|
|
83
|
+
Search Content
|
|
84
|
+
</label>
|
|
85
|
+
<input
|
|
86
|
+
id="search-input"
|
|
87
|
+
type="search"
|
|
88
|
+
value={query}
|
|
89
|
+
onChange={(e) => {
|
|
90
|
+
setQuery(e.target.value);
|
|
91
|
+
setPage(1); // Reset to first page on new search
|
|
92
|
+
}}
|
|
93
|
+
onKeyDown={(e) => {
|
|
94
|
+
if (e.key === 'Enter' && query.trim()) {
|
|
95
|
+
const params = new URLSearchParams();
|
|
96
|
+
params.set('q', query);
|
|
97
|
+
if (selectedCollections.length > 0) {
|
|
98
|
+
params.set('collections', selectedCollections.join(','));
|
|
99
|
+
}
|
|
100
|
+
navigate(`/search?${params.toString()}`);
|
|
101
|
+
}
|
|
102
|
+
}}
|
|
103
|
+
placeholder="Type to search articles, docs, and more..."
|
|
104
|
+
style={{
|
|
105
|
+
width: '100%',
|
|
106
|
+
padding: '0.875rem 1rem',
|
|
107
|
+
fontSize: '1rem',
|
|
108
|
+
border: '2px solid #e0e0e0',
|
|
109
|
+
borderRadius: '8px',
|
|
110
|
+
outline: 'none',
|
|
111
|
+
transition: 'border-color 0.2s',
|
|
112
|
+
}}
|
|
113
|
+
onFocus={(e) => {
|
|
114
|
+
e.currentTarget.style.borderColor = '#0066cc';
|
|
115
|
+
}}
|
|
116
|
+
onBlur={(e) => {
|
|
117
|
+
e.currentTarget.style.borderColor = '#e0e0e0';
|
|
118
|
+
}}
|
|
119
|
+
data-testid="search-input"
|
|
120
|
+
/>
|
|
121
|
+
</div>
|
|
122
|
+
|
|
123
|
+
{/* Collection Filters */}
|
|
124
|
+
<div style={{ marginBottom: '1.5rem' }}>
|
|
125
|
+
<label
|
|
126
|
+
style={{
|
|
127
|
+
display: 'block',
|
|
128
|
+
marginBottom: '0.5rem',
|
|
129
|
+
fontWeight: 600,
|
|
130
|
+
fontSize: '0.875rem',
|
|
131
|
+
color: '#666',
|
|
132
|
+
}}
|
|
133
|
+
>
|
|
134
|
+
Filter by Collection
|
|
135
|
+
</label>
|
|
136
|
+
<div style={{ display: 'flex', gap: '0.75rem', flexWrap: 'wrap' }}>
|
|
137
|
+
<button
|
|
138
|
+
type="button"
|
|
139
|
+
onClick={() => toggleCollection('blog')}
|
|
140
|
+
style={{
|
|
141
|
+
padding: '0.5rem 1rem',
|
|
142
|
+
border: selectedCollections.includes('blog')
|
|
143
|
+
? '2px solid #0066cc'
|
|
144
|
+
: '2px solid #e0e0e0',
|
|
145
|
+
background: selectedCollections.includes('blog')
|
|
146
|
+
? '#e6f2ff'
|
|
147
|
+
: '#fff',
|
|
148
|
+
color: selectedCollections.includes('blog') ? '#0066cc' : '#666',
|
|
149
|
+
borderRadius: '20px',
|
|
150
|
+
cursor: 'pointer',
|
|
151
|
+
fontSize: '0.875rem',
|
|
152
|
+
fontWeight: 500,
|
|
153
|
+
transition: 'all 0.2s',
|
|
154
|
+
}}
|
|
155
|
+
>
|
|
156
|
+
{selectedCollections.includes('blog') ? '✓ ' : ''}Blog
|
|
157
|
+
</button>
|
|
158
|
+
<button
|
|
159
|
+
type="button"
|
|
160
|
+
onClick={() => toggleCollection('docs')}
|
|
161
|
+
style={{
|
|
162
|
+
padding: '0.5rem 1rem',
|
|
163
|
+
border: selectedCollections.includes('docs')
|
|
164
|
+
? '2px solid #0066cc'
|
|
165
|
+
: '2px solid #e0e0e0',
|
|
166
|
+
background: selectedCollections.includes('docs')
|
|
167
|
+
? '#e6f2ff'
|
|
168
|
+
: '#fff',
|
|
169
|
+
color: selectedCollections.includes('docs') ? '#0066cc' : '#666',
|
|
170
|
+
borderRadius: '20px',
|
|
171
|
+
cursor: 'pointer',
|
|
172
|
+
fontSize: '0.875rem',
|
|
173
|
+
fontWeight: 500,
|
|
174
|
+
transition: 'all 0.2s',
|
|
175
|
+
}}
|
|
176
|
+
>
|
|
177
|
+
{selectedCollections.includes('docs') ? '✓ ' : ''}Docs
|
|
178
|
+
</button>
|
|
179
|
+
</div>
|
|
180
|
+
</div>
|
|
181
|
+
|
|
182
|
+
{/* Search Status */}
|
|
183
|
+
{isLoading && (
|
|
184
|
+
<div
|
|
185
|
+
style={{
|
|
186
|
+
padding: '1rem',
|
|
187
|
+
background: '#f0f9ff',
|
|
188
|
+
border: '1px solid #bae6fd',
|
|
189
|
+
borderRadius: '8px',
|
|
190
|
+
color: '#0369a1',
|
|
191
|
+
}}
|
|
192
|
+
data-testid="search-loading"
|
|
193
|
+
>
|
|
194
|
+
🔍 Searching...
|
|
195
|
+
</div>
|
|
196
|
+
)}
|
|
197
|
+
|
|
198
|
+
{error && (
|
|
199
|
+
<div
|
|
200
|
+
style={{
|
|
201
|
+
padding: '1rem',
|
|
202
|
+
background: '#fef2f2',
|
|
203
|
+
border: '1px solid #fecaca',
|
|
204
|
+
borderRadius: '8px',
|
|
205
|
+
color: '#dc2626',
|
|
206
|
+
}}
|
|
207
|
+
data-testid="search-error"
|
|
208
|
+
>
|
|
209
|
+
❌ Error: {error.message}
|
|
210
|
+
</div>
|
|
211
|
+
)}
|
|
212
|
+
|
|
213
|
+
{/* Results Count */}
|
|
214
|
+
{!isLoading && query && !error && (
|
|
215
|
+
<div
|
|
216
|
+
style={{
|
|
217
|
+
padding: '0.75rem 0',
|
|
218
|
+
marginBottom: '1rem',
|
|
219
|
+
borderBottom: '1px solid #e0e0e0',
|
|
220
|
+
fontSize: '0.875rem',
|
|
221
|
+
color: '#666',
|
|
222
|
+
}}
|
|
223
|
+
data-testid={total > 0 ? 'search-results-count' : 'search-no-results'}
|
|
224
|
+
>
|
|
225
|
+
{total > 0 ? (
|
|
226
|
+
<span>
|
|
227
|
+
Found <strong>{total}</strong> result{total !== 1 ? 's' : ''}{' '}
|
|
228
|
+
for <strong>"{query}"</strong>
|
|
229
|
+
{totalPages > 1 && (
|
|
230
|
+
<span style={{ marginLeft: '0.5rem', color: '#999' }}>
|
|
231
|
+
(Page {page} of {totalPages})
|
|
232
|
+
</span>
|
|
233
|
+
)}
|
|
234
|
+
</span>
|
|
235
|
+
) : (
|
|
236
|
+
<span>
|
|
237
|
+
No results found for <strong>"{query}"</strong>
|
|
238
|
+
</span>
|
|
239
|
+
)}
|
|
240
|
+
</div>
|
|
241
|
+
)}
|
|
242
|
+
|
|
243
|
+
{/* Results */}
|
|
244
|
+
{!isLoading && results.length > 0 && (
|
|
245
|
+
<>
|
|
246
|
+
<SearchResults results={results} />
|
|
247
|
+
|
|
248
|
+
{/* Pagination Controls */}
|
|
249
|
+
{totalPages > 1 && (
|
|
250
|
+
<div
|
|
251
|
+
style={{
|
|
252
|
+
marginTop: '2rem',
|
|
253
|
+
padding: '1rem 0',
|
|
254
|
+
borderTop: '1px solid #e0e0e0',
|
|
255
|
+
display: 'flex',
|
|
256
|
+
justifyContent: 'center',
|
|
257
|
+
alignItems: 'center',
|
|
258
|
+
gap: '1rem',
|
|
259
|
+
}}
|
|
260
|
+
>
|
|
261
|
+
<button
|
|
262
|
+
type="button"
|
|
263
|
+
onClick={() => setPage((p) => Math.max(1, p - 1))}
|
|
264
|
+
disabled={page === 1}
|
|
265
|
+
style={{
|
|
266
|
+
padding: '0.5rem 1rem',
|
|
267
|
+
border: '2px solid #e0e0e0',
|
|
268
|
+
background: page === 1 ? '#f5f5f5' : '#fff',
|
|
269
|
+
color: page === 1 ? '#999' : '#333',
|
|
270
|
+
borderRadius: '6px',
|
|
271
|
+
cursor: page === 1 ? 'not-allowed' : 'pointer',
|
|
272
|
+
fontSize: '0.875rem',
|
|
273
|
+
fontWeight: 500,
|
|
274
|
+
transition: 'all 0.2s',
|
|
275
|
+
}}
|
|
276
|
+
>
|
|
277
|
+
← Previous
|
|
278
|
+
</button>
|
|
279
|
+
<span style={{ fontSize: '0.875rem', color: '#666' }}>
|
|
280
|
+
Page <strong>{page}</strong> of <strong>{totalPages}</strong>
|
|
281
|
+
</span>
|
|
282
|
+
<button
|
|
283
|
+
type="button"
|
|
284
|
+
onClick={() => setPage((p) => Math.min(totalPages, p + 1))}
|
|
285
|
+
disabled={page === totalPages}
|
|
286
|
+
style={{
|
|
287
|
+
padding: '0.5rem 1rem',
|
|
288
|
+
border: '2px solid #e0e0e0',
|
|
289
|
+
background: page === totalPages ? '#f5f5f5' : '#fff',
|
|
290
|
+
color: page === totalPages ? '#999' : '#333',
|
|
291
|
+
borderRadius: '6px',
|
|
292
|
+
cursor: page === totalPages ? 'not-allowed' : 'pointer',
|
|
293
|
+
fontSize: '0.875rem',
|
|
294
|
+
fontWeight: 500,
|
|
295
|
+
transition: 'all 0.2s',
|
|
296
|
+
}}
|
|
297
|
+
>
|
|
298
|
+
Next →
|
|
299
|
+
</button>
|
|
300
|
+
</div>
|
|
301
|
+
)}
|
|
302
|
+
</>
|
|
303
|
+
)}
|
|
304
|
+
|
|
305
|
+
{/* Empty State */}
|
|
306
|
+
{!isLoading && !query && (
|
|
307
|
+
<div
|
|
308
|
+
style={{
|
|
309
|
+
padding: '3rem 1rem',
|
|
310
|
+
textAlign: 'center',
|
|
311
|
+
color: '#999',
|
|
312
|
+
}}
|
|
313
|
+
>
|
|
314
|
+
<div style={{ fontSize: '3rem', marginBottom: '1rem' }}>🔍</div>
|
|
315
|
+
<p style={{ fontSize: '1.125rem' }}>
|
|
316
|
+
Start typing to search through blog posts and documentation
|
|
317
|
+
</p>
|
|
318
|
+
</div>
|
|
319
|
+
)}
|
|
320
|
+
</div>
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
export default SearchBox;
|
|
325
|
+
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { Link, type SearchResultEntry } from 'reroute-js/react';
|
|
2
|
+
|
|
3
|
+
interface SearchResultsProps {
|
|
4
|
+
results: SearchResultEntry[];
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function SearchResults({ results }: SearchResultsProps) {
|
|
8
|
+
return (
|
|
9
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '1.5rem' }} data-testid="search-results-list">
|
|
10
|
+
{results.map((result) => (
|
|
11
|
+
<article
|
|
12
|
+
key={result.id}
|
|
13
|
+
style={{
|
|
14
|
+
padding: '1.5rem',
|
|
15
|
+
background: '#fafafa',
|
|
16
|
+
border: '1px solid #e0e0e0',
|
|
17
|
+
borderRadius: '8px',
|
|
18
|
+
transition: 'all 0.2s',
|
|
19
|
+
}}
|
|
20
|
+
onMouseEnter={(e) => {
|
|
21
|
+
e.currentTarget.style.background = '#f5f5f5';
|
|
22
|
+
e.currentTarget.style.borderColor = '#0066cc';
|
|
23
|
+
}}
|
|
24
|
+
onMouseLeave={(e) => {
|
|
25
|
+
e.currentTarget.style.background = '#fafafa';
|
|
26
|
+
e.currentTarget.style.borderColor = '#e0e0e0';
|
|
27
|
+
}}
|
|
28
|
+
data-testid={`search-result-${result.id}`}
|
|
29
|
+
>
|
|
30
|
+
{/* Collection Badge */}
|
|
31
|
+
<div style={{ marginBottom: '0.5rem' }}>
|
|
32
|
+
<span
|
|
33
|
+
style={{
|
|
34
|
+
display: 'inline-block',
|
|
35
|
+
padding: '0.25rem 0.75rem',
|
|
36
|
+
background: result.collection === 'blog' ? '#dbeafe' : '#dcfce7',
|
|
37
|
+
color: result.collection === 'blog' ? '#1e40af' : '#15803d',
|
|
38
|
+
borderRadius: '12px',
|
|
39
|
+
fontSize: '0.75rem',
|
|
40
|
+
fontWeight: 600,
|
|
41
|
+
textTransform: 'uppercase',
|
|
42
|
+
letterSpacing: '0.5px',
|
|
43
|
+
}}
|
|
44
|
+
>
|
|
45
|
+
{result.collection}
|
|
46
|
+
</span>
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
{/* Title */}
|
|
50
|
+
<h3 style={{ marginBottom: '0.75rem' }}>
|
|
51
|
+
<Link
|
|
52
|
+
to={`/${result.collection}/${result.slug}`}
|
|
53
|
+
style={{
|
|
54
|
+
fontSize: '1.5rem',
|
|
55
|
+
fontWeight: 600,
|
|
56
|
+
color: '#0066cc',
|
|
57
|
+
textDecoration: 'none',
|
|
58
|
+
}}
|
|
59
|
+
onMouseEnter={(e) => {
|
|
60
|
+
e.currentTarget.style.textDecoration = 'underline';
|
|
61
|
+
}}
|
|
62
|
+
onMouseLeave={(e) => {
|
|
63
|
+
e.currentTarget.style.textDecoration = 'none';
|
|
64
|
+
}}
|
|
65
|
+
>
|
|
66
|
+
{result.title}
|
|
67
|
+
</Link>
|
|
68
|
+
</h3>
|
|
69
|
+
|
|
70
|
+
{/* Excerpt */}
|
|
71
|
+
{result.excerpt && (
|
|
72
|
+
<p
|
|
73
|
+
style={{
|
|
74
|
+
marginBottom: '1rem',
|
|
75
|
+
color: '#666',
|
|
76
|
+
fontSize: '0.9375rem',
|
|
77
|
+
lineHeight: 1.6,
|
|
78
|
+
}}
|
|
79
|
+
>
|
|
80
|
+
{result.excerpt}
|
|
81
|
+
</p>
|
|
82
|
+
)}
|
|
83
|
+
|
|
84
|
+
{/* Read More Link */}
|
|
85
|
+
<div style={{ marginTop: '1rem', paddingTop: '1rem', borderTop: '1px solid #e0e0e0' }}>
|
|
86
|
+
<Link
|
|
87
|
+
to={`/${result.collection}/${result.slug}`}
|
|
88
|
+
style={{
|
|
89
|
+
color: '#0066cc',
|
|
90
|
+
textDecoration: 'none',
|
|
91
|
+
fontSize: '0.875rem',
|
|
92
|
+
fontWeight: 600,
|
|
93
|
+
display: 'inline-flex',
|
|
94
|
+
alignItems: 'center',
|
|
95
|
+
gap: '0.25rem',
|
|
96
|
+
}}
|
|
97
|
+
onMouseEnter={(e) => {
|
|
98
|
+
e.currentTarget.style.textDecoration = 'underline';
|
|
99
|
+
}}
|
|
100
|
+
onMouseLeave={(e) => {
|
|
101
|
+
e.currentTarget.style.textDecoration = 'none';
|
|
102
|
+
}}
|
|
103
|
+
>
|
|
104
|
+
Read full article →
|
|
105
|
+
</Link>
|
|
106
|
+
</div>
|
|
107
|
+
</article>
|
|
108
|
+
))}
|
|
109
|
+
</div>
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export default SearchResults;
|
|
114
|
+
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Search Example - Reroute</title>
|
|
7
|
+
<style>
|
|
8
|
+
* {
|
|
9
|
+
margin: 0;
|
|
10
|
+
padding: 0;
|
|
11
|
+
box-sizing: border-box;
|
|
12
|
+
}
|
|
13
|
+
body {
|
|
14
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
|
|
15
|
+
'Helvetica Neue', Arial, sans-serif;
|
|
16
|
+
line-height: 1.6;
|
|
17
|
+
color: #333;
|
|
18
|
+
background: #f5f5f5;
|
|
19
|
+
}
|
|
20
|
+
</style>
|
|
21
|
+
</head>
|
|
22
|
+
<body>
|
|
23
|
+
<div id="root"></div>
|
|
24
|
+
<script type="module" src="/index.tsx"></script>
|
|
25
|
+
</body>
|
|
26
|
+
</html>
|
|
27
|
+
|