fumapress 0.5.3 → 0.5.5

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/css/generated.css CHANGED
@@ -6,6 +6,7 @@
6
6
  @source inline("@orama/orama");
7
7
  @source inline("a");
8
8
  @source inline("above");
9
+ @source inline("absolute");
9
10
  @source inline("absolutePath");
10
11
  @source inline("accepts");
11
12
  @source inline("active:scale-95");
@@ -202,7 +203,6 @@
202
203
  @source inline("human");
203
204
  @source inline("i");
204
205
  @source inline("i18n");
205
- @source inline("i18nProvider");
206
206
  @source inline("if");
207
207
  @source inline("image");
208
208
  @source inline("imageSizes");
@@ -375,6 +375,7 @@
375
375
  @source inline("requests");
376
376
  @source inline("resolution");
377
377
  @source inline("resolve");
378
+ @source inline("resolveBaseUrl");
378
379
  @source inline("resolved");
379
380
  @source inline("rest");
380
381
  @source inline("result");
@@ -475,6 +476,7 @@
475
476
  @source inline("truncate");
476
477
  @source inline("type");
477
478
  @source inline("typeof");
479
+ @source inline("uiTranslations");
478
480
  @source inline("undefined");
479
481
  @source inline("unnecessary");
480
482
  @source inline("unoptimized");
@@ -512,6 +514,7 @@
512
514
  @source inline("width");
513
515
  @source inline("widths");
514
516
  @source inline("will");
517
+ @source inline("with");
515
518
  @source inline("without");
516
519
  @source inline("would");
517
520
  @source inline("wrapper");
@@ -1,8 +1,8 @@
1
1
  import { Link } from "./link.js";
2
+ import { joinPathname } from "../lib/pathname.js";
2
3
  import { getCreationDate, getPressContext } from "../lib/shared.js";
3
4
  import { cn } from "../lib/cn.js";
4
5
  import { I18nLabel } from "./i18n.js";
5
- import { joinPathname } from "../lib/pathname.js";
6
6
  import { getBlogContext } from "../plugins/blog.js";
7
7
  import { jsx, jsxs } from "react/jsx-runtime";
8
8
  import { CornerLeftUpIcon } from "lucide-react";
@@ -1,4 +1,5 @@
1
1
  "use client";
2
+ import { resolveBaseUrl } from "../lib/pathname.js";
2
3
  import { jsx, jsxs } from "react/jsx-runtime";
3
4
  import { useI18n } from "fumadocs-ui/contexts/i18n";
4
5
  import { SearchDialog, SearchDialogClose, SearchDialogContent, SearchDialogHeader, SearchDialogIcon, SearchDialogInput, SearchDialogList, SearchDialogOverlay } from "fumadocs-ui/components/dialog/search";
@@ -7,7 +8,10 @@ import { flexsearchStaticClient } from "fumadocs-core/search/client/flexsearch-s
7
8
  //#region src/components/flexsearch-static.tsx
