@ox-content/vite-plugin 2.9.0 → 2.11.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/dist/index.mjs CHANGED
@@ -1,8 +1,9 @@
1
1
  import { a as importNapiModuleSync, c as __exportAll, d as __toESM, i as importNapiModule, l as __require, o as __commonJSMin, r as transformMermaidStatic, s as __esmMin, t as mermaidClientScript, u as __toCommonJS } from "./mermaid.mjs";
2
2
  import { i as transformTabs, n as resetTabGroupCounter, t as generateTabsCSS } from "./tabs.mjs";
3
3
  import { n as transformYouTube, t as extractVideoId } from "./youtube.mjs";
4
- import { a as transformGitHub, i as prefetchGitHubRepos, n as fetchRepoData, t as collectGitHubRepos } from "./github.mjs";
4
+ import { c as prefetchGitHubRepos, i as fetchRepoData, l as prefetchGitHubSources, n as collectGitHubSources, o as parseGitHubLineRange, r as fetchGitHubSource, s as parseGitHubPermalink, t as collectGitHubRepos, u as transformGitHub } from "./github.mjs";
5
5
  import { a as transformOgp, i as prefetchOgpData, n as fetchOgpData, t as collectOgpUrls } from "./ogp.mjs";
6
+ import { a as normalizeVitePressFrontmatter, c as mergeThemes, i as generateVitePressMigrationConfig, l as resolveTheme, n as convertVitePressSidebar, o as defaultTheme, r as fromVitePressConfig, s as defineTheme, t as convertVitePressNav, u as themeToNapi } from "./vitepress.mjs";
6
7
  import { createRequire } from "node:module";
7
8
  import * as path$1 from "path";
8
9
  import { unified } from "unified";
@@ -17,6 +18,40 @@ import { glob } from "glob";
17
18
  import * as crypto from "crypto";
18
19
  import * as fs from "node:fs/promises";
19
20
  import { mkdir, writeFile } from "node:fs/promises";
21
+ //#region src/markdown.ts
22
+ const DEFAULT_MARKDOWN_EXTENSIONS = [
23
+ ".md",
24
+ ".markdown",
25
+ ".mdx"
26
+ ];
27
+ function normalizeMarkdownExtensions(extensions) {
28
+ const values = extensions?.length ? extensions : DEFAULT_MARKDOWN_EXTENSIONS;
29
+ const seen = /* @__PURE__ */ new Set();
30
+ const normalized = [];
31
+ for (const extension of values) {
32
+ const value = extension.startsWith(".") ? extension : `.${extension}`;
33
+ const key = value.toLowerCase();
34
+ if (!seen.has(key)) {
35
+ seen.add(key);
36
+ normalized.push(value);
37
+ }
38
+ }
39
+ return normalized;
40
+ }
41
+ function isMarkdownFilePath(filePath, extensions = DEFAULT_MARKDOWN_EXTENSIONS) {
42
+ const pathname = filePath.split("?")[0].split("#")[0].toLowerCase();
43
+ return extensions.some((extension) => pathname.endsWith(extension.toLowerCase()));
44
+ }
45
+ function stripMarkdownExtension(filePath, extensions = DEFAULT_MARKDOWN_EXTENSIONS) {
46
+ const match = [...extensions].sort((left, right) => right.length - left.length).find((extension) => filePath.toLowerCase().endsWith(extension.toLowerCase()));
47
+ return match ? filePath.slice(0, -match.length) : filePath;
48
+ }
49
+ function markdownGlobPattern(srcDir, extensions) {
50
+ const suffixes = extensions.map((extension) => extension.replace(/^\./, ""));
51
+ if (suffixes.length === 1) return path$1.join(srcDir, `**/*.${suffixes[0]}`);
52
+ return path$1.join(srcDir, `**/*.{${suffixes.join(",")}}`);
53
+ }
54
+ //#endregion
20
55
  //#region src/environment.ts
21
56
  /**
22
57
  * Creates the Markdown processing environment configuration.
@@ -49,7 +84,7 @@ function createMarkdownEnvironment(options) {
49
84
  rollupOptions: { external: [/^node:/, /\.node$/] }
50
85
  },
51
86
  resolve: {
52
- extensions: [".md", ".markdown"],
87
+ extensions: options.extensions,
53
88
  conditions: [
54
89
  "markdown",
55
90
  "node",
@@ -6797,6 +6832,58 @@ async function highlightCode(html, theme = "github-dark", langs = []) {
6797
6832
  return String(result);
6798
6833
  }
6799
6834
  //#endregion
6835
+ //#region src/plugins/index.ts
6836
+ /**
6837
+ * Transform all enabled plugins in HTML content.
6838
+ */
6839
+ async function transformAllPlugins(html, options = {}) {
6840
+ const { tabs = true, youtube = true, github = true, ogp, openGraph, mermaid = true, githubToken } = options;
6841
+ let result = html;
6842
+ const ogpOptions = openGraph ?? ogp ?? true;
6843
+ if (tabs) {
6844
+ const { transformTabs } = await import("./tabs.mjs").then((n) => n.r);
6845
+ result = await transformTabs(result);
6846
+ }
6847
+ if (youtube) {
6848
+ const { transformYouTube } = await import("./youtube.mjs").then((n) => n.r);
6849
+ result = await transformYouTube(result);
6850
+ }
6851
+ if (github !== false) {
6852
+ const { transformGitHub } = await import("./github.mjs").then((n) => n.a);
6853
+ result = await transformGitHub(result, void 0, {
6854
+ token: githubToken,
6855
+ ...typeof github === "object" ? github : {}
6856
+ });
6857
+ }
6858
+ if (ogpOptions !== false) {
6859
+ const { transformOgp } = await import("./ogp.mjs").then((n) => n.r);
6860
+ result = await transformOgp(result, void 0, typeof ogpOptions === "object" ? ogpOptions : {});
6861
+ }
6862
+ if (mermaid) {
6863
+ const { transformMermaidStatic } = await import("./mermaid.mjs").then((n) => n.n);
6864
+ result = await transformMermaidStatic(result);
6865
+ }
6866
+ return result;
6867
+ }
6868
+ /**
6869
+ * Transform built-in embed components in HTML content.
6870
+ */
6871
+ async function transformBuiltinEmbeds(html, options) {
6872
+ let result = html;
6873
+ if (options.github) {
6874
+ const { transformGitHub } = await import("./github.mjs").then((n) => n.a);
6875
+ result = await transformGitHub(result, void 0, {
6876
+ token: process.env.GITHUB_TOKEN,
6877
+ ...options.github
6878
+ });
6879
+ }
6880
+ if (options.openGraph) {
6881
+ const { transformOgp } = await import("./ogp.mjs").then((n) => n.r);
6882
+ result = await transformOgp(result, void 0, options.openGraph);
6883
+ }
6884
+ return result;
6885
+ }
6886
+ //#endregion
6800
6887
  //#region src/plugins/mermaid-protect.ts
