seemore 1.4.2 → 1.5.1

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 CHANGED
@@ -112,9 +112,9 @@ The preview is not just for reading — it is the fastest way to fix what you ar
112
112
  Inline editing is for local previews only — `seemore build` output is static, so nothing is emitted there. It is on by default in dev; switch it off with the `!` prefix:
113
113
 
114
114
  ```ts
115
- export default defineConfig({
115
+ export default {
116
116
  features: ['!content.edit'],
117
- });
117
+ };
118
118
  ```
119
119
 
120
120
  ## Publish it to the web
@@ -126,7 +126,7 @@ npx seemore build # static export to dist/ for any host
126
126
  The result is a `dist/` folder of plain web files: drop it on [Netlify](https://netlify.com), [Surge](https://surge.sh), [Cloudflare Pages](https://pages.cloudflare.com) or [GitHub Pages](https://pages.github.com), or hand it to any web host. Every page is prerendered to its own `index.html`, next to a `404.html` that every static host honours. On top of that, the small conventions individual hosts look for — `_redirects` for Netlify and Cloudflare Pages, `200.html` for Surge, `.nojekyll` for GitHub Pages — are written for you.
127
127
 
128
128
  > [!TIP]
129
- > Project sites on GitHub Pages live under `username.github.io/my-repo/`, not the root, so set `base` once: `defineConfig({ base: '/my-repo/' })` (or `--base /my-repo/` on the CLI). Building under GitHub Actions without it set prints the exact line to add.
129
+ > Project sites on GitHub Pages live under `username.github.io/my-repo/`, not the root, so set `base` once: `base: '/my-repo/'` (or `--base /my-repo/` on the CLI). Building under GitHub Actions without it set prints the exact line to add.
130
130
 
131
131
  ## Configuration
132
132
 
@@ -134,9 +134,7 @@ Optional — a folder with no config file builds correctly everywhere. To adjust
134
134
 
135
135
  ```ts
136
136
  // seemore.config.ts
137
- import { defineConfig } from 'seemore';
138
-
139
- export default defineConfig({
137
+ export default {
140
138
  title: 'My Docs',
141
139
  description: 'Everything about the thing.',
142
140
  favicon: './favicon.svg',
@@ -149,7 +147,7 @@ export default defineConfig({
149
147
  editLink: { base: 'https://github.com/you/repo/edit/main/docs' },
150
148
  search: 'static', // or { provider: 'orama-cloud', endpoint, apiKey } / { provider: 'algolia', appId, apiKey, indexName }
151
149
  exclude: ['drafts/**'],
152
- });
150
+ };
153
151
  ```
154
152
 
155
153
  ### Search
@@ -190,12 +188,12 @@ A flat list of switches for fine control, set as an array on the `features` key.
190
188
 
191
189
  ```ts
192
190
  // seemore.config.ts
193
- export default defineConfig({
191
+ export default {
194
192
  features: [
195
193
  'navigation.path', // off by default → this turns it on
196
194
  '!navigation.instant.prefetch', // on by default → this turns it off
197
195
  ],
198
- });
196
+ };
199
197
  ```
200
198
 
201
199
  | Flag | Default | Effect |
package/dist/index.d.ts CHANGED
@@ -128,12 +128,6 @@ interface ResolvedSeemoreConfig {
128
128
  configFile?: string;
129
129
  }
130
130
 
131
- /**
132
- * Identity function that exists purely for types: it makes `seemore.config.ts` autocomplete
133
- * every option — including every feature flag, which is a plain string union.
134
- */
135
- declare function defineConfig(config: SeemoreConfig): SeemoreConfig;
136
-
137
131
  /**
138
132
  * Frontmatter is validated, not restricted: unknown keys pass through so that a corpus
139
133
  * written for another tool still builds. Only the keys seemore acts on are typed.
@@ -147,4 +141,4 @@ declare const frontmatterSchema: z.ZodObject<{
147
141
  }, z.core.$loose>;
148
142
  type FrontmatterData = z.output<typeof frontmatterSchema> & Record<string, unknown>;
149
143
 
150
- export { type Feature, type FeatureFlag, type FrontmatterData, type NavItem, type ResolvedSeemoreConfig, type SearchConfig, type SeemoreConfig, type Theme, defineConfig };
144
+ export type { Feature, FeatureFlag, FrontmatterData, NavItem, ResolvedSeemoreConfig, SearchConfig, SeemoreConfig, Theme };
package/dist/index.js CHANGED
@@ -1,8 +1 @@
1
- // src/node/config/define.ts
2
- function defineConfig(config) {
3
- return config;
4
- }
5
- export {
6
- defineConfig
7
- };
8
1
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/node/config/define.ts"],"sourcesContent":["import type { SeemoreConfig } from './schema.js';\n\n/**\n * Identity function that exists purely for types: it makes `seemore.config.ts` autocomplete\n * every option — including every feature flag, which is a plain string union.\n */\nexport function defineConfig(config: SeemoreConfig): SeemoreConfig {\n return config;\n}\n"],"mappings":";AAMO,SAAS,aAAa,QAAsC;AACjE,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "seemore",
3
- "version": "1.4.2",
3
+ "version": "1.5.1",
4
4
  "description": "Let AI write the Markdown. Let seemore show it better — zero config documentation framework.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -1,4 +1,4 @@
1
- import { useMemo } from 'react';
1
+ import { useEffect, useMemo, useState } from 'react';
2
2
  import { useNavigate } from 'react-router';
3
3
  import { useDocsSearch } from 'fumadocs-core/search/client';
4
4
  import {
@@ -7,7 +7,6 @@ import {
7
7
  SearchDialogContent,
8
8
  SearchDialogHeader,
9
9
  SearchDialogIcon,
10
- SearchDialogInput,
11
10
  SearchDialogList,
12
11
  SearchDialogOverlay,
13
12
  } from 'fumadocs-ui/components/dialog/search';
@@ -44,14 +43,35 @@ export function SearchDialog(props: SharedProps) {
44
43
  const { search, setSearch, query } = useDocsSearch({ client });
45
44
  const results = query.data === 'empty' || query.data === undefined ? [] : query.data;
46
45
 
46
+ /**
47
+ * A long index is parsed on the first query, so the wait is seconds rather than
48
+ * milliseconds — long enough that the list's "No results found" reads as an answer.
49
+ *
50
+ * `query.isLoading` alone is not the whole wait: fumadocs debounces the input before it
51
+ * flips, so a keystroke's worth of that wrong answer shows first. A query is pending from
52
+ * the keystroke until the search settles.
53
+ */
54
+ const [pending, setPending] = useState(false);
55
+ useEffect(() => {
56
+ setPending(search !== '');
57
+ }, [search]);
58
+ useEffect(() => {
59
+ if (!query.isLoading) setPending(false);
60
+ }, [query.isLoading]);
61
+
47
62
  // `search.suggest`: complete the last word inline from the best result's title.
48
63
  const completion = useMemo(() => {
49
64
  if (!feature('search.suggest') || search === '' || results.length === 0) return '';
50
- const title = results[0]?.content ?? '';
65
+ // A result's content arrives with the matched span wrapped in `<mark>` — and the match
66
+ // is the query itself, so leaving the markup in place is a prefix test that can never
67
+ // pass.
68
+ const title = (results[0]?.content ?? '').replaceAll('<mark>', '').replaceAll('</mark>', '');
51
69
  return title.toLowerCase().startsWith(search.toLowerCase()) ? title.slice(search.length) : '';
52
70
  }, [results, search]);
53
71
 
54
- const items = results.map((result) => ({ ...result, external: false }));
72
+ // An empty box has nothing to answer: `null` collapses the list, where an empty array
73
+ // would answer "No results found".
74
+ const items = search === '' ? null : results.map((result) => ({ ...result, external: false }));
55
75
 
56
76
  return (
57
77
  <Dialog
@@ -71,7 +91,15 @@ export function SearchDialog(props: SharedProps) {
71
91
  <SearchDialogContent>
72
92
  <SearchDialogHeader>
73
93
  <SearchDialogIcon />
74
- <SearchDialogInput
94
+ {/*
95
+ Our own input rather than fumadocs' `SearchDialogInput`: that one overwrites
96
+ `placeholder` with its own translated "Search" after spreading props, so the
97
+ wording here would never reach the box. The state is already ours.
98
+ */}
99
+ <input
100
+ className="seemore-search-input"
101
+ value={search}
102
+ onChange={(event) => setSearch(event.target.value)}
75
103
  placeholder="Search documentation…"
76
104
  onKeyDown={(event) => {
77
105
  if (event.key !== 'ArrowRight' || completion === '') return;
@@ -87,13 +115,20 @@ export function SearchDialog(props: SharedProps) {
87
115
  <span>{completion}</span>
88
116
  </p>
89
117
  )}
90
- {query.error === undefined ? (
91
- <SearchDialogList items={items} />
92
- ) : (
118
+ {query.error !== undefined ? (
93
119
  // A search box that silently finds nothing is worse than one that says why.
94
120
  <p className="seemore-search-error" role="alert">
95
121
  {query.error.message}
96
122
  </p>
123
+ ) : pending && (items?.length ?? 0) === 0 ? (
124
+ // Only with nothing to show: results already on screen stay put while the next
125
+ // query runs, under the input icon's own pulse.
126
+ <div className="seemore-search-loading" role="status">
127
+ <span className="seemore-search-spinner" aria-hidden="true" />
128
+ Searching…
129
+ </div>
130
+ ) : (
131
+ <SearchDialogList items={items} />
97
132
  )}
98
133
  </SearchDialogContent>
99
134
  </Dialog>
@@ -68,6 +68,18 @@
68
68
  @apply opacity-60;
69
69
  }
70
70
 
71
+ .seemore-search-input {
72
+ @apply w-0 flex-1 bg-transparent text-lg placeholder:text-fd-muted-foreground focus-visible:outline-none;
73
+ }
74
+
75
+ .seemore-search-loading {
76
+ @apply flex items-center justify-center gap-2 px-4 py-6 text-sm text-fd-muted-foreground;
77
+ }
78
+
79
+ .seemore-search-spinner {
80
+ @apply h-4 w-4 animate-spin rounded-full border-2 border-fd-muted-foreground/30 border-t-fd-muted-foreground;
81
+ }
82
+
71
83
  .seemore-theme-toggle {
72
84
  @apply rounded-lg border border-fd-border p-2;
73
85
  }