From 56ffca17587046bfc6da2e82c4574e4e1cb77c38 Mon Sep 17 00:00:00 2001 From: tangly1024 Date: Tue, 29 Mar 2022 10:55:16 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E8=B4=A1=E7=8C=AE=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E7=9B=B8=E5=85=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 20 ++-- components/DebugPanel.js | 2 +- lib/global.js | 3 +- lib/theme.js | 1 - themes/empty/Layout404.js | 6 -- themes/empty/LayoutArchive.js | 69 ------------- themes/empty/LayoutBase.js | 135 ------------------------- themes/empty/LayoutCategory.js | 48 --------- themes/empty/LayoutCategoryIndex.js | 25 ----- themes/empty/LayoutIndex.js | 52 ---------- themes/empty/LayoutPage.js | 52 ---------- themes/empty/LayoutSearch.js | 60 ----------- themes/empty/LayoutSlug.js | 100 ------------------ themes/empty/LayoutTag.js | 49 --------- themes/empty/LayoutTagIndex.js | 24 ----- themes/empty/components/ArticleLock.js | 40 -------- themes/empty/components/SearchInput.js | 87 ---------------- themes/empty/config_empty.js | 4 - themes/empty/index.js | 25 ----- themes/index.js | 12 +-- 20 files changed, 19 insertions(+), 795 deletions(-) delete mode 100644 themes/empty/Layout404.js delete mode 100644 themes/empty/LayoutArchive.js delete mode 100644 themes/empty/LayoutBase.js delete mode 100644 themes/empty/LayoutCategory.js delete mode 100644 themes/empty/LayoutCategoryIndex.js delete mode 100644 themes/empty/LayoutIndex.js delete mode 100644 themes/empty/LayoutPage.js delete mode 100644 themes/empty/LayoutSearch.js delete mode 100644 themes/empty/LayoutSlug.js delete mode 100644 themes/empty/LayoutTag.js delete mode 100644 themes/empty/LayoutTagIndex.js delete mode 100644 themes/empty/components/ArticleLock.js delete mode 100644 themes/empty/components/SearchInput.js delete mode 100644 themes/empty/config_empty.js delete mode 100644 themes/empty/index.js diff --git a/README.md b/README.md index dbec7c25..92b2c1b6 100644 --- a/README.md +++ b/README.md @@ -50,12 +50,9 @@ |--|--|--|--| | [预览NEXT](https://preview.tangly1024.com/?theme=next) | [预览MEDIUM](https://preview.tangly1024.com/?theme=medium) | [预览HEXO](https://preview.tangly1024.com/?theme=hexo) | [预览FUKASAWA](https://preview.tangly1024.com/?theme=fukasawa) | +*只需修改`blog.config.js`文件的`THEME`即可实现主题切换。* 没找到喜欢的主题?[贡献](/CONTRIBUTING.md)一个吧~ -*只需修改`blog.config.js`文件的`THEME`即可实现主题切换。* - -## 更新日志 -请移步 [更新文档](https://docs.tangly1024.com/zh/changelog)查看 ## 快速起步 @@ -80,11 +77,15 @@ yarn run start # 本地启动NextJS服务 ## 引用技术 -- **框架**: Next.js +- **框架**: [Next.js](https://nextjs.org) - **样式**: [Tailwind CSS](https://www.tailwindcss.cn/) 和 `@tailwindcss/jit` compiler - **渲染**: [React-notion-x](https://github.com/NotionX/react-notion-x) -- **评论**: Gitalk, Cusdis, Utterances -- **图标**:[fontawesome](https://fontawesome.com/v5.15/icons?d=gallery) +- **评论**: [Giscus](https://giscus.app/zh-CN), [Gitalk](https://gitalk.github.io), [Cusdis](https://gitalk.github.io), [Utterances](https://utteranc.es) +- **图标**:[fontawesome v5.15](https://fontawesome.com/v5.15/icons?d=gallery) + +## 更新日志 +请移步 [更新文档](https://docs.tangly1024.com/zh/changelog)查看 + ## 致谢 感谢Craig Hart发起的Nobelium项目 @@ -92,13 +93,16 @@ yarn run start # 本地启动NextJS服务 Craig Hart
Craig Hart -## Contributors +## 贡献者
tangly1024
tangly1024

🎫 🔧 🎨 🐛
uWayLu
uWayLu

🔧 🐛
+十分期待你的[贡献](/CONTRIBUTING.md),一起来完善这个项目~ + + ## License The MIT License. diff --git a/components/DebugPanel.js b/components/DebugPanel.js index b1623e50..50add94a 100644 --- a/components/DebugPanel.js +++ b/components/DebugPanel.js @@ -2,7 +2,7 @@ import BLOG from '@/blog.config' import * as ThemeMap from '@/themes' import { useState } from 'react' import Select from './Select' -import { ALL_THEME } from '@/lib/theme' +import { ALL_THEME } from '@/themes' import { useGlobal } from '@/lib/global' /** * diff --git a/lib/global.js b/lib/global.js index 8ca8deed..e1692217 100644 --- a/lib/global.js +++ b/lib/global.js @@ -2,7 +2,8 @@ import { generateLocaleDict, initLocale } from './lang' import { createContext, useContext, useEffect, useState } from 'react' import Router from 'next/router' import BLOG from '@/blog.config' -import { ALL_THEME, initDarkMode, initTheme, saveThemeToCookies } from '@/lib/theme' +import { initDarkMode, initTheme, saveThemeToCookies } from '@/lib/theme' +import { ALL_THEME } from '@/themes' const GlobalContext = createContext() let hasInit = false diff --git a/lib/theme.js b/lib/theme.js index 55d47aad..1cecad78 100644 --- a/lib/theme.js +++ b/lib/theme.js @@ -1,7 +1,6 @@ import cookie from 'react-cookies' import BLOG from '@/blog.config' -export const ALL_THEME = ['hexo', 'next', 'medium', 'fukasawa', 'empty'] /** * 初始化主题 * @param isDarkMode diff --git a/themes/empty/Layout404.js b/themes/empty/Layout404.js deleted file mode 100644 index cd28a607..00000000 --- a/themes/empty/Layout404.js +++ /dev/null @@ -1,6 +0,0 @@ - -export const Layout404 = () => { - return
- 404 Not found. -
-} diff --git a/themes/empty/LayoutArchive.js b/themes/empty/LayoutArchive.js deleted file mode 100644 index 04a6d647..00000000 --- a/themes/empty/LayoutArchive.js +++ /dev/null @@ -1,69 +0,0 @@ -import BLOG from '@/blog.config' -import { useGlobal } from '@/lib/global' -import Link from 'next/link' -import LayoutBase from './LayoutBase' - -export const LayoutArchive = props => { - const { posts } = props - const { locale } = useGlobal() - const postsSortByDate = Object.create(posts) - - postsSortByDate.sort((a, b) => { - const dateA = new Date(a?.date.start_date || a.createdTime) - const dateB = new Date(b?.date.start_date || b.createdTime) - return dateB - dateA - }) - - const meta = { - title: `${locale.NAV.ARCHIVE} | ${BLOG.TITLE}`, - description: BLOG.DESCRIPTION, - type: 'website' - } - - const archivePosts = {} - - postsSortByDate.forEach(post => { - const date = post.date.start_date.slice(0, 7) - if (archivePosts[date]) { - archivePosts[date].push(post) - } else { - archivePosts[date] = [post] - } - }) - return ( - -
- {Object.keys(archivePosts).map(archiveTitle => ( -
-
- {archiveTitle} -
-
    - {archivePosts[archiveTitle].map(post => ( -
  • -
    - - {post.date.start_date} - {' '} -   - - - {post.title} - - -
    -
  • - ))} -
-
- ))} -
-
- ) -} diff --git a/themes/empty/LayoutBase.js b/themes/empty/LayoutBase.js deleted file mode 100644 index 7898c47d..00000000 --- a/themes/empty/LayoutBase.js +++ /dev/null @@ -1,135 +0,0 @@ -import CommonHead from '@/components/CommonHead' -import Live2D from '@/components/Live2D' -import Link from 'next/link' -import React from 'react' -import BLOG from '@/blog.config' -import { useGlobal } from '@/lib/global' -/** - * 基础布局 采用左右两侧布局,移动端使用顶部导航栏 - - * @returns {JSX.Element} - * @constructor - */ -const LayoutBase = props => { - const { children, meta, customNav } = props - const { locale } = useGlobal() - const d = new Date() - const currentYear = d.getFullYear() - const startYear = BLOG.SINCE && BLOG.SINCE !== currentYear && BLOG.SINCE + '-' - - let links = [ - { icon: 'fas fa-search', name: locale.NAV.SEARCH, to: '/search' }, - { icon: 'fas fa-archive', name: locale.NAV.ARCHIVE, to: '/archive' }, - { icon: 'fas fa-folder', name: locale.COMMON.CATEGORY, to: '/category' }, - { icon: 'fas fa-tag', name: locale.COMMON.TAGS, to: '/tag' } - ] - - if (customNav) { - links = links.concat(customNav) - } - - return ( -
- - {/* 导航菜单 */} -
-
-
- - - -
{BLOG.TITLE}
-
- -
- -
-
- - {/* 内容主体 */} -
-
{children}
-
-
- -
-
-
- - {/* 页脚 */} - -
- ) -} - -export default LayoutBase diff --git a/themes/empty/LayoutCategory.js b/themes/empty/LayoutCategory.js deleted file mode 100644 index 85d399f6..00000000 --- a/themes/empty/LayoutCategory.js +++ /dev/null @@ -1,48 +0,0 @@ -import BLOG from '@/blog.config' -import { useGlobal } from '@/lib/global' -import Link from 'next/link' -import { useState } from 'react' -import LayoutBase from './LayoutBase' - -export const LayoutCategory = props => { - const { category, posts } = props - const { locale } = useGlobal() - - const [page, updatePage] = useState(1) - let hasMore = false - const postsToShow = posts - ? Object.assign(posts).slice(0, BLOG.POSTS_PER_PAGE * page) - : [] - - if (posts) { - const totalCount = posts.length - hasMore = page * BLOG.POSTS_PER_PAGE < totalCount - } - const handleGetMore = () => { - if (!hasMore) return - updatePage(page + 1) - } - - return ( - - Category - {category} - {postsToShow.map(p => ( -
- - {p.title} - -
{p.summary}
-
- ))} -
-
- {' '} - {hasMore ? locale.COMMON.MORE : `${locale.COMMON.NO_MORE} 😰`}{' '} -
-
-
- ) -} diff --git a/themes/empty/LayoutCategoryIndex.js b/themes/empty/LayoutCategoryIndex.js deleted file mode 100644 index b748b140..00000000 --- a/themes/empty/LayoutCategoryIndex.js +++ /dev/null @@ -1,25 +0,0 @@ -import { useGlobal } from '@/lib/global' -import Link from 'next/link' -import LayoutBase from './LayoutBase' - -export const LayoutCategoryIndex = (props) => { - const { categories } = props - const { locale } = useGlobal() - return -
-
- {locale.COMMON.CATEGORY}: -
-
- {categories && categories.map(category => { - return -
- {category.name}({category.count}) -
- - })} -
-
-
-} diff --git a/themes/empty/LayoutIndex.js b/themes/empty/LayoutIndex.js deleted file mode 100644 index 817bcdfb..00000000 --- a/themes/empty/LayoutIndex.js +++ /dev/null @@ -1,52 +0,0 @@ -import BLOG from '@/blog.config' -import { useGlobal } from '@/lib/global' -import Link from 'next/link' -import { useRouter } from 'next/router' -import LayoutBase from './LayoutBase' - -export const LayoutIndex = props => { - const { posts, postCount } = props - - const { locale } = useGlobal() - const router = useRouter() - const totalPage = Math.ceil(postCount / BLOG.POSTS_PER_PAGE) - - const page = 1 - const showNext = page < totalPage && posts.length === BLOG.POSTS_PER_PAGE && posts.length < postCount - - const currentPage = +page - return ( - - {posts.map(p => ( -
- - {p.title} - -
{p.summary}
-
- ))} - - -
- ) -} diff --git a/themes/empty/LayoutPage.js b/themes/empty/LayoutPage.js deleted file mode 100644 index 2f5f795a..00000000 --- a/themes/empty/LayoutPage.js +++ /dev/null @@ -1,52 +0,0 @@ -import BLOG from '@/blog.config' -import { useGlobal } from '@/lib/global' -import Link from 'next/link' -import { useRouter } from 'next/router' -import LayoutBase from './LayoutBase' - -export const LayoutPage = (props) => { - const { page } = props - const { posts, postCount } = props - - const { locale } = useGlobal() - const router = useRouter() - const totalPage = Math.ceil(postCount / BLOG.POSTS_PER_PAGE) - - const showNext = page < totalPage && posts.length === BLOG.POSTS_PER_PAGE && posts.length < postCount - - const currentPage = +page - return ( - - {posts.map(p => ( -
- - {p.title} - -
{p.summary}
-
- ))} - - -
- ) -} diff --git a/themes/empty/LayoutSearch.js b/themes/empty/LayoutSearch.js deleted file mode 100644 index d24024a5..00000000 --- a/themes/empty/LayoutSearch.js +++ /dev/null @@ -1,60 +0,0 @@ -import BLOG from '@/blog.config' -import { useGlobal } from '@/lib/global' -import Link from 'next/link' -import { useEffect, useState } from 'react' -import SearchInput from './components/SearchInput' -import LayoutBase from './LayoutBase' - -export const LayoutSearch = props => { - const { keyword, posts } = props - useEffect(() => { - setTimeout(() => { - const container = document.getElementById('container') - if (container && container.innerHTML) { - const re = new RegExp(`${keyword}`, 'gim') - container.innerHTML = container.innerHTML.replace(re, `${keyword}`) - } - }, 100) - }) - - const { locale } = useGlobal() - - const [page, updatePage] = useState(1) - let hasMore = false - const postsToShow = posts - ? Object.assign(posts).slice(0, BLOG.POSTS_PER_PAGE * page) - : [] - - if (posts) { - const totalCount = posts.length - hasMore = page * BLOG.POSTS_PER_PAGE < totalCount - } - const handleGetMore = () => { - if (!hasMore) return - updatePage(page + 1) - } - - return ( - -

Search - {keyword}

- - {postsToShow.map(p => ( -
- - {p.title} - -
{p.summary}
-
- ))} -
-
- {' '} - {hasMore ? locale.COMMON.MORE : `${locale.COMMON.NO_MORE} 😰`}{' '} -
-
-
- ) -} diff --git a/themes/empty/LayoutSlug.js b/themes/empty/LayoutSlug.js deleted file mode 100644 index 7ff9b624..00000000 --- a/themes/empty/LayoutSlug.js +++ /dev/null @@ -1,100 +0,0 @@ -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 { useRef, useEffect } from 'react' -import { ArticleLock } from './components/ArticleLock' -import mediumZoom from 'medium-zoom' - -const mapPageUrl = id => { - return 'https://www.notion.so/' + id.replace(/-/g, '') -} - -export const LayoutSlug = props => { - const { post, lock, validPassword } = props - const meta = { - title: `${post.title} | ${BLOG.TITLE}`, - description: post.summary, - type: 'article', - tags: post.tags - } - - if (!lock && post?.blockMap?.block) { - post.content = Object.keys(post.blockMap.block) - post.toc = getPageTableOfContents(post, post.blockMap) - } - - const zoom = - typeof window !== 'undefined' && - mediumZoom({ - container: '.notion-viewport', - background: 'rgba(0, 0, 0, 0.2)', - margin: getMediumZoomMargin() - }) - const zoomRef = useRef(zoom ? zoom.clone() : null) - useEffect(() => { - // 将所有container下的所有图片添加medium-zoom - const container = document.getElementById('notion-article') - const imgList = container?.getElementsByTagName('img') - if (imgList && zoomRef.current) { - for (let i = 0; i < imgList.length; i++) { - zoomRef.current.attach(imgList[i]) - } - } - }) - return ( - -
-

{post?.title}

- - {lock && } - - {!lock &&
- {post.blockMap && ( - - )} -
} - -
-
- ) -} - -function getMediumZoomMargin () { - const width = window.innerWidth - - if (width < 500) { - return 8 - } else if (width < 800) { - return 20 - } else if (width < 1280) { - return 30 - } else if (width < 1600) { - return 40 - } else if (width < 1920) { - return 48 - } else { - return 72 - } -} diff --git a/themes/empty/LayoutTag.js b/themes/empty/LayoutTag.js deleted file mode 100644 index e1a549f4..00000000 --- a/themes/empty/LayoutTag.js +++ /dev/null @@ -1,49 +0,0 @@ -import BLOG from '@/blog.config' -import { useGlobal } from '@/lib/global' -import Link from 'next/link' -import { useState } from 'react' -import LayoutBase from './LayoutBase' - -export const LayoutTag = props => { - const { tag, posts } = props - const { locale } = useGlobal() - - const [page, updatePage] = useState(1) - - let hasMore = false - const postsToShow = posts - ? Object.assign(posts).slice(0, BLOG.POSTS_PER_PAGE * page) - : [] - - if (posts) { - const totalCount = posts.length - hasMore = page * BLOG.POSTS_PER_PAGE < totalCount - } - const handleGetMore = () => { - if (!hasMore) return - updatePage(page + 1) - } - - return ( - - Tag - {tag} - {postsToShow.map(p => ( -
- - {p.title} - -
{p.summary}
-
- ))} -
-
- {' '} - {hasMore ? locale.COMMON.MORE : `${locale.COMMON.NO_MORE} 😰`}{' '} -
-
-
- ) -} diff --git a/themes/empty/LayoutTagIndex.js b/themes/empty/LayoutTagIndex.js deleted file mode 100644 index 3fb41c9f..00000000 --- a/themes/empty/LayoutTagIndex.js +++ /dev/null @@ -1,24 +0,0 @@ -import { useGlobal } from '@/lib/global' -import Link from 'next/link' -import LayoutBase from './LayoutBase' - -export const LayoutTagIndex = (props) => { - const { tags } = props - const { locale } = useGlobal() - return -
-
{locale.COMMON.TAGS}:
-
- { tags.map(tag => { - return - }) } -
-
-} diff --git a/themes/empty/components/ArticleLock.js b/themes/empty/components/ArticleLock.js deleted file mode 100644 index bfdd00ce..00000000 --- a/themes/empty/components/ArticleLock.js +++ /dev/null @@ -1,40 +0,0 @@ -import { useGlobal } from '@/lib/global' - -/** - * 加密文章校验组件 - * @param {password, validPassword} props - * @param password 正确的密码 - * @param validPassword(bool) 回调函数,校验正确回调入参为true - * @returns - */ -export const ArticleLock = props => { - const { password, validPassword } = props - const { locale } = useGlobal() - - const submitPassword = () => { - const p = document.getElementById('password') - if (p && p.value && p.value === password) { - validPassword(true) - } else { - const tips = document.getElementById('tips') - if (tips) { - tips.innerHTML = '' - tips.innerHTML = `
${locale.COMMON.PASSWORD_ERROR}
` - } - } - } - - return
-
-
{locale.COMMON.ARTICLE_LOCK_TIPS}
-
- -
-  {locale.COMMON.SUBMIT} -
-
-
-
-
-
-} diff --git a/themes/empty/components/SearchInput.js b/themes/empty/components/SearchInput.js deleted file mode 100644 index 6a95ba01..00000000 --- a/themes/empty/components/SearchInput.js +++ /dev/null @@ -1,87 +0,0 @@ -import { useRouter } from 'next/router' -import { useGlobal } from '@/lib/global' -import { useImperativeHandle, useRef, useState } from 'react' - -let lock = false - -const SearchInput = ({ currentTag, currentSearch, cRef }) => { - const { locale } = useGlobal() - const router = useRouter() - const searchInputRef = useRef(null) - useImperativeHandle(cRef, () => { - return { - focus: () => { - searchInputRef?.current?.focus() - } - } - }) - const handleSearch = () => { - const key = searchInputRef.current.value - if (key && key !== '') { - router.push({ pathname: '/search/' + key }).then(r => { - console.log('搜索', key) - }) - } else { - router.push({ pathname: '/' }).then(r => { - }) - } - } - const handleKeyUp = (e) => { - if (e.keyCode === 13) { // 回车 - handleSearch(searchInputRef.current.value) - } else if (e.keyCode === 27) { // ESC - cleanSearch() - } - } - const cleanSearch = () => { - searchInputRef.current.value = '' - setShowClean(false) - } - function lockSearchInput () { - lock = true - } - - function unLockSearchInput () { - lock = false - } - const [showClean, setShowClean] = useState(false) - const updateSearchKey = (val) => { - if (lock) { - return - } - searchInputRef.current.value = val - if (val) { - setShowClean(true) - } else { - setShowClean(false) - } - } - - return
- updateSearchKey(e.target.value)} - defaultValue={currentSearch || ''} - /> - -
- -
- - {(showClean && -
- -
- )} -
-} - -export default SearchInput diff --git a/themes/empty/config_empty.js b/themes/empty/config_empty.js deleted file mode 100644 index 9f1ac567..00000000 --- a/themes/empty/config_empty.js +++ /dev/null @@ -1,4 +0,0 @@ -const CONFIG_EMPTY = { - TEST_CONFIG: 'TESET' -} -export default CONFIG_EMPTY diff --git a/themes/empty/index.js b/themes/empty/index.js deleted file mode 100644 index 95b0a6b2..00000000 --- a/themes/empty/index.js +++ /dev/null @@ -1,25 +0,0 @@ -import CONFIG_EMPTY from './config_empty' -import { LayoutIndex } from './LayoutIndex' -import { LayoutSearch } from './LayoutSearch' -import { LayoutArchive } from './LayoutArchive' -import { LayoutSlug } from './LayoutSlug' -import { Layout404 } from './Layout404' -import { LayoutCategory } from './LayoutCategory' -import { LayoutCategoryIndex } from './LayoutCategoryIndex' -import { LayoutPage } from './LayoutPage' -import { LayoutTag } from './LayoutTag' -import { LayoutTagIndex } from './LayoutTagIndex' - -export { - CONFIG_EMPTY as THEME_CONFIG, - LayoutIndex, - LayoutSearch, - LayoutArchive, - LayoutSlug, - Layout404, - LayoutCategory, - LayoutCategoryIndex, - LayoutPage, - LayoutTag, - LayoutTagIndex -} diff --git a/themes/index.js b/themes/index.js index 286446ca..e13cd4b3 100644 --- a/themes/index.js +++ b/themes/index.js @@ -5,11 +5,7 @@ import * as next from './next' import * as fukasawa from './fukasawa' import * as hexo from './hexo' import * as medium from './medium' -import * as empty from './empty' -export { - next, - fukasawa, - hexo, - medium, - empty -} +import * as example from './example' + +export const ALL_THEME = ['hexo', 'next', 'medium', 'fukasawa', 'example'] +export { hexo, next, medium, fukasawa, example } From 1d00151299e01cbf47a80d4238ee134d577ef3a4 Mon Sep 17 00:00:00 2001 From: tangly1024 Date: Tue, 29 Mar 2022 10:55:41 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E8=B4=A1=E7=8C=AE=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E7=9B=B8=E5=85=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CONTRIBUTING.md | 52 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..5fffb37a --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,52 @@ +# Contributing + +- [Setup](#setup) +- [Creating new themes](#creating-new-themes) +- [Adding localizations](#adding-localizations) + +Thanks for considering to contribute! + +## Setup + +To contribute to NotionNext, follow these steps: + +1. [Fork][fork] the repository to your GitHub account. +2. Clone the repository to your device (or use something like Codespaces). +3. Create a new branch in the repository. +4. Make your modifications. +5. Commit your modifications and push the branch. +6. [Create a PR][pr] from the branch in your fork to NotionNext' `main` branch. + +This project is built with [Next.js][next.js] and `yarn` as the package manager. +Here are some commands that you can use: + +- `yarn`: install dependencies +- `yarn dev`: compile and hot-reload for development +- `yarn build`: compile and minify for production +- `yarn start`: serve the compiled build in production mode + +## Creating new themes + +If you want to submit your custom theme to NotionNext, copy a new folder in +[`themes`][themes-dir] from [`example`][example]. The folder name will be the +theme's key. + +## Adding localizations + +If your language is not yet supported by NotionNext, please contribute a +localization! Follow these steps to add a new localization: + +1. Copy one of the [en-US.js][en-US.js] in [lang-dir][lang-dir] and rename the new + directory into your language's code ( e.g. `zh-CN.js`). +2. Start translating the strings. +3. Add your language config to [lang.js][lang.js]. +4. [Create a PR][pr] with your localization updates. + +[fork]: https://github.com/tangly1024/NotionNext/fork +[pr]: https://github.com/tangly1024/NotionNext/compare +[next.js]: https://github.com/vercel/next.js +[themes-dir]: themes +[example]: themes/example +[lang-dir]: lib/lang +[en-US.js]: lib/lang/en-US.js +[lang.js]: lib/lang.js From 05df88a776c5fcd74179efddec8dbbacc4768c57 Mon Sep 17 00:00:00 2001 From: tangly1024 Date: Tue, 29 Mar 2022 11:01:03 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=E8=B4=A1=E7=8C=AE=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E7=9B=B8=E5=85=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- themes/example/Layout404.js | 6 + themes/example/LayoutArchive.js | 69 ++++++++++++ themes/example/LayoutBase.js | 135 +++++++++++++++++++++++ themes/example/LayoutCategory.js | 48 ++++++++ themes/example/LayoutCategoryIndex.js | 25 +++++ themes/example/LayoutIndex.js | 52 +++++++++ themes/example/LayoutPage.js | 52 +++++++++ themes/example/LayoutSearch.js | 60 ++++++++++ themes/example/LayoutSlug.js | 100 +++++++++++++++++ themes/example/LayoutTag.js | 49 ++++++++ themes/example/LayoutTagIndex.js | 24 ++++ themes/example/components/ArticleLock.js | 40 +++++++ themes/example/components/SearchInput.js | 87 +++++++++++++++ themes/example/config_empty.js | 4 + themes/example/index.js | 25 +++++ 15 files changed, 776 insertions(+) create mode 100644 themes/example/Layout404.js create mode 100644 themes/example/LayoutArchive.js create mode 100644 themes/example/LayoutBase.js create mode 100644 themes/example/LayoutCategory.js create mode 100644 themes/example/LayoutCategoryIndex.js create mode 100644 themes/example/LayoutIndex.js create mode 100644 themes/example/LayoutPage.js create mode 100644 themes/example/LayoutSearch.js create mode 100644 themes/example/LayoutSlug.js create mode 100644 themes/example/LayoutTag.js create mode 100644 themes/example/LayoutTagIndex.js create mode 100644 themes/example/components/ArticleLock.js create mode 100644 themes/example/components/SearchInput.js create mode 100644 themes/example/config_empty.js create mode 100644 themes/example/index.js diff --git a/themes/example/Layout404.js b/themes/example/Layout404.js new file mode 100644 index 00000000..cd28a607 --- /dev/null +++ b/themes/example/Layout404.js @@ -0,0 +1,6 @@ + +export const Layout404 = () => { + return
+ 404 Not found. +
+} diff --git a/themes/example/LayoutArchive.js b/themes/example/LayoutArchive.js new file mode 100644 index 00000000..04a6d647 --- /dev/null +++ b/themes/example/LayoutArchive.js @@ -0,0 +1,69 @@ +import BLOG from '@/blog.config' +import { useGlobal } from '@/lib/global' +import Link from 'next/link' +import LayoutBase from './LayoutBase' + +export const LayoutArchive = props => { + const { posts } = props + const { locale } = useGlobal() + const postsSortByDate = Object.create(posts) + + postsSortByDate.sort((a, b) => { + const dateA = new Date(a?.date.start_date || a.createdTime) + const dateB = new Date(b?.date.start_date || b.createdTime) + return dateB - dateA + }) + + const meta = { + title: `${locale.NAV.ARCHIVE} | ${BLOG.TITLE}`, + description: BLOG.DESCRIPTION, + type: 'website' + } + + const archivePosts = {} + + postsSortByDate.forEach(post => { + const date = post.date.start_date.slice(0, 7) + if (archivePosts[date]) { + archivePosts[date].push(post) + } else { + archivePosts[date] = [post] + } + }) + return ( + +
+ {Object.keys(archivePosts).map(archiveTitle => ( +
+
+ {archiveTitle} +
+
    + {archivePosts[archiveTitle].map(post => ( +
  • +
    + + {post.date.start_date} + {' '} +   + + + {post.title} + + +
    +
  • + ))} +
+
+ ))} +
+
+ ) +} diff --git a/themes/example/LayoutBase.js b/themes/example/LayoutBase.js new file mode 100644 index 00000000..7898c47d --- /dev/null +++ b/themes/example/LayoutBase.js @@ -0,0 +1,135 @@ +import CommonHead from '@/components/CommonHead' +import Live2D from '@/components/Live2D' +import Link from 'next/link' +import React from 'react' +import BLOG from '@/blog.config' +import { useGlobal } from '@/lib/global' +/** + * 基础布局 采用左右两侧布局,移动端使用顶部导航栏 + + * @returns {JSX.Element} + * @constructor + */ +const LayoutBase = props => { + const { children, meta, customNav } = props + const { locale } = useGlobal() + const d = new Date() + const currentYear = d.getFullYear() + const startYear = BLOG.SINCE && BLOG.SINCE !== currentYear && BLOG.SINCE + '-' + + let links = [ + { icon: 'fas fa-search', name: locale.NAV.SEARCH, to: '/search' }, + { icon: 'fas fa-archive', name: locale.NAV.ARCHIVE, to: '/archive' }, + { icon: 'fas fa-folder', name: locale.COMMON.CATEGORY, to: '/category' }, + { icon: 'fas fa-tag', name: locale.COMMON.TAGS, to: '/tag' } + ] + + if (customNav) { + links = links.concat(customNav) + } + + return ( +
+ + {/* 导航菜单 */} +
+
+
+ + + +
{BLOG.TITLE}
+
+ +
+ +
+
+ + {/* 内容主体 */} +
+
{children}
+
+
+ +
+
+
+ + {/* 页脚 */} + +
+ ) +} + +export default LayoutBase diff --git a/themes/example/LayoutCategory.js b/themes/example/LayoutCategory.js new file mode 100644 index 00000000..85d399f6 --- /dev/null +++ b/themes/example/LayoutCategory.js @@ -0,0 +1,48 @@ +import BLOG from '@/blog.config' +import { useGlobal } from '@/lib/global' +import Link from 'next/link' +import { useState } from 'react' +import LayoutBase from './LayoutBase' + +export const LayoutCategory = props => { + const { category, posts } = props + const { locale } = useGlobal() + + const [page, updatePage] = useState(1) + let hasMore = false + const postsToShow = posts + ? Object.assign(posts).slice(0, BLOG.POSTS_PER_PAGE * page) + : [] + + if (posts) { + const totalCount = posts.length + hasMore = page * BLOG.POSTS_PER_PAGE < totalCount + } + const handleGetMore = () => { + if (!hasMore) return + updatePage(page + 1) + } + + return ( + + Category - {category} + {postsToShow.map(p => ( +
+ + {p.title} + +
{p.summary}
+
+ ))} +
+
+ {' '} + {hasMore ? locale.COMMON.MORE : `${locale.COMMON.NO_MORE} 😰`}{' '} +
+
+
+ ) +} diff --git a/themes/example/LayoutCategoryIndex.js b/themes/example/LayoutCategoryIndex.js new file mode 100644 index 00000000..b748b140 --- /dev/null +++ b/themes/example/LayoutCategoryIndex.js @@ -0,0 +1,25 @@ +import { useGlobal } from '@/lib/global' +import Link from 'next/link' +import LayoutBase from './LayoutBase' + +export const LayoutCategoryIndex = (props) => { + const { categories } = props + const { locale } = useGlobal() + return +
+
+ {locale.COMMON.CATEGORY}: +
+
+ {categories && categories.map(category => { + return +
+ {category.name}({category.count}) +
+ + })} +
+
+
+} diff --git a/themes/example/LayoutIndex.js b/themes/example/LayoutIndex.js new file mode 100644 index 00000000..817bcdfb --- /dev/null +++ b/themes/example/LayoutIndex.js @@ -0,0 +1,52 @@ +import BLOG from '@/blog.config' +import { useGlobal } from '@/lib/global' +import Link from 'next/link' +import { useRouter } from 'next/router' +import LayoutBase from './LayoutBase' + +export const LayoutIndex = props => { + const { posts, postCount } = props + + const { locale } = useGlobal() + const router = useRouter() + const totalPage = Math.ceil(postCount / BLOG.POSTS_PER_PAGE) + + const page = 1 + const showNext = page < totalPage && posts.length === BLOG.POSTS_PER_PAGE && posts.length < postCount + + const currentPage = +page + return ( + + {posts.map(p => ( +
+ + {p.title} + +
{p.summary}
+
+ ))} + + +
+ ) +} diff --git a/themes/example/LayoutPage.js b/themes/example/LayoutPage.js new file mode 100644 index 00000000..2f5f795a --- /dev/null +++ b/themes/example/LayoutPage.js @@ -0,0 +1,52 @@ +import BLOG from '@/blog.config' +import { useGlobal } from '@/lib/global' +import Link from 'next/link' +import { useRouter } from 'next/router' +import LayoutBase from './LayoutBase' + +export const LayoutPage = (props) => { + const { page } = props + const { posts, postCount } = props + + const { locale } = useGlobal() + const router = useRouter() + const totalPage = Math.ceil(postCount / BLOG.POSTS_PER_PAGE) + + const showNext = page < totalPage && posts.length === BLOG.POSTS_PER_PAGE && posts.length < postCount + + const currentPage = +page + return ( + + {posts.map(p => ( +
+ + {p.title} + +
{p.summary}
+
+ ))} + + +
+ ) +} diff --git a/themes/example/LayoutSearch.js b/themes/example/LayoutSearch.js new file mode 100644 index 00000000..d24024a5 --- /dev/null +++ b/themes/example/LayoutSearch.js @@ -0,0 +1,60 @@ +import BLOG from '@/blog.config' +import { useGlobal } from '@/lib/global' +import Link from 'next/link' +import { useEffect, useState } from 'react' +import SearchInput from './components/SearchInput' +import LayoutBase from './LayoutBase' + +export const LayoutSearch = props => { + const { keyword, posts } = props + useEffect(() => { + setTimeout(() => { + const container = document.getElementById('container') + if (container && container.innerHTML) { + const re = new RegExp(`${keyword}`, 'gim') + container.innerHTML = container.innerHTML.replace(re, `${keyword}`) + } + }, 100) + }) + + const { locale } = useGlobal() + + const [page, updatePage] = useState(1) + let hasMore = false + const postsToShow = posts + ? Object.assign(posts).slice(0, BLOG.POSTS_PER_PAGE * page) + : [] + + if (posts) { + const totalCount = posts.length + hasMore = page * BLOG.POSTS_PER_PAGE < totalCount + } + const handleGetMore = () => { + if (!hasMore) return + updatePage(page + 1) + } + + return ( + +

Search - {keyword}

+ + {postsToShow.map(p => ( +
+ + {p.title} + +
{p.summary}
+
+ ))} +
+
+ {' '} + {hasMore ? locale.COMMON.MORE : `${locale.COMMON.NO_MORE} 😰`}{' '} +
+
+
+ ) +} diff --git a/themes/example/LayoutSlug.js b/themes/example/LayoutSlug.js new file mode 100644 index 00000000..7ff9b624 --- /dev/null +++ b/themes/example/LayoutSlug.js @@ -0,0 +1,100 @@ +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 { useRef, useEffect } from 'react' +import { ArticleLock } from './components/ArticleLock' +import mediumZoom from 'medium-zoom' + +const mapPageUrl = id => { + return 'https://www.notion.so/' + id.replace(/-/g, '') +} + +export const LayoutSlug = props => { + const { post, lock, validPassword } = props + const meta = { + title: `${post.title} | ${BLOG.TITLE}`, + description: post.summary, + type: 'article', + tags: post.tags + } + + if (!lock && post?.blockMap?.block) { + post.content = Object.keys(post.blockMap.block) + post.toc = getPageTableOfContents(post, post.blockMap) + } + + const zoom = + typeof window !== 'undefined' && + mediumZoom({ + container: '.notion-viewport', + background: 'rgba(0, 0, 0, 0.2)', + margin: getMediumZoomMargin() + }) + const zoomRef = useRef(zoom ? zoom.clone() : null) + useEffect(() => { + // 将所有container下的所有图片添加medium-zoom + const container = document.getElementById('notion-article') + const imgList = container?.getElementsByTagName('img') + if (imgList && zoomRef.current) { + for (let i = 0; i < imgList.length; i++) { + zoomRef.current.attach(imgList[i]) + } + } + }) + return ( + +
+

{post?.title}

+ + {lock && } + + {!lock &&
+ {post.blockMap && ( + + )} +
} + +
+
+ ) +} + +function getMediumZoomMargin () { + const width = window.innerWidth + + if (width < 500) { + return 8 + } else if (width < 800) { + return 20 + } else if (width < 1280) { + return 30 + } else if (width < 1600) { + return 40 + } else if (width < 1920) { + return 48 + } else { + return 72 + } +} diff --git a/themes/example/LayoutTag.js b/themes/example/LayoutTag.js new file mode 100644 index 00000000..e1a549f4 --- /dev/null +++ b/themes/example/LayoutTag.js @@ -0,0 +1,49 @@ +import BLOG from '@/blog.config' +import { useGlobal } from '@/lib/global' +import Link from 'next/link' +import { useState } from 'react' +import LayoutBase from './LayoutBase' + +export const LayoutTag = props => { + const { tag, posts } = props + const { locale } = useGlobal() + + const [page, updatePage] = useState(1) + + let hasMore = false + const postsToShow = posts + ? Object.assign(posts).slice(0, BLOG.POSTS_PER_PAGE * page) + : [] + + if (posts) { + const totalCount = posts.length + hasMore = page * BLOG.POSTS_PER_PAGE < totalCount + } + const handleGetMore = () => { + if (!hasMore) return + updatePage(page + 1) + } + + return ( + + Tag - {tag} + {postsToShow.map(p => ( +
+ + {p.title} + +
{p.summary}
+
+ ))} +
+
+ {' '} + {hasMore ? locale.COMMON.MORE : `${locale.COMMON.NO_MORE} 😰`}{' '} +
+
+
+ ) +} diff --git a/themes/example/LayoutTagIndex.js b/themes/example/LayoutTagIndex.js new file mode 100644 index 00000000..3fb41c9f --- /dev/null +++ b/themes/example/LayoutTagIndex.js @@ -0,0 +1,24 @@ +import { useGlobal } from '@/lib/global' +import Link from 'next/link' +import LayoutBase from './LayoutBase' + +export const LayoutTagIndex = (props) => { + const { tags } = props + const { locale } = useGlobal() + return +
+
{locale.COMMON.TAGS}:
+
+ { tags.map(tag => { + return + }) } +
+
+} diff --git a/themes/example/components/ArticleLock.js b/themes/example/components/ArticleLock.js new file mode 100644 index 00000000..bfdd00ce --- /dev/null +++ b/themes/example/components/ArticleLock.js @@ -0,0 +1,40 @@ +import { useGlobal } from '@/lib/global' + +/** + * 加密文章校验组件 + * @param {password, validPassword} props + * @param password 正确的密码 + * @param validPassword(bool) 回调函数,校验正确回调入参为true + * @returns + */ +export const ArticleLock = props => { + const { password, validPassword } = props + const { locale } = useGlobal() + + const submitPassword = () => { + const p = document.getElementById('password') + if (p && p.value && p.value === password) { + validPassword(true) + } else { + const tips = document.getElementById('tips') + if (tips) { + tips.innerHTML = '' + tips.innerHTML = `
${locale.COMMON.PASSWORD_ERROR}
` + } + } + } + + return
+
+
{locale.COMMON.ARTICLE_LOCK_TIPS}
+
+ +
+  {locale.COMMON.SUBMIT} +
+
+
+
+
+
+} diff --git a/themes/example/components/SearchInput.js b/themes/example/components/SearchInput.js new file mode 100644 index 00000000..6a95ba01 --- /dev/null +++ b/themes/example/components/SearchInput.js @@ -0,0 +1,87 @@ +import { useRouter } from 'next/router' +import { useGlobal } from '@/lib/global' +import { useImperativeHandle, useRef, useState } from 'react' + +let lock = false + +const SearchInput = ({ currentTag, currentSearch, cRef }) => { + const { locale } = useGlobal() + const router = useRouter() + const searchInputRef = useRef(null) + useImperativeHandle(cRef, () => { + return { + focus: () => { + searchInputRef?.current?.focus() + } + } + }) + const handleSearch = () => { + const key = searchInputRef.current.value + if (key && key !== '') { + router.push({ pathname: '/search/' + key }).then(r => { + console.log('搜索', key) + }) + } else { + router.push({ pathname: '/' }).then(r => { + }) + } + } + const handleKeyUp = (e) => { + if (e.keyCode === 13) { // 回车 + handleSearch(searchInputRef.current.value) + } else if (e.keyCode === 27) { // ESC + cleanSearch() + } + } + const cleanSearch = () => { + searchInputRef.current.value = '' + setShowClean(false) + } + function lockSearchInput () { + lock = true + } + + function unLockSearchInput () { + lock = false + } + const [showClean, setShowClean] = useState(false) + const updateSearchKey = (val) => { + if (lock) { + return + } + searchInputRef.current.value = val + if (val) { + setShowClean(true) + } else { + setShowClean(false) + } + } + + return
+ updateSearchKey(e.target.value)} + defaultValue={currentSearch || ''} + /> + +
+ +
+ + {(showClean && +
+ +
+ )} +
+} + +export default SearchInput diff --git a/themes/example/config_empty.js b/themes/example/config_empty.js new file mode 100644 index 00000000..9f1ac567 --- /dev/null +++ b/themes/example/config_empty.js @@ -0,0 +1,4 @@ +const CONFIG_EMPTY = { + TEST_CONFIG: 'TESET' +} +export default CONFIG_EMPTY diff --git a/themes/example/index.js b/themes/example/index.js new file mode 100644 index 00000000..95b0a6b2 --- /dev/null +++ b/themes/example/index.js @@ -0,0 +1,25 @@ +import CONFIG_EMPTY from './config_empty' +import { LayoutIndex } from './LayoutIndex' +import { LayoutSearch } from './LayoutSearch' +import { LayoutArchive } from './LayoutArchive' +import { LayoutSlug } from './LayoutSlug' +import { Layout404 } from './Layout404' +import { LayoutCategory } from './LayoutCategory' +import { LayoutCategoryIndex } from './LayoutCategoryIndex' +import { LayoutPage } from './LayoutPage' +import { LayoutTag } from './LayoutTag' +import { LayoutTagIndex } from './LayoutTagIndex' + +export { + CONFIG_EMPTY as THEME_CONFIG, + LayoutIndex, + LayoutSearch, + LayoutArchive, + LayoutSlug, + Layout404, + LayoutCategory, + LayoutCategoryIndex, + LayoutPage, + LayoutTag, + LayoutTagIndex +}