@fjell/docs-template
Version:
Shared documentation template for Fjell projects
470 lines (458 loc) • 16.7 kB
JavaScript
// src/components/DocsApp.tsx
import { useEffect, useState } from "react";
// src/components/Navigation.tsx
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
var Navigation = ({
sections,
currentSection,
currentSubsection,
onSectionChange,
sidebarOpen,
setSidebarOpen
}) => {
return /* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ jsx(
"button",
{
className: "mobile-menu-button",
onClick: () => setSidebarOpen(!sidebarOpen),
"aria-label": "Toggle navigation",
children: /* @__PURE__ */ jsxs("div", { className: "hamburger", children: [
/* @__PURE__ */ jsx("span", {}),
/* @__PURE__ */ jsx("span", {}),
/* @__PURE__ */ jsx("span", {})
] })
}
),
/* @__PURE__ */ jsxs("div", { className: `sidebar ${sidebarOpen ? "sidebar-open" : ""}`, children: [
/* @__PURE__ */ jsx("nav", { className: "sidebar-nav", children: /* @__PURE__ */ jsx("ul", { className: "nav-sections", children: sections.map((section) => /* @__PURE__ */ jsxs("li", { children: [
/* @__PURE__ */ jsx(
"button",
{
className: `nav-item ${currentSection === section.id && !currentSubsection ? "active" : ""}`,
onClick: () => {
onSectionChange(section.id);
setSidebarOpen(false);
},
children: /* @__PURE__ */ jsxs("div", { className: "nav-item-content", children: [
/* @__PURE__ */ jsx("span", { className: "nav-title", children: section.title }),
/* @__PURE__ */ jsx("span", { className: "nav-subtitle", children: section.subtitle })
] })
}
),
section.subsections && currentSection === section.id && /* @__PURE__ */ jsx("ul", { className: "nav-subsections", children: section.subsections.map((subsection) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
"button",
{
className: `nav-subitem ${currentSubsection === subsection.id ? "active" : ""}`,
onClick: () => {
onSectionChange(section.id, subsection.id);
setSidebarOpen(false);
},
children: /* @__PURE__ */ jsxs("div", { className: "nav-subitem-content", children: [
/* @__PURE__ */ jsx("span", { className: "nav-subtitle", children: subsection.title }),
/* @__PURE__ */ jsx("span", { className: "nav-description", children: subsection.subtitle })
] })
}
) }, subsection.id)) })
] }, section.id)) }) }),
/* @__PURE__ */ jsx("div", { className: "sidebar-footer", children: /* @__PURE__ */ jsx("div", { className: "footer-text", children: "Built with Fjell Documentation Template" }) })
] })
] });
};
// src/components/ContentRenderer.tsx
import ReactMarkdown from "react-markdown";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { oneLight } from "react-syntax-highlighter/dist/esm/styles/prism";
import remarkGfm from "remark-gfm";
import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
var ContentRenderer = ({
content,
sectionData,
loading,
config,
onImageClick
}) => {
if (loading) {
return /* @__PURE__ */ jsx2("main", { className: "main-content", children: /* @__PURE__ */ jsx2("div", { className: "content", children: /* @__PURE__ */ jsxs2("div", { className: "loading-spinner", children: [
/* @__PURE__ */ jsx2("div", { className: "spinner" }),
/* @__PURE__ */ jsx2("p", { children: "Loading documentation..." })
] }) }) });
}
return /* @__PURE__ */ jsx2("main", { className: "main-content", children: /* @__PURE__ */ jsxs2("div", { className: "content", children: [
/* @__PURE__ */ jsx2("div", { className: "content-header", children: sectionData && /* @__PURE__ */ jsxs2(Fragment2, { children: [
/* @__PURE__ */ jsxs2("div", { className: "breadcrumb", children: [
/* @__PURE__ */ jsx2("span", { className: "breadcrumb-project", children: config.projectName }),
/* @__PURE__ */ jsx2("span", { className: "breadcrumb-separator", children: "/" }),
/* @__PURE__ */ jsx2("span", { className: "breadcrumb-section", children: sectionData.title })
] }),
/* @__PURE__ */ jsx2("h1", { className: "page-title", children: sectionData.title }),
/* @__PURE__ */ jsx2("p", { className: "page-subtitle", children: sectionData.subtitle })
] }) }),
/* @__PURE__ */ jsx2("div", { className: "markdown-content", children: /* @__PURE__ */ jsx2(
ReactMarkdown,
{
remarkPlugins: [remarkGfm],
components: {
code({ inline, className, children, ...props }) {
const match = /language-(\w+)/.exec(className || "");
return !inline && match ? /* @__PURE__ */ jsx2(
SyntaxHighlighter,
{
style: oneLight,
language: match[1],
PreTag: "div",
...props,
children: String(children).replace(/\n$/, "")
}
) : /* @__PURE__ */ jsx2("code", { className, ...props, children });
},
img({ src, alt, ...props }) {
const imageSrc = src?.startsWith("/") ? src : `${config.basePath}${src || ""}`;
return /* @__PURE__ */ jsx2(
"img",
{
src: imageSrc,
alt: alt || "",
...props,
onClick: () => onImageClick(imageSrc),
style: { cursor: "pointer" },
title: "Click to view fullscreen"
}
);
},
a({ href, children, ...props }) {
const isExternal = href?.startsWith("http") || href?.startsWith("//");
const linkProps = {
href,
...props,
...isExternal && {
target: "_blank",
rel: "noopener noreferrer"
}
};
return /* @__PURE__ */ jsx2("a", { ...linkProps, children });
}
},
children: content
}
) })
] }) });
};
// src/components/Header.tsx
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
var Header = ({
config,
version,
sidebarOpen,
setSidebarOpen
}) => {
const { projectName, branding } = config;
const nameParts = projectName.split(" ");
const brandMain = nameParts[0] || projectName;
const brandSub = nameParts.slice(1).join(" ");
return /* @__PURE__ */ jsx3("header", { className: "header", children: /* @__PURE__ */ jsxs3("div", { className: "header-container", children: [
/* @__PURE__ */ jsxs3("div", { className: "brand", children: [
/* @__PURE__ */ jsxs3("h1", { className: "brand-title", children: [
/* @__PURE__ */ jsx3("span", { className: "brand-fjell", children: brandMain }),
brandSub && /* @__PURE__ */ jsx3("span", { className: "brand-default", children: brandSub })
] }),
/* @__PURE__ */ jsx3("p", { className: "brand-tagline", children: branding.tagline })
] }),
/* @__PURE__ */ jsxs3("div", { className: "header-actions", children: [
version && (branding.github ? /* @__PURE__ */ jsxs3(
"a",
{
href: `${branding.github}/releases`,
className: "version-badge",
target: "_blank",
rel: "noopener noreferrer",
title: "View releases",
children: [
"v",
version
]
}
) : /* @__PURE__ */ jsxs3("span", { className: "version-badge", title: "Version information", children: [
"v",
version
] })),
branding.github && /* @__PURE__ */ jsx3(
"a",
{
href: branding.github,
className: "header-link",
target: "_blank",
rel: "noopener noreferrer",
children: "View Source"
}
),
branding.npm && /* @__PURE__ */ jsx3(
"a",
{
href: branding.npm,
className: "header-link",
target: "_blank",
rel: "noopener noreferrer",
children: "Install Package"
}
)
] }),
/* @__PURE__ */ jsxs3(
"button",
{
className: "menu-toggle",
onClick: () => setSidebarOpen(!sidebarOpen),
"aria-label": "Toggle navigation",
children: [
/* @__PURE__ */ jsx3("span", { className: "menu-line" }),
/* @__PURE__ */ jsx3("span", { className: "menu-line" }),
/* @__PURE__ */ jsx3("span", { className: "menu-line" })
]
}
)
] }) });
};
// src/components/Layout.tsx
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
var Layout = ({
config,
children,
sidebarOpen,
setSidebarOpen,
fullscreenImage,
setFullscreenImage,
version
}) => {
return /* @__PURE__ */ jsxs4("div", { className: `docs-app ${config.branding.theme ? `brand-${config.branding.theme}` : ""}`, children: [
/* @__PURE__ */ jsx4("div", { className: "header-background" }),
/* @__PURE__ */ jsx4(
Header,
{
config,
version,
sidebarOpen,
setSidebarOpen
}
),
/* @__PURE__ */ jsx4("div", { className: "layout", children }),
fullscreenImage && /* @__PURE__ */ jsx4(
"div",
{
className: "fullscreen-overlay",
onClick: () => setFullscreenImage(null),
children: /* @__PURE__ */ jsxs4("div", { className: "fullscreen-content", children: [
/* @__PURE__ */ jsx4(
"img",
{
src: fullscreenImage,
alt: "Fullscreen view",
className: "fullscreen-image"
}
),
/* @__PURE__ */ jsx4(
"button",
{
className: "close-fullscreen",
onClick: () => setFullscreenImage(null),
"aria-label": "Close fullscreen view",
children: "\xD7"
}
)
] })
}
),
sidebarOpen && /* @__PURE__ */ jsx4(
"div",
{
className: "sidebar-overlay",
onClick: () => setSidebarOpen(false)
}
)
] });
};
// src/utils/contentLoader.ts
var loadDocument = async (url) => {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Failed to fetch ${url}: ${response.status}`);
}
return response.text();
};
var processContent = (content) => {
return content;
};
var extractSection = (content, sectionTitle) => {
const sections = content.split("##");
const targetSection = sections.find(
(s) => s.trim().toLowerCase().startsWith(sectionTitle.toLowerCase())
);
if (targetSection) {
return "##" + targetSection;
}
return content;
};
var extractMultipleSections = (content, sectionTitles) => {
const sections = content.split("##");
const matchingSections = sections.filter(
(s) => sectionTitles.some(
(title) => s.trim().toLowerCase().startsWith(title.toLowerCase())
)
);
if (matchingSections.length > 0) {
return matchingSections.map((s) => "##" + s).join("\n\n");
}
return content;
};
var createGettingStartedContent = (content) => {
const installSection = extractSection(content, "Installation");
const basicSection = extractSection(content, "Basic Usage");
const configSection = extractSection(content, "Configuration");
let result = "# Getting Started\n\n";
if (installSection !== content && installSection.trim()) result += installSection + "\n\n";
if (basicSection !== content && basicSection.trim()) result += basicSection + "\n\n";
if (configSection !== content && configSection.trim()) result += configSection + "\n\n";
return result;
};
// src/components/DocsApp.tsx
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
var DocsApp = ({ config }) => {
const [currentSection, setCurrentSection] = useState(config.sections[0]?.id || "overview");
const [currentSubsection, setCurrentSubsection] = useState(null);
const [documents, setDocuments] = useState({});
const [loading, setLoading] = useState(true);
const [sidebarOpen, setSidebarOpen] = useState(false);
const [fullscreenImage, setFullscreenImage] = useState(null);
const [version, setVersion] = useState("");
useEffect(() => {
const handleRouting = () => {
const hash = window.location.hash.slice(1);
const [sectionId, subsectionId] = hash.split("/");
if (sectionId && config.sections.find((s) => s.id === sectionId)) {
setCurrentSection(sectionId);
setCurrentSubsection(subsectionId || null);
}
};
handleRouting();
window.addEventListener("hashchange", handleRouting);
return () => window.removeEventListener("hashchange", handleRouting);
}, [config]);
useEffect(() => {
const loadDocuments = async () => {
const loadedDocs = {};
if (config.branding.backgroundImage) {
document.documentElement.style.setProperty(
"--brand-background-image",
`url('${config.branding.backgroundImage}')`
);
}
if (config.version.source === "manual" && config.version.value) {
setVersion(config.version.value);
} else if (config.version.source === "env" && config.version.envVar) {
setVersion(process.env[config.version.envVar] || "dev");
} else if (config.version.source === "package.json") {
setVersion(window.__APP_VERSION__ || "1.0.0");
} else {
setVersion(window.__APP_VERSION__ || "1.0.0");
}
for (const section of config.sections) {
if (section.file) {
try {
const content = await loadDocument(section.file);
const processedContent = config.customContent?.[section.id] ? config.customContent[section.id](content) : content;
loadedDocs[section.id] = processedContent;
} catch (error) {
console.error(`Error loading ${section.file}:`, error);
loadedDocs[section.id] = getDefaultContent(section);
}
} else {
loadedDocs[section.id] = getDefaultContent(section);
}
if (section.subsections) {
for (const subsection of section.subsections) {
const subsectionKey = `${section.id}/${subsection.id}`;
try {
const content = await loadDocument(subsection.file);
loadedDocs[subsectionKey] = content;
} catch (error) {
console.error(`Error loading ${subsection.file}:`, error);
loadedDocs[subsectionKey] = `# ${subsection.title}
${subsection.subtitle}
Content coming soon...`;
}
}
}
}
setDocuments(loadedDocs);
setLoading(false);
};
loadDocuments();
}, [config]);
const getDefaultContent = (section) => {
return `# ${section.title}
${section.subtitle}
Content coming soon...`;
};
const currentSectionData = config.sections.find((s) => s.id === currentSection);
const currentContentKey = currentSubsection ? `${currentSection}/${currentSubsection}` : currentSection;
const currentContent = documents[currentContentKey] || "";
const handleSectionChange = (sectionId, subsectionId) => {
setCurrentSection(sectionId);
setCurrentSubsection(subsectionId || null);
const hash = subsectionId ? `${sectionId}/${subsectionId}` : sectionId;
window.location.hash = hash;
};
return /* @__PURE__ */ jsxs5(
Layout,
{
config,
sidebarOpen,
setSidebarOpen,
fullscreenImage,
setFullscreenImage,
version,
children: [
/* @__PURE__ */ jsx5(
Navigation,
{
sections: config.sections,
currentSection,
currentSubsection,
onSectionChange: handleSectionChange,
sidebarOpen,
setSidebarOpen
}
),
/* @__PURE__ */ jsx5(
ContentRenderer,
{
content: currentContent,
sectionId: currentSection,
subsectionId: currentSubsection,
sectionData: currentSectionData,
loading,
config,
onImageClick: setFullscreenImage
}
)
]
}
);
};
// src/index.ts
var renderDocsApp = (config) => {
return DocsApp({ config });
};
export {
ContentRenderer,
DocsApp,
Header,
Layout,
Navigation,
createGettingStartedContent,
extractMultipleSections,
extractSection,
loadDocument,
processContent,
renderDocsApp
};
//# sourceMappingURL=index.js.map