diff options
Diffstat (limited to 'pages')
-rw-r--r-- | pages/_app.tsx | 34 | ||||
-rw-r--r-- | pages/index.tsx | 101 | ||||
-rw-r--r-- | pages/post/[id].tsx | 216 | ||||
-rw-r--r-- | pages/search.tsx | 170 |
4 files changed, 0 insertions, 521 deletions
diff --git a/pages/_app.tsx b/pages/_app.tsx deleted file mode 100644 index a6c53d3..0000000 --- a/pages/_app.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import Head from 'next/head'; -import { useEffect, useState } from 'react'; - -import '../styles/button.css'; -import '../styles/card.css'; -import '../styles/code.css'; -import '../styles/globals.css'; -import '../styles/image.css'; -import '../styles/layout.css'; -import '../styles/navbar.css'; -import '../styles/print.css'; -import '../styles/search.css'; -import '../styles/tags.css'; -import '../styles/theme.css'; - -export default function Blog({ Component, pageProps }) { - var [dark, setDark] = useState(false); - useEffect(() => { - let colorSchemeQueryList = window.matchMedia('(prefers-color-scheme: dark)'); - setDark(!!colorSchemeQueryList.matches); - colorSchemeQueryList.addEventListener('change', e => setDark(!!e.matches)); - }, []); - - return <> - <Head> - <html lang='en-US' /> - <link rel='preload' as='style' href='/font/font.css' onLoad={() => this.rel = 'stylesheet'} /> - <meta property='og:url' content='https://blog.pipeframe.xyz' /> - <meta property='og:type' content='website' /> - <meta name='theme-color' content={dark ? '#0D0C1A' : '#EFE9F4'} /> - </Head> - <Component {...pageProps} /> - </>; -} diff --git a/pages/index.tsx b/pages/index.tsx deleted file mode 100644 index 634950d..0000000 --- a/pages/index.tsx +++ /dev/null @@ -1,101 +0,0 @@ -import Head from 'next/head'; -import Button from '../components/button'; -import PostCard from '../components/card'; -import Chapters, { chapter } from '../components/chapters'; -import Navbar, { MobileNavbar, NavbarItem } from '../components/navbar'; -import Seperator from '../components/seperator'; -import { ArticleMeta, getStaticProps as getBlogPage, RenderedArticle } from './post/[id]'; -import { PostsInfo } from './search'; - -import { useEffect, useState } from 'react'; - -// edit this to change the post displayed on the home page and the pinned posts -var posts = ['index', 'software']; - -export default function Home(props: { - posts: Array<{ - props: { - content: string; - meta: ArticleMeta; - }; - }>; -}) { - var [posts, setPosts] = useState<PostsInfo>({ posts: [], valid_tags: [] }); - - useEffect(() => { - (async () => { - var posts = await fetch('/posts.json'); - var postsJson: PostsInfo = await posts.json(); - setPosts(postsJson); - })(); - }, []); - - return <div> - <Head> - <title>Loek's Blog</title> - <meta property='og:site_name' content="Loek's blog" /> - <meta property='og:title' content="Loek's excruciatingly interesting blog" /> - <meta property='og:description' content='This is my personal blog website' /> - </Head> - <div className='centeredPage'> - <div className='titleWrapper'> - <h1>{props.posts[0].props.meta.title}</h1> - </div> - <div className='navAreaWrapper'> - <div className='sticky'> - <Navbar page='home' /> - <NavbarItem title='Pinned posts:' classList={['pinned']} /> - <Chapters - chapters={[ - ...props.posts.slice(1).map(post => { - return { - children: post.props.meta.chapters, - name: post.props.meta.title, - sectionLink: '/post/' + post.props.meta.id, - } as chapter; - }), - ]} - /> - </div> - </div> - <MobileNavbar /> - <div className='contentWrapper'> - {props.posts.map((post, index) => { - return <> - {index != 0 && <h1>{post.props.meta.title}</h1>} - <RenderedArticle content={post.props.content} /> - {index + 1 != props.posts.length && <Seperator />} - {index == 0 && <> - <h2>Recent posts</h2> - <div className='recentPosts'> - {posts.posts.sort((a, b) => ( - new Date(a.date).getTime() - - new Date(b.date).getTime() - )).reverse().slice(0, 4).map(post => { - return <PostCard post={post} />; - })} - </div> - - <div> - <Button text='Go to all posts' href='/search' /> - </div> - <Seperator /> - </>} - </>; - })} - </div> - </div> - </div>; -} - -export function getStaticProps() { - var postsContent = []; - - posts.forEach(id => { - postsContent.push(getBlogPage({ params: { id } })); - }); - - var staticProps = { props: { posts: postsContent } }; - - return staticProps; -} diff --git a/pages/post/[id].tsx b/pages/post/[id].tsx deleted file mode 100644 index 35a41e6..0000000 --- a/pages/post/[id].tsx +++ /dev/null @@ -1,216 +0,0 @@ -import Head from 'next/head'; -import { readdirSync, readFileSync } from 'fs'; -import { join } from 'path'; -import { CSSProperties, ReactNode, useState } from 'react'; -import ReactMarkdown from 'react-markdown'; -import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; -import rehypeRaw from 'rehype-raw'; -import gfm from 'remark-gfm'; - -import Chapters, { chapter } from '../../components/chapters'; -import Image from '../../components/image'; -import Navbar, { MobileNavbar } from '../../components/navbar'; -import Seperator from '../../components/seperator'; -import Tags from '../../components/tag'; - -export interface ArticleMeta { - title?: string; - subtitle?: string; - author?: string; - tags?: Array<string>; - date?: string; - chapters?: Array<chapter>; - cover?: string; - id?: string; -} - -var headingLevel = (input: string) => input?.match(/^[#]+/)[0]?.length || 0; - -var sectionID = (input: string) => - input - .replace(/[()\[\]{}!@#$%^&*<>?,./\;':"\\|=+]/g, '') - .replace(/\s/g, '-') - .toLowerCase(); - -function Heading(props: { - children?: ReactNode; - level?: number; -}) { - var HeadingTag = 'h' + props.level as keyof JSX.IntrinsicElements; - return <HeadingTag id={sectionID(props.children[0])} children={props.children} />; -} - -function Code(props: { - className?: string; - children?: ReactNode; -}) { - var language = /language-(\w+)/.exec(props.className || ''); - if (!language) return <code children={props.children} className={props.className} />; - return <SyntaxHighlighter - language={language[1]} - children={props.children.toString().trim()} - useInlineStyles={false} - PreTag='div' - style={{}} - />; -} - -export function RenderedArticle(props: { content: string; }) { - return <ReactMarkdown - rehypePlugins={[rehypeRaw]} - remarkPlugins={[gfm]} - children={props.content} - components={{ - img: ({ node, ...props }) => <Image src={props.src as string} alt={props.alt as string} />, - hr: Seperator, - - h1: Heading, // TODO: fix this garbage - h2: Heading, - h3: Heading, - h4: Heading, - h5: Heading, - h6: Heading, - - code: Code, - }} - />; -} - -var collapsed = false; -function toggle() { - collapsed = !collapsed; - document.documentElement.style.setProperty('--collapse-horizontal-gap', Number(collapsed).toString()); -} - -export default function Post(props: { - content: string; - meta: ArticleMeta; -}) { - return <div> - <Head> - <title>{props.meta.title} - Loek's Blog</title> - <meta property='og:site_name' content={props.meta.date} /> - <meta property='og:title' content={props.meta.title} /> - <meta property='og:description' content={props.meta.subtitle} /> - </Head> - <div className='centeredPage'> - <div className='titleWrapper'> - <h1 onClick={toggle}>{props.meta.title}</h1> - <p className='subtile'>{props.meta.subtitle}</p> - {props.meta.tags && <Tags tags={props.meta.tags} />} - </div> - <div className='navAreaWrapper'> - <div className='sticky'> - <Navbar /> - <Chapters chapters={props.meta.chapters} /> - </div> - </div> - <MobileNavbar /> - <div className='contentWrapper'> - <RenderedArticle content={props.content} /> - </div> - </div> - </div>; -} - -var parseTag = { - 'title': (val: string) => val, - 'subtitle': (val: string) => val, - 'author': (val: string) => val, - 'cover': (val: string) => val, - 'tags': (val: string) => val.split(',').map(i => i.trim()), - 'date': (val: string) => new Date(val).toDateString(), -}; - -function parseMeta(file: Array<string>): ArticleMeta { - var meta: ArticleMeta = {}; - - file.forEach(line => { - if (!line.startsWith('[meta]: ')) return; - var tags = line.match(/\[meta\]:\s+\<(.+?)\>\s+\((.+?)\)/); - if (!tags || !tags[1] || !tags[2]) return; - if (!parseTag.hasOwnProperty(tags[1])) return; - meta[tags[1]] = parseTag[tags[1]](tags[2]); - }); - - return meta; -} - -function parseToCRecursive(headings: Array<string>): Array<chapter> { - interface WIPchapter extends chapter { - unparsedChildren?: Array<string>; - } - var children: Array<WIPchapter> = []; - - var lowestLevel = headingLevel(headings[0]); - var currentChildIndex = -1; - for (var i in headings) { - var localLevel = headingLevel(headings[i]); - if (localLevel == lowestLevel) { - var chapterName = headings[i].match(/^[#]+\s+(.+)/)[1]; - children.push({ - name: chapterName, - sectionLink: '#' + sectionID(chapterName), - unparsedChildren: [], - }); - currentChildIndex += 1; - } else { - children[currentChildIndex].unparsedChildren.push(headings[i]); - } - } - - children.map(child => { - child.children = parseToCRecursive(child.unparsedChildren); - delete child.unparsedChildren; - - return child; - }); - - return children as Array<chapter>; -} - -function parseToC(file: Array<string>): Array<chapter> { - var fileAsStr = file.join('\n'); - fileAsStr = fileAsStr.replace(/```.*?```/gs, ''); // filter out code blocks from table of contents - var fileAsArr = fileAsStr.split('\n'); - var chapterStrings = fileAsArr.filter(line => line.startsWith('#')); - return parseToCRecursive(chapterStrings); -} - -function preprocessor(fileContent: string) { - var fileAsArr = fileContent.split('\n'); - var meta = parseMeta(fileAsArr); - meta.chapters = parseToC(fileAsArr); - var result = fileAsArr.join('\n').trim(); - return { meta, result }; -} - -export function getStaticProps(props: { params: { id: string; }; }) { - var filename = join('posts/', props.params.id + '.md'); - var filecontent = readFileSync(filename).toString().trim(); - - var parsed = preprocessor(filecontent); - parsed.meta.id = props.params.id; - - return { - props: { - content: parsed.result, - meta: parsed.meta, - }, - }; -} - -export function getStaticPaths() { - var files = readdirSync('posts').filter(f => f.endsWith('.md')); - - return { - paths: files.map((f) => { - return { - params: { - id: f.substr(0, f.length - 3), - }, - }; - }), - fallback: false, - }; -} diff --git a/pages/search.tsx b/pages/search.tsx deleted file mode 100644 index 14f5e15..0000000 --- a/pages/search.tsx +++ /dev/null @@ -1,170 +0,0 @@ -import Head from 'next/head'; -import Fuse from 'fuse.js'; -import { useEffect, useState } from 'react'; - -import Navbar, { MobileNavbar } from '../components/navbar'; -import Tags from '../components/tag'; - -import SearchOutlinedIcon from '@material-ui/icons/SearchOutlined'; - -function SearchBar(props: { searchFunction: () => void; }) { - return <div className='searchBar'> - <input - className='input' - id='searchInput' - placeholder='Search for posts...' - onChange={() => props.searchFunction()} - spellCheck='false' - autoComplete='off' - /> - <button className='button' onClick={() => props.searchFunction()}> - <SearchOutlinedIcon /> - </button> - </div>; -} - -export interface Post { - title: string; - subtitle: string; - author: string; - date: string; - id: string; - cover: string; - tags: Array<string>; -} - -export interface PostsInfo { - valid_tags: Array<string>; - posts: Array<Post>; -} - -function Post(props: { post: Post; }) { - return <a className='post' href={'/post/' + props.post.id}> - <b className='title'>{props.post.title}</b> - {props.post.subtitle && <p className='subtitle'>{props.post.subtitle}</p>} - <p className='authordate'> - Written by {props.post.author} on {new Date(props.post.date).toLocaleString('en-us', { - month: 'long', - day: 'numeric', - })} - </p> - <Tags tags={props.post.tags} /> - </a>; -} - -function Posts(props: { posts: Array<Post>; }) { - return <div className='searchResults'> - {props.posts.map(post => <Post post={post} key={Math.random().toString()} />)} - </div>; -} - -function searchFilter(query: string, tags: Array<string>) { - var output = { - query: '', - tags: [], - }; - - // remove string literals from tag matching - var queryWithoutLiterals = query.replace(/\".+?\"/g, ''); - - // find tags and remove them from the query - tags.forEach(tag => { - var index = queryWithoutLiterals.indexOf(tag); - if (index == -1) return; - - // remove tag from query - queryWithoutLiterals = queryWithoutLiterals.substr(0, index) - + queryWithoutLiterals.substr(index + tag.length); - - output.tags.push(tag); - }); - - // add back in the string literals (janky just gets pasted on end) - output.query = queryWithoutLiterals + ' ' - + (query.match(/\".+?\"/g) - ?.map(r => r.substr(1, r.length - 2)) - .join(' ') || ''); - return output; -} - -export default function SearchPage() { - var [posts, setPosts] = useState<PostsInfo>({ posts: [], valid_tags: [] }); - var [query, setQuery] = useState('-'); - var [visiblePosts, setVisiblePosts] = useState<Array<Post>>([]); - - var fuse = new Fuse(posts.posts, { - keys: [ - 'title', - 'subtitle', - 'author', - 'date', - 'id', - 'tags', - ], - isCaseSensitive: false, - }); - - useEffect(() => { - (async () => { - var query = new URLSearchParams(window.location.search).get('q') || ''; - if (query) { - (document.getElementById('searchInput') as HTMLInputElement).value = query; - } - - var posts = await fetch('/posts.json'); - var postsJson: PostsInfo = await posts.json(); - setPosts(postsJson); - setQuery(query); - })(); - }, []); - - useEffect(() => { - var search = searchFilter(query, posts.valid_tags); - - if (search.query.length == 0) { - var results = posts.posts; - } else { - var fuseSearch = fuse.search(search.query); - var results = fuseSearch.map(res => res.item); - } - - results = results.filter(result => { - for (var i in search.tags) { - if (!result.tags.includes(search.tags[i])) { - return false; - } - } - return true; - }); - results = results.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()); - setVisiblePosts(results); - }, [query]); - - return <div> - <Head> - <title>Search - Loek's Blog</title> - <meta property='og:site_name' content="Loek's blog" /> - <meta property='og:title' content="Loek's excruciatingly interesting blog" /> - <meta property='og:description' content='This is my personal blog website' /> - </Head> - <div className='centeredPage'> - <div className='titleWrapper'> - <h1>Search for posts</h1> - </div> - <div className='navAreaWrapper'> - <div className='sticky'> - <Navbar page='search' /> - </div> - </div> - <MobileNavbar /> - <div className='contentWrapper'> - <SearchBar - searchFunction={() => { - setTimeout(() => setQuery((document.getElementById('searchInput') as HTMLInputElement).value)); - }} - /> - <Posts posts={visiblePosts} /> - </div> - </div> - </div>; -} |