6801
6888
  /**
6802
6889
  * Extract `<div class="ox-mermaid">...</div>` blocks and replace
@@ -6978,6 +7065,10 @@ async function transformMarkdown(source, filePath, options, ssgOptions) {
6978
7065
  const highlightedHtml = await highlightCode(html, options.highlightTheme, options.highlightLangs);
6979
7066
  html = napi.mergeHighlightedCodeBlocks(originalHtml, highlightedHtml);
6980
7067
  }
7068
+ html = await transformBuiltinEmbeds(html, options.embeds ?? {
7069
+ github: {},
7070
+ openGraph: {}
7071
+ });
6981
7072
  html = restoreMermaidSvgs(html, svgs);
6982
7073
  return {
6983
7074
  code: generateModuleCode(html, frontmatter, toc, filePath, options),
@@ -8577,36 +8668,6 @@ async function renderSinglePage(entry, templateFn, templateSource, options, cach
8577
8668
  }
8578
8669
  }
8579
8670
  //#endregion
8580
- //#region src/plugins/index.ts
8581
- /**
8582
- * Transform all enabled plugins in HTML content.
8583
- */
8584
- async function transformAllPlugins(html, options = {}) {
8585
- const { tabs = true, youtube = true, github = true, ogp = true, mermaid = true, githubToken } = options;
8586
- let result = html;
8587
- if (tabs) {
8588
- const { transformTabs } = await import("./tabs.mjs").then((n) => n.r);
8589
- result = await transformTabs(result);
8590
- }
8591
- if (youtube) {
8592
- const { transformYouTube } = await import("./youtube.mjs").then((n) => n.r);
8593
- result = await transformYouTube(result);
8594
- }
8595
- if (github) {
8596
- const { transformGitHub } = await import("./github.mjs").then((n) => n.r);
8597
- result = await transformGitHub(result, void 0, { token: githubToken });
8598
- }
8599
- if (ogp) {
8600
- const { transformOgp } = await import("./ogp.mjs").then((n) => n.r);
8601
- result = await transformOgp(result);
8602
- }
8603
- if (mermaid) {
8604
- const { transformMermaidStatic } = await import("./mermaid.mjs").then((n) => n.n);
8605
- result = await transformMermaidStatic(result);
8606
- }
8607
- return result;
8608
- }
8609
- //#endregion
8610
8671
  //#region src/island/parse.ts
