diff --git a/themes/Medium/Layout404.js b/themes/Medium/Layout404.js
new file mode 100644
index 00000000..cd28a607
--- /dev/null
+++ b/themes/Medium/Layout404.js
@@ -0,0 +1,6 @@
+
+export const Layout404 = () => {
+ return
+ 404 Not found.
+
+}
diff --git a/themes/Medium/LayoutArchive.js b/themes/Medium/LayoutArchive.js
new file mode 100644
index 00000000..bf9ebdc5
--- /dev/null
+++ b/themes/Medium/LayoutArchive.js
@@ -0,0 +1,6 @@
+export const LayoutArchive = (props) => {
+ // const { posts, tags, categories, postCount } = props
+ return
+ Archive Page
+
+}
diff --git a/themes/Medium/LayoutBase.js b/themes/Medium/LayoutBase.js
new file mode 100644
index 00000000..866d7e64
--- /dev/null
+++ b/themes/Medium/LayoutBase.js
@@ -0,0 +1,33 @@
+import CommonHead from '@/components/CommonHead'
+import React from 'react'
+import Footer from './components/Footer'
+import InfoCard from './components/InfoCard'
+import LogoBar from './components/LogoBar'
+
+/**
+ * 基础布局 采用左右两侧布局,移动端使用顶部导航栏
+
+ * @returns {JSX.Element}
+ * @constructor
+ */
+const LayoutBase = props => {
+ const { children, meta } = props
+
+ return (
+
+
+
+
+
+
+
+
+ {children}
+
+
+
+
+ )
+}
+
+export default LayoutBase
diff --git a/themes/Medium/LayoutCategory.js b/themes/Medium/LayoutCategory.js
new file mode 100644
index 00000000..3a4bb0e9
--- /dev/null
+++ b/themes/Medium/LayoutCategory.js
@@ -0,0 +1,8 @@
+import LayoutBase from './LayoutBase'
+
+export const LayoutCategory = (props) => {
+ const { category } = props
+ return
+ Category - {category}
+
+}
diff --git a/themes/Medium/LayoutCategoryIndex.js b/themes/Medium/LayoutCategoryIndex.js
new file mode 100644
index 00000000..fcde0a08
--- /dev/null
+++ b/themes/Medium/LayoutCategoryIndex.js
@@ -0,0 +1,8 @@
+import LayoutBase from './LayoutBase'
+
+export const LayoutCategoryIndex = (props) => {
+ // const { tags, allPosts, categories, postCount, latestPosts } = props
+ return
+ CategoryIndex
+
+}
diff --git a/themes/Medium/LayoutIndex.js b/themes/Medium/LayoutIndex.js
new file mode 100644
index 00000000..3d336bd7
--- /dev/null
+++ b/themes/Medium/LayoutIndex.js
@@ -0,0 +1,9 @@
+import BlogPostListPage from './components/BlogPostListPage'
+import LayoutBase from './LayoutBase'
+
+export const LayoutIndex = (props) => {
+ // const { posts, tags, meta, categories, postCount, latestPosts } = props
+ return
+
+
+}
diff --git a/themes/Medium/LayoutPage.js b/themes/Medium/LayoutPage.js
new file mode 100644
index 00000000..d4355d46
--- /dev/null
+++ b/themes/Medium/LayoutPage.js
@@ -0,0 +1,8 @@
+import LayoutBase from '../Hexo/LayoutBase'
+
+export const LayoutPage = (props) => {
+ const { page } = props
+ return
+ Page - {page}
+
+}
diff --git a/themes/Medium/LayoutSearch.js b/themes/Medium/LayoutSearch.js
new file mode 100644
index 00000000..c7469a60
--- /dev/null
+++ b/themes/Medium/LayoutSearch.js
@@ -0,0 +1,31 @@
+import { useRouter } from 'next/router'
+import LayoutBase from './LayoutBase'
+
+export const LayoutSearch = (props) => {
+ const { posts } = props
+ let filteredPosts
+ const searchKey = getSearchKey()
+ if (searchKey) {
+ filteredPosts = posts.filter(post => {
+ const tagContent = post.tags ? post.tags.join(' ') : ''
+ const searchContent = post.title + post.summary + tagContent
+ return searchContent.toLowerCase().includes(searchKey.toLowerCase())
+ })
+ } else {
+ filteredPosts = posts
+ }
+
+ console.log(filteredPosts)
+
+ return
+ Search {searchKey}
+
+}
+
+function getSearchKey () {
+ const router = useRouter()
+ if (router.query && router.query.s) {
+ return router.query.s
+ }
+ return null
+}
diff --git a/themes/Medium/LayoutSlug.js b/themes/Medium/LayoutSlug.js
new file mode 100644
index 00000000..cb621b12
--- /dev/null
+++ b/themes/Medium/LayoutSlug.js
@@ -0,0 +1,53 @@
+import BLOG from '@/blog.config'
+import { getPageTableOfContents } from 'notion-utils'
+import 'prismjs'
+import 'prismjs/components/prism-bash'
+import 'prismjs/components/prism-javascript'
+import 'prismjs/components/prism-markup'
+import 'prismjs/components/prism-python'
+import 'prismjs/components/prism-typescript'
+import { Code, Collection, CollectionRow, Equation, NotionRenderer } from 'react-notion-x'
+import LayoutBase from './LayoutBase'
+import Comment from '@/components/Comment'
+
+const mapPageUrl = id => {
+ return 'https://www.notion.so/' + id.replace(/-/g, '')
+}
+
+export const LayoutSlug = (props) => {
+ const { post } = props
+ const meta = {
+ title: `${post.title} | ${BLOG.TITLE}`,
+ description: post.summary,
+ type: 'article',
+ tags: post.tags
+ }
+
+ if (post?.blockMap?.block) {
+ post.content = Object.keys(post.blockMap.block)
+ post.toc = getPageTableOfContents(post, post.blockMap)
+ }
+
+ return
+ {post?.title}
+ {/* Notion文章主体 */}
+
+ {post.blockMap && (
+
+ )}
+
+
+
+
+
+
+}
diff --git a/themes/Medium/LayoutTag.js b/themes/Medium/LayoutTag.js
new file mode 100644
index 00000000..5cfacb20
--- /dev/null
+++ b/themes/Medium/LayoutTag.js
@@ -0,0 +1,8 @@
+import LayoutBase from './LayoutBase'
+
+export const LayoutTag = (props) => {
+ const { tag } = props
+ return
+ Tag - {tag}
+
+}
diff --git a/themes/Medium/LayoutTagIndex.js b/themes/Medium/LayoutTagIndex.js
new file mode 100644
index 00000000..01a16e62
--- /dev/null
+++ b/themes/Medium/LayoutTagIndex.js
@@ -0,0 +1,8 @@
+import LayoutBase from './LayoutBase'
+
+export const LayoutTagIndex = (props) => {
+ // const { tags, categories, postCount, latestPosts } = props
+ return
+ TagIndex
+
+}
diff --git a/themes/Medium/components/BlogPostCard.js b/themes/Medium/components/BlogPostCard.js
new file mode 100644
index 00000000..3b63256e
--- /dev/null
+++ b/themes/Medium/components/BlogPostCard.js
@@ -0,0 +1,80 @@
+import BLOG from '@/blog.config'
+import { faAngleRight, faFolder } from '@fortawesome/free-solid-svg-icons'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import Link from 'next/link'
+import React from 'react'
+import { Code, Collection, CollectionRow, Equation, NotionRenderer } from 'react-notion-x'
+import TagItemMini from './TagItemMini'
+import CONFIG_MEDIUM from '../config_medium'
+import { useGlobal } from '@/lib/global'
+
+const BlogPostCard = ({ post, showSummary }) => {
+ const showPreview = CONFIG_MEDIUM.POST_LIST_PREVIEW && post.blockMap
+ const { locale } = useGlobal()
+ return (
+
+
+
+
+
+ {post.title}
+
+
+
+
+ {post.date.start_date}
+
+
+ {(!showPreview || showSummary) &&
+ {post.summary}
+
}
+
+ {showPreview &&
}
+
+ {/*
*/}
+
+
+
+
+
+
+ )
+}
+
+const mapPageUrl = id => {
+ return 'https://www.notion.so/' + id.replace(/-/g, '')
+}
+
+export default BlogPostCard
diff --git a/themes/Medium/components/BlogPostListEmpty.js b/themes/Medium/components/BlogPostListEmpty.js
new file mode 100644
index 00000000..86977fd0
--- /dev/null
+++ b/themes/Medium/components/BlogPostListEmpty.js
@@ -0,0 +1,12 @@
+
+/**
+ * 空白博客 列表
+ * @returns {JSX.Element}
+ * @constructor
+ */
+const BlogPostListEmpty = ({ currentSearch }) => {
+ return
+
没有找到文章 {(currentSearch &&
{currentSearch}
)}
+
+}
+export default BlogPostListEmpty
diff --git a/themes/Medium/components/BlogPostListPage.js b/themes/Medium/components/BlogPostListPage.js
new file mode 100644
index 00000000..fe91b9b0
--- /dev/null
+++ b/themes/Medium/components/BlogPostListPage.js
@@ -0,0 +1,32 @@
+import BlogPostCard from './BlogPostCard'
+import BLOG from '@/blog.config'
+import BlogPostListEmpty from './BlogPostListEmpty'
+import PaginationSimple from './PaginationSimple'
+
+/**
+ * 文章列表分页表格
+ * @param page 当前页
+ * @param posts 所有文章
+ * @param tags 所有标签
+ * @returns {JSX.Element}
+ * @constructor
+ */
+const BlogPostListPage = ({ page = 1, posts = [], postCount }) => {
+ const totalPage = Math.ceil(postCount / BLOG.POSTS_PER_PAGE)
+
+ if (!posts || posts.length === 0) {
+ return
+ } else {
+ return (
+
+ {/* 文章列表 */}
+ {posts.map(post => (
+
+ ))}
+
+
+ )
+ }
+}
+
+export default BlogPostListPage
diff --git a/themes/Medium/components/Card.js b/themes/Medium/components/Card.js
new file mode 100644
index 00000000..d24c046e
--- /dev/null
+++ b/themes/Medium/components/Card.js
@@ -0,0 +1,9 @@
+const Card = ({ children, headerSlot, className }) => {
+ return
+ <>{headerSlot}>
+
+
+}
+export default Card
diff --git a/themes/Medium/components/Footer.js b/themes/Medium/components/Footer.js
new file mode 100644
index 00000000..57ad7797
--- /dev/null
+++ b/themes/Medium/components/Footer.js
@@ -0,0 +1,30 @@
+import { faCopyright, faEye, faShieldAlt, faUsers, faHeart } from '@fortawesome/free-solid-svg-icons'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import React from 'react'
+import BLOG from '@/blog.config'
+
+const Footer = ({ title }) => {
+ const d = new Date()
+ const currentYear = d.getFullYear()
+ const startYear = BLOG.SINCE && BLOG.SINCE !== currentYear && BLOG.SINCE + '-'
+ return (
+
+ )
+}
+
+export default Footer
diff --git a/themes/Medium/components/InfoCard.js b/themes/Medium/components/InfoCard.js
new file mode 100644
index 00000000..47d300f6
--- /dev/null
+++ b/themes/Medium/components/InfoCard.js
@@ -0,0 +1,27 @@
+import BLOG from '@/blog.config'
+import Image from 'next/image'
+import Router from 'next/router'
+import React from 'react'
+import SocialButton from './SocialButton'
+
+const InfoCard = () => {
+ return <>
+
+
{ Router.push('/about') }}>
+
+
+
{BLOG.AUTHOR}
+
{BLOG.BIO}
+
+
+ >
+}
+
+export default InfoCard
diff --git a/themes/Medium/components/LogoBar.js b/themes/Medium/components/LogoBar.js
new file mode 100644
index 00000000..75a23aec
--- /dev/null
+++ b/themes/Medium/components/LogoBar.js
@@ -0,0 +1,25 @@
+import BLOG from '@/blog.config'
+import { useGlobal } from '@/lib/global'
+import { faEnvelope } from '@fortawesome/free-solid-svg-icons'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import Link from 'next/link'
+
+export default function LogoBar () {
+ const { locale } = useGlobal()
+
+ return
+}
diff --git a/themes/Medium/components/PaginationSimple.js b/themes/Medium/components/PaginationSimple.js
new file mode 100644
index 00000000..5eb30af8
--- /dev/null
+++ b/themes/Medium/components/PaginationSimple.js
@@ -0,0 +1,42 @@
+import BLOG from '@/blog.config'
+import Link from 'next/link'
+import { useRouter } from 'next/router'
+import { useGlobal } from '@/lib/global'
+
+/**
+ * 简易翻页插件
+ * @param page 当前页码
+ * @param showNext 是否有下一页
+ * @returns {JSX.Element}
+ * @constructor
+ */
+const PaginationSimple = ({ page, showNext }) => {
+ const { locale } = useGlobal()
+ const router = useRouter()
+ const currentPage = +page
+ return (
+
+ )
+}
+
+export default PaginationSimple
diff --git a/themes/Medium/components/SocialButton.js b/themes/Medium/components/SocialButton.js
new file mode 100644
index 00000000..c50f5984
--- /dev/null
+++ b/themes/Medium/components/SocialButton.js
@@ -0,0 +1,36 @@
+import BLOG from '@/blog.config'
+import { faGithub, faTelegram, faTwitter, faWeibo } from '@fortawesome/free-brands-svg-icons'
+import { faEnvelope, faRss } from '@fortawesome/free-solid-svg-icons'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import React from 'react'
+
+/**
+ * 社交联系方式按钮组
+ * @returns {JSX.Element}
+ * @constructor
+ */
+const SocialButton = () => {
+ return
+
+ {BLOG.CONTACT_GITHUB &&
+
+ }
+ {BLOG.CONTACT_TWITTER &&
+
+ }
+ {BLOG.CONTACT_TELEGRAM &&
+
+ }
+ {BLOG.CONTACT_WEIBO &&
+
+ }
+ {BLOG.CONTACT_EMAIL &&
+
+ }
+
+
+
+
+
+}
+export default SocialButton
diff --git a/themes/Medium/components/TagItemMini.js b/themes/Medium/components/TagItemMini.js
new file mode 100644
index 00000000..9fec9e35
--- /dev/null
+++ b/themes/Medium/components/TagItemMini.js
@@ -0,0 +1,17 @@
+import { faTag } from '@fortawesome/free-solid-svg-icons'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import Link from 'next/link'
+
+const TagItemMini = ({ tag, selected = false }) => {
+ return
+
+ {selected && } {tag.name + (tag.count ? `(${tag.count})` : '')}
+
+
+}
+
+export default TagItemMini
diff --git a/themes/Medium/config_medium.js b/themes/Medium/config_medium.js
new file mode 100644
index 00000000..9cb83c84
--- /dev/null
+++ b/themes/Medium/config_medium.js
@@ -0,0 +1,13 @@
+const CONFIG_MEDIUM = {
+
+ POST_LIST_COVER: true, // 文章列表显示图片封面
+ POST_LIST_PREVIEW: true, // 显示文章预览
+
+ // 菜单
+ MENU_ABOUT: true, // 显示关于
+ MENU_CATEGORY: true, // 显示分类
+ MENU_TAG: true, // 显示标签
+ MENU_ARCHIVE: true, // 显示归档
+ MENU_SEARCH: true // 显示搜索
+}
+export default CONFIG_MEDIUM
diff --git a/themes/Medium/index.js b/themes/Medium/index.js
new file mode 100644
index 00000000..5bcc655c
--- /dev/null
+++ b/themes/Medium/index.js
@@ -0,0 +1,12 @@
+import CONFIG_MEDIUM from './config_medium'
+export { CONFIG_MEDIUM as THEME_CONFIG }
+export { LayoutIndex } from './LayoutIndex'
+export { LayoutSearch } from './LayoutSearch'
+export { LayoutArchive } from './LayoutArchive'
+export { LayoutSlug } from './LayoutSlug'
+export { Layout404 } from './Layout404'
+export { LayoutCategory } from './LayoutCategory'
+export { LayoutCategoryIndex } from './LayoutCategoryIndex'
+export { LayoutPage } from './LayoutPage'
+export { LayoutTag } from './LayoutTag'
+export { LayoutTagIndex } from './LayoutTagIndex'
diff --git a/themes/index.js b/themes/index.js
index b28dd755..d17405e5 100644
--- a/themes/index.js
+++ b/themes/index.js
@@ -5,4 +5,5 @@
// export * from './Empty' // 空主题
// export * from './NEXT'
// export * from './Fukasawa'
-export * from './Hexo'
+// export * from './Hexo'
+export * from './Medium'