Skip to content

Commit

Permalink
added markdown page
Browse files Browse the repository at this point in the history
  • Loading branch information
remko48 committed Nov 1, 2023
1 parent 6117ce2 commit a72eeb2
Show file tree
Hide file tree
Showing 13 changed files with 324 additions and 10 deletions.
33 changes: 32 additions & 1 deletion pwa/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion pwa/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,8 @@
"react-loading-skeleton": "^3.1.0",
"react-paginate": "^8.1.4",
"react-query": "^3.34.19",
"react-select": "^5.3.2"
"react-select": "^5.3.2",
"showdown": "^2.1.0"
},
"devDependencies": {
"@types/dateformat": "^5.0.0",
Expand All @@ -96,6 +97,7 @@
"@types/react-helmet": "^6.1.5",
"@typescript-eslint/eslint-plugin": "^5.55.0",
"@typescript-eslint/parser": "^5.55.0",
"@types/showdown": "2.0.3",
"eslint": "^8.36.0",
"eslint-config-prettier": "^8.8.0",
"eslint-plugin-react": "^7.32.2",
Expand Down
15 changes: 15 additions & 0 deletions pwa/src/apiService/apiService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { DEFAULT_FOOTER_CONTENT_URL } from "../templates/templateParts/footer/Fo
// Resources
import OpenWoo from "./resources/openWoo";
import FooterContent from "./resources/footerContent";
import Markdown from "./resources/markdown";

interface PromiseMessage {
loading?: string;
Expand Down Expand Up @@ -41,13 +42,27 @@ export default class APIService {
});
}

public get MarkdownClient(): AxiosInstance {
return axios.create({
baseURL: process.env.GATSBY_BASE_URL ?? undefined,
headers: {
Accept: "application/vnd.github.html",
},
});
}

public get OpenWoo(): OpenWoo {
return new OpenWoo(this.BaseClient, this.Send);
}

public get FooterContent(): FooterContent {
return new FooterContent(this.FooterContentClient, this.Send);
}

public get Markdown(): Markdown {
return new Markdown(this.MarkdownClient, this.Send);
}

// Send method
public Send: TSendFunction = (instance, method, endpoint, payload, promiseMessage) => {
const _payload = JSON.stringify(payload);
Expand Down
17 changes: 17 additions & 0 deletions pwa/src/apiService/resources/markdown.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { TSendFunction } from "../apiService";
import { AxiosInstance } from "axios";

export default class Markdown {
private _instance: AxiosInstance;
private _send: TSendFunction;
constructor(_instance: AxiosInstance, send: TSendFunction) {
this._instance = _instance;
this._send = send;
}

public getContent = async (filePath: string): Promise<any> => {
const { data } = await this._send(this._instance, "GET", filePath);

return data;
};
}
17 changes: 17 additions & 0 deletions pwa/src/components/ParsedHTML/ParsedHTML.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
.container {
margin-block-start: var(--utrecht-space-block-3xl);
}

.container > div > article > *:not(:last-child),
.backLink {
margin-block-end: var(--utrecht-space-block-lg);
}

.backLink:hover {
cursor: pointer;
}

.backLink {
display: flex;
align-items: center;
}
85 changes: 85 additions & 0 deletions pwa/src/components/ParsedHTML/ParsedHTML.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import * as React from "react";
import * as styles from "./ParsedHTML.module.css";
import Parser from "html-react-parser";
import Skeleton from "react-loading-skeleton";
import clsx from "clsx";
import showdown from "showdown";
import { Alert } from "@utrecht/component-library-react/dist/css-module";
import { UseQueryResult } from "react-query";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faArrowLeft, faWarning } from "@fortawesome/free-solid-svg-icons";
import { useHtmlParser } from "../../hooks/htmlParser/useHtmlParser";
import { isHtml } from "../../services/isHtml";
import { Link } from "@utrecht/component-library-react/dist/css-module";
import { navigate } from "gatsby";
import { useTranslation } from "react-i18next";

interface ParsedHTMLProps {
contentQuery: UseQueryResult<any, Error>;
location: string;
layoutClassName?: string;
}

export const ParsedHTML: React.FC<ParsedHTMLProps> = ({ contentQuery, location, layoutClassName }) => {
const { t } = useTranslation();
const { options } = useHtmlParser(location);
let htmlContent;

showdown.setFlavor("github");

if (!isHtml(contentQuery.data)) {
const converter = new showdown.Converter();
htmlContent = `<div><article class="markdown-body entry-content container-lg" itemprop="text">${converter.makeHtml(
contentQuery.data,
)}</article></div>`;
}
if (isHtml(contentQuery.data)) {
htmlContent = contentQuery.data;
}

if (contentQuery.isLoading)
return (
<div className={styles.container}>
<Skeleton height="200px" />
</div>
);

if (contentQuery.isError)
return (
<div className={styles.container}>
<div>
<Link
className={styles.backLink}
href="/"
onClick={(e: any) => {
e.preventDefault(), navigate("/");
}}
tabIndex={0}
>
<FontAwesomeIcon icon={faArrowLeft} /> <span>{t("Back to homepage")}</span>
</Link>
</div>
<Alert icon={<FontAwesomeIcon icon={faWarning} />} type="error">
Oops, something went wrong retrieving the .md file from GitHub.
</Alert>
</div>
);

return (
<div className={clsx(styles.container, layoutClassName && layoutClassName)}>
<div>
<Link
className={styles.backLink}
href="/"
onClick={(e: any) => {
e.preventDefault(), navigate("/");
}}
tabIndex={0}
>
<FontAwesomeIcon icon={faArrowLeft} /> <span>{t("Back to homepage")}</span>
</Link>
</div>
{Parser(htmlContent, options)}
</div>
);
};
25 changes: 25 additions & 0 deletions pwa/src/hooks/markdown.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import * as React from "react";
import { useQuery } from "react-query";
import APIService from "../apiService/apiService";
import APIContext from "../apiService/apiContext";