8611
8672
  /**
8612
8673
  * Island Parser
@@ -8798,213 +8859,6 @@ initIslands((el, props) => {
8798
8859
  `;
8799
8860
  }
8800
8861
  //#endregion
8801
- //#region src/theme.ts
8802
- /**
8803
- * Default theme configuration.
8804
- * Based on the current ox-content SSG styles.
8805
- */
8806
- const defaultTheme = {
8807
- name: "default",
8808
- colors: {
8809
- primary: "#4f6fae",
8810
- primaryHover: "#425f96",
8811
- background: "#ffffff",
8812
- backgroundAlt: "#f5f7fb",
8813
- text: "#131a30",
8814
- textMuted: "#4f607b",
8815
- border: "#d2dbea",
8816
- codeBackground: "#101a31",
8817
- codeText: "#edf3ff"
8818
- },
8819
- darkColors: {
8820
- primary: "#86a4da",
8821
- primaryHover: "#a3bbe8",
8822
- background: "#060816",
8823
- backgroundAlt: "#0d1528",
8824
- text: "#ebf2ff",
8825
- textMuted: "#8ea0bf",
8826
- border: "#223252",
8827
- codeBackground: "#0a1020",
8828
- codeText: "#e7f0ff"
8829
- },
8830
- fonts: {
8831
- sans: "\"IBM Plex Sans\", \"Avenir Next\", \"Segoe UI Variable\", \"Segoe UI\", sans-serif",
8832
- mono: "\"IBM Plex Mono\", \"SFMono-Regular\", Consolas, monospace"
8833
- },
8834
- entryPage: { mode: "default" },
8835
- layout: {
8836
- sidebarWidth: "260px",
8837
- headerHeight: "60px",
8838
- maxContentWidth: "960px"
8839
- },
8840
- header: {
8841
- logo: void 0,
8842
- logoLight: void 0,
8843
- logoDark: void 0,
8844
- showSiteNameText: true,
8845
- logoWidth: 28,
8846
- logoHeight: 28
8847
- },
8848
- footer: {
8849
- message: void 0,
8850
- copyright: void 0
8851
- },
8852
- socialLinks: {},
8853
- embed: {},
8854
- css: "",
8855
- js: ""
8856
- };
8857
- /**
8858
- * Deep merge two objects.
8859
- */
8860
- function deepMerge(target, source) {
8861
- const result = { ...target };
8862
- for (const key of Object.keys(source)) {
8863
- const sourceValue = source[key];
8864
- const targetValue = target[key];
8865
- if (sourceValue !== void 0 && typeof sourceValue === "object" && sourceValue !== null && !Array.isArray(sourceValue) && typeof targetValue === "object" && targetValue !== null && !Array.isArray(targetValue)) result[key] = deepMerge(targetValue, sourceValue);
8866
- else if (sourceValue !== void 0) result[key] = sourceValue;
8867
- }
8868
- return result;
8869
- }
8870
- /**
8871
- * Defines a theme configuration with type checking.
8872
- *
8873
- * @example
8874
- * ```ts
8875
- * const myTheme = defineTheme({
8876
- * extends: defaultTheme,
8877
- * colors: {
8878
- * primary: '#3498db',
8879
- * },
8880
- * footer: {
8881
- * copyright: '2025 My Company',
8882
- * },
8883
- * });
8884
- * ```
8885
- */
8886
- function defineTheme(config) {
8887
- return config;
8888
- }
8889
- /**
8890
- * Merges multiple theme configurations.
8891
- * Later themes override earlier ones.
8892
- *
8893
- * @example
8894
- * ```ts
8895
- * const merged = mergeThemes(defaultTheme, customTheme, overrides);
8896
- * ```
8897
- */
8898
- function mergeThemes(...themes) {
8899
- if (themes.length === 0) return { ...defaultTheme };
8900
- let result = {};
8901
- for (const theme of themes) result = deepMerge(result, theme);
8902
- return result;
8903
- }
8904
- /**
8905
- * Resolves a theme configuration by merging with its extends chain and defaults.
8906
- */
8907
- function resolveTheme(config) {
8908
- if (!config) return resolveTheme(defaultTheme);
8909
- const chain = [];
8910
- let current = config;
8911
- while (current) {
8912
- chain.unshift(current);
8913
- current = current.extends;
8914
- }
8915
- if (chain[0] !== defaultTheme && chain[0]?.name !== "default") chain.unshift(defaultTheme);
8916
- const merged = mergeThemes(...chain);
8917
- return {
8918
- name: merged.name ?? "custom",
8919
- colors: merged.colors ?? defaultTheme.colors,
8920
- darkColors: merged.darkColors ?? defaultTheme.darkColors,
8921
- fonts: merged.fonts ?? defaultTheme.fonts,
8922
- entryPage: merged.entryPage ?? defaultTheme.entryPage,
8923
- layout: merged.layout ?? defaultTheme.layout,
8924
- header: merged.header ?? defaultTheme.header,
8925
- footer: merged.footer ?? defaultTheme.footer,
8926
- socialLinks: merged.socialLinks ?? defaultTheme.socialLinks,
8927
- sidebar: merged.sidebar ?? [],
8928
- embed: merged.embed ?? {},
8929
- css: merged.css ?? "",
8930
- js: merged.js ?? ""
8931
- };
8932
- }
8933
- /**
8934
- * Converts resolved theme to the format expected by Rust NAPI.
8935
- */
8936
- function themeToNapi(theme) {
8937
- const socialLinks = socialLinksToNapi(theme.socialLinks);
8938
- return {
8939
- colors: theme.colors.primary ? {
8940
- primary: theme.colors.primary,
8941
- primaryHover: theme.colors.primaryHover,
8942
- background: theme.colors.background,
8943
- backgroundAlt: theme.colors.backgroundAlt,
8944
- text: theme.colors.text,
8945
- textMuted: theme.colors.textMuted,
8946
- border: theme.colors.border,
8947
- codeBackground: theme.colors.codeBackground,
8948
- codeText: theme.colors.codeText
8949
- } : void 0,
8950
- darkColors: theme.darkColors.primary ? {
8951
- primary: theme.darkColors.primary,
8952
- primaryHover: theme.darkColors.primaryHover,
8953
- background: theme.darkColors.background,
8954
- backgroundAlt: theme.darkColors.backgroundAlt,
8955
- text: theme.darkColors.text,
8956
- textMuted: theme.darkColors.textMuted,
8957
- border: theme.darkColors.border,
8958
- codeBackground: theme.darkColors.codeBackground,
8959
- codeText: theme.darkColors.codeText
8960
- } : void 0,
8961
- fonts: theme.fonts.sans ? {
8962
- sans: theme.fonts.sans,
8963
- mono: theme.fonts.mono
8964
- } : void 0,
8965
- entryPage: theme.entryPage.mode ? { mode: theme.entryPage.mode } : void 0,
8966
- layout: theme.layout.sidebarWidth ? {
8967
- sidebarWidth: theme.layout.sidebarWidth,
8968
- headerHeight: theme.layout.headerHeight,
8969
- maxContentWidth: theme.layout.maxContentWidth
8970
- } : void 0,
8971
- header: theme.header.logo || theme.header.logoLight || theme.header.logoDark ? {
8972
- logo: theme.header.logo,
8973
- logoLight: theme.header.logoLight,
8974
- logoDark: theme.header.logoDark,
8975
- showSiteNameText: theme.header.showSiteNameText,
8976
- logoWidth: theme.header.logoWidth,
8977
- logoHeight: theme.header.logoHeight
8978
- } : void 0,
8979
- footer: theme.footer.message || theme.footer.copyright ? {
8980
- message: theme.footer.message,
8981
- copyright: theme.footer.copyright
8982
- } : void 0,
8983
- socialLinks,
8984
- embed: Object.keys(theme.embed).length > 0 ? theme.embed : void 0,
8985
- css: theme.css || void 0,
8986
- js: theme.js || void 0
8987
- };
8988
- }
8989
- function socialLinksToNapi(links) {
8990
- if (Array.isArray(links)) {
8991
- const items = links.map((item) => {
8992
- return {
8993
- icon: typeof item.icon === "string" ? item.icon : void 0,
8994
- iconSvg: typeof item.icon === "object" ? item.icon.svg : void 0,
8995
- link: item.link,
8996
- ariaLabel: item.ariaLabel
8997
- };
8998
- });
8999
- return items.length > 0 ? { links: items } : void 0;
9000
- }
9001
- return links.github || links.twitter || links.discord ? {
9002
- github: links.github,
9003
- twitter: links.twitter,
9004
- discord: links.discord
9005
- } : void 0;
9006
- }
9007
- //#endregion
9008
8862
  //#region src/ssg.ts
