Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions kotlin/src/jsMain/kotlin/Cache.kt
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,8 @@ fun getCacheData(): Promise<Array<String>> = cacheScope.promise {
arrayOf(serialize(cacheData), directoryTreeData, searchIndex)
}

@JsExport
fun getContent(fileNameString: String, content: String, cacheData: String, router: NextRouter, codeEncoder: CodeEncoder, mermaidRender: MermaidRender, texRender: TexRender, renderCanvas: CanvasRender): FC<Props> {
val (dependingLinks, fileNameInfo) = deserialize<CacheData>(cacheData)
fun getCachedContent(fileNameString: String, content: String, cacheData: CacheData, router: NextRouter, codeEncoder: CodeEncoder, mermaidRender: MermaidRender, texRender: TexRender, renderCanvas: CanvasRender): FC<Props> {
val (dependingLinks, fileNameInfo) = cacheData
val fileName = FileNameString(fileNameString)
return when {
fileName.isImageFile -> {
Expand All @@ -140,7 +139,7 @@ fun getContent(fileNameString: String, content: String, cacheData: String, route
val contentGetter: (String) -> FC<Props> = {
val contentFileName = SlugString(it.removeMdExtension()).toFileName(fileNameInfo.duplicatedFile)
val embedContent = dependingLinks.embedContents[contentFileName] ?: ""
getContent(contentFileName.fileName, embedContent, cacheData, router, codeEncoder, mermaidRender, texRender, renderCanvas)
getCachedContent(contentFileName.fileName, embedContent, cacheData, router, codeEncoder, mermaidRender, texRender, renderCanvas)
}
renderCanvas(canvasData, contentGetter)
}
Expand All @@ -150,6 +149,12 @@ fun getContent(fileNameString: String, content: String, cacheData: String, route
}
}

@JsExport
fun getContent(fileNameString: String, content: String, router: NextRouter, codeEncoder: CodeEncoder, mermaidRender: MermaidRender, texRender: TexRender, renderCanvas: CanvasRender): Promise<FC<Props>> = cacheScope.promise {
val (cache, directoryTreeData, searchIndex) = cacheData.get()!!
return@promise getCachedContent(fileNameString, content, cache, router, codeEncoder, mermaidRender, texRender, renderCanvas)
}

inline fun <reified T : @Serializable Any> deserialize(jsonString: String): T = json.decodeFromString(jsonString)

inline fun <reified T : @Serializable Any> serialize(obj: T): String = json.encodeToString(obj)
37 changes: 25 additions & 12 deletions src/components/MDContentData.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useState, useEffect } from 'react';
import Footer from "./Footer";
import { useRouter } from "next/router";
import { deserializeBackLinks, getContent } from "volglass-backend";
Expand Down Expand Up @@ -83,28 +84,40 @@ export interface MDContentData {
slugName: string;
fileName: string;
content: string;
cacheData: string;
backLinks: string;
}

interface ZeroProps {}

function MDContent({
slugName,
fileName,
content,
cacheData,
backLinks,
}: MDContentData): JSX.Element {
const router = useRouter();
const Content = getContent(
slugName,
`${content}`,
cacheData,
router,
codeEncoder,
renderMermaid(useCurrentTheme() === "dark"),
renderTex,
Canvas,
);
const [Content, setContent] = useState<React.FC<ZeroProps> | null>(null);
useEffect(() => {
const fetchData = async () => {
const tmpContent = await getContent(
slugName,
`${content}`,
router,
codeEncoder,
renderMermaid(useCurrentTheme() === "dark"),
renderTex,
Canvas,
);
setContent(tmpContent);
};

fetchData();
}, []);

if (!Content) {
return <div>Loading...</div>;
}

if (slugName.match(".canvas")) {
return (
<>
Expand Down
65 changes: 65 additions & 0 deletions src/components/NavBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import React from "react";
import { TreeData } from "../lib/markdown";
import { SearchData } from "../lib/search";
import dynamic from "next/dynamic";
import FolderTree from "../components/FolderTree";
import { SearchBar } from "../components/Search";

interface HomeElement extends HTMLElement {
checked: boolean;
}

const DynamicThemeSwitcher = dynamic(
async () => await import("../components/ThemeSwitcher"),
{
loading: () => <p>Loading...</p>,
ssr: false,
},
);

export interface NavBarData {
cacheData: string;
tree: TreeData;
flattenNodes: TreeData[];
searchIndex: SearchData[];
}

function NavBar({cacheData, tree, flattenNodes, searchIndex}: NavBarData): JSX.Element {
const burgerId = "hamburger-input";
const closeBurger = (): void => {
const element = document.getElementById(burgerId) as HomeElement | null;
if (element !== null) {
element.checked = false;
}
};

return (
<>
<div className="burger-menu">
<input type="checkbox" id={burgerId} />
<label id="hamburger-menu" htmlFor="hamburger-input">
<span className="menu">
{" "}
<span className="hamburger" />{" "}
</span>
</label>
<nav>
<FolderTree
tree={tree}
flattenNodes={flattenNodes}
onNodeSelect={closeBurger}
/>
</nav>
</div>
<div>
<nav className="nav-bar">
<DynamicThemeSwitcher />
<SearchBar index={searchIndex} />
<FolderTree tree={tree} flattenNodes={flattenNodes} />
</nav>
</div>
</>
);
}

export default NavBar;
30 changes: 30 additions & 0 deletions src/lib/context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { getCacheData } from "volglass-backend";
import { getFlattenArray, TreeData } from "./markdown";
import { SearchData } from "./search";

interface SharedContext {
cacheData: any;
tree: TreeData;
flattenNodes: TreeData[];
searchIndex: SearchData[];
}

let sharedContext: SharedContext | null = null;

export const getSharedContext = async (): Promise<SharedContext> => {
if (sharedContext !== null) {
return sharedContext;
}
const [cacheData, rawTreeData, rawSearchIndex] = await getCacheData();
const tree: TreeData = JSON.parse(rawTreeData);
const searchIndex: SearchData[] = JSON.parse(rawSearchIndex);
const flattenNodes = getFlattenArray(tree);

sharedContext = {
cacheData,
tree,
flattenNodes,
searchIndex,
}
return sharedContext;
};
98 changes: 16 additions & 82 deletions src/pages/[...id].tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import Head from "next/head";
import Layout from "../components/Layout";
import { getAllContentFilePaths, getDirectoryData, toSlug } from "../lib/slug";
import { getLocalGraphData, LocalGraphData } from "../lib/graph";
import { getFlattenArray, TreeData } from "../lib/markdown";
import { TreeData } from "../lib/markdown";
import { getSearchIndex, SearchData } from "../lib/search";
import {
getBackLinks,
Expand All @@ -22,25 +20,12 @@ import {
} from "../lib/io";
import dynamic from "next/dynamic";
import MDContent from "../components/MDContentData";
import FolderTree from "../components/FolderTree";
import { SearchBar } from "../components/Search";

// TODO make customizable
// FIXME This should be a string field, but I don't know to avoid init error
export function FIRST_PAGE(): string {
return "README";
}
interface HomeElement extends HTMLElement {
checked: boolean;
}

const DynamicThemeSwitcher = dynamic(
async () => await import("../components/ThemeSwitcher"),
{
loading: () => <p>Loading...</p>,
ssr: false,
},
);

// This trick is to dynamically load component that interact with window object (browser only)
const DynamicGraph = dynamic(async () => await import("../components/Graph"), {
Expand All @@ -51,75 +36,31 @@ export interface Prop {
slugName: string;
fileName: string;
markdownContent: string;
cacheData: string;
tree: TreeData;
flattenNodes: TreeData[];
graphData: LocalGraphData;
backLinks: string;
searchIndex: SearchData[];
}

export default function Home({
export default function Page({
slugName,
fileName,
markdownContent,
cacheData,
backLinks,
tree,
flattenNodes,
graphData,
searchIndex,
}: Prop): JSX.Element {
const burgerId = "hamburger-input";
const closeBurger = (): void => {
const element = document.getElementById(burgerId) as HomeElement | null;
if (element !== null) {
element.checked = false;
}
};

return (
<Layout>
<Head>{<meta name="title" content={slugName} />}</Head>
<div className="fixed flex h-full w-full flex-row overflow-hidden dark:bg-dark-background-primary">
<div className="burger-menu">
<input type="checkbox" id={burgerId} />
<label id="hamburger-menu" htmlFor="hamburger-input">
<span className="menu">
{" "}
<span className="hamburger" />{" "}
</span>
</label>
<nav>
<FolderTree
tree={tree}
flattenNodes={flattenNodes}
onNodeSelect={closeBurger}
/>
<DynamicGraph graph={graphData} />
</nav>
</div>
<div>
<nav className="nav-bar">
<DynamicThemeSwitcher />
<SearchBar index={searchIndex} />
<FolderTree tree={tree} flattenNodes={flattenNodes} />
</nav>
</div>
<MDContent
slugName={slugName}
fileName={fileName}
content={markdownContent}
cacheData={cacheData}
backLinks={backLinks}
/>
{!slugName.match(".canvas") ? (
<DynamicGraph graph={graphData} />
) : (
<></>
)}
</div>
</Layout>
<>
<MDContent
slugName={slugName}
fileName={fileName}
content={markdownContent}
backLinks={backLinks}
/>
{!slugName.match(".canvas") ? (
<DynamicGraph graph={graphData} />
) : (
<></>
)}
</>
);
}

Expand Down Expand Up @@ -158,29 +99,22 @@ export async function getStaticProps({
params: { id: string[] };
}): Promise<{ props: Prop }> {
const [cacheData, rawTreeData, rawSearchIndex] = await getCacheData();
const tree: TreeData = JSON.parse(rawTreeData);
const searchIndex: SearchData[] = JSON.parse(rawSearchIndex);

const slugString = `/${params.id.join("/")}`;
const slugName = toFileName(slugString, cacheData);
const fileName = toRawFileName(slugString, cacheData) ?? slugName;
const filePath = toFilePath(slugString, cacheData);
const markdownContent = isMediaFile(slugName) ? "" : readFileSync(filePath);
const flattenNodes = getFlattenArray(tree);
const backLinks = getBackLinks(slugString, cacheData, readFileSync);

const graphData = getLocalGraphData(slugString, cacheData);

return {
props: {
slugName,
fileName,
markdownContent,
cacheData,
tree,
flattenNodes,
backLinks,
graphData,
searchIndex,
},
};
}
15 changes: 13 additions & 2 deletions src/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,18 @@ import "../styles/style.css";
import "../styles/prism.css";
import "../styles/prism-dark.css";
import { AppProps } from "next/app";
import { getSharedContext } from "../lib/context";
import NavBar from "../components/NavBar";
import Layout from "../components/Layout";

export default function App({ Component, pageProps }: AppProps): JSX.Element {
return <Component {...pageProps} />;
export default async function App({ Component, pageProps }: AppProps): Promise<JSX.Element> {
const navBarProps = await getSharedContext();
return (
<Layout>
<div className="fixed flex h-full w-full flex-row overflow-hidden dark:bg-dark-background-primary">
<NavBar {...navBarProps} />;
<Component {...pageProps} />;
</div>
</Layout>
);
}
4 changes: 2 additions & 2 deletions src/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import Home, {
import Page, {
FIRST_PAGE,
getStaticProps as getDefaultStaticProps,
Prop,
} from "./[...id]";

export const Index = (prop: Prop) => Home(prop);
export const Index = (prop: Prop) => Page(prop);

export const getStaticProps = async () =>
getDefaultStaticProps({ params: { id: [FIRST_PAGE()] } });
Expand Down