export const useMarkdown = () => {
const API: APIService | null = React.useContext(APIContext);

const getContent = (filePath: string) =>
useQuery<any, Error>({
queryKey: ["contents", filePath],
queryFn: () => API?.Markdown.getContent(filePath),
onError: (error) => {
console.warn(error.message);
},
refetchOnWindowFocus: false,
refetchOnMount: false,
refetchOnReconnect: false,
retry: 1,
retryDelay: 2000,
staleTime: 1000 * 60 * 60, // one hour
});

return { getContent };
};
45 changes: 45 additions & 0 deletions pwa/src/hooks/useMarkdownDirectories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import * as React from "react";

export type TMarkdownDirectory = {
name: string;
location: string;
};

export const useMarkdownDirectories = () => {
const [directories, setDirectories] = React.useState<TMarkdownDirectory[]>([]);

React.useEffect(() => {
const markdownDirectoryPathsString: string | undefined = process.env.GATSBY_GITHUB_DOCS_DIRECTORY_PATHS;

if (!markdownDirectoryPathsString) return;

try {
const directories = JSON.parse(markdownDirectoryPathsString);

setDirectories(directories);
} catch {
console.warn("Something went wrong parsing the Markdown directories.");
}
}, []);

const getSlugFromName = (name: string): string => name?.replace(" ", "-");
const getNameFromSlug = (slug: string): string => slug?.replace("-", " "); // internal function

const getDirectoryReadMeLocation = (pageSlug: string): string => {
const directory = directories.find((directory) => directory.name === getNameFromSlug(pageSlug));

if (!directory) return "";

return `${directory.location}/README.md`;
};

const getDetailMdLocation = (pageSlug: string, detailPageSlug: string): string => {
const directory = directories.find((directory) => directory.name === getNameFromSlug(pageSlug));

if (!directory) return "";

return `${directory.location}/${getNameFromSlug(detailPageSlug)}.md`;
};

return { directories, getSlugFromName, getDirectoryReadMeLocation, getDetailMdLocation };
};
34 changes: 34 additions & 0 deletions pwa/src/pages/markdown/[md].tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import * as React from "react";
import qs from "qs";
import { PageProps } from "gatsby";
import { MarkdownContentTemplate } from "../../templates/markdown/MarkdownContentTemplate";
import { useGatsbyContext } from "../../context/gatsby";
import { useTranslation } from "react-i18next";
import { Page, PageContent } from "@utrecht/component-library-react/dist/css-module";

const MarkdownPage: React.FC<PageProps> = (props: PageProps) => {
const { t } = useTranslation();
const { gatsbyContext } = useGatsbyContext();

const url = gatsbyContext.location.search;
const [, params] = url.split("?");
const parsedParams = qs.parse(params);
const link = parsedParams.link?.toString();

const detailPageSlug = props.params.detailPageSlug;
const pageSlug = props.params.pageSlug;

if (!link) {
return <span>{t("No markdown file found, make sure that the query param link is filled")}</span>;
}

return (
<Page>
<PageContent>
<MarkdownContentTemplate {...{ pageSlug, detailPageSlug, link }} />
</PageContent>
</Page>
);
};

export default MarkdownPage;
8 changes: 8 additions & 0 deletions pwa/src/pages/markdown/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { navigate } from "gatsby";

const IndexPage = () => {
navigate("/");
return <></>;
};

export default IndexPage;
6 changes: 6 additions & 0 deletions pwa/src/services/isHtml.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
//This function checks if the string contains HTML code
export const isHtml = (data: string): boolean => {
const hmlRegex = /<([A-Za-z][A-Za-z0-9]*)\b[^>]*>(.*?)<\/\1>/;

return hmlRegex.test(data);
};
29 changes: 29 additions & 0 deletions pwa/src/templates/markdown/MarkdownContentTemplate.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import * as React from "react";
import { useMarkdown } from "../../hooks/markdown";
import { ParsedHTML } from "../../components/ParsedHTML/ParsedHTML";
import { useMarkdownDirectories } from "../../hooks/useMarkdownDirectories";

interface MarkdownContentTemplateProps {
pageSlug: string;
detailPageSlug: string;
link: string;
}

export const MarkdownContentTemplate: React.FC<MarkdownContentTemplateProps> = ({ pageSlug, detailPageSlug, link }) => {
const { getDetailMdLocation } = useMarkdownDirectories();

const location = getDetailMdLocation(pageSlug, detailPageSlug);

let content: any;

if (link.includes("https://github.com/")) {
const linkHttps = link.replace("https://github.com/", "https://api.github.com/repos/");
linkHttps.includes("/blob/main/")
? (content = useMarkdown().getContent(linkHttps.replace("/blob/main/", "/contents/")))
: (content = useMarkdown().getContent(linkHttps.replace("/blob/master/", "/contents/")));
} else {
content = useMarkdown().getContent(link.includes("https://api.github.com/repos/") ? link : link);
}

return <ParsedHTML contentQuery={content} {...{ location }} />;
};
Loading

0 comments on commit a72eeb2

Please sign in to comment.