8
9
  function DefaultSearchDialog(props) {
9
10
  const { locale } = useI18n();
10
- const { search, setSearch, query } = useDocsSearch({ client: flexsearchStaticClient({ locale }) });
11
+ const { search, setSearch, query } = useDocsSearch({ client: flexsearchStaticClient({
12
+ locale,
13
+ from: resolveBaseUrl(import.meta.env.BASE_URL, "/api/search")
14
+ }) });
11
15
  return /* @__PURE__ */ jsxs(SearchDialog, {
12
16
  search,
13
17
  onSearchChange: setSearch,
@@ -1,4 +1,5 @@
1
1
  "use client";
2
+ import { resolveBaseUrl } from "../lib/pathname.js";
2
3
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
4
  import { createContext, use, useMemo } from "react";
4
5
  import ReactDOM from "react-dom";
@@ -11,14 +12,14 @@ function parseLength(v) {
11
12
  return parsed;
12
13
  }
13
14
  /**
14
- * resolve src to a full pathname if relative
15
+ * resolve src to a full pathname with base URL included (or keep absolute URLs as-is)
15
16
  */
16
17
  function normalizeSrc(src, basePathname) {
17
- if (src.startsWith("http://") || src.startsWith("https://")) return src;
18
+ if (src.startsWith("http://") || src.startsWith("https://") || src.startsWith("data:")) return src;
18
19
  const base = new URL(basePathname, "http://localhost");
19
20
  const resolved = new URL(src, base);
20
21
  if (resolved.hostname !== base.hostname) throw new Error(`The src attribute "${src}" is not a valid relative path`);
21
- return resolved.pathname;
22
+ return resolveBaseUrl(import.meta.env.BASE_URL, resolved.pathname);
22
23
  }
23
24
  const ImageContext = createContext(null);
24
25
  function ImageProvider({ provider, children }) {
@@ -1,4 +1,5 @@
1
1
  "use client";
2
+ import { resolveBaseUrl } from "../lib/pathname.js";
2
3
  import { jsx, jsxs } from "react/jsx-runtime";
3
4
  import { useI18n } from "fumadocs-ui/contexts/i18n";
4
5
  import { SearchDialog, SearchDialogClose, SearchDialogContent, SearchDialogHeader, SearchDialogIcon, SearchDialogInput, SearchDialogList, SearchDialogOverlay } from "fumadocs-ui/components/dialog/search";
@@ -16,7 +17,8 @@ function DefaultSearchDialog(props) {
16
17
  const { search, setSearch, query } = useDocsSearch({
17
18
  type: "static",
18
19
  initOrama,
19
- locale
20
+ locale,
21
+ from: resolveBaseUrl(import.meta.env.BASE_URL, "/api/search")
20
22
  });
21
23
  return /* @__PURE__ */ jsxs(SearchDialog, {
22
24
  search,
package/dist/config.d.ts CHANGED
@@ -3,7 +3,6 @@ import { Adapter, Awaitable, PressLoaderOptions, ServerPluginOption } from "./li
3
3
  import { FC, ReactNode } from "react";
4
4
  import { LoaderConfig, LoaderOutput, Meta, Page, SourceUnion, _Internal } from "fumadocs-core/source";
5
5
  import { I18nConfig, SingularTranslationsAPI, TranslationsAPI } from "fumadocs-core/i18n";
6
- import { Translations } from "fumadocs-ui/i18n";
7
6
  import { BaseLayoutProps } from "fumadocs-ui/layouts/shared";
8
7
 
9
8
  //#region src/config.d.ts
@@ -23,11 +22,7 @@ interface BaseConfig<C extends ConfigContext> {
23
22
  */
24
23
  mode?: BuildMode;
25
24
  site?: SiteConfig;
26
- translations?: (C["i18n"] extends I18nConfig<infer Lang> ? TranslationsAPI<Lang, {
27
- ui: Translations;
28
- }> : never) | SingularTranslationsAPI<{
29
- ui: Translations;
30
- }>;
25
+ translations?: (C["i18n"] extends I18nConfig<infer Lang> ? TranslationsAPI<Lang> : never) | SingularTranslationsAPI;
31
26
  meta?: {
32
27
  /** render meta tags for any pages */root?: (this: AppContext<C>) => ReactNode; /** render meta tags for page */
33
28
  page?: (this: AppContext<C>, page: C["page"]) => ReactNode;
@@ -1,8 +1,8 @@
1
1
  import { Link } from "../components/link.js";
2
+ import { joinPathname } from "../lib/pathname.js";
2
3
  import { getPressContext } from "../lib/shared.js";
3
4
  import { cn } from "../lib/cn.js";
4
5
  import { I18nLabel } from "../components/i18n.js";
5
- import { joinPathname } from "../lib/pathname.js";
6
6
  import { getBlogContext } from "../plugins/blog.js";
7
7
  import { OrderedBlogGrid } from "../components/blog.js";
8
8
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
@@ -1,8 +1,8 @@
1
1
  import { Link } from "../components/link.js";
2
+ import { joinPathname } from "../lib/pathname.js";
2
3
  import { getCreationDate, getPressContext, renderBody, renderPageMeta, renderToc } from "../lib/shared.js";
3
4
  import { BlogPanel, BlogProvider } from "../components/blog-panel.js";
4
5
  import { getTags } from "../lib/shared/blog.js";
5
- import { joinPathname } from "../lib/pathname.js";
6
6
  import { createHomeLayout } from "./home.js";
7
7
  import { getBlogContext } from "../plugins/blog.js";
8
8
  import { LinkToHome } from "../components/blog.js";
@@ -1,7 +1,7 @@
1
1
  import { getPressContext, renderRootMeta } from "../lib/shared.js";
2
2
  import { PressProvider } from "../components/provider.js";
3
3
  import { jsx, jsxs } from "react/jsx-runtime";
4
- import { i18nProvider } from "fumadocs-ui/i18n";
4
+ import { i18nProvider, uiTranslations } from "fumadocs-ui/i18n";
5
5
  import stylesInline from "virtual:root.css?inline";
6
6
  import stylesHref from "virtual:root.css?url";
7
7
  //#region src/layouts/root.tsx
@@ -19,8 +19,8 @@ function createRootLayout(options) {
19
19
  ...options?.providerProps,
20
20
  children
21
21
  };
22
- if (ctx.translationsConfig && "config" in ctx.translationsConfig) providerProps.i18n ??= i18nProvider(ctx.translationsConfig, lang);
23
- else if (ctx.translationsConfig) providerProps.i18n ??= i18nProvider(ctx.translationsConfig);
22
+ if (ctx.translationsConfig && "config" in ctx.translationsConfig) providerProps.i18n ??= i18nProvider(ctx.translationsConfig.extend(uiTranslations()), lang);
23
+ else if (ctx.translationsConfig) providerProps.i18n ??= i18nProvider(ctx.translationsConfig.extend(uiTranslations()));
24
24
  if (hooks) for (const hook of hooks) providerProps = await hook(providerProps);
25
25
  return /* @__PURE__ */ jsxs("html", {
26
26
  lang: lang ?? "en",
@@ -1,4 +1,5 @@
1
1
  //#region src/lib/pathname.ts
2
+ /** Join multiple (full) pathnames */
2
3
  function joinPathname(...paths) {
3
4
  const segs = [];
4
5
  for (let p of paths) {
@@ -10,8 +11,14 @@ function joinPathname(...paths) {
10
11
  }
11
12
  const PATHNAME_SEGMENT_REGEX = /^[A-Za-z0-9\-._~!$&'()*+,;=:@]+$/;
12
13
  /** Check if the string is a full pathname (one that does not include `.` or `..`) */
13
- function isPlainPathname(s) {
14
+ function isFullPathname(s) {
14
15
  return s.startsWith("/") && s.slice(1).split("/").every((seg) => seg !== "." && seg !== ".." && PATHNAME_SEGMENT_REGEX.test(seg));
15
16
  }
17
+ /** add base URL to pathname */
18
+ function resolveBaseUrl(base, pathname) {
19
+ if (base.endsWith("/")) base = base.slice(0, -1);
20
+ if (pathname.startsWith("/")) pathname = pathname.slice(1);
21
+ return `${base}/${pathname}`;
22
+ }
16
23
  //#endregion
17
- export { isPlainPathname, joinPathname };
24
+ export { isFullPathname, joinPathname, resolveBaseUrl };
@@ -1,7 +1,7 @@
1
1
  import { __toESM } from "../_virtual/_rolldown/runtime.js";
2
2
  import { getGitRootDir } from "./fs.js";
3
3
  import { require_deepmerge } from "../node_modules/.pnpm/@fastify_deepmerge@3.2.1/node_modules/@fastify/deepmerge/index.js";
4
- import { disableSearchPlugin } from "../plugins/internal/disable-search.js";
4
+ import { applyDefaultsPlugin } from "../plugins/internal/defaults.js";
5
5
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
6
6
  import { Fragment as Fragment$1, isValidElement } from "react";
7
7
  import path from "node:path";
@@ -36,7 +36,7 @@ function resolvePlugins(plugins) {
36
36
  async function initApp(builder) {
37
37
  const config = builder.get();
38
38
  const { translations, site, mode = "default", layouts } = config;
39
- const plugins = resolvePlugins([...config.plugins, disableSearchPlugin()]);
39
+ const plugins = resolvePlugins([...config.plugins, ...applyDefaultsPlugin()]);
40
40
  const ctx = {
41
41
  $context: void 0,
42
42
  getLoader() {
@@ -1,5 +1,5 @@
1
- import { groupTags, groupTagsI18n } from "../lib/shared/blog.js";
2
1
  import { joinPathname } from "../lib/pathname.js";
2
+ import { groupTags, groupTagsI18n } from "../lib/shared/blog.js";
3
3
  import { createBlogLayout, createBlogLayoutPage } from "../layouts/blog.js";
4
4
  import { createBlogTagPage, createBlogTagsPage } from "../layouts/blog.tags.js";
5
5
  import { createBlogIndexPage } from "../layouts/blog.index.js";
@@ -1,6 +1,6 @@
1
1
  "use client";
2
- import { ImageProvider } from "../../components/image.js";
3
2
  import { joinPathname } from "../../lib/pathname.js";
3
+ import { ImageProvider } from "../../components/image.js";
4
4
  import { jsx } from "react/jsx-runtime";
5
5
  import { useMemo } from "react";
6
6
  //#region src/plugins/image/cloudflare.client.tsx
@@ -1,4 +1,5 @@
1
1
  "use client";
2
+ import { resolveBaseUrl } from "../../lib/pathname.js";
2
3
  import { ImageProvider } from "../../components/image.js";
3
4
  import { validateImageSrc } from "./self-hosted.utils.js";
4
5
  import { jsx } from "react/jsx-runtime";
@@ -16,7 +17,7 @@ function createProvider(config) {
16
17
  width: String(width),
17
18
  quality: String(quality)
18
19
  });
19
- return `${config.path}?${params.toString()}`;
20
+ return `${resolveBaseUrl(import.meta.env.BASE_URL, config.path)}?${params.toString()}`;
20
21
  },
21
22
  validate(src) {
22
23
  if (import.meta.env.DEV) {
@@ -1,5 +1,5 @@
1
1
  import { __toESM } from "../../_virtual/_rolldown/runtime.js";
2
- import { isPlainPathname } from "../../lib/pathname.js";
2
+ import { isFullPathname } from "../../lib/pathname.js";
3
3
  import { require_http_cache_semantics } from "../../node_modules/.pnpm/http-cache-semantics@4.2.0/node_modules/http-cache-semantics/index.js";
4
4
  //#region src/plugins/image/self-hosted.utils.ts
5
5
  var import_http_cache_semantics = /* @__PURE__ */ __toESM(require_http_cache_semantics(), 1);
@@ -253,7 +253,7 @@ function validateImageSrc(config, src) {
253
253
  };
254
254
  return { allowed: true };
255
255
  }
256
- if (isPlainPathname(src)) return { allowed: true };
256
+ if (isFullPathname(src)) return { allowed: true };
257
257
  return {
258
258
  allowed: false,
259
259
  reason: `Image URL "${src}" is not normalized`
@@ -0,0 +1,21 @@
1
+ import { fumapressTranslations } from "../../i18n.js";
2
+ //#region src/plugins/internal/defaults.ts
3
+ function applyDefaultsPlugin() {
4
+ return [{
5
+ name: "core:i18n",
6
+ init() {
7
+ if (this.translationsConfig) this.translationsConfig.extend(fumapressTranslations());
8
+ }
9
+ }, {
10
+ name: "core:disable-search-if-needed",
11
+ enforce: "post",
12
+ init() {
13
+ (this.data["core:provider"] ??= []).push((data) => {
14
+ data.search ??= { enabled: false };
15
+ return data;
16
+ });
17
+ }
18
+ }];
19
+ }
20
+ //#endregion
21
+ export { applyDefaultsPlugin };
@@ -1,6 +1,7 @@
1
- import { isPlainPathname } from "../lib/pathname.js";
1
+ import { isFullPathname, resolveBaseUrl } from "../lib/pathname.js";
2
2
  import { PayloadProvider, WithPayload } from "../components/openapi.payload.js";
3
3
  import { jsx } from "react/jsx-runtime";
4
+ import { openapiTranslations } from "fumadocs-openapi/i18n";
4
5
  //#region src/plugins/openapi.tsx
5
6
  /**
6
7
  * this will register the OpenAPI adapter & required layout configs.
@@ -15,9 +16,14 @@ function openapiPlugin(options) {
15
16
  }
16
17
  async function ServerPayloadProvider({ children }) {
17
18
  const payload = {};
19
+ let clientProxyUrl;
20
+ if (typeof server.options.proxyUrl === "string") {
21
+ const proxyUrl = server.options.proxyUrl;
22
+ clientProxyUrl = isFullPathname(proxyUrl) ? resolveBaseUrl(import.meta.env.BASE_URL, proxyUrl) : proxyUrl;
23
+ }
18
24
  for (const [schemaId, schema] of Object.entries(await server.getSchemas())) payload[schemaId] = {
19
25
  bundled: schema.bundled,
20
- proxyUrl: server.options.proxyUrl
26
+ proxyUrl: clientProxyUrl
21
27
  };
22
28
  return /* @__PURE__ */ jsx(PayloadProvider, {
23
29
  payload: JSON.stringify(payload),
@@ -30,6 +36,7 @@ function openapiPlugin(options) {
30
36
  this.adapters.push(adapter(options));
31
37
  initRenderers(this.data["core:docs-layout"] ??= {});
32
38
  initRenderers(this.data["core:notebook-layout"] ??= {});
39
+ if (this.translationsConfig) this.translationsConfig.extend(openapiTranslations());
33
40
  },
34
41
  renderPage({ page, fallback }) {
35
42
  if (!isOpenAPI(page.data)) return;
@@ -44,7 +51,7 @@ function openapiPlugin(options) {
44
51
  },
45
52
  async createPages({ createApi }) {
46
53
  const proxyUrl = server.options.proxyUrl;
47
- const { createProxy = typeof proxyUrl === "string" && isPlainPathname(proxyUrl) } = options;
54
+ const { createProxy = typeof proxyUrl === "string" && isFullPathname(proxyUrl) } = options;
48
55
  if (createProxy) {
49
56
  if (!proxyUrl) throw new Error(`[Fumapress] The "proxyUrl" option in createOpenAPI() is required to create proxy server`);
50
57
  if (this.mode === "static") throw new Error(`[Fumapress] static mode is not compatible with proxy server`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fumapress",
3
- "version": "0.5.3",
3
+ "version": "0.5.5",
4
4
  "description": "An opinionated docs framework powered by Fumadocs",
5
5
  "keywords": [
6
6
  "Docs",
@@ -56,7 +56,7 @@
56
56
  },
57
57
  "dependencies": {
58
58
  "@orama/orama": "^3.1.18",
59
- "@takumi-rs/image-response": "^1.6.0",
59
+ "@takumi-rs/image-response": "^1.7.0",
60
60
  "class-variance-authority": "^0.7.1",
61
61
  "flexsearch": "^0.8.212",
62
62
  "lucide-react": "^1.17.0",
@@ -74,7 +74,7 @@
74
74
  "@types/react": "^19.2.16",
75
75
  "@types/react-dom": "^19.2.3",
76
76
  "fumadocs-core": "^16.9.3",
77
- "fumadocs-mdx": "^15.0.10",
77
+ "fumadocs-mdx": "^15.0.11",
78
78
  "fumadocs-openapi": "^10.10.3",
79
79
  "fumadocs-ui": "^16.9.3",
80
80
  "hono": "^4.12.23",
@@ -82,7 +82,7 @@
82
82
  "react": "^19.2.7",
83
83
  "react-dom": "^19.2.7",
84
84
  "sharp": "^0.34.5",
85
- "tsdown": "0.22.1",
85
+ "tsdown": "0.22.2",
86
86
  "typescript": "^6.0.3",
87
87
  "waku": "1.0.0-beta.2"
88
88
  },
@@ -97,7 +97,7 @@
97
97
  "react": "^19.2.0",
98
98
  "react-dom": "^19.2.0",
99
99
  "sharp": "^0.34.0",
100
- "waku": "1.0.0-beta.1"
100
+ "waku": "1.0.0-beta.2"
101
101
  },
102
102
  "peerDependenciesMeta": {
103
103
  "sharp": {
@@ -1,15 +0,0 @@
1
- //#region src/plugins/internal/disable-search.ts
2
- function disableSearchPlugin() {
3
- return {
4
- name: "core:disable-search-if-needed",
5
- enforce: "post",
6
- init() {
7
- (this.data["core:provider"] ??= []).push((data) => {
8
- data.search ??= { enabled: false };
9
- return data;
10
- });
11
- }
12
- };
13
- }
14
- //#endregion
15
- export { disableSearchPlugin };