diff --git a/.env.local b/.env.local index 314ae9ad..3b00243c 100644 --- a/.env.local +++ b/.env.local @@ -1,5 +1,5 @@ # 环境变量 @see https://www.nextjs.cn/docs/basic-features/environment-variables -NEXT_PUBLIC_VERSION=4.5.3 +NEXT_PUBLIC_VERSION=4.6.1 # 可在此添加环境变量,去掉最左边的(# )注释即可 diff --git a/README_EN.md b/README_EN.md index dbb75684..dd4063e5 100644 --- a/README_EN.md +++ b/README_EN.md @@ -35,15 +35,15 @@ Live Demo:[https://preview.tangly1024.com/](https://preview.tangly1024.com/) It only takes a few minutes to set up your personal site: -- [Quick Deployment Tutorial - Multiple Options Available](https://tangly1024.com/article/notion-next) +- [Quick Deployment Tutorial - Multiple Options Available](https://docs.tangly1024.com/article/vercel-deploy-notion-next) -- [Customization Guide - How to Configure Feature Plugins](https://tangly1024.com/article/notion-next-guide) +- [Customization Guide - How to Configure Feature Plugins](https://docs.tangly1024.com/article/how-to-config-notion-next) -- [Development Guide - How to Conduct Local Development](https://tangly1024.com/article/how-to-develop-with-notion-next) +- [Development Guide - How to Conduct Local Development](https://docs.tangly1024.com/article/how-to-develop-with-notion-next) -- [Update Guide - How to Get the Latest Upgrade Patch](https://tangly1024.com/article/how-to-update-notionnext) +- [Update Guide - How to Get the Latest Upgrade Patch](https://docs.tangly1024.com/article/how-to-update-notionnext) -- [Version History - Check Feature Highlights for Each Version](https://tangly1024.com/article/notion-next-changelogs) +- [Version History - Check Feature Highlights for Each Version](https://docs.tangly1024.com/article/latest) ## Acknowledgements diff --git a/blog.config.js b/blog.config.js index 3a30ddea..7a2c2678 100644 --- a/blog.config.js +++ b/blog.config.js @@ -13,6 +13,7 @@ const BLOG = { APPEARANCE: process.env.NEXT_PUBLIC_APPEARANCE || 'light', // ['light', 'dark', 'auto'], // light 日间模式 , dark夜间模式, auto根据时间和主题自动夜间模式 APPEARANCE_DARK_TIME: process.env.NEXT_PUBLIC_APPEARANCE_DARK_TIME || [18, 6], // 夜间模式起至时间,false时关闭根据时间自动切换夜间模式 + TAG_SORT_BY_COUNT: true, // 标签是否按照文章数量倒序排列,文章多的标签排在前。 IS_TAG_COLOR_DISTINGUISHED: process.env.NEXT_PUBLIC_IS_TAG_COLOR_DISTINGUISHED === 'true' || true, // 对于名称相同的tag是否区分tag的颜色 @@ -38,6 +39,9 @@ const BLOG = { CONTACT_INSTAGRAM: process.env.NEXT_PUBLIC_CONTACT_INSTAGRAM || '', // 您的instagram地址 CONTACT_BILIBILI: process.env.NEXT_PUBLIC_CONTACT_BILIBILI || '', // B站主页 CONTACT_YOUTUBE: process.env.NEXT_PUBLIC_CONTACT_YOUTUBE || '', // Youtube主页 + CONTACT_XIAOHONGSHU: process.env.NEXT_PUBLIC_CONTACT_XIAOHONGSHU || '', // 小红书主页 + CONTACT_ZHISHIXINGQIU: process.env.NEXT_PUBLIC_CONTACT_ZHISHIXINGQIU || '', // 知识星球 + CONTACT_WEHCHAT_PUBLIC: process.env.NEXT_PUBLIC_CONTACT_WEHCHAT_PUBLIC || '', // 微信公众号 格式:https://mp.weixin.qq.com/mp/profile_ext?action=home&__biz=【xxxxxx】==#wechat_redirect NOTION_HOST: process.env.NEXT_PUBLIC_NOTION_HOST || 'https://www.notion.so', // Notion域名,您可以选择用自己的域名进行反向代理,如果不懂得什么是反向代理,请勿修改此项 @@ -204,9 +208,10 @@ const BLOG = { 'link,wechat,qq,weibo,email,facebook,twitter,telegram,messenger,line,reddit,whatsapp,linkedin', // 分享的服務,按顺序显示,逗号隔开 // 所有支持的分享服务:link(复制链接),wechat(微信),qq,weibo(微博),email(邮件),facebook,twitter,telegram,messenger,line,reddit,whatsapp,linkedin,vkshare,okshare,tumblr,livejournal,mailru,viber,workplace,pocket,instapaper,hatena - POST_URL_PREFIX: process.env.NEXT_PUBLIC_POST_URL_PREFIX || 'article', + // 文章URL前缀 + POST_URL_PREFIX: process.env.NEXT_PUBLIC_POST_URL_PREFIX ?? 'article', // POST类型文章的默认路径前缀,例如默认POST类型的路径是 /article/[slug] - // 如果此项配置为 '' 空, 则文章将没有前缀路径,使用场景: 希望文章前缀路径为 /post 的情况 支持多级 + // 如果此项配置为 '' 空, 则文章将没有前缀路径 // 支援類似 WP 可自訂文章連結格式的功能:https://wordpress.org/documentation/article/customize-permalinks/,目前只先實作 %year%/%month%/%day% // 例:如想連結改成前綴 article + 時間戳記,可變更為: 'article/%year%/%month%/%day%' @@ -540,7 +545,7 @@ const BLOG = { process.env.ENABLE_CACHE || process.env.npm_lifecycle_event === 'build' || process.env.npm_lifecycle_event === 'export', // 在打包过程中默认开启缓存,开发或运行时开启此功能意义不大。 - isProd: process.env.VERCEL_ENV === 'production', // distinguish between development and production environment (ref: https://vercel.com/docs/environment-variables#system-environment-variables) + isProd: process.env.VERCEL_ENV === 'production' || process.env.EXPORT, // distinguish between development and production environment (ref: https://vercel.com/docs/environment-variables#system-environment-variables) BUNDLE_ANALYZER: process.env.ANALYZE === 'true' || false, // 是否展示编译依赖内容与大小 VERSION: process.env.NEXT_PUBLIC_VERSION // 版本号 } diff --git a/components/AOSAnimation.js b/components/AOSAnimation.js index 076ab379..7d246def 100644 --- a/components/AOSAnimation.js +++ b/components/AOSAnimation.js @@ -10,14 +10,8 @@ import { useEffect } from 'react' export default function AOSAnimation() { const initAOS = async () => { Promise.all([ - loadExternalResource( - 'https://cdn.bootcdn.net/ajax/libs/aos/2.3.4/aos.js', - 'js' - ), - loadExternalResource( - 'https://cdn.bootcdn.net/ajax/libs/aos/2.3.4/aos.css', - 'css' - ) + loadExternalResource('/js/aos.js', 'js'), + loadExternalResource('/css/aos.css', 'css') ]).then(() => { if (window.AOS) { window.AOS.init() diff --git a/components/AlgoliaSearchModal.js b/components/AlgoliaSearchModal.js index ea727a48..eac9d8bf 100644 --- a/components/AlgoliaSearchModal.js +++ b/components/AlgoliaSearchModal.js @@ -1,11 +1,18 @@ -import { useState, useImperativeHandle, useRef, useEffect, Fragment } from 'react' -import algoliasearch from 'algoliasearch' import replaceSearchResult from '@/components/Mark' -import Link from 'next/link' -import { useGlobal } from '@/lib/global' -import throttle from 'lodash/throttle' import { siteConfig } from '@/lib/config' -import { useHotkeys } from 'react-hotkeys-hook'; +import { useGlobal } from '@/lib/global' +import algoliasearch from 'algoliasearch' +import throttle from 'lodash/throttle' +import Link from 'next/link' +import { useRouter } from 'next/router' +import { + Fragment, + useEffect, + useImperativeHandle, + useRef, + useState +} from 'react' +import { useHotkeys } from 'react-hotkeys-hook' const ShortCutActions = [ { @@ -20,7 +27,6 @@ const ShortCutActions = [ key: 'Esc', action: '关闭' } - ] /** @@ -36,31 +42,49 @@ export default function AlgoliaSearchModal({ cRef }) { const [totalPage, setTotalPage] = useState(0) const [totalHit, setTotalHit] = useState(0) const [useTime, setUseTime] = useState(0) - const inputRef = useRef(null) const [activeIndex, setActiveIndex] = useState(0) const [isLoading, setIsLoading] = useState(false) - useHotkeys('ctrl+k', (e) => { + const inputRef = useRef(null) + const router = useRouter() + + /** + * 快捷键设置 + */ + useHotkeys('ctrl+k', e => { e.preventDefault() setIsModalOpen(true) }) // 方向键调整选中 - useHotkeys('down', (e) => { - e.preventDefault() - if (activeIndex < searchResults.length - 1) { - setActiveIndex(activeIndex + 1) - } - }, { enableOnFormTags: true }) - useHotkeys('up', (e) => { - e.preventDefault() - if (activeIndex > 0) { - setActiveIndex(activeIndex - 1) - } - }, { enableOnFormTags: true }) + useHotkeys( + 'down', + e => { + e.preventDefault() + if (activeIndex < searchResults.length - 1) { + setActiveIndex(activeIndex + 1) + } + }, + { enableOnFormTags: true } + ) + useHotkeys( + 'up', + e => { + e.preventDefault() + if (activeIndex > 0) { + setActiveIndex(activeIndex - 1) + } + }, + { enableOnFormTags: true } + ) // esc关闭 - useHotkeys('esc', (e) => { - e.preventDefault() - setIsModalOpen(false) - }, { enableOnFormTags: true }) + useHotkeys( + 'esc', + e => { + e.preventDefault() + setIsModalOpen(false) + }, + { enableOnFormTags: true } + ) + // 跳转Search结果 const onJumpSearchResult = () => { if (searchResults.length > 0) { @@ -68,11 +92,15 @@ export default function AlgoliaSearchModal({ cRef }) { } } // enter跳转 - useHotkeys('enter', (e) => { - if (searchResults.length > 0) { - onJumpSearchResult(index) - } - }, { enableOnFormTags: true }) + useHotkeys( + 'enter', + e => { + if (searchResults.length > 0) { + onJumpSearchResult(index) + } + }, + { enableOnFormTags: true } + ) const resetSearch = () => { setActiveIndex(0) @@ -84,6 +112,16 @@ export default function AlgoliaSearchModal({ cRef }) { if (inputRef.current) inputRef.current.value = '' } + /** + * 页面路径变化后,自动关闭此modal + */ + useEffect(() => { + setIsModalOpen(false) + }, [router]) + + /** + * 自动聚焦搜索框 + */ useEffect(() => { if (isModalOpen) { setTimeout(() => { @@ -93,9 +131,10 @@ export default function AlgoliaSearchModal({ cRef }) { resetSearch() } }, [isModalOpen]) + /** - * 对外暴露方法 - */ + * 对外暴露方法 + **/ useImperativeHandle(cRef, () => { return { openSearch: () => { @@ -104,7 +143,10 @@ export default function AlgoliaSearchModal({ cRef }) { } }) - const client = algoliasearch(siteConfig('ALGOLIA_APP_ID'), siteConfig('ALGOLIA_SEARCH_ONLY_APP_KEY')) + const client = algoliasearch( + siteConfig('ALGOLIA_APP_ID'), + siteConfig('ALGOLIA_SEARCH_ONLY_APP_KEY') + ) const index = client.initIndex(siteConfig('ALGOLIA_INDEX')) /** @@ -131,7 +173,9 @@ export default function AlgoliaSearchModal({ cRef }) { setTotalHit(nbHits) setSearchResults(hits) setIsLoading(false) - const doms = document.getElementById('search-wrapper').getElementsByClassName('replace') + const doms = document + .getElementById('search-wrapper') + .getElementsByClassName('replace') setTimeout(() => { replaceSearchResult({ @@ -149,33 +193,35 @@ export default function AlgoliaSearchModal({ cRef }) { } // 定义节流函数,确保在用户停止输入一段时间后才会调用处理搜索的方法 - const throttledHandleInputChange = useRef(throttle((query, page = 0) => { - handleSearch(query, page); - }, 1000)); + const throttledHandleInputChange = useRef( + throttle((query, page = 0) => { + handleSearch(query, page) + }, 1000) + ) // 用于存储搜索延迟的计时器 - const searchTimer = useRef(null); + const searchTimer = useRef(null) // 修改input的onChange事件处理函数 - const handleInputChange = (e) => { - const query = e.target.value; + const handleInputChange = e => { + const query = e.target.value // 如果已经有计时器在等待搜索,先清除之前的计时器 if (searchTimer.current) { - clearTimeout(searchTimer.current); + clearTimeout(searchTimer.current) } // 设置新的计时器,在用户停止输入一段时间后触发搜索 searchTimer.current = setTimeout(() => { - throttledHandleInputChange.current(query); - }, 800); - }; + throttledHandleInputChange.current(query) + }, 800) + } /** * 切换页码 * @param {*} page */ - const switchPage = (page) => { + const switchPage = page => { throttledHandleInputChange.current(keyword, page) } @@ -191,58 +237,58 @@ export default function AlgoliaSearchModal({ cRef }) { } return (
+ id='search-wrapper' + className={`${ + isModalOpen ? 'opacity-100' : 'invisible opacity-0 pointer-events-none' + } z-30 fixed h-screen w-screen left-0 top-0 sm:mt-12 flex items-start justify-center mt-0`}> {/* 模态框 */}
-
-
搜索
+ className={`${ + isModalOpen ? 'opacity-100' : 'invisible opacity-0 translate-y-10' + } flex flex-col justify-between w-full min-h-[10rem] h-full md:h-fit max-w-xl dark:bg-hexo-black-gray dark:border-gray-800 bg-white dark:bg- p-5 rounded-lg z-50 shadow border hover:border-blue-600 duration-300 transition-all `}> +
+
+ 搜索 +
+ className='text-gray-600 fa-solid fa-xmark p-1 cursor-pointer hover:text-blue-600' + onClick={closeModal}>
handleInputChange(e)} - className="text-black dark:text-gray-200 bg-gray-50 dark:bg-gray-600 outline-blue-500 w-full px-4 my-2 py-1 mb-4 border rounded-md" + className='text-black dark:text-gray-200 bg-gray-50 dark:bg-gray-600 outline-blue-500 w-full px-4 my-2 py-1 mb-4 border rounded-md' ref={inputRef} /> {/* 标签组 */} -
+
- { - searchResults.length === 0 && keyword && !isLoading && ( -
-

无法找到相关结果 - "{keyword}"

-
- ) - } + {searchResults.length === 0 && keyword && !isLoading && ( +
+

+ {' '} + 无法找到相关结果 + "{keyword}" +

+
+ )}
- {totalHit === 0 && (
- { - ShortCutActions.map((action, index) => { - return
{action.key}
- {action.action}
- }) - } -
) - } + {totalHit === 0 && ( +
+ {ShortCutActions.map((action, index) => { + return ( + +
+ {action.key} +
+ + {action.action} + +
+ ) + })} +
+ )}
{totalHit > 0 && (

@@ -266,19 +319,18 @@ export default function AlgoliaSearchModal({ cRef }) {

)}
-
- - Algolia 提供搜索服务 +
+ + Algolia 提供搜索服务
-
{/* 遮罩 */}
) @@ -292,21 +344,31 @@ function TagGroups() { // 获取tagOptions数组前十个 const firstTenTags = tagOptions?.slice(0, 10) - return
- { - firstTenTags?.map((tag, index) => { - return -
-
{tag.name}
{tag.count ? {tag.count} : <>} -
- - - }) - } -
+ return ( +
+ {firstTenTags?.map((tag, index) => { + return ( + +
+
{tag.name}
+ {tag.count ? ( + {tag.count} + ) : ( + <> + )} +
+ + ) + })} +
+ ) } /** @@ -321,16 +383,16 @@ function Pagination(props) { return (
{Array.from({ length: totalPage }, (_, i) => { - const classNames = page === i - ? 'font-bold text-white bg-blue-600 dark:bg-yellow-600 rounded' - : 'hover:text-blue-600 hover:font-bold dark:text-gray-300' + const classNames = + page === i + ? 'font-bold text-white bg-blue-600 dark:bg-yellow-600 rounded' + : 'hover:text-blue-600 hover:font-bold dark:text-gray-300' return (
switchPage(i)} className={`text-center cursor-pointer w-6 h-6 ${classNames}`} - key={i} - > + key={i}> {i + 1}
) diff --git a/components/Comment.js b/components/Comment.js index ba0d4256..fce7078d 100644 --- a/components/Comment.js +++ b/components/Comment.js @@ -121,6 +121,11 @@ const Comment = ({ frontMatter, className }) => { return null } + // 特定文章关闭评论区 + if (frontMatter?.comment === 'Hide') { + return null + } + return (
{ - const handleContextMenu = (event) => { + setShow(false) + }, [router]) + + useEffect(() => { + const handleContextMenu = event => { event.preventDefault() // 计算点击位置加菜单宽高是否超出屏幕,如果超出则贴边弹出 - const x = (event.clientX < windowSize.width - width) ? event.clientX : windowSize.width - width - const y = (event.clientY < windowSize.height - height) ? event.clientY : windowSize.height - height + const x = + event.clientX < windowSize.width - width + ? event.clientX + : windowSize.width - width + const y = + event.clientY < windowSize.height - height + ? event.clientY + : windowSize.height - height setPosition({ y: `${y}px`, x: `${x}px` }) setShow(true) } - const handleClick = (event) => { - if (menuRef.current && !menuRef.current.contains(event.target)) { - setShow(false) - } + /** + * 鼠标点击即关闭菜单 + */ + const handleClick = event => { + setShow(false) } window.addEventListener('contextmenu', handleContextMenu) @@ -75,24 +86,24 @@ export default function CustomContextMenu(props) { function handleScrollTop() { window.scrollTo({ top: 0, behavior: 'smooth' }) - setShow(false) } function handleCopyLink() { const url = window.location.href - navigator.clipboard.writeText(url) + navigator.clipboard + .writeText(url) .then(() => { - console.log('页面地址已复制') + // console.log('页面地址已复制') + alert(`${locale.COMMON.PAGE_URL_COPIED} : ${url}`) }) - .catch((error) => { + .catch(error => { console.error('复制页面地址失败:', error) }) - setShow(false) } /** - * 切换主题 - */ + * 切换主题 + */ function handleChangeTheme() { const randomTheme = THEMES[Math.floor(Math.random() * THEMES.length)] // 从THEMES数组中 随机取一个主题 const query = router.query @@ -104,7 +115,7 @@ export default function CustomContextMenu(props) { * 复制内容 */ function handleCopy() { - const selectedText = document.getSelection().toString(); + const selectedText = document.getSelection().toString() if (selectedText) { const tempInput = document.createElement('input'); tempInput.value = selectedText; @@ -118,8 +129,6 @@ export default function CustomContextMenu(props) { } else { // alert("Please select some text first."); } - - setShow(false) } function handleChangeDarkMode() { @@ -131,77 +140,140 @@ export default function CustomContextMenu(props) { htmlElement.classList?.add(newStatus ? 'dark' : 'light') } + // 一些配置变量 + const CUSTOM_RIGHT_CLICK_CONTEXT_MENU_RANDOM_POST = siteConfig( + 'CUSTOM_RIGHT_CLICK_CONTEXT_MENU_RANDOM_POST' + ) + const CUSTOM_RIGHT_CLICK_CONTEXT_MENU_CATEGORY = siteConfig( + 'CUSTOM_RIGHT_CLICK_CONTEXT_MENU_CATEGORY' + ) + const CUSTOM_RIGHT_CLICK_CONTEXT_MENU_TAG = siteConfig( + 'CUSTOM_RIGHT_CLICK_CONTEXT_MENU_TAG' + ) + const CAN_COPY = siteConfig('CAN_COPY') + const CUSTOM_RIGHT_CLICK_CONTEXT_MENU_SHARE_LINK = siteConfig( + 'CUSTOM_RIGHT_CLICK_CONTEXT_MENU_SHARE_LINK' + ) + const CUSTOM_RIGHT_CLICK_CONTEXT_MENU_DARK_MODE = siteConfig( + 'CUSTOM_RIGHT_CLICK_CONTEXT_MENU_DARK_MODE' + ) + const CUSTOM_RIGHT_CLICK_CONTEXT_MENU_THEME_SWITCH = siteConfig( + 'CUSTOM_RIGHT_CLICK_CONTEXT_MENU_THEME_SWITCH' + ) return ( -
+
+ {/* 菜单内容 */} +
+ {/* 顶部导航按钮 */} +
+ + + + +
- {/* 菜单内容 */} -
- {/* 顶部导航按钮 */} -
- - - - -
- -
- - {/* 跳转导航按钮 */} -
- - {siteConfig('CUSTOM_RIGHT_CLICK_CONTEXT_MENU_RANDOM_POST') &&
- -
{locale.MENU.WALK_AROUND}
-
} - - {siteConfig('CUSTOM_RIGHT_CLICK_CONTEXT_MENU_CATEGORY') && - -
{locale.MENU.CATEGORY}
- } - - {siteConfig('CUSTOM_RIGHT_CLICK_CONTEXT_MENU_TAG') && - -
{locale.MENU.TAGS}
- } - -
- -
- - {/* 功能按钮 */} -
- - {siteConfig('CAN_COPY') && ( -
- -
{locale.MENU.COPY}
-
- )} - - {siteConfig('CUSTOM_RIGHT_CLICK_CONTEXT_MENU_SHARE_LINK') &&
- -
{locale.MENU.SHARE_URL}
-
} - - {siteConfig('CUSTOM_RIGHT_CLICK_CONTEXT_MENU_DARK_MODE') &&
- {isDarkMode ? : } -
{isDarkMode ? locale.MENU.LIGHT_MODE : locale.MENU.DARK_MODE}
-
} - - {siteConfig('CUSTOM_RIGHT_CLICK_CONTEXT_MENU_THEME_SWITCH') && ( -
- -
{locale.MENU.THEME_SWITCH}
-
- )} - -
+
+ {/* 跳转导航按钮 */} +
+ {CUSTOM_RIGHT_CLICK_CONTEXT_MENU_RANDOM_POST && ( +
+ +
{locale.MENU.WALK_AROUND}
-
+ )} + + {CUSTOM_RIGHT_CLICK_CONTEXT_MENU_CATEGORY && ( + + +
{locale.MENU.CATEGORY}
+ + )} + + {CUSTOM_RIGHT_CLICK_CONTEXT_MENU_TAG && ( + + +
{locale.MENU.TAGS}
+ + )} +
+ +
+ + {/* 功能按钮 */} +
+ {CAN_COPY && ( +
+ +
{locale.MENU.COPY}
+
+ )} + + {CUSTOM_RIGHT_CLICK_CONTEXT_MENU_SHARE_LINK && ( +
+ +
{locale.MENU.SHARE_URL}
+
+ )} + + {CUSTOM_RIGHT_CLICK_CONTEXT_MENU_DARK_MODE && ( +
+ {isDarkMode ? ( + + ) : ( + + )} +
+ {' '} + {isDarkMode ? locale.MENU.LIGHT_MODE : locale.MENU.DARK_MODE} +
+
+ )} + + {CUSTOM_RIGHT_CLICK_CONTEXT_MENU_THEME_SWITCH && ( +
+ +
+ {locale.MENU.THEME_SWITCH} +
+
+ )} +
+
+
) } diff --git a/components/ExternalPlugins.js b/components/ExternalPlugins.js index bf1c9e8b..24ba5b55 100644 --- a/components/ExternalPlugins.js +++ b/components/ExternalPlugins.js @@ -5,8 +5,7 @@ import LA51 from './LA51' import TianLiGPT from './TianliGPT' import WebWhiz from './Webwhiz' -import { CUSTOM_EXTERNAL_CSS, CUSTOM_EXTERNAL_JS } from '@/blog.config' -import { mapPageUrl } from '@/lib/notion/mapPageUrl' +import { convertInnerUrl } from '@/lib/notion/convertInnerUrl' import { isBrowser, loadExternalResource } from '@/lib/utils' import { useRouter } from 'next/router' import { useEffect } from 'react' @@ -44,8 +43,6 @@ const ExternalPlugin = props => { const CHATBASE_ID = siteConfig('CHATBASE_ID') const COMMENT_DAO_VOICE_ID = siteConfig('COMMENT_DAO_VOICE_ID') const AD_WWADS_ID = siteConfig('AD_WWADS_ID') - // const COMMENT_TWIKOO_ENV_ID = siteConfig('COMMENT_TWIKOO_ENV_ID') - // const COMMENT_TWIKOO_CDN_URL = siteConfig('COMMENT_TWIKOO_CDN_URL') const COMMENT_ARTALK_SERVER = siteConfig('COMMENT_ARTALK_SERVER') const COMMENT_ARTALK_JS = siteConfig('COMMENT_ARTALK_JS') const COMMENT_TIDIO_ID = siteConfig('COMMENT_TIDIO_ID') @@ -64,6 +61,8 @@ const ExternalPlugin = props => { const IMG_SHADOW = siteConfig('IMG_SHADOW') const ANIMATE_CSS_URL = siteConfig('ANIMATE_CSS_URL') const MOUSE_FOLLOW = siteConfig('MOUSE_FOLLOW') + const CUSTOM_EXTERNAL_CSS = siteConfig('CUSTOM_EXTERNAL_CSS') + const CUSTOM_EXTERNAL_JS = siteConfig('CUSTOM_EXTERNAL_JS') // 自定义样式css和js引入 if (isBrowser) { @@ -106,7 +105,7 @@ const ExternalPlugin = props => { } // 映射url - mapPageUrl(props?.allNavPages) + convertInnerUrl(props?.allNavPages) }, [router]) useEffect(() => { diff --git a/components/Fireworks.js b/components/Fireworks.js index cb6a2d98..a57a3023 100644 --- a/components/Fireworks.js +++ b/components/Fireworks.js @@ -18,7 +18,7 @@ const Fireworks = () => { // 异步加载 async function loadFireworks() { loadExternalResource( - 'https://cdn.bootcdn.net/ajax/libs/animejs/3.2.1/anime.min.js', + 'https://cdnjs.snrat.com/ajax/libs/animejs/3.2.1/anime.min.js', 'js' ).then(() => { loadExternalResource('/js/fireworks.js', 'js').then(() => { @@ -35,7 +35,11 @@ const Fireworks = () => { loadFireworks() return () => { - // 在组件卸载时清理资源(如果需要) + // 在组件卸载时清理资源 + const fireworksElements = document.getElementsByClassName('fireworks') + while (fireworksElements.length > 0) { + fireworksElements[0].parentNode.removeChild(fireworksElements[0]) + } } }, []) diff --git a/components/LazyImage.js b/components/LazyImage.js index 4e353ad8..039fed37 100644 --- a/components/LazyImage.js +++ b/components/LazyImage.js @@ -1,6 +1,7 @@ import { siteConfig } from '@/lib/config' import Head from 'next/head' import { useEffect, useRef, useState } from 'react' + /** * 图片懒加载 * @param {*} param0 @@ -20,27 +21,52 @@ export default function LazyImage({ style }) { const maxWidth = siteConfig('IMAGE_COMPRESS_WIDTH') + const defaultPlaceholderSrc = siteConfig('IMG_LAZY_LOAD_PLACEHOLDER') const imageRef = useRef(null) - const [adjustedSrc, setAdjustedSrc] = useState( - placeholderSrc || siteConfig('IMG_LAZY_LOAD_PLACEHOLDER') + const [currentSrc, setCurrentSrc] = useState( + placeholderSrc || defaultPlaceholderSrc ) - if (!placeholderSrc) { - placeholderSrc = siteConfig('IMG_LAZY_LOAD_PLACEHOLDER') - } - /** - * 图片加载成功回调 + * 占位图加载成功 */ - const handleImageLoad = () => { + const handleThumbnailLoaded = () => { + if (typeof onLoad === 'function') { + // onLoad() // 触发传递的onLoad回调函数 + } + } + // 原图加载完成 + const handleImageLoaded = img => { if (typeof onLoad === 'function') { onLoad() // 触发传递的onLoad回调函数 } } + /** + * 图片加载失败回调 + */ + const handleImageError = () => { + if (imageRef.current) { + // 尝试加载 placeholderSrc,如果失败则加载 defaultPlaceholderSrc + if (imageRef.current.src !== placeholderSrc && placeholderSrc) { + imageRef.current.src = placeholderSrc + } else { + imageRef.current.src = defaultPlaceholderSrc + } + } + } useEffect(() => { - const adjustedImageSrc = adjustImgSize(src, maxWidth) - setAdjustedSrc(adjustedImageSrc) + const adjustedImageSrc = + adjustImgSize(src, maxWidth) || defaultPlaceholderSrc + + // 加载原图 + const img = new Image() + img.src = adjustedImageSrc + img.onload = () => { + setCurrentSrc(adjustedImageSrc) + handleImageLoaded(adjustedImageSrc) + } + img.onerror = handleImageError const observer = new IntersectionObserver( entries => { @@ -69,9 +95,10 @@ export default function LazyImage({ // 动态添加width、height和className属性,仅在它们为有效值时添加 const imgProps = { ref: imageRef, - src: priority ? adjustedSrc : placeholderSrc, + src: currentSrc, alt: alt, - onLoad: handleImageLoad + onLoad: handleThumbnailLoaded, // 缩略图加载完成 + onError: handleImageError // 添加onError处理函数 } if (id) { @@ -95,6 +122,7 @@ export default function LazyImage({ if (style) { imgProps.style = style } + return ( <> {/* eslint-disable-next-line @next/next/no-img-element */} @@ -102,24 +130,25 @@ export default function LazyImage({ {/* 预加载 */} {priority && ( - + )} ) } + /** * 根据窗口尺寸决定压缩图片宽度 * @param {*} src * @param {*} maxWidth * @returns */ - const adjustImgSize = (src, maxWidth) => { if (!src) { - return siteConfig('IMG_LAZY_LOAD_PLACEHOLDER') + return null } - const screenWidth = window.screen.width + const screenWidth = + (typeof window !== 'undefined' && window?.screen?.width) || maxWidth // 屏幕尺寸大于默认图片尺寸,没必要再压缩 if (screenWidth > maxWidth) { diff --git a/components/LoadingProgress.js b/components/LoadingProgress.js index 61ea172e..f217765f 100644 --- a/components/LoadingProgress.js +++ b/components/LoadingProgress.js @@ -11,7 +11,7 @@ export default function LoadingProgress() { // 加载进度条 useEffect(() => { loadExternalResource( - 'https://cdn.bootcdn.net/ajax/libs/nprogress/0.2.0/nprogress.min.js', + 'https://cdnjs.snrat.com/ajax/libs/nprogress/0.2.0/nprogress.min.js', 'js' ).then(() => { if (window.NProgress) { @@ -19,7 +19,7 @@ export default function LoadingProgress() { // 调速 window.NProgress.settings.minimun = 0.1 loadExternalResource( - 'https://cdn.bootcdn.net/ajax/libs/nprogress/0.2.0/nprogress.min.css', + 'https://cdnjs.snrat.com/ajax/libs/nprogress/0.2.0/nprogress.min.css', 'css' ) } diff --git a/components/MouseFollow.js b/components/MouseFollow.js index 2215a0ce..cc10d29c 100644 --- a/components/MouseFollow.js +++ b/components/MouseFollow.js @@ -15,8 +15,26 @@ const MOUSE_FOLLOW = () => { loadExternalResource('/js/mouse-follow.js', 'js').then(url => { window.createMouseCanvas && window.createMouseCanvas()({ type, color }) }) + + return () => { + // 在组件卸载时清理资源 + const mouseFollowElement = document.getElementById('vixcityCanvas') + mouseFollowElement?.parentNode?.removeChild(mouseFollowElement) + } }, []) - return <> + return ( + <> + + + ) } export default MOUSE_FOLLOW diff --git a/components/Notification.js b/components/Notification.js new file mode 100644 index 00000000..4fd4fda1 --- /dev/null +++ b/components/Notification.js @@ -0,0 +1,63 @@ +import { useState } from 'react' + +/** + * 弹框通知 + * @returns + */ +const useNotification = () => { + const [message, setMessage] = useState('') + const [isVisible, setIsVisible] = useState(false) + + const showNotification = msg => { + setMessage(msg) + setIsVisible(true) + setTimeout(() => { + closeNotification() + }, 3000) + } + + const closeNotification = () => { + setIsVisible(false) + setMessage('') + } + + // 测试通知效果 + // const toggleVisible = () => { + // setIsVisible(prev => !prev) // 使用函数式更新 + // } + // useEffect(() => { + // document?.addEventListener('click', toggleVisible) + // return () => { + // document?.removeEventListener('click', toggleVisible) + // } + // }, []) + + /** + * 通知组件 + * @returns + */ + const Notification = () => { + return ( +
+
+ {message} + +
+
+ ) + } + + return { + showNotification, + closeNotification, + Notification + } +} + +export default useNotification diff --git a/components/NotionPage.js b/components/NotionPage.js index 41716cf4..5bc3c31a 100644 --- a/components/NotionPage.js +++ b/components/NotionPage.js @@ -31,7 +31,7 @@ const NotionPage = ({ post, className }) => { // 页面首次打开时执行的勾子 useEffect(() => { // 检测当前的url并自动滚动到对应目标 - autoScrollToTarget() + autoScrollToHash() }, []) // 页面文章发生变化时会执行的勾子 @@ -144,9 +144,9 @@ const processGalleryImg = zoom => { } /** - * 根据url参数自动滚动到指定区域 + * 根据url参数自动滚动到锚位置 */ -const autoScrollToTarget = () => { +const autoScrollToHash = () => { setTimeout(() => { // 跳转到指定标题 const needToJumpToTitle = window.location.hash diff --git a/components/ThemeSwitch.js b/components/ThemeSwitch.js index 788f81b1..33c7ff8f 100644 --- a/components/ThemeSwitch.js +++ b/components/ThemeSwitch.js @@ -1,17 +1,18 @@ import { useGlobal } from '@/lib/global' -import { useState } from 'react' -import { Draggable } from './Draggable' +import LANGS from '@/lib/lang' +import { getQueryParam } from '@/lib/utils' import { THEMES } from '@/themes/theme' import { useRouter } from 'next/router' +import { useState } from 'react' import DarkModeButton from './DarkModeButton' -import { getQueryParam } from '@/lib/utils' -import LANGS from '@/lib/lang' +import { Draggable } from './Draggable' /** * * @returns 主题切换 */ const ThemeSwitch = () => { - const { theme, lang, changeLang, locale, isDarkMode, toggleDarkMode } = useGlobal() + const { theme, lang, changeLang, locale, isDarkMode, toggleDarkMode } = + useGlobal() const router = useRouter() const currentTheme = getQueryParam(router.asPath, 'theme') || theme // const currentLang = getQueryParam(router.asPath, 'lang') || lang @@ -19,8 +20,12 @@ const ThemeSwitch = () => { // 修改当前路径url中的 theme 参数 // 例如 http://localhost?theme=hexo 跳转到 http://localhost?theme=newTheme - const onThemeSelectChange = (e) => { - document.ontouchmove = document.ontouchend = document.onmousemove = document.onmouseup = null + const onThemeSelectChange = e => { + document.ontouchmove = + document.ontouchend = + document.onmousemove = + document.onmouseup = + null setIsLoading(true) const newTheme = e.target.value const query = router.query @@ -28,59 +33,96 @@ const ThemeSwitch = () => { router.push({ pathname: router.pathname, query }).then(() => { setTimeout(() => { setIsLoading(false) - }, 500); + }, 500) }) } - const onLangSelectChange = (e) => { - document.ontouchmove = document.ontouchend = document.onmousemove = document.onmouseup = null + const onLangSelectChange = e => { + document.ontouchmove = + document.ontouchend = + document.onmousemove = + document.onmouseup = + null const newLang = e.target.value changeLang(newLang) } - return (<> - -
- {/* 深色按钮 */} -
- -
{isDarkMode ? locale.MENU.DARK_MODE : locale.MENU.LIGHT_MODE}
-
- - {/* 翻译按钮 */} -
- -
- - -
-
- - {/* 主题切换按钮 */} -
- -
- - -
-
+ return ( + <> + +
+ {/* 主题切换按钮 */} +
+ +
+ +
- +
- {/* 切换主题加载时的全屏遮罩 */} -
- + {/* 深色按钮 */} +
+ +
+ {isDarkMode ? locale.MENU.DARK_MODE : locale.MENU.LIGHT_MODE} +
+
+ + {/* 翻译按钮 */} +
+ +
+ + +
+
+ + + {/* 切换主题加载时的全屏遮罩 */} +
+ +
) } diff --git a/lib/config.js b/lib/config.js index 478bf84e..b4f70e82 100644 --- a/lib/config.js +++ b/lib/config.js @@ -2,7 +2,7 @@ import BLOG from '@/blog.config' import { useGlobal } from './global' -import { deepClone } from './utils' +import { deepClone, isUrl } from './utils' /** * 读取配置顺序 @@ -19,7 +19,7 @@ export const siteConfig = (key, defaultVal = null, extendConfig = {}) => { return null } - // 特殊配置处理;某些配置只在服务端生效;而Global的NOTION_CONFIG仅限前端组件使用,因此需要从extendConfig中读取 + // 特殊配置处理;以下配置只在服务端生效;而Global的NOTION_CONFIG仅限前端组件使用,因此需要从extendConfig中读取 switch (key) { case 'NEXT_REVALIDATE_SECOND': case 'POST_RECOMMEND_COUNT': @@ -32,6 +32,8 @@ export const siteConfig = (key, defaultVal = null, extendConfig = {}) => { case 'POST_LIST_STYLE': case 'POST_LIST_PREVIEW': case 'POST_URL_PREFIX_MAPPING_CATEGORY': + case 'IS_TAG_COLOR_DISTINGUISHED': + case 'TAG_SORT_BY_COUNT': return convertVal(extendConfig[key] || defaultVal || BLOG[key]) default: } @@ -53,8 +55,8 @@ export const siteConfig = (key, defaultVal = null, extendConfig = {}) => { let siteInfo = null if (global) { - val = global.NOTION_CONFIG?.[key] siteInfo = global.siteInfo + val = global.NOTION_CONFIG?.[key] || global.THEME_CONFIG?.[key] } if (!val) { @@ -89,46 +91,62 @@ export const siteConfig = (key, defaultVal = null, extendConfig = {}) => { return defaultVal } - // 从Notion_CONFIG读取的配置通常都是字符串,适当转义 return convertVal(val) } /** - * 配置默认都是string类型; - * 识别配置的值是否数字、布尔、[]数组,若是则转成对应类型 + * 从环境变量和NotionConfig读取的配置都是string类型; + * 这里识别出配置的字符值若为否 数字、布尔、[]数组,{}对象,若是则转成对应类型 + * 使用JSON和eval两个函数 * @param {*} val * @returns */ export const convertVal = val => { - if (typeof val === 'string') { - // 解析布尔 - if (val === 'true' || val === 'false') { - return JSON.parse(val) - } - - // 解析数字,parseInt将字符串转换为数字 - if (/^\d+$/.test(val)) { - return parseInt(val) - } - // 转移 [] , {} 这种json串为json对象 - try { - const parsedJson = JSON.parse(val) - // 检查解析后的结果是否是对象或数组 - if (typeof parsedJson === 'object' && parsedJson !== null) { - return parsedJson - } - } catch (error) { - // JSON 解析失败,返回原始字符串值 - return val - } - } - - try { - return JSON.parse(val) - } catch (error) { - // 如果值是一个字符串但不是有效的 JSON 格式,直接返回字符串 + // 如果传入参数本身就是obj、数组、boolean 就无需处理 + if (typeof val !== 'string' || !val) { return val } + + // 解析数字,parseInt将字符串转换为数字 + if (/^\d+$/.test(val)) { + return parseInt(val) + } + + // 检测是否url + if (isUrl(val)) { + return val + } + // 检测是否url + if (val === 'true' || val === 'false') { + return JSON.parse(val) + } + + // 配置值前可能有污染的空格 + if (val.indexOf('[') < 0 && val.indexOf('{') < 0) { + return val + } + + // 转换 [] , {} , true/false 这类字符串为对象 + try { + // 尝试解析json + const parsedJson = JSON.parse(val) + if (parsedJson !== null) { + return parsedJson + } + } catch (error) { + // try { + // // 尝试解析对象,对象解析能力不如上一步的json + // const evalObj = eval('(' + val + ')') + // if (evalObj !== null) { + // return evalObj + // } + // } catch (error) { + // // Ojbject 解析失败,返回原始字符串值 + // return val + // } + return val + } + return val } /** diff --git a/lib/db/getSiteData.js b/lib/db/getSiteData.js index 272c17f1..48de0f27 100755 --- a/lib/db/getSiteData.js +++ b/lib/db/getSiteData.js @@ -5,18 +5,18 @@ import getAllPageIds from '@/lib/notion/getAllPageIds' import { getAllTags } from '@/lib/notion/getAllTags' import { getConfigMapFromConfigPage } from '@/lib/notion/getNotionConfig' import getPageProperties, { - adjustPageProperties + adjustPageProperties } from '@/lib/notion/getPageProperties' -import { fetchInBatches, getPostBlocks } from '@/lib/notion/getPostBlocks' +import { fetchInBatches, getPage } from '@/lib/notion/getPostBlocks' import { compressImage, mapImgUrl } from '@/lib/notion/mapImage' import { deepClone } from '@/lib/utils' import { idToUuid } from 'notion-utils' import { siteConfig } from '../config' -import { extractLangId, extractLangPrefix } from '../utils/pageId' +import { extractLangId, extractLangPrefix, getShortId } from '../utils/pageId' export { getAllTags } from '../notion/getAllTags' export { getPost } from '../notion/getNotionPost' -export { getPostBlocks } from '../notion/getPostBlocks' +export { getPage as getPostBlocks } from '../notion/getPostBlocks' /** * 获取博客数据; 基于Notion实现 @@ -77,7 +77,18 @@ export async function getNotionPageData({ pageId, from }) { } // 返回给前端的数据做处理 - const db = deepClone(data) + return handleDataBeforeReturn(deepClone(data)) +} + +/** + * 返回给浏览器前端的数据处理 + * 适当脱敏 + * 减少体积 + * 其它处理 + * @param {*} db + */ +function handleDataBeforeReturn(db) { + // 清理多余数据 delete db.block delete db.schema delete db.rawMetadata @@ -91,42 +102,88 @@ export async function getNotionPageData({ pageId, from }) { // 清理多余的块 if (db?.notice) { db.notice = cleanBlock(db?.notice) + delete db.notice?.id } - if (db?.post) { - db.post = cleanBlock(db?.post) - } + + db.tagOptions = cleanIds(db?.tagOptions) + db.categoryOptions = cleanIds(db?.categoryOptions) + db.customMenu = cleanIds(db?.customMenu) + + // db.latestPosts = shortenIds(db?.latestPosts) + db.allNavPages = shortenIds(db?.allNavPages) + // db.allPages = cleanBlocks(db?.allPages) + return db } +/** + * 清理一组数据的id + * @param {*} items + * @returns + */ +function shortenIds(items) { + if (items && Array.isArray(items)) { + return deepClone( + items.map(item => { + item.short_id = getShortId(item.id) + delete item.id + return item + }) + ) + } + return items +} + +/** + * 清理一组数据的id + * @param {*} items + * @returns + */ +function cleanIds(items) { + if (items && Array.isArray(items)) { + return deepClone( + items.map(item => { + delete item.id + return item + }) + ) + } + return items +} + /** * 清理block数据 */ -function cleanBlock(post) { +function cleanBlock(item) { + const post = deepClone(item) const pageBlock = post?.blockMap?.block - for (const i in pageBlock) { - pageBlock[i] = cleanBlock(pageBlock[i]) - delete pageBlock[i]?.role - delete pageBlock[i]?.value?.version - delete pageBlock[i]?.value?.created_by_table - delete pageBlock[i]?.value?.created_by_id - delete pageBlock[i]?.value?.last_edited_by_table - delete pageBlock[i]?.value?.last_edited_by_id - delete pageBlock[i]?.value?.space_id - delete pageBlock[i]?.value?.version - delete pageBlock[i]?.value?.format?.copied_from_pointer - delete pageBlock[i]?.value?.format?.block_locked_by - delete pageBlock[i]?.value?.parent_table - delete pageBlock[i]?.value?.copied_from_pointer - delete pageBlock[i]?.value?.copied_from - delete pageBlock[i]?.value?.created_by_table - delete pageBlock[i]?.value?.created_by_id - delete pageBlock[i]?.value?.last_edited_by_table - delete pageBlock[i]?.value?.last_edited_by_id - delete pageBlock[i]?.value?.permissions - delete pageBlock[i]?.value?.alive - } - + // delete post?.id // delete post?.blockMap?.collection + + if (pageBlock) { + for (const i in pageBlock) { + pageBlock[i] = cleanBlock(pageBlock[i]) + delete pageBlock[i]?.role + delete pageBlock[i]?.value?.version + delete pageBlock[i]?.value?.created_by_table + delete pageBlock[i]?.value?.created_by_id + delete pageBlock[i]?.value?.last_edited_by_table + delete pageBlock[i]?.value?.last_edited_by_id + delete pageBlock[i]?.value?.space_id + delete pageBlock[i]?.value?.version + delete pageBlock[i]?.value?.format?.copied_from_pointer + delete pageBlock[i]?.value?.format?.block_locked_by + delete pageBlock[i]?.value?.parent_table + delete pageBlock[i]?.value?.copied_from_pointer + delete pageBlock[i]?.value?.copied_from + delete pageBlock[i]?.value?.created_by_table + delete pageBlock[i]?.value?.created_by_id + delete pageBlock[i]?.value?.last_edited_by_table + delete pageBlock[i]?.value?.last_edited_by_id + delete pageBlock[i]?.value?.permissions + delete pageBlock[i]?.value?.alive + } + } return post } @@ -290,7 +347,6 @@ export function getNavPages({ allPages }) { return ( post && post?.slug && - !post?.slug?.startsWith('http') && post?.type === 'Post' && post?.status === 'Published' ) @@ -320,7 +376,7 @@ async function getNotice(post) { return null } - post.blockMap = await getPostBlocks(post.id, 'data-notice') + post.blockMap = await getPage(post.id, 'data-notice') return post } @@ -372,7 +428,7 @@ const EmptyData = pageId => { */ async function getDataBaseInfoByNotionAPI({ pageId, from }) { console.log('[Fetching Data]', pageId, from) - const pageRecordMap = await getPostBlocks(pageId, from) + const pageRecordMap = await getPage(pageId, from) if (!pageRecordMap) { console.error('can`t get Notion Data ; Which id is: ', pageId) return {} @@ -455,6 +511,7 @@ async function getDataBaseInfoByNotionAPI({ pageId, from }) { adjustPageProperties(element, NOTION_CONFIG) }) + // 站点基础信息 const siteInfo = getSiteInfo({ collection, block, pageId }) // 文章计数 @@ -468,7 +525,7 @@ async function getDataBaseInfoByNotionAPI({ pageId, from }) { return ( post && post?.slug && - !post?.slug?.startsWith('http') && + // !post?.slug?.startsWith('http') && (post?.status === 'Invisible' || post?.status === 'Published') ) }) @@ -490,11 +547,17 @@ async function getDataBaseInfoByNotionAPI({ pageId, from }) { ) })?.[0] ) + // 所有分类 const categoryOptions = getAllCategories({ allPages, categoryOptions: getCategoryOptions(schema) }) - const tagOptions = getAllTags({ allPages, tagOptions: getTagOptions(schema) }) + // 所有标签 + const tagOptions = getAllTags({ + allPages, + tagOptions: getTagOptions(schema), + NOTION_CONFIG + }) // 旧的菜单 const customNav = getCustomNav({ allPages: collectionData.filter( diff --git a/lib/global.js b/lib/global.js index d673c3ea..1dcd0c9f 100644 --- a/lib/global.js +++ b/lib/global.js @@ -1,5 +1,6 @@ import { THEMES, + getThemeConfig, initDarkMode, saveDarkModeToLocalStorage } from '@/themes/theme' @@ -29,11 +30,14 @@ export function GlobalContextProvider(props) { tagOptions, NOTION_CONFIG } = props + const [lang, updateLang] = useState(NOTION_CONFIG?.LANG || LANG) // 默认语言 const [locale, updateLocale] = useState( generateLocaleDict(NOTION_CONFIG?.LANG || LANG) ) // 默认语言 const [theme, setTheme] = useState(NOTION_CONFIG?.THEME || THEME) // 默认博客主题 + const [THEME_CONFIG, SET_THEME_CONFIG] = useState(null) // 主题配置 + const defaultDarkMode = NOTION_CONFIG?.APPEARANCE || APPEARANCE const [isDarkMode, updateDarkMode] = useState(defaultDarkMode === 'dark') // 默认深色模式 const [onLoading, setOnLoading] = useState(false) // 抓取文章数据 @@ -54,6 +58,12 @@ export function GlobalContextProvider(props) { return newTheme } + // 抓取主题配置 + const updateThemeConfig = async theme => { + const config = await getThemeConfig(theme) + SET_THEME_CONFIG(config) + } + // 切换深色模式 const toggleDarkMode = () => { const newStatus = !isDarkMode @@ -79,7 +89,10 @@ export function GlobalContextProvider(props) { useEffect(() => { initDarkMode(updateDarkMode, defaultDarkMode) initLocale(lang, locale, updateLang, updateLocale) - redirectUserLang(NOTION_PAGE_ID) + // 可以 + if (NOTION_CONFIG?.REDIRECT_LANG) { + redirectUserLang(NOTION_PAGE_ID) + } }, []) // 加载进度条 @@ -96,6 +109,9 @@ export function GlobalContextProvider(props) { setOnLoading(false) } + const currentTheme = router?.query?.theme || theme + updateThemeConfig(currentTheme) + router.events.on('routeChangeStart', handleStart) router.events.on('routeChangeError', handleStop) router.events.on('routeChangeComplete', handleStop) @@ -111,6 +127,7 @@ export function GlobalContextProvider(props) { value={{ fullWidth, NOTION_CONFIG, + THEME_CONFIG, toggleDarkMode, onLoading, setOnLoading, diff --git a/lib/lang/en-US.js b/lib/lang/en-US.js index 47bb171c..6b40a44c 100644 --- a/lib/lang/en-US.js +++ b/lib/lang/en-US.js @@ -14,11 +14,15 @@ export default { INDEX: 'Home', RSS: 'RSS', SEARCH: 'Search', + NAVIGATOR: 'NAV', ABOUT: 'About', MAIL: 'E-Mail', ARCHIVE: 'Archive' }, COMMON: { + THEME: 'Theme', + ARTICLE_LIST: 'Article List', + RECOMMEND_POSTS: 'Recommend Posts', MORE: 'More', NO_MORE: 'No More', LATEST_POSTS: 'Latest posts', @@ -33,15 +37,21 @@ export default { COPYRIGHT: 'Copyright', AUTHOR: 'Author', URL: 'URL', + NOW: 'NOW', + RECOMMEND_BADGES: 'Recommend', + BLOG: 'Blog', POSTS: 'Posts', ARTICLE: 'Article', VISITORS: 'Visitors', VIEWS: 'Views', - COPYRIGHT_NOTICE: 'All articles in this blog, except for special statements, adopt BY-NC-SA agreement. Please indicate the source!', + PAGE_URL_COPIED: 'Page URL copied', + COPYRIGHT_NOTICE: + 'All articles in this blog, except for special statements, adopt BY-NC-SA agreement. Please indicate the source!', RESULT_OF_SEARCH: 'Results Found', ARTICLE_DETAIL: 'Article Details', PASSWORD_ERROR: 'Password Error!', ARTICLE_LOCK_TIPS: 'Please Enter the password:', + ARTICLE_UNLOCK_TIPS: 'Article Unlocked', NO_RESULTS_FOUND: 'No results found.', SUBMIT: 'Submit', POST_TIME: 'Post on', @@ -55,7 +65,8 @@ export default { START_READING: 'Start Reading', MINUTE: 'min', WORD_COUNT: 'Words', - READ_TIME: 'Read Time' + READ_TIME: 'Read Time', + NEXT_POST: '下一篇' }, PAGINATION: { PREV: 'Prev', diff --git a/lib/lang/ja-JP.js b/lib/lang/ja-JP.js index c27eef0b..628b28b3 100644 --- a/lib/lang/ja-JP.js +++ b/lib/lang/ja-JP.js @@ -29,11 +29,14 @@ export default { ARTICLE: '記事', VISITORS: '人の訪問者', VIEWS: '回の閲覧', - COPYRIGHT_NOTICE: 'この記事はCC BY-NC-SA 4.0 ライセンスの下でライセンスされています。転載する場合には出典を明らかにしてください。', + COPYRIGHT_NOTICE: + 'この記事はCC BY-NC-SA 4.0 ライセンスの下でライセンスされています。転載する場合には出典を明らかにしてください。', RESULT_OF_SEARCH: '個の検索結果', ARTICLE_DETAIL: '記事の詳細', PASSWORD_ERROR: 'パスワードが違います!', - ARTICLE_LOCK_TIPS: 'この記事はロックされています。アクセスパスワードを入力してください。', + ARTICLE_LOCK_TIPS: + 'この記事はロックされています。アクセスパスワードを入力してください。', + ARTICLE_UNLOCK_TIPS: '記事がロック解除されました', SUBMIT: '送信', POST_TIME: '公開日', LAST_EDITED_TIME: '最終更新日', diff --git a/lib/lang/zh-CN.js b/lib/lang/zh-CN.js index a2c8b1c7..c49ed544 100644 --- a/lib/lang/zh-CN.js +++ b/lib/lang/zh-CN.js @@ -20,6 +20,9 @@ export default { ARCHIVE: '归档' }, COMMON: { + THEME: 'Theme', + ARTICLE_LIST: '文章列表', + RECOMMEND_POSTS: '推荐文章', MORE: '更多', NO_MORE: '没有更多了', LATEST_POSTS: '最新发布', @@ -35,16 +38,21 @@ export default { AUTHOR: '作者', URL: '链接', ANALYTICS: '统计', + RECOMMEND_BADGES: '荐', + BLOG: '博客', + NOW: '此刻', POSTS: '篇文章', ARTICLE: '文章', VISITORS: '位访客', VIEWS: '次查看', + PAGE_URL_COPIED: '页面地址已复制', COPYRIGHT_NOTICE: '本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。', RESULT_OF_SEARCH: '篇搜索到的结果', NO_RESULTS_FOUND: '没有找到文章', ARTICLE_DETAIL: '文章详情', PASSWORD_ERROR: '密码错误!', ARTICLE_LOCK_TIPS: '文章已上锁,请输入访问密码', + ARTICLE_UNLOCK_TIPS: '文章已解锁', SUBMIT: '提交', POST_TIME: '发布于', LAST_EDITED_TIME: '最后更新', @@ -57,7 +65,8 @@ export default { START_READING: '开始阅读', MINUTE: '分钟', WORD_COUNT: '字数', - READ_TIME: '阅读时长' + READ_TIME: '阅读时长', + NEXT_POST: '下一篇' }, PAGINATION: { PREV: '上页', diff --git a/lib/lang/zh-HK.js b/lib/lang/zh-HK.js index f434fe6e..6e9df618 100644 --- a/lib/lang/zh-HK.js +++ b/lib/lang/zh-HK.js @@ -5,7 +5,39 @@ export default { RSS: '訂閱', SEARCH: '搜尋', ABOUT: '關於', - MAIL: '電郵' + MAIL: '電郵', + NAVIGATOR: '導航', + ARCHIVE: '封存' + }, + COMMON: { + ARTICLE_LIST: '文章列表', + MORE: '更多', + NO_MORE: '沒有更多了', + LATEST_POSTS: '最新文章', + TAGS: '標籤', + NO_TAG: '無標籤', + CATEGORY: '分類', + SHARE: '分享', + SCAN_QR_CODE: 'QRCode', + URL_COPIED: '連結已複製!', + TABLE_OF_CONTENTS: '目錄', + RELATE_POSTS: '相關文章', + COPYRIGHT: '著作權', + AUTHOR: '作者', + URL: '連結', + ANALYTICS: '分析', + POSTS: '篇文章', + ARTICLE: '文章', + VISITORS: '位訪客', + VIEWS: '次查看', + COPYRIGHT_NOTICE: '本文採用 CC BY-NC-SA 4.0 許可協議,轉載請註明出處。', + RESULT_OF_SEARCH: '篇搜尋到的结果', + ARTICLE_DETAIL: '完整文章', + PASSWORD_ERROR: '密碼錯誤!', + ARTICLE_LOCK_TIPS: '文章已上鎖,請輸入訪問密碼', + SUBMIT: '提交', + POST_TIME: '发布于', + LAST_EDITED_TIME: '最后更新' }, PAGINATION: { PREV: '上一頁', @@ -13,7 +45,7 @@ export default { }, SEARCH: { ARTICLES: '搜尋文章', - TAGS: '搜尋標簽' + TAGS: '搜尋標籤' }, POST: { BACK: '返回', diff --git a/lib/lang/zh-TW.js b/lib/lang/zh-TW.js index de746c08..1fe2dbb1 100644 --- a/lib/lang/zh-TW.js +++ b/lib/lang/zh-TW.js @@ -10,6 +10,7 @@ export default { ARCHIVE: '封存' }, COMMON: { + ARTICLE_LIST: '文章列表', MORE: '更多', NO_MORE: '沒有更多了', LATEST_POSTS: '最新文章', diff --git a/lib/notion/mapPageUrl.js b/lib/notion/convertInnerUrl.js similarity index 57% rename from lib/notion/mapPageUrl.js rename to lib/notion/convertInnerUrl.js index 80fe70d8..8762d805 100644 --- a/lib/notion/mapPageUrl.js +++ b/lib/notion/convertInnerUrl.js @@ -1,33 +1,44 @@ -import { uuidToId } from 'notion-utils' +import { idToUuid } from 'notion-utils' import { checkStrIsNotionId, getLastPartOfUrl, isBrowser } from '../utils' /** * 处理页面内连接跳转: - * 1. 若是本站域名,则在当前窗口打开、不开新窗口 - * 2. 若是Notion笔记中的内链,尝试转换成博客中现有的文章地址 + * 1.若是本站域名,则在当前窗口打开、不开新窗口 + * 2.url是notion-id,转成站内文章链接 */ -export const mapPageUrl = allPages => { +export const convertInnerUrl = allPages => { if (isBrowser) { + const allAnchorTags = document + ?.getElementById('notion-article') + ?.getElementsByTagName('a') + + if (!allAnchorTags) { + return + } const currentURL = window.location.origin + window.location.pathname - const allAnchorTags = document.getElementsByTagName('a') // 或者使用 document.querySelectorAll('a') 获取 NodeList + // url替换成slug for (const anchorTag of allAnchorTags) { // 检查url if (anchorTag?.href) { // 如果url是一个Notion_id,尝试匹配成博客的文章内链 const slug = getLastPartOfUrl(anchorTag.href) if (checkStrIsNotionId(slug)) { - const slugPage = allPages?.find(page => uuidToId(page.id) === slug) + const slugPage = allPages?.find(page => { + return idToUuid(slug).indexOf(page.short_id) === 0 + }) if (slugPage) { anchorTag.href = slugPage?.href } } } + } + // 链接在当前页面打开 + for (const anchorTag of allAnchorTags) { if (anchorTag?.target === '_blank') { const hrefWithoutQueryHash = anchorTag.href.split('?')[0].split('#')[0] const hrefWithRelativeHash = - currentURL.split('#')[0] + anchorTag.href.split('#')[1] - + currentURL.split('#')[0] || '' + anchorTag.href.split('#')[1] || '' if ( currentURL === hrefWithoutQueryHash || currentURL === hrefWithRelativeHash diff --git a/lib/notion/getAllCategories.js b/lib/notion/getAllCategories.js index 97c8ab3a..c7e66ba2 100644 --- a/lib/notion/getAllCategories.js +++ b/lib/notion/getAllCategories.js @@ -4,7 +4,7 @@ import { isIterable } from '../utils' * 获取所有文章的标签 * @param allPosts * @param sliceCount 默认截取数量为12,若为0则返回全部 - * @param tagOptions tags的下拉选项 + * @param categoryOptions categories的下拉选项 * @returns {Promise<{}|*[]>} */ @@ -13,8 +13,14 @@ import { isIterable } from '../utils' * @param allPosts * @returns {Promise<{}|*[]>} */ -export function getAllCategories({ allPages, categoryOptions, sliceCount = 0 }) { - const allPosts = allPages?.filter(page => page.type === 'Post' && page.status === 'Published') +export function getAllCategories({ + allPages, + categoryOptions, + sliceCount = 0 +}) { + const allPosts = allPages?.filter( + page => page.type === 'Post' && page.status === 'Published' + ) if (!allPosts || !categoryOptions) { return [] } diff --git a/lib/notion/getAllTags.js b/lib/notion/getAllTags.js index 4c7559b9..cac8dac7 100644 --- a/lib/notion/getAllTags.js +++ b/lib/notion/getAllTags.js @@ -1,5 +1,5 @@ +import { siteConfig } from '../config' import { isIterable } from '../utils' -import BLOG from '@/blog.config' /** * 获取所有文章的标签 @@ -8,8 +8,15 @@ import BLOG from '@/blog.config' * @param tagOptions tags的下拉选项 * @returns {Promise<{}|*[]>} */ -export function getAllTags({ allPages, sliceCount = 0, tagOptions }) { - const allPosts = allPages?.filter(page => page.type === 'Post' && page.status === 'Published') +export function getAllTags({ + allPages, + sliceCount = 0, + tagOptions, + NOTION_CONFIG +}) { + const allPosts = allPages?.filter( + page => page.type === 'Post' && page.status === 'Published' + ) if (!allPosts || !tagOptions) { return [] @@ -27,7 +34,12 @@ export function getAllTags({ allPages, sliceCount = 0, tagOptions }) { }) const list = [] - const { IS_TAG_COLOR_DISTINGUISHED } = BLOG + const IS_TAG_COLOR_DISTINGUISHED = siteConfig( + 'IS_TAG_COLOR_DISTINGUISHED', + false, + NOTION_CONFIG + ) + const TAG_SORT_BY_COUNT = siteConfig('TAG_SORT_BY_COUNT', true, NOTION_CONFIG) if (isIterable(tagOptions)) { if (!IS_TAG_COLOR_DISTINGUISHED) { // 如果不区分颜色, 那么不同颜色相同名称的tag当做同一种tag @@ -52,7 +64,10 @@ export function getAllTags({ allPages, sliceCount = 0, tagOptions }) { } // 按照数量排序 - // list.sort((a, b) => b.count - a.count) + if (TAG_SORT_BY_COUNT) { + list.sort((a, b) => b.count - a.count) + } + if (sliceCount && sliceCount > 0) { return list.slice(0, sliceCount) } else { diff --git a/lib/notion/getNotionConfig.js b/lib/notion/getNotionConfig.js index 889005f2..c0ef7c29 100644 --- a/lib/notion/getNotionConfig.js +++ b/lib/notion/getNotionConfig.js @@ -9,7 +9,7 @@ import { getDateValue, getTextContent } from 'notion-utils' import { deepClone } from '../utils' import getAllPageIds from './getAllPageIds' -import { getPostBlocks } from './getPostBlocks' +import { getPage } from './getPostBlocks' /** * 从Notion中读取Config配置表 @@ -41,12 +41,12 @@ export async function getConfigMapFromConfigPage(allPages) { } const configPageId = configPage.id // console.log('[Notion配置]请求配置数据 ', configPage.id) - let pageRecordMap = await getPostBlocks(configPageId, 'config-table') + let pageRecordMap = await getPage(configPageId, 'config-table') // console.log('配置中心Page', configPageId, pageRecordMap) let content = pageRecordMap.block[configPageId].value.content for (const table of ['Config-Table', 'CONFIG-TABLE']) { if (content) break - pageRecordMap = await getPostBlocks(configPageId, table) + pageRecordMap = await getPage(configPageId, table) content = pageRecordMap.block[configPageId].value.content } diff --git a/lib/notion/getNotionPost.js b/lib/notion/getNotionPost.js index 3e096a11..f5aabb2a 100644 --- a/lib/notion/getNotionPost.js +++ b/lib/notion/getNotionPost.js @@ -2,7 +2,7 @@ import BLOG from '@/blog.config' import { idToUuid } from 'notion-utils' import { defaultMapImageUrl } from 'react-notion-x' import formatDate from '../utils/formatDate' -import { getPostBlocks } from './getPostBlocks' +import { getPage } from './getPostBlocks' /** * 根据页面ID获取内容 @@ -10,7 +10,7 @@ import { getPostBlocks } from './getPostBlocks' * @returns */ export async function getPost(pageId) { - const blockMap = await getPostBlocks(pageId, 'slug') + const blockMap = await getPage(pageId, 'slug') if (!blockMap) { return null } diff --git a/lib/notion/getPageProperties.js b/lib/notion/getPageProperties.js index 38eede18..53d7ce87 100644 --- a/lib/notion/getPageProperties.js +++ b/lib/notion/getPageProperties.js @@ -6,10 +6,11 @@ import formatDate from '../utils/formatDate' import md5 from 'js-md5' import { siteConfig } from '../config' import { - checkContainHttp, - convertUrlStartWithOneSlash, - sliceUrlFromHttp + checkStartWithHttp, + convertUrlStartWithOneSlash, + getLastSegmentFromUrl } from '../utils' +import { extractLangPrefix } from '../utils/pageId' import { mapImgUrl } from './mapImage' /** @@ -94,6 +95,7 @@ export default async function getPageProperties( properties.type = properties.type?.[0] || '' properties.status = properties.status?.[0] || '' properties.category = properties.category?.[0] || '' + properties.comment = properties.comment?.[0] || '' // 映射值:用户个性化type和status字段的下拉框选项,在此映射回代码的英文标识 mapProperties(properties) @@ -185,26 +187,38 @@ export function adjustPageProperties(properties, NOTION_CONFIG) { properties.name = properties.title ?? '' } - // 开启伪静态路径 - if (JSON.parse(NOTION_CONFIG?.PSEUDO_STATIC || BLOG.PSEUDO_STATIC)) { - if ( - !properties?.href?.endsWith('.html') && - !properties?.href?.startsWith('http') - ) { - properties.href += '.html' - } - } - - // 检查处理外链 - properties.href = checkContainHttp(properties?.href) - ? sliceUrlFromHttp(properties?.href) - : convertUrlStartWithOneSlash(properties?.href) - - // 设置链接在页内或新页面打开 - if (properties.href?.indexOf('http') === 0) { + // http or https 开头的视为外链 + if (checkStartWithHttp(properties?.href)) { + properties.href = properties?.slug properties.target = '_blank' } else { properties.target = '_self' + // 伪静态路径右侧拼接.html + if (siteConfig('PSEUDO_STATIC', false, NOTION_CONFIG)) { + if ( + !properties?.href?.endsWith('.html') && + properties?.href !== '' && + properties?.href !== '#' && + properties?.href !== '/' + ) { + properties.href += '.html' + } + } + + // 相对路径转绝对路径:url左侧拼接 / + properties.href = convertUrlStartWithOneSlash(properties?.href) + } + + // 如果跳转链接是多语言,则在新窗口打开 + if (BLOG.NOTION_PAGE_ID.indexOf(',') > 0) { + const siteIds = BLOG.NOTION_PAGE_ID.split(',') + for (let index = 0; index < siteIds.length; index++) { + const siteId = siteIds[index] + const prefix = extractLangPrefix(siteId) + if (getLastSegmentFromUrl(properties.href) === prefix) { + properties.target = '_blank' + } + } } // 密码字段md5 @@ -221,6 +235,10 @@ export function adjustPageProperties(properties, NOTION_CONFIG) { * @returns */ function generateCustomizeSlug(postProperties, NOTION_CONFIG) { + // 外链不处理 + if (checkStartWithHttp(postProperties.slug)) { + return postProperties.slug + } let fullPrefix = '' const allSlugPatterns = siteConfig( 'POST_URL_PREFIX', @@ -272,5 +290,10 @@ function generateCustomizeSlug(postProperties, NOTION_CONFIG) { if (fullPrefix.endsWith('/')) { fullPrefix = fullPrefix.substring(0, fullPrefix.length - 1) // 去掉尾部部的"/" } - return `${fullPrefix}/${postProperties.slug ?? postProperties.id}` + + if(fullPrefix){ + return `${fullPrefix}/${postProperties.slug ?? postProperties.id}` + }else{ + return `${postProperties.slug ?? postProperties.id}` + } } diff --git a/lib/notion/getPostBlocks.js b/lib/notion/getPostBlocks.js index 0b151f17..2859345f 100644 --- a/lib/notion/getPostBlocks.js +++ b/lib/notion/getPostBlocks.js @@ -10,7 +10,7 @@ import { deepClone, delay } from '../utils' * @param {*} slice * @returns */ -export async function getPostBlocks(id, from, slice) { +export async function getPage(id, from, slice) { const cacheKey = 'page_block_' + id let pageBlock = await getDataFromCache(cacheKey) if (pageBlock) { @@ -27,28 +27,6 @@ export async function getPostBlocks(id, from, slice) { return pageBlock } -/** - * 针对在getDataBaseInfoByNotionAPI=>getPostBlocks中获取不到的溢出的block-id,用此方法另外抓取 - * @param {*} id - * @param {*} from - * @returns - */ -export async function getSingleBlock(id, from) { - const cacheKey = 'single_block_' + id - let pageBlock = await getDataFromCache(cacheKey) - if (pageBlock) { - // console.log('[API<<--缓存]', `from:${from}`, cacheKey) - return pageBlock - } - - pageBlock = await getPageWithRetry(id, 'single_' + from) - - if (pageBlock) { - await setDataToCache(cacheKey, pageBlock) - } - return pageBlock -} - /** * 调用接口,失败会重试 * @param {*} id @@ -103,49 +81,69 @@ export async function getPageWithRetry(id, from, retryAttempts = 3) { function filterPostBlocks(id, blockMap, slice) { const clonePageBlock = deepClone(blockMap) let count = 0 + const blocksToProcess = Object.keys(clonePageBlock?.block || {}) // 循环遍历文档的每个block - for (const i in clonePageBlock?.block) { - const b = clonePageBlock?.block[i] - if (slice && slice > 0 && count > slice) { - delete clonePageBlock?.block[i] - continue + for (let i = 0; i < blocksToProcess.length; i++) { + const blockId = blocksToProcess[i] + const b = clonePageBlock?.block[blockId] + + if (slice && slice > 0 && count > slice) { + delete clonePageBlock?.block[blockId] + continue + } + + // 当BlockId等于PageId时移除 + if (b?.value?.id === id) { + // 此block含有敏感信息 + delete b?.value?.properties + continue + } + + count++ + + if (b?.value?.type === 'sync_block' && b?.value?.children) { + const childBlocks = b.value.children + // 移除同步块 + delete clonePageBlock.block[blockId] + // 用子块替代同步块 + childBlocks.forEach((childBlock, index) => { + const newBlockId = `${blockId}_child_${index}` + clonePageBlock.block[newBlockId] = childBlock + blocksToProcess.splice(i + index + 1, 0, newBlockId) + }) + // 重新处理新加入的子块 + i-- + continue + } + + // 处理 c++、c#、汇编等语言名字映射 + if (b?.value?.type === 'code') { + if (b?.value?.properties?.language?.[0][0] === 'C++') { + b.value.properties.language[0][0] = 'cpp' } - // 当BlockId等于PageId时移除 - if (b?.value?.id === id) { - // 此block含有敏感信息 - delete b?.value?.properties - continue + if (b?.value?.properties?.language?.[0][0] === 'C#') { + b.value.properties.language[0][0] = 'csharp' } - - count++ - // 处理 c++、c#、汇编等语言名字映射 - if (b?.value?.type === 'code') { - if (b?.value?.properties?.language?.[0][0] === 'C++') { - b.value.properties.language[0][0] = 'cpp' - } - if (b?.value?.properties?.language?.[0][0] === 'C#') { - b.value.properties.language[0][0] = 'csharp' - } - if (b?.value?.properties?.language?.[0][0] === 'Assembly') { - b.value.properties.language[0][0] = 'asm6502' - } - } - - // 如果是文件,或嵌入式PDF,需要重新加密签名 - if ( - (b?.value?.type === 'file' || - b?.value?.type === 'pdf' || - b?.value?.type === 'video' || - b?.value?.type === 'audio') && - b?.value?.properties?.source?.[0][0] && - b?.value?.properties?.source?.[0][0].indexOf('amazonaws.com') > 0 - ) { - const oldUrl = b?.value?.properties?.source?.[0][0] - const newUrl = `https://notion.so/signed/${encodeURIComponent(oldUrl)}?table=block&id=${b?.value?.id}` - b.value.properties.source[0][0] = newUrl + if (b?.value?.properties?.language?.[0][0] === 'Assembly') { + b.value.properties.language[0][0] = 'asm6502' } } + + // 如果是文件,或嵌入式PDF,需要重新加密签名 + if ( + (b?.value?.type === 'file' || + b?.value?.type === 'pdf' || + b?.value?.type === 'video' || + b?.value?.type === 'audio') && + b?.value?.properties?.source?.[0][0] && + b?.value?.properties?.source?.[0][0].indexOf('amazonaws.com') > 0 + ) { + const oldUrl = b?.value?.properties?.source?.[0][0] + const newUrl = `https://notion.so/signed/${encodeURIComponent(oldUrl)}?table=block&id=${b?.value?.id}` + b.value.properties.source[0][0] = newUrl + } +} // 去掉不用的字段 if (id === BLOG.NOTION_PAGE_ID) { @@ -162,6 +160,11 @@ function filterPostBlocks(id, blockMap, slice) { * @returns */ export const fetchInBatches = async (ids, batchSize = 100) => { + // 如果 ids 不是数组,则将其转换为数组 + if (!Array.isArray(ids)) { + ids = [ids] + } + const authToken = BLOG.NOTION_ACCESS_TOKEN || null const api = new NotionAPI({ authToken, @@ -171,7 +174,7 @@ export const fetchInBatches = async (ids, batchSize = 100) => { let fetchedBlocks = {} for (let i = 0; i < ids.length; i += batchSize) { const batch = ids.slice(i, i + batchSize) - console.log('[API-->>请求] Fetching missing blocks', ids.length) + console.log('[API-->>请求] Fetching missing blocks', batch, ids.length) const start = new Date().getTime() const pageChunk = await api.getBlocks(batch) const end = new Date().getTime() diff --git a/lib/notion/mapImage.js b/lib/notion/mapImage.js index 491632bc..5bb1e9b1 100644 --- a/lib/notion/mapImage.js +++ b/lib/notion/mapImage.js @@ -110,6 +110,8 @@ const compressImage = (image, width, quality = 50, fmt = 'webp') => { return image } + if (image.includes(".svg")) return image + if (!width || width === 0) { width = siteConfig('IMAGE_COMPRESS_WIDTH') } diff --git a/lib/password.js b/lib/password.js new file mode 100644 index 00000000..67b341a6 --- /dev/null +++ b/lib/password.js @@ -0,0 +1,37 @@ +import { isBrowser } from './utils' + +/** + * 获取默认密码 + * 用户可以通过url中拼接参数,输入密码 + * 输入过一次的密码会被存储在浏览器中,便于下一次免密访问 + * 返回的是一组历史密码,便于客户端多次尝试 + */ +export const getPasswordQuery = path => { + // 使用 URL 对象解析 URL + const url = new URL(path, isBrowser ? window.location.origin : '') + + // 获取查询参数 + const queryParams = Object.fromEntries(url.searchParams.entries()) + + // 请求中带着密码 + if (queryParams.password) { + // 将已输入密码作为默认密码存放在 localStorage,便于下次读取并自动尝试 + localStorage.setItem('password_default', queryParams.password) + } + + // 获取路径部分 + const cleanedPath = url.pathname + + // 从 localStorage 中获取相关密码 + const storedPassword = localStorage.getItem('password_' + cleanedPath) + const defaultPassword = localStorage.getItem('password_default') + + // 将所有密码存储在一个数组中,并过滤掉无效值 + const passwords = [ + queryParams.password, + storedPassword, + defaultPassword + ].filter(Boolean) + + return passwords +} diff --git a/lib/plugins/algolia.js b/lib/plugins/algolia.js index fe2f9a83..d161bf58 100644 --- a/lib/plugins/algolia.js +++ b/lib/plugins/algolia.js @@ -6,7 +6,7 @@ import algoliasearch from 'algoliasearch' * 生成全文索引 * @param {*} allPages */ -const generateAlgoliaSearch = async({ allPages, force = false }) => { +const generateAlgoliaSearch = async ({ allPages, force = false }) => { allPages?.forEach(p => { // 判断这篇文章是否需要重新创建索引 if (p && !p.password) { @@ -19,7 +19,7 @@ const generateAlgoliaSearch = async({ allPages, force = false }) => { * 上传数据 * 根据上次修改文章日期和上次更新索引数据判断是否需要更新algolia索引 */ -const uploadDataToAlgolia = async(post) => { +const uploadDataToAlgolia = async post => { // Connect and authenticate with your Algolia app const client = algoliasearch(BLOG.ALGOLIA_APP_ID, BLOG.ALGOLIA_ADMIN_APP_KEY) @@ -61,14 +61,18 @@ const uploadDataToAlgolia = async(post) => { summary: post.summary, lastEditedDate: post.lastEditedDate, // 更新文章时间 lastIndexDate: new Date(), // 更新索引时间 - content: truncate(getPageContentText(post, post.blockMap), 9000) // 索引9000个字节,因为api限制总请求内容上限1万个字节 + content: truncate(getPageContentText(post, post.blockMap), 8192) // 索引8192个字符,API限制总请求内容上限1万个字节 } // console.log('更新Algolia索引', record) - index.saveObject(record).wait().then(r => { - console.log('Algolia索引更新', r) - }).catch(err => { - console.log('Algolia异常', err) - }) + index + .saveObject(record) + .wait() + .then(r => { + console.log('Algolia索引更新', r) + }) + .catch(err => { + console.log('Algolia异常', err) + }) } } diff --git a/lib/utils/index.js b/lib/utils/index.js index b8d78f30..27a6494f 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -47,7 +47,12 @@ export const memorize = Component => { return memo(MemoizedComponent) } -// 转换外链 +/** + * 诸如 article/https://test.com 等被错误拼接前缀的slug进行处理 + * 转换为 https://test.com + * @param {*} str + * @returns + */ export function sliceUrlFromHttp(str) { // 检查字符串是否包含http if (str?.includes('http:') || str?.includes('https:')) { @@ -81,12 +86,25 @@ export function convertUrlStartWithOneSlash(str) { return str } +/** + * 是否是一个相对或绝对路径的ur类 + * @param {*} str + * @returns + */ +export function isUrl(str) { + if (!str) { + return false + } + + return str?.indexOf('/') === 0 || checkStartWithHttp(str) +} + // 检查是否外链 -export function checkContainHttp(str) { +export function checkStartWithHttp(str) { // 检查字符串是否包含http - if (str?.includes('http:') || str?.includes('https:')) { + if (str?.indexOf('http:') === 0 || str?.indexOf('https:') === 0) { // 如果包含,找到http的位置 - return str?.indexOf('http') > -1 + return true } else { // 不包含 return false @@ -354,3 +372,19 @@ export const scanAndConvertToLinks = node => { } } } + +/** + * 获取url最后一个斜杆后面的内容 + * @param {*} url + * @returns + */ +export function getLastSegmentFromUrl(url) { + if (!url) { + return '' + } + // 去掉 URL 中的查询参数部分 + const trimmedUrl = url.split('?')[0] + // 获取最后一个斜杠后面的内容 + const segments = trimmedUrl.split('/') + return segments[segments.length - 1] +} diff --git a/lib/utils/pageId.js b/lib/utils/pageId.js index e3c648e1..c963df28 100644 --- a/lib/utils/pageId.js +++ b/lib/utils/pageId.js @@ -30,4 +30,18 @@ function extractLangId(str) { } } -module.exports = { extractLangPrefix, extractLangId } +/** + * 列表中用过来区分page只需要端的id足够 + */ + +function getShortId(uuid) { + if (!uuid || uuid.indexOf('-') < 0) { + return uuid + } + // 找到第一个 '-' 的位置 + const index = uuid.indexOf('-') + // 截取从开始到第一个 '-' 之前的部分 + return uuid.substring(0, index) +} + +module.exports = { extractLangPrefix, extractLangId, getShortId } diff --git a/lib/utils/post.js b/lib/utils/post.js index 87852eda..e6402a02 100644 --- a/lib/utils/post.js +++ b/lib/utils/post.js @@ -1,7 +1,7 @@ /** * 文章相关工具 */ -import { checkContainHttp } from '.' +import { checkStartWithHttp } from '.' /** * 获取文章的关联推荐文章列表,目前根据标签关联性筛选 @@ -50,7 +50,7 @@ export function checkSlugHasNoSlash(row) { } return ( (slug.match(/\//g) || []).length === 0 && - !checkContainHttp(slug) && + !checkStartWithHttp(slug) && row.type.indexOf('Menu') < 0 ) } @@ -67,7 +67,7 @@ export function checkSlugHasOneSlash(row) { } return ( (slug.match(/\//g) || []).length === 1 && - !checkContainHttp(slug) && + !checkStartWithHttp(slug) && row.type.indexOf('Menu') < 0 ) } @@ -85,6 +85,6 @@ export function checkSlugHasMorThanTwoSlash(row) { return ( (slug.match(/\//g) || []).length >= 2 && row.type.indexOf('Menu') < 0 && - !checkContainHttp(slug) + !checkStartWithHttp(slug) ) } diff --git a/next.config.js b/next.config.js index 20a1c311..5e5937cd 100644 --- a/next.config.js +++ b/next.config.js @@ -32,6 +32,16 @@ const locales = (function () { return langs })() +// 编译前执行 +// const preBuild = (function () { +// // 删除 public/sitemap.xml 文件 ; 否则会和/pages/sitemap.xml.js 冲突。 +// const sitemapPath = path.resolve(__dirname, 'public', 'sitemap.xml') +// if (fs.existsSync(sitemapPath)) { +// fs.unlinkSync(sitemapPath) +// console.log('Deleted existing sitemap.xml from public directory') +// } +// })() + /** * 扫描指定目录下的文件夹名,用于获取所有主题 * @param {*} directory @@ -53,7 +63,20 @@ function scanSubdirectories(directory) { return subdirectories } +/** + * @type {import('next').NextConfig} + */ + const nextConfig = { + output: process.env.EXPORT ? 'export' : undefined, + // 多语言, 在export时禁用 + i18n: process.env.EXPORT + ? undefined + : { + defaultLocale: BLOG.LANG.slice(0, 2), + // 支持的所有多语言,按需填写即可 + locales + }, images: { // 图片压缩 formats: ['image/avif', 'image/webp'], @@ -71,90 +94,87 @@ const nextConfig = { }, // 默认将feed重定向至 /public/rss/feed.xml - async redirects() { - return [ - { - source: '/feed', - destination: '/rss/feed.xml', - permanent: true - } - ] - }, - // 多语言, 在export时禁用 - i18n: - process.env.npm_lifecycle_event === 'export' - ? undefined - : { - defaultLocale: BLOG.LANG.slice(0, 2), - // 支持的所有多语言,按需填写即可 - locales - }, - // 重写url - async rewrites() { - // 处理多语言重定向 - const langsRewrites = [] - if (BLOG.NOTION_PAGE_ID.indexOf(',') > 0) { - const siteIds = BLOG.NOTION_PAGE_ID.split(',') - const langs = [] - for (let index = 0; index < siteIds.length; index++) { - const siteId = siteIds[index] - const prefix = extractLangPrefix(siteId) - // 如果包含前缀 例如 zh , en 等 - if (prefix) { - langs.push(prefix) - } - console.log('[Locales]', siteId) - } - - // 映射多语言 - // 示例: source: '/:locale(zh|en)/:path*' ; :locale() 会将语言放入重写后的 `?locale=` 中。 - langsRewrites.push( - { - source: `/:locale(${langs.join('|')})/:path*`, - destination: '/:path*' - }, - // 匹配没有路径的情况,例如 [domain]/zh 或 [domain]/en - { - source: `/:locale(${langs.join('|')})`, - destination: '/' - }, - // 匹配没有路径的情况,例如 [domain]/zh/ 或 [domain]/en/ - { - source: `/:locale(${langs.join('|')})/`, - destination: '/' - } - ) - } - - return [ - ...langsRewrites, - // 伪静态重写 - { - source: '/:path*.html', - destination: '/:path*' - } - ] - }, - async headers() { - return [ - { - source: '/:path*{/}?', - headers: [ - { key: 'Access-Control-Allow-Credentials', value: 'true' }, - { key: 'Access-Control-Allow-Origin', value: '*' }, + redirects: process.env.EXPORT + ? undefined + : async () => { + return [ { - key: 'Access-Control-Allow-Methods', - value: 'GET,OPTIONS,PATCH,DELETE,POST,PUT' - }, - { - key: 'Access-Control-Allow-Headers', - value: - 'X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version' + source: '/feed', + destination: '/rss/feed.xml', + permanent: true } ] - } - ] - }, + }, + // 重写url + rewrites: process.env.EXPORT + ? undefined + : async () => { + // 处理多语言重定向 + const langsRewrites = [] + if (BLOG.NOTION_PAGE_ID.indexOf(',') > 0) { + const siteIds = BLOG.NOTION_PAGE_ID.split(',') + const langs = [] + for (let index = 0; index < siteIds.length; index++) { + const siteId = siteIds[index] + const prefix = extractLangPrefix(siteId) + // 如果包含前缀 例如 zh , en 等 + if (prefix) { + langs.push(prefix) + } + console.log('[Locales]', siteId) + } + + // 映射多语言 + // 示例: source: '/:locale(zh|en)/:path*' ; :locale() 会将语言放入重写后的 `?locale=` 中。 + langsRewrites.push( + { + source: `/:locale(${langs.join('|')})/:path*`, + destination: '/:path*' + }, + // 匹配没有路径的情况,例如 [domain]/zh 或 [domain]/en + { + source: `/:locale(${langs.join('|')})`, + destination: '/' + }, + // 匹配没有路径的情况,例如 [domain]/zh/ 或 [domain]/en/ + { + source: `/:locale(${langs.join('|')})/`, + destination: '/' + } + ) + } + + return [ + ...langsRewrites, + // 伪静态重写 + { + source: '/:path*.html', + destination: '/:path*' + } + ] + }, + headers: process.env.EXPORT + ? undefined + : async () => { + return [ + { + source: '/:path*{/}?', + headers: [ + { key: 'Access-Control-Allow-Credentials', value: 'true' }, + { key: 'Access-Control-Allow-Origin', value: '*' }, + { + key: 'Access-Control-Allow-Methods', + value: 'GET,OPTIONS,PATCH,DELETE,POST,PUT' + }, + { + key: 'Access-Control-Allow-Headers', + value: + 'X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version' + } + ] + } + ] + }, webpack: (config, { dev, isServer }) => { // 动态主题:添加 resolve.alias 配置,将动态路径映射到实际路径 if (!isServer) { diff --git a/package.json b/package.json index 5e7761c7..55c67a2b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "notion-next", - "version": "4.5.3", + "version": "4.6.1", "homepage": "https://github.com/tangly1024/NotionNext.git", "license": "MIT", "repository": { @@ -17,8 +17,9 @@ "build": "next build", "start": "next start", "post-build": "next-sitemap --config next-sitemap.config.js", - "export": "next build && next-sitemap --config next-sitemap.config.js && next export", - "bundle-report": "cross-env ANALYZE=true yarn build" + "export": "cross-env EXPORT=true next build && next-sitemap --config next-sitemap.config.js", + "bundle-report": "cross-env ANALYZE=true next build", + "build-all-in-dev": "cross-env VERCEL_ENV=production next build" }, "dependencies": { "@headlessui/react": "^1.7.15", @@ -29,7 +30,7 @@ "js-md5": "^0.7.3", "lodash.throttle": "^4.1.1", "memory-cache": "^0.2.0", - "next": "13.3.1", + "next": "14.2.4", "notion-client": "6.15.6", "notion-utils": "6.15.6", "react": "^18.2.0", diff --git a/pages/[prefix]/[slug]/[...suffix].js b/pages/[prefix]/[slug]/[...suffix].js index 2050e81d..d9567c4c 100644 --- a/pages/[prefix]/[slug]/[...suffix].js +++ b/pages/[prefix]/[slug]/[...suffix].js @@ -30,17 +30,17 @@ export async function getStaticPaths() { const from = 'slug-paths' const { allPages } = await getGlobalData({ from }) - + const paths = allPages + ?.filter(row => checkSlugHasMorThanTwoSlash(row)) + .map(row => ({ + params: { + prefix: row.slug.split('/')[0], + slug: row.slug.split('/')[1], + suffix: row.slug.split('/').slice(2) + } + })) return { - paths: allPages - ?.filter(row => checkSlugHasMorThanTwoSlash(row)) - .map(row => ({ - params: { - prefix: row.slug.split('/')[0], - slug: row.slug.split('/')[1], - suffix: row.slug.split('/').slice(1) - } - })), + paths: paths, fallback: true } } @@ -83,16 +83,18 @@ export async function getStaticProps({ props.post = null return { props, - revalidate: siteConfig( - 'REVALIDATE_SECOND', - BLOG.NEXT_REVALIDATE_SECOND, - props.NOTION_CONFIG - ) + revalidate: process.env.EXPORT + ? undefined + : siteConfig( + 'NEXT_REVALIDATE_SECOND', + BLOG.NEXT_REVALIDATE_SECOND, + props.NOTION_CONFIG + ) } } // 文章内容加载 - if (!props?.posts?.blockMap) { + if (!props?.post?.blockMap) { props.post.blockMap = await getPostBlocks(props.post.id, from) } // 生成全文索引 && JSON.parse(BLOG.ALGOLIA_RECREATE_DATA) @@ -122,11 +124,13 @@ export async function getStaticProps({ delete props.allPages return { props, - revalidate: siteConfig( - 'NEXT_REVALIDATE_SECOND', - BLOG.NEXT_REVALIDATE_SECOND, - props.NOTION_CONFIG - ) + revalidate: process.env.EXPORT + ? undefined + : siteConfig( + 'NEXT_REVALIDATE_SECOND', + BLOG.NEXT_REVALIDATE_SECOND, + props.NOTION_CONFIG + ) } } diff --git a/pages/[prefix]/[slug]/index.js b/pages/[prefix]/[slug]/index.js index 2c844eba..6b50c206 100644 --- a/pages/[prefix]/[slug]/index.js +++ b/pages/[prefix]/[slug]/index.js @@ -72,16 +72,18 @@ export async function getStaticProps({ params: { prefix, slug }, locale }) { props.post = null return { props, - revalidate: siteConfig( - 'REVALIDATE_SECOND', - BLOG.NEXT_REVALIDATE_SECOND, - props.NOTION_CONFIG - ) + revalidate: process.env.EXPORT + ? undefined + : siteConfig( + 'NEXT_REVALIDATE_SECOND', + BLOG.NEXT_REVALIDATE_SECOND, + props.NOTION_CONFIG + ) } } // 文章内容加载 - if (!props?.posts?.blockMap) { + if (!props?.post?.blockMap) { props.post.blockMap = await getPostBlocks(props.post.id, from) } // 生成全文索引 && JSON.parse(BLOG.ALGOLIA_RECREATE_DATA) @@ -111,11 +113,13 @@ export async function getStaticProps({ params: { prefix, slug }, locale }) { delete props.allPages return { props, - revalidate: siteConfig( - 'NEXT_REVALIDATE_SECOND', - BLOG.NEXT_REVALIDATE_SECOND, - props.NOTION_CONFIG - ) + revalidate: process.env.EXPORT + ? undefined + : siteConfig( + 'NEXT_REVALIDATE_SECOND', + BLOG.NEXT_REVALIDATE_SECOND, + props.NOTION_CONFIG + ) } } diff --git a/pages/[prefix]/index.js b/pages/[prefix]/index.js index 469ca617..dc480fcc 100644 --- a/pages/[prefix]/index.js +++ b/pages/[prefix]/index.js @@ -1,7 +1,10 @@ import BLOG from '@/blog.config' +import useNotification from '@/components/Notification' import { siteConfig } from '@/lib/config' import { getGlobalData, getPost, getPostBlocks } from '@/lib/db/getSiteData' +import { useGlobal } from '@/lib/global' import { getPageTableOfContents } from '@/lib/notion/getPageTableOfContents' +import { getPasswordQuery } from '@/lib/password' import { uploadDataToAlgolia } from '@/lib/plugins/algolia' import { checkSlugHasNoSlash, getRecommendPost } from '@/lib/utils/post' import { getLayoutByTheme } from '@/themes/theme' @@ -19,9 +22,11 @@ import { useEffect, useState } from 'react' const Slug = props => { const { post } = props const router = useRouter() + const { locale } = useGlobal() // 文章锁🔐 const [lock, setLock] = useState(post?.password && post?.password !== '') + const { showNotification, Notification } = useNotification() /** * 验证文章密码 @@ -36,6 +41,7 @@ const Slug = props => { setLock(false) // 输入密码存入localStorage,下次自动提交 localStorage.setItem('password_' + router.asPath, passInput) + showNotification(locale.COMMON.ARTICLE_UNLOCK_TIPS) // 设置解锁成功提示显示 return true } return false @@ -56,10 +62,14 @@ const Slug = props => { } } - // 从localStorage中读取上次记录 自动提交密码 - const passInput = localStorage.getItem('password_' + router.asPath) - if (passInput) { - validPassword(passInput) + // 读取上次记录 自动提交密码 + const passInputs = getPasswordQuery(router.asPath) + if (passInputs.length > 0) { + for (const passInput of passInputs) { + if (validPassword(passInput)) { + break // 密码验证成功,停止尝试 + } + } } }, [post]) @@ -83,7 +93,12 @@ const Slug = props => { theme: siteConfig('THEME'), router: useRouter() }) - return + return ( + <> + + {post?.password && post?.password !== '' && !lock && } + + ) } export async function getStaticPaths() { @@ -109,17 +124,17 @@ export async function getStaticProps({ params: { prefix }, locale }) { let fullSlug = prefix const from = `slug-props-${fullSlug}` const props = await getGlobalData({ from, locale }) - if (siteConfig('PSEUDO_STATIC', BLOG.PSEUDO_STATIC, props.NOTION_CONFIG)) { + if (siteConfig('PSEUDO_STATIC', false, props.NOTION_CONFIG)) { if (!fullSlug.endsWith('.html')) { fullSlug += '.html' } } - + // 在列表内查找文章 props.post = props?.allPages?.find(p => { return ( p.type.indexOf('Menu') < 0 && - (p.slug === fullSlug || p.id === idToUuid(fullSlug)) + (p.slug === prefix || p.id === idToUuid(prefix)) ) }) @@ -136,16 +151,18 @@ export async function getStaticProps({ params: { prefix }, locale }) { props.post = null return { props, - revalidate: siteConfig( - 'REVALIDATE_SECOND', - BLOG.NEXT_REVALIDATE_SECOND, - props.NOTION_CONFIG - ) + revalidate: process.env.EXPORT + ? undefined + : siteConfig( + 'NEXT_REVALIDATE_SECOND', + BLOG.NEXT_REVALIDATE_SECOND, + props.NOTION_CONFIG + ) } } // 文章内容加载 - if (!props?.posts?.blockMap) { + if (!props?.post?.blockMap) { props.post.blockMap = await getPostBlocks(props.post.id, from) } @@ -176,11 +193,13 @@ export async function getStaticProps({ params: { prefix }, locale }) { delete props.allPages return { props, - revalidate: siteConfig( - 'NEXT_REVALIDATE_SECOND', - BLOG.NEXT_REVALIDATE_SECOND, - props.NOTION_CONFIG - ) + revalidate: process.env.EXPORT + ? undefined + : siteConfig( + 'NEXT_REVALIDATE_SECOND', + BLOG.NEXT_REVALIDATE_SECOND, + props.NOTION_CONFIG + ) } } diff --git a/pages/archive/index.js b/pages/archive/index.js index 9f0803c8..beecf79c 100644 --- a/pages/archive/index.js +++ b/pages/archive/index.js @@ -61,11 +61,13 @@ export async function getStaticProps({ locale }) { return { props, - revalidate: siteConfig( - 'NEXT_REVALIDATE_SECOND', - BLOG.NEXT_REVALIDATE_SECOND, - props.NOTION_CONFIG - ) + revalidate: process.env.EXPORT + ? undefined + : siteConfig( + 'NEXT_REVALIDATE_SECOND', + BLOG.NEXT_REVALIDATE_SECOND, + props.NOTION_CONFIG + ) } } diff --git a/pages/category/[category]/index.js b/pages/category/[category]/index.js index 879dc642..ea70b454 100644 --- a/pages/category/[category]/index.js +++ b/pages/category/[category]/index.js @@ -49,11 +49,13 @@ export async function getStaticProps({ params: { category }, locale }) { return { props, - revalidate: siteConfig( - 'NEXT_REVALIDATE_SECOND', - BLOG.NEXT_REVALIDATE_SECOND, - props.NOTION_CONFIG - ) + revalidate: process.env.EXPORT + ? undefined + : siteConfig( + 'NEXT_REVALIDATE_SECOND', + BLOG.NEXT_REVALIDATE_SECOND, + props.NOTION_CONFIG + ) } } diff --git a/pages/category/[category]/page/[page].js b/pages/category/[category]/page/[page].js index 39ce44df..120d762a 100644 --- a/pages/category/[category]/page/[page].js +++ b/pages/category/[category]/page/[page].js @@ -44,11 +44,13 @@ export async function getStaticProps({ params: { category, page } }) { return { props, - revalidate: siteConfig( - 'NEXT_REVALIDATE_SECOND', - BLOG.NEXT_REVALIDATE_SECOND, - props.NOTION_CONFIG - ) + revalidate: process.env.EXPORT + ? undefined + : siteConfig( + 'NEXT_REVALIDATE_SECOND', + BLOG.NEXT_REVALIDATE_SECOND, + props.NOTION_CONFIG + ) } } @@ -69,7 +71,7 @@ export async function getStaticPaths() { // 处理文章页数 const postCount = categoryPosts.length const totalPages = Math.ceil( - postCount / siteConfig('POSTS_PER_PAGE', 12, NOTION_CONFIG) + postCount / siteConfig('POSTS_PER_PAGE', null, NOTION_CONFIG) ) if (totalPages > 1) { for (let i = 1; i <= totalPages; i++) { diff --git a/pages/category/index.js b/pages/category/index.js index 423383bf..62a3cc9f 100644 --- a/pages/category/index.js +++ b/pages/category/index.js @@ -24,10 +24,12 @@ export async function getStaticProps({ locale }) { delete props.allPages return { props, - revalidate: siteConfig( - 'NEXT_REVALIDATE_SECOND', - BLOG.NEXT_REVALIDATE_SECOND, - props.NOTION_CONFIG - ) + revalidate: process.env.EXPORT + ? undefined + : siteConfig( + 'NEXT_REVALIDATE_SECOND', + BLOG.NEXT_REVALIDATE_SECOND, + props.NOTION_CONFIG + ) } } diff --git a/pages/index.js b/pages/index.js index b925bc0e..43f5b114 100644 --- a/pages/index.js +++ b/pages/index.js @@ -69,11 +69,13 @@ export async function getStaticProps(req) { return { props, - revalidate: siteConfig( - 'NEXT_REVALIDATE_SECOND', - BLOG.NEXT_REVALIDATE_SECOND, - props.NOTION_CONFIG - ) + revalidate: process.env.EXPORT + ? undefined + : siteConfig( + 'NEXT_REVALIDATE_SECOND', + BLOG.NEXT_REVALIDATE_SECOND, + props.NOTION_CONFIG + ) } } diff --git a/pages/page/[page].js b/pages/page/[page].js index 236c93ef..bd1dc7e2 100644 --- a/pages/page/[page].js +++ b/pages/page/[page].js @@ -23,7 +23,7 @@ export async function getStaticPaths({ locale }) { const from = 'page-paths' const { postCount, NOTION_CONFIG } = await getGlobalData({ from, locale }) const totalPages = Math.ceil( - postCount / siteConfig('POSTS_PER_PAGE', 12, NOTION_CONFIG) + postCount / siteConfig('POSTS_PER_PAGE', null, NOTION_CONFIG) ) return { // remove first page, we 're not gonna handle that. @@ -69,11 +69,13 @@ export async function getStaticProps({ params: { page } }) { delete props.allPages return { props, - revalidate: siteConfig( - 'NEXT_REVALIDATE_SECOND', - BLOG.NEXT_REVALIDATE_SECOND, - props.NOTION_CONFIG - ) + revalidate: process.env.EXPORT + ? undefined + : siteConfig( + 'NEXT_REVALIDATE_SECOND', + BLOG.NEXT_REVALIDATE_SECOND, + props.NOTION_CONFIG + ) } } diff --git a/pages/search/[keyword]/index.js b/pages/search/[keyword]/index.js index 6d753578..9b491b00 100644 --- a/pages/search/[keyword]/index.js +++ b/pages/search/[keyword]/index.js @@ -47,11 +47,13 @@ export async function getStaticProps({ params: { keyword }, locale }) { props.keyword = keyword return { props, - revalidate: siteConfig( - 'NEXT_REVALIDATE_SECOND', - BLOG.NEXT_REVALIDATE_SECOND, - props.NOTION_CONFIG - ) + revalidate: process.env.EXPORT + ? undefined + : siteConfig( + 'NEXT_REVALIDATE_SECOND', + BLOG.NEXT_REVALIDATE_SECOND, + props.NOTION_CONFIG + ) } } diff --git a/pages/search/[keyword]/page/[page].js b/pages/search/[keyword]/page/[page].js index 6e497b7c..bebaa9fc 100644 --- a/pages/search/[keyword]/page/[page].js +++ b/pages/search/[keyword]/page/[page].js @@ -45,11 +45,13 @@ export async function getStaticProps({ params: { keyword, page }, locale }) { delete props.allPages return { props, - revalidate: siteConfig( - 'NEXT_REVALIDATE_SECOND', - BLOG.NEXT_REVALIDATE_SECOND, - props.NOTION_CONFIG - ) + revalidate: process.env.EXPORT + ? undefined + : siteConfig( + 'NEXT_REVALIDATE_SECOND', + BLOG.NEXT_REVALIDATE_SECOND, + props.NOTION_CONFIG + ) } } diff --git a/pages/search/index.js b/pages/search/index.js index 87d2e16d..6226a400 100644 --- a/pages/search/index.js +++ b/pages/search/index.js @@ -54,11 +54,13 @@ export async function getStaticProps({ locale }) { ) return { props, - revalidate: siteConfig( - 'NEXT_REVALIDATE_SECOND', - BLOG.NEXT_REVALIDATE_SECOND, - props.NOTION_CONFIG - ) + revalidate: process.env.EXPORT + ? undefined + : siteConfig( + 'NEXT_REVALIDATE_SECOND', + BLOG.NEXT_REVALIDATE_SECOND, + props.NOTION_CONFIG + ) } } diff --git a/pages/signin.js b/pages/signin.js index b3a2765f..fbf618a4 100644 --- a/pages/signin.js +++ b/pages/signin.js @@ -27,11 +27,13 @@ export async function getStaticProps(req) { delete props.allPages return { props, - revalidate: siteConfig( - 'NEXT_REVALIDATE_SECOND', - BLOG.NEXT_REVALIDATE_SECOND, - props.NOTION_CONFIG - ) + revalidate: process.env.EXPORT + ? undefined + : siteConfig( + 'NEXT_REVALIDATE_SECOND', + BLOG.NEXT_REVALIDATE_SECOND, + props.NOTION_CONFIG + ) } } diff --git a/pages/signup.js b/pages/signup.js index d0852c4e..bbe38129 100644 --- a/pages/signup.js +++ b/pages/signup.js @@ -27,11 +27,13 @@ export async function getStaticProps(req) { delete props.allPages return { props, - revalidate: siteConfig( - 'NEXT_REVALIDATE_SECOND', - BLOG.NEXT_REVALIDATE_SECOND, - props.NOTION_CONFIG - ) + revalidate: process.env.EXPORT + ? undefined + : siteConfig( + 'NEXT_REVALIDATE_SECOND', + BLOG.NEXT_REVALIDATE_SECOND, + props.NOTION_CONFIG + ) } } diff --git a/pages/sitemap.xml.js b/pages/sitemap.xml.js index eb7f7bd8..2f6c20fe 100644 --- a/pages/sitemap.xml.js +++ b/pages/sitemap.xml.js @@ -27,7 +27,6 @@ export const getServerSideProps = async ctx => { 'Cache-Control', 'public, max-age=3600, stale-while-revalidate=59' ) - console.log('fff', fields) return getServerSideSitemap(ctx, fields) } @@ -55,7 +54,7 @@ function generateLocalesSitemap(link, allPages, locale) { priority: '0.7' }, { - loc: `${link}${locale}/feed`, + loc: `${link}${locale}/rss/feed.xml`, lastmod: new Date().toISOString().split('T')[0], changefreq: 'daily', priority: '0.7' diff --git a/pages/tag/[tag]/index.js b/pages/tag/[tag]/index.js index 2860f91c..79458257 100644 --- a/pages/tag/[tag]/index.js +++ b/pages/tag/[tag]/index.js @@ -45,11 +45,13 @@ export async function getStaticProps({ params: { tag }, locale }) { delete props.allPages return { props, - revalidate: siteConfig( - 'NEXT_REVALIDATE_SECOND', - BLOG.NEXT_REVALIDATE_SECOND, - props.NOTION_CONFIG - ) + revalidate: process.env.EXPORT + ? undefined + : siteConfig( + 'NEXT_REVALIDATE_SECOND', + BLOG.NEXT_REVALIDATE_SECOND, + props.NOTION_CONFIG + ) } } diff --git a/pages/tag/[tag]/page/[page].js b/pages/tag/[tag]/page/[page].js index f8e70c3c..da93f16f 100644 --- a/pages/tag/[tag]/page/[page].js +++ b/pages/tag/[tag]/page/[page].js @@ -34,11 +34,13 @@ export async function getStaticProps({ params: { tag, page }, locale }) { delete props.allPages return { props, - revalidate: siteConfig( - 'NEXT_REVALIDATE_SECOND', - BLOG.NEXT_REVALIDATE_SECOND, - props.NOTION_CONFIG - ) + revalidate: process.env.EXPORT + ? undefined + : siteConfig( + 'NEXT_REVALIDATE_SECOND', + BLOG.NEXT_REVALIDATE_SECOND, + props.NOTION_CONFIG + ) } } @@ -54,7 +56,7 @@ export async function getStaticPaths() { // 处理文章页数 const postCount = tagPosts.length const totalPages = Math.ceil( - postCount / siteConfig('POSTS_PER_PAGE', 12, NOTION_CONFIG) + postCount / siteConfig('POSTS_PER_PAGE', null, NOTION_CONFIG) ) if (totalPages > 1) { for (let i = 1; i <= totalPages; i++) { diff --git a/pages/tag/index.js b/pages/tag/index.js index f9cddea4..5433a033 100644 --- a/pages/tag/index.js +++ b/pages/tag/index.js @@ -26,11 +26,13 @@ export async function getStaticProps(req) { delete props.allPages return { props, - revalidate: siteConfig( - 'NEXT_REVALIDATE_SECOND', - BLOG.NEXT_REVALIDATE_SECOND, - props.NOTION_CONFIG - ) + revalidate: process.env.EXPORT + ? undefined + : siteConfig( + 'NEXT_REVALIDATE_SECOND', + BLOG.NEXT_REVALIDATE_SECOND, + props.NOTION_CONFIG + ) } } diff --git a/public/css/aos.css b/public/css/aos.css new file mode 100644 index 00000000..86710a57 --- /dev/null +++ b/public/css/aos.css @@ -0,0 +1,916 @@ +[data-aos][data-aos][data-aos-duration='50'], +body[data-aos-duration='50'] [data-aos] { + transition-duration: 50ms; +} +[data-aos][data-aos][data-aos-delay='50'], +body[data-aos-delay='50'] [data-aos] { + transition-delay: 0; +} +[data-aos][data-aos][data-aos-delay='50'].aos-animate, +body[data-aos-delay='50'] [data-aos].aos-animate { + transition-delay: 50ms; +} +[data-aos][data-aos][data-aos-duration='100'], +body[data-aos-duration='100'] [data-aos] { + transition-duration: 0.1s; +} +[data-aos][data-aos][data-aos-delay='100'], +body[data-aos-delay='100'] [data-aos] { + transition-delay: 0; +} +[data-aos][data-aos][data-aos-delay='100'].aos-animate, +body[data-aos-delay='100'] [data-aos].aos-animate { + transition-delay: 0.1s; +} +[data-aos][data-aos][data-aos-duration='150'], +body[data-aos-duration='150'] [data-aos] { + transition-duration: 0.15s; +} +[data-aos][data-aos][data-aos-delay='150'], +body[data-aos-delay='150'] [data-aos] { + transition-delay: 0; +} +[data-aos][data-aos][data-aos-delay='150'].aos-animate, +body[data-aos-delay='150'] [data-aos].aos-animate { + transition-delay: 0.15s; +} +[data-aos][data-aos][data-aos-duration='200'], +body[data-aos-duration='200'] [data-aos] { + transition-duration: 0.2s; +} +[data-aos][data-aos][data-aos-delay='200'], +body[data-aos-delay='200'] [data-aos] { + transition-delay: 0; +} +[data-aos][data-aos][data-aos-delay='200'].aos-animate, +body[data-aos-delay='200'] [data-aos].aos-animate { + transition-delay: 0.2s; +} +[data-aos][data-aos][data-aos-duration='250'], +body[data-aos-duration='250'] [data-aos] { + transition-duration: 0.25s; +} +[data-aos][data-aos][data-aos-delay='250'], +body[data-aos-delay='250'] [data-aos] { + transition-delay: 0; +} +[data-aos][data-aos][data-aos-delay='250'].aos-animate, +body[data-aos-delay='250'] [data-aos].aos-animate { + transition-delay: 0.25s; +} +[data-aos][data-aos][data-aos-duration='300'], +body[data-aos-duration='300'] [data-aos] { + transition-duration: 0.3s; +} +[data-aos][data-aos][data-aos-delay='300'], +body[data-aos-delay='300'] [data-aos] { + transition-delay: 0; +} +[data-aos][data-aos][data-aos-delay='300'].aos-animate, +body[data-aos-delay='300'] [data-aos].aos-animate { + transition-delay: 0.3s; +} +[data-aos][data-aos][data-aos-duration='350'], +body[data-aos-duration='350'] [data-aos] { + transition-duration: 0.35s; +} +[data-aos][data-aos][data-aos-delay='350'], +body[data-aos-delay='350'] [data-aos] { + transition-delay: 0; +} +[data-aos][data-aos][data-aos-delay='350'].aos-animate, +body[data-aos-delay='350'] [data-aos].aos-animate { + transition-delay: 0.35s; +} +[data-aos][data-aos][data-aos-duration='400'], +body[data-aos-duration='400'] [data-aos] { + transition-duration: 0.4s; +} +[data-aos][data-aos][data-aos-delay='400'], +body[data-aos-delay='400'] [data-aos] { + transition-delay: 0; +} +[data-aos][data-aos][data-aos-delay='400'].aos-animate, +body[data-aos-delay='400'] [data-aos].aos-animate { + transition-delay: 0.4s; +} +[data-aos][data-aos][data-aos-duration='450'], +body[data-aos-duration='450'] [data-aos] { + transition-duration: 0.45s; +} +[data-aos][data-aos][data-aos-delay='450'], +body[data-aos-delay='450'] [data-aos] { + transition-delay: 0; +} +[data-aos][data-aos][data-aos-delay='450'].aos-animate, +body[data-aos-delay='450'] [data-aos].aos-animate { + transition-delay: 0.45s; +} +[data-aos][data-aos][data-aos-duration='500'], +body[data-aos-duration='500'] [data-aos] { + transition-duration: 0.5s; +} +[data-aos][data-aos][data-aos-delay='500'], +body[data-aos-delay='500'] [data-aos] { + transition-delay: 0; +} +[data-aos][data-aos][data-aos-delay='500'].aos-animate, +body[data-aos-delay='500'] [data-aos].aos-animate { + transition-delay: 0.5s; +} +[data-aos][data-aos][data-aos-duration='550'], +body[data-aos-duration='550'] [data-aos] { + transition-duration: 0.55s; +} +[data-aos][data-aos][data-aos-delay='550'], +body[data-aos-delay='550'] [data-aos] { + transition-delay: 0; +} +[data-aos][data-aos][data-aos-delay='550'].aos-animate, +body[data-aos-delay='550'] [data-aos].aos-animate { + transition-delay: 0.55s; +} +[data-aos][data-aos][data-aos-duration='600'], +body[data-aos-duration='600'] [data-aos] { + transition-duration: 0.6s; +} +[data-aos][data-aos][data-aos-delay='600'], +body[data-aos-delay='600'] [data-aos] { + transition-delay: 0; +} +[data-aos][data-aos][data-aos-delay='600'].aos-animate, +body[data-aos-delay='600'] [data-aos].aos-animate { + transition-delay: 0.6s; +} +[data-aos][data-aos][data-aos-duration='650'], +body[data-aos-duration='650'] [data-aos] { + transition-duration: 0.65s; +} +[data-aos][data-aos][data-aos-delay='650'], +body[data-aos-delay='650'] [data-aos] { + transition-delay: 0; +} +[data-aos][data-aos][data-aos-delay='650'].aos-animate, +body[data-aos-delay='650'] [data-aos].aos-animate { + transition-delay: 0.65s; +} +[data-aos][data-aos][data-aos-duration='700'], +body[data-aos-duration='700'] [data-aos] { + transition-duration: 0.7s; +} +[data-aos][data-aos][data-aos-delay='700'], +body[data-aos-delay='700'] [data-aos] { + transition-delay: 0; +} +[data-aos][data-aos][data-aos-delay='700'].aos-animate, +body[data-aos-delay='700'] [data-aos].aos-animate { + transition-delay: 0.7s; +} +[data-aos][data-aos][data-aos-duration='750'], +body[data-aos-duration='750'] [data-aos] { + transition-duration: 0.75s; +} +[data-aos][data-aos][data-aos-delay='750'], +body[data-aos-delay='750'] [data-aos] { + transition-delay: 0; +} +[data-aos][data-aos][data-aos-delay='750'].aos-animate, +body[data-aos-delay='750'] [data-aos].aos-animate { + transition-delay: 0.75s; +} +[data-aos][data-aos][data-aos-duration='800'], +body[data-aos-duration='800'] [data-aos] { + transition-duration: 0.8s; +} +[data-aos][data-aos][data-aos-delay='800'], +body[data-aos-delay='800'] [data-aos] { + transition-delay: 0; +} +[data-aos][data-aos][data-aos-delay='800'].aos-animate, +body[data-aos-delay='800'] [data-aos].aos-animate { + transition-delay: 0.8s; +} +[data-aos][data-aos][data-aos-duration='850'], +body[data-aos-duration='850'] [data-aos] { + transition-duration: 0.85s; +} +[data-aos][data-aos][data-aos-delay='850'], +body[data-aos-delay='850'] [data-aos] { + transition-delay: 0; +} +[data-aos][data-aos][data-aos-delay='850'].aos-animate, +body[data-aos-delay='850'] [data-aos].aos-animate { + transition-delay: 0.85s; +} +[data-aos][data-aos][data-aos-duration='900'], +body[data-aos-duration='900'] [data-aos] { + transition-duration: 0.9s; +} +[data-aos][data-aos][data-aos-delay='900'], +body[data-aos-delay='900'] [data-aos] { + transition-delay: 0; +} +[data-aos][data-aos][data-aos-delay='900'].aos-animate, +body[data-aos-delay='900'] [data-aos].aos-animate { + transition-delay: 0.9s; +} +[data-aos][data-aos][data-aos-duration='950'], +body[data-aos-duration='950'] [data-aos] { + transition-duration: 0.95s; +} +[data-aos][data-aos][data-aos-delay='950'], +body[data-aos-delay='950'] [data-aos] { + transition-delay: 0; +} +[data-aos][data-aos][data-aos-delay='950'].aos-animate, +body[data-aos-delay='950'] [data-aos].aos-animate { + transition-delay: 0.95s; +} +[data-aos][data-aos][data-aos-duration='1000'], +body[data-aos-duration='1000'] [data-aos] { + transition-duration: 1s; +} +[data-aos][data-aos][data-aos-delay='1000'], +body[data-aos-delay='1000'] [data-aos] { + transition-delay: 0; +} +[data-aos][data-aos][data-aos-delay='1000'].aos-animate, +body[data-aos-delay='1000'] [data-aos].aos-animate { + transition-delay: 1s; +} +[data-aos][data-aos][data-aos-duration='1050'], +body[data-aos-duration='1050'] [data-aos] { + transition-duration: 1.05s; +} +[data-aos][data-aos][data-aos-delay='1050'], +body[data-aos-delay='1050'] [data-aos] { + transition-delay: 0; +} +[data-aos][data-aos][data-aos-delay='1050'].aos-animate, +body[data-aos-delay='1050'] [data-aos].aos-animate { + transition-delay: 1.05s; +} +[data-aos][data-aos][data-aos-duration='1100'], +body[data-aos-duration='1100'] [data-aos] { + transition-duration: 1.1s; +} +[data-aos][data-aos][data-aos-delay='1100'], +body[data-aos-delay='1100'] [data-aos] { + transition-delay: 0; +} +[data-aos][data-aos][data-aos-delay='1100'].aos-animate, +body[data-aos-delay='1100'] [data-aos].aos-animate { + transition-delay: 1.1s; +} +[data-aos][data-aos][data-aos-duration='1150'], +body[data-aos-duration='1150'] [data-aos] { + transition-duration: 1.15s; +} +[data-aos][data-aos][data-aos-delay='1150'], +body[data-aos-delay='1150'] [data-aos] { + transition-delay: 0; +} +[data-aos][data-aos][data-aos-delay='1150'].aos-animate, +body[data-aos-delay='1150'] [data-aos].aos-animate { + transition-delay: 1.15s; +} +[data-aos][data-aos][data-aos-duration='1200'], +body[data-aos-duration='1200'] [data-aos] { + transition-duration: 1.2s; +} +[data-aos][data-aos][data-aos-delay='1200'], +body[data-aos-delay='1200'] [data-aos] { + transition-delay: 0; +} +[data-aos][data-aos][data-aos-delay='1200'].aos-animate, +body[data-aos-delay='1200'] [data-aos].aos-animate { + transition-delay: 1.2s; +} +[data-aos][data-aos][data-aos-duration='1250'], +body[data-aos-duration='1250'] [data-aos] { + transition-duration: 1.25s; +} +[data-aos][data-aos][data-aos-delay='1250'], +body[data-aos-delay='1250'] [data-aos] { + transition-delay: 0; +} +[data-aos][data-aos][data-aos-delay='1250'].aos-animate, +body[data-aos-delay='1250'] [data-aos].aos-animate { + transition-delay: 1.25s; +} +[data-aos][data-aos][data-aos-duration='1300'], +body[data-aos-duration='1300'] [data-aos] { + transition-duration: 1.3s; +} +[data-aos][data-aos][data-aos-delay='1300'], +body[data-aos-delay='1300'] [data-aos] { + transition-delay: 0; +} +[data-aos][data-aos][data-aos-delay='1300'].aos-animate, +body[data-aos-delay='1300'] [data-aos].aos-animate { + transition-delay: 1.3s; +} +[data-aos][data-aos][data-aos-duration='1350'], +body[data-aos-duration='1350'] [data-aos] { + transition-duration: 1.35s; +} +[data-aos][data-aos][data-aos-delay='1350'], +body[data-aos-delay='1350'] [data-aos] { + transition-delay: 0; +} +[data-aos][data-aos][data-aos-delay='1350'].aos-animate, +body[data-aos-delay='1350'] [data-aos].aos-animate { + transition-delay: 1.35s; +} +[data-aos][data-aos][data-aos-duration='1400'], +body[data-aos-duration='1400'] [data-aos] { + transition-duration: 1.4s; +} +[data-aos][data-aos][data-aos-delay='1400'], +body[data-aos-delay='1400'] [data-aos] { + transition-delay: 0; +} +[data-aos][data-aos][data-aos-delay='1400'].aos-animate, +body[data-aos-delay='1400'] [data-aos].aos-animate { + transition-delay: 1.4s; +} +[data-aos][data-aos][data-aos-duration='1450'], +body[data-aos-duration='1450'] [data-aos] { + transition-duration: 1.45s; +} +[data-aos][data-aos][data-aos-delay='1450'], +body[data-aos-delay='1450'] [data-aos] { + transition-delay: 0; +} +[data-aos][data-aos][data-aos-delay='1450'].aos-animate, +body[data-aos-delay='1450'] [data-aos].aos-animate { + transition-delay: 1.45s; +} +[data-aos][data-aos][data-aos-duration='1500'], +body[data-aos-duration='1500'] [data-aos] { + transition-duration: 1.5s; +} +[data-aos][data-aos][data-aos-delay='1500'], +body[data-aos-delay='1500'] [data-aos] { + transition-delay: 0; +} +[data-aos][data-aos][data-aos-delay='1500'].aos-animate, +body[data-aos-delay='1500'] [data-aos].aos-animate { + transition-delay: 1.5s; +} +[data-aos][data-aos][data-aos-duration='1550'], +body[data-aos-duration='1550'] [data-aos] { + transition-duration: 1.55s; +} +[data-aos][data-aos][data-aos-delay='1550'], +body[data-aos-delay='1550'] [data-aos] { + transition-delay: 0; +} +[data-aos][data-aos][data-aos-delay='1550'].aos-animate, +body[data-aos-delay='1550'] [data-aos].aos-animate { + transition-delay: 1.55s; +} +[data-aos][data-aos][data-aos-duration='1600'], +body[data-aos-duration='1600'] [data-aos] { + transition-duration: 1.6s; +} +[data-aos][data-aos][data-aos-delay='1600'], +body[data-aos-delay='1600'] [data-aos] { + transition-delay: 0; +} +[data-aos][data-aos][data-aos-delay='1600'].aos-animate, +body[data-aos-delay='1600'] [data-aos].aos-animate { + transition-delay: 1.6s; +} +[data-aos][data-aos][data-aos-duration='1650'], +body[data-aos-duration='1650'] [data-aos] { + transition-duration: 1.65s; +} +[data-aos][data-aos][data-aos-delay='1650'], +body[data-aos-delay='1650'] [data-aos] { + transition-delay: 0; +} +[data-aos][data-aos][data-aos-delay='1650'].aos-animate, +body[data-aos-delay='1650'] [data-aos].aos-animate { + transition-delay: 1.65s; +} +[data-aos][data-aos][data-aos-duration='1700'], +body[data-aos-duration='1700'] [data-aos] { + transition-duration: 1.7s; +} +[data-aos][data-aos][data-aos-delay='1700'], +body[data-aos-delay='1700'] [data-aos] { + transition-delay: 0; +} +[data-aos][data-aos][data-aos-delay='1700'].aos-animate, +body[data-aos-delay='1700'] [data-aos].aos-animate { + transition-delay: 1.7s; +} +[data-aos][data-aos][data-aos-duration='1750'], +body[data-aos-duration='1750'] [data-aos] { + transition-duration: 1.75s; +} +[data-aos][data-aos][data-aos-delay='1750'], +body[data-aos-delay='1750'] [data-aos] { + transition-delay: 0; +} +[data-aos][data-aos][data-aos-delay='1750'].aos-animate, +body[data-aos-delay='1750'] [data-aos].aos-animate { + transition-delay: 1.75s; +} +[data-aos][data-aos][data-aos-duration='1800'], +body[data-aos-duration='1800'] [data-aos] { + transition-duration: 1.8s; +} +[data-aos][data-aos][data-aos-delay='1800'], +body[data-aos-delay='1800'] [data-aos] { + transition-delay: 0; +} +[data-aos][data-aos][data-aos-delay='1800'].aos-animate, +body[data-aos-delay='1800'] [data-aos].aos-animate { + transition-delay: 1.8s; +} +[data-aos][data-aos][data-aos-duration='1850'], +body[data-aos-duration='1850'] [data-aos] { + transition-duration: 1.85s; +} +[data-aos][data-aos][data-aos-delay='1850'], +body[data-aos-delay='1850'] [data-aos] { + transition-delay: 0; +} +[data-aos][data-aos][data-aos-delay='1850'].aos-animate, +body[data-aos-delay='1850'] [data-aos].aos-animate { + transition-delay: 1.85s; +} +[data-aos][data-aos][data-aos-duration='1900'], +body[data-aos-duration='1900'] [data-aos] { + transition-duration: 1.9s; +} +[data-aos][data-aos][data-aos-delay='1900'], +body[data-aos-delay='1900'] [data-aos] { + transition-delay: 0; +} +[data-aos][data-aos][data-aos-delay='1900'].aos-animate, +body[data-aos-delay='1900'] [data-aos].aos-animate { + transition-delay: 1.9s; +} +[data-aos][data-aos][data-aos-duration='1950'], +body[data-aos-duration='1950'] [data-aos] { + transition-duration: 1.95s; +} +[data-aos][data-aos][data-aos-delay='1950'], +body[data-aos-delay='1950'] [data-aos] { + transition-delay: 0; +} +[data-aos][data-aos][data-aos-delay='1950'].aos-animate, +body[data-aos-delay='1950'] [data-aos].aos-animate { + transition-delay: 1.95s; +} +[data-aos][data-aos][data-aos-duration='2000'], +body[data-aos-duration='2000'] [data-aos] { + transition-duration: 2s; +} +[data-aos][data-aos][data-aos-delay='2000'], +body[data-aos-delay='2000'] [data-aos] { + transition-delay: 0; +} +[data-aos][data-aos][data-aos-delay='2000'].aos-animate, +body[data-aos-delay='2000'] [data-aos].aos-animate { + transition-delay: 2s; +} +[data-aos][data-aos][data-aos-duration='2050'], +body[data-aos-duration='2050'] [data-aos] { + transition-duration: 2.05s; +} +[data-aos][data-aos][data-aos-delay='2050'], +body[data-aos-delay='2050'] [data-aos] { + transition-delay: 0; +} +[data-aos][data-aos][data-aos-delay='2050'].aos-animate, +body[data-aos-delay='2050'] [data-aos].aos-animate { + transition-delay: 2.05s; +} +[data-aos][data-aos][data-aos-duration='2100'], +body[data-aos-duration='2100'] [data-aos] { + transition-duration: 2.1s; +} +[data-aos][data-aos][data-aos-delay='2100'], +body[data-aos-delay='2100'] [data-aos] { + transition-delay: 0; +} +[data-aos][data-aos][data-aos-delay='2100'].aos-animate, +body[data-aos-delay='2100'] [data-aos].aos-animate { + transition-delay: 2.1s; +} +[data-aos][data-aos][data-aos-duration='2150'], +body[data-aos-duration='2150'] [data-aos] { + transition-duration: 2.15s; +} +[data-aos][data-aos][data-aos-delay='2150'], +body[data-aos-delay='2150'] [data-aos] { + transition-delay: 0; +} +[data-aos][data-aos][data-aos-delay='2150'].aos-animate, +body[data-aos-delay='2150'] [data-aos].aos-animate { + transition-delay: 2.15s; +} +[data-aos][data-aos][data-aos-duration='2200'], +body[data-aos-duration='2200'] [data-aos] { + transition-duration: 2.2s; +} +[data-aos][data-aos][data-aos-delay='2200'], +body[data-aos-delay='2200'] [data-aos] { + transition-delay: 0; +} +[data-aos][data-aos][data-aos-delay='2200'].aos-animate, +body[data-aos-delay='2200'] [data-aos].aos-animate { + transition-delay: 2.2s; +} +[data-aos][data-aos][data-aos-duration='2250'], +body[data-aos-duration='2250'] [data-aos] { + transition-duration: 2.25s; +} +[data-aos][data-aos][data-aos-delay='2250'], +body[data-aos-delay='2250'] [data-aos] { + transition-delay: 0; +} +[data-aos][data-aos][data-aos-delay='2250'].aos-animate, +body[data-aos-delay='2250'] [data-aos].aos-animate { + transition-delay: 2.25s; +} +[data-aos][data-aos][data-aos-duration='2300'], +body[data-aos-duration='2300'] [data-aos] { + transition-duration: 2.3s; +} +[data-aos][data-aos][data-aos-delay='2300'], +body[data-aos-delay='2300'] [data-aos] { + transition-delay: 0; +} +[data-aos][data-aos][data-aos-delay='2300'].aos-animate, +body[data-aos-delay='2300'] [data-aos].aos-animate { + transition-delay: 2.3s; +} +[data-aos][data-aos][data-aos-duration='2350'], +body[data-aos-duration='2350'] [data-aos] { + transition-duration: 2.35s; +} +[data-aos][data-aos][data-aos-delay='2350'], +body[data-aos-delay='2350'] [data-aos] { + transition-delay: 0; +} +[data-aos][data-aos][data-aos-delay='2350'].aos-animate, +body[data-aos-delay='2350'] [data-aos].aos-animate { + transition-delay: 2.35s; +} +[data-aos][data-aos][data-aos-duration='2400'], +body[data-aos-duration='2400'] [data-aos] { + transition-duration: 2.4s; +} +[data-aos][data-aos][data-aos-delay='2400'], +body[data-aos-delay='2400'] [data-aos] { + transition-delay: 0; +} +[data-aos][data-aos][data-aos-delay='2400'].aos-animate, +body[data-aos-delay='2400'] [data-aos].aos-animate { + transition-delay: 2.4s; +} +[data-aos][data-aos][data-aos-duration='2450'], +body[data-aos-duration='2450'] [data-aos] { + transition-duration: 2.45s; +} +[data-aos][data-aos][data-aos-delay='2450'], +body[data-aos-delay='2450'] [data-aos] { + transition-delay: 0; +} +[data-aos][data-aos][data-aos-delay='2450'].aos-animate, +body[data-aos-delay='2450'] [data-aos].aos-animate { + transition-delay: 2.45s; +} +[data-aos][data-aos][data-aos-duration='2500'], +body[data-aos-duration='2500'] [data-aos] { + transition-duration: 2.5s; +} +[data-aos][data-aos][data-aos-delay='2500'], +body[data-aos-delay='2500'] [data-aos] { + transition-delay: 0; +} +[data-aos][data-aos][data-aos-delay='2500'].aos-animate, +body[data-aos-delay='2500'] [data-aos].aos-animate { + transition-delay: 2.5s; +} +[data-aos][data-aos][data-aos-duration='2550'], +body[data-aos-duration='2550'] [data-aos] { + transition-duration: 2.55s; +} +[data-aos][data-aos][data-aos-delay='2550'], +body[data-aos-delay='2550'] [data-aos] { + transition-delay: 0; +} +[data-aos][data-aos][data-aos-delay='2550'].aos-animate, +body[data-aos-delay='2550'] [data-aos].aos-animate { + transition-delay: 2.55s; +} +[data-aos][data-aos][data-aos-duration='2600'], +body[data-aos-duration='2600'] [data-aos] { + transition-duration: 2.6s; +} +[data-aos][data-aos][data-aos-delay='2600'], +body[data-aos-delay='2600'] [data-aos] { + transition-delay: 0; +} +[data-aos][data-aos][data-aos-delay='2600'].aos-animate, +body[data-aos-delay='2600'] [data-aos].aos-animate { + transition-delay: 2.6s; +} +[data-aos][data-aos][data-aos-duration='2650'], +body[data-aos-duration='2650'] [data-aos] { + transition-duration: 2.65s; +} +[data-aos][data-aos][data-aos-delay='2650'], +body[data-aos-delay='2650'] [data-aos] { + transition-delay: 0; +} +[data-aos][data-aos][data-aos-delay='2650'].aos-animate, +body[data-aos-delay='2650'] [data-aos].aos-animate { + transition-delay: 2.65s; +} +[data-aos][data-aos][data-aos-duration='2700'], +body[data-aos-duration='2700'] [data-aos] { + transition-duration: 2.7s; +} +[data-aos][data-aos][data-aos-delay='2700'], +body[data-aos-delay='2700'] [data-aos] { + transition-delay: 0; +} +[data-aos][data-aos][data-aos-delay='2700'].aos-animate, +body[data-aos-delay='2700'] [data-aos].aos-animate { + transition-delay: 2.7s; +} +[data-aos][data-aos][data-aos-duration='2750'], +body[data-aos-duration='2750'] [data-aos] { + transition-duration: 2.75s; +} +[data-aos][data-aos][data-aos-delay='2750'], +body[data-aos-delay='2750'] [data-aos] { + transition-delay: 0; +} +[data-aos][data-aos][data-aos-delay='2750'].aos-animate, +body[data-aos-delay='2750'] [data-aos].aos-animate { + transition-delay: 2.75s; +} +[data-aos][data-aos][data-aos-duration='2800'], +body[data-aos-duration='2800'] [data-aos] { + transition-duration: 2.8s; +} +[data-aos][data-aos][data-aos-delay='2800'], +body[data-aos-delay='2800'] [data-aos] { + transition-delay: 0; +} +[data-aos][data-aos][data-aos-delay='2800'].aos-animate, +body[data-aos-delay='2800'] [data-aos].aos-animate { + transition-delay: 2.8s; +} +[data-aos][data-aos][data-aos-duration='2850'], +body[data-aos-duration='2850'] [data-aos] { + transition-duration: 2.85s; +} +[data-aos][data-aos][data-aos-delay='2850'], +body[data-aos-delay='2850'] [data-aos] { + transition-delay: 0; +} +[data-aos][data-aos][data-aos-delay='2850'].aos-animate, +body[data-aos-delay='2850'] [data-aos].aos-animate { + transition-delay: 2.85s; +} +[data-aos][data-aos][data-aos-duration='2900'], +body[data-aos-duration='2900'] [data-aos] { + transition-duration: 2.9s; +} +[data-aos][data-aos][data-aos-delay='2900'], +body[data-aos-delay='2900'] [data-aos] { + transition-delay: 0; +} +[data-aos][data-aos][data-aos-delay='2900'].aos-animate, +body[data-aos-delay='2900'] [data-aos].aos-animate { + transition-delay: 2.9s; +} +[data-aos][data-aos][data-aos-duration='2950'], +body[data-aos-duration='2950'] [data-aos] { + transition-duration: 2.95s; +} +[data-aos][data-aos][data-aos-delay='2950'], +body[data-aos-delay='2950'] [data-aos] { + transition-delay: 0; +} +[data-aos][data-aos][data-aos-delay='2950'].aos-animate, +body[data-aos-delay='2950'] [data-aos].aos-animate { + transition-delay: 2.95s; +} +[data-aos][data-aos][data-aos-duration='3000'], +body[data-aos-duration='3000'] [data-aos] { + transition-duration: 3s; +} +[data-aos][data-aos][data-aos-delay='3000'], +body[data-aos-delay='3000'] [data-aos] { + transition-delay: 0; +} +[data-aos][data-aos][data-aos-delay='3000'].aos-animate, +body[data-aos-delay='3000'] [data-aos].aos-animate { + transition-delay: 3s; +} +[data-aos][data-aos][data-aos-easing='linear'], +body[data-aos-easing='linear'] [data-aos] { + transition-timing-function: cubic-bezier(0.25, 0.25, 0.75, 0.75); +} +[data-aos][data-aos][data-aos-easing='ease'], +body[data-aos-easing='ease'] [data-aos] { + transition-timing-function: ease; +} +[data-aos][data-aos][data-aos-easing='ease-in'], +body[data-aos-easing='ease-in'] [data-aos] { + transition-timing-function: ease-in; +} +[data-aos][data-aos][data-aos-easing='ease-out'], +body[data-aos-easing='ease-out'] [data-aos] { + transition-timing-function: ease-out; +} +[data-aos][data-aos][data-aos-easing='ease-in-out'], +body[data-aos-easing='ease-in-out'] [data-aos] { + transition-timing-function: ease-in-out; +} +[data-aos][data-aos][data-aos-easing='ease-in-back'], +body[data-aos-easing='ease-in-back'] [data-aos] { + transition-timing-function: cubic-bezier(0.6, -0.28, 0.735, 0.045); +} +[data-aos][data-aos][data-aos-easing='ease-out-back'], +body[data-aos-easing='ease-out-back'] [data-aos] { + transition-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1.275); +} +[data-aos][data-aos][data-aos-easing='ease-in-out-back'], +body[data-aos-easing='ease-in-out-back'] [data-aos] { + transition-timing-function: cubic-bezier(0.68, -0.55, 0.265, 1.55); +} +[data-aos][data-aos][data-aos-easing='ease-in-sine'], +body[data-aos-easing='ease-in-sine'] [data-aos] { + transition-timing-function: cubic-bezier(0.47, 0, 0.745, 0.715); +} +[data-aos][data-aos][data-aos-easing='ease-out-sine'], +body[data-aos-easing='ease-out-sine'] [data-aos] { + transition-timing-function: cubic-bezier(0.39, 0.575, 0.565, 1); +} +[data-aos][data-aos][data-aos-easing='ease-in-out-sine'], +body[data-aos-easing='ease-in-out-sine'] [data-aos] { + transition-timing-function: cubic-bezier(0.445, 0.05, 0.55, 0.95); +} +[data-aos][data-aos][data-aos-easing='ease-in-quad'], +body[data-aos-easing='ease-in-quad'] [data-aos] { + transition-timing-function: cubic-bezier(0.55, 0.085, 0.68, 0.53); +} +[data-aos][data-aos][data-aos-easing='ease-out-quad'], +body[data-aos-easing='ease-out-quad'] [data-aos] { + transition-timing-function: cubic-bezier(0.25, 0.46, 0.45, 0.94); +} +[data-aos][data-aos][data-aos-easing='ease-in-out-quad'], +body[data-aos-easing='ease-in-out-quad'] [data-aos] { + transition-timing-function: cubic-bezier(0.455, 0.03, 0.515, 0.955); +} +[data-aos][data-aos][data-aos-easing='ease-in-cubic'], +body[data-aos-easing='ease-in-cubic'] [data-aos] { + transition-timing-function: cubic-bezier(0.55, 0.085, 0.68, 0.53); +} +[data-aos][data-aos][data-aos-easing='ease-out-cubic'], +body[data-aos-easing='ease-out-cubic'] [data-aos] { + transition-timing-function: cubic-bezier(0.25, 0.46, 0.45, 0.94); +} +[data-aos][data-aos][data-aos-easing='ease-in-out-cubic'], +body[data-aos-easing='ease-in-out-cubic'] [data-aos] { + transition-timing-function: cubic-bezier(0.455, 0.03, 0.515, 0.955); +} +[data-aos][data-aos][data-aos-easing='ease-in-quart'], +body[data-aos-easing='ease-in-quart'] [data-aos] { + transition-timing-function: cubic-bezier(0.55, 0.085, 0.68, 0.53); +} +[data-aos][data-aos][data-aos-easing='ease-out-quart'], +body[data-aos-easing='ease-out-quart'] [data-aos] { + transition-timing-function: cubic-bezier(0.25, 0.46, 0.45, 0.94); +} +[data-aos][data-aos][data-aos-easing='ease-in-out-quart'], +body[data-aos-easing='ease-in-out-quart'] [data-aos] { + transition-timing-function: cubic-bezier(0.455, 0.03, 0.515, 0.955); +} +[data-aos^='fade'][data-aos^='fade'] { + opacity: 0; + transition-property: opacity, transform; +} +[data-aos^='fade'][data-aos^='fade'].aos-animate { + opacity: 1; + transform: translateZ(0); +} +[data-aos='fade-up'] { + transform: translate3d(0, 100px, 0); +} +[data-aos='fade-down'] { + transform: translate3d(0, -100px, 0); +} +[data-aos='fade-right'] { + transform: translate3d(-100px, 0, 0); +} +[data-aos='fade-left'] { + transform: translate3d(100px, 0, 0); +} +[data-aos='fade-up-right'] { + transform: translate3d(-100px, 100px, 0); +} +[data-aos='fade-up-left'] { + transform: translate3d(100px, 100px, 0); +} +[data-aos='fade-down-right'] { + transform: translate3d(-100px, -100px, 0); +} +[data-aos='fade-down-left'] { + transform: translate3d(100px, -100px, 0); +} +[data-aos^='zoom'][data-aos^='zoom'] { + opacity: 0; + transition-property: opacity, transform; +} +[data-aos^='zoom'][data-aos^='zoom'].aos-animate { + opacity: 1; + transform: translateZ(0) scale(1); +} +[data-aos='zoom-in'] { + transform: scale(0.6); +} +[data-aos='zoom-in-up'] { + transform: translate3d(0, 100px, 0) scale(0.6); +} +[data-aos='zoom-in-down'] { + transform: translate3d(0, -100px, 0) scale(0.6); +} +[data-aos='zoom-in-right'] { + transform: translate3d(-100px, 0, 0) scale(0.6); +} +[data-aos='zoom-in-left'] { + transform: translate3d(100px, 0, 0) scale(0.6); +} +[data-aos='zoom-out'] { + transform: scale(1.2); +} +[data-aos='zoom-out-up'] { + transform: translate3d(0, 100px, 0) scale(1.2); +} +[data-aos='zoom-out-down'] { + transform: translate3d(0, -100px, 0) scale(1.2); +} +[data-aos='zoom-out-right'] { + transform: translate3d(-100px, 0, 0) scale(1.2); +} +[data-aos='zoom-out-left'] { + transform: translate3d(100px, 0, 0) scale(1.2); +} +[data-aos^='slide'][data-aos^='slide'] { + transition-property: transform; +} +[data-aos^='slide'][data-aos^='slide'].aos-animate { + transform: translateZ(0); +} +[data-aos='slide-up'] { + transform: translate3d(0, 100%, 0); +} +[data-aos='slide-down'] { + transform: translate3d(0, -100%, 0); +} +[data-aos='slide-right'] { + transform: translate3d(-100%, 0, 0); +} +[data-aos='slide-left'] { + transform: translate3d(100%, 0, 0); +} +[data-aos^='flip'][data-aos^='flip'] { + backface-visibility: hidden; + transition-property: transform; +} +[data-aos='flip-left'] { + transform: perspective(2500px) rotateY(-100deg); +} +[data-aos='flip-left'].aos-animate { + transform: perspective(2500px) rotateY(0); +} +[data-aos='flip-right'] { + transform: perspective(2500px) rotateY(100deg); +} +[data-aos='flip-right'].aos-animate { + transform: perspective(2500px) rotateY(0); +} +[data-aos='flip-up'] { + transform: perspective(2500px) rotateX(-100deg); +} +[data-aos='flip-up'].aos-animate { + transform: perspective(2500px) rotateX(0); +} +[data-aos='flip-down'] { + transform: perspective(2500px) rotateX(100deg); +} +[data-aos='flip-down'].aos-animate { + transform: perspective(2500px) rotateX(0); +} diff --git a/public/js/aos.js b/public/js/aos.js new file mode 100644 index 00000000..f77369b7 --- /dev/null +++ b/public/js/aos.js @@ -0,0 +1,672 @@ +!(function (e, t) { + 'object' == typeof exports && 'object' == typeof module + ? (module.exports = t()) + : 'function' == typeof define && define.amd + ? define([], t) + : 'object' == typeof exports + ? (exports.AOS = t()) + : (e.AOS = t()) +})(this, function () { + return (function (e) { + function t(o) { + if (n[o]) return n[o].exports + var i = (n[o] = { exports: {}, id: o, loaded: !1 }) + return e[o].call(i.exports, i, i.exports, t), (i.loaded = !0), i.exports + } + var n = {} + return (t.m = e), (t.c = n), (t.p = 'dist/'), t(0) + })([ + function (e, t, n) { + 'use strict' + function o(e) { + return e && e.__esModule ? e : { default: e } + } + var i = + Object.assign || + function (e) { + for (var t = 1; t < arguments.length; t++) { + var n = arguments[t] + for (var o in n) + Object.prototype.hasOwnProperty.call(n, o) && (e[o] = n[o]) + } + return e + }, + r = n(1), + a = (o(r), n(6)), + u = o(a), + c = n(7), + s = o(c), + f = n(8), + d = o(f), + l = n(9), + p = o(l), + m = n(10), + b = o(m), + v = n(11), + y = o(v), + g = n(14), + h = o(g), + w = [], + k = !1, + x = { + offset: 120, + delay: 0, + easing: 'ease', + duration: 400, + disable: !1, + once: !1, + startEvent: 'DOMContentLoaded', + throttleDelay: 99, + debounceDelay: 50, + disableMutationObserver: !1 + }, + j = function () { + var e = + arguments.length > 0 && void 0 !== arguments[0] && arguments[0] + if ((e && (k = !0), k)) + return (w = (0, y.default)(w, x)), (0, b.default)(w, x.once), w + }, + O = function () { + ;(w = (0, h.default)()), j() + }, + M = function () { + w.forEach(function (e, t) { + e.node.removeAttribute('data-aos'), + e.node.removeAttribute('data-aos-easing'), + e.node.removeAttribute('data-aos-duration'), + e.node.removeAttribute('data-aos-delay') + }) + }, + S = function (e) { + return ( + e === !0 || + ('mobile' === e && p.default.mobile()) || + ('phone' === e && p.default.phone()) || + ('tablet' === e && p.default.tablet()) || + ('function' == typeof e && e() === !0) + ) + }, + _ = function (e) { + ;(x = i(x, e)), (w = (0, h.default)()) + var t = document.all && !window.atob + return S(x.disable) || t + ? M() + : (x.disableMutationObserver || + d.default.isSupported() || + (console.info( + '\n aos: MutationObserver is not supported on this browser,\n code mutations observing has been disabled.\n You may have to call "refreshHard()" by yourself.\n ' + ), + (x.disableMutationObserver = !0)), + document + .querySelector('body') + .setAttribute('data-aos-easing', x.easing), + document + .querySelector('body') + .setAttribute('data-aos-duration', x.duration), + document + .querySelector('body') + .setAttribute('data-aos-delay', x.delay), + 'DOMContentLoaded' === x.startEvent && + ['complete', 'interactive'].indexOf(document.readyState) > -1 + ? j(!0) + : 'load' === x.startEvent + ? window.addEventListener(x.startEvent, function () { + j(!0) + }) + : document.addEventListener(x.startEvent, function () { + j(!0) + }), + window.addEventListener( + 'resize', + (0, s.default)(j, x.debounceDelay, !0) + ), + window.addEventListener( + 'orientationchange', + (0, s.default)(j, x.debounceDelay, !0) + ), + window.addEventListener( + 'scroll', + (0, u.default)(function () { + ;(0, b.default)(w, x.once) + }, x.throttleDelay) + ), + x.disableMutationObserver || d.default.ready('[data-aos]', O), + w) + } + e.exports = { init: _, refresh: j, refreshHard: O } + }, + function (e, t) {}, + , + , + , + , + function (e, t) { + ;(function (t) { + 'use strict' + function n(e, t, n) { + function o(t) { + var n = b, + o = v + return (b = v = void 0), (k = t), (g = e.apply(o, n)) + } + function r(e) { + return (k = e), (h = setTimeout(f, t)), M ? o(e) : g + } + function a(e) { + var n = e - w, + o = e - k, + i = t - n + return S ? j(i, y - o) : i + } + function c(e) { + var n = e - w, + o = e - k + return void 0 === w || n >= t || n < 0 || (S && o >= y) + } + function f() { + var e = O() + return c(e) ? d(e) : void (h = setTimeout(f, a(e))) + } + function d(e) { + return (h = void 0), _ && b ? o(e) : ((b = v = void 0), g) + } + function l() { + void 0 !== h && clearTimeout(h), (k = 0), (b = w = v = h = void 0) + } + function p() { + return void 0 === h ? g : d(O()) + } + function m() { + var e = O(), + n = c(e) + if (((b = arguments), (v = this), (w = e), n)) { + if (void 0 === h) return r(w) + if (S) return (h = setTimeout(f, t)), o(w) + } + return void 0 === h && (h = setTimeout(f, t)), g + } + var b, + v, + y, + g, + h, + w, + k = 0, + M = !1, + S = !1, + _ = !0 + if ('function' != typeof e) throw new TypeError(s) + return ( + (t = u(t) || 0), + i(n) && + ((M = !!n.leading), + (S = 'maxWait' in n), + (y = S ? x(u(n.maxWait) || 0, t) : y), + (_ = 'trailing' in n ? !!n.trailing : _)), + (m.cancel = l), + (m.flush = p), + m + ) + } + function o(e, t, o) { + var r = !0, + a = !0 + if ('function' != typeof e) throw new TypeError(s) + return ( + i(o) && + ((r = 'leading' in o ? !!o.leading : r), + (a = 'trailing' in o ? !!o.trailing : a)), + n(e, t, { leading: r, maxWait: t, trailing: a }) + ) + } + function i(e) { + var t = 'undefined' == typeof e ? 'undefined' : c(e) + return !!e && ('object' == t || 'function' == t) + } + function r(e) { + return ( + !!e && 'object' == ('undefined' == typeof e ? 'undefined' : c(e)) + ) + } + function a(e) { + return ( + 'symbol' == ('undefined' == typeof e ? 'undefined' : c(e)) || + (r(e) && k.call(e) == d) + ) + } + function u(e) { + if ('number' == typeof e) return e + if (a(e)) return f + if (i(e)) { + var t = 'function' == typeof e.valueOf ? e.valueOf() : e + e = i(t) ? t + '' : t + } + if ('string' != typeof e) return 0 === e ? e : +e + e = e.replace(l, '') + var n = m.test(e) + return n || b.test(e) ? v(e.slice(2), n ? 2 : 8) : p.test(e) ? f : +e + } + var c = + 'function' == typeof Symbol && 'symbol' == typeof Symbol.iterator + ? function (e) { + return typeof e + } + : function (e) { + return e && + 'function' == typeof Symbol && + e.constructor === Symbol && + e !== Symbol.prototype + ? 'symbol' + : typeof e + }, + s = 'Expected a function', + f = NaN, + d = '[object Symbol]', + l = /^\s+|\s+$/g, + p = /^[-+]0x[0-9a-f]+$/i, + m = /^0b[01]+$/i, + b = /^0o[0-7]+$/i, + v = parseInt, + y = + 'object' == ('undefined' == typeof t ? 'undefined' : c(t)) && + t && + t.Object === Object && + t, + g = + 'object' == ('undefined' == typeof self ? 'undefined' : c(self)) && + self && + self.Object === Object && + self, + h = y || g || Function('return this')(), + w = Object.prototype, + k = w.toString, + x = Math.max, + j = Math.min, + O = function () { + return h.Date.now() + } + e.exports = o + }).call( + t, + (function () { + return this + })() + ) + }, + function (e, t) { + ;(function (t) { + 'use strict' + function n(e, t, n) { + function i(t) { + var n = b, + o = v + return (b = v = void 0), (O = t), (g = e.apply(o, n)) + } + function r(e) { + return (O = e), (h = setTimeout(f, t)), M ? i(e) : g + } + function u(e) { + var n = e - w, + o = e - O, + i = t - n + return S ? x(i, y - o) : i + } + function s(e) { + var n = e - w, + o = e - O + return void 0 === w || n >= t || n < 0 || (S && o >= y) + } + function f() { + var e = j() + return s(e) ? d(e) : void (h = setTimeout(f, u(e))) + } + function d(e) { + return (h = void 0), _ && b ? i(e) : ((b = v = void 0), g) + } + function l() { + void 0 !== h && clearTimeout(h), (O = 0), (b = w = v = h = void 0) + } + function p() { + return void 0 === h ? g : d(j()) + } + function m() { + var e = j(), + n = s(e) + if (((b = arguments), (v = this), (w = e), n)) { + if (void 0 === h) return r(w) + if (S) return (h = setTimeout(f, t)), i(w) + } + return void 0 === h && (h = setTimeout(f, t)), g + } + var b, + v, + y, + g, + h, + w, + O = 0, + M = !1, + S = !1, + _ = !0 + if ('function' != typeof e) throw new TypeError(c) + return ( + (t = a(t) || 0), + o(n) && + ((M = !!n.leading), + (S = 'maxWait' in n), + (y = S ? k(a(n.maxWait) || 0, t) : y), + (_ = 'trailing' in n ? !!n.trailing : _)), + (m.cancel = l), + (m.flush = p), + m + ) + } + function o(e) { + var t = 'undefined' == typeof e ? 'undefined' : u(e) + return !!e && ('object' == t || 'function' == t) + } + function i(e) { + return ( + !!e && 'object' == ('undefined' == typeof e ? 'undefined' : u(e)) + ) + } + function r(e) { + return ( + 'symbol' == ('undefined' == typeof e ? 'undefined' : u(e)) || + (i(e) && w.call(e) == f) + ) + } + function a(e) { + if ('number' == typeof e) return e + if (r(e)) return s + if (o(e)) { + var t = 'function' == typeof e.valueOf ? e.valueOf() : e + e = o(t) ? t + '' : t + } + if ('string' != typeof e) return 0 === e ? e : +e + e = e.replace(d, '') + var n = p.test(e) + return n || m.test(e) ? b(e.slice(2), n ? 2 : 8) : l.test(e) ? s : +e + } + var u = + 'function' == typeof Symbol && 'symbol' == typeof Symbol.iterator + ? function (e) { + return typeof e + } + : function (e) { + return e && + 'function' == typeof Symbol && + e.constructor === Symbol && + e !== Symbol.prototype + ? 'symbol' + : typeof e + }, + c = 'Expected a function', + s = NaN, + f = '[object Symbol]', + d = /^\s+|\s+$/g, + l = /^[-+]0x[0-9a-f]+$/i, + p = /^0b[01]+$/i, + m = /^0o[0-7]+$/i, + b = parseInt, + v = + 'object' == ('undefined' == typeof t ? 'undefined' : u(t)) && + t && + t.Object === Object && + t, + y = + 'object' == ('undefined' == typeof self ? 'undefined' : u(self)) && + self && + self.Object === Object && + self, + g = v || y || Function('return this')(), + h = Object.prototype, + w = h.toString, + k = Math.max, + x = Math.min, + j = function () { + return g.Date.now() + } + e.exports = n + }).call( + t, + (function () { + return this + })() + ) + }, + function (e, t) { + 'use strict' + function n(e) { + var t = void 0, + o = void 0, + i = void 0 + for (t = 0; t < e.length; t += 1) { + if (((o = e[t]), o.dataset && o.dataset.aos)) return !0 + if ((i = o.children && n(o.children))) return !0 + } + return !1 + } + function o() { + return ( + window.MutationObserver || + window.WebKitMutationObserver || + window.MozMutationObserver + ) + } + function i() { + return !!o() + } + function r(e, t) { + var n = window.document, + i = o(), + r = new i(a) + ;(u = t), + r.observe(n.documentElement, { + childList: !0, + subtree: !0, + removedNodes: !0 + }) + } + function a(e) { + e && + e.forEach(function (e) { + var t = Array.prototype.slice.call(e.addedNodes), + o = Array.prototype.slice.call(e.removedNodes), + i = t.concat(o) + if (n(i)) return u() + }) + } + Object.defineProperty(t, '__esModule', { value: !0 }) + var u = function () {} + t.default = { isSupported: i, ready: r } + }, + function (e, t) { + 'use strict' + function n(e, t) { + if (!(e instanceof t)) + throw new TypeError('Cannot call a class as a function') + } + function o() { + return navigator.userAgent || navigator.vendor || window.opera || '' + } + Object.defineProperty(t, '__esModule', { value: !0 }) + var i = (function () { + function e(e, t) { + for (var n = 0; n < t.length; n++) { + var o = t[n] + ;(o.enumerable = o.enumerable || !1), + (o.configurable = !0), + 'value' in o && (o.writable = !0), + Object.defineProperty(e, o.key, o) + } + } + return function (t, n, o) { + return n && e(t.prototype, n), o && e(t, o), t + } + })(), + r = + /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i, + a = + /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i, + u = + /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i, + c = + /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i, + s = (function () { + function e() { + n(this, e) + } + return ( + i(e, [ + { + key: 'phone', + value: function () { + var e = o() + return !(!r.test(e) && !a.test(e.substr(0, 4))) + } + }, + { + key: 'mobile', + value: function () { + var e = o() + return !(!u.test(e) && !c.test(e.substr(0, 4))) + } + }, + { + key: 'tablet', + value: function () { + return this.mobile() && !this.phone() + } + } + ]), + e + ) + })() + t.default = new s() + }, + function (e, t) { + 'use strict' + Object.defineProperty(t, '__esModule', { value: !0 }) + var n = function (e, t, n) { + var o = e.node.getAttribute('data-aos-once') + t > e.position + ? e.node.classList.add('aos-animate') + : 'undefined' != typeof o && + ('false' === o || (!n && 'true' !== o)) && + e.node.classList.remove('aos-animate') + }, + o = function (e, t) { + var o = window.pageYOffset, + i = window.innerHeight + e.forEach(function (e, r) { + n(e, i + o, t) + }) + } + t.default = o + }, + function (e, t, n) { + 'use strict' + function o(e) { + return e && e.__esModule ? e : { default: e } + } + Object.defineProperty(t, '__esModule', { value: !0 }) + var i = n(12), + r = o(i), + a = function (e, t) { + return ( + e.forEach(function (e, n) { + e.node.classList.add('aos-init'), + (e.position = (0, r.default)(e.node, t.offset)) + }), + e + ) + } + t.default = a + }, + function (e, t, n) { + 'use strict' + function o(e) { + return e && e.__esModule ? e : { default: e } + } + Object.defineProperty(t, '__esModule', { value: !0 }) + var i = n(13), + r = o(i), + a = function (e, t) { + var n = 0, + o = 0, + i = window.innerHeight, + a = { + offset: e.getAttribute('data-aos-offset'), + anchor: e.getAttribute('data-aos-anchor'), + anchorPlacement: e.getAttribute('data-aos-anchor-placement') + } + switch ( + (a.offset && !isNaN(a.offset) && (o = parseInt(a.offset)), + a.anchor && + document.querySelectorAll(a.anchor) && + (e = document.querySelectorAll(a.anchor)[0]), + (n = (0, r.default)(e).top), + a.anchorPlacement) + ) { + case 'top-bottom': + break + case 'center-bottom': + n += e.offsetHeight / 2 + break + case 'bottom-bottom': + n += e.offsetHeight + break + case 'top-center': + n += i / 2 + break + case 'bottom-center': + n += i / 2 + e.offsetHeight + break + case 'center-center': + n += i / 2 + e.offsetHeight / 2 + break + case 'top-top': + n += i + break + case 'bottom-top': + n += e.offsetHeight + i + break + case 'center-top': + n += e.offsetHeight / 2 + i + } + return a.anchorPlacement || a.offset || isNaN(t) || (o = t), n + o + } + t.default = a + }, + function (e, t) { + 'use strict' + Object.defineProperty(t, '__esModule', { value: !0 }) + var n = function (e) { + for ( + var t = 0, n = 0; + e && !isNaN(e.offsetLeft) && !isNaN(e.offsetTop); + + ) + (t += e.offsetLeft - ('BODY' != e.tagName ? e.scrollLeft : 0)), + (n += e.offsetTop - ('BODY' != e.tagName ? e.scrollTop : 0)), + (e = e.offsetParent) + return { top: n, left: t } + } + t.default = n + }, + function (e, t) { + 'use strict' + Object.defineProperty(t, '__esModule', { value: !0 }) + var n = function (e) { + return ( + (e = e || document.querySelectorAll('[data-aos]')), + Array.prototype.map.call(e, function (e) { + return { node: e } + }) + ) + } + t.default = n + } + ]) +}) diff --git a/public/js/fireworks.js b/public/js/fireworks.js index 937f9bf3..f1e8b4c9 100644 --- a/public/js/fireworks.js +++ b/public/js/fireworks.js @@ -57,10 +57,10 @@ function createFireworks({ config, anime }) { function updateCoords(e) { pointerX = e.clientX || - (e.touches[0] ? e.touches[0].clientX : e.changedTouches[0].clientX) + (e?.touches[0] ? e.touches[0].clientX : e.changedTouches[0].clientX) pointerY = e.clientY || - (e.touches[0] ? e.touches[0].clientY : e.changedTouches[0].clientY) + (e?.touches[0] ? e.touches[0].clientY : e.changedTouches[0].clientY) } function setParticuleDirection(p) { diff --git a/public/js/sakura.js b/public/js/sakura.js index fcdf0739..ae018338 100644 --- a/public/js/sakura.js +++ b/public/js/sakura.js @@ -123,7 +123,7 @@ function createSakura() { 'style', 'position: fixed;left: 0;top: 0;pointer-events: none;' ) - canvas.setAttribute('id', id) + canvas.setAttribute('id', 'sakura') document.getElementsByTagName('body')[0].appendChild(canvas) cxt = canvas.getContext('2d') var sakuraList = new SakuraList() @@ -185,4 +185,4 @@ function destroySakura() { } window.createSakura = createSakura -window.destroySakura = destroySakura \ No newline at end of file +window.destroySakura = destroySakura diff --git a/public/js/typed.min.js b/public/js/typed.min.js new file mode 100644 index 00000000..52411da8 --- /dev/null +++ b/public/js/typed.min.js @@ -0,0 +1,626 @@ +/*! + * + * typed.js - A JavaScript Typing Animation Library + * Author: Matt Boldt + * Version: v2.0.12 + * Url: https://github.com/mattboldt/typed.js + * License(s): MIT + * + */ +;(function (t, e) { + 'object' == typeof exports && 'object' == typeof module + ? (module.exports = e()) + : 'function' == typeof define && define.amd + ? define([], e) + : 'object' == typeof exports + ? (exports.Typed = e()) + : (t.Typed = e()) +})(this, function () { + return (function (t) { + function e(n) { + if (s[n]) return s[n].exports + var i = (s[n] = { exports: {}, id: n, loaded: !1 }) + return t[n].call(i.exports, i, i.exports, e), (i.loaded = !0), i.exports + } + var s = {} + return (e.m = t), (e.c = s), (e.p = ''), e(0) + })([ + function (t, e, s) { + 'use strict' + function n(t, e) { + if (!(t instanceof e)) + throw new TypeError('Cannot call a class as a function') + } + Object.defineProperty(e, '__esModule', { value: !0 }) + var i = (function () { + function t(t, e) { + for (var s = 0; s < e.length; s++) { + var n = e[s] + ;(n.enumerable = n.enumerable || !1), + (n.configurable = !0), + 'value' in n && (n.writable = !0), + Object.defineProperty(t, n.key, n) + } + } + return function (e, s, n) { + return s && t(e.prototype, s), n && t(e, n), e + } + })(), + r = s(1), + o = s(3), + a = (function () { + function t(e, s) { + n(this, t), r.initializer.load(this, s, e), this.begin() + } + return ( + i(t, [ + { + key: 'toggle', + value: function () { + this.pause.status ? this.start() : this.stop() + } + }, + { + key: 'stop', + value: function () { + this.typingComplete || + this.pause.status || + (this.toggleBlinking(!0), + (this.pause.status = !0), + this.options.onStop(this.arrayPos, this)) + } + }, + { + key: 'start', + value: function () { + this.typingComplete || + (this.pause.status && + ((this.pause.status = !1), + this.pause.typewrite + ? this.typewrite( + this.pause.curString, + this.pause.curStrPos + ) + : this.backspace( + this.pause.curString, + this.pause.curStrPos + ), + this.options.onStart(this.arrayPos, this))) + } + }, + { + key: 'destroy', + value: function () { + this.reset(!1), this.options.onDestroy(this) + } + }, + { + key: 'reset', + value: function () { + var t = + arguments.length <= 0 || + void 0 === arguments[0] || + arguments[0] + clearInterval(this.timeout), + this.replaceText(''), + this.cursor && + this.cursor.parentNode && + (this.cursor.parentNode.removeChild(this.cursor), + (this.cursor = null)), + (this.strPos = 0), + (this.arrayPos = 0), + (this.curLoop = 0), + t && + (this.insertCursor(), + this.options.onReset(this), + this.begin()) + } + }, + { + key: 'begin', + value: function () { + var t = this + this.options.onBegin(this), + (this.typingComplete = !1), + this.shuffleStringsIfNeeded(this), + this.insertCursor(), + this.bindInputFocusEvents && this.bindFocusEvents(), + (this.timeout = setTimeout(function () { + t.currentElContent && 0 !== t.currentElContent.length + ? t.backspace( + t.currentElContent, + t.currentElContent.length + ) + : t.typewrite( + t.strings[t.sequence[t.arrayPos]], + t.strPos + ) + }, this.startDelay)) + } + }, + { + key: 'typewrite', + value: function (t, e) { + var s = this + this.fadeOut && + this.el.classList.contains(this.fadeOutClass) && + (this.el.classList.remove(this.fadeOutClass), + this.cursor && + this.cursor.classList.remove(this.fadeOutClass)) + var n = this.humanizer(this.typeSpeed), + i = 1 + return this.pause.status === !0 + ? void this.setPauseStatus(t, e, !0) + : void (this.timeout = setTimeout(function () { + e = o.htmlParser.typeHtmlChars(t, e, s) + var n = 0, + r = t.substr(e) + if ('^' === r.charAt(0) && /^\^\d+/.test(r)) { + var a = 1 + ;(r = /\d+/.exec(r)[0]), + (a += r.length), + (n = parseInt(r)), + (s.temporaryPause = !0), + s.options.onTypingPaused(s.arrayPos, s), + (t = t.substring(0, e) + t.substring(e + a)), + s.toggleBlinking(!0) + } + if ('`' === r.charAt(0)) { + for ( + ; + '`' !== t.substr(e + i).charAt(0) && + (i++, !(e + i > t.length)); + + ); + var u = t.substring(0, e), + l = t.substring(u.length + 1, e + i), + c = t.substring(e + i + 1) + ;(t = u + l + c), i-- + } + s.timeout = setTimeout(function () { + s.toggleBlinking(!1), + e >= t.length + ? s.doneTyping(t, e) + : s.keepTyping(t, e, i), + s.temporaryPause && + ((s.temporaryPause = !1), + s.options.onTypingResumed(s.arrayPos, s)) + }, n) + }, n)) + } + }, + { + key: 'keepTyping', + value: function (t, e, s) { + 0 === e && + (this.toggleBlinking(!1), + this.options.preStringTyped(this.arrayPos, this)), + (e += s) + var n = t.substr(0, e) + this.replaceText(n), this.typewrite(t, e) + } + }, + { + key: 'doneTyping', + value: function (t, e) { + var s = this + this.options.onStringTyped(this.arrayPos, this), + this.toggleBlinking(!0), + (this.arrayPos === this.strings.length - 1 && + (this.complete(), + this.loop === !1 || this.curLoop === this.loopCount)) || + (this.timeout = setTimeout(function () { + s.backspace(t, e) + }, this.backDelay)) + } + }, + { + key: 'backspace', + value: function (t, e) { + var s = this + if (this.pause.status === !0) + return void this.setPauseStatus(t, e, !1) + if (this.fadeOut) return this.initFadeOut() + this.toggleBlinking(!1) + var n = this.humanizer(this.backSpeed) + this.timeout = setTimeout(function () { + e = o.htmlParser.backSpaceHtmlChars(t, e, s) + var n = t.substr(0, e) + if ((s.replaceText(n), s.smartBackspace)) { + var i = s.strings[s.arrayPos + 1] + i && n === i.substr(0, e) + ? (s.stopNum = e) + : (s.stopNum = 0) + } + e > s.stopNum + ? (e--, s.backspace(t, e)) + : e <= s.stopNum && + (s.arrayPos++, + s.arrayPos === s.strings.length + ? ((s.arrayPos = 0), + s.options.onLastStringBackspaced(), + s.shuffleStringsIfNeeded(), + s.begin()) + : s.typewrite(s.strings[s.sequence[s.arrayPos]], e)) + }, n) + } + }, + { + key: 'complete', + value: function () { + this.options.onComplete(this), + this.loop ? this.curLoop++ : (this.typingComplete = !0) + } + }, + { + key: 'setPauseStatus', + value: function (t, e, s) { + ;(this.pause.typewrite = s), + (this.pause.curString = t), + (this.pause.curStrPos = e) + } + }, + { + key: 'toggleBlinking', + value: function (t) { + this.cursor && + (this.pause.status || + (this.cursorBlinking !== t && + ((this.cursorBlinking = t), + t + ? this.cursor.classList.add('typed-cursor--blink') + : this.cursor.classList.remove( + 'typed-cursor--blink' + )))) + } + }, + { + key: 'humanizer', + value: function (t) { + return Math.round((Math.random() * t) / 2) + t + } + }, + { + key: 'shuffleStringsIfNeeded', + value: function () { + this.shuffle && + (this.sequence = this.sequence.sort(function () { + return Math.random() - 0.5 + })) + } + }, + { + key: 'initFadeOut', + value: function () { + var t = this + return ( + (this.el.className += ' ' + this.fadeOutClass), + this.cursor && + (this.cursor.className += ' ' + this.fadeOutClass), + setTimeout(function () { + t.arrayPos++, + t.replaceText(''), + t.strings.length > t.arrayPos + ? t.typewrite(t.strings[t.sequence[t.arrayPos]], 0) + : (t.typewrite(t.strings[0], 0), (t.arrayPos = 0)) + }, this.fadeOutDelay) + ) + } + }, + { + key: 'replaceText', + value: function (t) { + this.attr + ? this.el.setAttribute(this.attr, t) + : this.isInput + ? (this.el.value = t) + : 'html' === this.contentType + ? (this.el.innerHTML = t) + : (this.el.textContent = t) + } + }, + { + key: 'bindFocusEvents', + value: function () { + var t = this + this.isInput && + (this.el.addEventListener('focus', function (e) { + t.stop() + }), + this.el.addEventListener('blur', function (e) { + ;(t.el.value && 0 !== t.el.value.length) || t.start() + })) + } + }, + { + key: 'insertCursor', + value: function () { + this.showCursor && + (this.cursor || + ((this.cursor = document.createElement('span')), + (this.cursor.className = 'typed-cursor'), + this.cursor.setAttribute('aria-hidden', !0), + (this.cursor.innerHTML = this.cursorChar), + this.el.parentNode && + this.el.parentNode.insertBefore( + this.cursor, + this.el.nextSibling + ))) + } + } + ]), + t + ) + })() + ;(e['default'] = a), (t.exports = e['default']) + }, + function (t, e, s) { + 'use strict' + function n(t) { + return t && t.__esModule ? t : { default: t } + } + function i(t, e) { + if (!(t instanceof e)) + throw new TypeError('Cannot call a class as a function') + } + Object.defineProperty(e, '__esModule', { value: !0 }) + var r = + Object.assign || + function (t) { + for (var e = 1; e < arguments.length; e++) { + var s = arguments[e] + for (var n in s) + Object.prototype.hasOwnProperty.call(s, n) && (t[n] = s[n]) + } + return t + }, + o = (function () { + function t(t, e) { + for (var s = 0; s < e.length; s++) { + var n = e[s] + ;(n.enumerable = n.enumerable || !1), + (n.configurable = !0), + 'value' in n && (n.writable = !0), + Object.defineProperty(t, n.key, n) + } + } + return function (e, s, n) { + return s && t(e.prototype, s), n && t(e, n), e + } + })(), + a = s(2), + u = n(a), + l = (function () { + function t() { + i(this, t) + } + return ( + o(t, [ + { + key: 'load', + value: function (t, e, s) { + if ( + ('string' == typeof s + ? (t.el = document.querySelector(s)) + : (t.el = s), + (t.options = r({}, u['default'], e)), + (t.isInput = 'input' === t.el.tagName.toLowerCase()), + (t.attr = t.options.attr), + (t.bindInputFocusEvents = t.options.bindInputFocusEvents), + (t.showCursor = !t.isInput && t.options.showCursor), + (t.cursorChar = t.options.cursorChar), + (t.cursorBlinking = !0), + (t.elContent = t.attr + ? t.el.getAttribute(t.attr) + : t.el.textContent), + (t.contentType = t.options.contentType), + (t.typeSpeed = t.options.typeSpeed), + (t.startDelay = t.options.startDelay), + (t.backSpeed = t.options.backSpeed), + (t.smartBackspace = t.options.smartBackspace), + (t.backDelay = t.options.backDelay), + (t.fadeOut = t.options.fadeOut), + (t.fadeOutClass = t.options.fadeOutClass), + (t.fadeOutDelay = t.options.fadeOutDelay), + (t.isPaused = !1), + (t.strings = t.options.strings.map(function (t) { + return t.trim() + })), + 'string' == typeof t.options.stringsElement + ? (t.stringsElement = document.querySelector( + t.options.stringsElement + )) + : (t.stringsElement = t.options.stringsElement), + t.stringsElement) + ) { + ;(t.strings = []), (t.stringsElement.style.display = 'none') + var n = Array.prototype.slice.apply( + t.stringsElement.children + ), + i = n.length + if (i) + for (var o = 0; o < i; o += 1) { + var a = n[o] + t.strings.push(a.innerHTML.trim()) + } + } + ;(t.strPos = 0), + (t.arrayPos = 0), + (t.stopNum = 0), + (t.loop = t.options.loop), + (t.loopCount = t.options.loopCount), + (t.curLoop = 0), + (t.shuffle = t.options.shuffle), + (t.sequence = []), + (t.pause = { + status: !1, + typewrite: !0, + curString: '', + curStrPos: 0 + }), + (t.typingComplete = !1) + for (var o in t.strings) t.sequence[o] = o + ;(t.currentElContent = this.getCurrentElContent(t)), + (t.autoInsertCss = t.options.autoInsertCss), + this.appendAnimationCss(t) + } + }, + { + key: 'getCurrentElContent', + value: function (t) { + var e = '' + return (e = t.attr + ? t.el.getAttribute(t.attr) + : t.isInput + ? t.el.value + : 'html' === t.contentType + ? t.el.innerHTML + : t.el.textContent) + } + }, + { + key: 'appendAnimationCss', + value: function (t) { + var e = 'data-typed-js-css' + if ( + t.autoInsertCss && + (t.showCursor || t.fadeOut) && + !document.querySelector('[' + e + ']') + ) { + var s = document.createElement('style') + ;(s.type = 'text/css'), s.setAttribute(e, !0) + var n = '' + t.showCursor && + (n += + '\n .typed-cursor{\n opacity: 1;\n }\n .typed-cursor.typed-cursor--blink{\n animation: typedjsBlink 0.7s infinite;\n -webkit-animation: typedjsBlink 0.7s infinite;\n animation: typedjsBlink 0.7s infinite;\n }\n @keyframes typedjsBlink{\n 50% { opacity: 0.0; }\n }\n @-webkit-keyframes typedjsBlink{\n 0% { opacity: 1; }\n 50% { opacity: 0.0; }\n 100% { opacity: 1; }\n }\n '), + t.fadeOut && + (n += + '\n .typed-fade-out{\n opacity: 0;\n transition: opacity .25s;\n }\n .typed-cursor.typed-cursor--blink.typed-fade-out{\n -webkit-animation: 0;\n animation: 0;\n }\n '), + 0 !== s.length && + ((s.innerHTML = n), document.body.appendChild(s)) + } + } + } + ]), + t + ) + })() + e['default'] = l + var c = new l() + e.initializer = c + }, + function (t, e) { + 'use strict' + Object.defineProperty(e, '__esModule', { value: !0 }) + var s = { + strings: [ + 'These are the default values...', + 'You know what you should do?', + 'Use your own!', + 'Have a great day!' + ], + stringsElement: null, + typeSpeed: 0, + startDelay: 0, + backSpeed: 0, + smartBackspace: !0, + shuffle: !1, + backDelay: 700, + fadeOut: !1, + fadeOutClass: 'typed-fade-out', + fadeOutDelay: 500, + loop: !1, + loopCount: 1 / 0, + showCursor: !0, + cursorChar: '|', + autoInsertCss: !0, + attr: null, + bindInputFocusEvents: !1, + contentType: 'html', + onBegin: function (t) {}, + onComplete: function (t) {}, + preStringTyped: function (t, e) {}, + onStringTyped: function (t, e) {}, + onLastStringBackspaced: function (t) {}, + onTypingPaused: function (t, e) {}, + onTypingResumed: function (t, e) {}, + onReset: function (t) {}, + onStop: function (t, e) {}, + onStart: function (t, e) {}, + onDestroy: function (t) {} + } + ;(e['default'] = s), (t.exports = e['default']) + }, + function (t, e) { + 'use strict' + function s(t, e) { + if (!(t instanceof e)) + throw new TypeError('Cannot call a class as a function') + } + Object.defineProperty(e, '__esModule', { value: !0 }) + var n = (function () { + function t(t, e) { + for (var s = 0; s < e.length; s++) { + var n = e[s] + ;(n.enumerable = n.enumerable || !1), + (n.configurable = !0), + 'value' in n && (n.writable = !0), + Object.defineProperty(t, n.key, n) + } + } + return function (e, s, n) { + return s && t(e.prototype, s), n && t(e, n), e + } + })(), + i = (function () { + function t() { + s(this, t) + } + return ( + n(t, [ + { + key: 'typeHtmlChars', + value: function (t, e, s) { + if ('html' !== s.contentType) return e + var n = t.substr(e).charAt(0) + if ('<' === n || '&' === n) { + var i = '' + for ( + i = '<' === n ? '>' : ';'; + t.substr(e + 1).charAt(0) !== i && + (e++, !(e + 1 > t.length)); + + ); + e++ + } + return e + } + }, + { + key: 'backSpaceHtmlChars', + value: function (t, e, s) { + if ('html' !== s.contentType) return e + var n = t.substr(e).charAt(0) + if ('>' === n || ';' === n) { + var i = '' + for ( + i = '>' === n ? '<' : '&'; + t.substr(e - 1).charAt(0) !== i && (e--, !(e < 0)); + + ); + e-- + } + return e + } + } + ]), + t + ) + })() + e['default'] = i + var r = new i() + e.htmlParser = r + } + ]) +}) +//# sourceMappingURL=typed.min.js.map diff --git a/public/svg/xiaohongshu.svg b/public/svg/xiaohongshu.svg new file mode 100644 index 00000000..3a5e4222 --- /dev/null +++ b/public/svg/xiaohongshu.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/svg/zhishixingqiu.svg b/public/svg/zhishixingqiu.svg new file mode 100644 index 00000000..77293e8f --- /dev/null +++ b/public/svg/zhishixingqiu.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/styles/globals.css b/styles/globals.css index 2b8f568f..bdc13fe9 100644 --- a/styles/globals.css +++ b/styles/globals.css @@ -264,3 +264,11 @@ a.avatar-wrapper { writing-mode: vertical-rl; /* 竖向排列从右向左 */ text-orientation: upright; /* 文字方向正常 */ } + +/* Chatbase 在移动端禁止遮挡 */ +@media (max-width: 700px) { + button#chatbase-bubble-button { + margin-bottom: 42px; + margin-right: 20px; + } +} diff --git a/styles/notion.css b/styles/notion.css index 0991011e..ba794f88 100644 --- a/styles/notion.css +++ b/styles/notion.css @@ -523,8 +523,10 @@ summary > .notion-h { .notion-page { /* width: var(--notion-max-width); */ width: 100% !important; - padding-left: calc(min(12px, 8vw)); - padding-right: calc(min(12px, 8vw)); + padding-left: 0px !important; + padding-right: 0px !important; + /* padding-left: calc(min(12px, 8vw)); */ + /* padding-right: calc(min(12px, 8vw)); */ } .notion-full-width { @@ -1449,6 +1451,7 @@ code[class*='language-'] { .notion-collection-card-property .notion-page-title-text { border-bottom: 0 none; + @apply dark:text-gray-200; } .notion-collection-card-property diff --git a/themes/commerce/components/BlogPostListPage.js b/themes/commerce/components/BlogPostListPage.js index d762e083..4c3ba5ac 100644 --- a/themes/commerce/components/BlogPostListPage.js +++ b/themes/commerce/components/BlogPostListPage.js @@ -14,7 +14,7 @@ import ProductCard from './ProductCard' */ const BlogPostListPage = ({ page = 1, posts = [], postCount, siteInfo }) => { const { NOTION_CONFIG } = useGlobal() - const POSTS_PER_PAGE = siteConfig('POSTS_PER_PAGE', 12, NOTION_CONFIG) + const POSTS_PER_PAGE = siteConfig('POSTS_PER_PAGE', null, NOTION_CONFIG) const totalPage = Math.ceil(postCount / POSTS_PER_PAGE) const showPagination = postCount >= POSTS_PER_PAGE if (!posts || posts.length === 0 || page > totalPage) { diff --git a/themes/commerce/components/BlogPostListScroll.js b/themes/commerce/components/BlogPostListScroll.js index 004e2b5e..a00b8cc3 100644 --- a/themes/commerce/components/BlogPostListScroll.js +++ b/themes/commerce/components/BlogPostListScroll.js @@ -20,7 +20,7 @@ const BlogPostListScroll = ({ siteInfo }) => { const { NOTION_CONFIG } = useGlobal() - const POSTS_PER_PAGE = siteConfig('POSTS_PER_PAGE', 12, NOTION_CONFIG) + const POSTS_PER_PAGE = siteConfig('POSTS_PER_PAGE', null, NOTION_CONFIG) const [page, updatePage] = useState(1) const postsToShow = getListByPage(posts, page, POSTS_PER_PAGE) diff --git a/themes/commerce/components/SocialButton.js b/themes/commerce/components/SocialButton.js index 555e8ea6..24823924 100644 --- a/themes/commerce/components/SocialButton.js +++ b/themes/commerce/components/SocialButton.js @@ -5,39 +5,101 @@ import { siteConfig } from '@/lib/config' * @constructor */ const SocialButton = () => { - return
-
- {siteConfig('CONTACT_GITHUB') && - - } - {siteConfig('CONTACT_TWITTER') && - - } - {siteConfig('CONTACT_TELEGRAM') && - - } - {siteConfig('CONTACT_LINKEDIN') && - - } - {siteConfig('CONTACT_WEIBO') && - - } - {siteConfig('CONTACT_INSTAGRAM') && - - } - {siteConfig('CONTACT_EMAIL') && - - } - {JSON.parse(siteConfig('ENABLE_RSS')) && - - } - {siteConfig('CONTACT_BILIBILI') && - - } - {siteConfig('CONTACT_YOUTUBE') && - - } + return ( +
+
+ {siteConfig('CONTACT_GITHUB') && ( + + + + )} + {siteConfig('CONTACT_TWITTER') && ( + + + + )} + {siteConfig('CONTACT_TELEGRAM') && ( + + + + )} + {siteConfig('CONTACT_LINKEDIN') && ( + + + + )} + {siteConfig('CONTACT_WEIBO') && ( + + + + )} + {siteConfig('CONTACT_INSTAGRAM') && ( + + + + )} + {siteConfig('CONTACT_EMAIL') && ( + + + + )} + {JSON.parse(siteConfig('ENABLE_RSS')) && ( + + + + )} + {siteConfig('CONTACT_BILIBILI') && ( + + + + )} + {siteConfig('CONTACT_YOUTUBE') && ( + + + + )} +
-
+ ) } export default SocialButton diff --git a/themes/example/components/BlogListPage.js b/themes/example/components/BlogListPage.js index f9ae29a4..fe56e0bf 100644 --- a/themes/example/components/BlogListPage.js +++ b/themes/example/components/BlogListPage.js @@ -14,7 +14,7 @@ export const BlogListPage = props => { const { locale, NOTION_CONFIG } = useGlobal() const router = useRouter() const totalPage = Math.ceil( - postCount / siteConfig('POSTS_PER_PAGE', 12, NOTION_CONFIG) + postCount / siteConfig('POSTS_PER_PAGE', null, NOTION_CONFIG) ) const currentPage = +page @@ -24,6 +24,7 @@ export const BlogListPage = props => { .split('?')[0] .replace(/\/page\/[1-9]\d*/, '') .replace(/\/$/, '') + .replace('.html', '') const showPageCover = siteConfig('EXAMPLE_POST_LIST_COVER', null, CONFIG) @@ -44,7 +45,7 @@ export const BlogListPage = props => { : `${pagePrefix}/page/${currentPage - 1}`, query: router.query.s ? { s: router.query.s } : {} }} - className={`${showPrev ? 'bg-black ' : 'bg-gray pointer-events-none '} text-white no-underline py-2 px-3 rounded`}> + className={`${showPrev ? 'bg-black dark:bg-hexo-black-gray' : 'bg-gray pointer-events-none invisible'} text-white no-underline py-2 px-3 rounded`}> {locale.PAGINATION.PREV} { pathname: `${pagePrefix}/page/${currentPage + 1}`, query: router.query.s ? { s: router.query.s } : {} }} - className={`${showNext ? 'bg-black ' : 'bg-gray pointer-events-none '} text-white no-underline py-2 px-3 rounded`}> + className={`${showNext ? 'bg-black dark:bg-hexo-black-gray ' : 'bg-gray pointer-events-none invisible'} text-white no-underline py-2 px-3 rounded`}> {locale.PAGINATION.NEXT}
diff --git a/themes/example/components/BlogListScroll.js b/themes/example/components/BlogListScroll.js index 3ddc340a..d712b919 100644 --- a/themes/example/components/BlogListScroll.js +++ b/themes/example/components/BlogListScroll.js @@ -13,7 +13,7 @@ export const BlogListScroll = props => { const { posts } = props const { locale, NOTION_CONFIG } = useGlobal() const [page, updatePage] = useState(1) - const POSTS_PER_PAGE = siteConfig('POSTS_PER_PAGE', 12, NOTION_CONFIG) + const POSTS_PER_PAGE = siteConfig('POSTS_PER_PAGE', null, NOTION_CONFIG) let hasMore = false const postsToShow = posts diff --git a/themes/example/components/Catalog.js b/themes/example/components/Catalog.js new file mode 100644 index 00000000..161a6cae --- /dev/null +++ b/themes/example/components/Catalog.js @@ -0,0 +1,94 @@ +import throttle from 'lodash.throttle' +import { uuidToId } from 'notion-utils' +import { useCallback, useEffect, useRef, useState } from 'react' + +/** + * 目录导航组件 + * @param toc + * @returns {JSX.Element} + * @constructor + */ +const Catalog = ({ toc }) => { + // 监听滚动事件 + useEffect(() => { + window.addEventListener('scroll', actionSectionScrollSpy) + actionSectionScrollSpy() + return () => { + window.removeEventListener('scroll', actionSectionScrollSpy) + } + }, []) + + // 目录自动滚动 + const tRef = useRef(null) + const tocIds = [] + + // 同步选中目录事件 + const [activeSection, setActiveSection] = useState(null) + const throttleMs = 200 + const actionSectionScrollSpy = useCallback( + throttle(() => { + const sections = document.getElementsByClassName('notion-h') + let prevBBox = null + let currentSectionId = activeSection + for (let i = 0; i < sections.length; ++i) { + const section = sections[i] + if (!section || !(section instanceof Element)) continue + if (!currentSectionId) { + currentSectionId = section.getAttribute('data-id') + } + const bbox = section.getBoundingClientRect() + const prevHeight = prevBBox ? bbox.top - prevBBox.bottom : 0 + const offset = Math.max(150, prevHeight / 4) + // GetBoundingClientRect returns values relative to viewport + if (bbox.top - offset < 0) { + currentSectionId = section.getAttribute('data-id') + prevBBox = bbox + continue + } + // No need to continue loop, if last element has been detected + break + } + setActiveSection(currentSectionId) + const index = tocIds.indexOf(currentSectionId) || 0 + tRef?.current?.scrollTo({ top: 28 * index, behavior: 'smooth' }) + }, throttleMs) + ) + + // 无目录就直接返回空 + if (!toc || toc.length < 1) { + return <> + } + + return ( +
+
+ +
+
+ ) +} + +export default Catalog diff --git a/themes/example/components/Header.js b/themes/example/components/Header.js index 5331eed2..ca60bf63 100644 --- a/themes/example/components/Header.js +++ b/themes/example/components/Header.js @@ -4,12 +4,13 @@ import { MenuList } from './MenuList' /** * 网站顶部 + * LOGO 和 菜单 * @returns */ export const Header = props => { return (
-
+
diff --git a/themes/example/components/MenuList.js b/themes/example/components/MenuList.js index 90b00c7e..ef0c122a 100644 --- a/themes/example/components/MenuList.js +++ b/themes/example/components/MenuList.js @@ -58,7 +58,7 @@ export const MenuList = props => { return (
+ ) } diff --git a/themes/example/components/TitleBar.js b/themes/example/components/TitleBar.js new file mode 100644 index 00000000..add6901a --- /dev/null +++ b/themes/example/components/TitleBar.js @@ -0,0 +1,48 @@ +import NotionIcon from '@/components/NotionIcon' +import { siteConfig } from '@/lib/config' +import { useGlobal } from '@/lib/global' +import CONFIG from '../config' + +/** + * 标题栏 + */ +export default function TitleBar(props) { + const { post } = props + const { fullWidth, siteInfo } = useGlobal() + + const title = post?.title || siteConfig('TITLE') + const description = post?.description || siteConfig('AUTHOR') + const headerImage = post?.pageCoverThumbnail + ? post.pageCoverThumbnail + : siteInfo?.pageCover + + const TITLE_BG = siteConfig('EXAMPLE_TITLE_IMAGE', false, CONFIG) + + return ( + <> + {/* 标题栏 */} + {!fullWidth && ( +
+

+ {siteConfig('POST_TITLE_ICON') && ( + + )} + {title} +

+

+ {description} +

+ {TITLE_BG && ( + <> + {/* eslint-disable-next-line @next/next/no-img-element */} + + + )} +
+ )} + + ) +} diff --git a/themes/example/config.js b/themes/example/config.js index 181d3a93..b1c0af1f 100644 --- a/themes/example/config.js +++ b/themes/example/config.js @@ -8,7 +8,12 @@ const CONFIG = { EXAMPLE_MENU_ARCHIVE: true, // 显示归档 EXAMPLE_MENU_SEARCH: true, // 显示搜索 - EXAMPLE_POST_LIST_COVER: true // 列表显示文章封面 + EXAMPLE_POST_LIST_COVER: true, // 列表显示文章封面 + EXAMPLE_TITLE_IMAGE: false, // 标题栏,是否背景图片 + + // 文章页面布局 + EXAMPLE_ARTICLE_LAYOUT_VERTICAL: false, // 文章详情,左右布局改为上下布局 + EXAMPLE_ARTICLE_HIDDEN_NOTIFICATION: false // 文章详情隐藏公告 } export default CONFIG diff --git a/themes/example/index.js b/themes/example/index.js index b1893935..90580a24 100644 --- a/themes/example/index.js +++ b/themes/example/index.js @@ -2,7 +2,6 @@ import Comment from '@/components/Comment' import replaceSearchResult from '@/components/Mark' -import NotionIcon from '@/components/NotionIcon' import NotionPage from '@/components/NotionPage' import ShareBar from '@/components/ShareBar' import { siteConfig } from '@/lib/config' @@ -21,6 +20,7 @@ import { PostLock } from './components/PostLock' import { PostMeta } from './components/PostMeta' import SearchInput from './components/SearchInput' import { SideBar } from './components/SideBar' +import TitleBar from './components/TitleBar' import CONFIG from './config' import { Style } from './style' @@ -32,36 +32,15 @@ import { Style } from './style' * @constructor */ const LayoutBase = props => { - const { children } = props + const { children, post } = props const { onLoading, fullWidth, locale } = useGlobal() - const router = useRouter() - const { post, category, tag } = props - const title = post?.title || siteConfig('TITLE') - const description = post?.description || siteConfig('AUTHOR') + // 文章详情页左右布局改为上下布局 + const LAYOUT_VERTICAL = + post && siteConfig('EXAMPLE_ARTICLE_LAYOUT_VERTICAL', false, CONFIG) - // 顶部如果是按照分类或标签查看文章列表,列表顶部嵌入一个横幅 - // 如果是搜索,则列表顶部嵌入 搜索框 - let slotTop = null - if (category) { - slotTop = ( -
- - {category} -
- ) - } else if (tag) { - slotTop =
#{tag}
- } else if (props.slotTop) { - slotTop = props.slotTop - } else if (router.route === '/search') { - // 嵌入一个搜索框在顶部 - slotTop = ( -
- -
- ) - } + // 网站左右布局颠倒 + const LAYOUT_SIDEBAR_REVERSE = siteConfig('LAYOUT_SIDEBAR_REVERSE', false) return (
{ {/* 页头 */}
+ {/* 标题栏 */} + {/* 主体 */}
- {/* 标题栏 */} - {!fullWidth && ( -
-

- {siteConfig('POST_TITLE_ICON') && ( - - )} - {title} -

-

{description}

-
- )} -
+ className={`relative mx-auto justify-center md:flex py-8 px-2 + ${LAYOUT_SIDEBAR_REVERSE ? 'flex-row-reverse' : ''} + ${LAYOUT_VERTICAL ? 'items-center flex-col' : 'items-start'} + `}> {/* 内容 */}
+ className={`${fullWidth ? '' : LAYOUT_VERTICAL ? 'max-w-5xl' : 'max-w-3xl'} w-full xl:px-14 lg:px-4`}> { leaveTo='opacity-0 -translate-y-16' unmount={false}> {/* 嵌入模块 */} - {slotTop} + {props.slotTop} {children}
{/* 侧边栏 */} - {!fullWidth && } + {!fullWidth && ( +
+ +
+ )}
@@ -150,8 +125,20 @@ const LayoutIndex = props => { * @returns */ const LayoutPostList = props => { + const { category, tag } = props + return ( <> + {/* 显示分类 */} + {category && ( +
+ + {category} +
+ )} + {/* 显示标签 */} + {tag &&
#{tag}
} + {siteConfig('POST_LIST_STYLE') === 'page' ? ( ) : ( @@ -192,7 +179,7 @@ const LayoutSlug = props => { {lock ? ( ) : ( -
+
@@ -237,7 +224,14 @@ const LayoutSearch = props => { } }, [router]) - return + return ( + <> +
+ +
+ + + ) } /** diff --git a/themes/fukasawa/components/AsideLeft.js b/themes/fukasawa/components/AsideLeft.js index f534d1b6..0fe42a91 100644 --- a/themes/fukasawa/components/AsideLeft.js +++ b/themes/fukasawa/components/AsideLeft.js @@ -1,22 +1,22 @@ -import Logo from './Logo' -import GroupCategory from './GroupCategory' -import { MenuList } from './MenuList' -import GroupTag from './GroupTag' -import SearchInput from './SearchInput' -import SiteInfo from './SiteInfo' -import Catalog from './Catalog' -import Announcement from './Announcement' -import { useRouter } from 'next/router' import DarkModeButton from '@/components/DarkModeButton' -import SocialButton from './SocialButton' -import CONFIG from '@/themes/fukasawa/config' import { AdSlot } from '@/components/GoogleAdsense' import { siteConfig } from '@/lib/config' -import MailChimpForm from './MailChimpForm' import { useGlobal } from '@/lib/global' -import { useEffect, useMemo, useState } from 'react' import { isBrowser } from '@/lib/utils' +import CONFIG from '@/themes/fukasawa/config' import { debounce } from 'lodash' +import { useRouter } from 'next/router' +import { useEffect, useMemo, useState } from 'react' +import Announcement from './Announcement' +import Catalog from './Catalog' +import GroupCategory from './GroupCategory' +import GroupTag from './GroupTag' +import Logo from './Logo' +import MailChimpForm from './MailChimpForm' +import { MenuList } from './MenuList' +import SearchInput from './SearchInput' +import SiteInfo from './SiteInfo' +import SocialButton from './SocialButton' /** * 侧边栏 @@ -24,16 +24,41 @@ import { debounce } from 'lodash' * @returns */ function AsideLeft(props) { - const { tagOptions, currentTag, categoryOptions, currentCategory, post, slot, notice } = props + const { + tagOptions, + currentTag, + categoryOptions, + currentCategory, + post, + slot, + notice + } = props const router = useRouter() const { fullWidth } = useGlobal() - const FUKASAWA_SIDEBAR_COLLAPSE_SATUS_DEFAULT = fullWidth || siteConfig('FUKASAWA_SIDEBAR_COLLAPSE_SATUS_DEFAULT', null, CONFIG) + const FUKASAWA_SIDEBAR_COLLAPSE_SATUS_DEFAULT = + fullWidth || + siteConfig('FUKASAWA_SIDEBAR_COLLAPSE_SATUS_DEFAULT', null, CONFIG) + + const FUKASAWA_SIDEBAR_COLLAPSE_ON_SCROLL = siteConfig( + 'FUKASAWA_SIDEBAR_COLLAPSE_ON_SCROLL', + false, + CONFIG + ) + + const FUKASAWA_SIDEBAR_COLLAPSE_BUTTON = siteConfig( + 'FUKASAWA_SIDEBAR_COLLAPSE_BUTTON', + null, + CONFIG + ) // 侧边栏折叠从 本地存储中获取 open 状态的初始值 const [isCollapsed, setIsCollapse] = useState(() => { if (typeof window !== 'undefined') { - return localStorage.getItem('fukasawa-sidebar-collapse') === 'true' || FUKASAWA_SIDEBAR_COLLAPSE_SATUS_DEFAULT + return ( + localStorage.getItem('fukasawa-sidebar-collapse') === 'true' || + FUKASAWA_SIDEBAR_COLLAPSE_SATUS_DEFAULT + ) } return FUKASAWA_SIDEBAR_COLLAPSE_SATUS_DEFAULT }) @@ -45,8 +70,8 @@ function AsideLeft(props) { } }, [isCollapsed]) + const isReverse = siteConfig('LAYOUT_SIDEBAR_REVERSE') const position = useMemo(() => { - const isReverse = JSON.parse(siteConfig('LAYOUT_SIDEBAR_REVERSE')) if (isCollapsed) { if (isReverse) { return 'right-2' @@ -69,7 +94,7 @@ function AsideLeft(props) { // 自动折叠侧边栏 onResize 窗口宽度小于1366 || 滚动条滚动至页面的300px时 ; 将open设置为false useEffect(() => { - if (!siteConfig('FUKASAWA_SIDEBAR_COLLAPSE_ON_SCROLL', false, CONFIG)) { + if (!FUKASAWA_SIDEBAR_COLLAPSE_ON_SCROLL) { return } const handleResize = debounce(() => { @@ -92,73 +117,89 @@ function AsideLeft(props) { } } }, []) - - - return