zudoku 0.18.5 → 0.18.7

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.
Files changed (86) hide show
  1. package/dist/app/entry.client.d.ts +1 -0
  2. package/dist/app/entry.client.js +8 -0
  3. package/dist/app/entry.client.js.map +1 -1
  4. package/dist/app/entry.server.d.ts +1 -0
  5. package/dist/app/entry.server.js +1 -0
  6. package/dist/app/entry.server.js.map +1 -1
  7. package/dist/lib/components/Layout.js +3 -2
  8. package/dist/lib/components/Layout.js.map +1 -1
  9. package/dist/lib/components/MobileTopNavigation.js +1 -1
  10. package/dist/lib/components/MobileTopNavigation.js.map +1 -1
  11. package/dist/lib/components/Search.d.ts +3 -1
  12. package/dist/lib/components/Search.js +3 -3
  13. package/dist/lib/components/Search.js.map +1 -1
  14. package/dist/lib/components/navigation/Sidebar.d.ts +3 -1
  15. package/dist/lib/components/navigation/Sidebar.js +2 -2
  16. package/dist/lib/components/navigation/Sidebar.js.map +1 -1
  17. package/dist/lib/components/navigation/SidebarCategory.d.ts +2 -1
  18. package/dist/lib/components/navigation/SidebarCategory.js +2 -2
  19. package/dist/lib/components/navigation/SidebarCategory.js.map +1 -1
  20. package/dist/lib/components/navigation/SidebarItem.d.ts +2 -2
  21. package/dist/lib/components/navigation/SidebarItem.js +5 -5
  22. package/dist/lib/components/navigation/SidebarItem.js.map +1 -1
  23. package/dist/lib/oas/graphql/index.js +4 -0
  24. package/dist/lib/oas/graphql/index.js.map +1 -1
  25. package/dist/lib/plugins/markdown/MdxPage.d.ts +1 -1
  26. package/dist/lib/plugins/markdown/MdxPage.js +2 -2
  27. package/dist/lib/plugins/markdown/MdxPage.js.map +1 -1
  28. package/dist/lib/plugins/markdown/index.d.ts +1 -0
  29. package/dist/lib/plugins/markdown/index.js.map +1 -1
  30. package/dist/lib/plugins/openapi/OperationList.js +44 -1
  31. package/dist/lib/plugins/openapi/OperationList.js.map +1 -1
  32. package/dist/lib/plugins/openapi/graphql/gql.d.ts +1 -1
  33. package/dist/lib/plugins/openapi/graphql/gql.js +1 -1
  34. package/dist/lib/plugins/openapi/graphql/gql.js.map +1 -1
  35. package/dist/lib/plugins/openapi/graphql/graphql.d.ts +2 -0
  36. package/dist/lib/plugins/openapi/graphql/graphql.js +1 -0
  37. package/dist/lib/plugins/openapi/graphql/graphql.js.map +1 -1
  38. package/dist/vite/build.js +7 -1
  39. package/dist/vite/build.js.map +1 -1
  40. package/dist/vite/config.js +10 -0
  41. package/dist/vite/config.js.map +1 -1
  42. package/dist/vite/dev-server.js +7 -0
  43. package/dist/vite/dev-server.js.map +1 -1
  44. package/dist/vite/output.js +31 -16
  45. package/dist/vite/output.js.map +1 -1
  46. package/dist/vite/plugin-mdx.js +48 -1
  47. package/dist/vite/plugin-mdx.js.map +1 -1
  48. package/lib/AnchorLink-BR0MvI7n.js +35 -0
  49. package/lib/AnchorLink-BR0MvI7n.js.map +1 -0
  50. package/lib/{MdxPage-B2FpJ9KC.js → MdxPage-HjFSZJQk.js} +85 -80
  51. package/lib/MdxPage-HjFSZJQk.js.map +1 -0
  52. package/lib/{OperationList-BkNQEsNs.js → OperationList-DzE32oyS.js} +971 -961
  53. package/lib/{OperationList-BkNQEsNs.js.map → OperationList-DzE32oyS.js.map} +1 -1
  54. package/lib/assets/{worker-BHClFO3A.js → worker-CyxLedqF.js} +435 -431
  55. package/lib/assets/{worker-BHClFO3A.js.map → worker-CyxLedqF.js.map} +1 -1
  56. package/lib/{createServer-CpJlUPtn.js → createServer-DTiCfoql.js} +5 -1
  57. package/lib/{createServer-CpJlUPtn.js.map → createServer-DTiCfoql.js.map} +1 -1
  58. package/lib/{index-C7SaIME0.js → index-NNCc1BSK.js} +5 -4
  59. package/lib/{index-C7SaIME0.js.map → index-NNCc1BSK.js.map} +1 -1
  60. package/lib/{AnchorLink-CDlhr8gL.js → index.esm-Bm8pj-bc.js} +223 -254
  61. package/lib/index.esm-Bm8pj-bc.js.map +1 -0
  62. package/lib/ui/Drawer.js +79 -79
  63. package/lib/ui/Drawer.js.map +1 -1
  64. package/lib/zudoku.components.js +445 -414
  65. package/lib/zudoku.components.js.map +1 -1
  66. package/lib/zudoku.openapi-worker.js +1 -1
  67. package/lib/zudoku.plugin-markdown.js +1 -1
  68. package/lib/zudoku.plugin-markdown.js.map +1 -1
  69. package/lib/zudoku.plugin-openapi.js +1 -1
  70. package/package.json +4 -3
  71. package/src/app/entry.client.tsx +9 -0
  72. package/src/app/entry.server.tsx +1 -0
  73. package/src/lib/components/Layout.tsx +8 -3
  74. package/src/lib/components/MobileTopNavigation.tsx +18 -18
  75. package/src/lib/components/Search.tsx +3 -3
  76. package/src/lib/components/navigation/Sidebar.tsx +18 -8
  77. package/src/lib/components/navigation/SidebarCategory.tsx +3 -0
  78. package/src/lib/components/navigation/SidebarItem.tsx +12 -2
  79. package/src/lib/oas/graphql/index.ts +4 -0
  80. package/src/lib/plugins/markdown/MdxPage.tsx +2 -0
  81. package/src/lib/plugins/markdown/index.tsx +1 -0
  82. package/src/lib/plugins/openapi/OperationList.tsx +62 -2
  83. package/src/lib/plugins/openapi/graphql/gql.ts +2 -2
  84. package/src/lib/plugins/openapi/graphql/graphql.ts +3 -0
  85. package/lib/AnchorLink-CDlhr8gL.js.map +0 -1
  86. package/lib/MdxPage-B2FpJ9KC.js.map +0 -1
