- {posts.map((data, i) => (
-
+ {posts.map(({ post }, i) => (
+
))}
{paginationJSX}
@@ -114,5 +126,3 @@ export const pageQuery = graphql`
}
}
`
-
-// tags
diff --git a/src/templates/blog-post.tsx b/src/templates/blog-post.tsx
index a20db6c..cdf336d 100644
--- a/src/templates/blog-post.tsx
+++ b/src/templates/blog-post.tsx
@@ -1,5 +1,5 @@
-import React from 'react'
-import { Link, graphql } from 'gatsby'
+import * as React from 'react'
+import { graphql, PageProps } from 'gatsby'
import Layout from '../components/layout'
import Seo from '../components/seo'
@@ -8,15 +8,19 @@ import { Section } from '../components/shared'
import Post from './post'
import { SideBar } from './sidebar'
-const BlogPostTemplate = ({ data, location, pageContext }) => {
+import type { PostNode, PageContext } from '../types'
+
+const BlogPostTemplate: React.FC<
+ PageProps<{ post: PostNode }, PageContext>
+> = ({ data, location, pageContext }) => {
const { post } = data
const { previewImg } = post.frontmatter
return (
{
+import type { PageContext, PostNode, SearchPost } from '../types'
+
+interface SearchData {
+ localSearchBlog: {
+ index: string
+ store: Record
+ }
+}
+
+const BlogSearchTemplate: React.FC> = ({
+ location,
+ pageContext
+}) => {
const queryData = useStaticQuery(graphql`
query {
localSearchBlog {
@@ -34,12 +44,12 @@ const BlogSearchTemplate = ({ location, pageContext }) => {
query,
queryData.localSearchBlog.index,
queryData.localSearchBlog.store
- )
- const postsJSX = []
+ ) as SearchPost[]
+ const postsJSX: React.ReactElement[] = []
if (query) {
posts.forEach((post, i) => {
- const _post = {
+ const _post: PostNode = {
id: post.id,
html: post.html,
fields: {
@@ -48,7 +58,7 @@ const BlogSearchTemplate = ({ location, pageContext }) => {
frontmatter: {
title: post.title,
date: post.date,
- previewImg: post.previewImg
+ previewImg: post.previewImg as PostNode['frontmatter']['previewImg']
}
}
diff --git a/src/templates/feed-list.tsx b/src/templates/feed-list.tsx
new file mode 100644
index 0000000..4eb2550
--- /dev/null
+++ b/src/templates/feed-list.tsx
@@ -0,0 +1,131 @@
+import { Link, graphql, PageProps } from 'gatsby'
+import * as React from 'react'
+import kebabCase from 'lodash/kebabCase'
+
+import Layout from '../components/layout'
+import Seo from '../components/seo'
+
+import { Section } from '../components/shared'
+import {
+ SectionHeading,
+ SectionDesc,
+ SolidButton
+} from '../components/shared/styledComponents'
+
+import Post from './postpreview'
+import { SideBar } from './sidebar'
+
+import type { PostNode, PageContext } from '../types'
+
+interface FeedListData {
+ remark: {
+ posts: Array<{ post: PostNode }>
+ }
+}
+
+const FeedListTemplate: React.FC> = ({
+ data,
+ location,
+ pageContext
+}) => {
+ const posts = data.remark.posts
+
+ const iniPath: string =
+ pageContext.page === 'index'
+ ? `/feed/`
+ : pageContext.page === 'year'
+ ? `/feed/${pageContext.year}/`
+ : pageContext.page === 'category'
+ ? `/feed/category/${kebabCase(pageContext.tag ?? '')}/`
+ : `/feed/`
+
+ const pageInfo = `Page ${pageContext.currentPage} of ${pageContext.numPages}`
+ const paginationJSX = Array.from(
+ { length: pageContext.numPages ?? 0 },
+ (_, i) => i + 1
+ ).map((pageIndex) => {
+ const link = pageIndex === 1 ? iniPath : `${iniPath}page/${pageIndex}`
+ return (
+
+ {pageIndex}
+
+ )
+ })
+ return (
+
+
+
+
+
+
+ {posts.map(({ post }, i) => (
+
+ ))}
+
+
{paginationJSX}
+
{pageInfo}
+
+
+
+
+
+
+
+ )
+}
+
+export default FeedListTemplate
+
+export const pageQuery = graphql`
+ query FeedListQuery(
+ $filter: MarkdownRemarkFilterInput!
+ $skip: Int!
+ $limit: Int!
+ ) {
+ remark: allMarkdownRemark(
+ filter: $filter
+ limit: $limit
+ skip: $skip
+ sort: { frontmatter: { date: DESC } }
+ ) {
+ posts: edges {
+ post: node {
+ html
+ fields {
+ slug
+ }
+ frontmatter {
+ title
+ date(formatString: "MMMM DD, YYYY")
+ author
+ authorLink
+ previewImg {
+ childImageSharp {
+ gatsbyImageData(layout: CONSTRAINED)
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+`
diff --git a/src/templates/feed-post.tsx b/src/templates/feed-post.tsx
new file mode 100644
index 0000000..cfc414b
--- /dev/null
+++ b/src/templates/feed-post.tsx
@@ -0,0 +1,71 @@
+import * as React from 'react'
+import { graphql, PageProps } from 'gatsby'
+
+import Layout from '../components/layout'
+import Seo from '../components/seo'
+
+import { Section } from '../components/shared'
+import Post from './post'
+import { SideBar } from './sidebar'
+
+import type { PostNode, PageContext } from '../types'
+
+const FeedPostTemplate: React.FC<
+ PageProps<{ post: PostNode }, PageContext>
+> = ({ data, location, pageContext }) => {
+ const { post } = data
+ const { previewImg } = post.frontmatter
+
+ return (
+
+
+
+
+ )
+}
+
+export default FeedPostTemplate
+
+export const pageQuery = graphql`
+ query FeedPostByPath($id: String!) {
+ post: markdownRemark(id: { eq: $id }) {
+ id
+ html
+ fields {
+ slug
+ }
+ frontmatter {
+ title
+ description
+ date(formatString: "MMMM DD, YYYY")
+ author
+ authorLink
+ previewImg {
+ childImageSharp {
+ gatsbyImageData(layout: CONSTRAINED)
+ }
+ }
+ tags
+ }
+ }
+ }
+`
diff --git a/src/templates/post/index.tsx b/src/templates/post/index.tsx
index 7e5d7b9..aac7291 100644
--- a/src/templates/post/index.tsx
+++ b/src/templates/post/index.tsx
@@ -1,12 +1,14 @@
-import React from 'react'
+import * as React from 'react'
import { Link } from 'gatsby'
import kebabCase from 'lodash/kebabCase'
-import { GatsbyImage } from 'gatsby-plugin-image'
+import { GatsbyImage, IGatsbyImageData } from 'gatsby-plugin-image'
import styled from 'styled-components'
-export const StyledLink = styled((props) => )`
+import type { PostNode } from '../../types'
+
+export const StyledLink = styled(Link)`
text-decoration: none;
color: black;
&:hover {
@@ -16,7 +18,8 @@ export const StyledLink = styled((props) => )`
`
const StyledTitle = styled.h5`
- font-family: 'Montserrat', 'HelveticaNeue', 'Helvetica Neue', Helvetica, Arial,
+ font-family:
+ 'Montserrat', 'HelveticaNeue', 'Helvetica Neue', Helvetica, Arial,
sans-serif;
margin-bottom: 0;
`
@@ -45,7 +48,8 @@ const StyledContent = styled.div`
padding: 15px 0;
color: #666666;
- font-family: 'Montserrat', 'HelveticaNeue', 'Helvetica Neue', Helvetica, Arial,
+ font-family:
+ 'Montserrat', 'HelveticaNeue', 'Helvetica Neue', Helvetica, Arial,
sans-serif;
a {
color: black;
@@ -116,7 +120,12 @@ const StyledContent = styled.div`
}
`
-const Post = ({ post, location }) => {
+interface PostProps {
+ post: PostNode
+ location?: Location
+}
+
+const Post: React.FC = ({ post }) => {
const tagsJSX = (post.frontmatter?.tags || []).map((tag, index) => (
{index > 0 && ', '}
@@ -134,14 +143,18 @@ const Post = ({ post, location }) => {
) : (
{post.frontmatter.author}
)
+ const previewImgData = post.frontmatter.previewImg?.childImageSharp
+ ?.gatsbyImageData as IGatsbyImageData | undefined
return (
-
+ {previewImgData && (
+
+ )}
{post.frontmatter.title}
diff --git a/src/templates/postpreview/index.tsx b/src/templates/postpreview/index.tsx
index e7be9a1..d0748bf 100644
--- a/src/templates/postpreview/index.tsx
+++ b/src/templates/postpreview/index.tsx
@@ -1,13 +1,15 @@
-import React from 'react'
+import * as React from 'react'
import { Link } from 'gatsby'
-import { GatsbyImage } from 'gatsby-plugin-image'
+import { GatsbyImage, IGatsbyImageData } from 'gatsby-plugin-image'
import styled from 'styled-components'
import getDescription from '../shared/getDescription'
-export const StyledLink = styled((props) => )`
+import type { PostNode } from '../../types'
+
+export const StyledLink = styled(Link)`
text-decoration: none;
color: black;
&:hover {
@@ -17,7 +19,8 @@ export const StyledLink = styled((props) => )`
`
const StyledTitle = styled.h5`
- font-family: 'Montserrat', 'HelveticaNeue', 'Helvetica Neue', Helvetica, Arial,
+ font-family:
+ 'Montserrat', 'HelveticaNeue', 'Helvetica Neue', Helvetica, Arial,
sans-serif;
font-size: 1rem;
margin-bottom: 0;
@@ -34,21 +37,31 @@ const StyledDesc = styled.p`
margin-bottom: 100px;
`
-const Post = ({ post }) => (
-
-
-
+interface PostPreviewProps {
+ post: PostNode
+}
- {post.frontmatter.title}
- {post.frontmatter.date}
- {getDescription(post.html)}
-
-
-)
+const PostPreview: React.FC = ({ post }) => {
+ const previewImgData = post.frontmatter.previewImg?.childImageSharp
+ ?.gatsbyImageData as IGatsbyImageData | undefined
+ return (
+
+
+ {previewImgData && (
+
+ )}
-export default Post
+ {post.frontmatter.title}
+ {post.frontmatter.date}
+ {getDescription(post.html)}
+
+
+ )
+}
+
+export default PostPreview
diff --git a/src/templates/shared/getDescription.ts b/src/templates/shared/getDescription.ts
index 9ad7784..9391c22 100644
--- a/src/templates/shared/getDescription.ts
+++ b/src/templates/shared/getDescription.ts
@@ -7,7 +7,7 @@ const extractContent = (s: string): string => {
return s
}
const getDescription = (content: string): string => {
- return extractContent(content).substr(0, 100) + '...'
+ return extractContent(content).slice(0, 100) + '...'
}
export default getDescription
diff --git a/src/templates/sidebar.tsx b/src/templates/sidebar.tsx
index b1f01cf..e64d44e 100644
--- a/src/templates/sidebar.tsx
+++ b/src/templates/sidebar.tsx
@@ -1,12 +1,17 @@
-import React, { useState, useEffect } from 'react'
+import * as React from 'react'
import { Link, navigate } from 'gatsby'
import kebabCase from 'lodash/kebabCase'
import styled, { css } from 'styled-components'
-import { pathPrefix } from '../../gatsby-config.js'
+import { FaRss } from 'react-icons/fa'
+import { pathPrefix, siteMetadata } from '../../gatsby-config'
import { StyledHeading } from '../styledComponents/blog'
import { SolidButton } from '../components/shared/styledComponents'
+import type { PageContext, RecentPost, TagCount } from '../types'
+
+const rssFeedUrl = `${siteMetadata.siteUrl}rss.xml`
+
const styles = css`
color: #314351;
opacity: 0.8;
@@ -29,10 +34,16 @@ const linkStyles = css`
text-decoration: underline;
}
`
-const ArchiveLink = styled((props) => )`
+const ArchiveLink = styled(Link)`
${linkStyles}
`
-const RecentPostLink = styled((props) => )`
+const RssAnchor = styled.a`
+ ${linkStyles}
+ display: flex;
+ align-items: center;
+ gap: 6px;
+`
+const RecentPostLink = styled(Link)`
${linkStyles}
padding: 3px;
border-bottom: 3px solid #e1e1e1;
@@ -59,30 +70,44 @@ const StyledPara = styled.p`
${styles}
`
-const Archives = ({ archives }) => (
+interface ArchivesProps {
+ archives: Record
+ basePath?: string
+}
+
+const Archives: React.FC = ({ archives, basePath = '' }) => (
<>
{Object.keys(archives)
.sort()
.reverse()
- .map((year, index) => (
-
+ .map((year) => (
+
{year} ({archives[year]})
))}
>
)
-const Categories = ({ tags }) => (
+interface CategoriesProps {
+ tags: TagCount[]
+ basePath?: string
+}
+
+const Categories: React.FC = ({ tags, basePath = '' }) => (
<>
{tags.map((tag, i) => (
-
+
{tag.name} ({tag.totalCount})
))}
>
)
-const RecentPosts = ({ posts }) => (
+interface RecentPostsProps {
+ posts: RecentPost[]
+}
+
+const RecentPosts: React.FC = ({ posts }) => (
<>
{posts.map((post) => (
@@ -92,22 +117,34 @@ const RecentPosts = ({ posts }) => (
>
)
-export const SideBar = ({ pageContext, location, notFoundPage = false }) => {
+interface SideBarProps {
+ pageContext: PageContext
+ location: Location
+ notFoundPage?: boolean
+ basePath?: string
+}
+
+export const SideBar: React.FC = ({
+ pageContext,
+ location,
+ notFoundPage = false,
+ basePath = ''
+}) => {
const params = new URLSearchParams(location.search.substring(1))
const queryUrl = params?.get('s') || ''
- const [query, setQuery] = useState(queryUrl)
- useEffect(() => {
+ const [query, setQuery] = React.useState(queryUrl)
+ React.useEffect(() => {
setQuery(queryUrl)
}, [queryUrl])
- const handleSubmit = (event) => {
+ const handleSubmit = (event: React.FormEvent) => {
event.preventDefault()
navigate('/search?s=' + query)
}
- const _handleKeyDown = (event) => {
+ const _handleKeyDown = (event: React.KeyboardEvent) => {
if (event.key === 'Enter') {
- handleSubmit(event)
+ navigate('/search?s=' + query)
}
}
@@ -130,13 +167,16 @@ export const SideBar = ({ pageContext, location, notFoundPage = false }) => {
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
- onKeyDown={(e) => _handleKeyDown(e)}
+ onKeyDown={_handleKeyDown}
className="form-control"
placeholder="Search"
aria-label="Search"
aria-describedby="button-addon2"
/>
-
+ navigate('/search?s=' + query)}
+ >
Search
@@ -168,11 +208,17 @@ export const SideBar = ({ pageContext, location, notFoundPage = false }) => {
<>
Archives
-
+
Categories
-
+
+
+
+ Subscribe
+
+ RSS Feed
+
>
)}
diff --git a/src/types/assets.d.ts b/src/types/assets.d.ts
new file mode 100644
index 0000000..8b62da4
--- /dev/null
+++ b/src/types/assets.d.ts
@@ -0,0 +1,25 @@
+declare module '*.png'
+declare module '*.jpg'
+declare module '*.jpeg'
+declare module '*.svg'
+declare module '*.gif'
+declare module '*.webp'
+declare module '*.ico'
+declare module '*.css'
+declare module '*.scss'
+
+declare module 'react-use-flexsearch' {
+ export function useFlexSearch(
+ query: string | null,
+ index: string,
+ store: Record
+ ): Array>
+}
+
+declare module '@browniebroke/gatsby-image-gallery' {
+ import * as React from 'react'
+ const Gallery: React.FC<
+ Record & { images: Array> }
+ >
+ export default Gallery
+}
diff --git a/src/types/declarations.d.ts b/src/types/declarations.d.ts
new file mode 100644
index 0000000..ae088f7
--- /dev/null
+++ b/src/types/declarations.d.ts
@@ -0,0 +1,10 @@
+import 'react'
+
+declare module 'react' {
+ interface InputHTMLAttributes extends HTMLAttributes {
+ rules?: string
+ }
+ interface TextareaHTMLAttributes extends HTMLAttributes {
+ rules?: string
+ }
+}
diff --git a/src/types/index.ts b/src/types/index.ts
index e69da0f..064ecff 100644
--- a/src/types/index.ts
+++ b/src/types/index.ts
@@ -1 +1,2 @@
export * from './nostr'
+export * from './site'
diff --git a/src/types/site.ts b/src/types/site.ts
new file mode 100644
index 0000000..a8fd41a
--- /dev/null
+++ b/src/types/site.ts
@@ -0,0 +1,139 @@
+import { IGatsbyImageData } from 'gatsby-plugin-image'
+import * as React from 'react'
+import type { IconType } from 'react-icons'
+
+export type PageType = 'index' | 'year' | 'category' | 'search'
+
+export interface RecentPost {
+ slug: string
+ title: string
+}
+
+export interface TagCount {
+ name: string
+ totalCount: number
+}
+
+export interface PageContext {
+ page: PageType
+ archives: Record
+ recentPosts: RecentPost[]
+ tags: TagCount[]
+ filter?: unknown
+ limit?: number
+ skip?: number
+ numPages?: number
+ currentPage?: number
+ year?: string
+ tag?: string
+}
+
+export interface PostFrontmatter {
+ title: string
+ date: string
+ author?: string
+ authorLink?: string
+ description?: string
+ tags?: string[]
+ heading?: string
+ desc?: string
+ previewImg?: {
+ childImageSharp?: {
+ gatsbyImageData: IGatsbyImageData
+ }
+ }
+}
+
+export interface PostNode {
+ id?: string
+ html: string
+ fields: {
+ slug: string
+ }
+ frontmatter: PostFrontmatter
+}
+
+export interface LayoutProps {
+ location: Location
+ heroSection?: boolean
+ heading?: string
+ desc?: string
+ children?: React.ReactNode
+}
+
+export interface HeroSectionProps {
+ location: Location
+ heading?: string
+ desc?: string
+}
+
+export interface NavibarProps {
+ location: Location
+}
+
+export interface ContainerProps {
+ children?: React.ReactNode
+}
+
+export interface SectionProps {
+ children?: React.ReactNode
+ color?: string
+ bgColor?: string
+ bottomArrow?: boolean
+}
+
+export interface SeoProps {
+ description?: string
+ lang?: string
+ meta?: Array<{ name?: string; property?: string; content?: string }>
+ title?: string
+ previewImg?: string
+}
+
+export interface SolidButtonProps {
+ children: React.ReactNode
+ theme?: 'light' | 'dark'
+ type?: 'button' | 'submit' | 'reset'
+ disabled?: boolean
+ onClick?: React.MouseEventHandler
+}
+
+export interface OutlineButtonProps {
+ children: React.ReactNode
+}
+
+export interface FaqProps {
+ question: string
+ answer: string
+}
+
+export interface FeatureProps {
+ title: string
+ desc: string
+ Icon: IconType
+ className?: string
+}
+
+export interface ReasonProps {
+ text: string
+ bgColor?: string
+}
+
+export interface ArtProps {
+ src: string
+ info?: string
+}
+
+export interface StyledAnchorProps {
+ href: string
+ children?: React.ReactNode
+}
+
+export type SearchPost = {
+ id: string
+ html: string
+ slug: string
+ title: string
+ date: string
+ previewImg?: PostFrontmatter['previewImg']
+}
diff --git a/static/fonts/Montserrat-300.woff2 b/static/fonts/Montserrat-300.woff2
new file mode 100644
index 0000000..a83d284
Binary files /dev/null and b/static/fonts/Montserrat-300.woff2 differ
diff --git a/static/fonts/Montserrat-400.woff2 b/static/fonts/Montserrat-400.woff2
new file mode 100644
index 0000000..6fbeafa
Binary files /dev/null and b/static/fonts/Montserrat-400.woff2 differ
diff --git a/static/fonts/Montserrat-500.woff2 b/static/fonts/Montserrat-500.woff2
new file mode 100644
index 0000000..429a87d
Binary files /dev/null and b/static/fonts/Montserrat-500.woff2 differ
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..65f1972
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,20 @@
+{
+ "compilerOptions": {
+ "target": "ESNext",
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "esModuleInterop": true,
+ "allowSyntheticDefaultImports": true,
+ "strict": true,
+ "forceConsistentCasingInFileNames": true,
+ "noEmit": true,
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "react-jsx"
+ },
+ "include": ["src/**/*", "gatsby-config.d.ts"],
+ "exclude": ["node_modules", "public", ".cache"]
+}