diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Emoji.tsx | 3 | ||||
-rw-r--r-- | src/Image.tsx | 7 | ||||
-rw-r--r-- | src/benzinConfig.ts | 9 | ||||
-rw-r--r-- | src/deepReadDir.tsx | 12 | ||||
-rw-r--r-- | src/lib/nginxAdapter.ts | 50 | ||||
-rw-r--r-- | src/lib/types.ts | 15 | ||||
-rw-r--r-- | src/pages/[...path].tsx | 39 | ||||
-rw-r--r-- | src/pages/_app.tsx | 2 |
8 files changed, 101 insertions, 36 deletions
diff --git a/src/Emoji.tsx b/src/Emoji.tsx index b928087..df5108d 100644 --- a/src/Emoji.tsx +++ b/src/Emoji.tsx @@ -1,5 +1,6 @@ import { FC } from 'react'; import Image from 'next/future/image'; +import benzinConfig from './benzinConfig'; interface Props { children: string[]; @@ -9,7 +10,7 @@ const Emoji: FC<Props> = ({ children }) => { const src = children[0]; return ( <Image - src={`/emoji/${src}`} + src={`${benzinConfig.CDN}/public/emoji/${src}`} width={16} alt={`${src}-emoji`} /> diff --git a/src/Image.tsx b/src/Image.tsx index 770c383..37e55ba 100644 --- a/src/Image.tsx +++ b/src/Image.tsx @@ -1,13 +1,12 @@ import ImageBase from 'next/future/image'; import { FC } from 'react'; +import benzinConfig from './benzinConfig'; type Props = Record<'src' | 'alt', string>; -const IMAGE_CDN = 'http://localhost:8000'; - const localizeSrc = (src: string) => { - if (process.env.NODE_ENV === 'production' || src.startsWith('http')) return src; - return IMAGE_CDN + src; + if (src.startsWith('http')) return src; + return benzinConfig.CDN + src; } const Image: FC<Props> = ({ src, ...props }) => { diff --git a/src/benzinConfig.ts b/src/benzinConfig.ts new file mode 100644 index 0000000..d310bb3 --- /dev/null +++ b/src/benzinConfig.ts @@ -0,0 +1,9 @@ +import { BenzinConfig } from './lib/types'; +import nginxAdapter from './lib/nginxAdapter'; + +const benzinConfig: BenzinConfig = { + CDN: 'http://localhost:8000', + adapter: nginxAdapter, +}; + +export default benzinConfig; diff --git a/src/deepReadDir.tsx b/src/deepReadDir.tsx deleted file mode 100644 index ec585ec..0000000 --- a/src/deepReadDir.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import {lstat, readdir} from 'node:fs/promises' -import {join} from 'node:path' - -const deepReadDir = async (dirPath: string): Promise<string[]> => await Promise.all( - (await readdir(dirPath)).map(async (entity) => { - const path = join(dirPath, entity) - return (await lstat(path)).isDirectory() ? await deepReadDir(path) : path - }), -).then(arr => arr.flat()); - - -export default deepReadDir; diff --git a/src/lib/nginxAdapter.ts b/src/lib/nginxAdapter.ts new file mode 100644 index 0000000..fdadd87 --- /dev/null +++ b/src/lib/nginxAdapter.ts @@ -0,0 +1,50 @@ +import _ from 'lodash'; +import Bluebird from 'bluebird'; +import axios from 'axios'; +import mem from 'mem'; +import { Adapter } from './types'; + +const listNginxDirectory = async (path: string): Promise<string[]> => { + const basePath = _.trimEnd(path.match('http[s]?://(.*?)/')?.[0], '/'); + const response = await axios(path); + const [_thisDir, ...results] = response.data + .match(/href='(.*)'/g) + .map((s: string) => s.match(/'(.*)'/)?.[1]) + .map((s: string) => basePath + s); + return results; +} + +const deepListNginxDirectory = async (path: string): Promise<string[]> => { + const objects = await listNginxDirectory(path); + const fileUrls = objects.filter(url => !url.endsWith('/')); + const dirUrls = objects.filter(url => url.endsWith('/')); + const deepFileUrls = await Bluebird.map(dirUrls, deepListNginxDirectory); + return _.flattenDeep([fileUrls, deepFileUrls]); +} + +const memoizedDeepListNginxDirectory = mem(deepListNginxDirectory, { maxAge: 60000 }); + + +// An adapter to fetch markdown & images from Nginx server with enabled directory view +const nginxAdapter: Adapter = { + async getStaticMarkdownPaths(cdn) { + const urls = await memoizedDeepListNginxDirectory(cdn); + const markdownPaths = _.compact(urls.map(globalPath => globalPath.match(`${cdn}/(.*)\.md`)?.[1])); + + return markdownPaths + .map(path => path.split('/')) + .map(path => ({ params: { path } })); + }, + + async getMarkdownSource(cdn, path) { + const { data: markdownSource } = await axios(`${cdn}/${path?.join('/')}.md`); + return markdownSource; + }, + + async getEmojiFileNames(cdn) { + const urls = await memoizedDeepListNginxDirectory(cdn); + return _.compact(urls.map((s: string) => s.match(/emoji\/(.*)/)?.[1])); + }, +} + +export default nginxAdapter; diff --git a/src/lib/types.ts b/src/lib/types.ts new file mode 100644 index 0000000..8bbf5bb --- /dev/null +++ b/src/lib/types.ts @@ -0,0 +1,15 @@ +import { GetStaticPathsResult } from 'next'; + +/* Collection of methods to fetch data & metadata from CDN */ +export interface Adapter { + getStaticMarkdownPaths: (cdn: string) => Promise<GetStaticPathsResult['paths']>; + getMarkdownSource: (cdn: string, path: string[]) => Promise<string>; + getEmojiFileNames: (cdn: string) => Promise<string[]>; +} + +export interface BenzinConfig { + CDN: string; + adapter: Adapter; +} + + diff --git a/src/pages/[...path].tsx b/src/pages/[...path].tsx index ea67d17..a49286d 100644 --- a/src/pages/[...path].tsx +++ b/src/pages/[...path].tsx @@ -4,23 +4,30 @@ import ReactMarkdown from 'react-markdown'; import Head from 'next/head'; import Emoji from '../Emoji'; import Image from '../Image'; -import deepReadDir from '../deepReadDir'; import emojiPlugin from '../emojiPlugin'; -import fs from 'fs'; import remarkGemoji from 'remark-gemoji'; +import benzinConfig from '../benzinConfig'; - -const MARKDOWN_DIR = '../eug-vs-xyz/src'; -const EMOJI_DIR = 'public/emoji'; - -const transformLinkURI = (uri: string): string => { +const transformLinkUri = (uri: string): string => { return uri.match(/(.*)\.md/)?.[1] || uri; } +const transformImageUri = (uri: string): string => { + return uri.startsWith('http') ? uri : benzinConfig.CDN + uri; +} + +export const config = { + unstable_runtimeJS: false, +}; + export const getStaticProps = async (context: GetStaticPropsContext) => { - const path = _.isArray(context.params?.path) && context.params?.path || [context.params?.path]; - const markdownSource = fs.readFileSync(`${MARKDOWN_DIR}/${path?.join('/')}.md`).toString(); - const emojiFileNames = fs.readdirSync(EMOJI_DIR); + const path = _.compact(_.isArray(context.params?.path) + ? context.params?.path + : [context.params?.path] + ); + + const markdownSource = await benzinConfig.adapter.getMarkdownSource(benzinConfig.CDN, path); + const emojiFileNames = await benzinConfig.adapter.getEmojiFileNames(benzinConfig.CDN); return { props: { @@ -32,12 +39,8 @@ export const getStaticProps = async (context: GetStaticPropsContext) => { } export const getStaticPaths = async () => { - const globalPaths = await deepReadDir(MARKDOWN_DIR); - const paths = globalPaths - .map(globalPath => globalPath.match(`${MARKDOWN_DIR}/(.*)\.md`)?.[1] ) - .filter(p => p) - .map(p => p?.split('/')) - .map(path => ({ params: { path } })); + const paths = await benzinConfig.adapter.getStaticMarkdownPaths(benzinConfig.CDN); + return { paths, fallback: 'blocking', @@ -54,7 +57,8 @@ const Page: NextPage = ({ markdownSource, emojiFileNames }: any) => { </Head> <main> <ReactMarkdown - transformLinkUri={transformLinkURI} + transformLinkUri={transformLinkUri} + transformImageUri={transformImageUri} rehypePlugins={[emojiPlugin(emojiFileNames), remarkGemoji]} components={{ emoji: Emoji, @@ -74,4 +78,3 @@ const Page: NextPage = ({ markdownSource, emojiFileNames }: any) => { }; export default Page; - diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index ba9372f..18692df 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -7,7 +7,7 @@ function MyApp({ Component, pageProps }: AppProps) { return ( <> <a href="/" style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', textDecoration: 'none', marginBottom: '12px' }}> - <Image src={logo} width={128} height={128} /> + <Image src={logo} width={128} height={128} alt="logo" /> <h1>{"Eugene's Space"}</h1> </a> <Component {...pageProps} /> |