@@ -2,7 +2,7 @@ const o = () => {
2
2
  const r = new SharedWorker(
3
3
  new URL(
4
4
  /* @vite-ignore */
5
- "./assets/worker-BHClFO3A.js",
5
+ "./assets/worker-CyxLedqF.js",
6
6
  import.meta.url
7
7
  ),
8
8
  { type: "module" }
@@ -74,7 +74,7 @@ const C = (n) => ({
74
74
  const h = {
75
75
  path: r,
76
76
  lazy: async () => {
77
- const { MdxPage: l } = await import("./MdxPage-B2FpJ9KC.js"), { default: p, ...g } = await a();
77
+ const { MdxPage: l } = await import("./MdxPage-HjFSZJQk.js"), { default: p, ...g } = await a();
78
78
  return {
79
79
  element: /* @__PURE__ */ P.jsx(
80
80
  l,
@@ -1 +1 @@
1
- {"version":3,"file":"zudoku.plugin-markdown.js","sources":["../src/lib/plugins/markdown/resolver.ts","../src/lib/plugins/markdown/index.tsx"],"sourcesContent":["import fs from \"fs\";\nimport path from \"path\";\nimport {\n ZudokuConfig,\n ZudokuDocsConfig,\n} from \"../../../config/validators/validate.js\";\n\nconst DEFAULT_DOCS_FILES = \"/pages/**/*.{md,mdx}\";\n\n// TODO: This should be dynamic based on the glob selector\nconst SUPPORTED_EXTENSIONS = [\".md\", \".mdx\"];\n\n/**\n * Utilities for resolving markdown file paths and routes\n */\nexport class DocResolver {\n constructor(private config: ZudokuConfig) {}\n\n fileMap = new Map<string, string>();\n\n /**\n * Gets the default docs config from the zudoku config\n */\n getDocsConfigs() {\n const docsConfigs: ZudokuDocsConfig[] = this.config.docs\n ? Array.isArray(this.config.docs)\n ? this.config.docs\n : [this.config.docs]\n : [{ files: DEFAULT_DOCS_FILES }];\n\n return docsConfigs;\n }\n\n /**\n * Resolves the first matching file system path for a given docId\n * @param docId - The docId to resolve\n * @returns\n */\n resolveFilePath(docId: string) {\n const docsConfigs = this.getDocsConfigs();\n let fsPath: string | undefined;\n\n docsConfigs.forEach(({ files: fileGlob }) => {\n if (fsPath) {\n return;\n }\n const rootDir = DocResolver.getRootDir(fileGlob);\n for (const ext of SUPPORTED_EXTENSIONS) {\n if (fsPath) {\n return;\n }\n const checkPath = path.join(rootDir, `${docId}${ext}`);\n if (fs.existsSync(checkPath)) {\n fsPath = checkPath;\n }\n }\n });\n\n return fsPath;\n }\n\n /**\n * Gets the root directory from a files glob\n */\n private static getRootDir(filesGlob: string) {\n let rootDir = filesGlob.split(\"**\")[0];\n if (!rootDir) {\n throw new Error(\"Invalid files glob. Must have '**' in the path.\");\n }\n rootDir = rootDir.replace(\"/**\", \"/\");\n return rootDir;\n }\n\n /**\n * Resolves the route path for a given file system path\n * @param options - The options to resolve the route path\n * @returns The string route path\n */\n static resolveRoutePath({\n filesGlob,\n fsPath,\n }: {\n filesGlob: string;\n fsPath: string;\n }) {\n const rootDir = this.getRootDir(filesGlob);\n const re = new RegExp(`^${rootDir}(.*).mdx?`);\n const match = fsPath.match(re);\n const routePath = match?.at(1);\n return routePath;\n }\n}\n","import type { Toc } from \"@stefanprobst/rehype-extract-toc\";\nimport type { MDXProps } from \"mdx/types.js\";\nimport { RouteObject } from \"react-router-dom\";\nimport { ZudokuDocsConfig } from \"../../../config/validators/validate.js\";\nimport type { ZudokuPlugin } from \"../../core/plugins.js\";\nimport { DocResolver } from \"./resolver.js\";\n\nexport interface MarkdownPluginOptions extends ZudokuDocsConfig {\n fileImports: Record<string, () => Promise<MDXImport>>;\n}\nexport type MarkdownPluginDefaultOptions = Pick<\n Frontmatter,\n \"toc\" | \"disablePager\"\n>;\n\nexport type Frontmatter = {\n title?: string;\n description?: string;\n category?: string;\n toc?: boolean;\n disablePager?: boolean;\n};\n\nexport type MDXImport = {\n tableOfContents: Toc;\n frontmatter: Frontmatter;\n default: (props: MDXProps) => JSX.Element;\n};\n\nexport const markdownPlugin = (\n options: MarkdownPluginOptions[],\n): ZudokuPlugin => ({\n getRoutes: () => {\n const routeMap = new Map<string, RouteObject>();\n options.forEach(({ fileImports, files, defaultOptions }) =>\n Object.entries(fileImports).flatMap(([file, importPromise]) => {\n const routePath = DocResolver.resolveRoutePath({\n filesGlob: files,\n fsPath: file,\n });\n\n if (!routePath) return [];\n\n if (routeMap.has(routePath)) {\n // eslint-disable-next-line no-console\n console.warn(\n `Duplicate route path found for ${routePath}. Skipping file at '${file}'.`,\n );\n return [];\n }\n\n const route: RouteObject = {\n path: routePath,\n lazy: async () => {\n const { MdxPage } = await import(\"./MdxPage.js\");\n const { default: Component, ...props } = await importPromise();\n return {\n element: (\n <MdxPage\n file={file}\n mdxComponent={Component}\n {...props}\n defaultOptions={defaultOptions}\n />\n ),\n };\n },\n };\n routeMap.set(routePath, route);\n }),\n );\n return [...routeMap.values()];\n },\n});\n"],"names":["DEFAULT_DOCS_FILES","SUPPORTED_EXTENSIONS","DocResolver","config","__publicField","docId","docsConfigs","fsPath","fileGlob","rootDir","ext","checkPath","path","fs","filesGlob","re","match","markdownPlugin","options","routeMap","fileImports","files","defaultOptions","file","importPromise","routePath","route","MdxPage","Component","props","jsx"],"mappings":";;;;;AAOA,MAAMA,IAAqB,wBAGrBC,IAAuB,CAAC,OAAO,MAAM;AAKpC,MAAMC,EAAY;AAAA,EACvB,YAAoBC,GAAsB;AAE1C,IAAAC,EAAA,qCAAc;AAFM,SAAA,SAAAD;AAAA,EAAuB;AAAA;AAAA;AAAA;AAAA,EAO3C,iBAAiB;AAOR,WANiC,KAAK,OAAO,OAChD,MAAM,QAAQ,KAAK,OAAO,IAAI,IAC5B,KAAK,OAAO,OACZ,CAAC,KAAK,OAAO,IAAI,IACnB,CAAC,EAAE,OAAOH,EAAA,CAAoB;AAAA,EAGpC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,gBAAgBK,GAAe;AACvB,UAAAC,IAAc,KAAK;AACrB,QAAAC;AAEJ,WAAAD,EAAY,QAAQ,CAAC,EAAE,OAAOE,QAAe;AAC3C,UAAID;AACF;AAEI,YAAAE,IAAUP,EAAY,WAAWM,CAAQ;AAC/C,iBAAWE,KAAOT,GAAsB;AACtC,YAAIM;AACF;AAEI,cAAAI,IAAYC,EAAK,KAAKH,GAAS,GAAGJ,CAAK,GAAGK,CAAG,EAAE;AACjD,QAAAG,EAAG,WAAWF,CAAS,MAChBJ,IAAAI;AAAA,MAEb;AAAA,IAAA,CACD,GAEMJ;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,OAAe,WAAWO,GAAmB;AAC3C,QAAIL,IAAUK,EAAU,MAAM,IAAI,EAAE,CAAC;AACrC,QAAI,CAACL;AACG,YAAA,IAAI,MAAM,iDAAiD;AAEzD,WAAAA,IAAAA,EAAQ,QAAQ,OAAO,GAAG,GAC7BA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,OAAO,iBAAiB;AAAA,IACtB,WAAAK;AAAA,IACA,QAAAP;AAAA,EAAA,GAIC;AACK,UAAAE,IAAU,KAAK,WAAWK,CAAS,GACnCC,IAAK,IAAI,OAAO,IAAIN,CAAO,WAAW,GACtCO,IAAQT,EAAO,MAAMQ,CAAE;AAEtB,WADWC,KAAA,gBAAAA,EAAO,GAAG;AAAA,EAE9B;AACF;AC9Da,MAAAC,IAAiB,CAC5BC,OACkB;AAAA,EAClB,WAAW,MAAM;AACT,UAAAC,wBAAe;AACb,WAAAD,EAAA;AAAA,MAAQ,CAAC,EAAE,aAAAE,GAAa,OAAAC,GAAO,gBAAAC,QACrC,OAAO,QAAQF,CAAW,EAAE,QAAQ,CAAC,CAACG,GAAMC,CAAa,MAAM;AACvD,cAAAC,IAAYvB,EAAY,iBAAiB;AAAA,UAC7C,WAAWmB;AAAA,UACX,QAAQE;AAAA,QAAA,CACT;AAEG,YAAA,CAACE,EAAW,QAAO;AAEnB,YAAAN,EAAS,IAAIM,CAAS;AAEhB,yBAAA;AAAA,YACN,kCAAkCA,CAAS,uBAAuBF,CAAI;AAAA,UAAA,GAEjE;AAGT,cAAMG,IAAqB;AAAA,UACzB,MAAMD;AAAA,UACN,MAAM,YAAY;AAChB,kBAAM,EAAE,SAAAE,EAAA,IAAY,MAAM,OAAO,uBAAc,GACzC,EAAE,SAASC,GAAW,GAAGC,EAAM,IAAI,MAAML;AACxC,mBAAA;AAAA,cACL,SACEM,gBAAAA,EAAA;AAAA,gBAACH;AAAA,gBAAA;AAAA,kBACC,MAAAJ;AAAA,kBACA,cAAcK;AAAA,kBACb,GAAGC;AAAA,kBACJ,gBAAAP;AAAA,gBAAA;AAAA,cACF;AAAA,YAAA;AAAA,UAGN;AAAA,QAAA;AAEO,QAAAH,EAAA,IAAIM,GAAWC,CAAK;AAAA,MAAA,CAC9B;AAAA,IAAA,GAEI,CAAC,GAAGP,EAAS,OAAA,CAAQ;AAAA,EAC9B;AACF;"}
1
+ {"version":3,"file":"zudoku.plugin-markdown.js","sources":["../src/lib/plugins/markdown/resolver.ts","../src/lib/plugins/markdown/index.tsx"],"sourcesContent":["import fs from \"fs\";\nimport path from \"path\";\nimport {\n ZudokuConfig,\n ZudokuDocsConfig,\n} from \"../../../config/validators/validate.js\";\n\nconst DEFAULT_DOCS_FILES = \"/pages/**/*.{md,mdx}\";\n\n// TODO: This should be dynamic based on the glob selector\nconst SUPPORTED_EXTENSIONS = [\".md\", \".mdx\"];\n\n/**\n * Utilities for resolving markdown file paths and routes\n */\nexport class DocResolver {\n constructor(private config: ZudokuConfig) {}\n\n fileMap = new Map<string, string>();\n\n /**\n * Gets the default docs config from the zudoku config\n */\n getDocsConfigs() {\n const docsConfigs: ZudokuDocsConfig[] = this.config.docs\n ? Array.isArray(this.config.docs)\n ? this.config.docs\n : [this.config.docs]\n : [{ files: DEFAULT_DOCS_FILES }];\n\n return docsConfigs;\n }\n\n /**\n * Resolves the first matching file system path for a given docId\n * @param docId - The docId to resolve\n * @returns\n */\n resolveFilePath(docId: string) {\n const docsConfigs = this.getDocsConfigs();\n let fsPath: string | undefined;\n\n docsConfigs.forEach(({ files: fileGlob }) => {\n if (fsPath) {\n return;\n }\n const rootDir = DocResolver.getRootDir(fileGlob);\n for (const ext of SUPPORTED_EXTENSIONS) {\n if (fsPath) {\n return;\n }\n const checkPath = path.join(rootDir, `${docId}${ext}`);\n if (fs.existsSync(checkPath)) {\n fsPath = checkPath;\n }\n }\n });\n\n return fsPath;\n }\n\n /**\n * Gets the root directory from a files glob\n */\n private static getRootDir(filesGlob: string) {\n let rootDir = filesGlob.split(\"**\")[0];\n if (!rootDir) {\n throw new Error(\"Invalid files glob. Must have '**' in the path.\");\n }\n rootDir = rootDir.replace(\"/**\", \"/\");\n return rootDir;\n }\n\n /**\n * Resolves the route path for a given file system path\n * @param options - The options to resolve the route path\n * @returns The string route path\n */\n static resolveRoutePath({\n filesGlob,\n fsPath,\n }: {\n filesGlob: string;\n fsPath: string;\n }) {\n const rootDir = this.getRootDir(filesGlob);\n const re = new RegExp(`^${rootDir}(.*).mdx?`);\n const match = fsPath.match(re);\n const routePath = match?.at(1);\n return routePath;\n }\n}\n","import type { Toc } from \"@stefanprobst/rehype-extract-toc\";\nimport type { MDXProps } from \"mdx/types.js\";\nimport { RouteObject } from \"react-router-dom\";\nimport { ZudokuDocsConfig } from \"../../../config/validators/validate.js\";\nimport type { ZudokuPlugin } from \"../../core/plugins.js\";\nimport { DocResolver } from \"./resolver.js\";\n\nexport interface MarkdownPluginOptions extends ZudokuDocsConfig {\n fileImports: Record<string, () => Promise<MDXImport>>;\n}\nexport type MarkdownPluginDefaultOptions = Pick<\n Frontmatter,\n \"toc\" | \"disablePager\"\n>;\n\nexport type Frontmatter = {\n title?: string;\n description?: string;\n category?: string;\n toc?: boolean;\n disablePager?: boolean;\n};\n\nexport type MDXImport = {\n tableOfContents: Toc;\n frontmatter: Frontmatter;\n excerpt?: string;\n default: (props: MDXProps) => JSX.Element;\n};\n\nexport const markdownPlugin = (\n options: MarkdownPluginOptions[],\n): ZudokuPlugin => ({\n getRoutes: () => {\n const routeMap = new Map<string, RouteObject>();\n options.forEach(({ fileImports, files, defaultOptions }) =>\n Object.entries(fileImports).flatMap(([file, importPromise]) => {\n const routePath = DocResolver.resolveRoutePath({\n filesGlob: files,\n fsPath: file,\n });\n\n if (!routePath) return [];\n\n if (routeMap.has(routePath)) {\n // eslint-disable-next-line no-console\n console.warn(\n `Duplicate route path found for ${routePath}. Skipping file at '${file}'.`,\n );\n return [];\n }\n\n const route: RouteObject = {\n path: routePath,\n lazy: async () => {\n const { MdxPage } = await import(\"./MdxPage.js\");\n const { default: Component, ...props } = await importPromise();\n return {\n element: (\n <MdxPage\n file={file}\n mdxComponent={Component}\n {...props}\n defaultOptions={defaultOptions}\n />\n ),\n };\n },\n };\n routeMap.set(routePath, route);\n }),\n );\n return [...routeMap.values()];\n },\n});\n"],"names":["DEFAULT_DOCS_FILES","SUPPORTED_EXTENSIONS","DocResolver","config","__publicField","docId","docsConfigs","fsPath","fileGlob","rootDir","ext","checkPath","path","fs","filesGlob","re","match","markdownPlugin","options","routeMap","fileImports","files","defaultOptions","file","importPromise","routePath","route","MdxPage","Component","props","jsx"],"mappings":";;;;;AAOA,MAAMA,IAAqB,wBAGrBC,IAAuB,CAAC,OAAO,MAAM;AAKpC,MAAMC,EAAY;AAAA,EACvB,YAAoBC,GAAsB;AAE1C,IAAAC,EAAA,qCAAc;AAFM,SAAA,SAAAD;AAAA,EAAuB;AAAA;AAAA;AAAA;AAAA,EAO3C,iBAAiB;AAOR,WANiC,KAAK,OAAO,OAChD,MAAM,QAAQ,KAAK,OAAO,IAAI,IAC5B,KAAK,OAAO,OACZ,CAAC,KAAK,OAAO,IAAI,IACnB,CAAC,EAAE,OAAOH,EAAA,CAAoB;AAAA,EAGpC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,gBAAgBK,GAAe;AACvB,UAAAC,IAAc,KAAK;AACrB,QAAAC;AAEJ,WAAAD,EAAY,QAAQ,CAAC,EAAE,OAAOE,QAAe;AAC3C,UAAID;AACF;AAEI,YAAAE,IAAUP,EAAY,WAAWM,CAAQ;AAC/C,iBAAWE,KAAOT,GAAsB;AACtC,YAAIM;AACF;AAEI,cAAAI,IAAYC,EAAK,KAAKH,GAAS,GAAGJ,CAAK,GAAGK,CAAG,EAAE;AACjD,QAAAG,EAAG,WAAWF,CAAS,MAChBJ,IAAAI;AAAA,MAEb;AAAA,IAAA,CACD,GAEMJ;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,OAAe,WAAWO,GAAmB;AAC3C,QAAIL,IAAUK,EAAU,MAAM,IAAI,EAAE,CAAC;AACrC,QAAI,CAACL;AACG,YAAA,IAAI,MAAM,iDAAiD;AAEzD,WAAAA,IAAAA,EAAQ,QAAQ,OAAO,GAAG,GAC7BA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,OAAO,iBAAiB;AAAA,IACtB,WAAAK;AAAA,IACA,QAAAP;AAAA,EAAA,GAIC;AACK,UAAAE,IAAU,KAAK,WAAWK,CAAS,GACnCC,IAAK,IAAI,OAAO,IAAIN,CAAO,WAAW,GACtCO,IAAQT,EAAO,MAAMQ,CAAE;AAEtB,WADWC,KAAA,gBAAAA,EAAO,GAAG;AAAA,EAE9B;AACF;AC7Da,MAAAC,IAAiB,CAC5BC,OACkB;AAAA,EAClB,WAAW,MAAM;AACT,UAAAC,wBAAe;AACb,WAAAD,EAAA;AAAA,MAAQ,CAAC,EAAE,aAAAE,GAAa,OAAAC,GAAO,gBAAAC,QACrC,OAAO,QAAQF,CAAW,EAAE,QAAQ,CAAC,CAACG,GAAMC,CAAa,MAAM;AACvD,cAAAC,IAAYvB,EAAY,iBAAiB;AAAA,UAC7C,WAAWmB;AAAA,UACX,QAAQE;AAAA,QAAA,CACT;AAEG,YAAA,CAACE,EAAW,QAAO;AAEnB,YAAAN,EAAS,IAAIM,CAAS;AAEhB,yBAAA;AAAA,YACN,kCAAkCA,CAAS,uBAAuBF,CAAI;AAAA,UAAA,GAEjE;AAGT,cAAMG,IAAqB;AAAA,UACzB,MAAMD;AAAA,UACN,MAAM,YAAY;AAChB,kBAAM,EAAE,SAAAE,EAAA,IAAY,MAAM,OAAO,uBAAc,GACzC,EAAE,SAASC,GAAW,GAAGC,EAAM,IAAI,MAAML;AACxC,mBAAA;AAAA,cACL,SACEM,gBAAAA,EAAA;AAAA,gBAACH;AAAA,gBAAA;AAAA,kBACC,MAAAJ;AAAA,kBACA,cAAcK;AAAA,kBACb,GAAGC;AAAA,kBACJ,gBAAAP;AAAA,gBAAA;AAAA,cACF;AAAA,YAAA;AAAA,UAGN;AAAA,QAAA;AAEO,QAAAH,EAAA,IAAIM,GAAWC,CAAK;AAAA,MAAA,CAC9B;AAAA,IAAA,GAEI,CAAC,GAAGP,EAAS,OAAA,CAAQ;AAAA,EAC9B;AACF;"}
@@ -1,5 +1,5 @@
1
1
  import "./jsx-runtime-B6kdoens.js";
2
- import { o as a } from "./index-C7SaIME0.js";
2
+ import { o as a } from "./index-NNCc1BSK.js";
3
3
  import "./utils-DcpDOncX.js";
4
4
  import "lucide-react";
5
5
  import "./hook-hEqe7fPB.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zudoku",
3
- "version": "0.18.5",
3
+ "version": "0.18.7",
4
4
  "type": "module",
5
5
  "homepage": "https://zudoku.dev",
6
6
  "repository": {
@@ -149,7 +149,7 @@
149
149
  "@types/react": "18.3.11",
150
150
  "@types/react-dom": "18.3.1",
151
151
  "@vitejs/plugin-react": "4.3.4",
152
- "@zudoku/config": "0.18.5",
152
+ "@zudoku/config": "0.18.7",
153
153
  "@zudoku/httpsnippet": "10.0.9",
154
154
  "@zudoku/react-helmet-async": "2.0.4",
155
155
  "autoprefixer": "10.4.20",
@@ -166,6 +166,7 @@
166
166
  "graphql-type-json": "0.3.2",
167
167
  "graphql-yoga": "5.7.0",
168
168
  "gray-matter": "4.0.3",
169
+ "hast-util-to-string": "3.0.1",
169
170
  "http-terminator": "3.2.0",
170
171
  "loglevel": "1.9.2",
171
172
  "lru-cache": "11.0.1",
@@ -202,7 +203,7 @@
202
203
  "ulidx": "2.4.1",
203
204
  "unist-util-visit": "5.0.0",
204
205
  "urql": "4.1.0",
205
- "vaul": "1.1.0",
206
+ "vaul": "1.1.1",
206
207
  "vfile": "6.0.3",
207
208
  "vite": "5.4.9",
208
209
  "yaml": "2.6.0",
@@ -6,6 +6,7 @@ import {
6
6
  } from "react-router-dom";
7
7
  import config from "virtual:zudoku-config";
8
8
  import "virtual:zudoku-theme.css";
9
+ import "vite/modulepreload-polyfill";
9
10
  import { Bootstrap } from "zudoku/components";
10
11
  import "./main.css";
11
12
  import { getRoutesByConfig } from "./main.js";
@@ -64,3 +65,11 @@ async function hydrate(routes: RouteObject[]) {
64
65
 
65
66
  hydrateRoot(root, <Bootstrap hydrate router={router} />);
66
67
  }
68
+
69
+ // This is a workaround to avoid version skewing
70
+ // See https://vite.dev/guide/build.html#load-error-handling
71
+ // TODO: Implement a more advanced solution if there are CDN urls or e.g. Vercel Skew Protection
72
+ window.addEventListener("vite:preloadError", (e) => {
73
+ e.preventDefault();
74
+ window.location.reload();
75
+ });
@@ -10,6 +10,7 @@ import {
10
10
  createStaticRouter,
11
11
  } from "react-router-dom/server.js";
12
12
  import "virtual:zudoku-theme.css";
13
+ import "vite/modulepreload-polyfill";
13
14
  import { BootstrapStatic, ServerError } from "zudoku/components";
14
15
  import type { ZudokuConfig } from "../config/config.js";
15
16
  import type { FileWritingResponse } from "../vite/prerender.js";
@@ -1,6 +1,6 @@
1
1
  import { Helmet } from "@zudoku/react-helmet-async";
2
2
  import { PanelLeftIcon } from "lucide-react";
3
- import { Suspense, useEffect, useRef, type ReactNode } from "react";
3
+ import { Suspense, useEffect, useRef, useState, type ReactNode } from "react";
4
4
  import { Outlet, useLocation, useNavigation } from "react-router-dom";
5
5
  import { useSpinDelay } from "spin-delay";
6
6
  import { Drawer, DrawerTrigger } from "../ui/Drawer.js";
@@ -49,6 +49,7 @@ export const Layout = ({ children }: { children?: ReactNode }) => {
49
49
  delay: 300,
50
50
  minDuration: 500,
51
51
  });
52
+ const [isDrawerOpen, setDrawerOpen] = useState(false);
52
53
 
53
54
  return (
54
55
  <>
@@ -70,8 +71,12 @@ export const Layout = ({ children }: { children?: ReactNode }) => {
70
71
  <LoadingFallback />
71
72
  ) : (
72
73
  <Suspense fallback={<LoadingFallback />}>
73
- <Drawer direction="left">
74
- <Sidebar />
74
+ <Drawer
75
+ direction="left"
76
+ open={isDrawerOpen}
77
+ onOpenChange={(open) => setDrawerOpen(open)}
78
+ >
79
+ <Sidebar onRequestClose={() => setDrawerOpen(false)} />
75
80
  <div
76
81
  className={cn(
77
82
  "lg:hidden -mx-10 px-10 py-2 sticky bg-background/80 backdrop-blur z-10 top-0 left-0 right-0 border-b",
@@ -30,27 +30,27 @@ export const MobileTopNavigation = () => {
30
30
  </DrawerTrigger>
31
31
  </div>
32
32
  <DrawerContent
33
- className="lg:hidden h-screen right-0 left-auto w-[320px] rounded-none overflow-auto"
33
+ className="lg:hidden h-[100dvh] right-0 left-auto w-[320px] rounded-none"
34
34
  aria-describedby={undefined}
35
35
  >
36
- <VisuallyHidden>
37
- <DrawerTitle>Navigation</DrawerTitle>
38
- </VisuallyHidden>
39
- <div className="flex p-4">
40
- <Search />
41
- </div>
42
- <ul className="flex flex-col items-center gap-4 p-4">
43
- <li>
44
- <ThemeSwitch />
45
- </li>
46
- {topNavigation.filter(isHiddenItem(isAuthenticated)).map((item) => (
47
- <li key={item.label}>
48
- <button onClick={() => setDrawerOpen(false)}>
49
- <TopNavItem {...item} />
50
- </button>
36
+ <div className="p-6 overflow-y-auto overscroll-none">
37
+ <VisuallyHidden>
38
+ <DrawerTitle>Navigation</DrawerTitle>
39
+ </VisuallyHidden>
40
+ <Search className="flex p-4" />
41
+ <ul className="flex flex-col items-center gap-4 p-4">
42
+ <li>
43
+ <ThemeSwitch />
51
44
  </li>
52
- ))}
53
- </ul>
45
+ {topNavigation.filter(isHiddenItem(isAuthenticated)).map((item) => (
46
+ <li key={item.label}>
47
+ <button onClick={() => setDrawerOpen(false)}>
48
+ <TopNavItem {...item} />
49
+ </button>
50
+ </li>
51
+ ))}
52
+ </ul>
53
+ </div>
54
54
  </DrawerContent>
55
55
  </Drawer>
56
56
  );
@@ -3,7 +3,7 @@ import { Suspense, useCallback, useEffect, useState } from "react";
3
3
  import { isSearchPlugin } from "../core/plugins.js";
4
4
  import { useZudoku } from "./context/ZudokuContext.js";
5
5
 
6
- export const Search = () => {
6
+ export const Search = ({ className }: { className?: string }) => {
7
7
  const ctx = useZudoku();
8
8
  const [isOpen, setIsOpen] = useState(false);
9
9
 
@@ -35,7 +35,7 @@ export const Search = () => {
35
35
  }
36
36
 
37
37
  return (
38
- <>
38
+ <div className={className}>
39
39
  <button
40
40
  type="button"
41
41
  onClick={() => setIsOpen(true)}
@@ -55,6 +55,6 @@ export const Search = () => {
55
55
  onClose,
56
56
  })}
57
57
  </Suspense>
58
- </>
58
+ </div>
59
59
  );
60
60
  };
@@ -7,7 +7,11 @@ import { Slotlet } from "../SlotletProvider.js";
7
7
  import { SidebarItem } from "./SidebarItem.js";
8
8
  import { SidebarWrapper } from "./SidebarWrapper.js";
9
9
 
10
- export const Sidebar = () => {
10
+ export const Sidebar = ({
11
+ onRequestClose,
12
+ }: {
13
+ onRequestClose?: () => void;
14
+ }) => {
11
15
  const navRef = useRef<HTMLDivElement | null>(null);
12
16
  const navigation = useCurrentNavigation();
13
17
 
@@ -24,15 +28,21 @@ export const Sidebar = () => {
24
28
  <Slotlet name="zudoku-after-navigation" />
25
29
  </SidebarWrapper>
26
30
  <DrawerContent
27
- className="lg:hidden h-screen left-0 p-6 w-[320px] rounded-none overflow-auto"
31
+ className="lg:hidden h-[100dvh] left-0 w-[320px] rounded-none"
28
32
  aria-describedby={undefined}
29
33
  >
30
- <VisuallyHidden>
31
- <DrawerTitle>Sidebar</DrawerTitle>
32
- </VisuallyHidden>
33
- {navigation.sidebar.map((item) => (
34
- <SidebarItem key={item.label} item={item} />
35
- ))}
34
+ <div className="p-6 overflow-y-auto overscroll-none">
35
+ <VisuallyHidden>
36
+ <DrawerTitle>Sidebar</DrawerTitle>
37
+ </VisuallyHidden>
38
+ {navigation.sidebar.map((item) => (
39
+ <SidebarItem
40
+ key={item.label}
41
+ item={item}
42
+ onRequestClose={onRequestClose}
43
+ />
44
+ ))}
45
+ </div>
36
46
  </DrawerContent>
37
47
  </>
38
48
  );
@@ -11,9 +11,11 @@ import { useIsCategoryOpen } from "./utils.js";
11
11
  export const SidebarCategory = ({
12
12
  category,
13
13
  level,
14
+ onRequestClose,
14
15
  }: {
15
16
  category: SidebarItemCategory;
16
17
  level: number;
18
+ onRequestClose?: () => void;
17
19
  }) => {
18
20
  const isCategoryOpen = useIsCategoryOpen(category);
19
21
  const [hasInteracted, setHasInteracted] = useState(false);
@@ -126,6 +128,7 @@ export const SidebarCategory = ({
126
128
  ("href" in item ? item.href : "") +
127
129
  item.label
128
130
  }
131
+ onRequestClose={onRequestClose}
129
132
  level={level + 1}
130
133
  item={item}
131
134
  />
@@ -37,17 +37,24 @@ export const DATA_ANCHOR_ATTR = "data-anchor";
37
37
  export const SidebarItem = ({
38
38
  item,
39
39
  level = 0,
40
+ onRequestClose,
40
41
  }: {
41
42
  item: SidebarItemType;
42
- basePath?: string;
43
43
  level?: number;
44
+ onRequestClose?: () => void;
44
45
  }) => {
45
46
  const { activeAnchor } = useViewportAnchor();
46
47
  const [searchParams] = useSearchParams();
47
48
 
48
49
  switch (item.type) {
49
50
  case "category":
50
- return <SidebarCategory category={item} level={level} />;
51
+ return (
52
+ <SidebarCategory
53
+ category={item}
54
+ level={level}
55
+ onRequestClose={onRequestClose}
56
+ />
57
+ );
51
58
  case "doc":
52
59
  return (
53
60
  <NavLink
@@ -55,6 +62,7 @@ export const SidebarItem = ({
55
62
  navigationListItem({ isActive, isTopLevel: level === 0 })
56
63
  }
57
64
  to={joinPath(item.id)}
65
+ onClick={onRequestClose}
58
66
  >
59
67
  {item.icon && <item.icon size={16} className="align-[-0.125em]" />}
60
68
  {item.badge ? (
@@ -79,6 +87,7 @@ export const SidebarItem = ({
79
87
  isTopLevel: level === 0,
80
88
  className: item.badge?.placement !== "start" && "justify-between",
81
89
  })}
90
+ onClick={onRequestClose}
82
91
  >
83
92
  {item.badge ? (
84
93
  <>
@@ -115,6 +124,7 @@ export const SidebarItem = ({
115
124
  href={item.href}
116
125
  target="_blank"
117
126
  rel="noopener noreferrer"
127
+ onClick={onRequestClose}
118
128
  >
119
129
  <span className="whitespace-normal">{item.label}</span>
120
130
  {/* This prevents that the icon would be positioned in its own line if the text fills a line entirely */}
@@ -390,6 +390,10 @@ const Schema = builder.objectRef<OpenAPIDocument>("Schema").implement({
390
390
  resolve: (root) => root.info.description,
391
391
  nullable: true,
392
392
  }),
393
+ summary: t.string({
394
+ resolve: (root) => root.info.summary,
395
+ nullable: true,
396
+ }),
393
397
  paths: t.field({
394
398
  type: [PathItem],
395
399
  resolve: (root) =>
@@ -42,6 +42,7 @@ export const MdxPage = ({
42
42
  frontmatter = {},
43
43
  defaultOptions,
44
44
  tableOfContents,
45
+ excerpt,
45
46
  }: PropsWithChildren<
46
47
  Omit<MDXImport, "default"> & {
47
48
  file: string;
@@ -85,6 +86,7 @@ export const MdxPage = ({
85
86
  <div className="xl:grid grid-cols-[--sidecar-grid-cols] gap-8 justify-between">
86
87
  <Helmet>
87
88
  <title>{pageTitle}</title>
89
+ {excerpt && <meta name="description" content={excerpt} />}
88
90
  </Helmet>
89
91
  <div
90
92
  className={cn(
@@ -24,6 +24,7 @@ export type Frontmatter = {
24
24
  export type MDXImport = {
25
25
  tableOfContents: Toc;
26
26
  frontmatter: Frontmatter;
27
+ excerpt?: string;
27
28
  default: (props: MDXProps) => JSX.Element;
28
29
  };
29
30
 
@@ -1,5 +1,6 @@
1
1
  import { ResultOf } from "@graphql-typed-document-node/core";
2
2
  import { useSuspenseQuery } from "@tanstack/react-query";
3
+ import { Helmet } from "@zudoku/react-helmet-async";
3
4
  import { CategoryHeading } from "../../components/CategoryHeading.js";
4
5
  import { Heading } from "../../components/Heading.js";
5
6
  import { Markdown, ProseClasses } from "../../components/Markdown.js";
@@ -81,6 +82,7 @@ const AllOperationsQuery = graphql(/* GraphQL */ `
81
82
  query AllOperations($input: JSON!, $type: SchemaType!) {
82
83
  schema(input: $input, type: $type) {
83
84
  description
85
+ summary
84
86
  title
85
87
  url
86
88
  version
@@ -96,19 +98,77 @@ const AllOperationsQuery = graphql(/* GraphQL */ `
96
98
  }
97
99
  `);
98
100
 
101
+ /**
102
+ * @description Clean up a commonmark formatted description for use in the meta
103
+ * description.
104
+ */
105
+ function cleanDescription(
106
+ description: string,
107
+ maxLength: number = 160,
108
+ ): string {
109
+ if (!description) {
110
+ return "";
111
+ }
112
+
113
+ // Replace Markdown links [text](url) with just "text"
114
+ description = description.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1");
115
+
116
+ // Remove Markdown image syntax: ![alt](url)
117
+ description = description.replace(/!\[.*?\]\(.*?\)/g, "");
118
+
119
+ // Remove other Markdown syntax (e.g., **bold**, _italic_, `code`)
120
+ description = description.replace(/[_*`~]/g, "");
121
+
122
+ // Remove headings (# Heading), blockquotes (> Quote), and horizontal rules (--- or ***)
123
+ description = description.replace(/^(?:>|\s*#+|-{3,}|\*{3,})/gm, "");
124
+
125
+ // Remove any remaining formatting characters
126
+ description = description.replace(/[|>{}[\]]/g, "");
127
+
128
+ // Collapse multiple spaces and trim the text
129
+ description = description.replace(/\s+/g, " ").trim();
130
+
131
+ // Limit to the specified maximum length
132
+ description = description.substring(0, maxLength);
133
+
134
+ // Escape for HTML safety
135
+ return description
136
+ .replace(/&/g, "&amp;")
137
+ .replace(/</g, "&lt;")
138
+ .replace(/>/g, "&gt;")
139
+ .replace(/"/g, "&quot;")
140
+ .replace(/'/g, "&#039;");
141
+ }
142
+
99
143
  export const OperationList = () => {
100
144
  const { input, type } = useOasConfig();
101
145
  const query = useCreateQuery(AllOperationsQuery, { input, type });
102
146
  const result = useSuspenseQuery(query);
103
-
147
+ const title = result.data.schema.title;
148
+ const summary = result.data.schema.summary;
149
+ const description = result.data.schema.description;
150
+ // The summary property is preferable here as it is a short description of
151
+ // the API, whereas the description property is typically longer and supports
152
+ // commonmark formatting, making it ill-suited for use in the meta description
153
+ const metaDescription = summary
154
+ ? summary
155
+ : description
156
+ ? cleanDescription(description)
157
+ : undefined;
104
158
  return (
105
159
  <div className="pt-[--padding-content-top]">
160
+ <Helmet>
161
+ <title>{title}</title>
162
+ {metaDescription && (
163
+ <meta name="description" content={metaDescription} />
164
+ )}
165
+ </Helmet>
106
166
  <div
107
167
  className={cn(ProseClasses, "mb-16 max-w-full prose-img:max-w-prose")}
108
168
  >
109
169
  <CategoryHeading>Overview</CategoryHeading>
110
170
  <Heading level={1} id="description" registerSidebarAnchor>
111
- {result.data.schema.title}
171
+ {title}
112
172
  </Heading>
113
173
  <Markdown content={result.data.schema.description ?? ""} />
114
174
  </div>
@@ -17,7 +17,7 @@ const documents = {
17
17
  types.ServersQueryDocument,
18
18
  "\n fragment OperationsFragment on OperationItem {\n slug\n summary\n method\n description\n operationId\n contentTypes\n path\n parameters {\n name\n in\n description\n required\n schema\n style\n examples {\n name\n description\n externalValue\n value\n summary\n }\n }\n requestBody {\n content {\n mediaType\n encoding {\n name\n }\n examples {\n name\n description\n externalValue\n value\n summary\n }\n schema\n }\n description\n required\n }\n responses {\n statusCode\n links\n description\n content {\n examples {\n name\n description\n externalValue\n value\n summary\n }\n mediaType\n encoding {\n name\n }\n schema\n }\n }\n }\n":
19
19
  types.OperationsFragmentFragmentDoc,
20
- "\n query AllOperations($input: JSON!, $type: SchemaType!) {\n schema(input: $input, type: $type) {\n description\n title\n url\n version\n tags {\n name\n description\n operations {\n slug\n ...OperationsFragment\n }\n }\n }\n }\n":
20
+ "\n query AllOperations($input: JSON!, $type: SchemaType!) {\n schema(input: $input, type: $type) {\n description\n summary\n title\n url\n version\n tags {\n name\n description\n operations {\n slug\n ...OperationsFragment\n }\n }\n }\n }\n":
21
21
  types.AllOperationsDocument,
22
22
  "\n query getServerQuery($input: JSON!, $type: SchemaType!) {\n schema(input: $input, type: $type) {\n url\n servers {\n url\n }\n }\n }\n":
23
23
  types.GetServerQueryDocument,
@@ -41,7 +41,7 @@ export function graphql(
41
41
  * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
42
42
  */
43
43
  export function graphql(
44
- source: "\n query AllOperations($input: JSON!, $type: SchemaType!) {\n schema(input: $input, type: $type) {\n description\n title\n url\n version\n tags {\n name\n description\n operations {\n slug\n ...OperationsFragment\n }\n }\n }\n }\n",
44
+ source: "\n query AllOperations($input: JSON!, $type: SchemaType!) {\n schema(input: $input, type: $type) {\n description\n summary\n title\n url\n version\n tags {\n name\n description\n operations {\n slug\n ...OperationsFragment\n }\n }\n }\n }\n",
45
45
  ): typeof import("./graphql.js").AllOperationsDocument;
46
46
  /**
47
47
  * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
@@ -136,6 +136,7 @@ export type Schema = {
136
136
  title: Scalars["String"]["output"];
137
137
  url: Scalars["String"]["output"];
138
138
  version: Scalars["String"]["output"];
139
+ summary?: Maybe<Scalars["String"]["output"]>;
139
140
  };
140
141
 
141
142
  export type SchemaOperationsArgs = {
@@ -261,6 +262,7 @@ export type AllOperationsQuery = {
261
262
  schema: {
262
263
  __typename?: "Schema";
263
264
  description?: string | null;
265
+ summary?: string | null;
264
266
  title: string;
265
267
  url: string;
266
268
  version: string;
@@ -419,6 +421,7 @@ export const AllOperationsDocument = new TypedDocumentString(`
419
421
  query AllOperations($input: JSON!, $type: SchemaType!) {
420
422
  schema(input: $input, type: $type) {
421
423
  description
424
+ summary
422
425
  title
423
426
  url
424
427
  version