9009
8863
  /**
9010
8864
  * SSG (Static Site Generation) module for ox-content
@@ -10381,7 +10235,8 @@ function resolveSsgOptions(ssg) {
10381
10235
  generateOgImage: ssg.generateOgImage ?? false,
10382
10236
  lastUpdated: ssg.lastUpdated ?? false,
10383
10237
  siteUrl: ssg.siteUrl,
10384
- theme: resolveTheme(ssg.theme)
10238
+ theme: resolveTheme(ssg.theme),
10239
+ navigation: ssg.navigation
10385
10240
  };
10386
10241
  }
10387
10242
  /**
@@ -10512,6 +10367,59 @@ async function externalizeSharedPageAssets(pages, outDir, base) {
10512
10367
  function getUrlPath$1(inputPath, srcDir) {
10513
10368
  return importNapiModuleSync().getSsgUrlPath(inputPath, srcDir);
10514
10369
  }
10370
+ function isExternalHref(value) {
10371
+ return /^[a-z][a-z0-9+.-]*:/i.test(value) || value.startsWith("//");
10372
+ }
10373
+ function splitHrefSuffix(value) {
10374
+ const match = /^([^?#]*)([?#].*)?$/.exec(value);
10375
+ return {
10376
+ pathname: match?.[1] ?? value,
10377
+ suffix: match?.[2] ?? ""
10378
+ };
10379
+ }
10380
+ function normalizeNavigationPath(value) {
10381
+ const { pathname, suffix } = splitHrefSuffix(value.trim());
10382
+ let normalized = pathname || "/";
10383
+ if (!normalized.startsWith("/")) normalized = `/${normalized}`;
10384
+ normalized = normalized.replace(/\/index(?:\.(?:html?|md|markdown))?$/i, "/").replace(/\.(?:html?|md|markdown)$/i, "");
10385
+ if (normalized !== "/") normalized = normalized.replace(/\/+$/, "");
10386
+ return {
10387
+ path: normalized || "/",
10388
+ suffix
10389
+ };
10390
+ }
10391
+ function buildHrefFromNavigationPath(pathname, base, extension) {
10392
+ if (pathname === "/" || pathname === "") return `${base}index${extension}`;
10393
+ return `${base}${pathname.replace(/^\/+/, "")}/index${extension}`;
10394
+ }
10395
+ /**
10396
+ * Resolves manual navigation config to the format used by the built-in SSG renderer.
10397
+ */
10398
+ function resolveNavigationGroups(navigation, base, extension) {
10399
+ if (!navigation) return;
10400
+ return navigation.map((group) => ({
10401
+ title: group.title,
10402
+ items: group.items.flatMap((item) => {
10403
+ const rawHref = item.href ?? item.path;
10404
+ if (!rawHref) return [];
10405
+ if (isExternalHref(rawHref) || rawHref.startsWith("#")) return [{
10406
+ title: item.title,
10407
+ path: item.path ?? rawHref,
10408
+ href: rawHref
10409
+ }];
10410
+ const { path } = normalizeNavigationPath(item.path ?? rawHref);
10411
+ const href = item.href ? (() => {
10412
+ const normalized = normalizeNavigationPath(item.href);
10413
+ return `${buildHrefFromNavigationPath(normalized.path, base, extension)}${normalized.suffix}`;
10414
+ })() : buildHrefFromNavigationPath(path, base, extension);
10415
+ return [{
10416
+ title: item.title,
10417
+ path,
10418
+ href
10419
+ }];
10420
+ })
10421
+ }));
10422
+ }
10515
10423
  function getPageLocale(urlPath, i18n) {
10516
10424
  if (!i18n) return void 0;
10517
10425
  return importNapiModuleSync().getSsgPageLocale(urlPath, i18n.defaultLocale, i18n.locales.map((locale) => locale.code)) ?? void 0;
@@ -10528,8 +10436,8 @@ function formatTitle(name) {
10528
10436
  /**
10529
10437
  * Collects all markdown files from the source directory.
10530
10438
  */
10531
- async function collectMarkdownFiles$1(srcDir) {
10532
- return (await glob(path$1.join(srcDir, "**/*.{md,markdown}"), {
10439
+ async function collectMarkdownFiles$1(srcDir, extensions = DEFAULT_MARKDOWN_EXTENSIONS) {
10440
+ return (await glob(markdownGlobPattern(srcDir, extensions), {
10533
10441
  nodir: true,
10534
10442
  ignore: [
10535
10443
  "**/node_modules/**",
@@ -10571,8 +10479,8 @@ async function buildSsg(options, root) {
10571
10479
  force: true
10572
10480
  });
10573
10481
  } catch {}
10574
- const markdownFiles = await collectMarkdownFiles$1(srcDir);
10575
- const navItems = ssgOptions.theme?.sidebar.length ? buildThemeNavItems(ssgOptions.theme.sidebar, base, ssgOptions.extension) : buildNavItems(markdownFiles, srcDir, base, ssgOptions.extension);
10482
+ const markdownFiles = await collectMarkdownFiles$1(srcDir, options.extensions);
10483
+ const navItems = resolveNavigationGroups(ssgOptions.navigation, base, ssgOptions.extension) ?? (ssgOptions.theme?.sidebar.length ? buildThemeNavItems(ssgOptions.theme.sidebar, base, ssgOptions.extension) : buildNavItems(markdownFiles, srcDir, base, ssgOptions.extension));
10576
10484
  let siteName = ssgOptions.siteName ?? "Documentation";
10577
10485
  if (!ssgOptions.siteName) try {
10578
10486
  const pkgPath = path$1.join(root, "package.json");
@@ -10591,22 +10499,23 @@ async function buildSsg(options, root) {
10591
10499
  baseUrl: base,
10592
10500
  sourcePath: inputPath
10593
10501
  });
10502
+ const frontmatter = normalizeVitePressFrontmatter(result.frontmatter);
10594
10503
  let transformedHtml = result.html;
10595
10504
  const { html: protectedHtml, svgs: mermaidSvgs } = protectMermaidSvgs(transformedHtml);
10596
10505
  transformedHtml = protectedHtml;
10597
10506
  const pluginOptions = {
10598
10507
  tabs: true,
10599
10508
  youtube: true,
10600
- github: true,
10601
- ogp: true,
10509
+ github: options.embeds.github,
10510
+ openGraph: options.embeds.openGraph,
10602
10511
  mermaid: true,
10603
10512
  githubToken: process.env.GITHUB_TOKEN
10604
10513
  };
10605
10514
  transformedHtml = await transformAllPlugins(transformedHtml, pluginOptions);
10606
10515
  if (hasIslands(transformedHtml)) transformedHtml = (await transformIslands(transformedHtml)).html;
10607
10516
  transformedHtml = restoreMermaidSvgs(transformedHtml, mermaidSvgs);
10608
- const title = extractTitle$1(transformedHtml, result.frontmatter);
10609
- const description = result.frontmatter.description;
10517
+ const title = extractTitle$1(transformedHtml, frontmatter);
10518
+ const description = frontmatter.description;
10610
10519
  const routePaths = getRoutePaths(inputPath, srcDir, outDir, base, ssgOptions.extension, ssgOptions.siteUrl);
10611
10520
  pageResults.push({
10612
10521
  inputPath,
@@ -10615,11 +10524,11 @@ async function buildSsg(options, root) {
10615
10524
  title,
10616
10525
  description,
10617
10526
  lastUpdated: napi?.getGitLastUpdated(inputPath, root) ?? void 0,
10618
- frontmatter: result.frontmatter,
10527
+ frontmatter,
10619
10528
  toc: result.toc
10620
10529
  });
10621
10530
  if (shouldGenerateOgImages) {
10622
- const { layout: _layout, ...frontmatterRest } = result.frontmatter;
10531
+ const { layout: _layout, ...frontmatterRest } = frontmatter;
10623
10532
  ogImageEntries.push({
10624
10533
  props: {
10625
10534
  ...frontmatterRest,
@@ -10744,7 +10653,7 @@ function resolveSearchOptions(options) {
10744
10653
  /**
10745
10654
  * Collects all Markdown files from a directory.
10746
10655
  */
10747
- async function collectMarkdownFiles(dir) {
10656
+ async function collectMarkdownFiles(dir, extensions = DEFAULT_MARKDOWN_EXTENSIONS) {
10748
10657
  const files = [];
10749
10658
  async function walk(currentDir) {
10750
10659
  try {
@@ -10752,7 +10661,7 @@ async function collectMarkdownFiles(dir) {
10752
10661
  for (const entry of entries) {
10753
10662
  const fullPath = path$1.join(currentDir, entry.name);
10754
10663
  if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules") await walk(fullPath);
10755
- else if (entry.isFile() && entry.name.endsWith(".md")) files.push(fullPath);
10664
+ else if (entry.isFile() && isMarkdownFilePath(entry.name, extensions)) files.push(fullPath);
10756
10665
  }
10757
10666
  } catch {}
10758
10667
  }
@@ -10762,7 +10671,7 @@ async function collectMarkdownFiles(dir) {
10762
10671
  /**
10763
10672
  * Builds the search index from Markdown files.
10764
10673
  */
10765
- async function buildSearchIndex(srcDir, base) {
10674
+ async function buildSearchIndex(srcDir, base, extensions = DEFAULT_MARKDOWN_EXTENSIONS) {
10766
10675
  const napi = await getOxContent();
10767
10676
  if (!napi) return JSON.stringify({
10768
10677
  documents: [],
@@ -10771,13 +10680,12 @@ async function buildSearchIndex(srcDir, base) {
10771
10680
  avg_dl: 0,
10772
10681
  doc_count: 0
10773
10682
  });
10774
- const files = await collectMarkdownFiles(srcDir);
10683
+ const files = await collectMarkdownFiles(srcDir, extensions);
10775
10684
  const documents = [];
10776
10685
  for (const file of files) try {
10777
10686
  const content = await fs$1.readFile(file, "utf-8");
10778
- const relativePath = path$1.relative(srcDir, file);
10779
- const url = base + relativePath.replace(/\.md$/, "").replace(/\\/g, "/");
10780
- const id = relativePath.replace(/\.md$/, "").replace(/\\/g, "/");
10687
+ const id = stripMarkdownExtension(path$1.relative(srcDir, file), extensions).replace(/\\/g, "/");
10688
+ const url = base + id;
10781
10689
  const extractSearchContent = napi.extractSearchContent;
10782
10690
  if (!extractSearchContent) {
10783
10691
  console.warn("[ox-content] Search not available: extractSearchContent not implemented");
@@ -10872,26 +10780,27 @@ function shouldSkip(url) {
10872
10780
  * Resolve a request URL to a markdown file path.
10873
10781
  * Returns null if no matching file exists.
10874
10782
  */
10875
- async function resolveMarkdownFile(url, srcDir) {
10783
+ async function resolveMarkdownFile(url, srcDir, extensions) {
10876
10784
  let pathname = url.split("?")[0].split("#")[0];
10877
10785
  if (pathname.endsWith("/index.html")) pathname = pathname.slice(0, -11) || "/";
10878
10786
  if (pathname !== "/" && pathname.endsWith("/")) pathname = pathname.slice(0, -1);
10879
- let relativePath;
10880
- if (pathname === "/") relativePath = "index.md";
10881
- else relativePath = pathname.slice(1) + ".md";
10882
- const filePath = path$1.join(srcDir, relativePath);
10883
- try {
10884
- await fs$1.access(filePath);
10885
- return filePath;
10886
- } catch {
10887
- const indexPath = path$1.join(srcDir, pathname === "/" ? "" : pathname.slice(1), "index.md");
10787
+ const routePath = pathname === "/" ? "" : pathname.slice(1);
10788
+ const directCandidates = pathname === "/" ? extensions.map((extension) => `index${extension}`) : isMarkdownFilePath(routePath, extensions) ? [routePath] : extensions.map((extension) => `${routePath}${extension}`);
10789
+ for (const relativePath of directCandidates) {
10790
+ const filePath = path$1.join(srcDir, relativePath);
10791
+ try {
10792
+ await fs$1.access(filePath);
10793
+ return filePath;
10794
+ } catch {}
10795
+ }
10796
+ for (const extension of extensions) {
10797
+ const indexPath = path$1.join(srcDir, routePath, `index${extension}`);
10888
10798
  try {
10889
10799
  await fs$1.access(indexPath);
10890
10800
  return indexPath;
10891
- } catch {
10892
- return null;
10893
- }
10801
+ } catch {}
10894
10802
  }
10803
+ return null;
10895
10804
  }
10896
10805
  /**
10897
10806
  * Inject Vite HMR client script into the HTML.
@@ -10946,32 +10855,33 @@ async function renderPage$1(filePath, options, navGroups, siteName, base, root)
10946
10855
  baseUrl: base,
10947
10856
  sourcePath: filePath
10948
10857
  });
10858
+ const frontmatter = normalizeVitePressFrontmatter(result.frontmatter);
10949
10859
  let transformedHtml = result.html;
10950
10860
  const { html: protectedHtml, svgs: mermaidSvgs } = protectMermaidSvgs(transformedHtml);
10951
10861
  transformedHtml = protectedHtml;
10952
10862
  transformedHtml = await transformAllPlugins(transformedHtml, {
10953
10863
  tabs: true,
10954
10864
  youtube: true,
10955
- github: true,
10956
- ogp: true,
10865
+ github: options.embeds.github,
10866
+ openGraph: options.embeds.openGraph,
10957
10867
  mermaid: true,
10958
10868
  githubToken: process.env.GITHUB_TOKEN
10959
10869
  });
10960
10870
  if (hasIslands(transformedHtml)) transformedHtml = (await transformIslands(transformedHtml)).html;
10961
10871
  transformedHtml = restoreMermaidSvgs(transformedHtml, mermaidSvgs);
10962
- const title = extractTitle$1(transformedHtml, result.frontmatter);
10963
- const description = result.frontmatter.description;
10872
+ const title = extractTitle$1(transformedHtml, frontmatter);
10873
+ const description = frontmatter.description;
10964
10874
  let entryPage;
10965
- if (result.frontmatter.layout === "entry") entryPage = {
10966
- hero: result.frontmatter.hero,
10967
- features: result.frontmatter.features
10875
+ if (frontmatter.layout === "entry") entryPage = {
10876
+ hero: frontmatter.hero,
10877
+ features: frontmatter.features
10968
10878
  };
10969
10879
  let html = await generateHtmlPage({
10970
10880
  title,
10971
10881
  description,
10972
10882
  content: transformedHtml,
10973
10883
  toc: result.toc,
10974
- frontmatter: result.frontmatter,
10884
+ frontmatter,
10975
10885
  path: getUrlPath$1(filePath, srcDir),
10976
10886
  href: getUrlPath$1(filePath, srcDir) || "/",
10977
10887
  entryPage
@@ -10991,7 +10901,7 @@ function createDevServerMiddleware(options, root, cache) {
10991
10901
  let routeUrl = url;
10992
10902
  if (base !== "/" && routeUrl.startsWith(base)) routeUrl = "/" + routeUrl.slice(base.length);
10993
10903
  if (shouldSkip(routeUrl)) return next();
10994
- const filePath = await resolveMarkdownFile(routeUrl, srcDir);
10904
+ const filePath = await resolveMarkdownFile(routeUrl, srcDir, options.extensions);
10995
10905
  if (!filePath) return next();
10996
10906
  try {
10997
10907
  const cached = cache.pages.get(filePath);
@@ -11002,7 +10912,10 @@ function createDevServerMiddleware(options, root, cache) {
11002
10912
  return;
11003
10913
  }
11004
10914
  if (!cache.siteName) cache.siteName = await resolveSiteName(options, root);
11005
- if (!cache.navGroups) cache.navGroups = buildNavItems(await collectMarkdownFiles$1(srcDir), srcDir, base, ".html");
10915
+ if (!cache.navGroups) {
10916
+ const markdownFiles = await collectMarkdownFiles$1(srcDir, options.extensions);
10917
+ cache.navGroups = resolveNavigationGroups(options.ssg.navigation, base, options.ssg.extension) ?? (options.ssg.theme?.sidebar.length ? buildThemeNavItems(options.ssg.theme.sidebar, base, options.ssg.extension) : buildNavItems(markdownFiles, srcDir, base, options.ssg.extension));
10918
+ }
11006
10919
  const html = await renderPage$1(filePath, options, cache.navGroups, cache.siteName, base, root);
11007
10920
  cache.pages.set(filePath, html);
11008
10921
  res.setHeader("Content-Type", "text/html");
@@ -11047,9 +10960,9 @@ function extractTitle(content, frontmatter) {
11047
10960
  const match = content.match(/^#\s+(.+)$/m);
11048
10961
  return match ? match[1].trim() : "";
11049
10962
  }
11050
- function getUrlPath(filePath, srcDir) {
10963
+ function getUrlPath(filePath, srcDir, extensions) {
11051
10964
  let rel = path$1.relative(srcDir, filePath).replace(/\\/g, "/");
11052
- rel = rel.replace(/\.md$/, "");
10965
+ rel = stripMarkdownExtension(rel, extensions);
11053
10966
  if (rel === "index") return "/";
11054
10967
  if (rel.endsWith("/index")) rel = rel.slice(0, -6);
11055
10968
  return "/" + rel;
@@ -11089,21 +11002,18 @@ function validatePage(page, options) {
11089
11002
  }
11090
11003
  async function collectPages(options, root) {
11091
11004
  const srcDir = path$1.resolve(root, options.srcDir);
11092
- const files = await glob("**/*.md", {
11093
- cwd: srcDir,
11094
- absolute: true
11095
- });
11005
+ const files = await glob(markdownGlobPattern(srcDir, options.extensions), { absolute: true });
11096
11006
  const pages = [];
11097
11007
  const generateOgImage = options.ogImage || options.ssg.generateOgImage;
11098
11008
  for (const file of files.sort()) {
11099
11009
  const content = fs$2.readFileSync(file, "utf-8");
11100
- const frontmatter = parseFrontmatter(content);
11010
+ const frontmatter = normalizeVitePressFrontmatter(parseFrontmatter(content));
11101
11011
  if (frontmatter.layout === "entry") continue;
11102
11012
  const title = extractTitle(content, frontmatter);
11103
11013
  const description = typeof frontmatter.description === "string" ? frontmatter.description : "";
11104
11014
  const author = typeof frontmatter.author === "string" ? frontmatter.author : "";
11105
11015
  const tags = Array.isArray(frontmatter.tags) ? frontmatter.tags : typeof frontmatter.tags === "string" ? [frontmatter.tags] : [];
11106
- const urlPath = getUrlPath(file, srcDir);
11016
+ const urlPath = getUrlPath(file, srcDir, options.extensions);
11107
11017
  const ogImageUrl = computeOgImageUrl(urlPath, options.base, options.ssg.siteUrl, generateOgImage, options.ssg.ogImage);
11108
11018
  const page = {
11109
11019
  path: path$1.relative(srcDir, file),
@@ -11752,7 +11662,11 @@ function sortDiagnostics(diagnostics) {
11752
11662
  }
11753
11663
  //#endregion
11754
11664
  //#region src/lint-files.ts
11755
- const DEFAULT_LINT_FILE_INCLUDE = ["**/*.md", "**/*.markdown"];
11665
+ const DEFAULT_LINT_FILE_INCLUDE = [
11666
+ "**/*.md",
11667
+ "**/*.markdown",
11668
+ "**/*.mdx"
11669
+ ];
11756
11670
  const DEFAULT_LINT_FILE_EXCLUDE = [
11757
11671
  "**/node_modules/**",
11758
11672
  "**/.git/**",
@@ -12459,13 +12373,13 @@ function oxContent(options = {}) {
12459
12373
  configureServer(devServer) {
12460
12374
  devServer.middlewares.use(async (req, res, next) => {
12461
12375
  const url = req.url;
12462
- if (!url || !url.endsWith(".md")) return next();
12376
+ if (!url || !isMarkdownFilePath(url, resolvedOptions.extensions)) return next();
12463
12377
  next();
12464
12378
  });
12465
12379
  },
12466
12380
  resolveId(id) {
12467
12381
  if (id.startsWith("virtual:ox-content/")) return "\0" + id;
12468
- if (id.endsWith(".md")) return id;
12382
+ if (isMarkdownFilePath(id, resolvedOptions.extensions)) return id;
12469
12383
  return null;
12470
12384
  },
12471
12385
  async load(id) {
@@ -12473,14 +12387,14 @@ function oxContent(options = {}) {
12473
12387
  return null;
12474
12388
  },
12475
12389
  async transform(code, id) {
12476
- if (!id.endsWith(".md")) return null;
12390
+ if (!isMarkdownFilePath(id, resolvedOptions.extensions)) return null;
12477
12391
  return {
12478
12392
  code: (await transformMarkdown(code, id, resolvedOptions)).code,
12479
12393
  map: null
12480
12394
  };
12481
12395
  },
12482
12396
  async handleHotUpdate({ file, server }) {
12483
- if (file.endsWith(".md")) {
12397
+ if (isMarkdownFilePath(file, resolvedOptions.extensions)) {
12484
12398
  server.ws.send({
12485
12399
  type: "custom",
12486
12400
  event: "ox-content:update",
@@ -12533,7 +12447,7 @@ function oxContent(options = {}) {
12533
12447
  const srcDir = path$1.resolve(root, resolvedOptions.srcDir);
12534
12448
  devServer.middlewares.use(createDevServerMiddleware(resolvedOptions, root, ssgDevCache));
12535
12449
  devServer.watcher.on("add", (file) => {
12536
- if (file.startsWith(srcDir) && file.endsWith(".md")) {
12450
+ if (file.startsWith(srcDir) && isMarkdownFilePath(file, resolvedOptions.extensions)) {
12537
12451
  invalidateNavCache(ssgDevCache);
12538
12452
  devServer.ws.send({
12539
12453
  type: "custom",
@@ -12546,7 +12460,7 @@ function oxContent(options = {}) {
12546
12460
  }
12547
12461
  });
12548
12462
  devServer.watcher.on("unlink", (file) => {
12549
- if (file.startsWith(srcDir) && file.endsWith(".md")) {
12463
+ if (file.startsWith(srcDir) && isMarkdownFilePath(file, resolvedOptions.extensions)) {
12550
12464
  invalidateNavCache(ssgDevCache);
12551
12465
  devServer.ws.send({
12552
12466
  type: "custom",
@@ -12559,7 +12473,7 @@ function oxContent(options = {}) {
12559
12473
  }
12560
12474
  });
12561
12475
  devServer.watcher.on("change", (file) => {
12562
- if (file.startsWith(srcDir) && file.endsWith(".md")) invalidatePageCache(ssgDevCache, file);
12476
+ if (file.startsWith(srcDir) && isMarkdownFilePath(file, resolvedOptions.extensions)) invalidatePageCache(ssgDevCache, file);
12563
12477
  });
12564
12478
  },
12565
12479
  async closeBundle() {
@@ -12599,7 +12513,7 @@ function oxContent(options = {}) {
12599
12513
  const root = config?.root || process.cwd();
12600
12514
  const srcDir = path$1.resolve(root, resolvedOptions.srcDir);
12601
12515
  try {
12602
- searchIndexJson = await buildSearchIndex(srcDir, resolvedOptions.base);
12516
+ searchIndexJson = await buildSearchIndex(srcDir, resolvedOptions.base, resolvedOptions.extensions);
12603
12517
  console.log("[ox-content] Search index built");
12604
12518
  } catch (err) {
12605
12519
  console.warn("[ox-content] Failed to build search index:", err);
@@ -12630,6 +12544,7 @@ function resolveOptions(options) {
12630
12544
  srcDir: options.srcDir ?? "content",
12631
12545
  outDir: options.outDir ?? "dist",
12632
12546
  base: options.base ?? "/",
12547
+ extensions: normalizeMarkdownExtensions(options.extensions),
12633
12548
  ssg: resolveSsgOptions(options.ssg),
12634
12549
  gfm: options.gfm ?? true,
12635
12550
  footnotes: options.footnotes ?? true,
@@ -12650,9 +12565,25 @@ function resolveOptions(options) {
12650
12565
  docs: resolveDocsOptions(options.docs),
12651
12566
  search: resolveSearchOptions(options.search),
12652
12567
  ogViewer: options.ogViewer ?? true,
12568
+ embeds: resolveBuiltinEmbedOptions(options.embeds),
12653
12569
  i18n: resolveI18nOptions(options.i18n)
12654
12570
  };
12655
12571
  }
12572
+ function resolveBuiltinEmbedOptions(options) {
12573
+ if (options === false) return {
12574
+ github: false,
12575
+ openGraph: false
12576
+ };
12577
+ return {
12578
+ github: resolveSingleEmbedOptions(options?.github),
12579
+ openGraph: resolveSingleEmbedOptions(options?.openGraph)
12580
+ };
12581
+ }
12582
+ function resolveSingleEmbedOptions(options) {
12583
+ if (options === false) return false;
12584
+ if (options === true || options === void 0) return {};
12585
+ return options;
12586
+ }
12656
12587
  function resolveCodeAnnotationsOptions(options) {
12657
12588
  if (!options) return {
12658
12589
  enabled: false,
@@ -12725,6 +12656,6 @@ function normalizeRuntimeBase(base) {
12725
12656
  return withLeading.endsWith("/") ? withLeading : `${withLeading}/`;
12726
12657
  }
12727
12658
  //#endregion
12728
- export { DEFAULT_HTML_TEMPLATE, DefaultTheme, Fragment, buildSearchIndex, buildSsg, clearRenderContext, collectGitHubRepos, collectOgpUrls, createI18nPlugin, createMarkdownEnvironment, createTheme, defaultTheme, defineTheme, each, extractDocs, extractIslandInfo, extractVideoId, fetchOgpData, fetchRepoData, generateFrontmatterTypes, generateHydrationScript, generateMarkdown, generateOgImages, generateTabsCSS, generateTypes, generateVirtualModule, hasIslands, inferType, jsx, jsxs, lintMarkdown, lintMarkdownAsync, lintMarkdownFile, lintMarkdownFiles, mergeThemes, mermaidClientScript, oxContent, prefetchGitHubRepos, prefetchOgpData, raw, renderAllPages, renderPage, renderToString, resolveDocsOptions, resolveI18nOptions, resolveOgImageOptions, resolveSearchOptions, resolveSsgOptions, resolveTheme, setRenderContext, shouldLintMarkdownFile, transformAllPlugins, transformGitHub, transformIslands, transformMarkdown, transformMermaidStatic, transformOgp, transformTabs, transformYouTube, useIsActive, useNav, usePageProps, useRenderContext, useSiteConfig, when, writeDocs, writeSearchIndex };
12659
+ export { DEFAULT_HTML_TEMPLATE, DEFAULT_MARKDOWN_EXTENSIONS, DefaultTheme, Fragment, buildSearchIndex, buildSsg, clearRenderContext, collectGitHubRepos, collectGitHubSources, collectOgpUrls, convertVitePressNav, convertVitePressSidebar, createI18nPlugin, createMarkdownEnvironment, createTheme, defaultTheme, defineTheme, each, extractDocs, extractIslandInfo, extractVideoId, fetchGitHubSource, fetchOgpData, fetchRepoData, fromVitePressConfig, generateFrontmatterTypes, generateHydrationScript, generateMarkdown, generateOgImages, generateTabsCSS, generateTypes, generateVirtualModule, generateVitePressMigrationConfig, hasIslands, inferType, isMarkdownFilePath, jsx, jsxs, lintMarkdown, lintMarkdownAsync, lintMarkdownFile, lintMarkdownFiles, mergeThemes, mermaidClientScript, normalizeMarkdownExtensions, normalizeVitePressFrontmatter, oxContent, parseGitHubLineRange, parseGitHubPermalink, prefetchGitHubRepos, prefetchGitHubSources, prefetchOgpData, raw, renderAllPages, renderPage, renderToString, resolveBuiltinEmbedOptions, resolveDocsOptions, resolveI18nOptions, resolveOgImageOptions, resolveSearchOptions, resolveSsgOptions, resolveTheme, setRenderContext, shouldLintMarkdownFile, stripMarkdownExtension, transformAllPlugins, transformGitHub, transformIslands, transformMarkdown, transformMermaidStatic, transformOgp, transformTabs, transformYouTube, useIsActive, useNav, usePageProps, useRenderContext, useSiteConfig, when, writeDocs, writeSearchIndex };
12729
12660
 
12730
12661
  //# sourceMappingURL=index.mjs.map