@tuwaio/docs-ui 0.3.2 → 0.4.0
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 +26 -2
- package/bin/search-index.js +11 -0
- package/dist/components/Footer.cjs +4 -4
- package/dist/components/Footer.js +1 -1
- package/dist/components/Logo.d.cts +2 -1
- package/dist/components/Logo.d.ts +2 -1
- package/dist/components/NavBar.cjs +4 -4
- package/dist/components/NavBar.d.cts +2 -1
- package/dist/components/NavBar.d.ts +2 -1
- package/dist/components/NavBar.js +1 -1
- package/dist/components/Orb.cjs +2 -2
- package/dist/components/Orb.d.cts +2 -1
- package/dist/components/Orb.d.ts +2 -1
- package/dist/components/Orb.js +1 -1
- package/dist/components/PolicyDocument.cjs +8 -8
- package/dist/components/PolicyDocument.d.cts +2 -1
- package/dist/components/PolicyDocument.d.ts +2 -1
- package/dist/components/PolicyDocument.js +1 -1
- package/dist/components/ThemeSwitcher.cjs +5 -5
- package/dist/components/ThemeSwitcher.d.cts +2 -1
- package/dist/components/ThemeSwitcher.d.ts +2 -1
- package/dist/components/ThemeSwitcher.js +1 -1
- package/dist/index.cjs +2 -0
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/policies.d.cts +2 -1
- package/dist/policies.d.ts +2 -1
- package/dist/preferences.cjs +55 -0
- package/dist/preferences.d.cts +28 -0
- package/dist/preferences.d.ts +28 -0
- package/dist/preferences.js +29 -0
- package/dist/searchIndex.cjs +77 -0
- package/dist/searchIndex.d.cts +40 -0
- package/dist/searchIndex.d.ts +40 -0
- package/dist/searchIndex.js +52 -0
- package/package.json +13 -9
package/README.md
CHANGED
|
@@ -12,8 +12,10 @@
|
|
|
12
12
|
- **Nextra layout parts:** `Navbar` and `Footer` wrap the navbar and footer of `nextra-theme-docs` with the site logo and a row of external links (`baseNavLinks`, `baseFooterLinks` are the defaults: the npm and GitHub organizations of TUWA). The footer also links the policies in force on tuwa.io and the list of all policies on the docs hub.
|
|
13
13
|
- **Logo:** `RemoteLogo` is an async React Server Component that fetches an SVG (by default the TUWA logo) and renders it as React elements, so it takes `className` and `currentColor`.
|
|
14
14
|
- **Theme switch:** `ThemeSwitcher` is a light/dark switch for any theme state (for example `next-themes`).
|
|
15
|
+
- **Interface settings for 30 days:** `PreferenceExpiry` is an inline script for the `<head>` of a site's root layout. It forgets the Local Storage values of the given keys (by default `theme`) 30 days after they were written, as the [Cookie Policy](https://tuwa.io/cookie) states, and dates each later write, whatever library writes it (`next-themes`, the `Tabs` of Nextra).
|
|
15
16
|
- **Decoration:** `Orb` (an animated sphere in the brand color of a TUWA project), `StarryBackground` (an animated star field on a canvas) and `NoSSR` (renders its children only on the client).
|
|
16
17
|
- **Policy pages:** `policies` lists the TUWA policies (Terms, Privacy, Cookie, Refund, Security, Data Processing Addendum) with their paths and whether they are in force (`publishedPolicies`; footers use `footerPolicies`). `loadPolicy` loads the text of a policy, and `PolicyDocument` renders it with the date of the version, a Download PDF button, anchored sections and a list of sections. The texts and PDFs come from [`TuwaIO/workflows/docs`](https://github.com/TuwaIO/workflows/tree/main/docs). Code that needs only the list or `loadPolicy` (footers, sitemaps, route handlers) imports them from `@tuwaio/docs-ui/policies`, which has no React, Next.js or Nextra imports.
|
|
18
|
+
- **Search index:** the `tuwa-docs-search-index` command builds the [Pagefind](https://pagefind.app) index that the Nextra search loads from `/_pagefind`, from the pages `next build` prerendered. It reads both places Next.js keeps them: `.next/server/app` and, in builds with a deployment adapter (Next.js 16.3.8 and later on Vercel), `.next/server/route-cache`, where `pagefind --site .next/server/app` finds no pages.
|
|
17
19
|
- **Stylesheet:** `dist/index.css` contains the `--tuwa-*` theme variables of [`@tuwaio/nova-core`](https://stories.tuwa.io/?path=/docs/packages-nova-core-overview--docs) (light and `.dark`), the styles of the Nextra parts and the Tailwind CSS v4 utilities used by the components, prefixed with `tuwadocs:`.
|
|
18
20
|
|
|
19
21
|
---
|
|
@@ -41,7 +43,8 @@ Import the stylesheet after the Nextra theme:
|
|
|
41
43
|
The root layout of a TUWA documentation site:
|
|
42
44
|
|
|
43
45
|
```tsx
|
|
44
|
-
import { baseNavLinks, Footer, Navbar, RemoteLogo } from '@tuwaio/docs-ui';
|
|
46
|
+
import { baseNavLinks, Footer, Navbar, PreferenceExpiry, RemoteLogo } from '@tuwaio/docs-ui';
|
|
47
|
+
import { Head } from 'nextra/components';
|
|
45
48
|
import { getPageMap } from 'nextra/page-map';
|
|
46
49
|
import { Layout } from 'nextra-theme-docs';
|
|
47
50
|
import type { ReactNode } from 'react';
|
|
@@ -60,6 +63,9 @@ const navLinks = [
|
|
|
60
63
|
export default async function RootLayout({ children }: { children: ReactNode }) {
|
|
61
64
|
return (
|
|
62
65
|
<html lang="en" suppressHydrationWarning>
|
|
66
|
+
<Head>
|
|
67
|
+
<PreferenceExpiry keys={['theme']} />
|
|
68
|
+
</Head>
|
|
63
69
|
<body>
|
|
64
70
|
<Layout
|
|
65
71
|
navbar={<Navbar logo={logo} links={navLinks} />}
|
|
@@ -115,11 +121,29 @@ export function PolicyLinks() {
|
|
|
115
121
|
|
|
116
122
|
Sections have stable anchors (`/refund#how-to-request-a-refund`), so hints and FAQ answers can link to a section.
|
|
117
123
|
|
|
124
|
+
### Search index
|
|
125
|
+
|
|
126
|
+
Run the command after `next build`. It uses the `pagefind` package of the site, so add it to the site's dependencies:
|
|
127
|
+
|
|
128
|
+
```json
|
|
129
|
+
{
|
|
130
|
+
"scripts": {
|
|
131
|
+
"build": "next build && pnpm postbuild",
|
|
132
|
+
"postbuild": "tuwa-docs-search-index"
|
|
133
|
+
},
|
|
134
|
+
"dependencies": {
|
|
135
|
+
"pagefind": "^1.5.2"
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
It writes the index to `public/_pagefind` (keep that directory in `.gitignore`) with the same URLs as `pagefind --site .next/server/app`, and fails when the build has no prerendered pages.
|
|
141
|
+
|
|
118
142
|
---
|
|
119
143
|
|
|
120
144
|
## 🗄️ Browser Storage
|
|
121
145
|
|
|
122
|
-
|
|
146
|
+
`ThemeSwitcher` stores nothing; it only calls `onToggle`, and the site keeps the theme. `PreferenceExpiry` stores `<key>:expires` (the time in epoch milliseconds) next to each key it is given, removes both once that time has passed, and wraps `Storage.prototype.setItem` on the page to date later writes of those keys.
|
|
123
147
|
|
|
124
148
|
## 🌐 External Services
|
|
125
149
|
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// `tuwa-docs-search-index`: the postbuild step of the TUWA documentation sites, after `next build` (see README)
|
|
3
|
+
import { buildSearchIndex } from '../dist/searchIndex.js';
|
|
4
|
+
|
|
5
|
+
buildSearchIndex().then(
|
|
6
|
+
({ urls }) => console.log(`Pagefind: indexed ${urls.length} pages into public/_pagefind`),
|
|
7
|
+
(error) => {
|
|
8
|
+
console.error(error instanceof Error ? error.message : error);
|
|
9
|
+
process.exit(1);
|
|
10
|
+
},
|
|
11
|
+
);
|
|
@@ -22,21 +22,21 @@ __export(Footer_exports, {
|
|
|
22
22
|
});
|
|
23
23
|
module.exports = __toCommonJS(Footer_exports);
|
|
24
24
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
25
|
-
var
|
|
25
|
+
var import_utils = require("@tuwaio/nova-core/utils");
|
|
26
26
|
var import_nextra_theme_docs = require("nextra-theme-docs");
|
|
27
27
|
var import_policies = require("../policies");
|
|
28
|
-
var
|
|
28
|
+
var import_utils2 = require("../utils");
|
|
29
29
|
function Footer({ links, logo }) {
|
|
30
30
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_nextra_theme_docs.Footer, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "tuwadocs:flex tuwadocs:w-full tuwadocs:flex-col tuwadocs:items-center tuwadocs:sm:items-start tuwa-footer-border tuwadocs:pt-8", children: [
|
|
31
31
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "tuwadocs:mb-6 tuwadocs:flex tuwadocs:items-center tuwadocs:gap-4 tuwadocs:w-full tuwadocs:justify-between", children: [
|
|
32
32
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "tuwadocs:flex tuwadocs:items-center", children: logo }),
|
|
33
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "tuwadocs:flex tuwadocs:items-center tuwadocs:gap-4 tuwadocs:text-sm", children: (links ??
|
|
33
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "tuwadocs:flex tuwadocs:items-center tuwadocs:gap-4 tuwadocs:text-sm", children: (links ?? import_utils2.baseFooterLinks).map(({ title, href, image, className }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
34
34
|
"a",
|
|
35
35
|
{
|
|
36
36
|
href,
|
|
37
37
|
target: "_blank",
|
|
38
38
|
rel: "noopener noreferrer",
|
|
39
|
-
className: (0,
|
|
39
|
+
className: (0, import_utils.cn)("tuwa-footer-link tuwadocs:flex tuwadocs:items-center tuwadocs:gap-1", className),
|
|
40
40
|
children: [
|
|
41
41
|
image,
|
|
42
42
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "tuwadocs:hidden tuwadocs:sm:inline", children: title })
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { cn } from "@tuwaio/nova-core";
|
|
2
|
+
import { cn } from "@tuwaio/nova-core/utils";
|
|
3
3
|
import { Footer as NextraFooter } from "nextra-theme-docs";
|
|
4
4
|
import { footerPolicies, getPolicyPageUrl, POLICIES_INDEX_URL } from "../policies";
|
|
5
5
|
import { baseFooterLinks } from "../utils";
|
|
@@ -21,4 +21,5 @@ interface RemoteLogoProps extends React__default.SVGProps<SVGSVGElement> {
|
|
|
21
21
|
*/
|
|
22
22
|
declare function RemoteLogo({ url, className, style, ...props }: RemoteLogoProps): Promise<string | React__default.JSX.Element>;
|
|
23
23
|
|
|
24
|
-
export { RemoteLogo
|
|
24
|
+
export { RemoteLogo };
|
|
25
|
+
export type { RemoteLogoProps };
|
|
@@ -21,4 +21,5 @@ interface RemoteLogoProps extends React__default.SVGProps<SVGSVGElement> {
|
|
|
21
21
|
*/
|
|
22
22
|
declare function RemoteLogo({ url, className, style, ...props }: RemoteLogoProps): Promise<string | React__default.JSX.Element>;
|
|
23
23
|
|
|
24
|
-
export { RemoteLogo
|
|
24
|
+
export { RemoteLogo };
|
|
25
|
+
export type { RemoteLogoProps };
|
|
@@ -22,17 +22,17 @@ __export(NavBar_exports, {
|
|
|
22
22
|
});
|
|
23
23
|
module.exports = __toCommonJS(NavBar_exports);
|
|
24
24
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
25
|
-
var
|
|
25
|
+
var import_utils = require("@tuwaio/nova-core/utils");
|
|
26
26
|
var import_nextra_theme_docs = require("nextra-theme-docs");
|
|
27
|
-
var
|
|
27
|
+
var import_utils2 = require("../utils");
|
|
28
28
|
function Navbar({ links, logo }) {
|
|
29
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_nextra_theme_docs.Navbar, { logo, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "tuwadocs:flex tuwadocs:items-center tuwadocs:gap-3", children: (links ??
|
|
29
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_nextra_theme_docs.Navbar, { logo, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "tuwadocs:flex tuwadocs:items-center tuwadocs:gap-3", children: (links ?? import_utils2.baseNavLinks).map(({ title, href, image, className }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
30
30
|
"a",
|
|
31
31
|
{
|
|
32
32
|
href,
|
|
33
33
|
target: "_blank",
|
|
34
34
|
rel: "noopener noreferrer",
|
|
35
|
-
className: (0,
|
|
35
|
+
className: (0, import_utils.cn)(
|
|
36
36
|
"tuwadocs:hidden tuwadocs:sm:flex tuwadocs:items-center tuwadocs:gap-1 tuwadocs:px-3 tuwadocs:py-1.5 tuwadocs:text-sm tuwadocs:font-medium tuwadocs:transition-colors tuwadocs:text-[var(--tuwa-text-primary)]",
|
|
37
37
|
className
|
|
38
38
|
),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { cn } from "@tuwaio/nova-core";
|
|
2
|
+
import { cn } from "@tuwaio/nova-core/utils";
|
|
3
3
|
import { Navbar as NextraNavbar } from "nextra-theme-docs";
|
|
4
4
|
import { baseNavLinks } from "../utils";
|
|
5
5
|
function Navbar({ links, logo }) {
|
package/dist/components/Orb.cjs
CHANGED
|
@@ -33,7 +33,7 @@ __export(Orb_exports, {
|
|
|
33
33
|
});
|
|
34
34
|
module.exports = __toCommonJS(Orb_exports);
|
|
35
35
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
36
|
-
var
|
|
36
|
+
var import_utils = require("@tuwaio/nova-core/utils");
|
|
37
37
|
var import_react = __toESM(require("react"), 1);
|
|
38
38
|
const packageColors = {
|
|
39
39
|
siwx: "#10b981",
|
|
@@ -294,7 +294,7 @@ const Orb = import_react.default.forwardRef(
|
|
|
294
294
|
"div",
|
|
295
295
|
{
|
|
296
296
|
ref,
|
|
297
|
-
className: (0,
|
|
297
|
+
className: (0, import_utils.cn)(
|
|
298
298
|
"tuwadocs:relative tuwadocs:flex tuwadocs:items-center tuwadocs:justify-center tuwa-orb-float",
|
|
299
299
|
className
|
|
300
300
|
),
|
|
@@ -26,4 +26,5 @@ interface OrbProps extends React__default.HTMLAttributes<HTMLDivElement> {
|
|
|
26
26
|
*/
|
|
27
27
|
declare const Orb: React__default.ForwardRefExoticComponent<OrbProps & React__default.RefAttributes<HTMLDivElement>>;
|
|
28
28
|
|
|
29
|
-
export { Orb
|
|
29
|
+
export { Orb };
|
|
30
|
+
export type { OrbProps, PackageType };
|
package/dist/components/Orb.d.ts
CHANGED
|
@@ -26,4 +26,5 @@ interface OrbProps extends React__default.HTMLAttributes<HTMLDivElement> {
|
|
|
26
26
|
*/
|
|
27
27
|
declare const Orb: React__default.ForwardRefExoticComponent<OrbProps & React__default.RefAttributes<HTMLDivElement>>;
|
|
28
28
|
|
|
29
|
-
export { Orb
|
|
29
|
+
export { Orb };
|
|
30
|
+
export type { OrbProps, PackageType };
|
package/dist/components/Orb.js
CHANGED
|
@@ -33,7 +33,7 @@ __export(PolicyDocument_exports, {
|
|
|
33
33
|
module.exports = __toCommonJS(PolicyDocument_exports);
|
|
34
34
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
35
35
|
var import_outline = require("@heroicons/react/24/outline");
|
|
36
|
-
var
|
|
36
|
+
var import_utils = require("@tuwaio/nova-core/utils");
|
|
37
37
|
var import_html_react_parser = __toESM(require("html-react-parser"), 1);
|
|
38
38
|
var import_policies = require("../policies");
|
|
39
39
|
const BLOCKED_ELEMENTS = /* @__PURE__ */ new Set([
|
|
@@ -80,7 +80,7 @@ function PolicyDocument({
|
|
|
80
80
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
81
81
|
"div",
|
|
82
82
|
{
|
|
83
|
-
className: (0,
|
|
83
|
+
className: (0, import_utils.cn)(
|
|
84
84
|
"tuwadocs:grid tuwadocs:grid-cols-1 tuwadocs:gap-10 tuwadocs:lg:grid-cols-[minmax(0,1fr)_14rem]",
|
|
85
85
|
className
|
|
86
86
|
),
|
|
@@ -89,7 +89,7 @@ function PolicyDocument({
|
|
|
89
89
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
90
90
|
"header",
|
|
91
91
|
{
|
|
92
|
-
className: (0,
|
|
92
|
+
className: (0, import_utils.cn)(
|
|
93
93
|
"tuwadocs:mb-8 tuwadocs:flex tuwadocs:flex-col tuwadocs:gap-4 tuwadocs:border-b tuwadocs:border-[var(--tuwa-border-primary)] tuwadocs:pb-6",
|
|
94
94
|
classNames?.header
|
|
95
95
|
),
|
|
@@ -97,7 +97,7 @@ function PolicyDocument({
|
|
|
97
97
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
98
98
|
"h1",
|
|
99
99
|
{
|
|
100
|
-
className: (0,
|
|
100
|
+
className: (0, import_utils.cn)(
|
|
101
101
|
"tuwadocs-policy-title tuwadocs:text-3xl tuwadocs:font-extrabold tuwadocs:tracking-tight tuwadocs:text-[var(--tuwa-text-primary)] tuwadocs:sm:text-4xl",
|
|
102
102
|
classNames?.title
|
|
103
103
|
),
|
|
@@ -105,7 +105,7 @@ function PolicyDocument({
|
|
|
105
105
|
}
|
|
106
106
|
),
|
|
107
107
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "tuwadocs:flex tuwadocs:flex-wrap tuwadocs:items-center tuwadocs:justify-between tuwadocs:gap-3", children: [
|
|
108
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { className: (0,
|
|
108
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { className: (0, import_utils.cn)("tuwadocs:text-sm tuwadocs:text-[var(--tuwa-text-secondary)]", classNames?.updated), children: [
|
|
109
109
|
labels?.updated ?? "Last updated",
|
|
110
110
|
" ",
|
|
111
111
|
content.updated
|
|
@@ -116,7 +116,7 @@ function PolicyDocument({
|
|
|
116
116
|
href: (0, import_policies.getPolicyFileUrl)(slug, "pdf", source),
|
|
117
117
|
target: "_blank",
|
|
118
118
|
rel: "noopener noreferrer",
|
|
119
|
-
className: (0,
|
|
119
|
+
className: (0, import_utils.cn)(
|
|
120
120
|
"tuwadocs-policy-title tuwadocs:inline-flex tuwadocs:h-10 tuwadocs:no-underline tuwadocs:items-center tuwadocs:gap-2 tuwadocs:rounded-[var(--tuwa-rounded-corners)] tuwadocs:border tuwadocs:border-[var(--tuwa-border-primary)] tuwadocs:bg-[var(--tuwa-bg-primary)] tuwadocs:px-4 tuwadocs:text-sm tuwadocs:font-bold tuwadocs:text-[var(--tuwa-text-primary)] tuwadocs:transition-colors tuwadocs:hover:bg-[var(--tuwa-bg-muted)]",
|
|
121
121
|
classNames?.download
|
|
122
122
|
),
|
|
@@ -130,13 +130,13 @@ function PolicyDocument({
|
|
|
130
130
|
]
|
|
131
131
|
}
|
|
132
132
|
),
|
|
133
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("article", { className: (0,
|
|
133
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("article", { className: (0, import_utils.cn)("tuwadocs-policy", classNames?.body), children: (0, import_html_react_parser.default)(content.html, { replace: sanitize }) })
|
|
134
134
|
] }),
|
|
135
135
|
content.sections.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
136
136
|
"nav",
|
|
137
137
|
{
|
|
138
138
|
"aria-label": tocLabel,
|
|
139
|
-
className: (0,
|
|
139
|
+
className: (0, import_utils.cn)(
|
|
140
140
|
"tuwadocs:hidden tuwadocs:lg:sticky tuwadocs:lg:top-24 tuwadocs:lg:block tuwadocs:lg:self-start",
|
|
141
141
|
classNames?.toc
|
|
142
142
|
),
|
|
@@ -54,4 +54,5 @@ interface PolicyDocumentProps {
|
|
|
54
54
|
*/
|
|
55
55
|
declare function PolicyDocument({ slug, content, source, labels, className, classNames, }: PolicyDocumentProps): React.JSX.Element;
|
|
56
56
|
|
|
57
|
-
export { PolicyDocument
|
|
57
|
+
export { PolicyDocument };
|
|
58
|
+
export type { PolicyDocumentProps };
|
|
@@ -54,4 +54,5 @@ interface PolicyDocumentProps {
|
|
|
54
54
|
*/
|
|
55
55
|
declare function PolicyDocument({ slug, content, source, labels, className, classNames, }: PolicyDocumentProps): React.JSX.Element;
|
|
56
56
|
|
|
57
|
-
export { PolicyDocument
|
|
57
|
+
export { PolicyDocument };
|
|
58
|
+
export type { PolicyDocumentProps };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { ArrowDownTrayIcon } from "@heroicons/react/24/outline";
|
|
3
|
-
import { cn } from "@tuwaio/nova-core";
|
|
3
|
+
import { cn } from "@tuwaio/nova-core/utils";
|
|
4
4
|
import parse, { Element } from "html-react-parser";
|
|
5
5
|
import { getPolicyFileUrl, POLICY_SOURCE } from "../policies";
|
|
6
6
|
const BLOCKED_ELEMENTS = /* @__PURE__ */ new Set([
|
|
@@ -24,7 +24,7 @@ __export(ThemeSwitcher_exports, {
|
|
|
24
24
|
module.exports = __toCommonJS(ThemeSwitcher_exports);
|
|
25
25
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
26
26
|
var import_solid = require("@heroicons/react/24/solid");
|
|
27
|
-
var
|
|
27
|
+
var import_utils = require("@tuwaio/nova-core/utils");
|
|
28
28
|
var import_react = require("react");
|
|
29
29
|
function ThemeSwitcher({ theme, onToggle }) {
|
|
30
30
|
const [mounted, setMounted] = (0, import_react.useState)(false);
|
|
@@ -36,7 +36,7 @@ function ThemeSwitcher({ theme, onToggle }) {
|
|
|
36
36
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
37
37
|
"div",
|
|
38
38
|
{
|
|
39
|
-
className: (0,
|
|
39
|
+
className: (0, import_utils.cn)(
|
|
40
40
|
"tuwadocs:relative tuwadocs:flex tuwadocs:h-10 tuwadocs:w-20 tuwadocs:md:h-8 tuwadocs:md:w-16 tuwadocs:rounded-[var(--tuwa-rounded-corners)] tuwadocs:border tuwadocs:opacity-0",
|
|
41
41
|
defaultStyles
|
|
42
42
|
)
|
|
@@ -48,7 +48,7 @@ function ThemeSwitcher({ theme, onToggle }) {
|
|
|
48
48
|
"button",
|
|
49
49
|
{
|
|
50
50
|
onClick: onToggle,
|
|
51
|
-
className: (0,
|
|
51
|
+
className: (0, import_utils.cn)(
|
|
52
52
|
"tuwadocs:group tuwadocs:relative tuwadocs:flex tuwadocs:h-10 tuwadocs:w-20 tuwadocs:md:h-8 tuwadocs:md:w-16 tuwadocs:cursor-pointer tuwadocs:items-center tuwadocs:rounded-[var(--tuwa-rounded-corners)] tuwadocs:p-1 tuwadocs:transition-all tuwadocs:duration-300 tuwadocs:ease-in-out tuwadocs:backdrop-blur-sm tuwadocs:border",
|
|
53
53
|
defaultStyles
|
|
54
54
|
),
|
|
@@ -57,7 +57,7 @@ function ThemeSwitcher({ theme, onToggle }) {
|
|
|
57
57
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
58
58
|
"div",
|
|
59
59
|
{
|
|
60
|
-
className: (0,
|
|
60
|
+
className: (0, import_utils.cn)(
|
|
61
61
|
"tuwadocs:absolute tuwadocs:top-[1px] tuwadocs:left-[2px] tuwadocs:z-10 tuwadocs:h-9 tuwadocs:w-9 tuwadocs:rounded-[var(--tuwa-rounded-corners)] tuwadocs:bg-[var(--tuwa-bg-primary)] tuwadocs:shadow-md tuwadocs:md:hidden",
|
|
62
62
|
isDark ? "tuwa-slide-dark" : "tuwa-slide-light"
|
|
63
63
|
)
|
|
@@ -66,7 +66,7 @@ function ThemeSwitcher({ theme, onToggle }) {
|
|
|
66
66
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
67
67
|
"div",
|
|
68
68
|
{
|
|
69
|
-
className: (0,
|
|
69
|
+
className: (0, import_utils.cn)(
|
|
70
70
|
"tuwadocs:absolute tuwadocs:top-[1px] tuwadocs:left-[2px] tuwadocs:z-10 tuwadocs:h-9 tuwadocs:w-9 tuwadocs:md:h-7 tuwadocs:md:w-7 tuwadocs:rounded-[var(--tuwa-rounded-corners)] tuwadocs:bg-[var(--tuwa-bg-primary)] tuwadocs:shadow-md tuwadocs:hidden tuwadocs:md:block",
|
|
71
71
|
isDark ? "tuwa-slide-dark" : "tuwa-slide-light"
|
|
72
72
|
)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { MoonIcon, SunIcon } from "@heroicons/react/24/solid";
|
|
4
|
-
import { cn } from "@tuwaio/nova-core";
|
|
4
|
+
import { cn } from "@tuwaio/nova-core/utils";
|
|
5
5
|
import { useEffect, useState } from "react";
|
|
6
6
|
function ThemeSwitcher({ theme, onToggle }) {
|
|
7
7
|
const [mounted, setMounted] = useState(false);
|
package/dist/index.cjs
CHANGED
|
@@ -42,6 +42,7 @@ __reExport(index_exports, require("./components/PolicyDocument"), module.exports
|
|
|
42
42
|
var import_StarryBackground = __toESM(require("./components/StarryBackground"), 1);
|
|
43
43
|
__reExport(index_exports, require("./components/ThemeSwitcher"), module.exports);
|
|
44
44
|
__reExport(index_exports, require("./policies"), module.exports);
|
|
45
|
+
__reExport(index_exports, require("./preferences"), module.exports);
|
|
45
46
|
__reExport(index_exports, require("./utils"), module.exports);
|
|
46
47
|
// Annotate the CommonJS export names for ESM import in node:
|
|
47
48
|
0 && (module.exports = {
|
|
@@ -54,5 +55,6 @@ __reExport(index_exports, require("./utils"), module.exports);
|
|
|
54
55
|
...require("./components/PolicyDocument"),
|
|
55
56
|
...require("./components/ThemeSwitcher"),
|
|
56
57
|
...require("./policies"),
|
|
58
|
+
...require("./preferences"),
|
|
57
59
|
...require("./utils")
|
|
58
60
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -7,5 +7,6 @@ export { PolicyDocument, PolicyDocumentProps } from './components/PolicyDocument
|
|
|
7
7
|
export { default as StarryBackground } from './components/StarryBackground.cjs';
|
|
8
8
|
export { ThemeSwitcher, ThemeSwitcherProps } from './components/ThemeSwitcher.cjs';
|
|
9
9
|
export { POLICIES_INDEX_URL, POLICY_SITE_URL, POLICY_SOURCE, Policy, PolicyContent, PolicySection, PolicySlug, footerPolicies, getPolicyFileUrl, getPolicyPageUrl, loadPolicy, policies, publishedPolicies } from './policies.cjs';
|
|
10
|
+
export { PREFERENCE_DAYS, PreferenceExpiry, expireStoredPreferences } from './preferences.cjs';
|
|
10
11
|
export { baseFooterLinks, baseNavLinks } from './utils.cjs';
|
|
11
12
|
import 'react';
|
package/dist/index.d.ts
CHANGED
|
@@ -7,5 +7,6 @@ export { PolicyDocument, PolicyDocumentProps } from './components/PolicyDocument
|
|
|
7
7
|
export { default as StarryBackground } from './components/StarryBackground.js';
|
|
8
8
|
export { ThemeSwitcher, ThemeSwitcherProps } from './components/ThemeSwitcher.js';
|
|
9
9
|
export { POLICIES_INDEX_URL, POLICY_SITE_URL, POLICY_SOURCE, Policy, PolicyContent, PolicySection, PolicySlug, footerPolicies, getPolicyFileUrl, getPolicyPageUrl, loadPolicy, policies, publishedPolicies } from './policies.js';
|
|
10
|
+
export { PREFERENCE_DAYS, PreferenceExpiry, expireStoredPreferences } from './preferences.js';
|
|
10
11
|
export { baseFooterLinks, baseNavLinks } from './utils.js';
|
|
11
12
|
import 'react';
|
package/dist/index.js
CHANGED
|
@@ -7,6 +7,7 @@ export * from "./components/PolicyDocument";
|
|
|
7
7
|
import { default as default3 } from "./components/StarryBackground";
|
|
8
8
|
export * from "./components/ThemeSwitcher";
|
|
9
9
|
export * from "./policies";
|
|
10
|
+
export * from "./preferences";
|
|
10
11
|
export * from "./utils";
|
|
11
12
|
export {
|
|
12
13
|
default2 as NoSSR,
|
package/dist/policies.d.cts
CHANGED
|
@@ -85,4 +85,5 @@ declare function loadPolicy(slug: PolicySlug, options?: {
|
|
|
85
85
|
revalidate?: number | false;
|
|
86
86
|
}): Promise<PolicyContent>;
|
|
87
87
|
|
|
88
|
-
export { POLICIES_INDEX_URL, POLICY_SITE_URL, POLICY_SOURCE,
|
|
88
|
+
export { POLICIES_INDEX_URL, POLICY_SITE_URL, POLICY_SOURCE, footerPolicies, getPolicyFileUrl, getPolicyPageUrl, loadPolicy, policies, publishedPolicies };
|
|
89
|
+
export type { Policy, PolicyContent, PolicySection, PolicySlug };
|
package/dist/policies.d.ts
CHANGED
|
@@ -85,4 +85,5 @@ declare function loadPolicy(slug: PolicySlug, options?: {
|
|
|
85
85
|
revalidate?: number | false;
|
|
86
86
|
}): Promise<PolicyContent>;
|
|
87
87
|
|
|
88
|
-
export { POLICIES_INDEX_URL, POLICY_SITE_URL, POLICY_SOURCE,
|
|
88
|
+
export { POLICIES_INDEX_URL, POLICY_SITE_URL, POLICY_SOURCE, footerPolicies, getPolicyFileUrl, getPolicyPageUrl, loadPolicy, policies, publishedPolicies };
|
|
89
|
+
export type { Policy, PolicyContent, PolicySection, PolicySlug };
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var preferences_exports = {};
|
|
20
|
+
__export(preferences_exports, {
|
|
21
|
+
PREFERENCE_DAYS: () => PREFERENCE_DAYS,
|
|
22
|
+
PreferenceExpiry: () => PreferenceExpiry,
|
|
23
|
+
expireStoredPreferences: () => expireStoredPreferences
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(preferences_exports);
|
|
26
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
27
|
+
const PREFERENCE_DAYS = 30;
|
|
28
|
+
function expireStoredPreferences(storage, proto, keys, ms, now) {
|
|
29
|
+
try {
|
|
30
|
+
for (const key of keys) {
|
|
31
|
+
const expiresAt = Number(storage.getItem(key + ":expires"));
|
|
32
|
+
if (!expiresAt || expiresAt <= now) {
|
|
33
|
+
storage.removeItem(key);
|
|
34
|
+
storage.removeItem(key + ":expires");
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const setItem = proto.setItem;
|
|
38
|
+
proto.setItem = function(key, value) {
|
|
39
|
+
setItem.call(this, key, value);
|
|
40
|
+
if (this === storage && keys.indexOf(key) >= 0) setItem.call(this, key + ":expires", String(Date.now() + ms));
|
|
41
|
+
};
|
|
42
|
+
} catch {
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function PreferenceExpiry({ keys = ["theme"] }) {
|
|
46
|
+
const ms = PREFERENCE_DAYS * 864e5;
|
|
47
|
+
const code = `(${expireStoredPreferences.toString()})(window.localStorage, Storage.prototype, ${JSON.stringify(keys)}, ${ms}, Date.now())`;
|
|
48
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("script", { dangerouslySetInnerHTML: { __html: code } });
|
|
49
|
+
}
|
|
50
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
51
|
+
0 && (module.exports = {
|
|
52
|
+
PREFERENCE_DAYS,
|
|
53
|
+
PreferenceExpiry,
|
|
54
|
+
expireStoredPreferences
|
|
55
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
/** How long the TUWA sites keep an interface setting after it is chosen (Cookie Policy). */
|
|
4
|
+
declare const PREFERENCE_DAYS = 30;
|
|
5
|
+
/**
|
|
6
|
+
* Forgets the given Local Storage values once their 30 days have passed (or when they were saved before values had an
|
|
7
|
+
* expiry), then dates every later write of those keys: `<key>:expires` holds the time in epoch milliseconds. It runs
|
|
8
|
+
* inline in the page head, before next-themes or Nextra read the values, so it works whatever library writes them;
|
|
9
|
+
* it is serialised with `toString()`, so it uses only its arguments.
|
|
10
|
+
*
|
|
11
|
+
* @param storage - `window.localStorage`.
|
|
12
|
+
* @param proto - `Storage.prototype`, whose `setItem` is wrapped to date the writes of `keys`.
|
|
13
|
+
* @param keys - The keys of the interface settings, such as `theme`.
|
|
14
|
+
* @param ms - How long a value is kept after it is written.
|
|
15
|
+
* @param now - The current time.
|
|
16
|
+
*/
|
|
17
|
+
declare function expireStoredPreferences(storage: Storage, proto: Pick<Storage, 'setItem'>, keys: string[], ms: number, now: number): void;
|
|
18
|
+
/**
|
|
19
|
+
* The inline script of {@link expireStoredPreferences}, for the `<head>` of a site's root layout, before the theme
|
|
20
|
+
* script of next-themes.
|
|
21
|
+
*
|
|
22
|
+
* @param props.keys - The Local Storage keys to keep for {@link PREFERENCE_DAYS} days; `theme` by default.
|
|
23
|
+
*/
|
|
24
|
+
declare function PreferenceExpiry({ keys }: {
|
|
25
|
+
keys?: string[];
|
|
26
|
+
}): React.JSX.Element;
|
|
27
|
+
|
|
28
|
+
export { PREFERENCE_DAYS, PreferenceExpiry, expireStoredPreferences };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
/** How long the TUWA sites keep an interface setting after it is chosen (Cookie Policy). */
|
|
4
|
+
declare const PREFERENCE_DAYS = 30;
|
|
5
|
+
/**
|
|
6
|
+
* Forgets the given Local Storage values once their 30 days have passed (or when they were saved before values had an
|
|
7
|
+
* expiry), then dates every later write of those keys: `<key>:expires` holds the time in epoch milliseconds. It runs
|
|
8
|
+
* inline in the page head, before next-themes or Nextra read the values, so it works whatever library writes them;
|
|
9
|
+
* it is serialised with `toString()`, so it uses only its arguments.
|
|
10
|
+
*
|
|
11
|
+
* @param storage - `window.localStorage`.
|
|
12
|
+
* @param proto - `Storage.prototype`, whose `setItem` is wrapped to date the writes of `keys`.
|
|
13
|
+
* @param keys - The keys of the interface settings, such as `theme`.
|
|
14
|
+
* @param ms - How long a value is kept after it is written.
|
|
15
|
+
* @param now - The current time.
|
|
16
|
+
*/
|
|
17
|
+
declare function expireStoredPreferences(storage: Storage, proto: Pick<Storage, 'setItem'>, keys: string[], ms: number, now: number): void;
|
|
18
|
+
/**
|
|
19
|
+
* The inline script of {@link expireStoredPreferences}, for the `<head>` of a site's root layout, before the theme
|
|
20
|
+
* script of next-themes.
|
|
21
|
+
*
|
|
22
|
+
* @param props.keys - The Local Storage keys to keep for {@link PREFERENCE_DAYS} days; `theme` by default.
|
|
23
|
+
*/
|
|
24
|
+
declare function PreferenceExpiry({ keys }: {
|
|
25
|
+
keys?: string[];
|
|
26
|
+
}): React.JSX.Element;
|
|
27
|
+
|
|
28
|
+
export { PREFERENCE_DAYS, PreferenceExpiry, expireStoredPreferences };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
const PREFERENCE_DAYS = 30;
|
|
3
|
+
function expireStoredPreferences(storage, proto, keys, ms, now) {
|
|
4
|
+
try {
|
|
5
|
+
for (const key of keys) {
|
|
6
|
+
const expiresAt = Number(storage.getItem(key + ":expires"));
|
|
7
|
+
if (!expiresAt || expiresAt <= now) {
|
|
8
|
+
storage.removeItem(key);
|
|
9
|
+
storage.removeItem(key + ":expires");
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
const setItem = proto.setItem;
|
|
13
|
+
proto.setItem = function(key, value) {
|
|
14
|
+
setItem.call(this, key, value);
|
|
15
|
+
if (this === storage && keys.indexOf(key) >= 0) setItem.call(this, key + ":expires", String(Date.now() + ms));
|
|
16
|
+
};
|
|
17
|
+
} catch {
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function PreferenceExpiry({ keys = ["theme"] }) {
|
|
21
|
+
const ms = PREFERENCE_DAYS * 864e5;
|
|
22
|
+
const code = `(${expireStoredPreferences.toString()})(window.localStorage, Storage.prototype, ${JSON.stringify(keys)}, ${ms}, Date.now())`;
|
|
23
|
+
return /* @__PURE__ */ jsx("script", { dangerouslySetInnerHTML: { __html: code } });
|
|
24
|
+
}
|
|
25
|
+
export {
|
|
26
|
+
PREFERENCE_DAYS,
|
|
27
|
+
PreferenceExpiry,
|
|
28
|
+
expireStoredPreferences
|
|
29
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var searchIndex_exports = {};
|
|
20
|
+
__export(searchIndex_exports, {
|
|
21
|
+
buildSearchIndex: () => buildSearchIndex,
|
|
22
|
+
findPrerenderedPages: () => findPrerenderedPages
|
|
23
|
+
});
|
|
24
|
+
module.exports = __toCommonJS(searchIndex_exports);
|
|
25
|
+
var import_node_fs = require("node:fs");
|
|
26
|
+
var import_node_path = require("node:path");
|
|
27
|
+
var import_node_url = require("node:url");
|
|
28
|
+
const directories = (dir) => (0, import_node_fs.existsSync)(dir) ? (0, import_node_fs.readdirSync)(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => (0, import_node_path.join)(dir, entry.name)) : [];
|
|
29
|
+
const htmlFiles = (root) => (0, import_node_fs.existsSync)(root) ? (0, import_node_fs.readdirSync)(root, { recursive: true, encoding: "utf8" }).filter((path) => path.endsWith(".html")).map((path) => ({ file: (0, import_node_path.join)(root, path), sourcePath: path.split(import_node_path.sep).join("/") })) : [];
|
|
30
|
+
function findPrerenderedPages(serverDir) {
|
|
31
|
+
const roots = [
|
|
32
|
+
(0, import_node_path.join)(serverDir, "app"),
|
|
33
|
+
...directories((0, import_node_path.join)(serverDir, "route-cache")).flatMap(directories).map((route) => (0, import_node_path.join)(route, "$"))
|
|
34
|
+
];
|
|
35
|
+
return roots.flatMap(htmlFiles).sort((a, b) => a.sourcePath < b.sourcePath ? -1 : 1);
|
|
36
|
+
}
|
|
37
|
+
async function loadPagefind(cwd) {
|
|
38
|
+
for (let dir = (0, import_node_path.resolve)(cwd); ; dir = (0, import_node_path.dirname)(dir)) {
|
|
39
|
+
const manifest = (0, import_node_path.join)(dir, "node_modules", "pagefind", "package.json");
|
|
40
|
+
if ((0, import_node_fs.existsSync)(manifest)) {
|
|
41
|
+
const pkg = JSON.parse((0, import_node_fs.readFileSync)(manifest, "utf8"));
|
|
42
|
+
const entry = pkg.exports?.["."]?.import ?? pkg.main ?? "lib/index.js";
|
|
43
|
+
return await import((0, import_node_url.pathToFileURL)((0, import_node_path.join)((0, import_node_path.dirname)(manifest), entry)).href);
|
|
44
|
+
}
|
|
45
|
+
if ((0, import_node_path.dirname)(dir) === dir) throw new Error("pagefind is not installed: add it to the dependencies of the site");
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
async function buildSearchIndex({
|
|
49
|
+
serverDir = ".next/server",
|
|
50
|
+
outputPath = "public/_pagefind",
|
|
51
|
+
cwd = process.cwd()
|
|
52
|
+
} = {}) {
|
|
53
|
+
const pages = findPrerenderedPages((0, import_node_path.resolve)(cwd, serverDir));
|
|
54
|
+
if (!pages.length) throw new Error(`Found no prerendered pages in ${serverDir}: run next build first`);
|
|
55
|
+
const pagefind = await loadPagefind(cwd);
|
|
56
|
+
try {
|
|
57
|
+
const { index, errors } = await pagefind.createIndex();
|
|
58
|
+
if (!index) throw new Error(`Pagefind: ${errors.join("; ")}`);
|
|
59
|
+
const urls = [];
|
|
60
|
+
for (const page of pages) {
|
|
61
|
+
const added = await index.addHTMLFile({ sourcePath: page.sourcePath, content: (0, import_node_fs.readFileSync)(page.file, "utf8") });
|
|
62
|
+
if (added.errors.length || !added.file)
|
|
63
|
+
throw new Error(`Pagefind, ${page.sourcePath}: ${added.errors.join("; ")}`);
|
|
64
|
+
urls.push(added.file.url);
|
|
65
|
+
}
|
|
66
|
+
const written = await index.writeFiles({ outputPath: (0, import_node_path.resolve)(cwd, outputPath) });
|
|
67
|
+
if (written.errors.length) throw new Error(`Pagefind: ${written.errors.join("; ")}`);
|
|
68
|
+
return { urls };
|
|
69
|
+
} finally {
|
|
70
|
+
await pagefind.close();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
74
|
+
0 && (module.exports = {
|
|
75
|
+
buildSearchIndex,
|
|
76
|
+
findPrerenderedPages
|
|
77
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/** A page that `next build` prerendered: its HTML file and the path from which Pagefind derives its URL. */
|
|
2
|
+
type PrerenderedPage = {
|
|
3
|
+
/** The HTML file in `.next/server`. */
|
|
4
|
+
file: string;
|
|
5
|
+
/** The path of the page in the site, such as `packages/orbit-core.html` (URL `/packages/orbit-core.html`). */
|
|
6
|
+
sourcePath: string;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Lists the pages that `next build` prerendered as HTML. A build without an adapter keeps them in
|
|
10
|
+
* `<serverDir>/app/<path>.html`; a build with an adapter (Next 16.3.8 and later on Vercel) keeps them in
|
|
11
|
+
* `<serverDir>/route-cache/<kind>/<hash of the route>/$/<path>.html`. Both give the same paths.
|
|
12
|
+
*
|
|
13
|
+
* @param serverDir - The `.next/server` directory of the site.
|
|
14
|
+
* @returns The pages, sorted by path.
|
|
15
|
+
*/
|
|
16
|
+
declare function findPrerenderedPages(serverDir: string): PrerenderedPage[];
|
|
17
|
+
/**
|
|
18
|
+
* Builds the Pagefind index of a documentation site from the pages that `next build` prerendered, in either layout
|
|
19
|
+
* of {@link findPrerenderedPages}, with the URLs `pagefind --site .next/server/app` gave them. Nextra's search loads it
|
|
20
|
+
* from `/_pagefind/pagefind.js`.
|
|
21
|
+
*
|
|
22
|
+
* Side effects: starts the Pagefind binary of the site's `pagefind` package and writes the index to `outputPath`.
|
|
23
|
+
*
|
|
24
|
+
* @param options - Where to read and write, relative to `cwd`.
|
|
25
|
+
* @param options.serverDir - The `.next/server` directory. Default `.next/server`.
|
|
26
|
+
* @param options.outputPath - Where the index is written. Default `public/_pagefind`.
|
|
27
|
+
* @param options.cwd - The directory of the site, where `pagefind` is installed. Default the current directory.
|
|
28
|
+
* @returns The URLs of the indexed pages.
|
|
29
|
+
* @throws When the build has no prerendered pages, or when Pagefind is missing or reports an error.
|
|
30
|
+
*/
|
|
31
|
+
declare function buildSearchIndex({ serverDir, outputPath, cwd, }?: {
|
|
32
|
+
serverDir?: string;
|
|
33
|
+
outputPath?: string;
|
|
34
|
+
cwd?: string;
|
|
35
|
+
}): Promise<{
|
|
36
|
+
urls: string[];
|
|
37
|
+
}>;
|
|
38
|
+
|
|
39
|
+
export { buildSearchIndex, findPrerenderedPages };
|
|
40
|
+
export type { PrerenderedPage };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/** A page that `next build` prerendered: its HTML file and the path from which Pagefind derives its URL. */
|
|
2
|
+
type PrerenderedPage = {
|
|
3
|
+
/** The HTML file in `.next/server`. */
|
|
4
|
+
file: string;
|
|
5
|
+
/** The path of the page in the site, such as `packages/orbit-core.html` (URL `/packages/orbit-core.html`). */
|
|
6
|
+
sourcePath: string;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Lists the pages that `next build` prerendered as HTML. A build without an adapter keeps them in
|
|
10
|
+
* `<serverDir>/app/<path>.html`; a build with an adapter (Next 16.3.8 and later on Vercel) keeps them in
|
|
11
|
+
* `<serverDir>/route-cache/<kind>/<hash of the route>/$/<path>.html`. Both give the same paths.
|
|
12
|
+
*
|
|
13
|
+
* @param serverDir - The `.next/server` directory of the site.
|
|
14
|
+
* @returns The pages, sorted by path.
|
|
15
|
+
*/
|
|
16
|
+
declare function findPrerenderedPages(serverDir: string): PrerenderedPage[];
|
|
17
|
+
/**
|
|
18
|
+
* Builds the Pagefind index of a documentation site from the pages that `next build` prerendered, in either layout
|
|
19
|
+
* of {@link findPrerenderedPages}, with the URLs `pagefind --site .next/server/app` gave them. Nextra's search loads it
|
|
20
|
+
* from `/_pagefind/pagefind.js`.
|
|
21
|
+
*
|
|
22
|
+
* Side effects: starts the Pagefind binary of the site's `pagefind` package and writes the index to `outputPath`.
|
|
23
|
+
*
|
|
24
|
+
* @param options - Where to read and write, relative to `cwd`.
|
|
25
|
+
* @param options.serverDir - The `.next/server` directory. Default `.next/server`.
|
|
26
|
+
* @param options.outputPath - Where the index is written. Default `public/_pagefind`.
|
|
27
|
+
* @param options.cwd - The directory of the site, where `pagefind` is installed. Default the current directory.
|
|
28
|
+
* @returns The URLs of the indexed pages.
|
|
29
|
+
* @throws When the build has no prerendered pages, or when Pagefind is missing or reports an error.
|
|
30
|
+
*/
|
|
31
|
+
declare function buildSearchIndex({ serverDir, outputPath, cwd, }?: {
|
|
32
|
+
serverDir?: string;
|
|
33
|
+
outputPath?: string;
|
|
34
|
+
cwd?: string;
|
|
35
|
+
}): Promise<{
|
|
36
|
+
urls: string[];
|
|
37
|
+
}>;
|
|
38
|
+
|
|
39
|
+
export { buildSearchIndex, findPrerenderedPages };
|
|
40
|
+
export type { PrerenderedPage };
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join, resolve, sep } from "node:path";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
const directories = (dir) => existsSync(dir) ? readdirSync(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => join(dir, entry.name)) : [];
|
|
5
|
+
const htmlFiles = (root) => existsSync(root) ? readdirSync(root, { recursive: true, encoding: "utf8" }).filter((path) => path.endsWith(".html")).map((path) => ({ file: join(root, path), sourcePath: path.split(sep).join("/") })) : [];
|
|
6
|
+
function findPrerenderedPages(serverDir) {
|
|
7
|
+
const roots = [
|
|
8
|
+
join(serverDir, "app"),
|
|
9
|
+
...directories(join(serverDir, "route-cache")).flatMap(directories).map((route) => join(route, "$"))
|
|
10
|
+
];
|
|
11
|
+
return roots.flatMap(htmlFiles).sort((a, b) => a.sourcePath < b.sourcePath ? -1 : 1);
|
|
12
|
+
}
|
|
13
|
+
async function loadPagefind(cwd) {
|
|
14
|
+
for (let dir = resolve(cwd); ; dir = dirname(dir)) {
|
|
15
|
+
const manifest = join(dir, "node_modules", "pagefind", "package.json");
|
|
16
|
+
if (existsSync(manifest)) {
|
|
17
|
+
const pkg = JSON.parse(readFileSync(manifest, "utf8"));
|
|
18
|
+
const entry = pkg.exports?.["."]?.import ?? pkg.main ?? "lib/index.js";
|
|
19
|
+
return await import(pathToFileURL(join(dirname(manifest), entry)).href);
|
|
20
|
+
}
|
|
21
|
+
if (dirname(dir) === dir) throw new Error("pagefind is not installed: add it to the dependencies of the site");
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
async function buildSearchIndex({
|
|
25
|
+
serverDir = ".next/server",
|
|
26
|
+
outputPath = "public/_pagefind",
|
|
27
|
+
cwd = process.cwd()
|
|
28
|
+
} = {}) {
|
|
29
|
+
const pages = findPrerenderedPages(resolve(cwd, serverDir));
|
|
30
|
+
if (!pages.length) throw new Error(`Found no prerendered pages in ${serverDir}: run next build first`);
|
|
31
|
+
const pagefind = await loadPagefind(cwd);
|
|
32
|
+
try {
|
|
33
|
+
const { index, errors } = await pagefind.createIndex();
|
|
34
|
+
if (!index) throw new Error(`Pagefind: ${errors.join("; ")}`);
|
|
35
|
+
const urls = [];
|
|
36
|
+
for (const page of pages) {
|
|
37
|
+
const added = await index.addHTMLFile({ sourcePath: page.sourcePath, content: readFileSync(page.file, "utf8") });
|
|
38
|
+
if (added.errors.length || !added.file)
|
|
39
|
+
throw new Error(`Pagefind, ${page.sourcePath}: ${added.errors.join("; ")}`);
|
|
40
|
+
urls.push(added.file.url);
|
|
41
|
+
}
|
|
42
|
+
const written = await index.writeFiles({ outputPath: resolve(cwd, outputPath) });
|
|
43
|
+
if (written.errors.length) throw new Error(`Pagefind: ${written.errors.join("; ")}`);
|
|
44
|
+
return { urls };
|
|
45
|
+
} finally {
|
|
46
|
+
await pagefind.close();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
export {
|
|
50
|
+
buildSearchIndex,
|
|
51
|
+
findPrerenderedPages
|
|
52
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tuwaio/docs-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"author": "Oleksandr Tkach",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -8,12 +8,15 @@
|
|
|
8
8
|
"main": "./dist/index.cjs",
|
|
9
9
|
"module": "./dist/index.js",
|
|
10
10
|
"types": "./dist/index.d.ts",
|
|
11
|
+
"bin": {
|
|
12
|
+
"tuwa-docs-search-index": "./bin/search-index.js"
|
|
13
|
+
},
|
|
11
14
|
"type": "module",
|
|
12
15
|
"exports": {
|
|
13
16
|
".": {
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
14
18
|
"import": "./dist/index.js",
|
|
15
|
-
"require": "./dist/index.cjs"
|
|
16
|
-
"types": "./dist/index.d.ts"
|
|
19
|
+
"require": "./dist/index.cjs"
|
|
17
20
|
},
|
|
18
21
|
"./policies": {
|
|
19
22
|
"types": "./dist/policies.d.ts",
|
|
@@ -27,6 +30,7 @@
|
|
|
27
30
|
"access": "public"
|
|
28
31
|
},
|
|
29
32
|
"files": [
|
|
33
|
+
"bin",
|
|
30
34
|
"dist",
|
|
31
35
|
"socket.json"
|
|
32
36
|
],
|
|
@@ -57,7 +61,7 @@
|
|
|
57
61
|
}
|
|
58
62
|
],
|
|
59
63
|
"dependencies": {
|
|
60
|
-
"@tuwaio/nova-core": "^0.9.
|
|
64
|
+
"@tuwaio/nova-core": "^0.9.3",
|
|
61
65
|
"clsx": "^2.1.1",
|
|
62
66
|
"html-react-parser": "^6.1.8",
|
|
63
67
|
"tailwind-merge": "^3.7.0"
|
|
@@ -72,17 +76,17 @@
|
|
|
72
76
|
"@heroicons/react": "^2.2.0",
|
|
73
77
|
"@tailwindcss/postcss": "^4.3.3",
|
|
74
78
|
"@tailwindcss/vite": "^4.3.3",
|
|
75
|
-
"@types/node": "^26",
|
|
79
|
+
"@types/node": "^26.6.5",
|
|
76
80
|
"@types/react": "^19.3.0",
|
|
77
81
|
"autoprefixer": "^10.6.1",
|
|
78
|
-
"next": "16.
|
|
82
|
+
"next": "16.4.1",
|
|
79
83
|
"nextra": "^4.6.1",
|
|
80
84
|
"nextra-theme-docs": "^4.6.1",
|
|
81
|
-
"postcss": "^8.5.
|
|
85
|
+
"postcss": "^8.5.30",
|
|
82
86
|
"postcss-cli": "^12.0.0",
|
|
83
|
-
"react": "^19.3.
|
|
87
|
+
"react": "^19.3.1",
|
|
84
88
|
"tailwindcss": "^4.3.3",
|
|
85
|
-
"tsup": "^
|
|
89
|
+
"tsup": "^9.0.0",
|
|
86
90
|
"typescript": "6.0.3"
|
|
87
91
|
},
|
|
88
92
|
"scripts": {
|